@reformer/renderer-json 1.0.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/json-form-renderer.d.ts +66 -0
- package/dist/context/json-renderer-context.d.ts +51 -0
- package/dist/converter/json-to-render-schema.d.ts +31 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +212 -0
- package/dist/registry/component-registry.d.ts +39 -0
- package/dist/registry/constants.d.ts +17 -0
- package/dist/registry/types.d.ts +54 -0
- package/dist/types/json-schema.d.ts +125 -0
- package/package.json +82 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { RenderBehaviorFn, RenderSchemaProxy } from '@reformer/renderer-react';
|
|
3
|
+
import { JsonFormSchema } from '../types/json-schema';
|
|
4
|
+
/**
|
|
5
|
+
* Props of {@link JsonFormRenderer}.
|
|
6
|
+
*
|
|
7
|
+
* @typeParam T - Тип формы (`getReformerForm<T>()`).
|
|
8
|
+
*/
|
|
9
|
+
export interface JsonFormRendererProps<T> {
|
|
10
|
+
/** JSON-схема формы. См. {@link JsonFormSchema}. */
|
|
11
|
+
schema: JsonFormSchema;
|
|
12
|
+
/** Опциональный behavior: hideWhen/patchProps/onComponentEvent поверх готовой схемы. */
|
|
13
|
+
renderBehavior?: RenderBehaviorFn<T>;
|
|
14
|
+
/** Колбэк, получающий построенный `RenderSchemaProxy` для внешних манипуляций. */
|
|
15
|
+
onSchemaReady?: (schema: RenderSchemaProxy<T>) => void;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Главный компонент пакета. Рендерит форму, описанную JSON-схемой.
|
|
19
|
+
*
|
|
20
|
+
* Должен использоваться внутри {@link JsonRendererProvider}, который снабжает рендерер
|
|
21
|
+
* реестром компонентов. Без реестра компонент бросит исключение при попытке резолва.
|
|
22
|
+
*
|
|
23
|
+
* @typeParam T - Тип формы.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```tsx
|
|
27
|
+
* import { useMemo } from 'react';
|
|
28
|
+
* import { getReformerForm } from '@reformer/core';
|
|
29
|
+
* import { Input, FormField } from '@reformer/ui-kit';
|
|
30
|
+
* import {
|
|
31
|
+
* JsonFormRenderer,
|
|
32
|
+
* JsonRendererProvider,
|
|
33
|
+
* defineRegistry,
|
|
34
|
+
* FIELD_WRAPPER,
|
|
35
|
+
* type JsonFormSchema,
|
|
36
|
+
* } from '@reformer/renderer-json';
|
|
37
|
+
*
|
|
38
|
+
* const schema: JsonFormSchema = {
|
|
39
|
+
* version: '1.0',
|
|
40
|
+
* root: {
|
|
41
|
+
* component: 'Box',
|
|
42
|
+
* children: [
|
|
43
|
+
* { selector: 'email', model: 'email', component: 'Input' },
|
|
44
|
+
* ],
|
|
45
|
+
* },
|
|
46
|
+
* };
|
|
47
|
+
*
|
|
48
|
+
* const registry = defineRegistry((reg) => {
|
|
49
|
+
* reg.field('Input', Input);
|
|
50
|
+
* reg.container('Box', ({ children }) => <div>{children}</div>);
|
|
51
|
+
* reg.container(FIELD_WRAPPER, FormField);
|
|
52
|
+
* });
|
|
53
|
+
*
|
|
54
|
+
* function MyForm() {
|
|
55
|
+
* const form = useMemo(() => getReformerForm({ email: '' }), []);
|
|
56
|
+
* return (
|
|
57
|
+
* <JsonRendererProvider settings={{ registry }}>
|
|
58
|
+
* <JsonFormRenderer schema={schema} form={form} />
|
|
59
|
+
* </JsonRendererProvider>
|
|
60
|
+
* );
|
|
61
|
+
* }
|
|
62
|
+
* ```
|
|
63
|
+
*
|
|
64
|
+
* @see [docs/llms/01-overview.md](../../docs/llms/01-overview.md)
|
|
65
|
+
*/
|
|
66
|
+
export declare function JsonFormRenderer<T>({ schema, renderBehavior, onSchemaReady, }: JsonFormRendererProps<T>): ReactNode;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { RendererSettings } from '@reformer/renderer-react';
|
|
3
|
+
import { ComponentRegistry } from '../registry/types';
|
|
4
|
+
/**
|
|
5
|
+
* Расширенные настройки рендерера: всё из `RendererSettings` плюс реестр.
|
|
6
|
+
*/
|
|
7
|
+
export interface JsonRendererSettings extends RendererSettings {
|
|
8
|
+
/** Реестр компонентов и source-значений. См. {@link defineRegistry}. */
|
|
9
|
+
registry?: ComponentRegistry;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Props {@link JsonRendererProvider}.
|
|
13
|
+
*/
|
|
14
|
+
export interface JsonRendererProviderProps {
|
|
15
|
+
/** Настройки рендерера, как минимум содержащие `registry`. */
|
|
16
|
+
settings: JsonRendererSettings;
|
|
17
|
+
/** Дочернее поддерево, в котором доступен `JsonFormRenderer` и `useJsonRendererSettings`. */
|
|
18
|
+
children: ReactNode;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Провайдер настроек для {@link JsonFormRenderer}. Прокидывает реестр и
|
|
22
|
+
* fieldWrapper во вложенные компоненты через React Context.
|
|
23
|
+
*
|
|
24
|
+
* Поддерживает вложенность: внутренний провайдер сливается с внешним
|
|
25
|
+
* (внешний имеет приоритет в случае дублей имён в реестре).
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```tsx
|
|
29
|
+
* <JsonRendererProvider settings={{ registry, fieldWrapper: FormField }}>
|
|
30
|
+
* <JsonFormRenderer schema={schema} form={form} />
|
|
31
|
+
* </JsonRendererProvider>
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export declare function JsonRendererProvider({ settings, children }: JsonRendererProviderProps): ReactNode;
|
|
35
|
+
/**
|
|
36
|
+
* Хук для чтения текущих настроек {@link JsonRendererProvider}.
|
|
37
|
+
*
|
|
38
|
+
* В режиме разработки бросает исключение, если вызван вне провайдера или
|
|
39
|
+
* если в провайдере не передан `registry`.
|
|
40
|
+
*
|
|
41
|
+
* @returns Текущие {@link JsonRendererSettings}.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```tsx
|
|
45
|
+
* function MyControl() {
|
|
46
|
+
* const { registry } = useJsonRendererSettings();
|
|
47
|
+
* return <span>{registry?.has('Input') ? 'ready' : 'not registered'}</span>;
|
|
48
|
+
* }
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export declare function useJsonRendererSettings(): JsonRendererSettings;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { RenderSchemaFn } from '@reformer/renderer-react';
|
|
2
|
+
import { JsonFormSchema } from '../types/json-schema';
|
|
3
|
+
import { ComponentRegistry } from '../registry/types';
|
|
4
|
+
/**
|
|
5
|
+
* Создаёт RenderSchemaFn из JSON-схемы
|
|
6
|
+
*
|
|
7
|
+
* Конвертирует JSON-схему в функцию RenderSchemaFn, которую можно
|
|
8
|
+
* передать в FormRenderer из @reformer/renderer-react.
|
|
9
|
+
*
|
|
10
|
+
* @param schema - JSON-схема формы
|
|
11
|
+
* @param registry - Реестр компонентов для резолва имён
|
|
12
|
+
* @returns RenderSchemaFn для FormRenderer
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* const jsonSchema = {
|
|
17
|
+
* root: {
|
|
18
|
+
* component: 'Box',
|
|
19
|
+
* children: [
|
|
20
|
+
* { model: 'email' },
|
|
21
|
+
* { model: 'password', component: 'InputPassword' }
|
|
22
|
+
* ]
|
|
23
|
+
* }
|
|
24
|
+
* };
|
|
25
|
+
*
|
|
26
|
+
* const renderSchema = createRenderSchemaFromJson(jsonSchema, registry);
|
|
27
|
+
*
|
|
28
|
+
* <FormRenderer render={renderSchema} settings={{ fieldWrapper: FormField }} />
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export declare function createRenderSchemaFromJson<T>(schema: JsonFormSchema, registry: ComponentRegistry): RenderSchemaFn<T>;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @reformer/renderer-json
|
|
3
|
+
*
|
|
4
|
+
* JSON-based form renderer for @reformer ecosystem
|
|
5
|
+
*
|
|
6
|
+
* @packageDocumentation
|
|
7
|
+
*/
|
|
8
|
+
export { JsonFormRenderer } from './components/json-form-renderer';
|
|
9
|
+
export type { JsonFormRendererProps } from './components/json-form-renderer';
|
|
10
|
+
export type { JsonFormSchema, JsonNode } from './types/json-schema';
|
|
11
|
+
export { isFieldNode, isContainerNode } from './types/json-schema';
|
|
12
|
+
export { defineRegistry } from './registry/component-registry';
|
|
13
|
+
export { FIELD_WRAPPER } from './registry/constants';
|
|
14
|
+
export type { ComponentRegistry, ComponentMetadata, RegistryBuilder } from './registry/types';
|
|
15
|
+
export { JsonRendererProvider, useJsonRendererSettings } from './context/json-renderer-context';
|
|
16
|
+
export type { JsonRendererSettings, JsonRendererProviderProps, } from './context/json-renderer-context';
|
|
17
|
+
export { createRenderSchemaFromJson } from './converter/json-to-render-schema';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { jsx as y } from "react/jsx-runtime";
|
|
2
|
+
import { useContext as g, createContext as w, useMemo as m, createElement as P } from "react";
|
|
3
|
+
import { RenderNodeComponent as R, createRenderSchema as C, FormRenderer as F } from "@reformer/renderer-react";
|
|
4
|
+
import { createFieldPath as $ } from "@reformer/core";
|
|
5
|
+
class u {
|
|
6
|
+
own = /* @__PURE__ */ new Map();
|
|
7
|
+
parent = null;
|
|
8
|
+
get(e) {
|
|
9
|
+
return this.own.get(e) ?? this.parent?.get(e);
|
|
10
|
+
}
|
|
11
|
+
getSource(e) {
|
|
12
|
+
const r = this.get(e);
|
|
13
|
+
if (!(!r || r.type !== "source"))
|
|
14
|
+
return r.component;
|
|
15
|
+
}
|
|
16
|
+
has(e) {
|
|
17
|
+
return this.own.has(e) || (this.parent?.has(e) ?? !1);
|
|
18
|
+
}
|
|
19
|
+
names() {
|
|
20
|
+
const e = this.parent?.names() ?? [], r = Array.from(this.own.keys());
|
|
21
|
+
return [.../* @__PURE__ */ new Set([...e, ...r])];
|
|
22
|
+
}
|
|
23
|
+
_set(e, r) {
|
|
24
|
+
this.own.has(e) && console.warn(`[ComponentRegistry] Overwriting entry: ${e}`), this.own.set(e, r);
|
|
25
|
+
}
|
|
26
|
+
static withParent(e, r) {
|
|
27
|
+
const n = new u();
|
|
28
|
+
return n.parent = e, r.own.forEach((o, i) => {
|
|
29
|
+
n.own.set(i, o);
|
|
30
|
+
}), n;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function _(t) {
|
|
34
|
+
const e = new u();
|
|
35
|
+
return t({
|
|
36
|
+
field(n, o, i) {
|
|
37
|
+
e._set(n, { component: o, type: "field", description: i });
|
|
38
|
+
},
|
|
39
|
+
container(n, o, i) {
|
|
40
|
+
e._set(n, { component: o, type: "container", description: i });
|
|
41
|
+
},
|
|
42
|
+
source(n, o, i) {
|
|
43
|
+
e._set(n, { component: o, type: "source", description: i });
|
|
44
|
+
}
|
|
45
|
+
}), e;
|
|
46
|
+
}
|
|
47
|
+
const b = "$fieldWrapper", l = w({});
|
|
48
|
+
function M({ settings: t, children: e }) {
|
|
49
|
+
const r = g(l), n = m(() => r.registry && t.registry ? u.withParent(r.registry, t.registry) : t.registry ?? r.registry, [r.registry, t.registry]), o = m(() => {
|
|
50
|
+
const i = n?.get(b)?.component;
|
|
51
|
+
return {
|
|
52
|
+
...r,
|
|
53
|
+
...t,
|
|
54
|
+
registry: n,
|
|
55
|
+
fieldWrapper: t.fieldWrapper ?? i ?? r.fieldWrapper
|
|
56
|
+
};
|
|
57
|
+
}, [r, t, n]);
|
|
58
|
+
return /* @__PURE__ */ y(l.Provider, { value: o, children: e });
|
|
59
|
+
}
|
|
60
|
+
function E() {
|
|
61
|
+
return g(l);
|
|
62
|
+
}
|
|
63
|
+
function h(t, e) {
|
|
64
|
+
const r = e.split(/\.|\[|\]/).filter(Boolean);
|
|
65
|
+
let n = t;
|
|
66
|
+
for (const o of r) {
|
|
67
|
+
if (n == null)
|
|
68
|
+
throw new Error(`Invalid field path: "${e}" - segment "${o}" not found`);
|
|
69
|
+
const i = parseInt(o, 10);
|
|
70
|
+
isNaN(i) ? n = n[o] : n = n[i];
|
|
71
|
+
}
|
|
72
|
+
if (n == null)
|
|
73
|
+
throw new Error(`Invalid field path: "${e}" - path resolved to null/undefined`);
|
|
74
|
+
return n;
|
|
75
|
+
}
|
|
76
|
+
function p(t) {
|
|
77
|
+
if (t === null || typeof t != "object") return !1;
|
|
78
|
+
const e = t;
|
|
79
|
+
return typeof e.model == "string" || typeof e.component == "string";
|
|
80
|
+
}
|
|
81
|
+
function S(t) {
|
|
82
|
+
return t !== null && typeof t == "object" && p(t.$template);
|
|
83
|
+
}
|
|
84
|
+
function x(t, e) {
|
|
85
|
+
const r = $(), n = s(t, r, e), o = ({ control: i }) => P(R, { node: n, form: i });
|
|
86
|
+
return o.displayName = "TemplateItemFC", o;
|
|
87
|
+
}
|
|
88
|
+
function a(t, e, r) {
|
|
89
|
+
if (typeof t == "string") {
|
|
90
|
+
const n = r.get(t);
|
|
91
|
+
if (n && n.type === "source") {
|
|
92
|
+
const o = n.component;
|
|
93
|
+
return typeof o == "function" ? (...i) => {
|
|
94
|
+
const c = o(...i);
|
|
95
|
+
return p(c) ? s(c, i[0], r) : c;
|
|
96
|
+
} : o;
|
|
97
|
+
}
|
|
98
|
+
return t;
|
|
99
|
+
}
|
|
100
|
+
if (S(t))
|
|
101
|
+
return x(t.$template, r);
|
|
102
|
+
if (p(t))
|
|
103
|
+
return s(t, e, r);
|
|
104
|
+
if (Array.isArray(t))
|
|
105
|
+
return t.map((n) => a(n, e, r));
|
|
106
|
+
if (typeof t == "function")
|
|
107
|
+
return (...n) => {
|
|
108
|
+
const o = t(...n);
|
|
109
|
+
return p(o) ? s(o, n[0], r) : o;
|
|
110
|
+
};
|
|
111
|
+
if (t !== null && typeof t == "object") {
|
|
112
|
+
const n = t, o = {};
|
|
113
|
+
let i = !1;
|
|
114
|
+
for (const c of Object.keys(n)) {
|
|
115
|
+
const f = a(n[c], e, r);
|
|
116
|
+
f !== n[c] && (i = !0), o[c] = f;
|
|
117
|
+
}
|
|
118
|
+
return i ? o : t;
|
|
119
|
+
}
|
|
120
|
+
return t;
|
|
121
|
+
}
|
|
122
|
+
function d(t, e, r) {
|
|
123
|
+
const n = {};
|
|
124
|
+
for (const o of Object.keys(t)) {
|
|
125
|
+
const i = t[o];
|
|
126
|
+
if ((o === "control" || o.endsWith("Control")) && typeof i == "string") {
|
|
127
|
+
n[o] = h(e, i);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if ((o === "component" || o.endsWith("Component")) && typeof i == "string") {
|
|
131
|
+
const c = r.get(i);
|
|
132
|
+
if (c) {
|
|
133
|
+
n[o] = c.component;
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
n[o] = a(i, e, r);
|
|
138
|
+
}
|
|
139
|
+
return n;
|
|
140
|
+
}
|
|
141
|
+
function I(t, e) {
|
|
142
|
+
return typeof t.model == "string" && t.model.length > 0 ? t.model : typeof t.selector == "string" && typeof t.component == "string" && !t.children && e.get(t.component)?.type === "field" ? t.selector : null;
|
|
143
|
+
}
|
|
144
|
+
function s(t, e, r) {
|
|
145
|
+
const n = I(t, r);
|
|
146
|
+
if (n !== null) {
|
|
147
|
+
const i = {
|
|
148
|
+
component: h(e, n)
|
|
149
|
+
};
|
|
150
|
+
return t.selector && (i.selector = t.selector), t.componentProps && (i.componentProps = d(
|
|
151
|
+
t.componentProps,
|
|
152
|
+
e,
|
|
153
|
+
r
|
|
154
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
155
|
+
)), i;
|
|
156
|
+
}
|
|
157
|
+
if (t.component) {
|
|
158
|
+
const o = r.get(t.component);
|
|
159
|
+
if (!o)
|
|
160
|
+
throw new Error(
|
|
161
|
+
`Component "${t.component}" not found in registry. Available components: ${r.names().join(", ")}`
|
|
162
|
+
);
|
|
163
|
+
if (o.type === "source")
|
|
164
|
+
throw new Error(
|
|
165
|
+
`Entry "${t.component}" is a 'source' and cannot be used as component`
|
|
166
|
+
);
|
|
167
|
+
const i = {
|
|
168
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
169
|
+
component: o.component
|
|
170
|
+
};
|
|
171
|
+
return t.selector && (i.selector = t.selector), t.componentProps && (i.componentProps = d(
|
|
172
|
+
t.componentProps,
|
|
173
|
+
e,
|
|
174
|
+
r
|
|
175
|
+
)), t.children && t.children.length > 0 && (i.children = t.children.map((c) => s(c, e, r))), i;
|
|
176
|
+
}
|
|
177
|
+
throw new Error(
|
|
178
|
+
`Invalid JSON node: must have either "model" (for fields) or "component" (for containers). Got: ${JSON.stringify(t)}`
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
function J(t, e) {
|
|
182
|
+
return (r) => s(t.root, r, e);
|
|
183
|
+
}
|
|
184
|
+
function L({
|
|
185
|
+
schema: t,
|
|
186
|
+
renderBehavior: e,
|
|
187
|
+
onSchemaReady: r
|
|
188
|
+
}) {
|
|
189
|
+
const { registry: n, ...o } = E(), i = m(() => {
|
|
190
|
+
const c = J(t, n), f = C(c);
|
|
191
|
+
return e && e(f), f;
|
|
192
|
+
}, [t, n, e]);
|
|
193
|
+
return m(() => {
|
|
194
|
+
r && r(i);
|
|
195
|
+
}, [i]), /* @__PURE__ */ y(F, { render: i, settings: o });
|
|
196
|
+
}
|
|
197
|
+
function W(t) {
|
|
198
|
+
return typeof t.model == "string" && t.model.length > 0;
|
|
199
|
+
}
|
|
200
|
+
function V(t) {
|
|
201
|
+
return typeof t.component == "string" && !W(t);
|
|
202
|
+
}
|
|
203
|
+
export {
|
|
204
|
+
b as FIELD_WRAPPER,
|
|
205
|
+
L as JsonFormRenderer,
|
|
206
|
+
M as JsonRendererProvider,
|
|
207
|
+
J as createRenderSchemaFromJson,
|
|
208
|
+
_ as defineRegistry,
|
|
209
|
+
V as isContainerNode,
|
|
210
|
+
W as isFieldNode,
|
|
211
|
+
E as useJsonRendererSettings
|
|
212
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { ComponentRegistry, ComponentMetadata, RegistryBuilder } from './types';
|
|
2
|
+
export declare class ComponentRegistryImpl implements ComponentRegistry {
|
|
3
|
+
private own;
|
|
4
|
+
private parent;
|
|
5
|
+
get(name: string): ComponentMetadata | undefined;
|
|
6
|
+
getSource<T = unknown>(name: string): T | undefined;
|
|
7
|
+
has(name: string): boolean;
|
|
8
|
+
names(): string[];
|
|
9
|
+
_set(name: string, metadata: ComponentMetadata): void;
|
|
10
|
+
static withParent(parent: ComponentRegistry, child: ComponentRegistry): ComponentRegistry;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Создаёт реестр компонентов через builder-callback.
|
|
14
|
+
*
|
|
15
|
+
* Реестр обязателен для работы {@link JsonFormRenderer} — иначе компоненты,
|
|
16
|
+
* упомянутые в JSON-схеме, не отрезолвятся.
|
|
17
|
+
*
|
|
18
|
+
* @param fn - Builder-callback. Получает {@link RegistryBuilder} с методами `field`, `container`, `source`.
|
|
19
|
+
* @returns Готовый {@link ComponentRegistry}, который кладётся в `JsonRendererProvider`.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* import { defineRegistry, FIELD_WRAPPER } from '@reformer/renderer-json';
|
|
24
|
+
* import { Input, Select, FormField } from '@reformer/ui-kit';
|
|
25
|
+
*
|
|
26
|
+
* const registry = defineRegistry((reg) => {
|
|
27
|
+
* reg.field('Input', Input);
|
|
28
|
+
* reg.field('Select', Select);
|
|
29
|
+
* reg.container(FIELD_WRAPPER, FormField);
|
|
30
|
+
* reg.source('LOAN_TYPES', [
|
|
31
|
+
* { value: 'consumer', label: 'Потребительский' },
|
|
32
|
+
* { value: 'mortgage', label: 'Ипотека' },
|
|
33
|
+
* ]);
|
|
34
|
+
* });
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* @see [docs/llms/03-registry.md](../../docs/llms/03-registry.md)
|
|
38
|
+
*/
|
|
39
|
+
export declare function defineRegistry(fn: (reg: RegistryBuilder) => void): ComponentRegistry;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Зарезервированный ключ реестра для контейнера-обёртки полей.
|
|
3
|
+
*
|
|
4
|
+
* Зарегистрируй компонент под этим именем (обычно `FormField` из `@reformer/ui-kit`),
|
|
5
|
+
* чтобы каждое поле получало label, error и hint автоматически.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { defineRegistry, FIELD_WRAPPER } from '@reformer/renderer-json';
|
|
10
|
+
* import { FormField } from '@reformer/ui-kit';
|
|
11
|
+
*
|
|
12
|
+
* const registry = defineRegistry((reg) => {
|
|
13
|
+
* reg.container(FIELD_WRAPPER, FormField);
|
|
14
|
+
* });
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export declare const FIELD_WRAPPER = "$fieldWrapper";
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { ComponentType } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Запись реестра. Хранит сам компонент (или source-значение) и его роль.
|
|
4
|
+
*
|
|
5
|
+
* @typeParam P - Пропсы компонента (для `field`/`container`).
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* const meta: ComponentMetadata = registry.get('Input')!;
|
|
10
|
+
* meta.type; // 'field'
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export interface ComponentMetadata<P = any> {
|
|
14
|
+
component: ComponentType<P> | unknown;
|
|
15
|
+
type: 'field' | 'container' | 'source';
|
|
16
|
+
description?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Read-only API реестра, доступное в рантайме.
|
|
20
|
+
*
|
|
21
|
+
* Создаётся через {@link defineRegistry}. Передаётся в `JsonRendererProvider`
|
|
22
|
+
* через `settings.registry`.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* if (registry.has('Input')) {
|
|
27
|
+
* registry.get('Input'); // ComponentMetadata
|
|
28
|
+
* }
|
|
29
|
+
* registry.names(); // ['Input', 'Box', 'LOAN_TYPES', ...]
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export interface ComponentRegistry {
|
|
33
|
+
get(name: string): ComponentMetadata | undefined;
|
|
34
|
+
getSource<T = unknown>(name: string): T | undefined;
|
|
35
|
+
has(name: string): boolean;
|
|
36
|
+
names(): string[];
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Builder, который попадает в callback {@link defineRegistry}.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```typescript
|
|
43
|
+
* defineRegistry((reg: RegistryBuilder) => {
|
|
44
|
+
* reg.field('Input', Input);
|
|
45
|
+
* reg.container('Box', Box);
|
|
46
|
+
* reg.source('LOAN_TYPES', LOAN_TYPES);
|
|
47
|
+
* });
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export interface RegistryBuilder {
|
|
51
|
+
field<P>(name: string, component: ComponentType<P>, description?: string): void;
|
|
52
|
+
container<P>(name: string, component: ComponentType<P>, description?: string): void;
|
|
53
|
+
source<T>(name: string, value: T, description?: string): void;
|
|
54
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Типы для JSON-схемы формы
|
|
3
|
+
*
|
|
4
|
+
* @module reformer/renderer-json/types
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Унифицированный узел JSON-схемы
|
|
8
|
+
*
|
|
9
|
+
* Может представлять:
|
|
10
|
+
* - Поле формы (если указан `model`)
|
|
11
|
+
* - Контейнер/компонент (если указан только `component`)
|
|
12
|
+
* - Поле с кастомным компонентом (если указаны оба)
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```json
|
|
16
|
+
* // Поле формы с дефолтным компонентом
|
|
17
|
+
* { "model": "email" }
|
|
18
|
+
*
|
|
19
|
+
* // Поле с кастомным компонентом
|
|
20
|
+
* { "model": "password", "component": "InputPassword" }
|
|
21
|
+
*
|
|
22
|
+
* // Контейнер
|
|
23
|
+
* { "component": "Box", "children": [...] }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export interface JsonNode {
|
|
27
|
+
/**
|
|
28
|
+
* Идентификатор узла для renderBehavior (hideWhen, patchProps).
|
|
29
|
+
* Необязателен — нужен только если к узлу привязано поведение.
|
|
30
|
+
*/
|
|
31
|
+
selector?: string;
|
|
32
|
+
/**
|
|
33
|
+
* Имя компонента из реестра: "Input", "Box", "Section", "Select"
|
|
34
|
+
*
|
|
35
|
+
* - Для полей: компонент для рендеринга значения (опционально, по умолчанию Input)
|
|
36
|
+
* - Для контейнеров: React-компонент контейнера (обязательно)
|
|
37
|
+
*/
|
|
38
|
+
component?: string;
|
|
39
|
+
/**
|
|
40
|
+
* Путь к полю формы (модели): "email", "personalData.firstName", "addresses[0].city"
|
|
41
|
+
*
|
|
42
|
+
* Если указан — узел представляет поле формы.
|
|
43
|
+
*/
|
|
44
|
+
model?: string;
|
|
45
|
+
/**
|
|
46
|
+
* Props для компонента (className, title и т.д.)
|
|
47
|
+
*/
|
|
48
|
+
componentProps?: Record<string, unknown>;
|
|
49
|
+
/**
|
|
50
|
+
* Дочерние узлы (для контейнеров)
|
|
51
|
+
*/
|
|
52
|
+
children?: JsonNode[];
|
|
53
|
+
/**
|
|
54
|
+
* Обёртка для узла (например, fieldWrapper)
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```json
|
|
58
|
+
* {
|
|
59
|
+
* "model": "email",
|
|
60
|
+
* "wrapper": {
|
|
61
|
+
* "component": "FormField",
|
|
62
|
+
* "componentProps": { "className": "col-span-2" }
|
|
63
|
+
* }
|
|
64
|
+
* }
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
wrapper?: JsonNode;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Корневая JSON-схема формы
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```json
|
|
74
|
+
* {
|
|
75
|
+
* "version": "1.0",
|
|
76
|
+
* "root": {
|
|
77
|
+
* "component": "Box",
|
|
78
|
+
* "componentProps": { "className": "space-y-4" },
|
|
79
|
+
* "children": [
|
|
80
|
+
* { "model": "email" },
|
|
81
|
+
* { "model": "password", "component": "InputPassword" }
|
|
82
|
+
* ]
|
|
83
|
+
* }
|
|
84
|
+
* }
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
export interface JsonFormSchema {
|
|
88
|
+
/**
|
|
89
|
+
* Версия схемы (для миграций)
|
|
90
|
+
*/
|
|
91
|
+
version?: string;
|
|
92
|
+
/**
|
|
93
|
+
* Корневой узел схемы
|
|
94
|
+
*/
|
|
95
|
+
root: JsonNode;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Type guard: проверяет, является ли узел полем формы (имеет `model`).
|
|
99
|
+
*
|
|
100
|
+
* @param node - Узел JSON-схемы.
|
|
101
|
+
* @returns `true`, если у узла есть непустое `model`.
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```typescript
|
|
105
|
+
* if (isFieldNode(node)) {
|
|
106
|
+
* // node.model гарантированно строка
|
|
107
|
+
* readField(node.model);
|
|
108
|
+
* }
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
export declare function isFieldNode(node: JsonNode): boolean;
|
|
112
|
+
/**
|
|
113
|
+
* Type guard: проверяет, является ли узел контейнером (только `component`, без `model`).
|
|
114
|
+
*
|
|
115
|
+
* @param node - Узел JSON-схемы.
|
|
116
|
+
* @returns `true`, если у узла есть `component`, но нет `model`.
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* ```typescript
|
|
120
|
+
* if (isContainerNode(node)) {
|
|
121
|
+
* node.children?.forEach(walk);
|
|
122
|
+
* }
|
|
123
|
+
* ```
|
|
124
|
+
*/
|
|
125
|
+
export declare function isContainerNode(node: JsonNode): boolean;
|
package/package.json
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@reformer/renderer-json",
|
|
3
|
+
"version": "1.0.0-beta.3",
|
|
4
|
+
"description": "JSON-based form renderer for @reformer ecosystem",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"sideEffects": false,
|
|
16
|
+
"scripts": {
|
|
17
|
+
"generate:llms": "node ../../scripts/generate-llms-txt .",
|
|
18
|
+
"build": "npm run generate:llms && vite build",
|
|
19
|
+
"build:stackblitz": "vite build",
|
|
20
|
+
"dev": "vite",
|
|
21
|
+
"test": "node ../../scripts/run-vitest.mjs",
|
|
22
|
+
"test:watch": "vitest watch"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"react",
|
|
26
|
+
"forms",
|
|
27
|
+
"reformer",
|
|
28
|
+
"renderer",
|
|
29
|
+
"json",
|
|
30
|
+
"typescript"
|
|
31
|
+
],
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=18.0.0"
|
|
34
|
+
},
|
|
35
|
+
"author": "Alexandr Bukhtatyy",
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "https://github.com/AlexandrBukhtatyy/ReFormer.git",
|
|
40
|
+
"directory": "packages/reformer-renderer-json"
|
|
41
|
+
},
|
|
42
|
+
"homepage": "https://alexandrbukhtatyy.github.io/ReFormer/",
|
|
43
|
+
"bugs": {
|
|
44
|
+
"url": "https://github.com/AlexandrBukhtatyy/ReFormer/issues"
|
|
45
|
+
},
|
|
46
|
+
"publishConfig": {
|
|
47
|
+
"access": "public"
|
|
48
|
+
},
|
|
49
|
+
"files": [
|
|
50
|
+
"dist",
|
|
51
|
+
"README.md",
|
|
52
|
+
"LICENSE"
|
|
53
|
+
],
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"@reformer/core": ">=1.1.0-beta.0",
|
|
56
|
+
"@reformer/renderer-react": ">=1.0.0-beta.0",
|
|
57
|
+
"@reformer/ui-kit": ">=1.0.0-beta.0",
|
|
58
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
59
|
+
"react-dom": "^18.0.0 || ^19.0.0"
|
|
60
|
+
},
|
|
61
|
+
"peerDependenciesMeta": {
|
|
62
|
+
"@reformer/ui-kit": {
|
|
63
|
+
"optional": true
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
"devDependencies": {
|
|
67
|
+
"@reformer/core": "*",
|
|
68
|
+
"@reformer/renderer-react": "*",
|
|
69
|
+
"@reformer/ui-kit": "*",
|
|
70
|
+
"@types/node": "^24.10.1",
|
|
71
|
+
"@types/react": "^19.2.7",
|
|
72
|
+
"@types/react-dom": "^19.2.3",
|
|
73
|
+
"@vitejs/plugin-react": "^5.1.0",
|
|
74
|
+
"@vitest/utils": "^4.0.8",
|
|
75
|
+
"react": "^19.2.1",
|
|
76
|
+
"react-dom": "^19.2.1",
|
|
77
|
+
"typescript": "^5.9.3",
|
|
78
|
+
"vite": "^7.2.2",
|
|
79
|
+
"vite-plugin-dts": "^4.5.4",
|
|
80
|
+
"vitest": "^4.0.8"
|
|
81
|
+
}
|
|
82
|
+
}
|