Add wildcard support (#805)

* Dispatch `datastar-sse` event on `document`

* Release note

* Fix

* Modify view transition

* Fixes

* Fixes

* Add wildcard support

* Add wildcard to OnSignalChange
This commit is contained in:
Ben Croker 2025-03-29 11:11:18 -06:00 committed by GitHub
parent 05c751770b
commit 5ba9759fba
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
35 changed files with 296 additions and 115 deletions

View File

@ -17,3 +17,6 @@ Each tagged version of Datastar is accompanied by a release note. Read the [rele
### Changed ### Changed
- The `datastar-sse` event is now dispatched on the `document` element, and using `data-on-datastar-sse` automatically listens for the event on the `document` ([#802](https://github.com/starfederation/datastar/issues/802)). - The `datastar-sse` event is now dispatched on the `document` element, and using `data-on-datastar-sse` automatically listens for the event on the `document` ([#802](https://github.com/starfederation/datastar/issues/802)).
- The `data-on-signals-change-*` attribute key now accepts a path in which `*` can be used as a wildcard (`data-on-signals-change-foo.*`).
- The `@setAll` action now accepts one or more space-separated paths in which `*` can be used as a wildcard (`@setAll('foo.* bar.*', true)`).
- The `@toggleAll` action now accepts one or more space-separated paths in which `*` can be used as a wildcard (`@toggleAll('foo.* bar.*', true)`).

View File

@ -10,7 +10,7 @@
Datastar helps you build reactive web applications with the simplicity of server-side rendering and the power of a full-stack SPA framework. Datastar helps you build reactive web applications with the simplicity of server-side rendering and the power of a full-stack SPA framework.
Getting started is as easy as adding a single 14.5 KiB script tag to your HTML. Getting started is as easy as adding a single 14.6 KiB script tag to your HTML.
```html ```html
<script type="module" src="https://cdn.jsdelivr.net/gh/starfederation/datastar@v1.0.0-beta.10/bundles/datastar.js"></script> <script type="module" src="https://cdn.jsdelivr.net/gh/starfederation/datastar@v1.0.0-beta.10/bundles/datastar.js"></script>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -10,7 +10,7 @@
Datastar helps you build reactive web applications with the simplicity of server-side rendering and the power of a full-stack SPA framework. Datastar helps you build reactive web applications with the simplicity of server-side rendering and the power of a full-stack SPA framework.
Getting started is as easy as adding a single 14.5 KiB script tag to your HTML. Getting started is as easy as adding a single 14.6 KiB script tag to your HTML.
```html ```html
<script type="module" src="https://cdn.jsdelivr.net/gh/starfederation/datastar@v1.0.0-beta.10/bundles/datastar.js"></script> <script type="module" src="https://cdn.jsdelivr.net/gh/starfederation/datastar@v1.0.0-beta.10/bundles/datastar.js"></script>

View File

@ -10,6 +10,7 @@ import {
PluginType, PluginType,
Requirement, Requirement,
} from '../../../../engine/types' } from '../../../../engine/types'
import { pathMatchesPattern } from '../../../../utils/paths'
import { modifyCasing } from '../../../../utils/text' import { modifyCasing } from '../../../../utils/text'
import { modifyTiming } from '../../../../utils/timing' import { modifyTiming } from '../../../../utils/timing'
import { modifyViewTransition } from '../../../../utils/view-transtions' import { modifyViewTransition } from '../../../../utils/view-transtions'
@ -33,10 +34,10 @@ export const OnSignalChange: AttributePlugin = {
} }
} }
const signalPath = modifyCasing(key, mods) const pattern = modifyCasing(key, mods)
const signalValues = new Map<Signal, any>() const signalValues = new Map<Signal, any>()
signals.walk((path, signal) => { signals.walk((path, signal) => {
if (path.startsWith(signalPath)) { if (pathMatchesPattern(path, pattern)) {
signalValues.set(signal, signal.value) signalValues.set(signal, signal.value)
} }
}) })

View File

@ -1,16 +1,25 @@
// Authors: Delaney Gillilan // Authors: Delaney Gillilan
// Icon: ion:checkmark-round // Icon: ion:checkmark-round
// Slug: Set all signals that match a regular expression // Slug: Set all signals that match the signal path
// Description: Set all signals that match one or more space-separated paths in which `*` can be used as a wildcard
import { type ActionPlugin, PluginType } from '../../../../engine/types' import { type ActionPlugin, PluginType } from '../../../../engine/types'
import { pathMatchesPattern } from '../../../../utils/paths'
import { trimDollarSignPrefix } from '../../../../utils/text'
export const SetAll: ActionPlugin = { export const SetAll: ActionPlugin = {
type: PluginType.Action, type: PluginType.Action,
name: 'setAll', name: 'setAll',
fn: ({ signals }, prefix: string, newValue) => { fn: ({ signals }, paths: string, newValue) => {
let patterns = paths.split(/\s+/).filter((p) => p !== '')
patterns = patterns.map((p) => trimDollarSignPrefix(p))
for (const pattern of patterns) {
signals.walk((path, signal) => { signals.walk((path, signal) => {
if (!path.startsWith(prefix)) return if (pathMatchesPattern(path, pattern)) {
signal.value = newValue signal.value = newValue
}
}) })
}
}, },
} }

View File

@ -1,16 +1,25 @@
// Authors: Delaney Gillilan // Authors: Delaney Gillilan
// Icon: material-symbols:toggle-off // Icon: material-symbols:toggle-off
// Slug: Toggle all signals that match a regular expression // Slug: Toggle all signals that match the signal path
// Description: Toggle all signals that match one or more space-separated paths in which `*` can be used as a wildcard
import { type ActionPlugin, PluginType } from '../../../../engine/types' import { type ActionPlugin, PluginType } from '../../../../engine/types'
import { pathMatchesPattern } from '../../../../utils/paths'
import { trimDollarSignPrefix } from '../../../../utils/text'
export const ToggleAll: ActionPlugin = { export const ToggleAll: ActionPlugin = {
type: PluginType.Action, type: PluginType.Action,
name: 'toggleAll', name: 'toggleAll',
fn: ({ signals }, prefix: string) => { fn: ({ signals }, paths: string) => {
let patterns = paths.split(/\s+/).filter((p) => p !== '')
patterns = patterns.map((p) => trimDollarSignPrefix(p))
for (const pattern of patterns) {
signals.walk((path, signal) => { signals.walk((path, signal) => {
if (!path.startsWith(prefix)) return if (pathMatchesPattern(path, pattern)) {
signal.value = !signal.value signal.value = !signal.value
}
}) })
}
}, },
} }

View File

@ -0,0 +1,7 @@
export function pathMatchesPattern(path: string, pattern: string) {
const regex = new RegExp(
`^${pattern.replaceAll('.', '\\.').replaceAll('*', '.*')}$`,
)
return regex.test(path)
}

4
sdk/go/consts.go generated
View File

@ -7,8 +7,8 @@ import "time"
const ( const (
DatastarKey = "datastar" DatastarKey = "datastar"
Version = "1.0.0-beta.10" Version = "1.0.0-beta.10"
VersionClientByteSize = 40008 VersionClientByteSize = 40231
VersionClientByteSizeGzip = 14887 VersionClientByteSizeGzip = 14988
//region Default durations //region Default durations

View File

@ -55,6 +55,8 @@ func setupTests(ctx context.Context, router chi.Router) (err error) {
{ID: "on_signal_change"}, {ID: "on_signal_change"},
{ID: "on_signal_change_path"}, {ID: "on_signal_change_path"},
{ID: "on_signal_change_path_once"}, {ID: "on_signal_change_path_once"},
{ID: "on_signal_change_path_wildcard"},
{ID: "persist_signals"},
{ID: "plugin_name_prefix"}, {ID: "plugin_name_prefix"},
{ID: "radio_value"}, {ID: "radio_value"},
{ID: "ref"}, {ID: "ref"},
@ -62,6 +64,9 @@ func setupTests(ctx context.Context, router chi.Router) (err error) {
{ID: "remove_initiating_fragment"}, {ID: "remove_initiating_fragment"},
{ID: "select_multiple"}, {ID: "select_multiple"},
{ID: "select_single"}, {ID: "select_single"},
{ID: "set_all_path"},
{ID: "set_all_path_wildcard"},
{ID: "set_all_paths"},
{ID: "sse_error_event"}, {ID: "sse_error_event"},
{ID: "sse_events"}, {ID: "sse_events"},
}, },

View File

@ -5,5 +5,5 @@ import (
) )
func TestUnitOnSignalChangePathOnce(t *testing.T) { func TestUnitOnSignalChangePathOnce(t *testing.T) {
setupPageTestOnClick(t, "tests/on_signal_change_path_once") setupPageTestOnLoad(t, "tests/on_signal_change_path_once")
} }

View File

@ -5,5 +5,5 @@ import (
) )
func TestUnitOnSignalChangePath(t *testing.T) { func TestUnitOnSignalChangePath(t *testing.T) {
setupPageTestOnClick(t, "tests/on_signal_change_path") setupPageTestOnLoad(t, "tests/on_signal_change_path")
} }

View File

@ -0,0 +1,9 @@
package smoketests
import (
"testing"
)
func TestUnitOnSignalChangePathWildcard(t *testing.T) {
setupPageTestOnLoad(t, "tests/on_signal_change_path_wildcard")
}

View File

@ -5,5 +5,5 @@ import (
) )
func TestUnitOnSignalChange(t *testing.T) { func TestUnitOnSignalChange(t *testing.T) {
setupPageTestOnClick(t, "tests/on_signal_change") setupPageTestOnLoad(t, "tests/on_signal_change")
} }

View File

@ -0,0 +1,39 @@
package smoketests
import (
"testing"
"github.com/Jeffail/gabs/v2"
"github.com/go-rod/rod"
"github.com/stretchr/testify/assert"
)
func TestPersistSignals(t *testing.T) {
setupPageTest(t, "tests/persist_signals", func(runner runnerFn) {
runner("tests/persist_signals", func(t *testing.T, page *rod.Page) {
checkLocalStorage := func(path string) string {
fromLocalStorage := page.MustEval(`k => localStorage[k]`, "datastar")
marshalled := fromLocalStorage.String()
c, err := gabs.ParseJSON([]byte(marshalled))
assert.NoError(t, err)
actual, ok := c.Path(path).Data().(string)
assert.True(t, ok)
return actual
}
page.MustWaitIdle()
assert.Equal(t, "", checkLocalStorage("foo"))
assert.Equal(t, "", checkLocalStorage("bar"))
assert.Equal(t, "", checkLocalStorage("baz"))
page.MustWaitIdle()
foo := page.MustElement("#foo")
bar := page.MustElement("#bar")
foo.MustInput("1")
bar.MustInput("1")
page.MustWaitIdle()
assert.Equal(t, "1", checkLocalStorage("foo"))
assert.Equal(t, "1", checkLocalStorage("bar"))
assert.Equal(t, "", checkLocalStorage("baz"))
})
})
}

View File

@ -10,6 +10,7 @@ import (
func TestExamplePersist(t *testing.T) { func TestExamplePersist(t *testing.T) {
setupPageTest(t, "examples/persist", func(runner runnerFn) { setupPageTest(t, "examples/persist", func(runner runnerFn) {
t.Skip("skipping test, handled by unit tests")
runner("persist", func(t *testing.T, page *rod.Page) { runner("persist", func(t *testing.T, page *rod.Page) {
page.MustWaitIdle() page.MustWaitIdle()

View File

@ -0,0 +1,9 @@
package smoketests
import (
"testing"
)
func TestUnitSetAllPath(t *testing.T) {
setupPageTestOnLoad(t, "tests/set_all_path")
}

View File

@ -0,0 +1,9 @@
package smoketests
import (
"testing"
)
func TestUnitSetAllPathWildcard(t *testing.T) {
setupPageTestOnLoad(t, "tests/set_all_path_wildcard")
}

View File

@ -0,0 +1,9 @@
package smoketests
import (
"testing"
)
func TestUnitSetAllPaths(t *testing.T) {
setupPageTestOnLoad(t, "tests/set_all_paths")
}

View File

@ -390,7 +390,7 @@ Now when the `Fetch a question` button is clicked, the server will respond with
### `data-indicator` ### `data-indicator`
The [`data-indicator`](/reference/attribute_plugins#data-data-indicator) attribute sets the value of a signal to `true` while the request is in flight, otherwise `false`. We can use this signal to show a loading indicator, which may be desirable for slower responses. The [`data-indicator`](/reference/attribute_plugins#data-indicator) attribute sets the value of a signal to `true` while the request is in flight, otherwise `false`. We can use this signal to show a loading indicator, which may be desirable for slower responses.
```html ```html
<div id="question"></div> <div id="question"></div>
@ -454,19 +454,19 @@ Actions in Datastar are helper functions that are available in `data-*` attribut
### `@setAll()` ### `@setAll()`
The `@setAll()` action sets the values of multiple signals at once. It takes a path prefix that is used to match against signals, and a value to set them to, as arguments. The `@setAll()` action sets the value of all matching signals to the expression provided in the second argument. The first argument can be one or more space-separated paths in which `*` can be used as a wildcard.
```html ```html
<button data-on-click="@setAll('form.', true)"></button> <button data-on-click="@setAll('foo.*', $bar)"></button>
``` ```
This sets the values of all signals namespaced under the `form` signal to `true`, which could be useful for enabling input fields in a form. This sets the values of all signals namespaced under the `foo` signal to the value of `$bar`. This can be useful for checking multiple checkbox fields in a form, for example:
```html ```html
<input type="checkbox" data-bind-checkboxes.checkbox1 /> Checkbox 1 <input type="checkbox" data-bind-checkboxes.checkbox1 /> Checkbox 1
<input type="checkbox" data-bind-checkboxes.checkbox2 /> Checkbox 2 <input type="checkbox" data-bind-checkboxes.checkbox2 /> Checkbox 2
<input type="checkbox" data-bind-checkboxes.checkbox3 /> Checkbox 3 <input type="checkbox" data-bind-checkboxes.checkbox3 /> Checkbox 3
<button data-on-click="@setAll('checkboxes.', true)">Check All</button> <button data-on-click="@setAll('checkboxes.*', true)">Check All</button>
``` ```
<div class="flex flex-col items-start gap-2 p-8 alert"> <div class="flex flex-col items-start gap-2 p-8 alert">
@ -488,26 +488,26 @@ This sets the values of all signals namespaced under the `form` signal to `true`
<input type="checkbox" data-bind-checkboxes1.checkbox3 class="toggle" /> <input type="checkbox" data-bind-checkboxes1.checkbox3 class="toggle" />
</label> </label>
</div> </div>
<button data-on-click="@setAll('checkboxes1.', true)" class="mt-4 btn btn-secondary"> <button data-on-click="@setAll('checkboxes1.*', true)" class="mt-4 btn btn-secondary">
Check All Check All
</button> </button>
</div> </div>
### `@toggleAll()` ### `@toggleAll()`
The `@toggleAll()` action toggles the values of multiple signals at once. It takes a path prefix that is used to match against signals, as an argument. The `@toggleAll()` action toggles the value of all matching signals. The first argument can be one or more space-separated paths in which `*` can be used as a wildcard.
```html ```html
<button data-on-click="@toggleAll('form.')"></button> <button data-on-click="@toggleAll('foo.*')"></button>
``` ```
This toggles the values of all signals containing `form.` (to either `true` or `false`), which could be useful for toggling input fields in a form. This toggles the values of all signals namespaced under the `foo` signal (to either `true` or `false`). This can be useful for toggling multiple checkbox fields in a form, for example:
```html ```html
<input type="checkbox" data-bind-checkboxes.checkbox1 /> Checkbox 1 <input type="checkbox" data-bind-checkboxes.checkbox1 /> Checkbox 1
<input type="checkbox" data-bind-checkboxes.checkbox2 /> Checkbox 2 <input type="checkbox" data-bind-checkboxes.checkbox2 /> Checkbox 2
<input type="checkbox" data-bind-checkboxes.checkbox3 /> Checkbox 3 <input type="checkbox" data-bind-checkboxes.checkbox3 /> Checkbox 3
<button data-on-click="@toggleAll('checkboxes.')">Toggle All</button> <button data-on-click="@toggleAll('checkboxes.*')">Toggle All</button>
``` ```
<div class="flex flex-col items-start gap-2 p-8 alert"> <div class="flex flex-col items-start gap-2 p-8 alert">
@ -529,7 +529,7 @@ This toggles the values of all signals containing `form.` (to either `true` or `
<input type="checkbox" data-bind-checkboxes2.checkbox_3 class="toggle" /> <input type="checkbox" data-bind-checkboxes2.checkbox_3 class="toggle" />
</label> </label>
</div> </div>
<button data-on-click="@toggleAll('checkboxes2.')" class="mt-4 btn btn-secondary"> <button data-on-click="@toggleAll('checkboxes2.*')" class="mt-4 btn btn-secondary">
Toggle All Toggle All
</button> </button>
</div> </div>

View File

@ -137,22 +137,38 @@ Copies the provided evaluated expression to the clipboard.
### `@setAll()` ### `@setAll()`
Arguments: `@setAll(pathPrefix: string, value: any)` Arguments: `@setAll(paths: string, value: any)`
Sets all the signals that start with the prefix to the expression provided in the second argument. This is useful for setting all the values of a signal namespace at once. Sets the value of all matching signals to the expression provided in the second argument. The first argument can be one or more space-separated signal paths in which `*` can be used as a wildcard.
```html ```html
<div data-on-change="@setAll('foo.', true)"></div> <!-- Sets the value of `$foo` to `true` -->
<div data-signals-foo="false">
<button data-on-click="@setAll('foo', $bar)"></button>
</div>
<!-- Sets the values of `$foo` and `$bar.baz` to `true` -->
<div data-signals-foo="false" data-signals-bar.baz="false">
<button data-on-click="@setAll('foo bar.*', true)"></button>
</div>
``` ```
### `@toggleAll()` ### `@toggleAll()`
Arguments: `@toggleAll(pathPrefix: string)` Arguments: `@toggleAll(paths: string)`
Toggles all the signals that start with the prefix. This is useful for toggling all the values of a signal namespace at once. Toggles the value of all matching signals. The first argument can be one or more space-separated signal paths or namespaced signal paths in which `*` can be used as a wildcard.
```html ```html
<div data-on-click="@toggleAll('foo.')"></div> <!-- Toggles the value of `$foo` -->
<div data-signals-foo="false">
<button data-on-change="@toggleAll('foo')"></button>
</div>
<!-- Toggles the values of `$foo` and `$bar.baz` -->
<div data-signals-foo="false" data-signals-bar.baz="false">
<button data-on-click="@toggleAll('foo bar.*')"></button>
</div>
``` ```
### `@fit()` ### `@fit()`

View File

@ -41,7 +41,7 @@ Datastar provides the following
<div> <div>
The Datastar <a href="https://marketplace.visualstudio.com/items?itemName=starfederation.datastar-vscode">VSCode The Datastar <a href="https://marketplace.visualstudio.com/items?itemName=starfederation.datastar-vscode">VSCode
extension</a> and <a href="https://plugins.jetbrains.com/plugin/26072-datastar-support">IntelliJ plugin</a> extension</a> and <a href="https://plugins.jetbrains.com/plugin/26072-datastar-support">IntelliJ plugin</a>
provided autocompletion for all <code>data-*</code> attributes. provide autocompletion for all <code>data-*</code> attributes.
</div> </div>
</div> </div>
@ -542,6 +542,7 @@ Modifiers allow you to modify the element intersection behavior and the timing o
- `.1s` - Throttle for 1 second. - `.1s` - Throttle for 1 second.
- `.noleading` - Throttle without leading edge. - `.noleading` - Throttle without leading edge.
- `.trail` - Throttle with trailing edge. - `.trail` - Throttle with trailing edge.
- `__viewtransition` - Wraps the expression in `document.startViewTransition()` when the View Transition API is available.
```html ```html
<div data-on-intersect__once__full="$fullyIntersected = true"></div> <div data-on-intersect__once__full="$fullyIntersected = true"></div>
@ -563,6 +564,7 @@ Modifiers allow you to modify the interval duration.
- `.500ms` - Interval duration of 500 milliseconds. - `.500ms` - Interval duration of 500 milliseconds.
- `.1s` - Interval duration of 1 second (default). - `.1s` - Interval duration of 1 second (default).
- `.leading` - Execute the first interval immediately. - `.leading` - Execute the first interval immediately.
- `__viewtransition` - Wraps the expression in `document.startViewTransition()` when the View Transition API is available.
```html ```html
<div data-on-interval__duration.500ms="$count++"></div> <div data-on-interval__duration.500ms="$count++"></div>
@ -583,6 +585,7 @@ Modifiers allow you to add a delay to the event listener.
- `__delay` - Delay the event listener. - `__delay` - Delay the event listener.
- `.500ms` - Delay for 500 milliseconds. - `.500ms` - Delay for 500 milliseconds.
- `.1s` - Delay for 1 second. - `.1s` - Delay for 1 second.
- `__viewtransition` - Wraps the expression in `document.startViewTransition()` when the View Transition API is available.
```html ```html
<div data-on-load__delay.500ms="$count = 1"></div> <div data-on-load__delay.500ms="$count = 1"></div>
@ -610,6 +613,7 @@ Modifiers allow you to modify the timing of the event listener.
- `.1s` - Throttle for 1 second. - `.1s` - Throttle for 1 second.
- `.noleading` - Throttle without leading edge. - `.noleading` - Throttle without leading edge.
- `.trail` - Throttle with trailing edge. - `.trail` - Throttle with trailing edge.
- `__viewtransition` - Wraps the expression in `document.startViewTransition()` when the View Transition API is available.
```html ```html
<div data-on-raf__debounce.10ms="$count++"></div> <div data-on-raf__debounce.10ms="$count++"></div>
@ -629,6 +633,14 @@ A key can be provided to only trigger the event when a specific signal changes.
<div data-on-signal-change-foo="$fooCount++"></div> <div data-on-signal-change-foo="$fooCount++"></div>
``` ```
The signal path can contain `*` as a wildcard.
```html
<div data-signals-foo.bar="1"
data-on-signal-change-foo.*="$fooCount++"
></div>
```
#### Modifiers #### Modifiers
Modifiers allow you to modify the timing of the event listener. Modifiers allow you to modify the timing of the event listener.
@ -643,6 +655,7 @@ Modifiers allow you to modify the timing of the event listener.
- `.1s` - Throttle for 1 second. - `.1s` - Throttle for 1 second.
- `.noleading` - Throttle without leading edge. - `.noleading` - Throttle without leading edge.
- `.trail` - Throttle with trailing edge. - `.trail` - Throttle with trailing edge.
- `__viewtransition` - Wraps the expression in `document.startViewTransition()` when the View Transition API is available.
```html ```html
<div data-on-signal-change__debounce.100ms="$count++"></div> <div data-on-signal-change__debounce.100ms="$count++"></div>

View File

@ -76,9 +76,9 @@ Action plugins are used in Datastar expressions to perform specific actions.
| Action | Description | | Action | Description |
|--------|-------------| |--------|-------------|
| [`@setAll()`](/reference/action_plugins#setall) | Sets all signals with a specific prefix to a provided value. | | [`@setAll()`](/reference/action_plugins#setall) | Sets all signal to a provided value. |
| [`@toggleAll()`](/reference/action_plugins#toggleall) | Toggles all signals that start with a given prefix. | | [`@toggleAll()`](/reference/action_plugins#toggleall) | Toggles all signal values. |
| [`@fit()`](/reference/action_plugins#fit) | Makes a value linearly interpolate from an original range to a new one. | | [`@fit()`](/reference/action_plugins#fit) | Makes a value linearly interpolate. |
View the [action plugins reference](/reference/action_plugins) View the [action plugins reference](/reference/action_plugins)

View File

@ -1,10 +1,8 @@
# On Signal Change # On Signal Change
Tests that a signal change is detected. Tests detecting a signal change.
<div data-signals="{foo: {bar: 0}, result: 0}" data-on-signal-change="$result = $foo.bar"> <div data-signals="{foo: {bar: 0}, result: 0}" data-on-signal-change="$result = $foo.bar" data-on-load="$foo.bar = 1">
<button id="clickable" data-on-click="$foo.bar = 1" class="btn">Change</button>
<hr />
Result: Result:
<code id="result" data-text="$result"></code> <code id="result" data-text="$result"></code>
<hr /> <hr />

View File

@ -1,10 +1,8 @@
# On Signal Change Path # On Signal Change Path
Tests that a signal change with a path is detected. Tests detecting a signal change with a path.
<div data-signals="{foo: {bar: 0}, result: 0}" data-on-signal-change-foo="$result = $foo.bar"> <div data-signals="{foo: 0, result: 0}" data-on-signal-change-foo="$result = $foo" data-on-load="$foo = 1">
<button id="clickable" data-on-click="$foo.bar = 1" class="btn">Change</button>
<hr />
Result: Result:
<code id="result" data-text="$result"></code> <code id="result" data-text="$result"></code>
<hr /> <hr />

View File

@ -1,10 +1,8 @@
# On Signal Change Path Once # On Signal Change Path Once
Tests that a signal change with a path is detected and the expression is called once. Tests detecting a signal change with a path, and that the expression is called once.
<div data-signals="{foo: {bar: 0}, result: 0}" data-on-signal-change-foo="$result++"> <div data-signals="{foo: {bar: 0}, result: 0}" data-on-signal-change-foo.bar="$result++" data-on-load="$foo.bar = 1">
<button id="clickable" data-on-click="$foo.bar = 1" class="btn">Change</button>
<hr />
Result: Result:
<code id="result" data-text="$result"></code> <code id="result" data-text="$result"></code>
<hr /> <hr />

View File

@ -0,0 +1,10 @@
# On Signal Change Path Wildcard
Tests detecting a signal change with a path using a wildcard.
<div data-signals="{foo: {bar: 0}, result: 0}" data-on-signal-change-foo.*="$result = $foo.bar" data-on-load="$foo.bar = 1">
Result:
<code id="result" data-text="$result"></code>
<hr />
Expected result on click: <code>1</code>
</div>

View File

@ -0,0 +1,8 @@
# Persist Signals
Tests persisting signals.
<div data-signals="{foo: 0, bar: 0, baz: 0}" data-persist="foo bar" data-on-load="$foo = 1; $bar = 1; $baz = 1">
Expected value in local storage (in alphabetical order):
<pre><code>datastar: {"bar":1,"foo":1}</code></pre>
</div>

View File

@ -0,0 +1,10 @@
# Set All Path
Tests the set all action on a single path.
<div data-signals="{foo: false, result: 0}" data-on-load="@setAll('foo', true)">
Result:
<code id="result" data-text="$result = $foo ? 1 : 0"></code>
<hr />
Expected result on load: <code>1</code>
</div>

View File

@ -0,0 +1,10 @@
# Set All Path Wildcard
Tests the set all action on a path using a wildcard.
<div data-signals="{foo: {bar: false}, result: 0}" data-on-load="@setAll('foo.*', true)">
Result:
<code id="result" data-text="$result = $foo.bar ? 1 : 0"></code>
<hr />
Expected result on load: <code>1</code>
</div>

View File

@ -0,0 +1,10 @@
# Set All Path
Tests the set all action on multiple paths.
<div data-signals="{foo: false, bar: false, result: 0}" data-on-load="@setAll('foo bar', true)">
Result:
<code id="result" data-text="$result = $foo && $bar ? 1 : 0"></code>
<hr />
Expected result on load: <code>1</code>
</div>