blocks-renderer 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 +45 -0
- package/dist/index.cjs +214 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +106 -0
- package/dist/index.d.ts +106 -0
- package/dist/index.js +196 -0
- package/dist/index.js.map +1 -0
- package/package.json +69 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Constructive
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# blocks-renderer
|
|
2
|
+
|
|
3
|
+
React adapter for `blocks-schema` UI documents: a recursive
|
|
4
|
+
renderer with layerable widget registries, binding resolution, form state, and
|
|
5
|
+
a visible unknown-block fallback.
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import { DocumentRenderer, composeRegistry } from 'blocks-renderer';
|
|
11
|
+
|
|
12
|
+
const registry = composeRegistry(
|
|
13
|
+
baseRegistry, // shadcn-style primitives
|
|
14
|
+
appRegistry, // app-specific blocks
|
|
15
|
+
documentOverrides, // per-document swaps
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
<DocumentRenderer
|
|
19
|
+
document={document}
|
|
20
|
+
registry={registry}
|
|
21
|
+
scope={{ row, user }}
|
|
22
|
+
initialValues={{ title: 'Draft' }}
|
|
23
|
+
onSubmit={(values) => save(values)}
|
|
24
|
+
onAction={(action, event) => runFlow(action)}
|
|
25
|
+
/>;
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Concepts
|
|
29
|
+
|
|
30
|
+
- **Registry layering** — `composeRegistry(...layers)` merges
|
|
31
|
+
`type → component` maps left-to-right, later layers winning. Hosts customize
|
|
32
|
+
widgets (inputs, textareas, rich text, custom blocks) by layering, never by
|
|
33
|
+
forking the renderer.
|
|
34
|
+
- **Blocks** — every component receives `{ node, props, children }`, where
|
|
35
|
+
`props` has the node's `bindings` resolved against the current scope and
|
|
36
|
+
`children` are already rendered.
|
|
37
|
+
- **Bindings** — `{{ path.to.value }}` templates resolve against
|
|
38
|
+
`scope + values`; a lone placeholder yields the raw value, mixed text
|
|
39
|
+
interpolates.
|
|
40
|
+
- **Fields** — widget implementations call `useBlockField(name)` for
|
|
41
|
+
value/error wiring; constraints from the document (`required`, length,
|
|
42
|
+
range, `pattern`) validate on change and on submit.
|
|
43
|
+
- **Unknown blocks** — unregistered node types render `UnknownBlock`, a
|
|
44
|
+
visible gap instead of a crash, so documents can name blocks a host has not
|
|
45
|
+
installed.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
BlockRenderer: () => BlockRenderer,
|
|
24
|
+
DocumentRenderer: () => DocumentRenderer,
|
|
25
|
+
RendererProvider: () => RendererProvider,
|
|
26
|
+
UnknownBlock: () => UnknownBlock,
|
|
27
|
+
composeRegistry: () => composeRegistry,
|
|
28
|
+
readPath: () => readPath,
|
|
29
|
+
registeredTypes: () => registeredTypes,
|
|
30
|
+
resolveBinding: () => resolveBinding,
|
|
31
|
+
resolveBlock: () => resolveBlock,
|
|
32
|
+
resolveNodeProps: () => resolveNodeProps,
|
|
33
|
+
useBlockField: () => useBlockField,
|
|
34
|
+
useRenderer: () => useRenderer
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(index_exports);
|
|
37
|
+
|
|
38
|
+
// src/bindings.ts
|
|
39
|
+
var TEMPLATE = /\{\{\s*([^}\s]+)\s*\}\}/g;
|
|
40
|
+
function readPath(scope, path) {
|
|
41
|
+
let current = scope;
|
|
42
|
+
for (const segment of path.split(".")) {
|
|
43
|
+
if (current == null || typeof current !== "object") return void 0;
|
|
44
|
+
current = current[segment];
|
|
45
|
+
}
|
|
46
|
+
return current;
|
|
47
|
+
}
|
|
48
|
+
function resolveBinding(expression, scope) {
|
|
49
|
+
const single = expression.match(/^\{\{\s*([^}\s]+)\s*\}\}$/);
|
|
50
|
+
if (single) {
|
|
51
|
+
return readPath(scope, single[1]);
|
|
52
|
+
}
|
|
53
|
+
return expression.replace(TEMPLATE, (_match, path) => {
|
|
54
|
+
const value = readPath(scope, path);
|
|
55
|
+
return value == null ? "" : String(value);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function resolveNodeProps(node, scope) {
|
|
59
|
+
if (!node.bindings) return node.props ?? {};
|
|
60
|
+
const resolved = { ...node.props ?? {} };
|
|
61
|
+
for (const [prop, expression] of Object.entries(node.bindings)) {
|
|
62
|
+
resolved[prop] = resolveBinding(expression, scope);
|
|
63
|
+
}
|
|
64
|
+
return resolved;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// src/context.tsx
|
|
68
|
+
var import_react = require("react");
|
|
69
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
70
|
+
var RendererContext = (0, import_react.createContext)(null);
|
|
71
|
+
function RendererProvider({ children, value }) {
|
|
72
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RendererContext.Provider, { value, children });
|
|
73
|
+
}
|
|
74
|
+
function useRenderer() {
|
|
75
|
+
const context = (0, import_react.useContext)(RendererContext);
|
|
76
|
+
if (!context) {
|
|
77
|
+
throw new Error("useRenderer must be used within a RendererProvider (or a DocumentRenderer)");
|
|
78
|
+
}
|
|
79
|
+
return context;
|
|
80
|
+
}
|
|
81
|
+
function useBlockField(name) {
|
|
82
|
+
const { values, errors, setValue, setError, mode } = useRenderer();
|
|
83
|
+
if (!name) {
|
|
84
|
+
return { value: void 0, error: void 0, setValue: () => {
|
|
85
|
+
}, setError: () => {
|
|
86
|
+
}, mode };
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
value: values[name],
|
|
90
|
+
error: errors[name],
|
|
91
|
+
setValue: (value) => setValue(name, value),
|
|
92
|
+
setError: (error) => setError(name, error),
|
|
93
|
+
mode
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// src/registry.ts
|
|
98
|
+
function composeRegistry(...layers) {
|
|
99
|
+
const composed = {};
|
|
100
|
+
for (const layer of layers) {
|
|
101
|
+
if (!layer) continue;
|
|
102
|
+
Object.assign(composed, layer);
|
|
103
|
+
}
|
|
104
|
+
return composed;
|
|
105
|
+
}
|
|
106
|
+
function resolveBlock(registry, type) {
|
|
107
|
+
return registry[type];
|
|
108
|
+
}
|
|
109
|
+
function registeredTypes(registry) {
|
|
110
|
+
return Object.keys(registry).sort();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// src/renderer.tsx
|
|
114
|
+
var import_blocks_schema = require("blocks-schema");
|
|
115
|
+
var import_react2 = require("react");
|
|
116
|
+
|
|
117
|
+
// src/unknown-block.tsx
|
|
118
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
119
|
+
function UnknownBlock({ node }) {
|
|
120
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { "data-block-unknown": node.type, role: "note", children: [
|
|
121
|
+
"Unknown block: ",
|
|
122
|
+
node.type
|
|
123
|
+
] });
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// src/renderer.tsx
|
|
127
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
128
|
+
function BlockRenderer({ node }) {
|
|
129
|
+
const { registry, scope } = useRenderer();
|
|
130
|
+
const Component = registry[node.type];
|
|
131
|
+
if (!Component) {
|
|
132
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(UnknownBlock, { node });
|
|
133
|
+
}
|
|
134
|
+
const children = node.children?.length ? node.children.map((child) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(BlockRenderer, { node: child }, child.key)) : void 0;
|
|
135
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Component, { node, props: resolveNodeProps(node, scope), children });
|
|
136
|
+
}
|
|
137
|
+
function DocumentRenderer({
|
|
138
|
+
document,
|
|
139
|
+
registry,
|
|
140
|
+
initialValues,
|
|
141
|
+
scope: externalScope,
|
|
142
|
+
onSubmit,
|
|
143
|
+
onChange,
|
|
144
|
+
onAction,
|
|
145
|
+
mode = "preview",
|
|
146
|
+
className
|
|
147
|
+
}) {
|
|
148
|
+
const defaults = (0, import_react2.useMemo)(() => (0, import_blocks_schema.collectDefaultValues)(document.page), [document]);
|
|
149
|
+
const constraints = (0, import_react2.useMemo)(() => (0, import_blocks_schema.collectFieldConstraints)(document.page), [document]);
|
|
150
|
+
const [values, setValues] = (0, import_react2.useState)(() => ({ ...defaults, ...initialValues }));
|
|
151
|
+
const [errors, setErrors] = (0, import_react2.useState)({});
|
|
152
|
+
(0, import_react2.useEffect)(() => {
|
|
153
|
+
setValues((previous) => ({ ...defaults, ...initialValues, ...previous }));
|
|
154
|
+
}, [defaults, initialValues]);
|
|
155
|
+
const setError = (0, import_react2.useCallback)((name, error) => {
|
|
156
|
+
setErrors((previous) => {
|
|
157
|
+
if (error) return { ...previous, [name]: error };
|
|
158
|
+
if (!(name in previous)) return previous;
|
|
159
|
+
const next = { ...previous };
|
|
160
|
+
delete next[name];
|
|
161
|
+
return next;
|
|
162
|
+
});
|
|
163
|
+
}, []);
|
|
164
|
+
const setValue = (0, import_react2.useCallback)(
|
|
165
|
+
(name, value) => {
|
|
166
|
+
setValues((previous) => {
|
|
167
|
+
const next = { ...previous, [name]: value };
|
|
168
|
+
onChange?.(next);
|
|
169
|
+
return next;
|
|
170
|
+
});
|
|
171
|
+
const field = constraints[name];
|
|
172
|
+
if (field) {
|
|
173
|
+
setError(name, (0, import_blocks_schema.validateField)(value, field.constraints, field.required));
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
[onChange, constraints, setError]
|
|
177
|
+
);
|
|
178
|
+
const handleAction = (0, import_react2.useCallback)(
|
|
179
|
+
(action, event) => {
|
|
180
|
+
if (event === "submit" && onSubmit) {
|
|
181
|
+
const failures = {};
|
|
182
|
+
for (const name of (0, import_blocks_schema.collectFieldNames)(document.page)) {
|
|
183
|
+
const field = constraints[name];
|
|
184
|
+
if (!field) continue;
|
|
185
|
+
const error = (0, import_blocks_schema.validateField)(values[name], field.constraints, field.required);
|
|
186
|
+
if (error) failures[name] = error;
|
|
187
|
+
}
|
|
188
|
+
if (Object.keys(failures).length > 0) {
|
|
189
|
+
setErrors(failures);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
onSubmit(values);
|
|
193
|
+
}
|
|
194
|
+
onAction?.(action, event);
|
|
195
|
+
},
|
|
196
|
+
[onSubmit, onAction, values, document.page, constraints]
|
|
197
|
+
);
|
|
198
|
+
const contextValue = (0, import_react2.useMemo)(
|
|
199
|
+
() => ({
|
|
200
|
+
document,
|
|
201
|
+
registry,
|
|
202
|
+
mode,
|
|
203
|
+
values,
|
|
204
|
+
errors,
|
|
205
|
+
setValue,
|
|
206
|
+
setError,
|
|
207
|
+
scope: { ...externalScope, values, ...values },
|
|
208
|
+
onAction: handleAction
|
|
209
|
+
}),
|
|
210
|
+
[document, registry, mode, values, errors, setValue, setError, externalScope, handleAction]
|
|
211
|
+
);
|
|
212
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(RendererProvider, { value: contextValue, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(BlockRenderer, { node: document.page }) }) });
|
|
213
|
+
}
|
|
214
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/bindings.ts","../src/context.tsx","../src/registry.ts","../src/renderer.tsx","../src/unknown-block.tsx"],"sourcesContent":["export { readPath, resolveBinding, resolveNodeProps } from './bindings';\nexport { RendererProvider, useBlockField, useRenderer } from './context';\nexport { composeRegistry, registeredTypes, resolveBlock } from './registry';\nexport { BlockRenderer, DocumentRenderer } from './renderer';\nexport type { DocumentRendererProps } from './renderer';\nexport { UnknownBlock } from './unknown-block';\nexport type { BlockComponent, BlockProps, BlockRegistry, RenderMode, RendererContextValue } from './types';\n","import type { UINode, UINodeProps } from 'blocks-schema';\n\nconst TEMPLATE = /\\{\\{\\s*([^}\\s]+)\\s*\\}\\}/g;\n\n/** Read a dotted path (`row.author.name`) out of a scope object. */\nexport function readPath(scope: Record<string, unknown>, path: string): unknown {\n\tlet current: unknown = scope;\n\tfor (const segment of path.split('.')) {\n\t\tif (current == null || typeof current !== 'object') return undefined;\n\t\tcurrent = (current as Record<string, unknown>)[segment];\n\t}\n\treturn current;\n}\n\n/**\n * Resolve a binding expression. A template that is exactly one placeholder\n * yields the raw value (so a boolean or an object survives); a template mixed\n * with text is interpolated as a string.\n */\nexport function resolveBinding(expression: string, scope: Record<string, unknown>): unknown {\n\tconst single = expression.match(/^\\{\\{\\s*([^}\\s]+)\\s*\\}\\}$/);\n\tif (single) {\n\t\treturn readPath(scope, single[1]);\n\t}\n\n\treturn expression.replace(TEMPLATE, (_match, path: string) => {\n\t\tconst value = readPath(scope, path);\n\t\treturn value == null ? '' : String(value);\n\t});\n}\n\n/** Apply a node's `bindings` over its static props. */\nexport function resolveNodeProps(node: UINode, scope: Record<string, unknown>): UINodeProps {\n\tif (!node.bindings) return node.props ?? {};\n\n\tconst resolved: UINodeProps = { ...(node.props ?? {}) };\n\tfor (const [prop, expression] of Object.entries(node.bindings)) {\n\t\tresolved[prop] = resolveBinding(expression, scope);\n\t}\n\treturn resolved;\n}\n","'use client';\n\nimport { createContext, useContext } from 'react';\nimport type { ReactNode } from 'react';\n\nimport type { RendererContextValue } from './types';\n\nconst RendererContext = createContext<RendererContextValue | null>(null);\n\nexport function RendererProvider({ children, value }: { children: ReactNode; value: RendererContextValue }) {\n\treturn <RendererContext.Provider value={value}>{children}</RendererContext.Provider>;\n}\n\nexport function useRenderer(): RendererContextValue {\n\tconst context = useContext(RendererContext);\n\tif (!context) {\n\t\tthrow new Error('useRenderer must be used within a RendererProvider (or a DocumentRenderer)');\n\t}\n\treturn context;\n}\n\n/**\n * Resolved props plus the value/error wiring for a field node. Widget\n * implementations use this instead of reaching into the document themselves.\n */\nexport function useBlockField(name: string | undefined) {\n\tconst { values, errors, setValue, setError, mode } = useRenderer();\n\tif (!name) {\n\t\treturn { value: undefined, error: undefined, setValue: () => {}, setError: () => {}, mode };\n\t}\n\treturn {\n\t\tvalue: values[name],\n\t\terror: errors[name],\n\t\tsetValue: (value: unknown) => setValue(name, value),\n\t\tsetError: (error: string | null) => setError(name, error),\n\t\tmode,\n\t};\n}\n","import type { BlockComponent, BlockRegistry } from './types';\n\n/**\n * Layer registries left-to-right, later layers winning. This is how a host\n * customizes rendering: base primitives, then an app registry, then per-document\n * overrides — no forking of the renderer, and no single global map.\n */\nexport function composeRegistry(...layers: (BlockRegistry | undefined)[]): BlockRegistry {\n\tconst composed: BlockRegistry = {};\n\tfor (const layer of layers) {\n\t\tif (!layer) continue;\n\t\tObject.assign(composed, layer);\n\t}\n\treturn composed;\n}\n\nexport function resolveBlock(registry: BlockRegistry, type: string): BlockComponent | undefined {\n\treturn registry[type];\n}\n\n/** Node types the registry can render, sorted for stable output. */\nexport function registeredTypes(registry: BlockRegistry): string[] {\n\treturn Object.keys(registry).sort();\n}\n","'use client';\n\nimport {\n\tcollectDefaultValues,\n\tcollectFieldConstraints,\n\tcollectFieldNames,\n\tvalidateField,\n\ttype UIAction,\n\ttype UIDocument,\n\ttype UINode,\n} from 'blocks-schema';\nimport { useCallback, useEffect, useMemo, useState } from 'react';\n\nimport { resolveNodeProps } from './bindings';\nimport { RendererProvider, useRenderer } from './context';\nimport { UnknownBlock } from './unknown-block';\nimport type { BlockRegistry, RenderMode, RendererContextValue } from './types';\n\n/**\n * Renders one node and, recursively, its children: resolve the node type in the\n * registry, resolve bindings against the current scope, render children first,\n * fall back to {@link UnknownBlock}.\n */\nexport function BlockRenderer({ node }: { node: UINode }) {\n\tconst { registry, scope } = useRenderer();\n\tconst Component = registry[node.type];\n\n\tif (!Component) {\n\t\treturn <UnknownBlock node={node} />;\n\t}\n\n\tconst children = node.children?.length\n\t\t? node.children.map((child) => <BlockRenderer key={child.key} node={child} />)\n\t\t: undefined;\n\n\treturn (\n\t\t<Component node={node} props={resolveNodeProps(node, scope)}>\n\t\t\t{children}\n\t\t</Component>\n\t);\n}\n\nexport interface DocumentRendererProps {\n\tdocument: UIDocument;\n\tregistry: BlockRegistry;\n\tinitialValues?: Record<string, unknown>;\n\t/** Extra data for binding expressions, e.g. `{ row, user, params }`. */\n\tscope?: Record<string, unknown>;\n\tonSubmit?: (values: Record<string, unknown>) => void;\n\tonChange?: (values: Record<string, unknown>) => void;\n\tonAction?: (action: UIAction, event: string) => void;\n\tmode?: RenderMode;\n\tclassName?: string;\n}\n\nexport function DocumentRenderer({\n\tdocument,\n\tregistry,\n\tinitialValues,\n\tscope: externalScope,\n\tonSubmit,\n\tonChange,\n\tonAction,\n\tmode = 'preview',\n\tclassName,\n}: DocumentRendererProps) {\n\tconst defaults = useMemo(() => collectDefaultValues(document.page), [document]);\n\tconst constraints = useMemo(() => collectFieldConstraints(document.page), [document]);\n\n\tconst [values, setValues] = useState<Record<string, unknown>>(() => ({ ...defaults, ...initialValues }));\n\tconst [errors, setErrors] = useState<Record<string, string>>({});\n\n\tuseEffect(() => {\n\t\tsetValues((previous) => ({ ...defaults, ...initialValues, ...previous }));\n\t}, [defaults, initialValues]);\n\n\tconst setError = useCallback((name: string, error: string | null) => {\n\t\tsetErrors((previous) => {\n\t\t\tif (error) return { ...previous, [name]: error };\n\t\t\tif (!(name in previous)) return previous;\n\t\t\tconst next = { ...previous };\n\t\t\tdelete next[name];\n\t\t\treturn next;\n\t\t});\n\t}, []);\n\n\tconst setValue = useCallback(\n\t\t(name: string, value: unknown) => {\n\t\t\tsetValues((previous) => {\n\t\t\t\tconst next = { ...previous, [name]: value };\n\t\t\t\tonChange?.(next);\n\t\t\t\treturn next;\n\t\t\t});\n\n\t\t\tconst field = constraints[name];\n\t\t\tif (field) {\n\t\t\t\tsetError(name, validateField(value, field.constraints, field.required));\n\t\t\t}\n\t\t},\n\t\t[onChange, constraints, setError],\n\t);\n\n\tconst handleAction = useCallback(\n\t\t(action: UIAction, event: string) => {\n\t\t\tif (event === 'submit' && onSubmit) {\n\t\t\t\tconst failures: Record<string, string> = {};\n\t\t\t\tfor (const name of collectFieldNames(document.page)) {\n\t\t\t\t\tconst field = constraints[name];\n\t\t\t\t\tif (!field) continue;\n\t\t\t\t\tconst error = validateField(values[name], field.constraints, field.required);\n\t\t\t\t\tif (error) failures[name] = error;\n\t\t\t\t}\n\n\t\t\t\tif (Object.keys(failures).length > 0) {\n\t\t\t\t\tsetErrors(failures);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tonSubmit(values);\n\t\t\t}\n\n\t\t\tonAction?.(action, event);\n\t\t},\n\t\t[onSubmit, onAction, values, document.page, constraints],\n\t);\n\n\tconst contextValue: RendererContextValue = useMemo(\n\t\t() => ({\n\t\t\tdocument,\n\t\t\tregistry,\n\t\t\tmode,\n\t\t\tvalues,\n\t\t\terrors,\n\t\t\tsetValue,\n\t\t\tsetError,\n\t\t\tscope: { ...externalScope, values, ...values },\n\t\t\tonAction: handleAction,\n\t\t}),\n\t\t[document, registry, mode, values, errors, setValue, setError, externalScope, handleAction],\n\t);\n\n\treturn (\n\t\t<RendererProvider value={contextValue}>\n\t\t\t<div className={className}>\n\t\t\t\t<BlockRenderer node={document.page} />\n\t\t\t</div>\n\t\t</RendererProvider>\n\t);\n}\n","'use client';\n\nimport type { UINode } from 'blocks-schema';\n\n/**\n * Rendered when no registry layer satisfies a node type. A document may name\n * blocks a given host has not installed, so an unknown type is a visible gap,\n * never a thrown render.\n */\nexport function UnknownBlock({ node }: { node: UINode }) {\n\treturn (\n\t\t<div data-block-unknown={node.type} role=\"note\">\n\t\t\tUnknown block: {node.type}\n\t\t</div>\n\t);\n}\n"],"mappings":"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","names":["import_react","import_jsx_runtime","import_jsx_runtime"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { UINode, UINodeProps, UIDocument, UIAction } from 'blocks-schema';
|
|
2
|
+
import * as react from 'react';
|
|
3
|
+
import { ComponentType, ReactNode } from 'react';
|
|
4
|
+
|
|
5
|
+
/** Read a dotted path (`row.author.name`) out of a scope object. */
|
|
6
|
+
declare function readPath(scope: Record<string, unknown>, path: string): unknown;
|
|
7
|
+
/**
|
|
8
|
+
* Resolve a binding expression. A template that is exactly one placeholder
|
|
9
|
+
* yields the raw value (so a boolean or an object survives); a template mixed
|
|
10
|
+
* with text is interpolated as a string.
|
|
11
|
+
*/
|
|
12
|
+
declare function resolveBinding(expression: string, scope: Record<string, unknown>): unknown;
|
|
13
|
+
/** Apply a node's `bindings` over its static props. */
|
|
14
|
+
declare function resolveNodeProps(node: UINode, scope: Record<string, unknown>): UINodeProps;
|
|
15
|
+
|
|
16
|
+
type RenderMode = 'preview' | 'edit';
|
|
17
|
+
/**
|
|
18
|
+
* Props every block component receives. Children are already rendered, and
|
|
19
|
+
* `props` has the node's bindings resolved against the current scope.
|
|
20
|
+
*/
|
|
21
|
+
interface BlockProps {
|
|
22
|
+
node: UINode;
|
|
23
|
+
props: UINodeProps;
|
|
24
|
+
children?: ReactNode;
|
|
25
|
+
}
|
|
26
|
+
type BlockComponent = ComponentType<BlockProps>;
|
|
27
|
+
/** Node type → component. Layered by {@link composeRegistry}. */
|
|
28
|
+
type BlockRegistry = Record<string, BlockComponent>;
|
|
29
|
+
interface RendererContextValue {
|
|
30
|
+
document: UIDocument;
|
|
31
|
+
registry: BlockRegistry;
|
|
32
|
+
mode: RenderMode;
|
|
33
|
+
values: Record<string, unknown>;
|
|
34
|
+
errors: Record<string, string>;
|
|
35
|
+
setValue: (name: string, value: unknown) => void;
|
|
36
|
+
setError: (name: string, error: string | null) => void;
|
|
37
|
+
/** Scope for binding expressions (`{{ row.title }}`), merged with `values`. */
|
|
38
|
+
scope: Record<string, unknown>;
|
|
39
|
+
onAction?: (action: UIAction, event: string) => void;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
declare function RendererProvider({ children, value }: {
|
|
43
|
+
children: ReactNode;
|
|
44
|
+
value: RendererContextValue;
|
|
45
|
+
}): react.JSX.Element;
|
|
46
|
+
declare function useRenderer(): RendererContextValue;
|
|
47
|
+
/**
|
|
48
|
+
* Resolved props plus the value/error wiring for a field node. Widget
|
|
49
|
+
* implementations use this instead of reaching into the document themselves.
|
|
50
|
+
*/
|
|
51
|
+
declare function useBlockField(name: string | undefined): {
|
|
52
|
+
value: undefined;
|
|
53
|
+
error: undefined;
|
|
54
|
+
setValue: () => void;
|
|
55
|
+
setError: () => void;
|
|
56
|
+
mode: RenderMode;
|
|
57
|
+
} | {
|
|
58
|
+
value: unknown;
|
|
59
|
+
error: string;
|
|
60
|
+
setValue: (value: unknown) => void;
|
|
61
|
+
setError: (error: string | null) => void;
|
|
62
|
+
mode: RenderMode;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Layer registries left-to-right, later layers winning. This is how a host
|
|
67
|
+
* customizes rendering: base primitives, then an app registry, then per-document
|
|
68
|
+
* overrides — no forking of the renderer, and no single global map.
|
|
69
|
+
*/
|
|
70
|
+
declare function composeRegistry(...layers: (BlockRegistry | undefined)[]): BlockRegistry;
|
|
71
|
+
declare function resolveBlock(registry: BlockRegistry, type: string): BlockComponent | undefined;
|
|
72
|
+
/** Node types the registry can render, sorted for stable output. */
|
|
73
|
+
declare function registeredTypes(registry: BlockRegistry): string[];
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Renders one node and, recursively, its children: resolve the node type in the
|
|
77
|
+
* registry, resolve bindings against the current scope, render children first,
|
|
78
|
+
* fall back to {@link UnknownBlock}.
|
|
79
|
+
*/
|
|
80
|
+
declare function BlockRenderer({ node }: {
|
|
81
|
+
node: UINode;
|
|
82
|
+
}): react.JSX.Element;
|
|
83
|
+
interface DocumentRendererProps {
|
|
84
|
+
document: UIDocument;
|
|
85
|
+
registry: BlockRegistry;
|
|
86
|
+
initialValues?: Record<string, unknown>;
|
|
87
|
+
/** Extra data for binding expressions, e.g. `{ row, user, params }`. */
|
|
88
|
+
scope?: Record<string, unknown>;
|
|
89
|
+
onSubmit?: (values: Record<string, unknown>) => void;
|
|
90
|
+
onChange?: (values: Record<string, unknown>) => void;
|
|
91
|
+
onAction?: (action: UIAction, event: string) => void;
|
|
92
|
+
mode?: RenderMode;
|
|
93
|
+
className?: string;
|
|
94
|
+
}
|
|
95
|
+
declare function DocumentRenderer({ document, registry, initialValues, scope: externalScope, onSubmit, onChange, onAction, mode, className, }: DocumentRendererProps): react.JSX.Element;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Rendered when no registry layer satisfies a node type. A document may name
|
|
99
|
+
* blocks a given host has not installed, so an unknown type is a visible gap,
|
|
100
|
+
* never a thrown render.
|
|
101
|
+
*/
|
|
102
|
+
declare function UnknownBlock({ node }: {
|
|
103
|
+
node: UINode;
|
|
104
|
+
}): react.JSX.Element;
|
|
105
|
+
|
|
106
|
+
export { type BlockComponent, type BlockProps, type BlockRegistry, BlockRenderer, DocumentRenderer, type DocumentRendererProps, type RenderMode, type RendererContextValue, RendererProvider, UnknownBlock, composeRegistry, readPath, registeredTypes, resolveBinding, resolveBlock, resolveNodeProps, useBlockField, useRenderer };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { UINode, UINodeProps, UIDocument, UIAction } from 'blocks-schema';
|
|
2
|
+
import * as react from 'react';
|
|
3
|
+
import { ComponentType, ReactNode } from 'react';
|
|
4
|
+
|
|
5
|
+
/** Read a dotted path (`row.author.name`) out of a scope object. */
|
|
6
|
+
declare function readPath(scope: Record<string, unknown>, path: string): unknown;
|
|
7
|
+
/**
|
|
8
|
+
* Resolve a binding expression. A template that is exactly one placeholder
|
|
9
|
+
* yields the raw value (so a boolean or an object survives); a template mixed
|
|
10
|
+
* with text is interpolated as a string.
|
|
11
|
+
*/
|
|
12
|
+
declare function resolveBinding(expression: string, scope: Record<string, unknown>): unknown;
|
|
13
|
+
/** Apply a node's `bindings` over its static props. */
|
|
14
|
+
declare function resolveNodeProps(node: UINode, scope: Record<string, unknown>): UINodeProps;
|
|
15
|
+
|
|
16
|
+
type RenderMode = 'preview' | 'edit';
|
|
17
|
+
/**
|
|
18
|
+
* Props every block component receives. Children are already rendered, and
|
|
19
|
+
* `props` has the node's bindings resolved against the current scope.
|
|
20
|
+
*/
|
|
21
|
+
interface BlockProps {
|
|
22
|
+
node: UINode;
|
|
23
|
+
props: UINodeProps;
|
|
24
|
+
children?: ReactNode;
|
|
25
|
+
}
|
|
26
|
+
type BlockComponent = ComponentType<BlockProps>;
|
|
27
|
+
/** Node type → component. Layered by {@link composeRegistry}. */
|
|
28
|
+
type BlockRegistry = Record<string, BlockComponent>;
|
|
29
|
+
interface RendererContextValue {
|
|
30
|
+
document: UIDocument;
|
|
31
|
+
registry: BlockRegistry;
|
|
32
|
+
mode: RenderMode;
|
|
33
|
+
values: Record<string, unknown>;
|
|
34
|
+
errors: Record<string, string>;
|
|
35
|
+
setValue: (name: string, value: unknown) => void;
|
|
36
|
+
setError: (name: string, error: string | null) => void;
|
|
37
|
+
/** Scope for binding expressions (`{{ row.title }}`), merged with `values`. */
|
|
38
|
+
scope: Record<string, unknown>;
|
|
39
|
+
onAction?: (action: UIAction, event: string) => void;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
declare function RendererProvider({ children, value }: {
|
|
43
|
+
children: ReactNode;
|
|
44
|
+
value: RendererContextValue;
|
|
45
|
+
}): react.JSX.Element;
|
|
46
|
+
declare function useRenderer(): RendererContextValue;
|
|
47
|
+
/**
|
|
48
|
+
* Resolved props plus the value/error wiring for a field node. Widget
|
|
49
|
+
* implementations use this instead of reaching into the document themselves.
|
|
50
|
+
*/
|
|
51
|
+
declare function useBlockField(name: string | undefined): {
|
|
52
|
+
value: undefined;
|
|
53
|
+
error: undefined;
|
|
54
|
+
setValue: () => void;
|
|
55
|
+
setError: () => void;
|
|
56
|
+
mode: RenderMode;
|
|
57
|
+
} | {
|
|
58
|
+
value: unknown;
|
|
59
|
+
error: string;
|
|
60
|
+
setValue: (value: unknown) => void;
|
|
61
|
+
setError: (error: string | null) => void;
|
|
62
|
+
mode: RenderMode;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Layer registries left-to-right, later layers winning. This is how a host
|
|
67
|
+
* customizes rendering: base primitives, then an app registry, then per-document
|
|
68
|
+
* overrides — no forking of the renderer, and no single global map.
|
|
69
|
+
*/
|
|
70
|
+
declare function composeRegistry(...layers: (BlockRegistry | undefined)[]): BlockRegistry;
|
|
71
|
+
declare function resolveBlock(registry: BlockRegistry, type: string): BlockComponent | undefined;
|
|
72
|
+
/** Node types the registry can render, sorted for stable output. */
|
|
73
|
+
declare function registeredTypes(registry: BlockRegistry): string[];
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Renders one node and, recursively, its children: resolve the node type in the
|
|
77
|
+
* registry, resolve bindings against the current scope, render children first,
|
|
78
|
+
* fall back to {@link UnknownBlock}.
|
|
79
|
+
*/
|
|
80
|
+
declare function BlockRenderer({ node }: {
|
|
81
|
+
node: UINode;
|
|
82
|
+
}): react.JSX.Element;
|
|
83
|
+
interface DocumentRendererProps {
|
|
84
|
+
document: UIDocument;
|
|
85
|
+
registry: BlockRegistry;
|
|
86
|
+
initialValues?: Record<string, unknown>;
|
|
87
|
+
/** Extra data for binding expressions, e.g. `{ row, user, params }`. */
|
|
88
|
+
scope?: Record<string, unknown>;
|
|
89
|
+
onSubmit?: (values: Record<string, unknown>) => void;
|
|
90
|
+
onChange?: (values: Record<string, unknown>) => void;
|
|
91
|
+
onAction?: (action: UIAction, event: string) => void;
|
|
92
|
+
mode?: RenderMode;
|
|
93
|
+
className?: string;
|
|
94
|
+
}
|
|
95
|
+
declare function DocumentRenderer({ document, registry, initialValues, scope: externalScope, onSubmit, onChange, onAction, mode, className, }: DocumentRendererProps): react.JSX.Element;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Rendered when no registry layer satisfies a node type. A document may name
|
|
99
|
+
* blocks a given host has not installed, so an unknown type is a visible gap,
|
|
100
|
+
* never a thrown render.
|
|
101
|
+
*/
|
|
102
|
+
declare function UnknownBlock({ node }: {
|
|
103
|
+
node: UINode;
|
|
104
|
+
}): react.JSX.Element;
|
|
105
|
+
|
|
106
|
+
export { type BlockComponent, type BlockProps, type BlockRegistry, BlockRenderer, DocumentRenderer, type DocumentRendererProps, type RenderMode, type RendererContextValue, RendererProvider, UnknownBlock, composeRegistry, readPath, registeredTypes, resolveBinding, resolveBlock, resolveNodeProps, useBlockField, useRenderer };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
// src/bindings.ts
|
|
2
|
+
var TEMPLATE = /\{\{\s*([^}\s]+)\s*\}\}/g;
|
|
3
|
+
function readPath(scope, path) {
|
|
4
|
+
let current = scope;
|
|
5
|
+
for (const segment of path.split(".")) {
|
|
6
|
+
if (current == null || typeof current !== "object") return void 0;
|
|
7
|
+
current = current[segment];
|
|
8
|
+
}
|
|
9
|
+
return current;
|
|
10
|
+
}
|
|
11
|
+
function resolveBinding(expression, scope) {
|
|
12
|
+
const single = expression.match(/^\{\{\s*([^}\s]+)\s*\}\}$/);
|
|
13
|
+
if (single) {
|
|
14
|
+
return readPath(scope, single[1]);
|
|
15
|
+
}
|
|
16
|
+
return expression.replace(TEMPLATE, (_match, path) => {
|
|
17
|
+
const value = readPath(scope, path);
|
|
18
|
+
return value == null ? "" : String(value);
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
function resolveNodeProps(node, scope) {
|
|
22
|
+
if (!node.bindings) return node.props ?? {};
|
|
23
|
+
const resolved = { ...node.props ?? {} };
|
|
24
|
+
for (const [prop, expression] of Object.entries(node.bindings)) {
|
|
25
|
+
resolved[prop] = resolveBinding(expression, scope);
|
|
26
|
+
}
|
|
27
|
+
return resolved;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// src/context.tsx
|
|
31
|
+
import { createContext, useContext } from "react";
|
|
32
|
+
import { jsx } from "react/jsx-runtime";
|
|
33
|
+
var RendererContext = createContext(null);
|
|
34
|
+
function RendererProvider({ children, value }) {
|
|
35
|
+
return /* @__PURE__ */ jsx(RendererContext.Provider, { value, children });
|
|
36
|
+
}
|
|
37
|
+
function useRenderer() {
|
|
38
|
+
const context = useContext(RendererContext);
|
|
39
|
+
if (!context) {
|
|
40
|
+
throw new Error("useRenderer must be used within a RendererProvider (or a DocumentRenderer)");
|
|
41
|
+
}
|
|
42
|
+
return context;
|
|
43
|
+
}
|
|
44
|
+
function useBlockField(name) {
|
|
45
|
+
const { values, errors, setValue, setError, mode } = useRenderer();
|
|
46
|
+
if (!name) {
|
|
47
|
+
return { value: void 0, error: void 0, setValue: () => {
|
|
48
|
+
}, setError: () => {
|
|
49
|
+
}, mode };
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
value: values[name],
|
|
53
|
+
error: errors[name],
|
|
54
|
+
setValue: (value) => setValue(name, value),
|
|
55
|
+
setError: (error) => setError(name, error),
|
|
56
|
+
mode
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// src/registry.ts
|
|
61
|
+
function composeRegistry(...layers) {
|
|
62
|
+
const composed = {};
|
|
63
|
+
for (const layer of layers) {
|
|
64
|
+
if (!layer) continue;
|
|
65
|
+
Object.assign(composed, layer);
|
|
66
|
+
}
|
|
67
|
+
return composed;
|
|
68
|
+
}
|
|
69
|
+
function resolveBlock(registry, type) {
|
|
70
|
+
return registry[type];
|
|
71
|
+
}
|
|
72
|
+
function registeredTypes(registry) {
|
|
73
|
+
return Object.keys(registry).sort();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// src/renderer.tsx
|
|
77
|
+
import {
|
|
78
|
+
collectDefaultValues,
|
|
79
|
+
collectFieldConstraints,
|
|
80
|
+
collectFieldNames,
|
|
81
|
+
validateField
|
|
82
|
+
} from "blocks-schema";
|
|
83
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
84
|
+
|
|
85
|
+
// src/unknown-block.tsx
|
|
86
|
+
import { jsxs } from "react/jsx-runtime";
|
|
87
|
+
function UnknownBlock({ node }) {
|
|
88
|
+
return /* @__PURE__ */ jsxs("div", { "data-block-unknown": node.type, role: "note", children: [
|
|
89
|
+
"Unknown block: ",
|
|
90
|
+
node.type
|
|
91
|
+
] });
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// src/renderer.tsx
|
|
95
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
96
|
+
function BlockRenderer({ node }) {
|
|
97
|
+
const { registry, scope } = useRenderer();
|
|
98
|
+
const Component = registry[node.type];
|
|
99
|
+
if (!Component) {
|
|
100
|
+
return /* @__PURE__ */ jsx2(UnknownBlock, { node });
|
|
101
|
+
}
|
|
102
|
+
const children = node.children?.length ? node.children.map((child) => /* @__PURE__ */ jsx2(BlockRenderer, { node: child }, child.key)) : void 0;
|
|
103
|
+
return /* @__PURE__ */ jsx2(Component, { node, props: resolveNodeProps(node, scope), children });
|
|
104
|
+
}
|
|
105
|
+
function DocumentRenderer({
|
|
106
|
+
document,
|
|
107
|
+
registry,
|
|
108
|
+
initialValues,
|
|
109
|
+
scope: externalScope,
|
|
110
|
+
onSubmit,
|
|
111
|
+
onChange,
|
|
112
|
+
onAction,
|
|
113
|
+
mode = "preview",
|
|
114
|
+
className
|
|
115
|
+
}) {
|
|
116
|
+
const defaults = useMemo(() => collectDefaultValues(document.page), [document]);
|
|
117
|
+
const constraints = useMemo(() => collectFieldConstraints(document.page), [document]);
|
|
118
|
+
const [values, setValues] = useState(() => ({ ...defaults, ...initialValues }));
|
|
119
|
+
const [errors, setErrors] = useState({});
|
|
120
|
+
useEffect(() => {
|
|
121
|
+
setValues((previous) => ({ ...defaults, ...initialValues, ...previous }));
|
|
122
|
+
}, [defaults, initialValues]);
|
|
123
|
+
const setError = useCallback((name, error) => {
|
|
124
|
+
setErrors((previous) => {
|
|
125
|
+
if (error) return { ...previous, [name]: error };
|
|
126
|
+
if (!(name in previous)) return previous;
|
|
127
|
+
const next = { ...previous };
|
|
128
|
+
delete next[name];
|
|
129
|
+
return next;
|
|
130
|
+
});
|
|
131
|
+
}, []);
|
|
132
|
+
const setValue = useCallback(
|
|
133
|
+
(name, value) => {
|
|
134
|
+
setValues((previous) => {
|
|
135
|
+
const next = { ...previous, [name]: value };
|
|
136
|
+
onChange?.(next);
|
|
137
|
+
return next;
|
|
138
|
+
});
|
|
139
|
+
const field = constraints[name];
|
|
140
|
+
if (field) {
|
|
141
|
+
setError(name, validateField(value, field.constraints, field.required));
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
[onChange, constraints, setError]
|
|
145
|
+
);
|
|
146
|
+
const handleAction = useCallback(
|
|
147
|
+
(action, event) => {
|
|
148
|
+
if (event === "submit" && onSubmit) {
|
|
149
|
+
const failures = {};
|
|
150
|
+
for (const name of collectFieldNames(document.page)) {
|
|
151
|
+
const field = constraints[name];
|
|
152
|
+
if (!field) continue;
|
|
153
|
+
const error = validateField(values[name], field.constraints, field.required);
|
|
154
|
+
if (error) failures[name] = error;
|
|
155
|
+
}
|
|
156
|
+
if (Object.keys(failures).length > 0) {
|
|
157
|
+
setErrors(failures);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
onSubmit(values);
|
|
161
|
+
}
|
|
162
|
+
onAction?.(action, event);
|
|
163
|
+
},
|
|
164
|
+
[onSubmit, onAction, values, document.page, constraints]
|
|
165
|
+
);
|
|
166
|
+
const contextValue = useMemo(
|
|
167
|
+
() => ({
|
|
168
|
+
document,
|
|
169
|
+
registry,
|
|
170
|
+
mode,
|
|
171
|
+
values,
|
|
172
|
+
errors,
|
|
173
|
+
setValue,
|
|
174
|
+
setError,
|
|
175
|
+
scope: { ...externalScope, values, ...values },
|
|
176
|
+
onAction: handleAction
|
|
177
|
+
}),
|
|
178
|
+
[document, registry, mode, values, errors, setValue, setError, externalScope, handleAction]
|
|
179
|
+
);
|
|
180
|
+
return /* @__PURE__ */ jsx2(RendererProvider, { value: contextValue, children: /* @__PURE__ */ jsx2("div", { className, children: /* @__PURE__ */ jsx2(BlockRenderer, { node: document.page }) }) });
|
|
181
|
+
}
|
|
182
|
+
export {
|
|
183
|
+
BlockRenderer,
|
|
184
|
+
DocumentRenderer,
|
|
185
|
+
RendererProvider,
|
|
186
|
+
UnknownBlock,
|
|
187
|
+
composeRegistry,
|
|
188
|
+
readPath,
|
|
189
|
+
registeredTypes,
|
|
190
|
+
resolveBinding,
|
|
191
|
+
resolveBlock,
|
|
192
|
+
resolveNodeProps,
|
|
193
|
+
useBlockField,
|
|
194
|
+
useRenderer
|
|
195
|
+
};
|
|
196
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/bindings.ts","../src/context.tsx","../src/registry.ts","../src/renderer.tsx","../src/unknown-block.tsx"],"sourcesContent":["import type { UINode, UINodeProps } from 'blocks-schema';\n\nconst TEMPLATE = /\\{\\{\\s*([^}\\s]+)\\s*\\}\\}/g;\n\n/** Read a dotted path (`row.author.name`) out of a scope object. */\nexport function readPath(scope: Record<string, unknown>, path: string): unknown {\n\tlet current: unknown = scope;\n\tfor (const segment of path.split('.')) {\n\t\tif (current == null || typeof current !== 'object') return undefined;\n\t\tcurrent = (current as Record<string, unknown>)[segment];\n\t}\n\treturn current;\n}\n\n/**\n * Resolve a binding expression. A template that is exactly one placeholder\n * yields the raw value (so a boolean or an object survives); a template mixed\n * with text is interpolated as a string.\n */\nexport function resolveBinding(expression: string, scope: Record<string, unknown>): unknown {\n\tconst single = expression.match(/^\\{\\{\\s*([^}\\s]+)\\s*\\}\\}$/);\n\tif (single) {\n\t\treturn readPath(scope, single[1]);\n\t}\n\n\treturn expression.replace(TEMPLATE, (_match, path: string) => {\n\t\tconst value = readPath(scope, path);\n\t\treturn value == null ? '' : String(value);\n\t});\n}\n\n/** Apply a node's `bindings` over its static props. */\nexport function resolveNodeProps(node: UINode, scope: Record<string, unknown>): UINodeProps {\n\tif (!node.bindings) return node.props ?? {};\n\n\tconst resolved: UINodeProps = { ...(node.props ?? {}) };\n\tfor (const [prop, expression] of Object.entries(node.bindings)) {\n\t\tresolved[prop] = resolveBinding(expression, scope);\n\t}\n\treturn resolved;\n}\n","'use client';\n\nimport { createContext, useContext } from 'react';\nimport type { ReactNode } from 'react';\n\nimport type { RendererContextValue } from './types';\n\nconst RendererContext = createContext<RendererContextValue | null>(null);\n\nexport function RendererProvider({ children, value }: { children: ReactNode; value: RendererContextValue }) {\n\treturn <RendererContext.Provider value={value}>{children}</RendererContext.Provider>;\n}\n\nexport function useRenderer(): RendererContextValue {\n\tconst context = useContext(RendererContext);\n\tif (!context) {\n\t\tthrow new Error('useRenderer must be used within a RendererProvider (or a DocumentRenderer)');\n\t}\n\treturn context;\n}\n\n/**\n * Resolved props plus the value/error wiring for a field node. Widget\n * implementations use this instead of reaching into the document themselves.\n */\nexport function useBlockField(name: string | undefined) {\n\tconst { values, errors, setValue, setError, mode } = useRenderer();\n\tif (!name) {\n\t\treturn { value: undefined, error: undefined, setValue: () => {}, setError: () => {}, mode };\n\t}\n\treturn {\n\t\tvalue: values[name],\n\t\terror: errors[name],\n\t\tsetValue: (value: unknown) => setValue(name, value),\n\t\tsetError: (error: string | null) => setError(name, error),\n\t\tmode,\n\t};\n}\n","import type { BlockComponent, BlockRegistry } from './types';\n\n/**\n * Layer registries left-to-right, later layers winning. This is how a host\n * customizes rendering: base primitives, then an app registry, then per-document\n * overrides — no forking of the renderer, and no single global map.\n */\nexport function composeRegistry(...layers: (BlockRegistry | undefined)[]): BlockRegistry {\n\tconst composed: BlockRegistry = {};\n\tfor (const layer of layers) {\n\t\tif (!layer) continue;\n\t\tObject.assign(composed, layer);\n\t}\n\treturn composed;\n}\n\nexport function resolveBlock(registry: BlockRegistry, type: string): BlockComponent | undefined {\n\treturn registry[type];\n}\n\n/** Node types the registry can render, sorted for stable output. */\nexport function registeredTypes(registry: BlockRegistry): string[] {\n\treturn Object.keys(registry).sort();\n}\n","'use client';\n\nimport {\n\tcollectDefaultValues,\n\tcollectFieldConstraints,\n\tcollectFieldNames,\n\tvalidateField,\n\ttype UIAction,\n\ttype UIDocument,\n\ttype UINode,\n} from 'blocks-schema';\nimport { useCallback, useEffect, useMemo, useState } from 'react';\n\nimport { resolveNodeProps } from './bindings';\nimport { RendererProvider, useRenderer } from './context';\nimport { UnknownBlock } from './unknown-block';\nimport type { BlockRegistry, RenderMode, RendererContextValue } from './types';\n\n/**\n * Renders one node and, recursively, its children: resolve the node type in the\n * registry, resolve bindings against the current scope, render children first,\n * fall back to {@link UnknownBlock}.\n */\nexport function BlockRenderer({ node }: { node: UINode }) {\n\tconst { registry, scope } = useRenderer();\n\tconst Component = registry[node.type];\n\n\tif (!Component) {\n\t\treturn <UnknownBlock node={node} />;\n\t}\n\n\tconst children = node.children?.length\n\t\t? node.children.map((child) => <BlockRenderer key={child.key} node={child} />)\n\t\t: undefined;\n\n\treturn (\n\t\t<Component node={node} props={resolveNodeProps(node, scope)}>\n\t\t\t{children}\n\t\t</Component>\n\t);\n}\n\nexport interface DocumentRendererProps {\n\tdocument: UIDocument;\n\tregistry: BlockRegistry;\n\tinitialValues?: Record<string, unknown>;\n\t/** Extra data for binding expressions, e.g. `{ row, user, params }`. */\n\tscope?: Record<string, unknown>;\n\tonSubmit?: (values: Record<string, unknown>) => void;\n\tonChange?: (values: Record<string, unknown>) => void;\n\tonAction?: (action: UIAction, event: string) => void;\n\tmode?: RenderMode;\n\tclassName?: string;\n}\n\nexport function DocumentRenderer({\n\tdocument,\n\tregistry,\n\tinitialValues,\n\tscope: externalScope,\n\tonSubmit,\n\tonChange,\n\tonAction,\n\tmode = 'preview',\n\tclassName,\n}: DocumentRendererProps) {\n\tconst defaults = useMemo(() => collectDefaultValues(document.page), [document]);\n\tconst constraints = useMemo(() => collectFieldConstraints(document.page), [document]);\n\n\tconst [values, setValues] = useState<Record<string, unknown>>(() => ({ ...defaults, ...initialValues }));\n\tconst [errors, setErrors] = useState<Record<string, string>>({});\n\n\tuseEffect(() => {\n\t\tsetValues((previous) => ({ ...defaults, ...initialValues, ...previous }));\n\t}, [defaults, initialValues]);\n\n\tconst setError = useCallback((name: string, error: string | null) => {\n\t\tsetErrors((previous) => {\n\t\t\tif (error) return { ...previous, [name]: error };\n\t\t\tif (!(name in previous)) return previous;\n\t\t\tconst next = { ...previous };\n\t\t\tdelete next[name];\n\t\t\treturn next;\n\t\t});\n\t}, []);\n\n\tconst setValue = useCallback(\n\t\t(name: string, value: unknown) => {\n\t\t\tsetValues((previous) => {\n\t\t\t\tconst next = { ...previous, [name]: value };\n\t\t\t\tonChange?.(next);\n\t\t\t\treturn next;\n\t\t\t});\n\n\t\t\tconst field = constraints[name];\n\t\t\tif (field) {\n\t\t\t\tsetError(name, validateField(value, field.constraints, field.required));\n\t\t\t}\n\t\t},\n\t\t[onChange, constraints, setError],\n\t);\n\n\tconst handleAction = useCallback(\n\t\t(action: UIAction, event: string) => {\n\t\t\tif (event === 'submit' && onSubmit) {\n\t\t\t\tconst failures: Record<string, string> = {};\n\t\t\t\tfor (const name of collectFieldNames(document.page)) {\n\t\t\t\t\tconst field = constraints[name];\n\t\t\t\t\tif (!field) continue;\n\t\t\t\t\tconst error = validateField(values[name], field.constraints, field.required);\n\t\t\t\t\tif (error) failures[name] = error;\n\t\t\t\t}\n\n\t\t\t\tif (Object.keys(failures).length > 0) {\n\t\t\t\t\tsetErrors(failures);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tonSubmit(values);\n\t\t\t}\n\n\t\t\tonAction?.(action, event);\n\t\t},\n\t\t[onSubmit, onAction, values, document.page, constraints],\n\t);\n\n\tconst contextValue: RendererContextValue = useMemo(\n\t\t() => ({\n\t\t\tdocument,\n\t\t\tregistry,\n\t\t\tmode,\n\t\t\tvalues,\n\t\t\terrors,\n\t\t\tsetValue,\n\t\t\tsetError,\n\t\t\tscope: { ...externalScope, values, ...values },\n\t\t\tonAction: handleAction,\n\t\t}),\n\t\t[document, registry, mode, values, errors, setValue, setError, externalScope, handleAction],\n\t);\n\n\treturn (\n\t\t<RendererProvider value={contextValue}>\n\t\t\t<div className={className}>\n\t\t\t\t<BlockRenderer node={document.page} />\n\t\t\t</div>\n\t\t</RendererProvider>\n\t);\n}\n","'use client';\n\nimport type { UINode } from 'blocks-schema';\n\n/**\n * Rendered when no registry layer satisfies a node type. A document may name\n * blocks a given host has not installed, so an unknown type is a visible gap,\n * never a thrown render.\n */\nexport function UnknownBlock({ node }: { node: UINode }) {\n\treturn (\n\t\t<div data-block-unknown={node.type} role=\"note\">\n\t\t\tUnknown block: {node.type}\n\t\t</div>\n\t);\n}\n"],"mappings":";AAEA,IAAM,WAAW;AAGV,SAAS,SAAS,OAAgC,MAAuB;AAC/E,MAAI,UAAmB;AACvB,aAAW,WAAW,KAAK,MAAM,GAAG,GAAG;AACtC,QAAI,WAAW,QAAQ,OAAO,YAAY,SAAU,QAAO;AAC3D,cAAW,QAAoC,OAAO;AAAA,EACvD;AACA,SAAO;AACR;AAOO,SAAS,eAAe,YAAoB,OAAyC;AAC3F,QAAM,SAAS,WAAW,MAAM,2BAA2B;AAC3D,MAAI,QAAQ;AACX,WAAO,SAAS,OAAO,OAAO,CAAC,CAAC;AAAA,EACjC;AAEA,SAAO,WAAW,QAAQ,UAAU,CAAC,QAAQ,SAAiB;AAC7D,UAAM,QAAQ,SAAS,OAAO,IAAI;AAClC,WAAO,SAAS,OAAO,KAAK,OAAO,KAAK;AAAA,EACzC,CAAC;AACF;AAGO,SAAS,iBAAiB,MAAc,OAA6C;AAC3F,MAAI,CAAC,KAAK,SAAU,QAAO,KAAK,SAAS,CAAC;AAE1C,QAAM,WAAwB,EAAE,GAAI,KAAK,SAAS,CAAC,EAAG;AACtD,aAAW,CAAC,MAAM,UAAU,KAAK,OAAO,QAAQ,KAAK,QAAQ,GAAG;AAC/D,aAAS,IAAI,IAAI,eAAe,YAAY,KAAK;AAAA,EAClD;AACA,SAAO;AACR;;;ACtCA,SAAS,eAAe,kBAAkB;AAQlC;AAHR,IAAM,kBAAkB,cAA2C,IAAI;AAEhE,SAAS,iBAAiB,EAAE,UAAU,MAAM,GAAyD;AAC3G,SAAO,oBAAC,gBAAgB,UAAhB,EAAyB,OAAe,UAAS;AAC1D;AAEO,SAAS,cAAoC;AACnD,QAAM,UAAU,WAAW,eAAe;AAC1C,MAAI,CAAC,SAAS;AACb,UAAM,IAAI,MAAM,4EAA4E;AAAA,EAC7F;AACA,SAAO;AACR;AAMO,SAAS,cAAc,MAA0B;AACvD,QAAM,EAAE,QAAQ,QAAQ,UAAU,UAAU,KAAK,IAAI,YAAY;AACjE,MAAI,CAAC,MAAM;AACV,WAAO,EAAE,OAAO,QAAW,OAAO,QAAW,UAAU,MAAM;AAAA,IAAC,GAAG,UAAU,MAAM;AAAA,IAAC,GAAG,KAAK;AAAA,EAC3F;AACA,SAAO;AAAA,IACN,OAAO,OAAO,IAAI;AAAA,IAClB,OAAO,OAAO,IAAI;AAAA,IAClB,UAAU,CAAC,UAAmB,SAAS,MAAM,KAAK;AAAA,IAClD,UAAU,CAAC,UAAyB,SAAS,MAAM,KAAK;AAAA,IACxD;AAAA,EACD;AACD;;;AC9BO,SAAS,mBAAmB,QAAsD;AACxF,QAAM,WAA0B,CAAC;AACjC,aAAW,SAAS,QAAQ;AAC3B,QAAI,CAAC,MAAO;AACZ,WAAO,OAAO,UAAU,KAAK;AAAA,EAC9B;AACA,SAAO;AACR;AAEO,SAAS,aAAa,UAAyB,MAA0C;AAC/F,SAAO,SAAS,IAAI;AACrB;AAGO,SAAS,gBAAgB,UAAmC;AAClE,SAAO,OAAO,KAAK,QAAQ,EAAE,KAAK;AACnC;;;ACrBA;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAIM;AACP,SAAS,aAAa,WAAW,SAAS,gBAAgB;;;ACAxD;AAFK,SAAS,aAAa,EAAE,KAAK,GAAqB;AACxD,SACC,qBAAC,SAAI,sBAAoB,KAAK,MAAM,MAAK,QAAO;AAAA;AAAA,IAC/B,KAAK;AAAA,KACtB;AAEF;;;ADaS,gBAAAA,YAAA;AALF,SAAS,cAAc,EAAE,KAAK,GAAqB;AACzD,QAAM,EAAE,UAAU,MAAM,IAAI,YAAY;AACxC,QAAM,YAAY,SAAS,KAAK,IAAI;AAEpC,MAAI,CAAC,WAAW;AACf,WAAO,gBAAAA,KAAC,gBAAa,MAAY;AAAA,EAClC;AAEA,QAAM,WAAW,KAAK,UAAU,SAC7B,KAAK,SAAS,IAAI,CAAC,UAAU,gBAAAA,KAAC,iBAA8B,MAAM,SAAjB,MAAM,GAAkB,CAAE,IAC3E;AAEH,SACC,gBAAAA,KAAC,aAAU,MAAY,OAAO,iBAAiB,MAAM,KAAK,GACxD,UACF;AAEF;AAeO,SAAS,iBAAiB;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP;AACD,GAA0B;AACzB,QAAM,WAAW,QAAQ,MAAM,qBAAqB,SAAS,IAAI,GAAG,CAAC,QAAQ,CAAC;AAC9E,QAAM,cAAc,QAAQ,MAAM,wBAAwB,SAAS,IAAI,GAAG,CAAC,QAAQ,CAAC;AAEpF,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAkC,OAAO,EAAE,GAAG,UAAU,GAAG,cAAc,EAAE;AACvG,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAiC,CAAC,CAAC;AAE/D,YAAU,MAAM;AACf,cAAU,CAAC,cAAc,EAAE,GAAG,UAAU,GAAG,eAAe,GAAG,SAAS,EAAE;AAAA,EACzE,GAAG,CAAC,UAAU,aAAa,CAAC;AAE5B,QAAM,WAAW,YAAY,CAAC,MAAc,UAAyB;AACpE,cAAU,CAAC,aAAa;AACvB,UAAI,MAAO,QAAO,EAAE,GAAG,UAAU,CAAC,IAAI,GAAG,MAAM;AAC/C,UAAI,EAAE,QAAQ,UAAW,QAAO;AAChC,YAAM,OAAO,EAAE,GAAG,SAAS;AAC3B,aAAO,KAAK,IAAI;AAChB,aAAO;AAAA,IACR,CAAC;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,QAAM,WAAW;AAAA,IAChB,CAAC,MAAc,UAAmB;AACjC,gBAAU,CAAC,aAAa;AACvB,cAAM,OAAO,EAAE,GAAG,UAAU,CAAC,IAAI,GAAG,MAAM;AAC1C,mBAAW,IAAI;AACf,eAAO;AAAA,MACR,CAAC;AAED,YAAM,QAAQ,YAAY,IAAI;AAC9B,UAAI,OAAO;AACV,iBAAS,MAAM,cAAc,OAAO,MAAM,aAAa,MAAM,QAAQ,CAAC;AAAA,MACvE;AAAA,IACD;AAAA,IACA,CAAC,UAAU,aAAa,QAAQ;AAAA,EACjC;AAEA,QAAM,eAAe;AAAA,IACpB,CAAC,QAAkB,UAAkB;AACpC,UAAI,UAAU,YAAY,UAAU;AACnC,cAAM,WAAmC,CAAC;AAC1C,mBAAW,QAAQ,kBAAkB,SAAS,IAAI,GAAG;AACpD,gBAAM,QAAQ,YAAY,IAAI;AAC9B,cAAI,CAAC,MAAO;AACZ,gBAAM,QAAQ,cAAc,OAAO,IAAI,GAAG,MAAM,aAAa,MAAM,QAAQ;AAC3E,cAAI,MAAO,UAAS,IAAI,IAAI;AAAA,QAC7B;AAEA,YAAI,OAAO,KAAK,QAAQ,EAAE,SAAS,GAAG;AACrC,oBAAU,QAAQ;AAClB;AAAA,QACD;AAEA,iBAAS,MAAM;AAAA,MAChB;AAEA,iBAAW,QAAQ,KAAK;AAAA,IACzB;AAAA,IACA,CAAC,UAAU,UAAU,QAAQ,SAAS,MAAM,WAAW;AAAA,EACxD;AAEA,QAAM,eAAqC;AAAA,IAC1C,OAAO;AAAA,MACN;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,OAAO,EAAE,GAAG,eAAe,QAAQ,GAAG,OAAO;AAAA,MAC7C,UAAU;AAAA,IACX;AAAA,IACA,CAAC,UAAU,UAAU,MAAM,QAAQ,QAAQ,UAAU,UAAU,eAAe,YAAY;AAAA,EAC3F;AAEA,SACC,gBAAAA,KAAC,oBAAiB,OAAO,cACxB,0BAAAA,KAAC,SAAI,WACJ,0BAAAA,KAAC,iBAAc,MAAM,SAAS,MAAM,GACrC,GACD;AAEF;","names":["jsx"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "blocks-renderer",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Recursive React renderer for Constructive Blocks JSON UI documents, with layerable widget registries",
|
|
5
|
+
"private": false,
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"homepage": "https://constructive-io.github.io/blocks/",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/constructive-io/blocks.git",
|
|
11
|
+
"directory": "packages/blocks-renderer"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/constructive-io/blocks/issues"
|
|
15
|
+
},
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"type": "module",
|
|
20
|
+
"sideEffects": false,
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"import": {
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"default": "./dist/index.js"
|
|
26
|
+
},
|
|
27
|
+
"require": {
|
|
28
|
+
"types": "./dist/index.d.cts",
|
|
29
|
+
"default": "./dist/index.cjs"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"./package.json": "./package.json"
|
|
33
|
+
},
|
|
34
|
+
"main": "./dist/index.cjs",
|
|
35
|
+
"module": "./dist/index.js",
|
|
36
|
+
"types": "./dist/index.d.ts",
|
|
37
|
+
"files": [
|
|
38
|
+
"dist",
|
|
39
|
+
"LICENSE",
|
|
40
|
+
"README.md"
|
|
41
|
+
],
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "tsup",
|
|
44
|
+
"dev": "tsup --watch",
|
|
45
|
+
"lint:types": "tsc --noEmit",
|
|
46
|
+
"test": "vitest run",
|
|
47
|
+
"test:watch": "vitest",
|
|
48
|
+
"clean": "rm -rf dist"
|
|
49
|
+
},
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"blocks-schema": "^0.2.0"
|
|
52
|
+
},
|
|
53
|
+
"peerDependencies": {
|
|
54
|
+
"react": "^18.0.0 || ^19.0.0"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@types/react": "^19.2.7",
|
|
58
|
+
"@types/react-dom": "^19.2.3",
|
|
59
|
+
"react": "^19.2.3",
|
|
60
|
+
"react-dom": "^19.2.3",
|
|
61
|
+
"tsup": "^8.5.1",
|
|
62
|
+
"typescript": "^5.9.3",
|
|
63
|
+
"vitest": "^3.2.4"
|
|
64
|
+
},
|
|
65
|
+
"engines": {
|
|
66
|
+
"node": ">=24.0.0"
|
|
67
|
+
},
|
|
68
|
+
"gitHead": "b0fd1126b68b23543a51949c1e847c4d820863d3"
|
|
69
|
+
}
|