diff --git a/packages/docs/components/Action/js-api.md b/packages/docs/components/Action/js-api.md index fda961cd..e2331f4b 100644 --- a/packages/docs/components/Action/js-api.md +++ b/packages/docs/components/Action/js-api.md @@ -45,6 +45,8 @@ Modifiers can be chained with a `.` as separator: Use this option to define the components that should be used as targets to the [effect callback](#effect). Multiple components can be defined by using a single space as delimiter. +When an Action is inside a [`DataScope`](../DataScope/index.md), targets are limited to components in that same nearest scope. Actions outside a `DataScope` keep resolving targets globally. + ::: info Name definition The `Action` component will use the `name` property defined in the static `config` object of each class to resolve components on the page. diff --git a/packages/docs/components/DataBind/index.md b/packages/docs/components/DataBind/index.md index 6618a4fd..e44b3a5a 100644 --- a/packages/docs/components/DataBind/index.md +++ b/packages/docs/components/DataBind/index.md @@ -41,6 +41,68 @@ registerComponent(DataBind); ::: +### Multiple virtual bindings + +Use virtual `data-bind:*` attributes to update several parts of an element from the same value: + +| Syntax | Behavior | +| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- | +| `data-bind:prop.` | Assigns the DOM property. | +| `data-bind:attr.` | Removes the attribute for `false`, `null`, or `undefined`; writes an empty attribute for `true`; otherwise writes the stringified value. | +| `data-bind:class.` | Toggles the class according to the result's boolean value. | +| `data-bind:style.` | Clears the style for `false`, `null`, or `undefined`; otherwise writes the stringified value. | +| `data-bind:text` | Assigns `textContent`. | + +A non-empty attribute value is a JavaScript expression with access to `value`, `target`, and `$data`. An empty attribute passes through the current value. When an element has one or more virtual bindings, they replace the default single `textContent` or property update. Bindings are read when first used; changing their attributes afterward is not supported. + +Use kebab-case for camel-cased DOM properties because HTML attribute names are case-insensitive, for example `data-bind:prop.tab-index` targets `tabIndex`. + +For ARIA attributes, explicitly stringify booleans when `"false"` must remain present, for example `data-bind:attr.aria-selected="String(value === 'overview')"`. + +The following Tabs-like controls keep their labels while updating state and panels from the `tab` group: + +```html + + + + + +
+ Overview panel +
+
+ Details panel +
+``` + +Expression errors are reported without interrupting updates to the other bindings, matching `DataComputed` and `DataEffect` behavior. + ### Advanced usage with computed and effects The whole family of `Data...` components can be used to create reactivity in your HTML with only a few `data-...` attributes. diff --git a/packages/docs/components/DataBind/js-api.md b/packages/docs/components/DataBind/js-api.md index 0e802160..2437476d 100644 --- a/packages/docs/components/DataBind/js-api.md +++ b/packages/docs/components/DataBind/js-api.md @@ -28,14 +28,21 @@ If the option is explicitly set with the `data-option-prop` attribute, it will o - Type: `boolean` - Default: `false` -Use the `data-option-immediate` attribute on a `DataBind` component to propage its value on mount to other components in the same group. +Use the `data-option-immediate` attribute on a `DataBind` component to propagate its value on mount to other components in the same group. Immediate keyed values inside a [`DataScope`](../DataScope/index.md) are collected before subscribers are notified. ### `group` - Type: `string` - Default: `''` -The `group` option is used to group instances together. All related instances will be updated when the value changes. +The `group` option is used to group instances together. All related instances will be updated when the value changes. Inside a [`DataScope`](../DataScope/index.md), an omitted group inherits the scope's group and remains isolated from other scopes. + +### `key` + +- Type: `string` +- Default: the native form control `name`, when scoped + +A keyed value updates only bindings with the same key while notifying unkeyed subscribers. Keys are local to a `DataScope`; unscoped bindings preserve scalar group behavior. When using it with multiple checkboxes or select multiple, use the `[]` suffix to push each selected value in an array. See the [checkboxes example](/components/DataBind/examples.md#checkboxes) for more details on how this works. @@ -61,15 +68,42 @@ Wether new values should be pushed to an array instead of a single value. This i ## Methods -### `set(value: string | boolean | string[], dispatch = true)` +### `set(value: DataValue, dispatch = true)` Set the value for the current instance and dispatch it to others if the second parameter `dispatch` is set to `true` (default). +`DataValue` accepts `boolean`, `string`, `string[]`, `number`, `Date`, `null`, or `undefined`. + **Params** -- `value` (`string | boolean | string[]`): the value to set +- `value` (`DataValue`): the value to set - `dispatch` (`boolean`, default to `true`): wether to dispatch the value to other related instances or not +The mutation helpers below are available on `DataBind` and `DataModel`. They are not supported on computed values or effects. + +### `toggle(onValue = true, offValue = false)` + +Toggle between two values and dispatch the result to the group. Single checkboxes support the default boolean values; custom values require a target that can represent them without coercing them to `checked`. Radio inputs are not supported. + +Custom values can describe disclosure state without repeating comparison logic in an Action: + +```html + +``` + +### `increment(step = 1)` + +Convert the current value to a number, increment it by `step`, and dispatch the result. A non-numeric current value starts at `0`. Pass a negative step to decrement. Date inputs are not supported. + +### `cycle(values)` + +Select and dispatch the value following the current value in the given array. The method wraps to the first value; an unknown current value also selects the first value. An empty array does nothing. + ### `get()` Get the value for the current instance. diff --git a/packages/docs/components/DataComputed/js-api.md b/packages/docs/components/DataComputed/js-api.md index 00630a98..c0e93e7d 100644 --- a/packages/docs/components/DataComputed/js-api.md +++ b/packages/docs/components/DataComputed/js-api.md @@ -14,7 +14,7 @@ The `DataComputed` component extends the [`DataBind` component](../DataBind/js-a - Type: `string` - Default: `''` -Use this option to define a piece of JavaScript code to transform the value before it is updated on the target. The `value` and `target` variables can be used to access both the current value of the binding and the DOM element targeted by the changes. +Use this option to define a piece of JavaScript code to transform the value before it is updated on the target. The `value` and `target` variables can be used to access both the current value of the binding and the DOM element targeted by the changes. Inside a `DataScope`, the third `$data` argument is a frozen snapshot of all keyed values in the resolved group. **Example** diff --git a/packages/docs/components/DataEffect/js-api.md b/packages/docs/components/DataEffect/js-api.md index 36765726..2d911447 100644 --- a/packages/docs/components/DataEffect/js-api.md +++ b/packages/docs/components/DataEffect/js-api.md @@ -14,7 +14,7 @@ The `DataEffect` component extends the [`DataBind` component](../DataBind/js-api - Type: `string` - Default: `''` -Use this option to define a piece of JavaScript code to be executed when the value changes. The `value` and `target` variables can be used to access both the current value of the binding and the DOM element targeted by the changes. +Use this option to define a piece of JavaScript code to be executed when the value changes. The `value` and `target` variables can be used to access both the current value of the binding and the DOM element targeted by the changes. Inside a `DataScope`, the third `$data` argument is a frozen snapshot of all keyed values in the resolved group. **Example** diff --git a/packages/docs/components/DataScope/examples.md b/packages/docs/components/DataScope/examples.md new file mode 100644 index 00000000..6696b0b8 --- /dev/null +++ b/packages/docs/components/DataScope/examples.md @@ -0,0 +1,92 @@ +--- +title: DataScope examples +--- + +# Examples + +## Isolated reusable widgets + +Sibling scopes remain independent even when they use the same group name. In the following example, each profile owns its `person` group and its own `$data` snapshot. Updating one profile does not update the other. + + + + + + +:::code-group + +<<< ./stories/isolated.twig +<<< ./stories/isolated.js + +::: + + + +## Tabs + +The following example implements two independent tab interfaces with `DataScope`, [`Action`](../Action/index.md), and [`DataBind`](../DataBind/index.md). Each scope contains a hidden source of truth, while virtual bindings synchronize the selected state and panels. The `Tabs` component is not registered. + + + + + + +:::code-group + +<<< ./stories/tabs.twig +<<< ./stories/tabs.js + +::: + + + +## Accordion + +This example uses the same pattern for two independent accordion interfaces. Each action toggles the scoped hidden value; opening one panel closes the other panels in that scope, and clicking the open item closes it. The `Accordion` component is not registered. + + + + + + +:::code-group + +<<< ./stories/accordion.twig +<<< ./stories/accordion.js + +::: + + + +## Nested and explicit groups + +A Data component without `data-option-group` inherits the group of its nearest `DataScope`. An explicit group creates another group inside the same scope instead of escaping the scope. + +```html +
+ + + + + + +
+ + +
+
+``` diff --git a/packages/docs/components/DataScope/index.md b/packages/docs/components/DataScope/index.md new file mode 100644 index 00000000..b14093ac --- /dev/null +++ b/packages/docs/components/DataScope/index.md @@ -0,0 +1,39 @@ +--- +badges: [JS] +--- + +# DataScope + +Use the `DataScope` component to isolate [`DataBind`](../DataBind/index.md), [`DataModel`](../DataModel/index.md), [`DataComputed`](../DataComputed/index.md), and [`DataEffect`](../DataEffect/index.md) groups inside a reusable widget. [`Action`](../Action/index.md) targets inside the widget are isolated by the same scope. + +Descendant Data components inherit the scope's group unless they define their own `data-option-group`. Nested scopes use the nearest `DataScope` boundary. + +## Table of content + +- [Examples](./examples.md) +- [JavaScript API](./js-api.md) + +## Usage + +Import the `DataScope` component with the Data components used by your application. Add `DataScope` to the root element of a widget, then omit `data-option-group` from descendants that should inherit its default group. + + + + + + +:::code-group + +<<< ./stories/basic.twig +<<< ./stories/basic.js + +::: + + + +The native `name` of a form control becomes its key inside the scope. Use `data-option-key` to define a key explicitly. Unkeyed computed values and effects receive the complete frozen `$data` snapshot, allowing expressions to combine several keyed values. + +See the [JavaScript API](./js-api.md) for details about group inheritance, keys, and scoped data snapshots. diff --git a/packages/docs/components/DataScope/js-api.md b/packages/docs/components/DataScope/js-api.md new file mode 100644 index 00000000..1aa7dfb8 --- /dev/null +++ b/packages/docs/components/DataScope/js-api.md @@ -0,0 +1,56 @@ +--- +title: DataScope JavaScript API +outline: deep +--- + +# JS API + +The `DataScope` component defines a local boundary for Data groups and stores keyed values in frozen `$data` snapshots. + +## Options + +### `group` + +- Type: `string` +- Default: `'default'` + +The `group` option defines the group inherited by descendant Data components that do not set their own `data-option-group`. + +Each group belongs to its nearest `DataScope`. Two scopes can therefore use the same group name without sharing values. An explicit group on a descendant creates another isolated group within the same scope. + +## Keys + +Inside a scope, a Data component resolves its key from: + +1. its `data-option-key` option; +2. the native `name` of an ``, `