GithubHelp home page GithubHelp logo

centau / vide Goto Github PK

View Code? Open in Web Editor NEW
48.0 4.0 6.0 273 KB

A reactive Luau library for creating UI.

Home Page: https://centau.github.io/vide/

License: MIT License

Lua 100.00%
declarative luau reactive roblox ui

vide's Introduction


Vide is a reactive Luau UI library inspired by Solid.

  • Fully Luau typecheckable
  • Declarative and concise syntax.
  • Reactively driven.

Getting started

Read the crash course for a quick introduction to the library.

Code sample

local create = vide.create
local source = vide.source

local function Counter()
    local count = source(0)

    return create "TextButton" {
        Text = function()
            return "count: " .. count()
        end,

        Activated = function()
            count(count() + 1)
        end
    }
end

vide's People

Contributors

aloroid avatar centau avatar littensy avatar quantix-dev avatar returnedtrue avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

vide's Issues

`show()`/`switch()` not parenting created instances

Using the below test script in the studio command bar:

local vide = require(workspace.vide)

local create = vide.create
local read = vide.read
local show = vide.show
local source = vide.source

local function input(_props: { Readonly: () -> boolean })
    local readonly = function()
        print("readonly updated")
        return read(_props.Readonly or false)
    end

    local function ReadonlyInput()
        print("create readonly input")
        return create("TextLabel") {
            BackgroundColor3 = Color3.fromRGB(255, 0, 0),
            Size = UDim2.fromScale(1, 1),
        }
    end

    local function Input()
        print("create input")
        return create("TextBox") {
            BackgroundColor3 = Color3.fromRGB(0, 255, 0),
            Size = UDim2.fromScale(1, 1),
            Text = "Input Box",
        }
    end

    return create("Frame") {
        BackgroundTransparency = 1,
        ClipsDescendants = false,
        Size = UDim2.fromScale(1, 0.5),
        show(readonly, ReadonlyInput, Input),
    }
end

vide.mount(function()
    local readonly = source(false)

    -- update source after 3 seconds
    task.delay(3, function()
        print("update readonly")
        readonly(true)
    end)

    return input({
        Readonly = readonly
    })
end, game.StarterGui)

I noted that, while it was correctly updating show() and creating the components (detected via their prints) it was not properly parenting them in the instance tree.

image
Above is a screenshot ofthe generated instances, as you can see only Frame (the top-level component) was being generated whilst neither the TextBox or TextLabel are visible.

See the output of the prints below,
image

Dynamically rendered 'component sources'

Currently, to render a component conditionally, you need to create and destroy it from a watch() call, which can get tedious.

To make it easy to dynamically render content, Solid provides the <Dynamic> component. Vide can provide a similar API where you'd pass a derived source that returns a component:

Dynamic {
    component = function()
        return options[selected()]
    end,
    -- ...props
}

I don't think it's necessary to make it a component, considering that Vide doesn't seem to provide built-in components yet. An API more similar to this would be nice as well:

dynamic(function()
    local Option = options[selected()]

    return Option {
        -- ...props
    }
end)

APIs to unmount components with nested children

Because Vide's nested props allow you to nest children as well, I often declare multiple child elements in components. However, it's difficult to work with the underlying instances if I ever need to change their parent or delete them.

Currently, I delete nested children by applying them to a dummy folder and then destroying the folder:

local function Component()
    return { Foo(), Bar(), Baz() }
end

local function unmount(instances: { any })
    local container = create "Folder" { instances }
    container:Destroy()
end
-- cleaning up in lists
values(list, function()
    local children = Component()

    cleanup(function()
        unmount(children)
    end)

    return children
end)
-- cleaning up in stories
return function(target: Instance)
    local children = Component()

    apply(target)(children)

    return function()
        unmount(children)
    end
end

This works, but I would love it if Vide included APIs to help manage these types of components.

Other alternatives like using folders can behave weirdly with UI, and using invisible frames isn't always the same as directly parenting multiple children.

Spring when underdamped results in weird animations

When damping_ratio is adjusted and set to 0.5, this results in weird positional values as shown below in the video:

RobloxStudioBeta_HnQP14JCEX.mp4

Note: This does not happen when the spring is critically damped.

Code:

local Positional = Source(UDim2.fromScale(0, 0))

local Frame = Create "Frame" {
	Layout = {
		AnchorPoint = Vector2.new(0.5, 0.5),
		Size = UDim2.fromScale(0.1, 0.1),
		Position = Spring(Positional, 5, 0.5),
	},

	Parent = MainInterface
}

while (task.wait(6)) do
	local xRand = math.random(2, 8) / 10
	local yRand = math.random(2, 8) / 10

	Positional(UDim2.fromScale(xRand, yRand))
end

Add function `update`

Adding the function listed in the title would be beneficial for development processes with vide.

update(instance: Instance)

Updates an existing instance by applying the properties and states passed.

Type

type Properties = Map<string|number, any>

function update(instance: Instance): (Properties) -> nil

Details

  • It follows the same rules as creating a property but it simply just updates an existing instance passed with those properties.

Example

local frame = script.Parent
print(frame.Name) --> "Frame"

update(frame) {
    Name = "SomeFrame"
}

print(frame.Name) --> "SomeFrame"

Implement values and indexes

Implementation of values and indexes transform functions for state tables. @centau defined its use and functionality as related to the transformation and creation of new states.

local names = source({ "Eli", "Bobby" })

local frames = values(names , function(i, name)
    --> 'i' (index/key) remains constant and will not be transformed when values are returned
    return create("Frame") { Name = name }
end)

--> Results:
--> frames = { [1] = Eli (Instance), [2] = Bobby (Instance) }

local numbers = source({ 1, 2, 3, 4, 5 })

local multiples = indexes(numbers , function(i, v)
    --> 'v' (value) remains constant and will not be transformed when values are returned
    return tostring(i)
end)

--> Results:
--> multiples = { ["1"] = 1, ["2"] = 2, ["3"] = 3, ["4"] = 4 }

Add features to normalize sources and values

There are often cases where props may be either a source or a raw value. Being able to unwrap a T | Source<T> value into T would help allow developers to accept both static and reactive values in components and composables. The functionality would be similar to unref() from Vue.

local function composable(value: string | Source<string>)
    watch(function()
        print(unwrap(value)) -- Hello, world!
    end)
end

Vue also allows you to pass a ref to ref(), and it will be returned as-is. Being able to normalize a T | Source<T> value to Source<T> would go hand-in-hand with the above proposal.

local function composable(value: string | Source<string>)
    local normalized = source(value)
    
    watch(function()
        print(normalized()) -- Hello, world!
    end)
end

Cannot create effect in non-reactive scope.

I get this warning or error whenever I try and use **effect** in my return function for my component.

return function (props: {
    Title: string,
    Open: boolean,

    Position: UDim2,
})
    local panel
    local size = source(35)

    effect(function()
        if not panel then return end

        panel.Size = UDim2.fromOffset(250, size())
    end, size)

    panel = create "Frame" { ... }

Allow `cleanup` to receive `RBXScriptConnection`, `Instance` and tables for cleanup

Currently, cleanup is limited to just calling functions. By allowing it to receive a RBXScriptConnection, Instance and tables it makes it a lot easier to create connections and objects and have them be cleaned up when the scope is destroyed.

For RBXScriptConnections, we can just call :Disconnect.
For Instances, we can just call :Destroy()
For tables, we can look for a method called Destroy and destroy and if that does not exist, Disconnect anddisconnect`

Add `indexes`/`values` API for dynamic keys and values

There are some cases where you want to render a list of entities whose keys and values may change. If you have a table source Array<Item>, and want to assign a component for each item id without re-creating the component when the item or index changes, you'd map it to a new table of type Map<string, Item> like so:

local itemsById = derive(function()
    local map: Map<string, Item> = {}
    for _, item in items do
        map[item.id] = item
    end
    return map
end)

return indexes(itemsById, function(item: () -> Item, id: string)
    -- ...
end)

This approach works fine, but you lose the original order of the item in the process. It's tedious to keep this in mind when rendering lists where both keys and values change over time.

Storing an array of sources and using vide.values instead is also an alternative, but it is not the best option in some cases, especially when using a separate library for state management.

Proposal

I think Vide could have a new table source API, vide.entries(), with an additional argument for assigning a unique identifier to an entity. This way, both the key and value passed to transform are sources, allowing the component to react to any change in order and state without being re-created.

return entries(items, function(item: Item, index: number)
    return item.id
end, function(item: () -> Item, index: () -> number, id: string)
    -- ...
end)

This is similar to the key prop from React, which allows you to render components with the latest item index and value for as long as their key prop stays the same. Components are only unmounted when the data associated with that specific key is removed from the table.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.