@ea-lab/reactive-json-docs 2.10.0 → 2.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/public/rjbuild/docs/core/element/special/DataSync.md +41 -1
- package/public/rjbuild/docs/core/element/special/DataSync.yaml +41 -1
- package/public/rjbuild/docs/core/hook/index.md +3 -0
- package/public/rjbuild/docs/core/hook/index.yaml +3 -0
- package/public/rjbuild/docs/core/reaction/setClipboardData.md +1 -1
- package/public/rjbuild/docs/core/reaction/setClipboardData.yaml +1 -1
- package/public/rjbuild/docs/getting-started/rjbuild-structure.md +5 -1
- package/public/rjbuild/docs/getting-started/rjbuild-structure.yaml +5 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ea-lab/reactive-json-docs",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.11.0",
|
|
4
4
|
"description": "Complete documentation for Reactive-JSON - Components, examples and LLM-parsable guides",
|
|
5
5
|
"main": "public/rjbuild/docs/index.yaml",
|
|
6
6
|
"files": [
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"private": false,
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@craco/craco": "^7.1.0",
|
|
29
|
-
"@ea-lab/reactive-json": "^2.
|
|
29
|
+
"@ea-lab/reactive-json": "^2.11.1",
|
|
30
30
|
"@ea-lab/reactive-json-chartjs": "^1.1.1",
|
|
31
31
|
"@npmcli/fs": "^4.0.0",
|
|
32
32
|
"@reduxjs/toolkit": "^2.6.1",
|
|
@@ -262,6 +262,46 @@ const plugins = mergeComponentCollections([
|
|
|
262
262
|
|
|
263
263
|
The registry must be a **shared singleton**: every `DataSync` that resolves the same `dataSyncGroups` object joins the same groups. Provide one instance, not one per render.
|
|
264
264
|
|
|
265
|
+
## Host-controlled saving (`useDataSyncPolicy`)
|
|
266
|
+
|
|
267
|
+
Some hosts decide *when* the content is saved, instead of each `DataSync`: an editor with a global **Save** button, for example. The same RjBuild can then autosave in one host and wait for the Save button in another, without any change to its YAML.
|
|
268
|
+
|
|
269
|
+
Register a `useDataSyncPolicy` hook under `plugins.hook`. It is called on every render of every `DataSync`, with the resolved store path of the syncable (without the `data.` prefix) and the `DataSync` props, and returns the policy of this syncable:
|
|
270
|
+
|
|
271
|
+
| Key | Type | Description |
|
|
272
|
+
|-----|------|-------------|
|
|
273
|
+
| `manual` | boolean | `true` holds the user edits instead of saving them. Default: `false` (the `mode` of the `DataSync` applies). |
|
|
274
|
+
| `flushSignal` | any | In manual mode, a change of this value saves the held edits. Its value at mount does not save anything. |
|
|
275
|
+
| `onDirty` | function | Called on each held edit, e.g. to enable a Save button or a "leave the page?" guard. |
|
|
276
|
+
| `defaultMergeKey` | function(syncable) | The group identity used when the `DataSync` has no `mergeKey`, e.g. `(syncable) => syncable?.item_url`. Return `null` to leave the syncable alone. |
|
|
277
|
+
|
|
278
|
+
```js
|
|
279
|
+
import { useContext } from "react";
|
|
280
|
+
|
|
281
|
+
const useDataSyncPolicy = ({ path, props }) => {
|
|
282
|
+
// The host state, e.g. from a React context provided by the editor.
|
|
283
|
+
const editor = useContext(EditorContext);
|
|
284
|
+
|
|
285
|
+
return {
|
|
286
|
+
manual: Boolean(editor?.editing) && path === "content",
|
|
287
|
+
flushSignal: editor?.saveCount,
|
|
288
|
+
onDirty: editor?.markDirty,
|
|
289
|
+
defaultMergeKey: (syncable) => syncable?.item_url,
|
|
290
|
+
};
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
const plugins = mergeComponentCollections([{ hook: { useDataSyncPolicy } }]);
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
Behavior in manual mode:
|
|
297
|
+
- An edit is held and `onDirty` is called. The `trigger` property still saves immediately.
|
|
298
|
+
- When `flushSignal` changes, each `DataSync` holding edits saves them once. In a `mergeKey` group, only the owner writes, with the latest shared data.
|
|
299
|
+
- The edits are still mirrored live to the other members of the group, so that every mount shows the same content, and so that the owner writes the edits made on the other mounts.
|
|
300
|
+
|
|
301
|
+
Without the hook, every `DataSync` follows its own `mode`, and only an explicit `mergeKey` groups syncables. The hook is a plugin: it applies to the roots (and subroots) that receive it, so an app can restrict it to an editor.
|
|
302
|
+
|
|
303
|
+
**Limitation**: in manual mode, the live mirror follows each store change. In a form updated on each keystroke (`TextField`), each keystroke is mirrored to the other members of the group.
|
|
304
|
+
|
|
265
305
|
## Using with Templates
|
|
266
306
|
|
|
267
307
|
`DataSync` supports `TemplateContext`, so it can be used inside templates to sync individual items in a list:
|
|
@@ -362,6 +402,6 @@ renderView:
|
|
|
362
402
|
- Only one sync can be active at a time per `DataSync` instance. Concurrent changes while a sync is in flight are not queued.
|
|
363
403
|
- The entire Syncable Object is sent in the POST body, not just the `data` field.
|
|
364
404
|
- Only POST requests are supported for submission. Custom HTTP methods are not configurable.
|
|
365
|
-
- `
|
|
405
|
+
- `delete_url` is part of the Syncable Object structure but is not used by the component itself. `item_url` is only used to recognize a syncable reloaded from the server (its data is then not posted back).
|
|
366
406
|
- No built-in support for optimistic updates. The local data is replaced by the server response.
|
|
367
407
|
- CORS errors are not retried (they are indistinguishable from network errors when the browser is online).
|
|
@@ -335,6 +335,46 @@ renderView:
|
|
|
335
335
|
|
|
336
336
|
The registry must be a **shared singleton**: every `DataSync` that resolves the same `dataSyncGroups` object joins the same groups. Provide one instance, not one per render.
|
|
337
337
|
|
|
338
|
+
## Host-controlled saving (`useDataSyncPolicy`)
|
|
339
|
+
|
|
340
|
+
Some hosts decide *when* the content is saved, instead of each `DataSync`: an editor with a global **Save** button, for example. The same RjBuild can then autosave in one host and wait for the Save button in another, without any change to its YAML.
|
|
341
|
+
|
|
342
|
+
Register a `useDataSyncPolicy` hook under `plugins.hook`. It is called on every render of every `DataSync`, with the resolved store path of the syncable (without the `data.` prefix) and the `DataSync` props, and returns the policy of this syncable:
|
|
343
|
+
|
|
344
|
+
| Key | Type | Description |
|
|
345
|
+
|-----|------|-------------|
|
|
346
|
+
| `manual` | boolean | `true` holds the user edits instead of saving them. Default: `false` (the `mode` of the `DataSync` applies). |
|
|
347
|
+
| `flushSignal` | any | In manual mode, a change of this value saves the held edits. Its value at mount does not save anything. |
|
|
348
|
+
| `onDirty` | function | Called on each held edit, e.g. to enable a Save button or a "leave the page?" guard. |
|
|
349
|
+
| `defaultMergeKey` | function(syncable) | The group identity used when the `DataSync` has no `mergeKey`, e.g. `(syncable) => syncable?.item_url`. Return `null` to leave the syncable alone. |
|
|
350
|
+
|
|
351
|
+
```js
|
|
352
|
+
import { useContext } from "react";
|
|
353
|
+
|
|
354
|
+
const useDataSyncPolicy = ({ path, props }) => {
|
|
355
|
+
// The host state, e.g. from a React context provided by the editor.
|
|
356
|
+
const editor = useContext(EditorContext);
|
|
357
|
+
|
|
358
|
+
return {
|
|
359
|
+
manual: Boolean(editor?.editing) && path === "content",
|
|
360
|
+
flushSignal: editor?.saveCount,
|
|
361
|
+
onDirty: editor?.markDirty,
|
|
362
|
+
defaultMergeKey: (syncable) => syncable?.item_url,
|
|
363
|
+
};
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
const plugins = mergeComponentCollections([{ hook: { useDataSyncPolicy } }]);
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
Behavior in manual mode:
|
|
370
|
+
- An edit is held and `onDirty` is called. The `trigger` property still saves immediately.
|
|
371
|
+
- When `flushSignal` changes, each `DataSync` holding edits saves them once. In a `mergeKey` group, only the owner writes, with the latest shared data.
|
|
372
|
+
- The edits are still mirrored live to the other members of the group, so that every mount shows the same content, and so that the owner writes the edits made on the other mounts.
|
|
373
|
+
|
|
374
|
+
Without the hook, every `DataSync` follows its own `mode`, and only an explicit `mergeKey` groups syncables. The hook is a plugin: it applies to the roots (and subroots) that receive it, so an app can restrict it to an editor.
|
|
375
|
+
|
|
376
|
+
**Limitation**: in manual mode, the live mirror follows each store change. In a form updated on each keystroke (`TextField`), each keystroke is mirrored to the other members of the group.
|
|
377
|
+
|
|
338
378
|
## Using with Templates
|
|
339
379
|
|
|
340
380
|
`DataSync` supports `TemplateContext`, so it can be used inside templates to sync individual items in a list:
|
|
@@ -382,6 +422,6 @@ renderView:
|
|
|
382
422
|
- Only one sync can be active at a time per `DataSync` instance. Concurrent changes while a sync is in flight are not queued.
|
|
383
423
|
- The entire Syncable Object is sent in the POST body, not just the `data` field.
|
|
384
424
|
- Only POST requests are supported for submission. Custom HTTP methods are not configurable.
|
|
385
|
-
- `
|
|
425
|
+
- `delete_url` is part of the Syncable Object structure but is not used by the component itself. `item_url` is only used to recognize a syncable reloaded from the server (its data is then not posted back).
|
|
386
426
|
- No built-in support for optimistic updates. The local data is replaced by the server response.
|
|
387
427
|
- CORS errors are not retried (they are indistinguishable from network errors when the browser is online).
|
|
@@ -12,6 +12,9 @@ Hooks in Reactive-JSON are reusable React functions that encapsulate common comp
|
|
|
12
12
|
### Attribute Processing
|
|
13
13
|
- **[useTransformedAttributes](./useTransformedAttributes.md)**: Applies attribute transformations based on configured transformers
|
|
14
14
|
|
|
15
|
+
### Hooks provided by the application
|
|
16
|
+
- **useDataSyncPolicy**: Decides how each `DataSync` saves (automatic or held until a flush signal) and how the syncables are grouped. The core has no default implementation: without it, every `DataSync` follows its own `mode`. See [DataSync: Host-controlled saving](../element/special/DataSync.md#host-controlled-saving-usedatasyncpolicy).
|
|
17
|
+
|
|
15
18
|
## Architecture
|
|
16
19
|
|
|
17
20
|
Reactive-JSON hooks follow these core principles:
|
|
@@ -15,6 +15,9 @@ renderView:
|
|
|
15
15
|
### Attribute Processing
|
|
16
16
|
- **[useTransformedAttributes](./useTransformedAttributes)**: Applies attribute transformations based on configured transformers
|
|
17
17
|
|
|
18
|
+
### Hooks provided by the application
|
|
19
|
+
- **useDataSyncPolicy**: Decides how each `DataSync` saves (automatic or held until a flush signal) and how the syncables are grouped. The core has no default implementation: without it, every `DataSync` follows its own `mode`. See *Host-controlled saving* in [DataSync](../element/special/DataSync).
|
|
20
|
+
|
|
18
21
|
## Architecture
|
|
19
22
|
|
|
20
23
|
Reactive-JSON hooks follow these core principles:
|
|
@@ -12,7 +12,7 @@ The `setClipboardData` reaction copies a specified value to the user's clipboard
|
|
|
12
12
|
- When triggered, the reaction evaluates the `value` property.
|
|
13
13
|
- A string or a number is copied with the `navigator.clipboard.writeText()` API. Other values (objects, arrays) and empty values are ignored.
|
|
14
14
|
- Outside a secure context (plain http on a host other than `localhost`), where the Clipboard API is not available, or when it fails, the copy falls back to the deprecated `copy` command on a temporary text field.
|
|
15
|
-
- An error is logged to the console if the copy fails, and `statusPath` receives `"failed"`.
|
|
15
|
+
- An error is logged to the console if the copy fails, and `statusPath` receives `"failed"`. A value that cannot be copied (empty, object, array) also reports `"failed"`.
|
|
16
16
|
|
|
17
17
|
## Example
|
|
18
18
|
|
|
@@ -29,7 +29,7 @@ renderView:
|
|
|
29
29
|
- When triggered, the reaction evaluates the `value` property.
|
|
30
30
|
- A string or a number is copied with the `navigator.clipboard.writeText()` API. Other values (objects, arrays) and empty values are ignored.
|
|
31
31
|
- Outside a secure context (plain http on a host other than `localhost`), where the Clipboard API is not available, or when it fails, the copy falls back to the deprecated `copy` command on a temporary text field.
|
|
32
|
-
- An error is logged to the console if the copy fails, and `statusPath` receives `"failed"`.
|
|
32
|
+
- An error is logged to the console if the copy fails, and `statusPath` receives `"failed"`. A value that cannot be copied (empty, object, array) also reports `"failed"`.
|
|
33
33
|
|
|
34
34
|
## Limitations
|
|
35
35
|
|
|
@@ -336,10 +336,14 @@ additionalDataSource:
|
|
|
336
336
|
blocking: true
|
|
337
337
|
```
|
|
338
338
|
|
|
339
|
+
**Array values:** when the value resolves to an array, the param is repeated for each item: `{ param: "domains", value: ~~.domains }` with `domains: ["a.fr", "b.fr"]` gives `?domains=a.fr&domains=b.fr`. The empty items are skipped, and an empty array counts as an empty value. For a PHP backend, name the param with brackets (`domains[]`) to receive an array.
|
|
340
|
+
|
|
339
341
|
**Null handling for params:**
|
|
340
342
|
- If either the key or the value resolves to `null`/empty and `required` is absent or `false`, the param is **silently omitted** from the URL.
|
|
341
343
|
- If either resolves to `null`/empty and `required: true` is set, the entire URL is **aborted**, triggering `fallbackDataSource` if defined.
|
|
342
344
|
|
|
345
|
+
An aborted URL logs a warning naming the caller (`additionalDataSource`, `redirectNow`, `href attribute`...), the empty part and its position in the array.
|
|
346
|
+
|
|
343
347
|
```yaml
|
|
344
348
|
additionalDataSource:
|
|
345
349
|
- src:
|
|
@@ -409,7 +413,7 @@ The segments format is also accepted by:
|
|
|
409
413
|
- the URL attributes (`href`, `src`, `action`, `formAction`, `poster`) of any element: the attribute is omitted when a required segment is empty;
|
|
410
414
|
- `parts` of the [joinData](../core/reaction/joinData.md) reaction, which writes the composed string in the data.
|
|
411
415
|
|
|
412
|
-
There, the references are evaluated in the current template context: `~.slug` reads the current template data (e.g. the current `Switch` item), `~~.` the global data, `~>key` an ancestor. In `additionalDataSource.src`, both `~.` and `~~.` read the root data.
|
|
416
|
+
There, the references are evaluated in the current template context: `~.slug` reads the current template data (e.g. the current `Switch` item), `~~.` the global data, `~>key` an ancestor. In `additionalDataSource.src`, both `~.` and `~~.` read the root data. In a reaction, the event placeholders also work in the parts, `required` included: `{ segment: "<reactive-json:event-new-value>.id", required: true }`.
|
|
413
417
|
|
|
414
418
|
```yaml
|
|
415
419
|
- type: a
|
|
@@ -355,10 +355,14 @@ renderView:
|
|
|
355
355
|
|
|
356
356
|
A param object adds a key-value pair to the URL query string. Both the key and the value accept store references.
|
|
357
357
|
|
|
358
|
+
**Array values:** when the value resolves to an array, the param is repeated for each item: `{ param: "domains", value: ~~.domains }` with `domains: ["a.fr", "b.fr"]` gives `?domains=a.fr&domains=b.fr`. The empty items are skipped, and an empty array counts as an empty value. For a PHP backend, name the param with brackets (`domains[]`) to receive an array.
|
|
359
|
+
|
|
358
360
|
**Null handling:**
|
|
359
361
|
- If either the key or the value resolves to `null`/empty and `required` is absent or `false`, the param is **silently omitted** from the URL.
|
|
360
362
|
- If either resolves to `null`/empty and `required: true` is set, the entire URL is **aborted**, triggering `fallbackDataSource` if defined.
|
|
361
363
|
|
|
364
|
+
An aborted URL logs a warning naming the caller (`additionalDataSource`, `redirectNow`, `href attribute`...), the empty part and its position in the array.
|
|
365
|
+
|
|
362
366
|
- type: TabbedSerializer
|
|
363
367
|
yamlSerializedContent: |
|
|
364
368
|
additionalDataSource:
|
|
@@ -439,7 +443,7 @@ renderView:
|
|
|
439
443
|
- the URL attributes (`href`, `src`, `action`, `formAction`, `poster`) of any element: the attribute is omitted when a required segment is empty;
|
|
440
444
|
- `parts` of the [joinData](../core/reaction/joinData) reaction, which writes the composed string in the data.
|
|
441
445
|
|
|
442
|
-
There, the references are evaluated in the current template context: `~.slug` reads the current template data (e.g. the current `Switch` item), `~~.` the global data, `~>key` an ancestor. In `additionalDataSource.src`, both `~.` and `~~.` read the root data.
|
|
446
|
+
There, the references are evaluated in the current template context: `~.slug` reads the current template data (e.g. the current `Switch` item), `~~.` the global data, `~>key` an ancestor. In `additionalDataSource.src`, both `~.` and `~~.` read the root data. In a reaction, the event placeholders also work in the parts, `required` included: `{ segment: "<reactive-json:event-new-value>.id", required: true }`.
|
|
443
447
|
|
|
444
448
|
```yaml
|
|
445
449
|
- type: a
|