diff --git a/.github/workflows/wallypesde.yml b/.github/workflows/wallypesde.yml index 0c705b0..29198ab 100644 --- a/.github/workflows/wallypesde.yml +++ b/.github/workflows/wallypesde.yml @@ -1,7 +1,6 @@ name: publish to wally and pesde on: - workflow_dispatch: release: types: [published] diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e584d2..8bd1bdd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,22 +6,6 @@ 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 a9ae8e0..fcd8183 100644 --- a/init.luau +++ b/init.luau @@ -4,7 +4,5 @@ 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 94c8ede..aec01c5 100644 --- a/pesde.toml +++ b/pesde.toml @@ -1,5 +1,5 @@ name = "centau/vide" -version = "0.4.1" +version = "0.4.0" 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 2138d06..a35c1b0 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 then - error(`current scope is not owned by a scope`, 0) + if not parent or parent.effect then + error(`current scope is not owned by a stable scope`, 0) end local node = create_node(parent, false, false) diff --git a/src/create.luau b/src/create.luau index 120d17d..77683eb 100644 --- a/src/create.luau +++ b/src/create.luau @@ -5,10 +5,11 @@ local defaults = require "./defaults" local apply = require "./apply" local flags = require "./flags" -local function create_constructor_for_class(class: string): ({ [unknown]: unknown }) -> Instance - local function constructor(properties: { [unknown]: unknown }): Instance +local ctor_cache = {} :: { [string]: (AnyProps) -> Instance } +local function lazy_init(_, class: string) + local function ctor(properties: AnyProps): 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] @@ -22,37 +23,31 @@ local function create_constructor_for_class(class: string): ({ [unknown]: unknow return apply(instance, properties) end - return constructor + ctor_cache[class] = ctor + return ctor end - -local constructor_cache = {} :: { [string]: ({ [unknown]: unknown }) -> Instance } +setmetatable(ctor_cache, { __index = lazy_init }) -- todo: remove support for different overloads -local function create(class_or_instance: string|Instance, properties: { [unknown]: unknown }?): unknown +local function create(class_or_instance: string|Instance, properties: AnyProps?): ((AnyProps) -> Instance) | Instance 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 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 + 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) 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 constructor(properties) - else constructor + then ctor(properties) + else ctor end -export type Instances = { +type Instances = { Folder: Folder, BillboardGui: BillboardGui, CanvasGroup: CanvasGroup, @@ -70,29 +65,18 @@ export 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, } -export type function Properties(instance: type?) +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] @@ -114,6 +98,13 @@ export type function Properties(instance: type?) return properties end -type Create = (Name|keyof|"") -> (Properties>) -> index +type AnyProps = { [any]: any } +type Create = ( + (Instance) -> (AnyProps) -> Instance +) & ( + (string, AnyProps) -> Instance +) & ( + (Name|keyof) -> (Properties>) -> index +) -return create :: Create +return (create :: any) :: Create diff --git a/src/defaults.luau b/src/defaults.luau index 77040ff..bcdabaf 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 c0fbe0e..11d1bd2 100644 --- a/src/init.luau +++ b/src/init.luau @@ -1,12 +1,10 @@ assert(game, "when using vide outside of Roblox, require lib.luau instead") -local vide = require("@self/lib") +local vide = require(script.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 a8a3885..28784b4 100644 --- a/src/lib.luau +++ b/src/lib.luau @@ -1,4 +1,4 @@ -local version = { major = 0, minor = 4, patch = 1 } +local version = { major = 0, minor = 4, patch = 0 } local root = require "./root" local branch = require "./branch" @@ -27,8 +27,6 @@ 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 deleted file mode 100644 index b30ffd5..0000000 --- a/test/create-type-test.luau +++ /dev/null @@ -1,27 +0,0 @@ -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 new file mode 100644 index 0000000..3a1e6fd --- /dev/null +++ b/test/create-types.luau @@ -0,0 +1,70 @@ +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 1584e06..2b44317 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.1" +version = "0.4.0" registry = "https://github.com/UpliftGames/wally-index" realm = "shared" include = ["default.project.json", "LICENSE", "src"]