diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..876fbd3
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
+*.luau linguist-language=Lua
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
deleted file mode 100644
index 9c22f03..0000000
--- a/.github/workflows/build.yml
+++ /dev/null
@@ -1,33 +0,0 @@
-name: release
-
-on:
- release:
- types: [published]
-
-permissions:
- contents: write
-
-env:
- GH_TOKEN: ${{ github.token }}
-
-jobs:
- build:
- runs-on: ubuntu-latest
- steps:
-
- - name: checkout
- uses: actions/checkout@v4
- with:
- fetch-depth: 0
-
- - name: setup rokit
- uses: CompeyDev/setup-rokit@v0.1.2
-
- - name: build
- run: rojo build default.project.json -o build.rbxm
-
- - name: release
- run: gh release upload ${{github.event.release.tag_name}} build.rbxm
-
-
-
\ No newline at end of file
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index 5bb1717..10f047c 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -1,6 +1,9 @@
name: site-deploy
on:
+ push:
+ branches: [main] # todo: remove later
+
workflow_dispatch:
permissions:
@@ -21,21 +24,21 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
- uses: actions/checkout@v4
+ uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup Node
- uses: actions/setup-node@v4
+ uses: actions/setup-node@v3
with:
node-version: 18
- name: Setup Pages
- uses: actions/configure-pages@v4
+ uses: actions/configure-pages@v3
- name: Install dependencies
run: npm install
- name: Build with VitePress
run: npm run docs:build
- name: Upload artifact
- uses: actions/upload-pages-artifact@v3
+ uses: actions/upload-pages-artifact@v2
with:
path: docs/.vitepress/dist
@@ -49,4 +52,4 @@ jobs:
steps:
- name: Deploy to GitHub Pages
id: deployment
- uses: actions/deploy-pages@v4
+ uses: actions/deploy-pages@v2
diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml
index 138f29b..a6cd4dc 100644
--- a/.github/workflows/unit-test.yml
+++ b/.github/workflows/unit-test.yml
@@ -8,13 +8,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repo
- uses: actions/checkout@v4
+ uses: actions/checkout@v3
- name: Install Luau zip
- uses: robinraju/release-downloader@v1.9
+ uses: robinraju/release-downloader@v1.6
with:
repository: Roblox/luau
- tag: "0.679"
+ latest: true
fileName: luau-ubuntu.zip
out-file-path: bin
diff --git a/.github/workflows/wallypesde.yml b/.github/workflows/wallypesde.yml
deleted file mode 100644
index 0c705b0..0000000
--- a/.github/workflows/wallypesde.yml
+++ /dev/null
@@ -1,54 +0,0 @@
-name: publish to wally and pesde
-
-on:
- workflow_dispatch:
- release:
- types: [published]
-
-permissions:
- contents: write
-
-env:
- GH_TOKEN: ${{ github.token }}
-
-jobs:
- wally:
- runs-on: ubuntu-latest
- steps:
-
- - name: checkout
- uses: actions/checkout@v4
- with:
- fetch-depth: 0
-
- - name: setup rokit
- uses: CompeyDev/setup-rokit@v0.1.2
-
- - name: login
- run: wally login --token "${{ secrets.WALLY_TOKEN }}"
-
- - name: publish
- run: wally publish
-
- pesde:
- runs-on: ubuntu-latest
- steps:
-
- - name: checkout
- uses: actions/checkout@v4
- with:
- fetch-depth: 0
-
- - name: setup rokit
- uses: CompeyDev/setup-rokit@v0.1.2
-
- - name: setup pesde
- run: pesde install
-
- - name: login
- run: pesde auth login --token "${{ secrets.PESDE_TOKEN }}"
-
- - name: publish
- run: pesde publish --yes
-
-
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 18f0f88..bd0480d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,16 +1,10 @@
.vscode
_local
+aftman.toml
sourcemap.json
docs/.vitepress/dist
docs/.vitepress/cache
docs/node_modules
docs/package-lock.json
-
-luau_packages/
-lune_packages/
-.pesde/
-
-wally.lock
-pesde.lock
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3e584d2..83d16e5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,83 +6,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
--------------------------------------------------------------------------------
-## [0.4.1] - 2026-07-11
-
-### Changed
-
-- `branch()` is now allowed to be used within a reactive scope.
-
-### Deprecated
-
-- `create()` overloads. Supported is now only `create(class)(props)`.
-
-### Fixed
-
-- `create()` types in the new solver should now work without `::`.
-
---------------------------------------------------------------------------------
-
-## [0.4.0] - 2026-01-17
-
-### Added
-
-- `create("ClassName", { props })` and `create(Instance, { props })` syntax.
-- `cleanup()` now accepts `thread` types.
-- Implicit effects to set children can now recursively create more implicit
- effects to set children.
-- `spring()` returns a second value, a setter to set position, velocity and
- impulse.
-- Improved `spring()` updating and unscheduling.
-- `show()` now receives a source to its callback returning the current value
- of the condition.
-- Ignore `false` passed as a child.
-- Flag `vide.defaults` to disable the setting of default properties.
-- Delayed scope destruction for control flow functions: `show()` `switch()` `indexes()` `values()`.
-- Better `create()` types for the new type solver.
-
-### Changed
-
-- A scope can no longer be destroyed while it is active. Strict mode will check
- for this.
-- Implicit effects to set children now unparent all children when the effect is
- destroyed.
-- Error reporting should be improved with better formatting when effects invoke
- other effects and no more loss of stack traces.
-- Nesting parent properties now work, and they are now also checked for
- duplicates like other properties.
-
-### Removed
-
-- Aggregate construction when setting properties with `create()`.
-
---------------------------------------------------------------------------------
-
-## [0.3.1] - 2024-10-09
-
-### Added
-
-- Context functions now also return results.
-- `version` table with current version.
-
---------------------------------------------------------------------------------
-
-## [0.3.0] - 2024-10-06
-
-### Added
-
-- `context()`.
-
-### Changed
-
-- `root()` now returns its destructor as the first value by default.
-
-### Fixed
-
-- Error stack traces being lost.
-- `root()` now destroys the scope automatically if an error occurs during call.
-
---------------------------------------------------------------------------------
-
## [0.2.0] - 2023-11-22
### Added
diff --git a/README.md b/README.md
index 13e41b6..8ce077b 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,7 @@ for a quick introduction to the library.
## Code sample
-```luau
+```lua
local create = vide.create
local source = vide.source
diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts
index 37918e8..0a3500d 100644
--- a/docs/.vitepress/config.ts
+++ b/docs/.vitepress/config.ts
@@ -1,83 +1,71 @@
-//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" }
- ]
- }
-})
+//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",
+
+ // 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: Control Flow", link: "/api/reactivity-flow" },
+ { 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: "Root Scopes", link: "/tut/crash-course/6-root" },
+ { text: "Stateful Components", link: "/tut/crash-course/7-stateful-component" },
+ { text: "Property Binding", link: "/tut/crash-course/8-property-binding" },
+ { text: "Derived Sources", link: "/tut/crash-course/9-derived-source" },
+ { text: "Cleanup", link: "/tut/crash-course/10-cleanup" },
+ { text: "Control Flow", link: "/tut/crash-course/11-control-flow" },
+ { text: "Property Nesting", link: "/tut/crash-course/12-property-nesting" },
+ { text: "Actions", link: "/tut/crash-course/13-actions" },
+ { text: "Strict Mode", link: "/tut/crash-course/14-strict-mode" },
+ { text: "Concepts Summary", link: "/tut/crash-course/15-concepts" }
+ ]
+ },
+ {
+ text: "Advanced Reactivity",
+ items: [
+ { text: "Reactive Scopes", link: "/tut/advanced/reactive-scoping.md"}
+ ]
+ }
+ ],
+ },
+
+ socialLinks: [
+ { icon: "github", link: "https://github.com/centau/vide" }
+ ]
+ }
+})
diff --git a/docs/.vitepress/theme/index.css b/docs/.vitepress/theme/index.css
deleted file mode 100644
index 9c3ca9a..0000000
--- a/docs/.vitepress/theme/index.css
+++ /dev/null
@@ -1,25 +0,0 @@
-.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
- }
-
\ No newline at end of file
diff --git a/docs/.vitepress/theme/index.js b/docs/.vitepress/theme/index.js
index 54a4c62..b8b9aa6 100644
--- a/docs/.vitepress/theme/index.js
+++ b/docs/.vitepress/theme/index.js
@@ -1,8 +1,4 @@
-// .vitepress/theme/index.js
-import DefaultTheme from 'vitepress/theme'
-import './vars.css'
-import './index.css'
-
-export default {
- extends: DefaultTheme,
-}
+// .vitepress/theme/index.js
+import DefaultTheme from 'vitepress/theme'
+import './vars.css'
+export default DefaultTheme
diff --git a/docs/.vitepress/theme/vars.css b/docs/.vitepress/theme/vars.css
index 7fbf3b5..64df54f 100644
--- a/docs/.vitepress/theme/vars.css
+++ b/docs/.vitepress/theme/vars.css
@@ -1,52 +1,3 @@
-/* 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;
-}
-
+:root {
+ --vp-c-brand-1: #3086ff;
+}
diff --git a/docs/api/animation.md b/docs/api/animation.md
index ca06b39..16e48d7 100644
--- a/docs/api/animation.md
+++ b/docs/api/animation.md
@@ -1,30 +1,25 @@
-# Animation
+# Animation API
-## spring() REACTIVE
+## spring()
Returns a new source with a value always moving torwards the input source value.
- **Type**
- ```luau
+ ```lua
function spring(
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 = ({
- position: T?,
- velocity: T?,
- impulse: T?
- }) -> ()
```
- **Details**
- Creates a reactive scope internally to detect source updates.
+ The output source value is updated every step based on the input source
+ value.
The movement is physically simulated according to a
[spring](https://en.wikipedia.org/wiki/Simple_harmonic_motion).
@@ -44,7 +39,3 @@ Returns a new source with a value always moving torwards the input source value.
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.
- :::
diff --git a/docs/api/creation.md b/docs/api/creation.md
index 578b10f..8e67478 100644
--- a/docs/api/creation.md
+++ b/docs/api/creation.md
@@ -1,4 +1,39 @@
-# Element Creation
+# Element Creation API
+
+
+
+## mount()
+
+Runs a function in a new reactive scope and optionally applies its result to a
+target instance.
+
+- **Type**
+
+ ```lua
+ function mount(component: () -> T, target: Instance?): () -> ()
+ ```
+
+- **Details**
+
+ The result of the function is applied to a target in the same way
+ properties are using `create()`.
+
+ The function is ran in a new reactive scope, just like
+ [root()](reactivity-core.md#root).
+
+ Returns a function that when called will destroy the reactive scope.
+
+- **Example**
+
+ ```lua
+ local function App()
+ return create "ScreenGui" {
+ create "TextLabel" { Text = "Vide" }
+ }
+ end
+
+ mount(App, game.StarterGui)
+ ```
## create()
@@ -6,11 +41,11 @@ Creates a new UI element, applying any given properties.
- **Type**
- ```luau
+ ```lua
function create(class: string): (Properties) -> Instance
function create(instance: Instance): (Properties) -> Instance
- type Properties = Map
+ type Properties = Map
```
- **Details**
@@ -41,53 +76,73 @@ Creates a new UI element, applying any given properties.
Basic element creation.
- ```luau
- local frame = create "TextButton" {
- Name = "Button",
- Size = UDim2.fromOffset(200, 160),
-
- Activated = function()
- print "clicked"
- end,
-
- create "UICorner" {}
+ ```lua
+ local frame = create "Frame" {
+ Name = "NewFrame",
+ Position = UDim2.fromScale(1, 0)
}
```
+ A component using property nesting.
+
+ ```lua
+ type Layout = {
+ Layout = {
+ Position: UDim2?,
+ Size: UDim2?,
+ AnchorPoint: Vector2?
+ }
+ }
+
+ type Children = {
+ Children = Array
+ }
+
+ function Background(props: Layout & Children & {
+ Color: Color3
+ })
+ return create "Frame" {
+ BackgroundColor3 = props.Color,
+ props.Layout,
+ props.Children
+ }
+ end
+ ```
+
## action()
-Creates a special object that can be passed to `create()` to invoke custom
-actions on instances.
+Creates a callback that can be passed to `create()` to invoke custom actions on
+instances.
- **Type**
- ```luau
+ ```lua
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.
+ When passed to `create()`, the given callback 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.
+ other actions. Higher priority numbers are ran after lower priority numbers.
- **Example**
An action to listen to changed properties:
- ```luau
- local function changed(property: string, fn: (new) -> ())
+ ```lua
+ local function changed(property: string, callback: (new) -> ())
return action(function(instance)
- local cn = instance:GetPropertyChangedSignal(property):Connect(function()
- fn(instance[property])
+ local con - instance:GetPropertyChangedSignal(property):Connect(function()
+ callback(instance[property])
end)
- -- disconnect on scope destruction to allow gc of instance
+ -- disconnect on reactive scope destruction to allow gc of instance
cleanup(function()
- cn:Disconnect()
+ con:Disconnect()
end)
end)
end
@@ -95,7 +150,7 @@ actions on instances.
local output = source ""
create "TextBox" {
- -- will update the output source anytime the text property is changed
+ -- will update the `output` source anytime the text property is changed
changed("Text", output)
}
```
@@ -106,47 +161,16 @@ A wrapper for `action()` to listen for property changes.
- **Type**
- ```luau
- function changed(property: string, fn: (unknown) -> ()): Action
+ ```lua
+ function changed(property: string, callback: (...unknown) -> ()): Action
```
- **Details**
- Will run the given function immediately and whenever the property updates.
+ Will run the given callback any time the property is changed, as well as
+ when the action is initially run.
- The function is called with the updated property value.
+ The changed connection is disconnected when the reactive scope the action is
+ ran in is destroyed.
Runs with an action priority of 1.
-
-## mount() STABLE
-
-Runs a function in a new stable scope and optionally applies its result to a
-target instance.
-
-- **Type**
-
- ```luau
- function mount(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)
- ```
diff --git a/docs/api/reactivity-core.md b/docs/api/reactivity-core.md
index ebbc5dd..133bc4a 100644
--- a/docs/api/reactivity-core.md
+++ b/docs/api/reactivity-core.md
@@ -1,155 +1,125 @@
-# Reactivity: Core
+# Reactivity API: Core
-## Scopes
-
-Vide code can run in one of two scopes: STABLE or REACTIVE.
-
-- 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.
+Yielding is not allowed in any reactive scope. Strict mode can check for this.
:::
-## root() STABLE
+## root()
-Runs a function in a new stable scope.
+Creates and runs a function in a new reactive scope.
- **Type**
- ```luau
- function root(fn: (Destructor) -> T...): (Destructor, T...)
-
- type Destructor = () -> ()
+ ```lua
+ function root(fn: (destroy: () -> ()) -> T...): T...
```
- **Details**
- Returns a destructor and any values returned by the callback.
+ Returns the result of the given function.
+
+ Creates a new root reactive scope, where creation and derivations of sources
+ can be tracked and properly disposed of.
+
+ A function to destroy the root is passed into the callback, which will run
+ any cleanups and allow derived sources created to garbage collect.
## source()
-Creates a new source.
+Creates a new source with the given value.
- **Type**
- ```luau
- function source(value: T): Source
-
- type Source =
- () -> T -- get
- & (T) -> () -- set
+ ```lua
+ function source(value: T): (T?) -> T
```
- **Details**
- Call the returned source with no argument to read its value.
- Call the returned source with an argument to set its value.
+ Calling the returned source with no argument will return its stored value,
+ calling with an argument will set a new value.
+
+ Reading from the source from within a reactive scope will cause changes
+ to that source to be tracked and anything depending on it to update.
+
+ Sources can be created outside of reactive scopes.
- **Example**
- ```luau
+ ```lua
local count = source(0)
- print(count())-- 0
- count(count() + 1)
- print(count()) -- 1
+
+ count() -- 0
+
+ count(count() + 1) -- 1
```
-## effect() REACTIVE
+## effect()
-Runs a function in a new reactive scope.
+Runs a side-effect in a new reactive scope on source update.
- **Type**
- ```luau
- function effect(fn: () -> ())
+ ```lua
+ function effect(callback: () -> ())
```
- **Details**
- The function is ran once immediately.
+ Any time a source referenced in the callback is changed, the callback will
+ be reran.
+
+ The callback is ran to initially ran on first call to find dependent sources.
- **Example**
- ```luau
- local count = source(1)
+ ```lua
+ local num = source(1)
effect(function()
- print(count())
+ print(num())
end)
-- prints 1
- count(2)
+ num(num() + 1)
-- prints 2
```
-## derive() REACTIVE
+## derive()
-Runs a function in a new reactive scope to compute a value for new source.
+Derives a new source in a new reactive scope from existing sources.
- **Type**
- ```luau
- function derive(fn: () -> T): () -> T
+ ```lua
+ function derive(source: () -> T): () -> T
```
- **Details**
- Anytime the reactive scope reruns, the output source value is set to what is
- returned.
+ The derived source will have its value recalculated when any source source
+ it derives from is updated.
- The function is ran once immediately.
+ Anytime its value is recalculated it is also cached, subsequent calls will
+ retun this cached value until it recalculates again.
+
+ The callback is ran to initially ran on first call to find dependent sources.
- **Example**
- ```luau
+ ```lua
local count = source(0)
local text = derive(function() return `count: {count()}` end)
- print(text()) -- "count: 0"
+ text() -- "count: 0"
count(1)
- print(text()) -- "count: 1"
+ 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"
- ```
-
- :::
+--------------------------------------------------------------------------------
diff --git a/docs/api/reactivity-dynamic.md b/docs/api/reactivity-dynamic.md
deleted file mode 100644
index f0b3229..0000000
--- a/docs/api/reactivity-dynamic.md
+++ /dev/null
@@ -1,229 +0,0 @@
-# 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() REACTIVE
-
-Shows a component if the source is truthy. Optionally shows a fallback component
-if the source is falsey.
-
-- **Type**
-
- ```luau
- function show(source: () -> unknown, component: Constructor): () -> T?
- function show(source: () -> unknown, component: Constructor, fallback: () -> U): () -> T | U
-
- type Constructor = () -> (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() REACTIVE
-
-Shows one of a set of components depending on a source and a mapping table.
-
-- **Type**
-
- ```luau
- function switch(source: () -> K): (map: Map>): () -> V?
-
- type Constructor = () -> (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() REACTIVE
-
-Shows a component for each index in a table.
-
-- **Type**
-
- ```luau
- function indexes(
- source: () -> Map,
- constructor: (value: () -> VI, index: KI) -> (VO, number?)
- ): Array
- ```
-
-- **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
-
- 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() REACTIVE
-
-Shows a component for each value in a table.
-
-- **Type**
-
- ```luau
- function values(
- source: () -> Map,
- constructor: (value: VI, index: () -> KI) -> (VO, number?)
- ): Array
-
-- **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
-
- 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.
-
---------------------------------------------------------------------------------
diff --git a/docs/api/reactivity-flow.md b/docs/api/reactivity-flow.md
new file mode 100644
index 0000000..47dafa8
--- /dev/null
+++ b/docs/api/reactivity-flow.md
@@ -0,0 +1,218 @@
+# Reactivity API: Control Flow
+
+
+
+## show()
+
+Shows one of two components depending on an input source.
+
+- **Type**
+
+ ```lua
+ function show(source: () -> unknown, component: () -> T): () -> T?
+ function show(source: () -> unknown, component: () -> T, fallback: () -> U): () -> T | U
+ ```
+
+- **Details**
+
+ Returns a source holding an instance of the currently shown component.
+
+ When the input source changes from a falsey to a truthy value, the
+ component will be reran under a new reactive scope. If it changes from a
+ truthy to falsey value, the reactive scope the component was created in will
+ be destroyed, and the returned source will output `nil`, or a fallback
+ component if given.
+
+ The fallback component is also ran under a new reactive scope, and destroyed
+ when the input source switches back to truthy.
+
+## switch()
+
+Shows one of a set of components depending on an input source and a mapping table.
+
+- **Type**
+
+ ```lua
+ function switch(source: () -> K): (map: Map V>) -> V?
+ ```
+
+- **Details**
+
+ Returns a source holding an instance of the currently shown component.
+
+ When the input source changes, the new value will be used to lookup a given
+ mapping table to get a component, which will be ran under a new reactive
+ scope. If the input source changes, the reactive scope the component was
+ created in will be destroyed, and a new component created under a new
+ reactive scope. If no component is found for an input value, the switch will
+ output `nil`.
+
+- **Example**
+
+ ```lua
+ 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()
+
+Maps each index in a table source to an object.
+
+- **Type**
+
+ ```lua
+ function indexes(
+ source: () -> Map,
+ transform: (value: () -> VI, index: KI) -> VO
+ ): Array
+
+- **Details**
+
+ Returns a source holding an array of instances currently shown.
+
+ When the input source changes, each *index* in the new table is compared with
+ the last input table.
+
+ - For any new index, the `transform` function is ran under a new reactive
+ scope to produce a new instance.
+ - For any removed index, the reactive scope for that index is destroyed.
+ - Unchanged indexes are untouched.
+
+ The transform function is called only ever *once* for each index in the
+ source table.
+
+ 1. First argument is a *source containing the index's value*.
+ 2. Second argument is the *index itself*.
+
+ Anytime an existing index's value changes, the transform function is not
+ rerun, instead the source value for that index will update, causing anything
+ depending on it to update too.
+
+- **Example**
+
+ The intended purpose of this function is to map each index in a table to
+ a UI element.
+
+ ```lua
+ type Item = {
+ name: string,
+ icon: number
+ }
+
+ local items = source {} :: () -> Array
+
+ 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()
+
+Maps each value in a table source to an object.
+
+- **Type**
+
+ ```lua
+ function values(
+ source: () -> Map,
+ transform: (value: VI, index: () -> KI) -> VO
+ ): Array
+
+- **Details**
+
+ Returns a source holding an array of instances currently shown.
+
+ When the input source changes, each *value* in the new table is compared with
+ the last input table. Similar to `indexes()` but for values instead of indexes.
+
+ - For any new value, the `transform` function is ran under a new reactive
+ scope to produce a new instance.
+ - For any removed value, the reactive scope for that value is destroyed.
+ - Unchanged values are untouched.
+
+ The transform function is only ever called *once* for each value in the
+ source table.
+
+ 1. First argument is the *value itself*.
+ 2. Second argument is a *source containing the value's index*.
+
+ Anytime an existing value's index changes, the transform function is not
+ rerun, instead the source index for that value will update, causing anything
+ depending on it to update too.
+
+ ::: warning
+ Having primitive values in the input source table can cause unexpected
+ behavior, as duplicate values can result in multiple tranforms being ran for
+ a single value, meaning there can be multiple source indexes bound to the
+ same UI element. Strict mode has checks for this.
+ :::
+
+- **Example**
+
+ The intended purpose of this function is to map each value in a table to
+ a UI element.
+
+ ```lua
+ type Item = {
+ name: string,
+ icon: number
+ }
+
+ local items = source {} :: () -> Array
+
+ 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 value. It maps an index to a UI element.
+
+ e.g.
+ - List of character or weapon stats.
+
+ In most cases, both functions will appear to have the same behavior.
+ 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 value is not destroyed when indexes are changed, and the source index
+ can easily be put through a spring.
+
+--------------------------------------------------------------------------------
diff --git a/docs/api/reactivity-utility.md b/docs/api/reactivity-utility.md
index b8bff4d..ee82067 100644
--- a/docs/api/reactivity-utility.md
+++ b/docs/api/reactivity-utility.md
@@ -1,58 +1,53 @@
-# Reactivity: Utility
+# Reactivity API: Utility
## cleanup()
-Queues a callback to run when a scope is reran or destroyed.
+Runs a callback anytime a reactive scope is reran or destroyed.
- **Type**
- ```luau
- function cleanup(v: Function | Disconnectable | Destroyable | thread)
+ ```lua
+ function cleanup(callback: () -> ())
+ function cleanup(obj: Destroyable)
+ function cleanup(obj: Disconnectable)
- type Function = () -> ()
type Destroyable = { destroy: () -> () }
type Disconnectable = { disconnect: () -> () }
```
- **Example**
- ```luau
- local count = source(0)
+ ```lua
+ local data = source(1)
- local destroy = root(function()
- effect(function()
- count()
+ effect(function()
+ local label = create "TextLabel" { Text = data() }
- cleanup(function()
- print "cleaned"
- end)
+ cleanup(function()
+ label:Destroy()
end)
- end
-
- -- nothing printed yet
- count(1) -- prints "cleaned"
- count(2) -- prints "cleaned"
- destroy() -- prints "cleaned"
+ end)
```
-## untrack() STABLE
+## untrack()
-Runs a function in a new stable scope.
+Runs a given function where any sources read will not be tracked by a reactive
+scope.
- **Type**
- ```luau
+ ```lua
function untrack(source: () -> T): T
```
- **Details**
- Can be used inside a reactive scope to read from sources you do not want
- tracked by the reactive scope.
+ Updates made to a source passed to `untrack()` will not cause updates to
+ anything depending on that source.
- **Example**
- ```luau
+ ```lua
local a = source(0)
local b = source(0)
@@ -61,98 +56,39 @@ Runs a function in a new stable scope.
end)
print(sum()) -- 0
- b(1) -- untracked so reactive scope created by derive() does not rerun
+ b(1)
print(sum()) -- 0
- a(1) -- reactive scope created by derive() reruns
+ a(1)
print(sum()) -- 2
```
## read()
-Utility used to read a value that is either a primitive or a source.
+Utility used to read a value that is either a primitive or a source. Sources
+read can still be tracked inside a reactive scope.
- **Type**
- ```luau
+ ```lua
function read(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.
+Runs a given function where any source updates made within the function do not
+trigger effects until after the function runs.
- **Type**
- ```luau
+ ```lua
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() STABLE
-
-Creates a new context.
-
-- **Type**
-
- ```luau
- function context(default: T): Context
-
- type Context =
- () -> T -- get
- & (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)
- ```
+ sources need to be updated. Updating those sources inside a batch call will
+ only cause the effect to run once after the batch call ends instead of after
+ each time a source is updated.
+--------------------------------------------------------------------------------
diff --git a/docs/api/strict-mode.md b/docs/api/strict-mode.md
index 6f9ee80..958672e 100644
--- a/docs/api/strict-mode.md
+++ b/docs/api/strict-mode.md
@@ -2,7 +2,7 @@
Strict mode is library-wide and can get set by doing:
-```luau
+```lua
vide.strict = true
```
@@ -14,24 +14,25 @@ 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.
+1. Run derived sources twice a source updates.
+2. Run effects twice when a source updates.
+3. Throw an error if yields occur where they are not allowed.
+4. Checks for `indexes()` and `values()` returning primitive values.
+5. Checks for `values()` input having duplicate values.
+6. Checks for duplicate nested properties at same depth.
+7. Better error reporting and stack traces + creation traces of property bindings.
-By rerunning reactive scopes twice each time they update, it helps ensure that
-computations are pure, and that any cleanup is done correctly.
+By rerunning derived sources and effects twice each time they update,it helps
+ensure that derived source computations are pure, and that any
+cleanups made in derived sources or effects are done correctly.
Accidental yielding within reactive scopes can break Vide's reactive graph,
-which strict mode will catch.
+which strict mode can 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.
+when binding properties to sources.
-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
+It is recommend 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 don't
need to worry about disabling strict mode unless you have manually enabled it.
diff --git a/docs/index.md b/docs/index.md
index 295d137..6fe3a4a 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -1,16 +1,14 @@
---
# https://vitepress.dev/reference/default-theme-home-page
layout: home
-pageClass: home
-next:
- text: 'Introduction'
- link: '/tut/crash-course/1-introduction'
hero:
- name: "Vide"
+ name: Vide
+ text: ""
tagline: A reactive UI library for Luau.
image:
src: /logo.svg
+ alt: Vide
actions:
- theme: brand
text: Tutorials
@@ -19,4 +17,8 @@ hero:
text: API Reference
link: /api/reactivity-core
+features:
+ - title: In Development
+ details: Not recommended for production use.
---
+
diff --git a/docs/package.json b/docs/package.json
index a329007..e156bea 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -1,12 +1,14 @@
-{
- "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"
- }
-}
+{
+ "type": "module",
+
+ "scripts": {
+ "docs:dev": "vitepress dev",
+ "docs:build": "vitepress build",
+ "docs:preview": "vitepress preview"
+ },
+
+ "devDependencies": {
+ "vitepress": "^1.0.0-rc.4",
+ "vitepress-plugin-mermaid": "^2.0.14"
+ }
+}
diff --git a/docs/public/full_logo.svg b/docs/public/full_logo.svg
index 1962800..85488e0 100644
--- a/docs/public/full_logo.svg
+++ b/docs/public/full_logo.svg
@@ -1,4 +1,4 @@
-