mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-10-23 05:51:39 +00:00
update docs
This commit is contained in:
parent
0b1e8c89dc
commit
7b73720143
586 changed files with 5616 additions and 1325 deletions
83
.cursor/rules/vide/.vitepress/config.ts
Normal file
83
.cursor/rules/vide/.vitepress/config.ts
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
//import { defineConfig } from "vitepress"
|
||||
import { withMermaid } from "vitepress-plugin-mermaid";
|
||||
|
||||
// https://vitepress.dev/reference/site-config
|
||||
export default withMermaid({
|
||||
title: "Vide",
|
||||
titleTemplate: ":title - A reactive UI library for Luau",
|
||||
description: "A reactive UI library for Luau.",
|
||||
base: "/vide/",
|
||||
head: [["link", { rel: "icon", href: "/vide/logo.svg" }]],
|
||||
|
||||
themeConfig: {
|
||||
logo: "/logo.svg",
|
||||
|
||||
search: {
|
||||
provider: "local"
|
||||
},
|
||||
|
||||
footer: {
|
||||
message: 'Released under the MIT License.',
|
||||
},
|
||||
|
||||
// https://vitepress.dev/reference/default-theme-config
|
||||
nav: [
|
||||
{ text: "Home", link: "/" },
|
||||
{ text: "Tutorials", link: "/tut/crash-course/1-introduction" },
|
||||
{ text: "API", link: "/api/reactivity-core"},
|
||||
],
|
||||
|
||||
sidebar: {
|
||||
"/api/": [
|
||||
{
|
||||
text: "API",
|
||||
items: [
|
||||
{ text: "Reactivity: Core", link: "/api/reactivity-core" },
|
||||
{ text: "Reactivity: Utility", link: "/api/reactivity-utility" },
|
||||
{ text: "Reactivity: Dynamic Scoping", link: "/api/reactivity-dynamic" },
|
||||
{ text: "Element Creation", link: "/api/creation" },
|
||||
{ text: "Animation", link: "/api/animation" },
|
||||
{ text: "Strict Mode", link: "/api/strict-mode" },
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
"/tut/": [
|
||||
{
|
||||
text: "Crash Course",
|
||||
items: [
|
||||
{ text: "Introduction", link: "/tut/crash-course/1-introduction" },
|
||||
{ text: "Element Creation", link: "/tut/crash-course/2-creation" },
|
||||
{ text: "Components", link: "/tut/crash-course/3-components" },
|
||||
{ text: "Sources", link: "/tut/crash-course/4-source" },
|
||||
{ text: "Effects", link: "/tut/crash-course/5-effect" },
|
||||
{ text: "Scopes", link: "/tut/crash-course/6-scope" },
|
||||
{ text: "Reactive Components", link: "/tut/crash-course/7-reactive-component" },
|
||||
{ text: "Implicit Effects", link: "/tut/crash-course/8-implicit-effect" },
|
||||
{ text: "Derived Sources", link: "/tut/crash-course/9-derived-source" },
|
||||
{ text: "Cleanup", link: "/tut/crash-course/10-cleanup" },
|
||||
{ text: "Dynamic Scopes", link: "/tut/crash-course/11-dynamic-scopes" },
|
||||
{ text: "Actions", link: "/tut/crash-course/12-actions" },
|
||||
{ text: "Strict Mode", link: "/tut/crash-course/13-strict-mode" },
|
||||
{ text: "Concepts Summary", link: "/tut/crash-course/14-concepts" }
|
||||
]
|
||||
},
|
||||
{
|
||||
text: "Advanced",
|
||||
items: [
|
||||
{ text: "Dynamic Scopes", link: "/tut/advanced/dynamic-scopes"}
|
||||
]
|
||||
},
|
||||
{
|
||||
text: "Design Patterns",
|
||||
items: [
|
||||
]
|
||||
}
|
||||
],
|
||||
},
|
||||
|
||||
socialLinks: [
|
||||
{ icon: "github", link: "https://github.com/centau/vide" }
|
||||
]
|
||||
}
|
||||
})
|
||||
25
.cursor/rules/vide/.vitepress/theme/index.css
Normal file
25
.cursor/rules/vide/.vitepress/theme/index.css
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
.home > * > .VPNavBar:not(.top) {
|
||||
backdrop-filter: blur(0.5rem);
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.home > * > .VPNavBar > .divider {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.home > .VPContent {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.VPHome {
|
||||
margin-top: auto !important;
|
||||
margin-bottom: auto !important;
|
||||
}
|
||||
|
||||
.VPBadge a {
|
||||
text-decoration: none;
|
||||
color: inherit
|
||||
}
|
||||
|
||||
8
.cursor/rules/vide/.vitepress/theme/index.js
Normal file
8
.cursor/rules/vide/.vitepress/theme/index.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// .vitepress/theme/index.js
|
||||
import DefaultTheme from 'vitepress/theme'
|
||||
import './vars.css'
|
||||
import './index.css'
|
||||
|
||||
export default {
|
||||
extends: DefaultTheme,
|
||||
}
|
||||
52
.cursor/rules/vide/.vitepress/theme/vars.css
Normal file
52
.cursor/rules/vide/.vitepress/theme/vars.css
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
/* Colors */
|
||||
|
||||
:root {
|
||||
--vp-c-brand-1: #3086ff;
|
||||
--vp-c-brand-2: #75aeff;
|
||||
|
||||
--vp-button-brand-bg: #3661a2;
|
||||
--vp-button-brand-hover-bg: #24447f;
|
||||
--vp-button-brand-press-bg: #4896f3;
|
||||
|
||||
--vp-home-hero-name-color: transparent;
|
||||
--vp-home-hero-name-background: -webkit-linear-gradient(
|
||||
120deg,
|
||||
#3661a2,
|
||||
#4896f3
|
||||
);
|
||||
--vp-home-hero-logo-background: -webkit-linear-gradient(
|
||||
120deg,
|
||||
#3661a2,
|
||||
#4896f3
|
||||
);
|
||||
|
||||
--vp-home-hero-image-filter: blur(96px);
|
||||
|
||||
--vp-c-bg: #f2f5f8;
|
||||
--vp-c-bg-alt: #dfe8f5;
|
||||
--vp-c-bg-elv: #dde7f4;
|
||||
--vp-c-bg-soft: #e8f1fe;
|
||||
|
||||
--vp-c-border: #c0c3c6;
|
||||
--vp-c-divider: #dfe2e6;
|
||||
--vp-c-gutter: #dfe2e6;
|
||||
--vp-plugin-tabs-tab-bg: var(--vp-c-bg);
|
||||
|
||||
--vp-badge-info-bg: #122d26;
|
||||
--vp-badge-info-text: #6bdbbd;
|
||||
--vp-badge-tip-bg: #132741;
|
||||
--vp-badge-tip-text: #70abfa;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--vp-c-brand-2: #234782;
|
||||
--vp-c-bg: #0d131b;
|
||||
--vp-c-bg-alt: #111720;
|
||||
--vp-c-bg-elv: #182231;
|
||||
--vp-c-bg-soft: #182231;
|
||||
|
||||
--vp-c-border: #111720;
|
||||
--vp-c-divider: #1d273c;
|
||||
--vp-c-gutter: #181d27;
|
||||
}
|
||||
|
||||
50
.cursor/rules/vide/api/animation.md
Normal file
50
.cursor/rules/vide/api/animation.md
Normal 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.
|
||||
:::
|
||||
152
.cursor/rules/vide/api/creation.md
Normal file
152
.cursor/rules/vide/api/creation.md
Normal 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)
|
||||
```
|
||||
155
.cursor/rules/vide/api/reactivity-core.md
Normal file
155
.cursor/rules/vide/api/reactivity-core.md
Normal 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"
|
||||
```
|
||||
|
||||
:::
|
||||
229
.cursor/rules/vide/api/reactivity-dynamic.md
Normal file
229
.cursor/rules/vide/api/reactivity-dynamic.md
Normal 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.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
158
.cursor/rules/vide/api/reactivity-utility.md
Normal file
158
.cursor/rules/vide/api/reactivity-utility.md
Normal 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)
|
||||
```
|
||||
|
||||
37
.cursor/rules/vide/api/strict-mode.md
Normal file
37
.cursor/rules/vide/api/strict-mode.md
Normal 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.
|
||||
22
.cursor/rules/vide/index.md
Normal file
22
.cursor/rules/vide/index.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
# https://vitepress.dev/reference/default-theme-home-page
|
||||
layout: home
|
||||
pageClass: home
|
||||
next:
|
||||
text: 'Introduction'
|
||||
link: '/tut/crash-course/1-introduction'
|
||||
|
||||
hero:
|
||||
name: "Vide"
|
||||
tagline: A reactive UI library for Luau.
|
||||
image:
|
||||
src: /logo.svg
|
||||
actions:
|
||||
- theme: brand
|
||||
text: Tutorials
|
||||
link: /tut/crash-course/1-introduction
|
||||
- theme: alt
|
||||
text: API Reference
|
||||
link: /api/reactivity-core
|
||||
|
||||
---
|
||||
12
.cursor/rules/vide/package.json
Normal file
12
.cursor/rules/vide/package.json
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"docs:dev": "vitepress dev",
|
||||
"docs:build": "vitepress build",
|
||||
"docs:preview": "vitepress preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vitepress": "^1.4.5",
|
||||
"vitepress-plugin-mermaid": "2.0.17"
|
||||
}
|
||||
}
|
||||
67
.cursor/rules/vide/public/full_logo.svg
Normal file
67
.cursor/rules/vide/public/full_logo.svg
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
<svg width="375" height="220" viewBox="15 7 115 68" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_d_1_41)">
|
||||
<g clip-path="url(#clip0_1_41)">
|
||||
<rect x="18" y="12" width="110" height="48" rx="8" fill="url(#paint0_radial_1_41)"/>
|
||||
<path d="M65.4148 32.2727L68.0057 39.6193H68.108L70.6989 32.2727H71.8068L68.6023 41H67.5114L64.3068 32.2727H65.4148ZM77.223 32.2727V41H76.1662V32.2727H77.223ZM85.0352 41H82.342V32.2727H85.1545C86.0011 32.2727 86.7255 32.4474 87.3278 32.7969C87.93 33.1435 88.3917 33.642 88.7127 34.2926C89.0337 34.9403 89.1942 35.7159 89.1942 36.6193C89.1942 37.5284 89.0323 38.3111 88.7085 38.9673C88.3846 39.6207 87.913 40.1236 87.2937 40.4759C86.6744 40.8253 85.9215 41 85.0352 41ZM83.3988 40.0625H84.967C85.6886 40.0625 86.2866 39.9233 86.761 39.6449C87.2354 39.3665 87.5891 38.9702 87.8221 38.456C88.055 37.9418 88.1715 37.3295 88.1715 36.6193C88.1715 35.9148 88.0565 35.3082 87.8263 34.7997C87.5962 34.2884 87.2525 33.8963 86.7951 33.6236C86.3377 33.348 85.7681 33.2102 85.0863 33.2102H83.3988V40.0625ZM93.967 41V32.2727H99.234V33.2102H95.0238V36.1591H98.9613V37.0966H95.0238V40.0625H99.3022V41H93.967ZM109.022 32.2727H111.127V37.902C111.127 38.5526 110.973 39.1193 110.663 39.6023C110.353 40.0824 109.922 40.4545 109.368 40.7188C108.814 40.9801 108.17 41.1108 107.437 41.1108C106.696 41.1108 106.048 40.9801 105.494 40.7188C104.94 40.4545 104.51 40.0824 104.203 39.6023C103.896 39.1193 103.743 38.5526 103.743 37.902V32.2727H105.852V37.7188C105.852 38.0199 105.917 38.2884 106.048 38.5241C106.181 38.7599 106.368 38.9446 106.606 39.0781C106.845 39.2116 107.122 39.2784 107.437 39.2784C107.752 39.2784 108.028 39.2116 108.264 39.0781C108.502 38.9446 108.689 38.7599 108.822 38.5241C108.956 38.2884 109.022 38.0199 109.022 37.7188V32.2727ZM117.559 32.2727V41H115.45V32.2727H117.559Z" fill="white"/>
|
||||
<g filter="url(#filter1_ddd_1_41)">
|
||||
<path d="M39.4019 47.5C40.5566 49.5 43.4434 49.5 44.5981 47.5L56.4546 26.9639C57.2786 25.5367 56.1196 23.7812 54.4834 23.9783L48.3548 24.7164C46.9029 24.8913 45.6622 25.8444 45.1191 27.2021L42 35L38 25L29.5166 23.9783C27.8804 23.7812 26.7214 25.5367 27.5454 26.9639L39.4019 47.5Z" fill="url(#paint1_radial_1_41)"/>
|
||||
<path d="M38 25L42 35L46 45.0718C45.1412 46.5593 42.9736 46.4973 42.2012 44.9632L31.7728 24.25L38 25Z" fill="url(#paint2_radial_1_41)"/>
|
||||
<path d="M56.4546 26.9639L44.5981 47.5C43.4434 49.5 40.5566 49.5 39.4019 47.5L38 45.0718L34 38.1436C35.6267 40.9319 39.7492 40.627 40.9481 37.6298L42 35L46 25L54.4834 23.9783C56.1196 23.7812 57.2786 25.5367 56.4546 26.9639Z" fill="url(#paint3_radial_1_41)"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_d_1_41" x="0" y="0" width="150" height="88" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feMorphology radius="4" operator="erode" in="SourceAlpha" result="effect1_dropShadow_1_41"/>
|
||||
<feOffset dx="2" dy="8"/>
|
||||
<feGaussianBlur stdDeviation="12"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0.0690196 0 0 0 0 0.313726 0 0 0 0.2 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1_41"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1_41" result="shape"/>
|
||||
</filter>
|
||||
<filter id="filter1_ddd_1_41" x="13.2732" y="11.9631" width="61.4536" height="57.0369" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dx="2" dy="4"/>
|
||||
<feGaussianBlur stdDeviation="8"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0.12549 0 0 0 0 0.313726 0 0 0 0.25 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1_41"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dx="1" dy="2"/>
|
||||
<feGaussianBlur stdDeviation="4"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0.12549 0 0 0 0 0.313726 0 0 0 0.15 0"/>
|
||||
<feBlend mode="normal" in2="effect1_dropShadow_1_41" result="effect2_dropShadow_1_41"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset/>
|
||||
<feGaussianBlur stdDeviation="1.5"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0.12549 0 0 0 0 0.313726 0 0 0 0.15 0"/>
|
||||
<feBlend mode="normal" in2="effect2_dropShadow_1_41" result="effect3_dropShadow_1_41"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect3_dropShadow_1_41" result="shape"/>
|
||||
</filter>
|
||||
<radialGradient id="paint0_radial_1_41" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(73 12) rotate(90) scale(48 58.022)">
|
||||
<stop stop-color="#3D506C"/>
|
||||
<stop offset="1" stop-color="#2F415C"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint1_radial_1_41" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(42 51.5) rotate(-90) scale(28 32.3316)">
|
||||
<stop stop-color="#2A57A2"/>
|
||||
<stop offset="1" stop-color="#407BBA"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint2_radial_1_41" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(39 24) rotate(74.3578) scale(25.9615 15.2089)">
|
||||
<stop stop-color="#1D314F"/>
|
||||
<stop offset="1" stop-color="#0D1A2E"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint3_radial_1_41" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(34 52) rotate(-49.3987) scale(36.8782 31.6434)">
|
||||
<stop stop-color="#5792D9"/>
|
||||
<stop offset="1" stop-color="#6AA8E9"/>
|
||||
</radialGradient>
|
||||
<clipPath id="clip0_1_41">
|
||||
<rect x="18" y="12" width="110" height="48" rx="8" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.9 KiB |
33
.cursor/rules/vide/public/logo.svg
Normal file
33
.cursor/rules/vide/public/logo.svg
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<svg width="200" height="200" viewBox="0 0 152 152" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_ii_282_2)">
|
||||
<path d="M63.659 130.625C69.1438 140.125 82.8561 140.125 88.341 130.625L144.659 33.0785C148.573 26.2993 143.068 17.9607 135.296 18.8969L106.185 22.4029C99.2887 23.2337 93.3954 27.7609 90.8157 34.21L76 71.25L57 23.75L16.7038 18.8969C8.93178 17.9607 3.42658 26.2993 7.34067 33.0785L63.659 130.625Z" fill="url(#paint0_radial_282_2)"/>
|
||||
<path d="M57 23.75L76 71.25L95 119.091C90.9207 126.157 80.6246 125.862 76.9557 118.575L27.4208 20.1875L57 23.75Z" fill="url(#paint1_radial_282_2)"/>
|
||||
<path d="M144.659 33.0785L88.3409 130.625C82.8561 140.125 69.1438 140.125 63.659 130.625L57 119.091L38 86.1821C45.7268 99.4265 65.3087 97.9783 71.0034 83.7416L76 71.25L95 23.75L135.296 18.8969C143.068 17.9607 148.573 26.2993 144.659 33.0785Z" fill="#4896F3"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_ii_282_2" x="6.04761" y="18.8246" width="163.905" height="166.925" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dx="24" dy="48"/>
|
||||
<feGaussianBlur stdDeviation="96"/>
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0.12549 0 0 0 0 0.313726 0 0 0 0.2 0"/>
|
||||
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_282_2"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dx="12" dy="24"/>
|
||||
<feGaussianBlur stdDeviation="48"/>
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0.12549 0 0 0 0 0.313726 0 0 0 0.1 0"/>
|
||||
<feBlend mode="normal" in2="effect1_innerShadow_282_2" result="effect2_innerShadow_282_2"/>
|
||||
</filter>
|
||||
<radialGradient id="paint0_radial_282_2" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(76 149.625) rotate(-90) scale(133 153.575)">
|
||||
<stop stop-color="#24447F"/>
|
||||
<stop offset="1" stop-color="#3661A2"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint1_radial_282_2" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(61.75 19) rotate(74.3578) scale(123.317 72.2423)">
|
||||
<stop stop-color="#1D314F"/>
|
||||
<stop offset="1" stop-color="#0D1A2E"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
148
.cursor/rules/vide/tut/advanced/dynamic-scopes.md
Normal file
148
.cursor/rules/vide/tut/advanced/dynamic-scopes.md
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
# Dynamic Scopes
|
||||
|
||||
Dynamic scopes are scopes that are created and destroyed in response to
|
||||
source updates. This is needed for conditionally rendering parts of your UI,
|
||||
such as opening and closing menus.
|
||||
|
||||
While Vide provides functions for common ways to do this, this section will
|
||||
show how you can implement them yourself so you are not limited by only what is
|
||||
provided.
|
||||
|
||||
## Recreating [`show()`](/api/reactivity-dynamic#show-reactive)
|
||||
|
||||
The most basic one, `show()`, can be
|
||||
implemented yourself like so:
|
||||
|
||||
```luau
|
||||
local function show(toggle: () -> unknown, component: () -> Instance)
|
||||
return derive(function()
|
||||
return if toggle() then untrack(component) else nil
|
||||
end)
|
||||
end
|
||||
```
|
||||
|
||||
The main thing to note here is the use of `untrack()`. This function runs its
|
||||
callback in a new stable scope. Without this, if the component were to create
|
||||
a reactive scope, an error would occur since a reactive scope cannot be created
|
||||
within a reactive scope.
|
||||
|
||||
```mermaid
|
||||
%%{init: {
|
||||
"theme": "base",
|
||||
"themeVariables": {
|
||||
"primaryColor": "#111720",
|
||||
"primaryTextColor": "#fff",
|
||||
"primaryBorderColor": "#444455",
|
||||
"lineColor": "#79B8FF",
|
||||
"tertiaryColor": "#0d131b",
|
||||
"tertiaryBorderColor": "#444455"
|
||||
}
|
||||
}}%%
|
||||
|
||||
graph
|
||||
|
||||
subgraph derive ["derive (reactive)"]
|
||||
|
||||
subgraph untrack ["untrack (stable)"]
|
||||
subgraph effect ["effect (reactive)"]
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
You can see from the above graph how the effect would not be created directly
|
||||
inside the derive, there is a stable scope between them. This requirement exists
|
||||
as a guard against unintentional rerendering of UI.
|
||||
|
||||
## Recreating [`switch()`](/api/reactivity-dynamic#switch-reactive)
|
||||
|
||||
```luau
|
||||
local function switch(key)
|
||||
return function(map)
|
||||
return derive(function()
|
||||
local component = map[key()]
|
||||
return if component then untrack(component) else nil
|
||||
end)
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
## Recreating [`indexes()`](/api/reactivity-dynamic#indexes-reactive)
|
||||
|
||||
This is a more complicated function because it manages multiple scopes at the
|
||||
same time, unlike the previous functions. Because some scopes may persist
|
||||
between reruns, we cannot use `untrack()` anymore which automatically destroys
|
||||
on rerun; we must use `root()` where the lifetime of each scope is managed
|
||||
manually and independently.
|
||||
|
||||
```luau
|
||||
local function indexes<I, VI, VO>(
|
||||
input: () -> Map<I, VI>,
|
||||
transform: (value: () -> VI, index: I) -> VO
|
||||
)
|
||||
local index_caches = {} :: Map<I, {
|
||||
previous_input: VI,
|
||||
output: VO,
|
||||
source: (VI) -> VI,
|
||||
destroy: () -> ()
|
||||
}?>
|
||||
|
||||
-- destroy all scopes if the parent scope is destroyed
|
||||
cleanup(function()
|
||||
for _, cache in index_caches do
|
||||
assert(cache).destroy()
|
||||
end
|
||||
end)
|
||||
|
||||
return derive(function()
|
||||
local new_input = input()
|
||||
|
||||
-- destroy scopes of removed indexes
|
||||
for i, cache in index_caches do
|
||||
if new_input[i] == nil then
|
||||
assert(cache).destroy()
|
||||
index_caches[i] = nil
|
||||
end
|
||||
end
|
||||
|
||||
-- create scopes or update sources of added or changed index values
|
||||
for i, v in new_input do
|
||||
local cache = index_caches[i]
|
||||
|
||||
if cache == nil then -- no scope created for this index, create one
|
||||
local src = source(v)
|
||||
|
||||
local destroy, result = root(function()
|
||||
return transform(src, i)
|
||||
end)
|
||||
|
||||
index_caches[i] = {
|
||||
destroy = destroy,
|
||||
source = src,
|
||||
output = result,
|
||||
previous_input = v
|
||||
}
|
||||
elseif cache.previous_input ~= v then -- scope exists, update source
|
||||
cache.previous_input = v
|
||||
cache.source(v)
|
||||
else -- scope exists and value has not changed; do nothing
|
||||
end
|
||||
end
|
||||
|
||||
-- return the cached output values as an array
|
||||
local array = table.create(#index_caches)
|
||||
|
||||
for _, cache in index_caches do
|
||||
table.insert(array, assert(cache).output)
|
||||
end
|
||||
|
||||
return array
|
||||
end)
|
||||
end
|
||||
```
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Though the above functions are already provided to you by Vide, this serves as
|
||||
an example for how you may create your own dynamic scope functions.
|
||||
21
.cursor/rules/vide/tut/crash-course/1-introduction.md
Normal file
21
.cursor/rules/vide/tut/crash-course/1-introduction.md
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# Introduction
|
||||
|
||||
This is a tutorial that introduces the concepts and usage of Vide.
|
||||
|
||||
Vide is heavily inspired by [Solid](https://www.solidjs.com/).
|
||||
|
||||
## Why Vide?
|
||||
|
||||
Vide's reactive and declarative API aims to let you program UI as simply as
|
||||
possible, with a strong focus on how data flows through your application.
|
||||
|
||||
Some of Vide's main design choices:
|
||||
|
||||
- Syntax minimal.
|
||||
- Data oriented.
|
||||
- Typechecking compatible.
|
||||
- Instance independent.
|
||||
|
||||
Vide's reactivity operates with the concept
|
||||
of scopes which carries a learning curve, though is what makes Vide's minimal
|
||||
syntax possible. The crash course will introduce these concepts gradually.
|
||||
38
.cursor/rules/vide/tut/crash-course/10-cleanup.md
Normal file
38
.cursor/rules/vide/tut/crash-course/10-cleanup.md
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
# Cleanup
|
||||
|
||||
Sometimes you may need to do some cleanup when destroying a component or after
|
||||
a side-effect from a source update. Vide provides a function `cleanup()` which
|
||||
is used to queue a callback for the next time a reactive scope is rerun or
|
||||
destroyed, or when a stable scope is destroyed.
|
||||
|
||||
```luau
|
||||
local root = vide.root
|
||||
local source = vide.source
|
||||
local effect = vide.effect
|
||||
local cleanup = vide.cleanup
|
||||
|
||||
local count = source(0)
|
||||
|
||||
local destroy = root(function()
|
||||
effect(function()
|
||||
local x = count()
|
||||
cleanup(function() print(x) end)
|
||||
end)
|
||||
|
||||
cleanup(function() print "root destroyed" end)
|
||||
end)
|
||||
|
||||
count(1) -- prints "0"
|
||||
count(2) -- prints "1"
|
||||
destroy() -- prints "2" and "root destroyed"
|
||||
```
|
||||
|
||||
::: tip
|
||||
Roblox instances do not need to be explicitly destroyed for their
|
||||
memory to be freed, they only need to be parented to `nil`. So there is no
|
||||
need to use `cleanup()` to destroy instances. However, be wary of connecting
|
||||
a function that references an instance to an event from the same instance,
|
||||
this causes the instance to reference itself and never be freed. In such a case
|
||||
you would need to use `cleanup()` to disconnect this connection or to explicitly
|
||||
destroy the instance.
|
||||
:::
|
||||
211
.cursor/rules/vide/tut/crash-course/11-dynamic-scopes.md
Normal file
211
.cursor/rules/vide/tut/crash-course/11-dynamic-scopes.md
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
# Dynamic Scopes
|
||||
|
||||
Eventually you may need a way to dynamically create and destroy UI elements
|
||||
resulting from source updates. Vide provides functions to help you do this,
|
||||
known as *dynamic scope* functions.
|
||||
|
||||
These functions create and destroy scopes for you in response to source
|
||||
updates. They return a source containing the created component. This source can
|
||||
be parented as a child which will update the shown children whenever the source
|
||||
updates.
|
||||
|
||||
The simplest example is using `show()`.
|
||||
|
||||
```luau
|
||||
local source = vide.source
|
||||
local create = vide.create
|
||||
local show = vide.show
|
||||
local root = vide.root
|
||||
|
||||
function Button(props: { Text: string, Activated: () -> () })
|
||||
return create "TextButton" {
|
||||
Text = props.Text,
|
||||
Activated = props.Activated
|
||||
}
|
||||
end
|
||||
|
||||
function Menu()
|
||||
return create "TextLabel" {
|
||||
Text = "This is a menu"
|
||||
}
|
||||
end
|
||||
|
||||
function App()
|
||||
local toggled = source(false)
|
||||
|
||||
return create "ScreenGui" {
|
||||
Button {
|
||||
Text = "Toggle Menu",
|
||||
Activated = function()
|
||||
toggled(not toggled())
|
||||
end
|
||||
},
|
||||
|
||||
show(toggled, Menu) -- [!code highlight]
|
||||
}
|
||||
end
|
||||
|
||||
root(function()
|
||||
App().Parent = game.StarterGui
|
||||
end)
|
||||
```
|
||||
|
||||
This is a complete example of rendering UI which has a single button that
|
||||
toggles the opening of a menu.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Another common function is `indexes()`. This function creates a component for
|
||||
each index in a table.
|
||||
|
||||
Each component created is done so in a new and independent stable scope. The
|
||||
indexes of the table are checked each source update to prevent redunant
|
||||
destruction and recreation of UI elements.
|
||||
|
||||
```luau
|
||||
local source = vide.source
|
||||
local create = vide.create
|
||||
local indexes = vide.indexes
|
||||
local root = vide.root
|
||||
|
||||
local function Todo(props: {
|
||||
Text: () -> string,
|
||||
Position: number,
|
||||
Activated: () -> ()
|
||||
})
|
||||
return create "TextButton" {
|
||||
Text = function() return props.Position .. ": " .. props.Text() end,
|
||||
LayoutOrder = props.Position,
|
||||
Activated = Activated
|
||||
}
|
||||
end
|
||||
|
||||
local function TodoList(props: { List: () -> Array<string> })
|
||||
return create "Frame" {
|
||||
create "UIListLayout" {},
|
||||
|
||||
indexes(props.List, function(text, i) -- [!code highlight]
|
||||
return Todo {
|
||||
Text = text,
|
||||
Position = i,
|
||||
Activated = function() -- remove the todo when clicked
|
||||
local list = props.List()
|
||||
table.remove(list, i)
|
||||
props.List(list)
|
||||
end
|
||||
}
|
||||
end)
|
||||
}
|
||||
end
|
||||
|
||||
function App()
|
||||
local list = source {
|
||||
"finish the crash course",
|
||||
"star Vide's GitHub"
|
||||
}
|
||||
|
||||
return create "ScreenGui" {
|
||||
TodoList { List = list },
|
||||
}
|
||||
end
|
||||
|
||||
root(function()
|
||||
App().Parent = game.StarterGui
|
||||
end)
|
||||
```
|
||||
|
||||
The reactive graph for the above example:
|
||||
|
||||
```mermaid
|
||||
%%{init: {
|
||||
"theme": "base",
|
||||
"themeVariables": {
|
||||
"primaryColor": "#111720",
|
||||
"primaryTextColor": "#fff",
|
||||
"primaryBorderColor": "#111720",
|
||||
"lineColor": "#79B8FF",
|
||||
"tertiaryColor": "#0d131b",
|
||||
"tertiaryBorderColor": "#0d131b"
|
||||
}
|
||||
}}%%
|
||||
|
||||
graph
|
||||
|
||||
subgraph root ["root"]
|
||||
direction LR
|
||||
todoList --> indexes -.- subroot1 & subroot2
|
||||
|
||||
subgraph subroot1 ["indexes scope 1"]
|
||||
direction LR
|
||||
value1[todo] --> prop1["prop binding"]
|
||||
end
|
||||
|
||||
subgraph subroot2 ["indexes scope 2"]
|
||||
direction LR
|
||||
value2[todo] --> prop2[prop binding]
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
When you edit a table in a source, you must set that table again to actually
|
||||
update the source.
|
||||
|
||||
```luau
|
||||
local src = source { 1, 2 }
|
||||
local data = src()
|
||||
table.insert(data, 3) -- no effects will run
|
||||
src(data) -- effects will run
|
||||
```
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
All dynamic scope functions also support delaying the destruction of the scope.
|
||||
This is useful for playing any sort of animation or effect before the UI
|
||||
instance is removed.
|
||||
|
||||
If you have the following code, for example:
|
||||
|
||||
```lua
|
||||
local function Menu()
|
||||
return create "Frame" {}
|
||||
end
|
||||
|
||||
local toggled = source(true)
|
||||
|
||||
create "ScreenGui" {
|
||||
show(toggled, function()
|
||||
return Menu {}
|
||||
end)
|
||||
}
|
||||
|
||||
toggled(false) -- menu will disappear immediately
|
||||
```
|
||||
|
||||
```lua
|
||||
local function Menu(props: { Visible: () -> boolean })
|
||||
local transparency = spring(function()
|
||||
return if p.Visible then 0 else 1
|
||||
end
|
||||
|
||||
return create "Frame" {
|
||||
BackgroundTransparency = transparency
|
||||
}
|
||||
end
|
||||
|
||||
local toggled = source(true)
|
||||
|
||||
create "ScreenGui" {
|
||||
show(toggled, function(_, present)
|
||||
return Menu { p.Visible = present }, 3 -- give a generous 3 seconds for the spring to complete before destroying
|
||||
end)
|
||||
}
|
||||
|
||||
toggled(false)
|
||||
-- `present` will go `false` immediately
|
||||
-- transparency will begin being sprung
|
||||
-- after 3 seconds the scope is destroyed, giving the spring enough time to complete
|
||||
```
|
||||
|
||||
If `toggled` goes from truthy to falsey, beginning the timer, but then back
|
||||
to truthy before the timer finishes, the timer is cancelled and the scope is
|
||||
not destroyed.
|
||||
55
.cursor/rules/vide/tut/crash-course/12-actions.md
Normal file
55
.cursor/rules/vide/tut/crash-course/12-actions.md
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
# Actions
|
||||
|
||||
Actions are special callbacks that you can pass along with properties,
|
||||
to run some code on an instance receiving them.
|
||||
|
||||
```luau
|
||||
local action = vide.action
|
||||
|
||||
create "TextLabel" {
|
||||
Text = "test",
|
||||
|
||||
action(function(instance)
|
||||
print(instance.Text)
|
||||
end)
|
||||
}
|
||||
|
||||
-- will print "test"
|
||||
```
|
||||
|
||||
Actions can be wrapped with functions for reuse. Below is an example of an
|
||||
action used to listen for property changes:
|
||||
|
||||
```luau
|
||||
local action = vide.action
|
||||
local source = vide.source
|
||||
local effect = vide.effect
|
||||
local cleanup = vide.cleanup
|
||||
|
||||
local function changed(property: string, callback: (new) -> ())
|
||||
return action(function(instance)
|
||||
local connection = instance:GetPropertyChangedSignal(property):Connect(function()
|
||||
callback(instance[property])
|
||||
end)
|
||||
|
||||
-- remember to clean up the connection when the reactive scope the action
|
||||
-- is ran in is destroyed, so the instance can be garbage collected
|
||||
cleanup(connection)
|
||||
end)
|
||||
end
|
||||
|
||||
local output = source ""
|
||||
|
||||
local instance = create "TextBox" {
|
||||
changed("Text", output)
|
||||
}
|
||||
|
||||
effect(function()
|
||||
print(output())
|
||||
end)
|
||||
|
||||
instance.Text = "foo" -- "foo" will be printed by the effect
|
||||
```
|
||||
|
||||
The source `output` will be updated with the new property value any time it is
|
||||
changed externally.
|
||||
36
.cursor/rules/vide/tut/crash-course/13-strict-mode.md
Normal file
36
.cursor/rules/vide/tut/crash-course/13-strict-mode.md
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
# Strict Mode
|
||||
|
||||
While developing UI with Vide, you should use Vide's strict mode, which can
|
||||
be set with `vide.strict = true` once when you first require Vide. Strict mode
|
||||
will add extra safety checks and emit better error traces, particularly when
|
||||
errors occur in property bindings.
|
||||
|
||||
Strict mode is automatically enabled when Vide is required in O0 or O1
|
||||
optimization (default studio level). You can `vide.strict = false` if you do not
|
||||
want this.
|
||||
|
||||
Strict mode will run derived sources and effects twice each time they update.
|
||||
This is to help ensure that derived source computations are pure, and that any
|
||||
cleanups made in derived sources or effects are done properly.
|
||||
|
||||
```luau
|
||||
local source = vide.source
|
||||
local effect = vide.effect
|
||||
|
||||
vide.strict = true
|
||||
|
||||
local count = source(0)
|
||||
|
||||
local ran = 0
|
||||
effect(function()
|
||||
count()
|
||||
ran += 1
|
||||
end)
|
||||
|
||||
print(ran) -- 2
|
||||
count(1)
|
||||
print(ran) -- 4
|
||||
```
|
||||
|
||||
A full list of what strict mode will do can be found
|
||||
[here](../../api/strict-mode).
|
||||
113
.cursor/rules/vide/tut/crash-course/14-concepts.md
Normal file
113
.cursor/rules/vide/tut/crash-course/14-concepts.md
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
# Concepts Summary
|
||||
|
||||
A summary of all the concepts covered during the crash course.
|
||||
|
||||
## Source
|
||||
|
||||
A source of data.
|
||||
|
||||
Stores a single value that can be updated.
|
||||
|
||||
Created with `source()`.
|
||||
|
||||
## Derived Source
|
||||
|
||||
A new source composed of other sources.
|
||||
|
||||
Created with a plain function or with `derive()`.
|
||||
|
||||
## Effect
|
||||
|
||||
Anything that happens in response to a source update.
|
||||
|
||||
Created with `effect()`.
|
||||
|
||||
## Stable Scope
|
||||
|
||||
One of the two types of Vide scopes.
|
||||
|
||||
Created by:
|
||||
|
||||
- `root()`
|
||||
- `untrack()`
|
||||
- `show()`
|
||||
- `indexes()`
|
||||
|
||||
Stable scopes do not track sources and never rerun.
|
||||
|
||||
New stable or reactive scopes can be created within a stable scope.
|
||||
|
||||
## Reactive Scope
|
||||
|
||||
Created by:
|
||||
|
||||
- `effect()`
|
||||
- `derive()`
|
||||
|
||||
Reactive scopes do track sources and will rerun when those sources update.
|
||||
|
||||
Reactive scopes cannot be created within a reactive scope, but stable scopes
|
||||
can be created within a reactive scope.
|
||||
|
||||
## Scope Cleanup
|
||||
|
||||
When a scope is rerun or destroyed, all scopes created within it are
|
||||
automatically destroyed.
|
||||
|
||||
Any functions queued by `cleanup()` are also ran.
|
||||
|
||||
## Reactive Graph
|
||||
|
||||
The combination of stable and reactive scopes can viewed graphically, called a
|
||||
*reactive graph*. This can be a more intuitive way to think of the
|
||||
relationships between effects and the sources they depend on.
|
||||
|
||||
### Code
|
||||
|
||||
```luau
|
||||
local count = source(0)
|
||||
|
||||
root(function()
|
||||
local text = derive(function()
|
||||
return "count: " .. count()
|
||||
end)
|
||||
|
||||
effect(function()
|
||||
print(text())
|
||||
end)
|
||||
end)
|
||||
```
|
||||
|
||||
### Graph resulting from code
|
||||
|
||||
```mermaid
|
||||
%%{init: {
|
||||
"theme": "base",
|
||||
"themeVariables": {
|
||||
"primaryColor": "#111720",
|
||||
"primaryTextColor": "#fff",
|
||||
"primaryBorderColor": "#111720",
|
||||
"lineColor": "#79B8FF",
|
||||
"tertiaryColor": "#0d131b",
|
||||
"tertiaryBorderColor": "#202530"
|
||||
}
|
||||
}}%%
|
||||
|
||||
graph LR
|
||||
|
||||
subgraph root
|
||||
text --> effect
|
||||
end
|
||||
|
||||
count --> text
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- Since `count` is a source, not an effect, it can exist
|
||||
outside of scopes.
|
||||
- An update to `count` will cause `text` to rerun, which
|
||||
then causes `effect` to rerun.
|
||||
- When the root scope is destroyed, `text` and
|
||||
`effect` will be destroyed alongside it, since they were created within it.
|
||||
`count` will be untouched and future updates to `count` will have no effect.
|
||||
38
.cursor/rules/vide/tut/crash-course/2-creation.md
Normal file
38
.cursor/rules/vide/tut/crash-course/2-creation.md
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
# Creating UI
|
||||
|
||||
Instances are created using `create()`.
|
||||
|
||||
Parentheses `()` can be omitted when calling functions with string or
|
||||
table literals for brevity.
|
||||
|
||||
```luau
|
||||
local create = vide.create
|
||||
|
||||
return create "ScreenGui" {
|
||||
create "Frame" {
|
||||
AnchorPoint = Vector2.new(0.5, 0.5),
|
||||
Position = UDim2.fromScale(0.5, 0.5),
|
||||
Size = UDim2.fromScale(0.4, 0.7),
|
||||
|
||||
create "TextLabel" {
|
||||
Text = "hi"
|
||||
},
|
||||
|
||||
create "TextLabel" {
|
||||
Text = "bye"
|
||||
},
|
||||
|
||||
create "TextButton" {
|
||||
Text = "click me",
|
||||
|
||||
Activated = function()
|
||||
print "clicked!"
|
||||
end
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Assign a value to a string key to set a property, and assign a value to a
|
||||
number key to set a child. Events can be connected to by assigning a function
|
||||
to a string key.
|
||||
65
.cursor/rules/vide/tut/crash-course/3-components.md
Normal file
65
.cursor/rules/vide/tut/crash-course/3-components.md
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
# Components
|
||||
|
||||
Vide encourages separating different parts of your UI into functions called
|
||||
*components*.
|
||||
|
||||
A component is a function that creates and returns a piece of UI.
|
||||
|
||||
This is a way to separate your UI into small chunks that you can reuse and put
|
||||
together.
|
||||
|
||||
::: code-group
|
||||
|
||||
```luau [Button.luau]
|
||||
local create = vide.create
|
||||
|
||||
local function Button(props: {
|
||||
Position: UDim2,
|
||||
Text: string,
|
||||
Activated: () -> ()
|
||||
})
|
||||
return create "TextButton" {
|
||||
BackgroundColor3 = Color3.fromRGB(50, 50, 50),
|
||||
TextColor3 = Color3.fromRGB(255, 255, 255),
|
||||
Size = UDim2.fromOffset(200, 150),
|
||||
|
||||
Position = props.Position,
|
||||
Text = props.Text,
|
||||
Activated = props.Activated,
|
||||
|
||||
create "UICorner" {}
|
||||
}
|
||||
end
|
||||
|
||||
return Button
|
||||
```
|
||||
|
||||
```luau [Menu.luau]
|
||||
local create = vide.create
|
||||
|
||||
local Button = require(Button)
|
||||
|
||||
local function Menu()
|
||||
return create "ScreenGui" {
|
||||
Button {
|
||||
Position = UDim2.fromOffset(200, 200),
|
||||
Text = "back",
|
||||
Activated = function()
|
||||
print "go to previous page"
|
||||
end
|
||||
},
|
||||
|
||||
Button {
|
||||
Position = UDim2.fromOffset(400, 200),
|
||||
Text = "next",
|
||||
Activated = function()
|
||||
print "go to next page"
|
||||
end
|
||||
}
|
||||
}
|
||||
end
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
A single parameter `props` is used to pass properties to the component.
|
||||
38
.cursor/rules/vide/tut/crash-course/4-source.md
Normal file
38
.cursor/rules/vide/tut/crash-course/4-source.md
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
# Sources
|
||||
|
||||
Sources are special objects that store a single value and are the core of
|
||||
Vide's reactivity.
|
||||
|
||||
A source can be created using `source()`.
|
||||
|
||||
```luau
|
||||
local source = vide.source
|
||||
|
||||
local count = source(0)
|
||||
```
|
||||
|
||||
The value passed to `source()` is the initial value of the source.
|
||||
|
||||
The value of a source can be set by calling it with an argument, and can be read
|
||||
by calling it with no arguments.
|
||||
|
||||
```luau
|
||||
count(count() + 1) -- increment count by 1
|
||||
```
|
||||
|
||||
Sources can be *derived* by wrapping them in functions.
|
||||
|
||||
```luau
|
||||
local count = source(0)
|
||||
|
||||
local text = function()
|
||||
return "count: " .. tostring(count())
|
||||
end
|
||||
|
||||
print(text()) -- "count: 0"
|
||||
count(1)
|
||||
print(text()) -- "count: 1"
|
||||
```
|
||||
|
||||
While the above can be achieved with plain variables, the use for sources will
|
||||
be obvious in the next part.
|
||||
71
.cursor/rules/vide/tut/crash-course/5-effect.md
Normal file
71
.cursor/rules/vide/tut/crash-course/5-effect.md
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
# Effects
|
||||
|
||||
Effects are functions that are ran in response to source updates.
|
||||
A source and effect is analogous to a signal and connection.
|
||||
|
||||
Effects are created using `effect()`.
|
||||
|
||||
```luau
|
||||
local source = vide.source
|
||||
local effect = vide.effect
|
||||
|
||||
local count = source(0)
|
||||
|
||||
effect(function()
|
||||
print("count: " .. count())
|
||||
end)
|
||||
|
||||
-- "count: 0" printed
|
||||
count(1)
|
||||
-- "count: 1" printed
|
||||
```
|
||||
|
||||
Any source read inside an effect is tracked and will rerun the effect when
|
||||
that source is updated.
|
||||
|
||||
The effect runs its callback once immediately to initially figure out what
|
||||
sources are being read.
|
||||
|
||||
Derived sources are also tracked, it does not matter how deeply nested
|
||||
inside a function a source is.
|
||||
|
||||
```luau
|
||||
local source = vide.source
|
||||
local effect = vide.effect
|
||||
|
||||
local count = source(1)
|
||||
|
||||
local doubled = function()
|
||||
return count() * 2
|
||||
end
|
||||
|
||||
effect(function()
|
||||
print("doubled count: " .. doubled())
|
||||
end)
|
||||
|
||||
-- "doubled count: 2" printed
|
||||
count(2)
|
||||
-- "doubled count: 4" printed
|
||||
```
|
||||
|
||||
If a source is updated with the same value it already had, it will not rerun
|
||||
effects depending on it.
|
||||
|
||||
You can also read from a source within an effect without the effect tracking it.
|
||||
|
||||
```luau
|
||||
local source = vide.source
|
||||
local effect = vide.effect
|
||||
local untrack = vide.untrack
|
||||
|
||||
local a = source(0)
|
||||
local b = source(0)
|
||||
|
||||
effect(function()
|
||||
print(`a: {a()} b: {untrack(b)}`)
|
||||
end)
|
||||
|
||||
a(1) -- prints "a: 1 b: 0"
|
||||
b(1) -- prints nothing
|
||||
a(2) -- prints "a: 2 b: 1"
|
||||
```
|
||||
88
.cursor/rules/vide/tut/crash-course/6-scope.md
Normal file
88
.cursor/rules/vide/tut/crash-course/6-scope.md
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
# Scopes
|
||||
|
||||
Just like how a signal's connection may need to be disconnected, a source's
|
||||
effect also may need to be disconnected.
|
||||
|
||||
But the disconnecting of many signals and connections is tedious and verbose.
|
||||
Vide instead operates on the concept of scopes which provides a much cleaner
|
||||
API, given that you follow a few rules.
|
||||
|
||||
There are two types of scopes: stable and reactive.
|
||||
|
||||
- A scope must be created within another scope.
|
||||
- Stable scopes never rerun.
|
||||
- Reactive scopes can rerun.
|
||||
- A reactive scope cannot be created within another reactive scope, only within
|
||||
a stable scope.
|
||||
|
||||
An exception to the first rule is `root()`, which creates the initial scope that
|
||||
you destroy manually with a destructor function it returns.
|
||||
|
||||
`root()` creates a stable scope.
|
||||
`effect()` creates a reactive scope.
|
||||
|
||||
Whenever a scope is destroyed, any scope created within that scope is also
|
||||
destroyed, and so on.
|
||||
|
||||
```luau
|
||||
local root = vide.root
|
||||
local source = vide.source
|
||||
local effect = vide.effect
|
||||
|
||||
local count = source(0)
|
||||
|
||||
local function setup()
|
||||
effect(function()
|
||||
print(count())
|
||||
end)
|
||||
end
|
||||
|
||||
setup() -- error, effect() tried to create a reactive scope with no stable scope
|
||||
|
||||
local destroy = root(setup) -- ok since effect() was called in a stable scope
|
||||
|
||||
count(1) -- prints "1"
|
||||
count(2) -- prints "2"
|
||||
|
||||
destroy()
|
||||
|
||||
count(3) -- reactive scope created by effect() is destroyed, it does not rerun
|
||||
```
|
||||
|
||||
Vide's reactivity can be represented graphically, as a *reactive graph*.
|
||||
|
||||
The reactive graph for the above example looks like so:
|
||||
|
||||
```mermaid
|
||||
%%{init: {
|
||||
"theme": "base",
|
||||
"themeVariables": {
|
||||
"primaryColor": "#111720",
|
||||
"primaryTextColor": "#fff",
|
||||
"primaryBorderColor": "#111720",
|
||||
"lineColor": "#79B8FF",
|
||||
"tertiaryColor": "#0d131b",
|
||||
"tertiaryBorderColor": "#0d131b"
|
||||
}
|
||||
}}%%
|
||||
|
||||
graph
|
||||
|
||||
subgraph root
|
||||
direction LR
|
||||
count --> effect
|
||||
end
|
||||
```
|
||||
|
||||
When the stable `root()` scope is destroyed, the reactive `effect()`
|
||||
scope will also be destroyed since it was created within it.
|
||||
|
||||
This is important because you may have an effect that updates the property of a
|
||||
UI instance, meaning the effect is referencing and holding that instance in
|
||||
memory. The effect being destroyed will remove this reference, allowing the
|
||||
instance to be garbage collected.
|
||||
|
||||
You don't need to worry about ensuring all your effects are created within a
|
||||
stable scope, since you should be creating all your UI and effects within a
|
||||
single top-level `root()` call that puts all your UI together, making it safe to
|
||||
assume any effect created will be created under this stable scope.
|
||||
60
.cursor/rules/vide/tut/crash-course/7-reactive-component.md
Normal file
60
.cursor/rules/vide/tut/crash-course/7-reactive-component.md
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
# Reactive Components
|
||||
|
||||
Reactive components in Vide are created using sources and effects - sources to
|
||||
store the data, and effects to display the data.
|
||||
|
||||
```luau
|
||||
local create = vide.create
|
||||
local source = vide.source
|
||||
local effect = vide.effect
|
||||
|
||||
local function Counter()
|
||||
local count = source(0)
|
||||
|
||||
local instance = create "TextButton" {
|
||||
Activated = function()
|
||||
count(count() + 1)
|
||||
end
|
||||
}
|
||||
|
||||
effect(function()
|
||||
instance.Text = "count: " .. count()
|
||||
end)
|
||||
|
||||
return instance
|
||||
end
|
||||
```
|
||||
|
||||
Above is an example of a counter component, that when clicked, will increment
|
||||
its internal count, and automatically update its text to reflect that count.
|
||||
|
||||
Each instance of `Counter()` will maintain its own independent count, since the
|
||||
count source is created inside the component.
|
||||
|
||||
External sources can also be passed into components for them to use.
|
||||
|
||||
```luau
|
||||
local function CountDisplay(props: { count: () -> number })
|
||||
local count = props.count
|
||||
|
||||
local instance = create "TextLabel" {}
|
||||
|
||||
effect(function()
|
||||
instance.Text = "count: " .. count()
|
||||
end)
|
||||
|
||||
return instance
|
||||
end
|
||||
|
||||
local count = source(0)
|
||||
|
||||
CountDisplay {
|
||||
count = count
|
||||
}
|
||||
|
||||
count(1) -- the CountDisplay component will update to display this count
|
||||
```
|
||||
|
||||
Sources can be created internally or passed in from externally, there are no
|
||||
restrictions on how they are used as long as the effect using it is created
|
||||
within a stable scope.
|
||||
83
.cursor/rules/vide/tut/crash-course/8-implicit-effect.md
Normal file
83
.cursor/rules/vide/tut/crash-course/8-implicit-effect.md
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
# Implicit Effects
|
||||
|
||||
Explicitly creating effects to update properties is tedious. You can
|
||||
*implicitly* create an effect to update properties instead.
|
||||
|
||||
::: code-group
|
||||
|
||||
```luau [Implicit Effect]
|
||||
local create = vide.create
|
||||
local source = vide.source
|
||||
|
||||
local function Counter()
|
||||
local count = source(0)
|
||||
|
||||
return create "TextButton" {
|
||||
Activated = function()
|
||||
count(count() + 1)
|
||||
end,
|
||||
|
||||
Text = function()
|
||||
return "count: " .. count()
|
||||
end
|
||||
}
|
||||
end
|
||||
```
|
||||
|
||||
```luau [Explicit Effect]
|
||||
local create = vide.create
|
||||
local source = vide.source
|
||||
local effect = vide.effect
|
||||
|
||||
local function Counter()
|
||||
local count = source(0)
|
||||
|
||||
local instance = create "TextButton" {
|
||||
Activated = function()
|
||||
count(count() + 1)
|
||||
end
|
||||
}
|
||||
|
||||
effect(function()
|
||||
instance.Text = "count: " .. count()
|
||||
end)
|
||||
|
||||
return instance
|
||||
end
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
This example is equivalent to the example seen on the previous page.
|
||||
|
||||
Instead of explicitly creating an effect, assigning a (non-event) property a
|
||||
function will implicitly create an effect to update that property.
|
||||
|
||||
## Children
|
||||
|
||||
Children can also be set in a similar manner. A source passed as a child (passed
|
||||
with a number key instead of string key) can return an instance or an array of
|
||||
instances. An effect is automatically created to unparent removed instances and
|
||||
parent new instances on source update.
|
||||
|
||||
```luau
|
||||
local items = source {
|
||||
create "TextLabel" { Text = "A" }
|
||||
}
|
||||
|
||||
local function List(props: { children: () -> { Instance } })
|
||||
return create "Frame" {
|
||||
create "UIListLayout" {},
|
||||
props.children
|
||||
}
|
||||
end
|
||||
|
||||
local list = List { children = items } -- creates a list with text label "A"
|
||||
|
||||
items {
|
||||
create "TextLabel" { Text = "B" },
|
||||
create "TextLabel" { Text = "C" }
|
||||
}
|
||||
|
||||
-- this will automatically unparent text label "A", and parent labels "B" and "C"
|
||||
```
|
||||
93
.cursor/rules/vide/tut/crash-course/9-derived-source.md
Normal file
93
.cursor/rules/vide/tut/crash-course/9-derived-source.md
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
# Derived Sources
|
||||
|
||||
We have seen the basic way to derive a source:
|
||||
|
||||
```luau
|
||||
local count = source(0)
|
||||
|
||||
local text = function()
|
||||
return "count: " .. tostring(count())
|
||||
end
|
||||
|
||||
print(text()) -- "count: 0"
|
||||
count(1)
|
||||
print(text()) -- "count: 1"
|
||||
```
|
||||
|
||||
However, in some cases where this source could be used by multiple effects at
|
||||
the same time, the function wrapping the source will needlessly rerun to convert
|
||||
the count into a string for each effect using it.
|
||||
|
||||
```luau
|
||||
local source = vide.source
|
||||
local effect = vide.effect
|
||||
|
||||
local count = source(0)
|
||||
|
||||
local text = function()
|
||||
print "ran"
|
||||
return "count: " .. tostring(count())
|
||||
end
|
||||
|
||||
effect(function() text() end)
|
||||
effect(function() text() end)
|
||||
|
||||
count(1) -- prints "ran" x2
|
||||
```
|
||||
|
||||
To avoid this, you can use `derive()` to derive a new source instead. This will
|
||||
run a function in a reactive scope only when a source used inside updated.
|
||||
Reading this derived source multiple times will just return a cached result.
|
||||
|
||||
```luau
|
||||
local source = vide.source
|
||||
local effect = vide.effect
|
||||
local derive = vide.derive
|
||||
|
||||
local count = source(0)
|
||||
|
||||
local text = derive(function()
|
||||
print "ran"
|
||||
return "count: " .. tostring(count())
|
||||
end)
|
||||
|
||||
effect(function() text() end)
|
||||
effect(function() text() end)
|
||||
|
||||
count(1) -- prints "ran" x1
|
||||
```
|
||||
|
||||
Because `derive()` creates a reactive scope, it must be called within a stable
|
||||
scope, just like `effect()`.
|
||||
|
||||
If the recalculated value is the same as the old value, the derived source will
|
||||
not rerun the effects using it.
|
||||
|
||||
The reactive graph for the above example:
|
||||
|
||||
```mermaid
|
||||
%%{init: {
|
||||
"theme": "base",
|
||||
"themeVariables": {
|
||||
"primaryColor": "#111720",
|
||||
"primaryTextColor": "#fff",
|
||||
"primaryBorderColor": "#111720",
|
||||
"lineColor": "#79B8FF",
|
||||
"tertiaryColor": "#0d131b",
|
||||
"tertiaryBorderColor": "#0d131b"
|
||||
}
|
||||
}}%%
|
||||
|
||||
graph
|
||||
|
||||
subgraph root
|
||||
direction LR
|
||||
count --> text --> effect1 & effect2
|
||||
end
|
||||
```
|
||||
|
||||
Deriving a source in this manner is similar to creating an effect to update
|
||||
another source. You should avoid doing this using an effect however.
|
||||
Improper usage could accidently create infinite loops in the reactive graph.
|
||||
Always favour deriving when you need one source to update based on another
|
||||
source.
|
||||
Loading…
Add table
Add a link
Reference in a new issue