Compare commits

..

No commits in common. "main" and "0.4.0" have entirely different histories.
main ... 0.4.0

12 changed files with 105 additions and 94 deletions

View file

@ -1,7 +1,6 @@
name: publish to wally and pesde
on:
workflow_dispatch:
release:
types: [published]

View file

@ -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

View file

@ -4,7 +4,5 @@ export type source<T> = vide.source<T>
export type Source<T> = vide.Source<T>
export type context<T> = vide.context<T>
export type Context<T> = vide.Context<T>
export type Instances = vide.Instances
export type Properties<T> = vide.Properties<T>
return vide

View file

@ -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"

View file

@ -13,8 +13,8 @@ local function branch<T>(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)

View file

@ -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>(Name|keyof<Instances>|"") -> (Properties<index<Instances, Name>>) -> index<Instances, Name>
type AnyProps = { [any]: any }
type Create = (
(Instance) -> (AnyProps) -> Instance
) & (
(string, AnyProps) -> Instance
) & (
<Name>(Name|keyof<Instances>) -> (Properties<index<Instances, Name>>) -> index<Instances, Name>
)
return create :: Create
return (create :: any) :: Create

View file

@ -110,4 +110,4 @@ return {
BorderColor3 = Color3.new(0, 0, 0),
BorderSizePixel = 0
}
} :: { [string]: { [string]: unknown} }
}

View file

@ -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<T> = vide.source<T>
export type Source<T> = vide.Source<T>
export type context<T> = vide.context<T>
export type Context<T> = vide.Context<T>
export type Instances = vide.Instances
export type Properties<T> = vide.Properties<T>
return vide

View file

@ -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<T> = source.Source<T>
export type source<T> = Source<T>
export type Context<T> = context.Context<T>
export type context<T> = Context<T>
export type Instances = create.Instances
export type Properties<T> = create.Properties<T>
local function step(dt: number)
if game then debug.profilebegin("VIDE STEP") end

View file

@ -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,
}

70
test/create-types.luau Normal file
View file

@ -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: Name | keyof<Instances>): (Properties<index<Instances, Name>>) -> index<Instances, Name>
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,
}

View file

@ -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"]