@continuum-dev/react 0.1.1 → 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 +41 -5
- package/lib/context.d.ts +26 -0
- package/lib/context.d.ts.map +1 -1
- package/lib/context.js +9 -0
- package/lib/error-boundary.d.ts +4 -0
- package/lib/error-boundary.d.ts.map +1 -1
- package/lib/error-boundary.js +11 -0
- package/lib/fallback.d.ts +3 -0
- package/lib/fallback.d.ts.map +1 -1
- package/lib/fallback.js +3 -0
- package/lib/hooks.d.ts +45 -1
- package/lib/hooks.d.ts.map +1 -1
- package/lib/hooks.js +74 -2
- package/lib/renderer.d.ts +3 -0
- package/lib/renderer.d.ts.map +1 -1
- package/lib/renderer.js +88 -58
- package/lib/types.d.ts +40 -0
- package/lib/types.d.ts.map +1 -1
- 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.
|
|
@@ -343,6 +347,36 @@ Scans current snapshot values for suggestions and provides accept-all / reject-a
|
|
|
343
347
|
const { hasSuggestions, acceptAll, rejectAll } = useContinuumSuggestions();
|
|
344
348
|
```
|
|
345
349
|
|
|
350
|
+
#### `useContinuumAction(intentId)`
|
|
351
|
+
|
|
352
|
+
Handles action dispatch with built-in loading and result state.
|
|
353
|
+
|
|
354
|
+
```ts
|
|
355
|
+
const { dispatch, isDispatching, lastResult } = useContinuumAction('submit_form');
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
When multiple dispatches overlap, `isDispatching` and `lastResult` reflect the latest in-flight dispatch.
|
|
359
|
+
|
|
360
|
+
Example action component:
|
|
361
|
+
|
|
362
|
+
```tsx
|
|
363
|
+
function SubmitButton({ definition }: ContinuumNodeProps) {
|
|
364
|
+
const intentId = definition.intentId ?? '';
|
|
365
|
+
const { dispatch, isDispatching, lastResult } = useContinuumAction(intentId);
|
|
366
|
+
|
|
367
|
+
return (
|
|
368
|
+
<div>
|
|
369
|
+
<button disabled={isDispatching} onClick={() => dispatch(definition.id)}>
|
|
370
|
+
{isDispatching ? 'Working...' : definition.label}
|
|
371
|
+
</button>
|
|
372
|
+
{lastResult && (
|
|
373
|
+
<span>{lastResult.success ? 'Done' : 'Failed'}</span>
|
|
374
|
+
)}
|
|
375
|
+
</div>
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
```
|
|
379
|
+
|
|
346
380
|
---
|
|
347
381
|
|
|
348
382
|
## The node contract
|
|
@@ -443,14 +477,15 @@ function UndoButton() {
|
|
|
443
477
|
- scoped item state storage
|
|
444
478
|
- default template values
|
|
445
479
|
- canonical nested ids for collection children
|
|
480
|
+
- headless control wiring through your own collection components
|
|
446
481
|
|
|
447
|
-
|
|
482
|
+
Collection controls are now passed as props to your mapped components:
|
|
448
483
|
|
|
449
|
-
-
|
|
450
|
-
- `
|
|
451
|
-
- `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
|
|
452
487
|
|
|
453
|
-
|
|
488
|
+
This keeps collection behavior built in while letting your design system fully own the markup and styles.
|
|
454
489
|
|
|
455
490
|
---
|
|
456
491
|
|
|
@@ -477,6 +512,7 @@ The fallback renders:
|
|
|
477
512
|
Every rendered node is wrapped in `NodeErrorBoundary`.
|
|
478
513
|
|
|
479
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.
|
|
480
516
|
|
|
481
517
|
---
|
|
482
518
|
|
package/lib/context.d.ts
CHANGED
|
@@ -2,22 +2,48 @@ import type { Session } from '@continuum-dev/session';
|
|
|
2
2
|
import type { ContinuitySnapshot, NodeValue, ViewportState } from '@continuum-dev/contract';
|
|
3
3
|
import type { ContinuumNodeMap, ContinuumProviderProps } from './types.js';
|
|
4
4
|
type Listener = () => void;
|
|
5
|
+
/**
|
|
6
|
+
* Subscription-oriented store facade over Continuum session state.
|
|
7
|
+
*/
|
|
5
8
|
export interface ContinuumStore {
|
|
9
|
+
/** Returns the latest continuity snapshot. */
|
|
6
10
|
getSnapshot(): ContinuitySnapshot | null;
|
|
11
|
+
/** Subscribes to snapshot updates. */
|
|
7
12
|
subscribeSnapshot(listener: Listener): () => void;
|
|
13
|
+
/** Subscribes to diagnostics-related updates. */
|
|
8
14
|
subscribeDiagnostics(listener: Listener): () => void;
|
|
15
|
+
/** Returns a node value by canonical id. */
|
|
9
16
|
getNodeValue(nodeId: string): NodeValue | undefined;
|
|
17
|
+
/** Returns viewport state by canonical node id. */
|
|
10
18
|
getNodeViewport(nodeId: string): ViewportState | undefined;
|
|
19
|
+
/** Subscribes to updates for a specific node id. */
|
|
11
20
|
subscribeNode(nodeId: string, listener: Listener): () => void;
|
|
21
|
+
/** Releases store subscriptions and listeners. */
|
|
12
22
|
destroy(): void;
|
|
13
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Value shape exposed through `ContinuumContext`.
|
|
26
|
+
*/
|
|
14
27
|
export interface ContinuumContextValue {
|
|
28
|
+
/** Backing Continuum session instance. */
|
|
15
29
|
session: Session;
|
|
30
|
+
/** Subscription-friendly store facade over session state. */
|
|
16
31
|
store: ContinuumStore;
|
|
32
|
+
/** Resolved node type to component map. */
|
|
17
33
|
componentMap: ContinuumNodeMap;
|
|
34
|
+
/** True when provider loaded from existing persisted state. */
|
|
18
35
|
wasHydrated: boolean;
|
|
19
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* React context backing all `@continuum-dev/react` hooks and renderer behavior.
|
|
39
|
+
*/
|
|
20
40
|
export declare const ContinuumContext: import("react").Context<ContinuumContextValue | null>;
|
|
41
|
+
/**
|
|
42
|
+
* Initializes and provides Continuum session context to the React subtree.
|
|
43
|
+
*
|
|
44
|
+
* Creates (or hydrates) a session once, wires optional persistence, and
|
|
45
|
+
* provides a reactive store used by hooks and renderer components.
|
|
46
|
+
*/
|
|
21
47
|
export declare function ContinuumProvider({ components, persist, storageKey, maxPersistBytes, onPersistError, sessionOptions, children, }: ContinuumProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
22
48
|
export {};
|
|
23
49
|
//# sourceMappingURL=context.d.ts.map
|
package/lib/context.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../../../packages/react/src/lib/context.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,kBAAkB,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACxF,OAAO,KAAK,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAE3E,KAAK,QAAQ,GAAG,MAAM,IAAI,CAAC;AA+C3B,MAAM,WAAW,cAAc;IAC7B,WAAW,IAAI,kBAAkB,GAAG,IAAI,CAAC;IACzC,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,IAAI,CAAC;IAClD,oBAAoB,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,IAAI,CAAC;IACrD,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CAAC;IACpD,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAAC;IAC3D,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,MAAM,IAAI,CAAC;IAC9D,OAAO,IAAI,IAAI,CAAC;CACjB;AAoFD,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,cAAc,CAAC;IACtB,YAAY,EAAE,gBAAgB,CAAC;IAC/B,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,eAAO,MAAM,gBAAgB,uDAAoD,CAAC;AAuClF,wBAAgB,iBAAiB,CAAC,EAChC,UAAU,EACV,OAAe,EACf,UAAgC,EAChC,eAAe,EACf,cAAc,EACd,cAAc,EACd,QAAQ,GACT,EAAE,sBAAsB,2CAsDxB"}
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../../../packages/react/src/lib/context.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,kBAAkB,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACxF,OAAO,KAAK,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAE3E,KAAK,QAAQ,GAAG,MAAM,IAAI,CAAC;AA+C3B;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,8CAA8C;IAC9C,WAAW,IAAI,kBAAkB,GAAG,IAAI,CAAC;IACzC,sCAAsC;IACtC,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,IAAI,CAAC;IAClD,iDAAiD;IACjD,oBAAoB,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,IAAI,CAAC;IACrD,4CAA4C;IAC5C,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CAAC;IACpD,mDAAmD;IACnD,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAAC;IAC3D,oDAAoD;IACpD,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,MAAM,IAAI,CAAC;IAC9D,kDAAkD;IAClD,OAAO,IAAI,IAAI,CAAC;CACjB;AAoFD;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,0CAA0C;IAC1C,OAAO,EAAE,OAAO,CAAC;IACjB,6DAA6D;IAC7D,KAAK,EAAE,cAAc,CAAC;IACtB,2CAA2C;IAC3C,YAAY,EAAE,gBAAgB,CAAC;IAC/B,+DAA+D;IAC/D,WAAW,EAAE,OAAO,CAAC;CACtB;AAED;;GAEG;AACH,eAAO,MAAM,gBAAgB,uDAAoD,CAAC;AAuClF;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,UAAU,EACV,OAAe,EACf,UAAgC,EAChC,eAAe,EACf,cAAc,EACd,cAAc,EACd,QAAQ,GACT,EAAE,sBAAsB,2CAsDxB"}
|
package/lib/context.js
CHANGED
|
@@ -112,6 +112,9 @@ function createContinuumStore(session) {
|
|
|
112
112
|
},
|
|
113
113
|
};
|
|
114
114
|
}
|
|
115
|
+
/**
|
|
116
|
+
* React context backing all `@continuum-dev/react` hooks and renderer behavior.
|
|
117
|
+
*/
|
|
115
118
|
export const ContinuumContext = createContext(null);
|
|
116
119
|
const DEFAULT_STORAGE_KEY = 'continuum_session';
|
|
117
120
|
function resolveStorage(persist) {
|
|
@@ -143,6 +146,12 @@ function useStableMap(map) {
|
|
|
143
146
|
ref.current = map;
|
|
144
147
|
return map;
|
|
145
148
|
}
|
|
149
|
+
/**
|
|
150
|
+
* Initializes and provides Continuum session context to the React subtree.
|
|
151
|
+
*
|
|
152
|
+
* Creates (or hydrates) a session once, wires optional persistence, and
|
|
153
|
+
* provides a reactive store used by hooks and renderer components.
|
|
154
|
+
*/
|
|
146
155
|
export function ContinuumProvider({ components, persist = false, storageKey = DEFAULT_STORAGE_KEY, maxPersistBytes, onPersistError, sessionOptions, children, }) {
|
|
147
156
|
const storage = resolveStorage(persist);
|
|
148
157
|
const stableComponents = useStableMap(components);
|
package/lib/error-boundary.d.ts
CHANGED
|
@@ -7,9 +7,13 @@ interface NodeErrorBoundaryState {
|
|
|
7
7
|
hasError: boolean;
|
|
8
8
|
message: string;
|
|
9
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* Per-node error boundary used by the renderer to isolate component failures.
|
|
12
|
+
*/
|
|
10
13
|
export declare class NodeErrorBoundary extends Component<NodeErrorBoundaryProps, NodeErrorBoundaryState> {
|
|
11
14
|
state: NodeErrorBoundaryState;
|
|
12
15
|
static getDerivedStateFromError(error: unknown): NodeErrorBoundaryState;
|
|
16
|
+
componentDidUpdate(prevProps: NodeErrorBoundaryProps): void;
|
|
13
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;
|
|
14
18
|
}
|
|
15
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,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
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Component } from 'react';
|
|
3
|
+
/**
|
|
4
|
+
* Per-node error boundary used by the renderer to isolate component failures.
|
|
5
|
+
*/
|
|
3
6
|
export class NodeErrorBoundary extends Component {
|
|
4
7
|
state = {
|
|
5
8
|
hasError: false,
|
|
@@ -11,6 +14,14 @@ export class NodeErrorBoundary extends Component {
|
|
|
11
14
|
message: error instanceof Error ? error.message : String(error),
|
|
12
15
|
};
|
|
13
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
|
+
}
|
|
14
25
|
render() {
|
|
15
26
|
if (this.state.hasError) {
|
|
16
27
|
return (_jsxs("div", { "data-continuum-render-error": this.props.nodeId, children: ["Node render failed: ", this.props.nodeId, " (", this.state.message, ")"] }));
|
package/lib/fallback.d.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
1
|
import type { ContinuumNodeProps } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Default renderer used when no component exists for a node type.
|
|
4
|
+
*/
|
|
2
5
|
export declare function FallbackComponent({ value, onChange, definition, }: ContinuumNodeProps): import("react/jsx-runtime").JSX.Element;
|
|
3
6
|
//# sourceMappingURL=fallback.d.ts.map
|
package/lib/fallback.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fallback.d.ts","sourceRoot":"","sources":["../../../../packages/react/src/lib/fallback.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAErD,wBAAgB,iBAAiB,CAAC,EAChC,KAAK,EACL,QAAQ,EACR,UAAU,GACX,EAAE,kBAAkB,2CAoCpB"}
|
|
1
|
+
{"version":3,"file":"fallback.d.ts","sourceRoot":"","sources":["../../../../packages/react/src/lib/fallback.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAErD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,KAAK,EACL,QAAQ,EACR,UAAU,GACX,EAAE,kBAAkB,2CAoCpB"}
|
package/lib/fallback.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Default renderer used when no component exists for a node type.
|
|
4
|
+
*/
|
|
2
5
|
export function FallbackComponent({ value, onChange, definition, }) {
|
|
3
6
|
const raw = value;
|
|
4
7
|
const textValue = typeof raw?.['value'] === 'string' || typeof raw?.['value'] === 'number'
|
package/lib/hooks.d.ts
CHANGED
|
@@ -1,32 +1,76 @@
|
|
|
1
1
|
import type { Session } from '@continuum-dev/session';
|
|
2
|
-
import type { ContinuitySnapshot, NodeValue, ViewportState, ProposedValue } from '@continuum-dev/contract';
|
|
2
|
+
import type { ContinuitySnapshot, NodeValue, ViewportState, ProposedValue, ActionResult } from '@continuum-dev/contract';
|
|
3
3
|
interface NodeStateScope {
|
|
4
4
|
subscribeNode: (nodeId: string, listener: () => void) => () => void;
|
|
5
5
|
getNodeValue: (nodeId: string) => NodeValue | undefined;
|
|
6
6
|
setNodeValue: (nodeId: string, value: NodeValue) => void;
|
|
7
7
|
}
|
|
8
|
+
/**
|
|
9
|
+
* Internal scope context used by collection item renderers to map local node ids
|
|
10
|
+
* onto collection-backed values.
|
|
11
|
+
*/
|
|
8
12
|
export declare const NodeStateScopeContext: import("react").Context<NodeStateScope | null>;
|
|
13
|
+
/**
|
|
14
|
+
* Returns the active Continuum session from provider context.
|
|
15
|
+
*/
|
|
9
16
|
export declare function useContinuumSession(): Session;
|
|
17
|
+
/**
|
|
18
|
+
* Subscribes to and updates a specific node value by canonical node id.
|
|
19
|
+
*
|
|
20
|
+
* @param nodeId Canonical node id.
|
|
21
|
+
*/
|
|
10
22
|
export declare function useContinuumState(nodeId: string): [NodeValue | undefined, (value: NodeValue) => void];
|
|
23
|
+
/**
|
|
24
|
+
* Subscribes to the full continuity snapshot.
|
|
25
|
+
*/
|
|
11
26
|
export declare function useContinuumSnapshot(): ContinuitySnapshot | null;
|
|
27
|
+
/**
|
|
28
|
+
* Subscribes to and updates viewport state for a specific node.
|
|
29
|
+
*
|
|
30
|
+
* @param nodeId Canonical node id.
|
|
31
|
+
*/
|
|
12
32
|
export declare function useContinuumViewport(nodeId: string): [ViewportState | undefined, (state: ViewportState) => void];
|
|
33
|
+
/**
|
|
34
|
+
* Subscribes to session diagnostics (`issues`, `diffs`, `resolutions`, checkpoints).
|
|
35
|
+
*/
|
|
13
36
|
export declare function useContinuumDiagnostics(): {
|
|
14
37
|
issues: ReturnType<Session["getIssues"]>;
|
|
15
38
|
diffs: ReturnType<Session["getDiffs"]>;
|
|
16
39
|
resolutions: ReturnType<Session["getResolutions"]>;
|
|
17
40
|
checkpoints: ReturnType<Session["getCheckpoints"]>;
|
|
18
41
|
};
|
|
42
|
+
/**
|
|
43
|
+
* Indicates whether the provider session was restored from persistence.
|
|
44
|
+
*/
|
|
19
45
|
export declare function useContinuumHydrated(): boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Returns conflict state and resolution actions for one node.
|
|
48
|
+
*
|
|
49
|
+
* @param nodeId Canonical node id.
|
|
50
|
+
*/
|
|
20
51
|
export declare function useContinuumConflict(nodeId: string): {
|
|
21
52
|
hasConflict: boolean;
|
|
22
53
|
proposal: ProposedValue | null;
|
|
23
54
|
accept: () => void;
|
|
24
55
|
reject: () => void;
|
|
25
56
|
};
|
|
57
|
+
/**
|
|
58
|
+
* Aggregates suggestion state and exposes bulk accept/reject operations.
|
|
59
|
+
*/
|
|
26
60
|
export declare function useContinuumSuggestions(): {
|
|
27
61
|
hasSuggestions: boolean;
|
|
28
62
|
acceptAll: () => void;
|
|
29
63
|
rejectAll: () => void;
|
|
30
64
|
};
|
|
65
|
+
/**
|
|
66
|
+
* Returns an action dispatcher bound to an intent id with dispatch state.
|
|
67
|
+
*
|
|
68
|
+
* @param intentId Registered action intent id to dispatch.
|
|
69
|
+
*/
|
|
70
|
+
export declare function useContinuumAction(intentId: string): {
|
|
71
|
+
dispatch: (nodeId: string) => Promise<ActionResult>;
|
|
72
|
+
isDispatching: boolean;
|
|
73
|
+
lastResult: ActionResult | null;
|
|
74
|
+
};
|
|
31
75
|
export {};
|
|
32
76
|
//# sourceMappingURL=hooks.d.ts.map
|
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,MAAM,qBAAqB,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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createContext, useContext, useCallback, useRef, useSyncExternalStore } from 'react';
|
|
1
|
+
import { createContext, useContext, useCallback, useRef, useState, useSyncExternalStore } from 'react';
|
|
2
2
|
import { ContinuumContext } from './context.js';
|
|
3
3
|
function shallowArrayEqual(left, right) {
|
|
4
4
|
if (left.length !== right.length) {
|
|
@@ -38,7 +38,14 @@ function shallowViewportEqual(left, right) {
|
|
|
38
38
|
left.isExpanded === right.isExpanded &&
|
|
39
39
|
left.isFocused === right.isFocused);
|
|
40
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* Internal scope context used by collection item renderers to map local node ids
|
|
43
|
+
* onto collection-backed values.
|
|
44
|
+
*/
|
|
41
45
|
export const NodeStateScopeContext = createContext(null);
|
|
46
|
+
/**
|
|
47
|
+
* Returns the active Continuum session from provider context.
|
|
48
|
+
*/
|
|
42
49
|
export function useContinuumSession() {
|
|
43
50
|
const ctx = useContext(ContinuumContext);
|
|
44
51
|
if (!ctx) {
|
|
@@ -46,6 +53,11 @@ export function useContinuumSession() {
|
|
|
46
53
|
}
|
|
47
54
|
return ctx.session;
|
|
48
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Subscribes to and updates a specific node value by canonical node id.
|
|
58
|
+
*
|
|
59
|
+
* @param nodeId Canonical node id.
|
|
60
|
+
*/
|
|
49
61
|
export function useContinuumState(nodeId) {
|
|
50
62
|
const ctx = useContext(ContinuumContext);
|
|
51
63
|
const scope = useContext(NodeStateScopeContext);
|
|
@@ -70,7 +82,7 @@ export function useContinuumState(nodeId) {
|
|
|
70
82
|
}
|
|
71
83
|
valueCacheRef.current = nextValue;
|
|
72
84
|
return nextValue;
|
|
73
|
-
}, [store, nodeId]);
|
|
85
|
+
}, [scope, store, nodeId]);
|
|
74
86
|
const value = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
75
87
|
const setValue = useCallback((next) => {
|
|
76
88
|
if (scope) {
|
|
@@ -81,6 +93,9 @@ export function useContinuumState(nodeId) {
|
|
|
81
93
|
}, [scope, session, nodeId]);
|
|
82
94
|
return [value, setValue];
|
|
83
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Subscribes to the full continuity snapshot.
|
|
98
|
+
*/
|
|
84
99
|
export function useContinuumSnapshot() {
|
|
85
100
|
const ctx = useContext(ContinuumContext);
|
|
86
101
|
if (!ctx) {
|
|
@@ -114,11 +129,22 @@ export function useContinuumSnapshot() {
|
|
|
114
129
|
}, [store]);
|
|
115
130
|
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
116
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* Subscribes to and updates viewport state for a specific node.
|
|
134
|
+
*
|
|
135
|
+
* @param nodeId Canonical node id.
|
|
136
|
+
*/
|
|
117
137
|
export function useContinuumViewport(nodeId) {
|
|
118
138
|
const ctx = useContext(ContinuumContext);
|
|
139
|
+
const scope = useContext(NodeStateScopeContext);
|
|
119
140
|
if (!ctx) {
|
|
120
141
|
throw new Error('useContinuumViewport must be used within a <ContinuumProvider>');
|
|
121
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
|
+
}
|
|
122
148
|
const { session, store } = ctx;
|
|
123
149
|
const viewportCacheRef = useRef(undefined);
|
|
124
150
|
const subscribe = useCallback((onStoreChange) => store.subscribeNode(nodeId, onStoreChange), [store, nodeId]);
|
|
@@ -137,6 +163,9 @@ export function useContinuumViewport(nodeId) {
|
|
|
137
163
|
}, [session, nodeId]);
|
|
138
164
|
return [viewport, setViewport];
|
|
139
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* Subscribes to session diagnostics (`issues`, `diffs`, `resolutions`, checkpoints).
|
|
168
|
+
*/
|
|
140
169
|
export function useContinuumDiagnostics() {
|
|
141
170
|
const ctx = useContext(ContinuumContext);
|
|
142
171
|
if (!ctx) {
|
|
@@ -165,6 +194,9 @@ export function useContinuumDiagnostics() {
|
|
|
165
194
|
const subscribe = useCallback((onStoreChange) => store.subscribeDiagnostics(onStoreChange), [store]);
|
|
166
195
|
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
167
196
|
}
|
|
197
|
+
/**
|
|
198
|
+
* Indicates whether the provider session was restored from persistence.
|
|
199
|
+
*/
|
|
168
200
|
export function useContinuumHydrated() {
|
|
169
201
|
const ctx = useContext(ContinuumContext);
|
|
170
202
|
if (!ctx) {
|
|
@@ -172,6 +204,11 @@ export function useContinuumHydrated() {
|
|
|
172
204
|
}
|
|
173
205
|
return ctx.wasHydrated;
|
|
174
206
|
}
|
|
207
|
+
/**
|
|
208
|
+
* Returns conflict state and resolution actions for one node.
|
|
209
|
+
*
|
|
210
|
+
* @param nodeId Canonical node id.
|
|
211
|
+
*/
|
|
175
212
|
export function useContinuumConflict(nodeId) {
|
|
176
213
|
const ctx = useContext(ContinuumContext);
|
|
177
214
|
if (!ctx) {
|
|
@@ -203,6 +240,9 @@ export function useContinuumConflict(nodeId) {
|
|
|
203
240
|
reject,
|
|
204
241
|
};
|
|
205
242
|
}
|
|
243
|
+
/**
|
|
244
|
+
* Aggregates suggestion state and exposes bulk accept/reject operations.
|
|
245
|
+
*/
|
|
206
246
|
export function useContinuumSuggestions() {
|
|
207
247
|
const ctx = useContext(ContinuumContext);
|
|
208
248
|
if (!ctx) {
|
|
@@ -260,3 +300,35 @@ export function useContinuumSuggestions() {
|
|
|
260
300
|
rejectAll,
|
|
261
301
|
};
|
|
262
302
|
}
|
|
303
|
+
/**
|
|
304
|
+
* Returns an action dispatcher bound to an intent id with dispatch state.
|
|
305
|
+
*
|
|
306
|
+
* @param intentId Registered action intent id to dispatch.
|
|
307
|
+
*/
|
|
308
|
+
export function useContinuumAction(intentId) {
|
|
309
|
+
const ctx = useContext(ContinuumContext);
|
|
310
|
+
if (!ctx) {
|
|
311
|
+
throw new Error('useContinuumAction must be used within a <ContinuumProvider>');
|
|
312
|
+
}
|
|
313
|
+
const { session } = ctx;
|
|
314
|
+
const [isDispatching, setIsDispatching] = useState(false);
|
|
315
|
+
const [lastResult, setLastResult] = useState(null);
|
|
316
|
+
const dispatchIdRef = useRef(0);
|
|
317
|
+
const dispatch = useCallback(async (nodeId) => {
|
|
318
|
+
const dispatchId = ++dispatchIdRef.current;
|
|
319
|
+
setIsDispatching(true);
|
|
320
|
+
try {
|
|
321
|
+
const result = await session.dispatchAction(intentId, nodeId);
|
|
322
|
+
if (dispatchIdRef.current === dispatchId) {
|
|
323
|
+
setLastResult(result);
|
|
324
|
+
}
|
|
325
|
+
return result;
|
|
326
|
+
}
|
|
327
|
+
finally {
|
|
328
|
+
if (dispatchIdRef.current === dispatchId) {
|
|
329
|
+
setIsDispatching(false);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}, [session, intentId]);
|
|
333
|
+
return { dispatch, isDispatching, lastResult };
|
|
334
|
+
}
|
package/lib/renderer.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import type { ViewDefinition } from '@continuum-dev/contract';
|
|
2
|
+
/**
|
|
3
|
+
* Renders a `ViewDefinition` tree using components registered in `ContinuumProvider`.
|
|
4
|
+
*/
|
|
2
5
|
export declare function ContinuumRenderer({ view }: {
|
|
3
6
|
view: ViewDefinition;
|
|
4
7
|
}): import("react/jsx-runtime").JSX.Element;
|
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,49 +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
|
});
|
|
231
|
+
/**
|
|
232
|
+
* Renders a `ViewDefinition` tree using components registered in `ContinuumProvider`.
|
|
233
|
+
*/
|
|
204
234
|
export function ContinuumRenderer({ view }) {
|
|
205
|
-
return (_jsx(
|
|
235
|
+
return (_jsx(_Fragment, { children: (view.nodes ?? []).map((node) => (_jsx(NodeRenderer, { definition: node, parentPath: "" }, node.id))) }));
|
|
206
236
|
}
|
package/lib/types.d.ts
CHANGED
|
@@ -1,31 +1,71 @@
|
|
|
1
1
|
import type { ViewNode, NodeValue } from '@continuum-dev/contract';
|
|
2
2
|
import type { SessionOptions } from '@continuum-dev/session';
|
|
3
3
|
import type { ComponentType } from 'react';
|
|
4
|
+
/**
|
|
5
|
+
* Props passed to node renderer components in the Continuum map.
|
|
6
|
+
*
|
|
7
|
+
* @template T Node value shape consumed by the component.
|
|
8
|
+
*/
|
|
4
9
|
export interface ContinuumNodeProps<T = NodeValue> {
|
|
10
|
+
/** Current node value from session state. */
|
|
5
11
|
value: T | undefined;
|
|
12
|
+
/** Writes a new node value into session state. */
|
|
6
13
|
onChange: (value: T) => void;
|
|
14
|
+
/** Raw node definition from the active view. */
|
|
7
15
|
definition: ViewNode;
|
|
16
|
+
/** Canonical node id, including parent path for nested nodes. */
|
|
8
17
|
nodeId?: string;
|
|
18
|
+
/** Rendered children for container-like nodes. */
|
|
9
19
|
children?: React.ReactNode;
|
|
20
|
+
/** Additional mapped props provided by renderers/integrations. */
|
|
10
21
|
[prop: string]: unknown;
|
|
11
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Component registry keyed by Continuum node `type`.
|
|
25
|
+
*/
|
|
12
26
|
export type ContinuumNodeMap = Record<string, ComponentType<ContinuumNodeProps<any>>>;
|
|
27
|
+
/**
|
|
28
|
+
* Backward-compatible alias for `ContinuumNodeProps`.
|
|
29
|
+
*
|
|
30
|
+
* @template T Node value shape consumed by the component.
|
|
31
|
+
*/
|
|
13
32
|
export type ContinuumComponentProps<T = NodeValue> = ContinuumNodeProps<T>;
|
|
33
|
+
/**
|
|
34
|
+
* Backward-compatible alias for `ContinuumNodeMap`.
|
|
35
|
+
*/
|
|
14
36
|
export type ContinuumComponentMap = ContinuumNodeMap;
|
|
37
|
+
/**
|
|
38
|
+
* Error metadata emitted when persistence fails in the provider.
|
|
39
|
+
*/
|
|
15
40
|
export interface ContinuumPersistError {
|
|
41
|
+
/** Persistence failure category. */
|
|
16
42
|
reason: 'size_limit' | 'storage_error';
|
|
43
|
+
/** Storage key used for persistence. */
|
|
17
44
|
key: string;
|
|
45
|
+
/** Serialized payload size in bytes. */
|
|
18
46
|
attemptedBytes?: number;
|
|
47
|
+
/** Configured max byte limit for persistence. */
|
|
19
48
|
maxBytes?: number;
|
|
49
|
+
/** Original error/cause when available. */
|
|
20
50
|
cause?: unknown;
|
|
21
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Props for `ContinuumProvider`.
|
|
54
|
+
*/
|
|
22
55
|
export interface ContinuumProviderProps {
|
|
56
|
+
/** Node type to component map used by `ContinuumRenderer`. */
|
|
23
57
|
components: ContinuumNodeMap;
|
|
58
|
+
/** Optional browser persistence mode. */
|
|
24
59
|
persist?: 'sessionStorage' | 'localStorage' | false;
|
|
60
|
+
/** Storage key for persisted session data. */
|
|
25
61
|
storageKey?: string;
|
|
62
|
+
/** Maximum serialized bytes allowed for persisted payloads. */
|
|
26
63
|
maxPersistBytes?: number;
|
|
64
|
+
/** Callback invoked when persistence errors occur. */
|
|
27
65
|
onPersistError?: (error: ContinuumPersistError) => void;
|
|
66
|
+
/** Options forwarded to `@continuum-dev/session`. */
|
|
28
67
|
sessionOptions?: SessionOptions;
|
|
68
|
+
/** React subtree that consumes Continuum context. */
|
|
29
69
|
children: React.ReactNode;
|
|
30
70
|
}
|
|
31
71
|
//# sourceMappingURL=types.d.ts.map
|
package/lib/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../packages/react/src/lib/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAC/D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAE3C,MAAM,WAAW,kBAAkB,CAAC,CAAC,GAAG,SAAS;IAC/C,KAAK,EAAE,CAAC,GAAG,SAAS,CAAC;IACrB,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;IAC7B,UAAU,EAAE,QAAQ,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;CACzB;AAED,MAAM,MAAM,gBAAgB,GAAG,MAAM,CACnC,MAAM,EACN,aAAa,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC,CACvC,CAAC;AAEF,MAAM,MAAM,uBAAuB,CAAC,CAAC,GAAG,SAAS,IAAI,kBAAkB,CAAC,CAAC,CAAC,CAAC;AAC3E,MAAM,MAAM,qBAAqB,GAAG,gBAAgB,CAAC;AAErD,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,YAAY,GAAG,eAAe,CAAC;IACvC,GAAG,EAAE,MAAM,CAAC;IACZ,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,sBAAsB;IACrC,UAAU,EAAE,gBAAgB,CAAC;IAC7B,OAAO,CAAC,EAAE,gBAAgB,GAAG,cAAc,GAAG,KAAK,CAAC;IACpD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,qBAAqB,KAAK,IAAI,CAAC;IACxD,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../packages/react/src/lib/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAC/D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAE3C;;;;GAIG;AACH,MAAM,WAAW,kBAAkB,CAAC,CAAC,GAAG,SAAS;IAC/C,6CAA6C;IAC7C,KAAK,EAAE,CAAC,GAAG,SAAS,CAAC;IACrB,kDAAkD;IAClD,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;IAC7B,gDAAgD;IAChD,UAAU,EAAE,QAAQ,CAAC;IACrB,iEAAiE;IACjE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kDAAkD;IAClD,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,kEAAkE;IAClE,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG,MAAM,CACnC,MAAM,EACN,aAAa,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC,CACvC,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,uBAAuB,CAAC,CAAC,GAAG,SAAS,IAAI,kBAAkB,CAAC,CAAC,CAAC,CAAC;AAC3E;;GAEG;AACH,MAAM,MAAM,qBAAqB,GAAG,gBAAgB,CAAC;AAErD;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,oCAAoC;IACpC,MAAM,EAAE,YAAY,GAAG,eAAe,CAAC;IACvC,wCAAwC;IACxC,GAAG,EAAE,MAAM,CAAC;IACZ,wCAAwC;IACxC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,iDAAiD;IACjD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2CAA2C;IAC3C,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,8DAA8D;IAC9D,UAAU,EAAE,gBAAgB,CAAC;IAC7B,yCAAyC;IACzC,OAAO,CAAC,EAAE,gBAAgB,GAAG,cAAc,GAAG,KAAK,CAAC;IACpD,8CAA8C;IAC9C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,+DAA+D;IAC/D,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sDAAsD;IACtD,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,qBAAqB,KAAK,IAAI,CAAC;IACxD,iDAAiD;IACjD,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,qDAAqD;IACrD,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B"}
|
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
|
}
|