Compare commits

...

7 commits
0.4.0 ... main

Author SHA1 Message Date
raine
f3bfc65607
feat: export vide.create types and include more ui element types (#79)
* feat: add more ui instances to Create type

* fix: use string requires in init.luau

@self string requires are now supported by Roblox, so this should be fine. It works in my Pesde patches and fixes issues with missing types.

* feat: export vide.create types

Instances and Properties are both useful types that should be able to be used by consumers (e.g. wrapper components).

* fix: typo

* feat: include exports in repo init.luau

Fixes #84
2026-08-05 17:43:28 +01:00
centauri
452060a533 Bump wally and pesde version 2026-07-11 13:31:48 +01:00
centauri
5ed4c01940 Bump version to 0.4.1 2026-07-11 13:25:38 +01:00
centauri
19eaeb424f Deprecate create() overloads
Closes #70
2026-04-29 21:33:29 +01:00
centauri
7deae9c9f6 Allow vide.branch to be used in a reactive scope 2026-03-10 13:46:13 +00:00
centau
3faa1ed701
Merge pull request #64 from wiam77/patch-1
Simplify connector check in Properties type function
2026-01-26 12:09:00 +00:00
wiam
820bda8eb1
Simplify connector check in Properties type function
Removed redundant check for connector in the Properties type function.
2026-01-25 23:15:48 -03:00
12 changed files with 94 additions and 105 deletions

View file

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

View file

@ -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 ## [0.4.0] - 2026-01-17
### Added ### Added

View file

@ -4,5 +4,7 @@ export type source<T> = vide.source<T>
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 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 return vide

View file

@ -1,5 +1,5 @@
name = "centau/vide" name = "centau/vide"
version = "0.4.0" version = "0.4.1"
description = "A reactive Luau library for creating UI." description = "A reactive Luau library for creating UI."
authors = ["centau"] authors = ["centau"]
repository = "https://github.com/centau/vide" repository = "https://github.com/centau/vide"

View file

@ -13,8 +13,8 @@ local function branch<T>(fn: () -> T): (() -> (), T)
end end
local parent = current.owner local parent = current.owner
if not parent or parent.effect then if not parent then
error(`current scope is not owned by a stable scope`, 0) error(`current scope is not owned by a scope`, 0)
end end
local node = create_node(parent, false, false) local node = create_node(parent, false, false)

View file

@ -5,9 +5,8 @@ local defaults = require "./defaults"
local apply = require "./apply" local apply = require "./apply"
local flags = require "./flags" local flags = require "./flags"
local ctor_cache = {} :: { [string]: (AnyProps) -> Instance } local function create_constructor_for_class(class: string): ({ [unknown]: unknown }) -> Instance
local function lazy_init(_, class: string) local function constructor(properties: { [unknown]: unknown }): Instance
local function ctor(properties: AnyProps): Instance
local ok, instance: Instance = pcall(Instance.new, class :: any) 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
@ -23,31 +22,37 @@ local function lazy_init(_, class: string)
return apply(instance, properties) return apply(instance, properties)
end end
ctor_cache[class] = ctor return constructor
return ctor
end end
setmetatable(ctor_cache, { __index = lazy_init })
local constructor_cache = {} :: { [string]: ({ [unknown]: unknown }) -> Instance }
-- todo: remove support for different overloads -- 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 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) error("bad argument #1, expected string or instance, got " .. typeof(class_or_instance), 0)
end end
local ctor = if type(class_or_instance) == "string" local constructor: ({ [unknown]: unknown }) -> Instance
then ctor_cache[class_or_instance] if type(class_or_instance) == "string" then
else function(properties) constructor = constructor_cache[class_or_instance]
local clone = class_or_instance:Clone() if not constructor then
if not clone then error "attempt to clone a non-archivable instance" end constructor = create_constructor_for_class(class_or_instance)
return apply(clone, properties) 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 end
return if properties return if properties
then ctor(properties) then constructor(properties)
else ctor else constructor
end end
type Instances = { export type Instances = {
Folder: Folder, Folder: Folder,
BillboardGui: BillboardGui, BillboardGui: BillboardGui,
CanvasGroup: CanvasGroup, CanvasGroup: CanvasGroup,
@ -65,18 +70,29 @@ type Instances = {
UIGradient: UIGradient, UIGradient: UIGradient,
UIGridLayout: UIGridLayout, UIGridLayout: UIGridLayout,
UIListLayout: UIListLayout, 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, Camera: Camera,
WorldModel: WorldModel, WorldModel: WorldModel,
} }
type function Properties(instance: type?) export type function Properties(instance: type?)
local properties = types.newtable() local properties = types.newtable()
while instance do while instance do
for i, v in instance:properties() do for i, v in instance:properties() do
local connector = v.read and v.read.tag == "table" and v.read:readproperty(types.singleton("Connect")) local connector = v.read and v.read.tag == "table" and v.read:readproperty(types.singleton("Connect"))
if connector then if connector then
if not connector then continue end
local params = connector:parameters().head local params = connector:parameters().head
if not params then continue end if not params then continue end
local listener = params[2] local listener = params[2]
@ -98,13 +114,6 @@ type function Properties(instance: type?)
return properties return properties
end end
type AnyProps = { [any]: any } type Create = <Name>(Name|keyof<Instances>|"") -> (Properties<index<Instances, Name>>) -> index<Instances, Name>
type Create = (
(Instance) -> (AnyProps) -> Instance
) & (
(string, AnyProps) -> Instance
) & (
<Name>(Name|keyof<Instances>) -> (Properties<index<Instances, Name>>) -> index<Instances, Name>
)
return (create :: any) :: Create return create :: Create

View file

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

View file

@ -1,10 +1,12 @@
assert(game, "when using vide outside of Roblox, require lib.luau instead") 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<T> = vide.source<T> export type source<T> = vide.source<T>
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 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 return vide

View file

@ -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 root = require "./root"
local branch = require "./branch" local branch = require "./branch"
@ -27,6 +27,8 @@ export type Source<T> = source.Source<T>
export type source<T> = Source<T> export type source<T> = Source<T>
export type Context<T> = context.Context<T> export type Context<T> = context.Context<T>
export type context<T> = 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) local function step(dt: number)
if game then debug.profilebegin("VIDE STEP") end if game then debug.profilebegin("VIDE STEP") end

View file

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

View file

@ -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: 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" name = "centau/vide"
description = "A reactive Luau library for creating UI. " description = "A reactive Luau library for creating UI. "
license = "MIT" license = "MIT"
version = "0.4.0" version = "0.4.1"
registry = "https://github.com/UpliftGames/wally-index" registry = "https://github.com/UpliftGames/wally-index"
realm = "shared" realm = "shared"
include = ["default.project.json", "LICENSE", "src"] include = ["default.project.json", "LICENSE", "src"]