update docs

This commit is contained in:
Joe 2026-02-09 21:47:49 -06:00
parent 0b1e8c89dc
commit 7b73720143
586 changed files with 5616 additions and 1325 deletions

View file

@ -0,0 +1,50 @@
# Animation
## spring() <Badge type="tip" text="STABLE"><a href="/vide/api/reactivity-core#Scopes">REACTIVE</a></Badge>
Returns a new source with a value always moving torwards the input source value.
- **Type**
```luau
function spring<T>(
source: () -> T & Animatable,
period: number = 1,
damping_ratio: number = 1
): (() -> T, SpringControl<T>)
type Animatable = number | CFrame | Color3 | UDim | UDim2 | Vector2 | Vector3 | Rect
type SpringControl<T> = ({
position: T?,
velocity: T?,
impulse: T?
}) -> ()
```
- **Details**
Creates a reactive scope internally to detect source updates.
The movement is physically simulated according to a
[spring](https://en.wikipedia.org/wiki/Simple_harmonic_motion).
`period` is the amount of time in seconds it takes for the spring to
complete one full cycle if undamped.
`damping_ratio` is the amount of resistance applied to the spring.
- \>1 = Overdamped - slowly reaches target without any overshoot.
- 1 = Critically damped - reaches target without any overshoot.
- <1 = Underdamped - reaches target with some overshoot.
- 0 = Undamped - never stabilizes, oscillates forever.
By default, the spring solver is ran at 120 Hz in
[`Heartbeat`](https://create.roblox.com/docs/reference/engine/classes/RunService#Heartbeat).
You can change when the solver runs by calling `vide.step(dt)`, which will
advance the simulation time by `dt` seconds and automatically stop the
solver running in heartbeat.
::: warning
Large periods or damping ratios can break the spring.
:::

View file

@ -0,0 +1,152 @@
# Element Creation
## create()
Creates a new UI element, applying any given properties.
- **Type**
```luau
function create(class: string): (Properties) -> Instance
function create(instance: Instance): (Properties) -> Instance
type Properties = Map<string|number, unknown>
```
- **Details**
The function can take either a `string` or an `Instance` as its first argument.
- If given a `string`, a new instance with the same class name will be created.
- If given an `Instance`, a new instance that is a clone of the given instance
will be created.
This returns another function that is used to apply any properties to the new
instance.
- **Property setting rules**
- **index is string:**
- **value is function:**
- **property is event:** connect function as callback
- **property is not event:** create effect to update property
- **value is not function:** set property to value
- **index is number:**
- **value is action:** run action
- **value is table:** recurse table
- **value is function:** create effect to update children
- **value is instance:** set instance as child
- **Example**
Basic element creation.
```luau
local frame = create "TextButton" {
Name = "Button",
Size = UDim2.fromOffset(200, 160),
Activated = function()
print "clicked"
end,
create "UICorner" {}
}
```
## action()
Creates a special object that can be passed to `create()` to invoke custom
actions on instances.
- **Type**
```luau
function action((Instance) -> (), priority: number = 1): Action
```
- **Details**
When passed to `create()`, the function is called with the instance being
created as the only argument. Actions take precedence over property and
child assignments.
A priority can be optionally specified to ensure certain actions run after
other actions. Lower priority values are ran first.
- **Example**
An action to listen to changed properties:
```luau
local function changed(property: string, fn: (new) -> ())
return action(function(instance)
local cn = instance:GetPropertyChangedSignal(property):Connect(function()
fn(instance[property])
end)
-- disconnect on scope destruction to allow gc of instance
cleanup(function()
cn:Disconnect()
end)
end)
end
local output = source ""
create "TextBox" {
-- will update the output source anytime the text property is changed
changed("Text", output)
}
```
## changed()
A wrapper for `action()` to listen for property changes.
- **Type**
```luau
function changed(property: string, fn: (unknown) -> ()): Action
```
- **Details**
Will run the given function immediately and whenever the property updates.
The function is called with the updated property value.
Runs with an action priority of 1.
## mount() <Badge type="info" text="STABLE"><a href="/vide/api/reactivity-core#Scopes">STABLE</a></Badge>
Runs a function in a new stable scope and optionally applies its result to a
target instance.
- **Type**
```luau
function mount<T>(component: () -> T, target: Instance?): () -> ()
```
- **Details**
This is a utility for `root()` when parenting a component to an existing
instance.
The result of the function is applied to a target in the same way
properties are using `create()`.
Returns a function that when called will destroy the stable scope.
- **Example**
```luau
local function App()
return create "ScreenGui" {
create "TextLabel" { Text = "Vide" }
}
end
local destroy = mount(App, game.StarterGui)
```

View file

@ -0,0 +1,155 @@
# Reactivity: Core
## Scopes
Vide code can run in one of two scopes: <Badge type="info" text="STABLE"><a href="/vide/api/reactivity-core#Scopes">STABLE</a></Badge> or <Badge type="tip" text="STABLE"><a href="/vide/api/reactivity-core#Scopes">REACTIVE</a></Badge>.
- Reactive scopes rerun if a source read within updates.
- Stable scopes never rerun.
- Reactive scopes cannot be created directly within another reactive scope.
- When a scope is destroyed, all scopes created within are also destroyed.
Different functions in Vide's API will run code in different scopes.
:::warning
Yielding is not allowed in any stable or reactive scope. Strict mode will check
for this.
:::
## root() <Badge type="info" text="STABLE"><a href="/vide/api/reactivity-core#Scopes">STABLE</a></Badge>
Runs a function in a new stable scope.
- **Type**
```luau
function root<T...>(fn: (Destructor) -> T...): (Destructor, T...)
type Destructor = () -> ()
```
- **Details**
Returns a destructor and any values returned by the callback.
## source()
Creates a new source.
- **Type**
```luau
function source<T>(value: T): Source<T>
type Source<T> =
() -> T -- get
& (T) -> () -- set
```
- **Details**
Call the returned source with no argument to read its value.
Call the returned source with an argument to set its value.
- **Example**
```luau
local count = source(0)
print(count())-- 0
count(count() + 1)
print(count()) -- 1
```
## effect() <Badge type="tip" text="STABLE"><a href="/vide/api/reactivity-core#Scopes">REACTIVE</a></Badge>
Runs a function in a new reactive scope.
- **Type**
```luau
function effect(fn: () -> ())
```
- **Details**
The function is ran once immediately.
- **Example**
```luau
local count = source(1)
effect(function()
print(count())
end)
-- prints 1
count(2)
-- prints 2
```
## derive() <Badge type="tip" text="STABLE"><a href="/vide/api/reactivity-core#Scopes">REACTIVE</a></Badge>
Runs a function in a new reactive scope to compute a value for new source.
- **Type**
```luau
function derive<T>(fn: () -> T): () -> T
```
- **Details**
Anytime the reactive scope reruns, the output source value is set to what is
returned.
The function is ran once immediately.
- **Example**
```luau
local count = source(0)
local text = derive(function() return `count: {count()}` end)
print(text()) -- "count: 0"
count(1)
print(text()) -- "count: 1"
```
A `derive()` should be used instead of a pure function when you expect it to
be read multiple times between updates, because `derive()` will cache the
result to prevent recomputing it on every read.
::: code-group
```luau [Pure Function]
local count = source(0)
local text = function()
print "ran"
return `count: {count()}`
end
count(1)
print(text()) -- prints "ran" followed by "count: 1"
print(text()) -- prints "ran" followed by "count: 1"
```
```luau [Derived Source]
local count = source(0)
local text = derive(function() -- [!code highlight]
print "ran"
return `count: {count()}`
end) -- [!code highlight]
count(1) -- prints "ran"
print(text()) -- prints "count: 1"
print(text()) -- prints "count: 1"
```
:::

View file

@ -0,0 +1,229 @@
# Reactivity: Dynamic Scopes
Dynamic scopes are scopes that are created or destroyed in response to
source updates. Vide provides functions for some common use-cases for dynamic scopes.
## show() <Badge type="tip" text="STABLE"><a href="/vide/api/reactivity-core#Scopes">REACTIVE</a></Badge>
Shows a component if the source is truthy. Optionally shows a fallback component
if the source is falsey.
- **Type**
```luau
function show<T>(source: () -> unknown, component: Constructor<T>): () -> T?
function show<T, U>(source: () -> unknown, component: Constructor<T>, fallback: () -> U): () -> T | U
type Constructor<T> = () -> (T, number?)
```
- **Details**
Creates a reactive scope internally to detect source updates.
The component is run in a stable scope when truthy, otherwise the stable
scope is destroyed.
Returns a source holding an instance of the currently shown component or
`nil` if no component is currently shown.
Destruction of the scope can be delayed by returning the number of seconds
to delay by, after the component.
## switch() <Badge type="tip" text="STABLE"><a href="/vide/api/reactivity-core#Scopes">REACTIVE</a></Badge>
Shows one of a set of components depending on a source and a mapping table.
- **Type**
```luau
function switch<K, V>(source: () -> K): (map: Map<K, Constructor<V>>): () -> V?
type Constructor<T> = () -> (T, number?)
```
- **Details**
Creates a reactive scope internally to detect source updates.
When the source updates, its value is inputted into a map to get a component
constructor. This component is then run in a stable scope. The previous
stable scope is destroyed.
Returns a source holding an instance of the currently shown component or
`nil` if no component is currently shown.
Destruction of the scope can be delayed by returning the number of seconds
to delay by, after the component.
- **Example**
```luau
local logged = source(false)
local button = switch(logged) {
[true] = function()
return Button { Text = "Log out", Toggle = logged }
end,
[false] = function()
return Button { Text = "Log in", Toggle = logged }
end
}
```
## indexes() <Badge type="tip" text="STABLE"><a href="/vide/api/reactivity-core#Scopes">REACTIVE</a></Badge>
Shows a component for each index in a table.
- **Type**
```luau
function indexes<KI, VI, VO>(
source: () -> Map<KI, VI>,
constructor: (value: () -> VI, index: KI) -> (VO, number?)
): Array<VO>
```
- **Details**
Creates a reactive scope internally to detect source updates.
When the source table updates, a component is generated for each index in
the table.
- For any added index, the `constructor` function is run in a new stable
scope to produce an instance that is cached.
- For any removed index, the stable scope for that index is destroyed.
The `constructor` function is called with:
1. A *source containing the index's value*.
2. The *index itself*.
Anytime an existing index's value changes, the `constructor` function is not
rerun, instead, that index's corresponding source is updated with the new
value.
Returns a source holding an array of instances currently shown.
Destruction of the scope can be delayed by returning the number of seconds
to delay by, after the component.
- **Example**
```luau
type Item = {
name: string,
icon: number
}
local items = source {} :: () -> Array<Item>
local displays = indexes(items, function(item, i)
return ItemDisplay {
Name = function()
return i .. ": " .. item().name
end,
Image = function()
return "rbxassetid://" .. item().icon
end,
}
end)
```
## values() <Badge type="tip" text="STABLE"><a href="/vide/api/reactivity-core#Scopes">REACTIVE</a></Badge>
Shows a component for each value in a table.
- **Type**
```luau
function values<KI, VI, VO>(
source: () -> Map<KI, VI>,
constructor: (value: VI, index: () -> KI) -> (VO, number?)
): Array<VO>
- **Details**
Operates with the same idea as `indexes()`, but applied to values instead of
indexes.
Creates a reactive scope internally to detect source updates.
When the source table updates, a component is generated for each value in
the table.
- For any added value, the `constructor` function is run in a new stable scope
to produce an instance that is cached.
- For any removed value, the stable scope for that value is destroyed.
The `constructor` function is called with:
1. The *value itself*.
2. A *source containing the value's index*.
Anytime an existing value's index changes, the `constructor` function is not
rerun, instead, that value's corresponding source is updated with the new
index.
Returns a source holding an array of instances currently shown.
Destruction of the scope can be delayed by returning the number of seconds
to delay by, after the component.
::: warning
Having the same values appear multiple times in the input source table can
cause unexpected behavior. Strict mode has checks for this.
:::
- **Example**
```luau
type Item = {
name: string,
icon: number
}
local items = source {} :: () -> Array<Item>
local displays = values(items, function(item, i)
return ItemDisplay {
Name = function()
return i() .. ": " .. item.Name
end
Image = "rbxassetid://" .. item.icon,
}
end)
```
- **Extra**
When should you use `indexes()` and `values()`?
`values()` should be used when you have a fixed set of objects where the
same objects can be re-arranged in the source table. It maps a value to a
UI element.
e.g.
- List of all players.
- Inventory of items.
- Chat message history.
- Toast notifications.
`indexes()` should be used in other cases, especially when your source table
has primitive values. It maps an index to a UI element.
e.g.
- List of character or weapon stats.
In most cases, both functions will produce the same observed result.
The main difference is performance, picking the right function to use can
result in less property updates and less re-renders. One case to note is
that `values()` works nicely when animating re-ordering of instances, since
the source index can be used to animate a change in position for the UI
element.
--------------------------------------------------------------------------------

View file

@ -0,0 +1,158 @@
# Reactivity: Utility
## cleanup()
Queues a callback to run when a scope is reran or destroyed.
- **Type**
```luau
function cleanup(v: Function | Disconnectable | Destroyable | thread)
type Function = () -> ()
type Destroyable = { destroy: () -> () }
type Disconnectable = { disconnect: () -> () }
```
- **Example**
```luau
local count = source(0)
local destroy = root(function()
effect(function()
count()
cleanup(function()
print "cleaned"
end)
end)
end
-- nothing printed yet
count(1) -- prints "cleaned"
count(2) -- prints "cleaned"
destroy() -- prints "cleaned"
```
## untrack() <Badge type="info" text="STABLE"><a href="/vide/api/reactivity-core#Scopes">STABLE</a></Badge>
Runs a function in a new stable scope.
- **Type**
```luau
function untrack<T>(source: () -> T): T
```
- **Details**
Can be used inside a reactive scope to read from sources you do not want
tracked by the reactive scope.
- **Example**
```luau
local a = source(0)
local b = source(0)
local sum = derive(function()
return a() + untrack(b)
end)
print(sum()) -- 0
b(1) -- untracked so reactive scope created by derive() does not rerun
print(sum()) -- 0
a(1) -- reactive scope created by derive() reruns
print(sum()) -- 2
```
## read()
Utility used to read a value that is either a primitive or a source.
- **Type**
```luau
function read<T>(value: T | () -> T): T
```
## batch()
Runs a function where any source updates made within the function do not
trigger effects until after the function ends.
- **Type**
```luau
function batch(fn: () -> ())
```
- **Details**
Improves performance when an effect depends on multiple sources, and those
sources need to be updated.
- **Example**
```luau
local a = source(0)
local b = source(0)
effect(function()
print(a() + b())
end)
-- prints "0"
batch(function()
a(1) -- no print
b(2) -- no print
end)
-- prints "3"
```
## context() <Badge type="info" text="STABLE"><a href="/vide/api/reactivity-core#Scopes">STABLE</a></Badge>
Creates a new context.
- **Type**
```luau
function context<T>(default: T): Context<T>
type Context<T> =
() -> T -- get
& <U>(T, () -> U) -> U -- set
```
- **Details**
Calling `context()` returns a new context function.
Call this function with no arguments to get the context value.
Call this function with a value and a function to create a new context with
the given value.
The new context is run under a stable scope.
- **Example**
```luau
local theme = context()
local function Button()
print(theme())
end
root(function()
theme("light", function()
Button() -- prints "light"
theme("dark", function()
Button() -- prints "dark"
end)
end)
end)
```

View file

@ -0,0 +1,37 @@
# Strict Mode
Strict mode is library-wide and can get set by doing:
```luau
vide.strict = true
```
It is automatically enabled when Vide is first required and not running in O2
optimization level.
Strict mode is designed to help the development process by adding safety checks
and identifying improper usage.
Currently, strict mode will:
1. Run reactive scopes twice when a source updates.
2. Throw an error if yields occur where they are not allowed.
3. Checks for `indexes()` and `values()` outputting primitive values.
4. Checks for `values()` input having duplicate values.
5. Checks for duplicate nested properties at same depth.
6. Checks for destruction of an active scope.
7. Better error reporting and stack traces.
By rerunning reactive scopes twice each time they update, it helps ensure that
computations are pure, and that any cleanup is done correctly.
Accidental yielding within reactive scopes can break Vide's reactive graph,
which strict mode will catch.
As well as additional safety checks, Vide will dedicate extra resources to
recording and better emitting stack traces where errors occur, particularly
when implicit effects are created for instance property updating.
It is recommended to develop UI with strict mode and to disable it when pushing to
production. In Roblox, production code compiles at O2 by default, so you do not
need to worry about disabling strict mode unless you have manually enabled it.