@continuum-dev/react 0.1.1
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/LICENSE +21 -0
- package/README.md +549 -0
- package/index.d.ts +7 -0
- package/index.d.ts.map +1 -0
- package/index.js +6 -0
- package/lib/context.d.ts +23 -0
- package/lib/context.d.ts.map +1 -0
- package/lib/context.js +185 -0
- package/lib/error-boundary.d.ts +16 -0
- package/lib/error-boundary.d.ts.map +1 -0
- package/lib/error-boundary.js +20 -0
- package/lib/fallback.d.ts +3 -0
- package/lib/fallback.d.ts.map +1 -0
- package/lib/fallback.js +13 -0
- package/lib/fallback.spec.d.ts +2 -0
- package/lib/fallback.spec.d.ts.map +1 -0
- package/lib/hooks.d.ts +32 -0
- package/lib/hooks.d.ts.map +1 -0
- package/lib/hooks.js +262 -0
- package/lib/integration.spec.d.ts +2 -0
- package/lib/integration.spec.d.ts.map +1 -0
- package/lib/persistence.d.ts +3 -0
- package/lib/persistence.d.ts.map +1 -0
- package/lib/persistence.spec.d.ts +2 -0
- package/lib/persistence.spec.d.ts.map +1 -0
- package/lib/renderer.d.ts +5 -0
- package/lib/renderer.d.ts.map +1 -0
- package/lib/renderer.js +206 -0
- package/lib/types.d.ts +31 -0
- package/lib/types.d.ts.map +1 -0
- package/lib/types.js +1 -0
- package/package.json +50 -0
package/lib/renderer.js
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { memo, useContext, useMemo } from 'react';
|
|
3
|
+
import { getChildNodes } from '@continuum-dev/contract';
|
|
4
|
+
import { ContinuumContext } from './context.js';
|
|
5
|
+
import { NodeStateScopeContext, useContinuumState } from './hooks.js';
|
|
6
|
+
import { FallbackComponent } from './fallback.js';
|
|
7
|
+
import { NodeErrorBoundary } from './error-boundary.js';
|
|
8
|
+
const noopOnChange = () => undefined;
|
|
9
|
+
function toCanonicalId(id, parentPath) {
|
|
10
|
+
return parentPath.length > 0 ? `${parentPath}/${id}` : id;
|
|
11
|
+
}
|
|
12
|
+
function useResolvedComponent(definition) {
|
|
13
|
+
const ctx = useContext(ContinuumContext);
|
|
14
|
+
if (!ctx) {
|
|
15
|
+
throw new Error('ContinuumRenderer must be used within a <ContinuumProvider>');
|
|
16
|
+
}
|
|
17
|
+
const { componentMap } = ctx;
|
|
18
|
+
return (componentMap[definition.type] ??
|
|
19
|
+
componentMap['default'] ??
|
|
20
|
+
FallbackComponent);
|
|
21
|
+
}
|
|
22
|
+
function normalizeCollectionNodeValue(value) {
|
|
23
|
+
const items = value?.value?.items;
|
|
24
|
+
if (!Array.isArray(items)) {
|
|
25
|
+
return { value: { items: [] } };
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
...value,
|
|
29
|
+
value: {
|
|
30
|
+
items: items.map((item) => ({
|
|
31
|
+
values: item && typeof item === 'object' && item.values && typeof item.values === 'object'
|
|
32
|
+
? { ...item.values }
|
|
33
|
+
: {},
|
|
34
|
+
})),
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function toRelativeNodeId(collectionCanonicalId, nodeId) {
|
|
39
|
+
if (nodeId === collectionCanonicalId) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
if (nodeId.startsWith(`${collectionCanonicalId}/`)) {
|
|
43
|
+
return nodeId.slice(collectionCanonicalId.length + 1);
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
function normalizeMinItems(value) {
|
|
48
|
+
if (value === undefined || value < 0) {
|
|
49
|
+
return 0;
|
|
50
|
+
}
|
|
51
|
+
return Math.floor(value);
|
|
52
|
+
}
|
|
53
|
+
function normalizeMaxItems(value) {
|
|
54
|
+
if (value === undefined || value < 0) {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
return Math.floor(value);
|
|
58
|
+
}
|
|
59
|
+
function createInitialCollectionState(node) {
|
|
60
|
+
const minItems = normalizeMinItems(node.minItems);
|
|
61
|
+
return {
|
|
62
|
+
items: Array.from({ length: minItems }, () => ({
|
|
63
|
+
values: collectTemplateDefaults(node.template),
|
|
64
|
+
})),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function collectTemplateDefaults(node, parentPath = '') {
|
|
68
|
+
const nodeId = toCanonicalId(node.id, parentPath);
|
|
69
|
+
if (node.type === 'collection') {
|
|
70
|
+
return {
|
|
71
|
+
[nodeId]: {
|
|
72
|
+
value: createInitialCollectionState(node),
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
const values = {};
|
|
77
|
+
if ('defaultValue' in node && node.defaultValue !== undefined) {
|
|
78
|
+
values[nodeId] = { value: node.defaultValue };
|
|
79
|
+
}
|
|
80
|
+
const children = getChildNodes(node);
|
|
81
|
+
for (const child of children) {
|
|
82
|
+
Object.assign(values, collectTemplateDefaults(child, nodeId));
|
|
83
|
+
}
|
|
84
|
+
return values;
|
|
85
|
+
}
|
|
86
|
+
const StatefulNodeRenderer = memo(function StatefulNodeRenderer({ definition, parentPath }) {
|
|
87
|
+
const Component = useResolvedComponent(definition);
|
|
88
|
+
const canonicalId = toCanonicalId(definition.id, parentPath);
|
|
89
|
+
const [value, setValue] = useContinuumState(canonicalId);
|
|
90
|
+
if (definition.hidden) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
return (_jsx("div", { "data-continuum-id": definition.id, children: _jsx(NodeErrorBoundary, { nodeId: definition.id, children: _jsx(Component, { value: value, onChange: setValue, definition: definition, nodeId: canonicalId }) }) }));
|
|
94
|
+
});
|
|
95
|
+
const ContainerNodeRenderer = memo(function ContainerNodeRenderer({ definition, parentPath }) {
|
|
96
|
+
const Component = useResolvedComponent(definition);
|
|
97
|
+
if (definition.hidden) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
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("div", { "data-continuum-id": definition.id, children: _jsx(NodeErrorBoundary, { nodeId: definition.id, children: _jsx(Component, { value: undefined, onChange: noopOnChange, definition: definition, nodeId: canonicalId, children: childNodes }) }) }));
|
|
103
|
+
});
|
|
104
|
+
const CollectionItemRenderer = memo(function CollectionItemRenderer({ collectionCanonicalId, itemIndex, template, templateDefaults, canRemove, onRemove, }) {
|
|
105
|
+
const ctx = useContext(ContinuumContext);
|
|
106
|
+
if (!ctx) {
|
|
107
|
+
throw new Error('ContinuumRenderer must be used within a <ContinuumProvider>');
|
|
108
|
+
}
|
|
109
|
+
const { session, store } = ctx;
|
|
110
|
+
const scope = useMemo(() => ({
|
|
111
|
+
subscribeNode: (_nodeId, listener) => store.subscribeNode(collectionCanonicalId, listener),
|
|
112
|
+
getNodeValue: (nodeId) => {
|
|
113
|
+
const relativeId = toRelativeNodeId(collectionCanonicalId, nodeId);
|
|
114
|
+
if (!relativeId) {
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
const collectionValue = normalizeCollectionNodeValue(store.getNodeValue(collectionCanonicalId));
|
|
118
|
+
return (collectionValue.value.items[itemIndex]?.values?.[relativeId] ??
|
|
119
|
+
templateDefaults[relativeId]);
|
|
120
|
+
},
|
|
121
|
+
setNodeValue: (nodeId, nextValue) => {
|
|
122
|
+
const relativeId = toRelativeNodeId(collectionCanonicalId, nodeId);
|
|
123
|
+
if (!relativeId) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const collectionValue = normalizeCollectionNodeValue(store.getNodeValue(collectionCanonicalId));
|
|
127
|
+
const items = collectionValue.value.items.map((item) => ({
|
|
128
|
+
values: { ...item.values },
|
|
129
|
+
}));
|
|
130
|
+
while (items.length <= itemIndex) {
|
|
131
|
+
items.push({ values: {} });
|
|
132
|
+
}
|
|
133
|
+
items[itemIndex] = {
|
|
134
|
+
values: {
|
|
135
|
+
...items[itemIndex].values,
|
|
136
|
+
[relativeId]: nextValue,
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
session.updateState(collectionCanonicalId, {
|
|
140
|
+
...collectionValue,
|
|
141
|
+
value: { items },
|
|
142
|
+
});
|
|
143
|
+
},
|
|
144
|
+
}), [collectionCanonicalId, itemIndex, session, store, templateDefaults]);
|
|
145
|
+
return (_jsx(NodeStateScopeContext.Provider, { value: scope, children: _jsxs("div", { "data-continuum-collection-item": `${collectionCanonicalId}:${itemIndex}`, className: "continuum-collection-item", children: [_jsx(NodeRenderer, { definition: template, parentPath: collectionCanonicalId }), canRemove ? (_jsx("div", { className: "continuum-collection-item-actions", children: _jsx("button", { type: "button", "data-continuum-collection-remove": `${collectionCanonicalId}:${itemIndex}`, onClick: () => onRemove(itemIndex), className: "continuum-collection-remove", children: "\u00D7" }) })) : null] }) }));
|
|
146
|
+
});
|
|
147
|
+
const CollectionNodeRenderer = memo(function CollectionNodeRenderer({ definition, parentPath, }) {
|
|
148
|
+
const Component = useResolvedComponent(definition);
|
|
149
|
+
const canonicalId = toCanonicalId(definition.id, parentPath);
|
|
150
|
+
const [collectionValue, setCollectionValue] = useContinuumState(canonicalId);
|
|
151
|
+
const normalizedCollection = normalizeCollectionNodeValue(collectionValue);
|
|
152
|
+
const minItems = normalizeMinItems(definition.minItems);
|
|
153
|
+
const maxItems = normalizeMaxItems(definition.maxItems);
|
|
154
|
+
const templateDefaults = useMemo(() => collectTemplateDefaults(definition.template), [definition.template]);
|
|
155
|
+
const canAdd = maxItems === undefined ||
|
|
156
|
+
normalizedCollection.value.items.length < maxItems;
|
|
157
|
+
const canRemove = normalizedCollection.value.items.length > minItems;
|
|
158
|
+
if (definition.hidden) {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
const addItem = () => {
|
|
162
|
+
if (!canAdd) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
const items = [
|
|
166
|
+
...normalizedCollection.value.items.map((item) => ({
|
|
167
|
+
values: { ...item.values },
|
|
168
|
+
})),
|
|
169
|
+
{ values: { ...templateDefaults } },
|
|
170
|
+
];
|
|
171
|
+
setCollectionValue({
|
|
172
|
+
...normalizedCollection,
|
|
173
|
+
value: { items },
|
|
174
|
+
});
|
|
175
|
+
};
|
|
176
|
+
const removeItem = (index) => {
|
|
177
|
+
if (!canRemove) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
const items = normalizedCollection.value.items
|
|
181
|
+
.map((item) => ({ values: { ...item.values } }))
|
|
182
|
+
.filter((_, itemIndex) => itemIndex !== index);
|
|
183
|
+
if (items.length < minItems) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
setCollectionValue({
|
|
187
|
+
...normalizedCollection,
|
|
188
|
+
value: { items },
|
|
189
|
+
});
|
|
190
|
+
};
|
|
191
|
+
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("div", { "data-continuum-id": definition.id, children: _jsx(NodeErrorBoundary, { nodeId: definition.id, children: _jsxs(Component, { value: collectionValue, onChange: setCollectionValue, definition: definition, nodeId: canonicalId, children: [renderedItems, _jsx("div", { className: "continuum-collection-add-container", children: _jsx("button", { type: "button", "data-continuum-collection-add": canonicalId, onClick: addItem, disabled: !canAdd, className: "continuum-collection-add", children: "+ Add item" }) })] }) }) }));
|
|
193
|
+
});
|
|
194
|
+
const NodeRenderer = memo(function NodeRenderer({ definition, parentPath }) {
|
|
195
|
+
if (definition.type === 'collection') {
|
|
196
|
+
return _jsx(CollectionNodeRenderer, { definition: definition, parentPath: parentPath });
|
|
197
|
+
}
|
|
198
|
+
const childNodes = getChildNodes(definition);
|
|
199
|
+
if (childNodes.length > 0) {
|
|
200
|
+
return _jsx(ContainerNodeRenderer, { definition: definition, parentPath: parentPath });
|
|
201
|
+
}
|
|
202
|
+
return _jsx(StatefulNodeRenderer, { definition: definition, parentPath: parentPath });
|
|
203
|
+
});
|
|
204
|
+
export function ContinuumRenderer({ view }) {
|
|
205
|
+
return (_jsx("div", { "data-continuum-view": view.viewId, children: (view.nodes ?? []).map((node) => (_jsx(NodeRenderer, { definition: node, parentPath: "" }, node.id))) }));
|
|
206
|
+
}
|
package/lib/types.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ViewNode, NodeValue } from '@continuum-dev/contract';
|
|
2
|
+
import type { SessionOptions } from '@continuum-dev/session';
|
|
3
|
+
import type { ComponentType } from 'react';
|
|
4
|
+
export interface ContinuumNodeProps<T = NodeValue> {
|
|
5
|
+
value: T | undefined;
|
|
6
|
+
onChange: (value: T) => void;
|
|
7
|
+
definition: ViewNode;
|
|
8
|
+
nodeId?: string;
|
|
9
|
+
children?: React.ReactNode;
|
|
10
|
+
[prop: string]: unknown;
|
|
11
|
+
}
|
|
12
|
+
export type ContinuumNodeMap = Record<string, ComponentType<ContinuumNodeProps<any>>>;
|
|
13
|
+
export type ContinuumComponentProps<T = NodeValue> = ContinuumNodeProps<T>;
|
|
14
|
+
export type ContinuumComponentMap = ContinuumNodeMap;
|
|
15
|
+
export interface ContinuumPersistError {
|
|
16
|
+
reason: 'size_limit' | 'storage_error';
|
|
17
|
+
key: string;
|
|
18
|
+
attemptedBytes?: number;
|
|
19
|
+
maxBytes?: number;
|
|
20
|
+
cause?: unknown;
|
|
21
|
+
}
|
|
22
|
+
export interface ContinuumProviderProps {
|
|
23
|
+
components: ContinuumNodeMap;
|
|
24
|
+
persist?: 'sessionStorage' | 'localStorage' | false;
|
|
25
|
+
storageKey?: string;
|
|
26
|
+
maxPersistBytes?: number;
|
|
27
|
+
onPersistError?: (error: ContinuumPersistError) => void;
|
|
28
|
+
sessionOptions?: SessionOptions;
|
|
29
|
+
children: React.ReactNode;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +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"}
|
package/lib/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@continuum-dev/react",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public"
|
|
6
|
+
},
|
|
7
|
+
"description": "React bindings for the Continuum continuity runtime",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/brytoncooper/cooper-continuum.git",
|
|
12
|
+
"directory": "packages/react"
|
|
13
|
+
},
|
|
14
|
+
"author": "Bryton Cooper",
|
|
15
|
+
"keywords": [
|
|
16
|
+
"continuum",
|
|
17
|
+
"ai",
|
|
18
|
+
"ui",
|
|
19
|
+
"continuity",
|
|
20
|
+
"reconciliation",
|
|
21
|
+
"react",
|
|
22
|
+
"hooks",
|
|
23
|
+
"provider"
|
|
24
|
+
],
|
|
25
|
+
"type": "module",
|
|
26
|
+
"sideEffects": false,
|
|
27
|
+
"main": "./index.js",
|
|
28
|
+
"types": "./index.d.ts",
|
|
29
|
+
"exports": {
|
|
30
|
+
".": {
|
|
31
|
+
"types": "./index.d.ts",
|
|
32
|
+
"import": "./index.js",
|
|
33
|
+
"default": "./index.js"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"**/*.js",
|
|
38
|
+
"**/*.d.ts",
|
|
39
|
+
"**/*.d.ts.map",
|
|
40
|
+
"README.md",
|
|
41
|
+
"LICENSE*"
|
|
42
|
+
],
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"react": ">=18"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@continuum-dev/contract": "^0.1.1",
|
|
48
|
+
"@continuum-dev/session": "^0.1.1"
|
|
49
|
+
}
|
|
50
|
+
}
|