diff --git a/.github/workflows/wallypesde.yml b/.github/workflows/wallypesde.yml index 29198ab..0c705b0 100644 --- a/.github/workflows/wallypesde.yml +++ b/.github/workflows/wallypesde.yml @@ -1,6 +1,7 @@ name: publish to wally and pesde on: + workflow_dispatch: release: types: [published] diff --git a/CHANGELOG.md b/CHANGELOG.md index 8bd1bdd..3e584d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,22 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). -------------------------------------------------------------------------------- +## [0.4.1] - 2026-07-11 + +### Changed + +- `branch()` is now allowed to be used within a reactive scope. + +### Deprecated + +- `create()` overloads. Supported is now only `create(class)(props)`. + +### Fixed + +- `create()` types in the new solver should now work without `::`. + +-------------------------------------------------------------------------------- + ## [0.4.0] - 2026-01-17 ### Added diff --git a/init.luau b/init.luau index fcd8183..a9ae8e0 100644 --- a/init.luau +++ b/init.luau @@ -4,5 +4,7 @@ export type source = vide.source export type Source = vide.Source export type context = vide.context export type Context = vide.Context +export type Instances = vide.Instances +export type Properties = vide.Properties return vide diff --git a/pesde.toml b/pesde.toml index aec01c5..94c8ede 100644 --- a/pesde.toml +++ b/pesde.toml @@ -1,5 +1,5 @@ name = "centau/vide" -version = "0.4.0" +version = "0.4.1" description = "A reactive Luau library for creating UI." authors = ["centau"] repository = "https://github.com/centau/vide" diff --git a/src/branch.luau b/src/branch.luau index a35c1b0..2138d06 100644 --- a/src/branch.luau +++ b/src/branch.luau @@ -13,8 +13,8 @@ local function branch(fn: () -> T): (() -> (), T) end local parent = current.owner - if not parent or parent.effect then - error(`current scope is not owned by a stable scope`, 0) + if not parent then + error(`current scope is not owned by a scope`, 0) end local node = create_node(parent, false, false) diff --git a/src/create.luau b/src/create.luau index 77683eb..120d17d 100644 --- a/src/create.luau +++ b/src/create.luau @@ -5,11 +5,10 @@ local defaults = require "./defaults" local apply = require "./apply" local flags = require "./flags" -local ctor_cache = {} :: { [string]: (AnyProps) -> Instance } -local function lazy_init(_, class: string) - local function ctor(properties: AnyProps): Instance +local function create_constructor_for_class(class: string): ({ [unknown]: unknown }) -> Instance + local function constructor(properties: { [unknown]: unknown }): Instance local ok, instance: Instance = pcall(Instance.new, class :: any) - if not ok then error(`invalid class name { class }`, 0) end + if not ok then error(`invalid class name {class}`, 0) end if flags.defaults then local default: { [string]: unknown }? = defaults[class] @@ -23,31 +22,37 @@ local function lazy_init(_, class: string) return apply(instance, properties) end - ctor_cache[class] = ctor - return ctor + return constructor end -setmetatable(ctor_cache, { __index = lazy_init }) + +local constructor_cache = {} :: { [string]: ({ [unknown]: unknown }) -> Instance } -- todo: remove support for different overloads -local function create(class_or_instance: string|Instance, properties: AnyProps?): ((AnyProps) -> Instance) | Instance +local function create(class_or_instance: string|Instance, properties: { [unknown]: unknown }?): unknown if type(class_or_instance) ~= "string" and typeof(class_or_instance) ~= "Instance" then error("bad argument #1, expected string or instance, got " .. typeof(class_or_instance), 0) end - local ctor = if type(class_or_instance) == "string" - then ctor_cache[class_or_instance] - else function(properties) - local clone = class_or_instance:Clone() - if not clone then error "attempt to clone a non-archivable instance" end - return apply(clone, properties) + local constructor: ({ [unknown]: unknown }) -> Instance + if type(class_or_instance) == "string" then + constructor = constructor_cache[class_or_instance] + if not constructor then + constructor = create_constructor_for_class(class_or_instance) + constructor_cache[class_or_instance] = constructor end + else + constructor = function(props) + local clone = assert(class_or_instance:Clone(), "attempt to clone a non-archivable instance") + return apply(clone, props) + end + end return if properties - then ctor(properties) - else ctor + then constructor(properties) + else constructor end -type Instances = { +export type Instances = { Folder: Folder, BillboardGui: BillboardGui, CanvasGroup: CanvasGroup, @@ -65,18 +70,29 @@ type Instances = { UIGradient: UIGradient, UIGridLayout: UIGridLayout, UIListLayout: UIListLayout, + UISizeConstraint: UISizeConstraint, + UITextSizeConstraint: UITextSizeConstraint, + UIScale: UIScale, + UIPadding: UIPadding, + UIStroke: UIStroke, + UIFlexItem: UIFlexItem, + UIPageLayout: UIPageLayout, + UITableLayout: UITableLayout, + VideoFrame: VideoFrame, + ViewportFrame: ViewportFrame, + ProximityPrompt: ProximityPrompt, + UIDragDetector: UIDragDetector, Camera: Camera, WorldModel: WorldModel, } -type function Properties(instance: type?) +export type function Properties(instance: type?) local properties = types.newtable() while instance do for i, v in instance:properties() do local connector = v.read and v.read.tag == "table" and v.read:readproperty(types.singleton("Connect")) if connector then - if not connector then continue end local params = connector:parameters().head if not params then continue end local listener = params[2] @@ -98,13 +114,6 @@ type function Properties(instance: type?) return properties end -type AnyProps = { [any]: any } -type Create = ( - (Instance) -> (AnyProps) -> Instance -) & ( - (string, AnyProps) -> Instance -) & ( - (Name|keyof) -> (Properties>) -> index -) +type Create = (Name|keyof|"") -> (Properties>) -> index -return (create :: any) :: Create +return create :: Create diff --git a/src/defaults.luau b/src/defaults.luau index bcdabaf..77040ff 100644 --- a/src/defaults.luau +++ b/src/defaults.luau @@ -110,4 +110,4 @@ return { BorderColor3 = Color3.new(0, 0, 0), BorderSizePixel = 0 } -} +} :: { [string]: { [string]: unknown} } diff --git a/src/init.luau b/src/init.luau index 11d1bd2..c0fbe0e 100644 --- a/src/init.luau +++ b/src/init.luau @@ -1,10 +1,12 @@ assert(game, "when using vide outside of Roblox, require lib.luau instead") -local vide = require(script.lib) +local vide = require("@self/lib") export type source = vide.source export type Source = vide.Source export type context = vide.context export type Context = vide.Context +export type Instances = vide.Instances +export type Properties = vide.Properties return vide diff --git a/src/lib.luau b/src/lib.luau index 28784b4..a8a3885 100644 --- a/src/lib.luau +++ b/src/lib.luau @@ -1,4 +1,4 @@ -local version = { major = 0, minor = 4, patch = 0 } +local version = { major = 0, minor = 4, patch = 1 } local root = require "./root" local branch = require "./branch" @@ -27,6 +27,8 @@ export type Source = source.Source export type source = Source export type Context = context.Context export type context = Context +export type Instances = create.Instances +export type Properties = create.Properties local function step(dt: number) if game then debug.profilebegin("VIDE STEP") end diff --git a/test/create-type-test.luau b/test/create-type-test.luau new file mode 100644 index 0000000..b30ffd5 --- /dev/null +++ b/test/create-type-test.luau @@ -0,0 +1,27 @@ +local vide = require "../src/" + +local count = vide.source(0) + +vide.create("TextButton") { + BackgroundTransparency = 1, + AnchorPoint = "bad value", -- should error + InvalidProperty = true, -- should error + + Text = function() + return "count: " .. count() + end, + + Size = function() -- should error + return "bad value" + end, + + MouseEnter = function(x, y) + + end, + + Activated = "bad value", -- should error + + vide.create "TextLabel" {}, + + function() end, +} diff --git a/test/create-types.luau b/test/create-types.luau deleted file mode 100644 index 3a1e6fd..0000000 --- a/test/create-types.luau +++ /dev/null @@ -1,70 +0,0 @@ -type Pseudo = { - Test: number -} - -type Instances = { - TextLabel: TextLabel, - TextButton: TextButton, - ImageLabel: ImageLabel, - Pseudo: Pseudo -} - -type function Properties(instance: type?) - local properties = types.newtable() - - while instance do - for i, v in instance:properties() do - local connector = v.read and v.read.tag == "table" and v.read:readproperty(types.singleton("Connect")) - if connector then - if not connector then continue end - local params = connector:parameters().head - if not params then continue end - local listener = params[2] - if not listener then continue end - properties:setproperty(i, types.optional(listener)) - elseif v.write then - properties:setproperty(i, types.optional(types.unionof( - v.write, - types.newfunction({}, { head = { v.write } }) - ))) - end - end - - instance = instance:readparent() - end - - properties:setindexer(types.number, types.any) - - return properties -end - -local function create(name: Name | keyof): (Properties>) -> index - return function() - return "" :: any - end -end - -local vide = require "../src/" -local count = vide.source(0) -create("TextButton") { -- todo: why is `:: "TextButton"` not necessary? - BackgroundTransparency = 1, - AnchorPoint = "bad value", -- should error - InvalidProperty = true, -- should error - - Text = function() - return "count: " .. count() - end, - - Size = function() -- should error - return "bad value" - end, - - MouseEnter = function(x, y) - - end, - - Activated = "bad value", -- should error - - create "TextLabel" {}, - function() end, -} diff --git a/wally.toml b/wally.toml index 2b44317..1584e06 100644 --- a/wally.toml +++ b/wally.toml @@ -2,7 +2,7 @@ name = "centau/vide" description = "A reactive Luau library for creating UI. " license = "MIT" -version = "0.4.0" +version = "0.4.1" registry = "https://github.com/UpliftGames/wally-index" realm = "shared" include = ["default.project.json", "LICENSE", "src"]