@continuum-dev/react 0.1.2 → 0.1.3
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/README.md +13 -5
- package/lib/error-boundary.d.ts +1 -0
- package/lib/error-boundary.d.ts.map +1 -1
- package/lib/error-boundary.js +8 -0
- package/lib/hooks.d.ts.map +1 -1
- package/lib/hooks.js +15 -3
- package/lib/renderer.d.ts.map +1 -1
- package/lib/renderer.js +85 -58
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -311,6 +311,8 @@ Tracks non-data state (focus, expansion, scroll, zoom, offsets) inside the sessi
|
|
|
311
311
|
const [viewport, setViewport] = useContinuumViewport('table');
|
|
312
312
|
```
|
|
313
313
|
|
|
314
|
+
If this hook is called from inside a collection-item scope, Continuum logs a development warning because viewport state is not currently scoped per collection item.
|
|
315
|
+
|
|
314
316
|
#### `useContinuumSession()`
|
|
315
317
|
|
|
316
318
|
Returns the active session for full session API access.
|
|
@@ -327,6 +329,8 @@ Subscribes to the full current `ContinuitySnapshot`.
|
|
|
327
329
|
const snapshot = useContinuumSnapshot();
|
|
328
330
|
```
|
|
329
331
|
|
|
332
|
+
Snapshots are delivered as immutable top-level copies so consumer code cannot accidentally mutate session internals.
|
|
333
|
+
|
|
330
334
|
#### `useContinuumHydrated()`
|
|
331
335
|
|
|
332
336
|
Indicates whether provider initialization came from persisted storage.
|
|
@@ -351,6 +355,8 @@ Handles action dispatch with built-in loading and result state.
|
|
|
351
355
|
const { dispatch, isDispatching, lastResult } = useContinuumAction('submit_form');
|
|
352
356
|
```
|
|
353
357
|
|
|
358
|
+
When multiple dispatches overlap, `isDispatching` and `lastResult` reflect the latest in-flight dispatch.
|
|
359
|
+
|
|
354
360
|
Example action component:
|
|
355
361
|
|
|
356
362
|
```tsx
|
|
@@ -471,14 +477,15 @@ function UndoButton() {
|
|
|
471
477
|
- scoped item state storage
|
|
472
478
|
- default template values
|
|
473
479
|
- canonical nested ids for collection children
|
|
480
|
+
- headless control wiring through your own collection components
|
|
474
481
|
|
|
475
|
-
|
|
482
|
+
Collection controls are now passed as props to your mapped components:
|
|
476
483
|
|
|
477
|
-
-
|
|
478
|
-
- `
|
|
479
|
-
- `data-continuum
|
|
484
|
+
- collection root components receive `onAdd`, `canAdd`, `onRemove`, and `canRemove`
|
|
485
|
+
- template root components receive `itemIndex`, `onRemove`, and `canRemove`
|
|
486
|
+
- no renderer-owned wrapper elements or `data-continuum-*` control attributes are injected
|
|
480
487
|
|
|
481
|
-
|
|
488
|
+
This keeps collection behavior built in while letting your design system fully own the markup and styles.
|
|
482
489
|
|
|
483
490
|
---
|
|
484
491
|
|
|
@@ -505,6 +512,7 @@ The fallback renders:
|
|
|
505
512
|
Every rendered node is wrapped in `NodeErrorBoundary`.
|
|
506
513
|
|
|
507
514
|
If one component crashes while rendering a dynamic node, sibling regions can keep working.
|
|
515
|
+
When a later rerender provides recoverable children, the boundary resets and the node can render again.
|
|
508
516
|
|
|
509
517
|
---
|
|
510
518
|
|
package/lib/error-boundary.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ interface NodeErrorBoundaryState {
|
|
|
13
13
|
export declare class NodeErrorBoundary extends Component<NodeErrorBoundaryProps, NodeErrorBoundaryState> {
|
|
14
14
|
state: NodeErrorBoundaryState;
|
|
15
15
|
static getDerivedStateFromError(error: unknown): NodeErrorBoundaryState;
|
|
16
|
+
componentDidUpdate(prevProps: NodeErrorBoundaryProps): void;
|
|
16
17
|
render(): string | number | bigint | boolean | Iterable<ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | import("react/jsx-runtime").JSX.Element | null | undefined;
|
|
17
18
|
}
|
|
18
19
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-boundary.d.ts","sourceRoot":"","sources":["../../../../packages/react/src/lib/error-boundary.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAElD,UAAU,sBAAsB;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED,UAAU,sBAAsB;IAC9B,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,qBAAa,iBAAkB,SAAQ,SAAS,CAC9C,sBAAsB,EACtB,sBAAsB,CACvB;IACU,KAAK,EAAE,sBAAsB,CAGpC;IAEF,MAAM,CAAC,wBAAwB,CAAC,KAAK,EAAE,OAAO,GAAG,sBAAsB;IAO9D,MAAM;CAUhB"}
|
|
1
|
+
{"version":3,"file":"error-boundary.d.ts","sourceRoot":"","sources":["../../../../packages/react/src/lib/error-boundary.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAElD,UAAU,sBAAsB;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED,UAAU,sBAAsB;IAC9B,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,qBAAa,iBAAkB,SAAQ,SAAS,CAC9C,sBAAsB,EACtB,sBAAsB,CACvB;IACU,KAAK,EAAE,sBAAsB,CAGpC;IAEF,MAAM,CAAC,wBAAwB,CAAC,KAAK,EAAE,OAAO,GAAG,sBAAsB;IAO9D,kBAAkB,CAAC,SAAS,EAAE,sBAAsB;IASpD,MAAM;CAUhB"}
|
package/lib/error-boundary.js
CHANGED
|
@@ -14,6 +14,14 @@ export class NodeErrorBoundary extends Component {
|
|
|
14
14
|
message: error instanceof Error ? error.message : String(error),
|
|
15
15
|
};
|
|
16
16
|
}
|
|
17
|
+
componentDidUpdate(prevProps) {
|
|
18
|
+
if (this.state.hasError && prevProps.children !== this.props.children) {
|
|
19
|
+
this.setState({
|
|
20
|
+
hasError: false,
|
|
21
|
+
message: '',
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
}
|
|
17
25
|
render() {
|
|
18
26
|
if (this.state.hasError) {
|
|
19
27
|
return (_jsxs("div", { "data-continuum-render-error": this.props.nodeId, children: ["Node render failed: ", this.props.nodeId, " (", this.state.message, ")"] }));
|
package/lib/hooks.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../../../packages/react/src/lib/hooks.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,kBAAkB,EAAE,SAAS,EAAE,aAAa,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAwDrH,UAAU,cAAc;IACtB,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,CAAC;IACpE,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,SAAS,GAAG,SAAS,CAAC;IACxD,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;CAC1D;AAED;;;GAGG;AACH,eAAO,MAAM,qBAAqB,gDAA6C,CAAC;AAEhF;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,OAAO,CAQ7C;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,GACb,CAAC,SAAS,GAAG,SAAS,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC,CAiDrD;AAED;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,kBAAkB,GAAG,IAAI,CAiDhE;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,GACb,CAAC,aAAa,GAAG,SAAS,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../../../packages/react/src/lib/hooks.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,kBAAkB,EAAE,SAAS,EAAE,aAAa,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAwDrH,UAAU,cAAc;IACtB,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,CAAC;IACpE,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,SAAS,GAAG,SAAS,CAAC;IACxD,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;CAC1D;AAED;;;GAGG;AACH,eAAO,MAAM,qBAAqB,gDAA6C,CAAC;AAEhF;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,OAAO,CAQ7C;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,GACb,CAAC,SAAS,GAAG,SAAS,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC,CAiDrD;AAED;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,kBAAkB,GAAG,IAAI,CAiDhE;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,GACb,CAAC,aAAa,GAAG,SAAS,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC,CA4C7D;AAED;;GAEG;AACH,wBAAgB,uBAAuB;YAS3B,UAAU,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;WACjC,UAAU,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;iBACzB,UAAU,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;iBACrC,UAAU,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;EAmCrD;AAED;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,OAAO,CAQ9C;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG;IACpD,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,EAAE,aAAa,GAAG,IAAI,CAAC;IAC/B,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB,CAsCA;AAED;;GAEG;AACH,wBAAgB,uBAAuB,IAAI;IACzC,cAAc,EAAE,OAAO,CAAC;IACxB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,SAAS,EAAE,MAAM,IAAI,CAAC;CACvB,CAgEA;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG;IACpD,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;IACpD,aAAa,EAAE,OAAO,CAAC;IACvB,UAAU,EAAE,YAAY,GAAG,IAAI,CAAC;CACjC,CAgCA"}
|
package/lib/hooks.js
CHANGED
|
@@ -82,7 +82,7 @@ export function useContinuumState(nodeId) {
|
|
|
82
82
|
}
|
|
83
83
|
valueCacheRef.current = nextValue;
|
|
84
84
|
return nextValue;
|
|
85
|
-
}, [store, nodeId]);
|
|
85
|
+
}, [scope, store, nodeId]);
|
|
86
86
|
const value = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
87
87
|
const setValue = useCallback((next) => {
|
|
88
88
|
if (scope) {
|
|
@@ -136,9 +136,15 @@ export function useContinuumSnapshot() {
|
|
|
136
136
|
*/
|
|
137
137
|
export function useContinuumViewport(nodeId) {
|
|
138
138
|
const ctx = useContext(ContinuumContext);
|
|
139
|
+
const scope = useContext(NodeStateScopeContext);
|
|
139
140
|
if (!ctx) {
|
|
140
141
|
throw new Error('useContinuumViewport must be used within a <ContinuumProvider>');
|
|
141
142
|
}
|
|
143
|
+
if (scope
|
|
144
|
+
&& typeof process !== 'undefined'
|
|
145
|
+
&& process.env.NODE_ENV !== 'production') {
|
|
146
|
+
console.warn(`useContinuumViewport("${nodeId}") called inside a collection scope. Viewport state is not supported for collection item nodes.`);
|
|
147
|
+
}
|
|
142
148
|
const { session, store } = ctx;
|
|
143
149
|
const viewportCacheRef = useRef(undefined);
|
|
144
150
|
const subscribe = useCallback((onStoreChange) => store.subscribeNode(nodeId, onStoreChange), [store, nodeId]);
|
|
@@ -307,15 +313,21 @@ export function useContinuumAction(intentId) {
|
|
|
307
313
|
const { session } = ctx;
|
|
308
314
|
const [isDispatching, setIsDispatching] = useState(false);
|
|
309
315
|
const [lastResult, setLastResult] = useState(null);
|
|
316
|
+
const dispatchIdRef = useRef(0);
|
|
310
317
|
const dispatch = useCallback(async (nodeId) => {
|
|
318
|
+
const dispatchId = ++dispatchIdRef.current;
|
|
311
319
|
setIsDispatching(true);
|
|
312
320
|
try {
|
|
313
321
|
const result = await session.dispatchAction(intentId, nodeId);
|
|
314
|
-
|
|
322
|
+
if (dispatchIdRef.current === dispatchId) {
|
|
323
|
+
setLastResult(result);
|
|
324
|
+
}
|
|
315
325
|
return result;
|
|
316
326
|
}
|
|
317
327
|
finally {
|
|
318
|
-
|
|
328
|
+
if (dispatchIdRef.current === dispatchId) {
|
|
329
|
+
setIsDispatching(false);
|
|
330
|
+
}
|
|
319
331
|
}
|
|
320
332
|
}, [session, intentId]);
|
|
321
333
|
return { dispatch, isDispatching, lastResult };
|
package/lib/renderer.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../../../packages/react/src/lib/renderer.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAIV,cAAc,EAEf,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../../../packages/react/src/lib/renderer.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAIV,cAAc,EAEf,MAAM,qBAAqB,CAAC;AAkZ7B;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,IAAI,EAAE,EAAE;IAAE,IAAI,EAAE,cAAc,CAAA;CAAE,2CAQnE"}
|
package/lib/renderer.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { jsx as _jsx,
|
|
2
|
-
import { memo, useContext, useMemo } from 'react';
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { memo, useCallback, useContext, useMemo } from 'react';
|
|
3
3
|
import { getChildNodes } from '@continuum-dev/contract';
|
|
4
4
|
import { ContinuumContext } from './context.js';
|
|
5
5
|
import { NodeStateScopeContext, useContinuumState } from './hooks.js';
|
|
@@ -83,68 +83,90 @@ function collectTemplateDefaults(node, parentPath = '') {
|
|
|
83
83
|
}
|
|
84
84
|
return values;
|
|
85
85
|
}
|
|
86
|
-
const StatefulNodeRenderer = memo(function StatefulNodeRenderer({ definition, parentPath }) {
|
|
86
|
+
const StatefulNodeRenderer = memo(function StatefulNodeRenderer({ definition, parentPath, mappedProps, }) {
|
|
87
87
|
const Component = useResolvedComponent(definition);
|
|
88
88
|
const canonicalId = toCanonicalId(definition.id, parentPath);
|
|
89
89
|
const [value, setValue] = useContinuumState(canonicalId);
|
|
90
90
|
if (definition.hidden) {
|
|
91
91
|
return null;
|
|
92
92
|
}
|
|
93
|
-
return (_jsx(
|
|
93
|
+
return (_jsx(_Fragment, { children: _jsx(NodeErrorBoundary, { nodeId: definition.id, children: _jsx(Component, { value: value, onChange: setValue, definition: definition, nodeId: canonicalId, ...mappedProps }) }) }));
|
|
94
94
|
});
|
|
95
|
-
const ContainerNodeRenderer = memo(function ContainerNodeRenderer({ definition, parentPath }) {
|
|
95
|
+
const ContainerNodeRenderer = memo(function ContainerNodeRenderer({ definition, parentPath, mappedProps, }) {
|
|
96
96
|
const Component = useResolvedComponent(definition);
|
|
97
97
|
if (definition.hidden) {
|
|
98
98
|
return null;
|
|
99
99
|
}
|
|
100
100
|
const canonicalId = toCanonicalId(definition.id, parentPath);
|
|
101
|
-
const childNodes = getChildNodes(definition).map((child) => (_jsx(NodeRenderer, { definition: child, parentPath: canonicalId }, child.id)));
|
|
102
|
-
return (_jsx(
|
|
101
|
+
const childNodes = getChildNodes(definition).map((child) => (_jsx(NodeRenderer, { definition: child, parentPath: canonicalId, mappedProps: mappedProps }, child.id)));
|
|
102
|
+
return (_jsx(_Fragment, { children: _jsx(NodeErrorBoundary, { nodeId: definition.id, children: _jsx(Component, { value: undefined, onChange: noopOnChange, definition: definition, nodeId: canonicalId, ...mappedProps, children: childNodes }) }) }));
|
|
103
103
|
});
|
|
104
104
|
const CollectionItemRenderer = memo(function CollectionItemRenderer({ collectionCanonicalId, itemIndex, template, templateDefaults, canRemove, onRemove, }) {
|
|
105
105
|
const ctx = useContext(ContinuumContext);
|
|
106
|
+
const parentScope = useContext(NodeStateScopeContext);
|
|
106
107
|
if (!ctx) {
|
|
107
108
|
throw new Error('ContinuumRenderer must be used within a <ContinuumProvider>');
|
|
108
109
|
}
|
|
109
110
|
const { session, store } = ctx;
|
|
110
|
-
const scope = useMemo(() =>
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
111
|
+
const scope = useMemo(() => {
|
|
112
|
+
const readCollectionValue = parentScope
|
|
113
|
+
? () => normalizeCollectionNodeValue(parentScope.getNodeValue(collectionCanonicalId))
|
|
114
|
+
: () => normalizeCollectionNodeValue(store.getNodeValue(collectionCanonicalId));
|
|
115
|
+
const writeCollectionValue = parentScope
|
|
116
|
+
? (next) => parentScope.setNodeValue(collectionCanonicalId, next)
|
|
117
|
+
: (next) => session.updateState(collectionCanonicalId, next);
|
|
118
|
+
const subscribeToCollection = parentScope
|
|
119
|
+
? (listener) => parentScope.subscribeNode(collectionCanonicalId, listener)
|
|
120
|
+
: (listener) => store.subscribeNode(collectionCanonicalId, listener);
|
|
121
|
+
return {
|
|
122
|
+
subscribeNode: (_nodeId, listener) => subscribeToCollection(listener),
|
|
123
|
+
getNodeValue: (nodeId) => {
|
|
124
|
+
const relativeId = toRelativeNodeId(collectionCanonicalId, nodeId);
|
|
125
|
+
if (!relativeId) {
|
|
126
|
+
return undefined;
|
|
127
|
+
}
|
|
128
|
+
const collectionValue = readCollectionValue();
|
|
129
|
+
return (collectionValue.value.items[itemIndex]?.values?.[relativeId] ??
|
|
130
|
+
templateDefaults[relativeId]);
|
|
131
|
+
},
|
|
132
|
+
setNodeValue: (nodeId, nextValue) => {
|
|
133
|
+
const relativeId = toRelativeNodeId(collectionCanonicalId, nodeId);
|
|
134
|
+
if (!relativeId) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const collectionValue = readCollectionValue();
|
|
138
|
+
const items = collectionValue.value.items.map((item) => ({
|
|
139
|
+
values: { ...item.values },
|
|
140
|
+
}));
|
|
141
|
+
while (items.length <= itemIndex) {
|
|
142
|
+
items.push({ values: {} });
|
|
143
|
+
}
|
|
144
|
+
items[itemIndex] = {
|
|
145
|
+
values: {
|
|
146
|
+
...items[itemIndex].values,
|
|
147
|
+
[relativeId]: nextValue,
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
writeCollectionValue({
|
|
151
|
+
...collectionValue,
|
|
152
|
+
value: { items },
|
|
153
|
+
});
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
}, [collectionCanonicalId, itemIndex, session, store, templateDefaults, parentScope]);
|
|
157
|
+
return (_jsx(NodeStateScopeContext.Provider, { value: scope, children: _jsx(NodeRenderer, { definition: template, parentPath: collectionCanonicalId, mappedProps: {
|
|
158
|
+
itemIndex,
|
|
159
|
+
canRemove,
|
|
160
|
+
onRemove: () => onRemove(itemIndex),
|
|
161
|
+
} }) }));
|
|
146
162
|
});
|
|
147
163
|
const CollectionNodeRenderer = memo(function CollectionNodeRenderer({ definition, parentPath, }) {
|
|
164
|
+
const ctx = useContext(ContinuumContext);
|
|
165
|
+
const parentScope = useContext(NodeStateScopeContext);
|
|
166
|
+
if (!ctx) {
|
|
167
|
+
throw new Error('ContinuumRenderer must be used within a <ContinuumProvider>');
|
|
168
|
+
}
|
|
169
|
+
const { store } = ctx;
|
|
148
170
|
const Component = useResolvedComponent(definition);
|
|
149
171
|
const canonicalId = toCanonicalId(definition.id, parentPath);
|
|
150
172
|
const [collectionValue, setCollectionValue] = useContinuumState(canonicalId);
|
|
@@ -158,52 +180,57 @@ const CollectionNodeRenderer = memo(function CollectionNodeRenderer({ definition
|
|
|
158
180
|
if (definition.hidden) {
|
|
159
181
|
return null;
|
|
160
182
|
}
|
|
161
|
-
const
|
|
162
|
-
|
|
183
|
+
const readCollectionValue = useCallback(() => normalizeCollectionNodeValue(parentScope
|
|
184
|
+
? parentScope.getNodeValue(canonicalId)
|
|
185
|
+
: store.getNodeValue(canonicalId)), [canonicalId, parentScope, store]);
|
|
186
|
+
const addItem = useCallback(() => {
|
|
187
|
+
const current = readCollectionValue();
|
|
188
|
+
if (maxItems !== undefined && current.value.items.length >= maxItems) {
|
|
163
189
|
return;
|
|
164
190
|
}
|
|
165
191
|
const items = [
|
|
166
|
-
...
|
|
192
|
+
...current.value.items.map((item) => ({
|
|
167
193
|
values: { ...item.values },
|
|
168
194
|
})),
|
|
169
195
|
{ values: { ...templateDefaults } },
|
|
170
196
|
];
|
|
171
197
|
setCollectionValue({
|
|
172
|
-
...
|
|
198
|
+
...current,
|
|
173
199
|
value: { items },
|
|
174
200
|
});
|
|
175
|
-
};
|
|
176
|
-
const removeItem = (index) => {
|
|
177
|
-
|
|
201
|
+
}, [maxItems, readCollectionValue, setCollectionValue, templateDefaults]);
|
|
202
|
+
const removeItem = useCallback((index) => {
|
|
203
|
+
const current = readCollectionValue();
|
|
204
|
+
if (current.value.items.length <= minItems) {
|
|
178
205
|
return;
|
|
179
206
|
}
|
|
180
|
-
const items =
|
|
207
|
+
const items = current.value.items
|
|
181
208
|
.map((item) => ({ values: { ...item.values } }))
|
|
182
209
|
.filter((_, itemIndex) => itemIndex !== index);
|
|
183
210
|
if (items.length < minItems) {
|
|
184
211
|
return;
|
|
185
212
|
}
|
|
186
213
|
setCollectionValue({
|
|
187
|
-
...
|
|
214
|
+
...current,
|
|
188
215
|
value: { items },
|
|
189
216
|
});
|
|
190
|
-
};
|
|
217
|
+
}, [minItems, readCollectionValue, setCollectionValue]);
|
|
191
218
|
const renderedItems = normalizedCollection.value.items.map((_, index) => (_jsx(CollectionItemRenderer, { collectionCanonicalId: canonicalId, itemIndex: index, template: definition.template, templateDefaults: templateDefaults, canRemove: canRemove, onRemove: removeItem }, index)));
|
|
192
|
-
return (_jsx(
|
|
219
|
+
return (_jsx(_Fragment, { children: _jsx(NodeErrorBoundary, { nodeId: definition.id, children: _jsx(Component, { value: collectionValue, onChange: setCollectionValue, definition: definition, nodeId: canonicalId, canAdd: canAdd, canRemove: canRemove, onAdd: addItem, onRemove: removeItem, children: renderedItems }) }) }));
|
|
193
220
|
});
|
|
194
|
-
const NodeRenderer = memo(function NodeRenderer({ definition, parentPath }) {
|
|
221
|
+
const NodeRenderer = memo(function NodeRenderer({ definition, parentPath, mappedProps, }) {
|
|
195
222
|
if (definition.type === 'collection') {
|
|
196
223
|
return _jsx(CollectionNodeRenderer, { definition: definition, parentPath: parentPath });
|
|
197
224
|
}
|
|
198
225
|
const childNodes = getChildNodes(definition);
|
|
199
226
|
if (childNodes.length > 0) {
|
|
200
|
-
return _jsx(ContainerNodeRenderer, { definition: definition, parentPath: parentPath });
|
|
227
|
+
return _jsx(ContainerNodeRenderer, { definition: definition, parentPath: parentPath, mappedProps: mappedProps });
|
|
201
228
|
}
|
|
202
|
-
return _jsx(StatefulNodeRenderer, { definition: definition, parentPath: parentPath });
|
|
229
|
+
return _jsx(StatefulNodeRenderer, { definition: definition, parentPath: parentPath, mappedProps: mappedProps });
|
|
203
230
|
});
|
|
204
231
|
/**
|
|
205
232
|
* Renders a `ViewDefinition` tree using components registered in `ContinuumProvider`.
|
|
206
233
|
*/
|
|
207
234
|
export function ContinuumRenderer({ view }) {
|
|
208
|
-
return (_jsx(
|
|
235
|
+
return (_jsx(_Fragment, { children: (view.nodes ?? []).map((node) => (_jsx(NodeRenderer, { definition: node, parentPath: "" }, node.id))) }));
|
|
209
236
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@continuum-dev/react",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"react": ">=18"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@continuum-dev/contract": "^0.1.
|
|
48
|
-
"@continuum-dev/session": "^0.1.
|
|
47
|
+
"@continuum-dev/contract": "^0.1.3",
|
|
48
|
+
"@continuum-dev/session": "^0.1.3"
|
|
49
49
|
}
|
|
50
50
|
}
|