@reformer/renderer-json 7.0.0 → 9.0.0-beta.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/README.md ADDED
@@ -0,0 +1,87 @@
1
+ # @reformer/renderer-json
2
+
3
+ Render [`@reformer/core`](https://www.npmjs.com/package/@reformer/core) forms from a **JSON schema**.
4
+ Field bindings, components and data sources are expressed as string operators
5
+ (`$model(...)`, `$component(...)`, `$dataSource(...)`), so an entire form can be described as data and
6
+ rendered through a component registry — no per-field React code.
7
+
8
+ ## Documentation
9
+
10
+ Full documentation is available at [https://alexandrbukhtatyy.github.io/ReFormer/](https://alexandrbukhtatyy.github.io/ReFormer/)
11
+
12
+ ## Features
13
+
14
+ - Describe forms declaratively as JSON (`JsonFormSchema`)
15
+ - String operators bind schema leaves to a reactive `FormModel` and to registered components
16
+ - Component registry (`defineRegistry`) maps operator names to your React components
17
+ - Optional schema validation against a meta-schema (ajv, loaded dynamically — dev only)
18
+ - TypeScript support, tree-shakable exports
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ npm install @reformer/renderer-json @reformer/core @reformer/renderer-react
24
+ ```
25
+
26
+ `@reformer/ui-kit` is an optional peer — use it (or your own components) to populate the registry.
27
+
28
+ ## Quick Start
29
+
30
+ ```tsx
31
+ import { useMemo } from 'react';
32
+ import { createModel } from '@reformer/core';
33
+ import { Input, Box, FormField } from '@reformer/ui-kit';
34
+ import {
35
+ JsonFormRenderer,
36
+ JsonRendererProvider,
37
+ defineRegistry,
38
+ FIELD_WRAPPER,
39
+ type JsonFormSchema,
40
+ } from '@reformer/renderer-json';
41
+
42
+ // Bindings are string operators: '$model(...)', '$component(...)', '$dataSource(...)'.
43
+ const schema: JsonFormSchema = {
44
+ version: '1.0',
45
+ root: {
46
+ component: '$component(Box)',
47
+ children: [
48
+ {
49
+ value: '$model(email)',
50
+ component: '$component(Input)',
51
+ componentProps: { label: 'Email' },
52
+ },
53
+ ],
54
+ },
55
+ };
56
+
57
+ type MyForm = { email: string };
58
+
59
+ export function MyFormPage() {
60
+ // M1: the model is the source of truth; schema leaves bind to its signals.
61
+ const model = useMemo(() => createModel<MyForm>({ email: '' }), []);
62
+ const registry = useMemo(
63
+ () =>
64
+ defineRegistry((reg) => {
65
+ reg.component('Input', Input);
66
+ reg.component('Box', Box);
67
+ reg.component(FIELD_WRAPPER, FormField); // system field wrapper
68
+ }),
69
+ []
70
+ );
71
+
72
+ // The model is provided through the provider (settings.model), not as a renderer prop.
73
+ return (
74
+ <JsonRendererProvider settings={{ registry, model }}>
75
+ <JsonFormRenderer<MyForm> schema={schema} validate={import.meta.env.DEV} />
76
+ </JsonRendererProvider>
77
+ );
78
+ }
79
+ ```
80
+
81
+ `JsonFormRenderer` accepts only `{ schema, renderBehavior?, onSchemaReady?, validate? }`. The
82
+ `FormModel` is supplied via `JsonRendererProvider` settings (`model`); schema leaves are bound to its
83
+ signals by the built-in converter.
84
+
85
+ ## License
86
+
87
+ MIT
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,41 @@
1
+ const s = /^\$(model|component|dataSource)\((.+)\)$/;
2
+ function n(e) {
3
+ if (typeof e != "string") return null;
4
+ const t = s.exec(e);
5
+ return t ? { op: t[1], arg: t[2] } : null;
6
+ }
7
+ const $ = (e) => n(e)?.op === "model", g = (e) => n(e)?.op === "component", b = (e) => n(e)?.op === "dataSource", p = "http://json-schema.org/draft-07/schema#", c = "https://reformer.dev/schemas/form-schema.schema.json", m = "ReFormer JSON form schema (M1, string-operator DSL)", d = "Base meta-schema: validates node structure + operator string syntax ($model/$component/$dataSource). Component-name enum is tightened per-app via buildFormSchemaMetaSchema; $dataSource names and $model paths are checked outside this schema.", l = "object", f = ["root"], u = !1, h = { $schema: { type: "string" }, version: { type: "string" }, root: { $ref: "#/definitions/node" } }, y = { modelOp: { type: "string", pattern: "^\\$model\\(.+\\)$", description: 'Model binding: "$model(path)" — path resolved at runtime (not validated here).' }, componentOp: { type: "string", pattern: "^\\$component\\(.+\\)$", description: 'Registry component: "$component(Name)". Name is tightened to a registry enum by buildFormSchemaMetaSchema.' }, node: { $comment: "Discriminated by which operator key is present (array→value→component), mirroring the isArrayNode/isFieldNode/isContainerNode runtime guards. Using oneOf here made ajv (allErrors) report failures against all three branches for any single mistake, burying the real cause; if/then/else surfaces only the relevant branch's errors.", if: { type: "object", required: ["array"] }, then: { $ref: "#/definitions/arrayNode" }, else: { if: { type: "object", required: ["value"] }, then: { $ref: "#/definitions/fieldNode" }, else: { $ref: "#/definitions/containerNode" } } }, fieldNode: { type: "object", required: ["value"], additionalProperties: !1, properties: { selector: { type: "string" }, value: { $ref: "#/definitions/modelOp" }, component: { $ref: "#/definitions/componentOp" }, componentProps: { type: "object" }, wrapper: { $ref: "#/definitions/node" } } }, arrayNode: { type: "object", required: ["array", "item"], additionalProperties: !1, properties: { selector: { type: "string" }, array: { $ref: "#/definitions/modelOp" }, item: { type: "object", required: ["$template"], additionalProperties: !1, properties: { $template: { $ref: "#/definitions/node" } } }, initialValue: { type: "object" }, componentProps: { type: "object" } } }, containerNode: { type: "object", required: ["component"], additionalProperties: !1, properties: { selector: { type: "string" }, component: { $ref: "#/definitions/componentOp" }, componentProps: { type: "object" }, children: { type: "array", items: { $ref: "#/definitions/node" } } } } }, i = {
8
+ $schema: p,
9
+ $id: c,
10
+ title: m,
11
+ description: d,
12
+ type: l,
13
+ required: f,
14
+ additionalProperties: u,
15
+ properties: h,
16
+ definitions: y
17
+ }, S = i;
18
+ function N(e) {
19
+ return e.names().filter((t) => e.get(t)?.type === "component");
20
+ }
21
+ function O(e) {
22
+ return e.names().filter((t) => e.get(t)?.type === "dataSource");
23
+ }
24
+ function j(e) {
25
+ const t = JSON.parse(JSON.stringify(i)), o = e?.componentNames;
26
+ if (o && o.length > 0) {
27
+ const r = t.definitions.componentOp;
28
+ delete r.pattern, r.enum = o.map((a) => `$component(${a})`);
29
+ }
30
+ return t;
31
+ }
32
+ export {
33
+ g as a,
34
+ b,
35
+ j as c,
36
+ O as d,
37
+ S as f,
38
+ N as g,
39
+ $ as i,
40
+ n as p
41
+ };
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
- import { jsx as u, jsxs as M } from "react/jsx-runtime";
2
- import { useContext as N, createContext as O, useMemo as g, useState as C, useEffect as F } from "react";
3
- import { createRenderSchema as j, FormRenderer as k } from "@reformer/renderer-react";
4
- import { i as f, a as R, p as m, b as A } from "./index-BZLcW0SX.js";
5
- import { c as rr, f as er, g as tr, d as nr } from "./index-BZLcW0SX.js";
6
- class h {
1
+ import { jsx as m, jsxs as R } from "react/jsx-runtime";
2
+ import { useContext as $, createContext as O, useMemo as w, useState as F, useEffect as j } from "react";
3
+ import { createRenderSchema as k, FormRenderer as v } from "@reformer/renderer-react";
4
+ import { i as p, a as E, p as d, b as A } from "./index-Bu5XPCQ7.js";
5
+ import { c as rr, f as er, g as tr, d as nr } from "./index-Bu5XPCQ7.js";
6
+ class u {
7
7
  own = /* @__PURE__ */ new Map();
8
8
  parent = null;
9
9
  get(e) {
@@ -25,52 +25,58 @@ class h {
25
25
  this.own.has(e) && console.warn(`[ComponentRegistry] Overwriting entry: ${e}`), this.own.set(e, t);
26
26
  }
27
27
  static withParent(e, t) {
28
- const n = new h();
29
- return n.parent = e, t.own.forEach((o, i) => {
30
- n.own.set(i, o);
31
- }), n;
28
+ const n = new u(), o = (s) => {
29
+ if (s instanceof u)
30
+ s.own.forEach((i, a) => n.own.set(a, i));
31
+ else
32
+ for (const i of s.names()) {
33
+ const a = s.get(i);
34
+ a && n.own.set(i, a);
35
+ }
36
+ };
37
+ return e instanceof u ? n.parent = e : o(e), o(t), n;
32
38
  }
33
39
  }
34
40
  function K(r) {
35
- const e = new h();
41
+ const e = new u();
36
42
  return r({
37
- component(n, o, i) {
38
- e._set(n, { component: o, type: "component", description: i });
43
+ component(n, o, s) {
44
+ e._set(n, { component: o, type: "component", description: s });
39
45
  },
40
- dataSource(n, o, i) {
41
- e._set(n, { component: o, type: "dataSource", description: i });
46
+ dataSource(n, o, s) {
47
+ e._set(n, { component: o, type: "dataSource", description: s });
42
48
  }
43
49
  }), e;
44
50
  }
45
- const v = "$fieldWrapper", w = O({});
51
+ const C = "$fieldWrapper", P = O({});
46
52
  function Q({ settings: r, children: e }) {
47
- const t = N(w), n = g(() => t.registry && r.registry ? h.withParent(t.registry, r.registry) : r.registry ?? t.registry, [t.registry, r.registry]), o = g(() => {
48
- const i = n?.get(v)?.component;
53
+ const t = $(P), n = w(() => t.registry && r.registry ? u.withParent(t.registry, r.registry) : r.registry ?? t.registry, [t.registry, r.registry]), o = w(() => {
54
+ const s = n?.get(C)?.component;
49
55
  return {
50
56
  ...t,
51
57
  ...r,
52
58
  registry: n,
53
- fieldWrapper: r.fieldWrapper ?? i ?? t.fieldWrapper
59
+ fieldWrapper: r.fieldWrapper ?? s ?? t.fieldWrapper
54
60
  };
55
61
  }, [t, r, n]);
56
- return /* @__PURE__ */ u(w.Provider, { value: o, children: e });
62
+ return /* @__PURE__ */ m(P.Provider, { value: o, children: e });
57
63
  }
58
- function D() {
59
- return N(w);
64
+ function W() {
65
+ return $(P);
60
66
  }
61
- function $(r) {
67
+ function J(r) {
62
68
  const e = r;
63
- return f(e.array) && typeof e.item == "object" && e.item !== null && "$template" in e.item;
69
+ return p(e.array) && typeof e.item == "object" && e.item !== null && "$template" in e.item;
64
70
  }
65
- function E(r) {
66
- return f(r.value);
71
+ function M(r) {
72
+ return p(r.value);
67
73
  }
68
- function W(r) {
69
- return R(r.component) && !E(r) && !$(r);
74
+ function D(r) {
75
+ return E(r.component) && !M(r) && !J(r);
70
76
  }
71
- function P(r, e) {
77
+ function y(r, e) {
72
78
  if (!r) return;
73
- const t = m(r)?.arg;
79
+ const t = d(r)?.arg;
74
80
  if (!t) throw new Error(`Invalid $component operator: "${r}"`);
75
81
  const n = e.get(t);
76
82
  if (!n)
@@ -87,28 +93,30 @@ function L(r, e) {
87
93
  throw new Error(
88
94
  `Data source "${r}" not found in registry. Available: ${e.names().join(", ")}`
89
95
  );
96
+ if (t.type !== "dataSource")
97
+ throw new Error(`Entry "${r}" is a '${t.type}' and cannot be used as $dataSource(...)`);
90
98
  return t.component;
91
99
  }
92
- function I(r, e) {
100
+ function V(r, e) {
93
101
  return e.split(".").reduce((t, n) => t == null ? t : t[n], r);
94
102
  }
95
- function V(r) {
103
+ function q(r) {
96
104
  if (r === null || typeof r != "object") return !1;
97
105
  const e = r;
98
- return f(e.value) || f(e.array) || R(e.component);
106
+ return p(e.value) || p(e.array) || E(e.component);
99
107
  }
100
- function _(r) {
108
+ function T(r) {
101
109
  return r == null ? r : JSON.parse(JSON.stringify(r));
102
110
  }
103
111
  function b(r, e, t) {
104
- if (A(r)) return L(m(r).arg, t);
105
- if (R(r)) return P(r, t);
106
- if (f(r)) return e.signalAt(m(r).arg);
107
- if (V(r)) return p(r, e, t);
112
+ if (A(r)) return L(d(r).arg, t);
113
+ if (E(r)) return y(r, t);
114
+ if (p(r)) return e.signalAt(d(r).arg);
115
+ if (q(r)) return h(r, e, t);
108
116
  if (Array.isArray(r)) return r.map((n) => b(n, e, t));
109
117
  if (r !== null && typeof r == "object") {
110
118
  const n = r, o = {};
111
- for (const i of Object.keys(n)) o[i] = b(n[i], e, t);
119
+ for (const s of Object.keys(n)) o[s] = b(n[s], e, t);
112
120
  return o;
113
121
  }
114
122
  return r;
@@ -119,43 +127,45 @@ function S(r, e, t) {
119
127
  for (const o of Object.keys(r)) n[o] = b(r[o], e, t);
120
128
  return n;
121
129
  }
122
- function p(r, e, t) {
123
- if ($(r)) {
124
- const n = I(e, m(r.array).arg), o = r.item.$template, i = r.initialValue;
130
+ function h(r, e, t) {
131
+ if (J(r)) {
132
+ const n = V(e, d(r.array).arg), o = r.item.$template, s = r.initialValue;
125
133
  return {
126
134
  ...r.selector ? { selector: r.selector } : {},
127
135
  array: n,
128
- initialValue: () => i ? _(i) : {},
129
- item: (y) => p(o, y, t),
136
+ initialValue: () => s ? T(s) : {},
137
+ item: (i) => h(o, i, t),
130
138
  componentProps: S(r.componentProps, e, t)
131
139
  };
132
140
  }
133
- if (E(r)) {
134
- const n = m(r.value).arg, o = e.signalAt(n);
135
- return !o && typeof console < "u" && console.warn(`[JsonRenderer/M1] No model signal for "${n}".`), {
141
+ if (M(r)) {
142
+ const n = d(r.value).arg, o = e.signalAt(n);
143
+ !o && typeof console < "u" && console.warn(`[JsonRenderer/M1] No model signal for "${n}".`);
144
+ const s = S(r.componentProps, e, t), i = r.wrapper ? y(r.wrapper.component, t) : void 0;
145
+ return {
136
146
  ...r.selector ? { selector: r.selector } : {},
137
147
  value: o,
138
- component: P(r.component, t),
139
- componentProps: S(r.componentProps, e, t)
148
+ component: y(r.component, t),
149
+ componentProps: i ? { ...s ?? {}, fieldWrapper: i } : s
140
150
  };
141
151
  }
142
- if (W(r))
152
+ if (D(r))
143
153
  return {
144
154
  ...r.selector ? { selector: r.selector } : {},
145
- component: P(r.component, t),
155
+ component: y(r.component, t),
146
156
  componentProps: S(r.componentProps, e, t),
147
- children: r.children?.map((n) => p(n, e, t))
157
+ children: r.children?.map((n) => h(n, e, t))
148
158
  };
149
159
  throw new Error(`Invalid JSON node (M1): ${JSON.stringify(r)}`);
150
160
  }
151
161
  function U(r, e, t) {
152
- return p(r.root, t, e);
162
+ return h(r.root, t, e);
153
163
  }
154
- function T(r, e, t) {
155
- return () => p(r.root, t, e);
164
+ function z(r, e, t) {
165
+ return () => h(r.root, t, e);
156
166
  }
157
- function q({ errors: r }) {
158
- return /* @__PURE__ */ M(
167
+ function I({ errors: r }) {
168
+ return /* @__PURE__ */ R(
159
169
  "div",
160
170
  {
161
171
  role: "alert",
@@ -169,12 +179,12 @@ function q({ errors: r }) {
169
179
  font: "14px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace"
170
180
  },
171
181
  children: [
172
- /* @__PURE__ */ M("strong", { style: { display: "block", marginBottom: 8, fontSize: 15 }, children: [
182
+ /* @__PURE__ */ R("strong", { style: { display: "block", marginBottom: 8, fontSize: 15 }, children: [
173
183
  "Невалидная JSON-схема формы (",
174
184
  r.length,
175
185
  ")"
176
186
  ] }),
177
- /* @__PURE__ */ u("ul", { style: { margin: 0, paddingLeft: 20 }, children: r.map((e, t) => /* @__PURE__ */ u("li", { style: { marginBottom: 4 }, children: e }, t)) })
187
+ /* @__PURE__ */ m("ul", { style: { margin: 0, paddingLeft: 20 }, children: r.map((e, t) => /* @__PURE__ */ m("li", { style: { marginBottom: 4 }, children: e }, t)) })
178
188
  ]
179
189
  }
180
190
  );
@@ -185,56 +195,60 @@ function X({
185
195
  onSchemaReady: t,
186
196
  validate: n = !1
187
197
  }) {
188
- const { registry: o, model: i, ...y } = D(), [a, d] = C(
198
+ const { registry: o, model: s, ...i } = W(), [a, g] = F(
189
199
  n ? void 0 : null
190
200
  );
191
- F(() => {
201
+ j(() => {
192
202
  if (!n) {
193
- d(null);
203
+ g(null);
194
204
  return;
195
205
  }
196
- let l = !1;
197
- return d(void 0), import("./validate.js").then(({ validateFormSchema: s }) => {
198
- if (l) return;
199
- const { valid: J, errors: x } = s(r, { registry: o });
200
- d(J ? null : x);
201
- }).catch((s) => {
202
- l || d([`Schema validator failed to load: ${String(s)}`]);
206
+ let f = !1;
207
+ return g(void 0), import("./validate.js").then(({ validateFormSchema: c }) => {
208
+ if (f) return;
209
+ const { valid: N, errors: x } = c(r, { registry: o });
210
+ g(N ? null : x);
211
+ }).catch((c) => {
212
+ f || g([`Schema validator failed to load: ${String(c)}`]);
203
213
  }), () => {
204
- l = !0;
214
+ f = !0;
205
215
  };
206
216
  }, [n, r, o]);
207
- const c = g(() => {
208
- if (!i)
217
+ const l = w(() => {
218
+ if (!s)
209
219
  throw new Error(
210
220
  "JsonFormRenderer: settings.model is required (M1). Pass the FormModel via JsonRendererProvider."
211
221
  );
212
222
  if (a !== null) return null;
213
- const l = T(r, o, i), s = j(l);
214
- return e && e(s), s;
215
- }, [r, o, e, i, a]);
216
- return g(() => {
217
- c && t && t(c);
218
- }, [c]), a && a.length > 0 ? /* @__PURE__ */ u(q, { errors: a }) : c ? /* @__PURE__ */ u(k, { render: c, settings: y }) : null;
223
+ if (!o)
224
+ throw new Error(
225
+ "JsonFormRenderer: settings.registry is required. Pass a ComponentRegistry via JsonRendererProvider."
226
+ );
227
+ const f = z(r, o, s), c = k(f);
228
+ return e && e(c), c;
229
+ }, [r, o, e, s, a]);
230
+ return w(() => {
231
+ l && t && t(l);
232
+ }, [l]), a && a.length > 0 ? /* @__PURE__ */ m(I, { errors: a }) : l ? /* @__PURE__ */ m(v, { render: l, settings: i }) : null;
219
233
  }
220
234
  export {
221
- v as FIELD_WRAPPER,
235
+ C as FIELD_WRAPPER,
222
236
  X as JsonFormRenderer,
223
237
  Q as JsonRendererProvider,
224
- q as SchemaErrorPanel,
238
+ I as SchemaErrorPanel,
225
239
  rr as buildFormSchemaMetaSchema,
226
240
  U as convertJsonToM1Tree,
227
- T as createRenderSchemaFromJsonM1,
241
+ z as createRenderSchemaFromJsonM1,
228
242
  K as defineRegistry,
229
243
  er as formSchemaMetaSchema,
230
244
  tr as getComponentNames,
231
245
  nr as getDataSourceNames,
232
- $ as isArrayNode,
233
- R as isComponentOp,
234
- W as isContainerNode,
246
+ J as isArrayNode,
247
+ E as isComponentOp,
248
+ D as isContainerNode,
235
249
  A as isDataSourceOp,
236
- E as isFieldNode,
237
- f as isModelOp,
238
- m as parseOperator,
239
- D as useJsonRendererSettings
250
+ M as isFieldNode,
251
+ p as isModelOp,
252
+ d as parseOperator,
253
+ W as useJsonRendererSettings
240
254
  };
@@ -23,11 +23,14 @@ declare const _default: {
23
23
  "description": "Registry component: \"$component(Name)\". Name is tightened to a registry enum by buildFormSchemaMetaSchema."
24
24
  },
25
25
  "node": {
26
- "oneOf": [
27
- { "$ref": "#/definitions/fieldNode" },
28
- { "$ref": "#/definitions/arrayNode" },
29
- { "$ref": "#/definitions/containerNode" }
30
- ]
26
+ "$comment": "Discriminated by which operator key is present (array→value→component), mirroring the isArrayNode/isFieldNode/isContainerNode runtime guards. Using oneOf here made ajv (allErrors) report failures against all three branches for any single mistake, burying the real cause; if/then/else surfaces only the relevant branch's errors.",
27
+ "if": { "type": "object", "required": ["array"] },
28
+ "then": { "$ref": "#/definitions/arrayNode" },
29
+ "else": {
30
+ "if": { "type": "object", "required": ["value"] },
31
+ "then": { "$ref": "#/definitions/fieldNode" },
32
+ "else": { "$ref": "#/definitions/containerNode" }
33
+ }
31
34
  },
32
35
  "fieldNode": {
33
36
  "type": "object",
@@ -38,9 +38,13 @@ export declare function getComponentNames(registry: ComponentRegistry): string[]
38
38
  */
39
39
  export declare function getDataSourceNames(registry: ComponentRegistry): string[];
40
40
  /**
41
- * Конкретная мета-схема: базовая + (если заданы `componentNames`) сужение паттерна `$component(...)`
42
- * до enum имён (`^\$component\((Input|Select|…)\)$`). `$dataSource`-имена JSON Schema'й не покрыть
43
- * (вложены в произвольный componentProps) — их проверяет рекурсивный обход в `validateFormSchema`.
41
+ * Конкретная мета-схема: базовая + (если заданы `componentNames`) сужение `$component(...)` до
42
+ * enum допустимых значений (`["$component(Input)", "$component(Select)", …]`). `$dataSource`-имена
43
+ * JSON Schema'й не покрыть (вложены в произвольный componentProps) — их проверяет рекурсивный обход
44
+ * в `validateFormSchema`.
45
+ *
46
+ * enum, а не regex-`pattern`: ajv перечисляет допустимые имена в тексте ошибки, IDE даёт
47
+ * автодополнение по значениям, а имена не нужно экранировать под regex (напр. `$fieldWrapper`).
44
48
  *
45
49
  * @example
46
50
  * ```ts