@reformer/renderer-json 6.0.0 → 7.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/dist/components/json-form-renderer.d.ts +20 -20
- package/dist/components/schema-error-panel.d.ts +18 -1
- package/dist/context/json-renderer-context.d.ts +10 -3
- package/dist/converter/json-to-render-schema.d.ts +40 -1
- package/dist/{index-CI7YxPq2.js → index-BZLcW0SX.js} +2 -5
- package/dist/index.d.ts +1 -1
- package/dist/index.js +31 -34
- package/dist/operators.d.ts +44 -5
- package/dist/registry/component-registry.d.ts +6 -6
- package/dist/registry/constants.d.ts +1 -1
- package/dist/registry/types.d.ts +16 -10
- package/dist/schema/index.d.ts +36 -4
- package/dist/types/json-schema.d.ts +43 -3
- package/dist/validate.d.ts +8 -2
- package/dist/validate.js +2 -2
- package/package.json +1 -1
|
@@ -32,11 +32,11 @@ export interface JsonFormRendererProps<T> {
|
|
|
32
32
|
*
|
|
33
33
|
* @typeParam T - Тип формы.
|
|
34
34
|
*
|
|
35
|
-
* @example
|
|
35
|
+
* @example Форма из JSON-схемы (M1)
|
|
36
36
|
* ```tsx
|
|
37
37
|
* import { useMemo } from 'react';
|
|
38
|
-
* import {
|
|
39
|
-
* import { Input, FormField } from '@reformer/ui-kit';
|
|
38
|
+
* import { createModel } from '@reformer/core';
|
|
39
|
+
* import { Input, Box, FormField } from '@reformer/ui-kit';
|
|
40
40
|
* import {
|
|
41
41
|
* JsonFormRenderer,
|
|
42
42
|
* JsonRendererProvider,
|
|
@@ -45,12 +45,17 @@ export interface JsonFormRendererProps<T> {
|
|
|
45
45
|
* type JsonFormSchema,
|
|
46
46
|
* } from '@reformer/renderer-json';
|
|
47
47
|
*
|
|
48
|
+
* // Привязки — строки-операторы: '$model(...)', '$component(...)', '$dataSource(...)'.
|
|
48
49
|
* const schema: JsonFormSchema = {
|
|
49
50
|
* version: '1.0',
|
|
50
51
|
* root: {
|
|
51
|
-
* component: 'Box',
|
|
52
|
+
* component: '$component(Box)',
|
|
52
53
|
* children: [
|
|
53
|
-
* {
|
|
54
|
+
* {
|
|
55
|
+
* value: '$model(email)',
|
|
56
|
+
* component: '$component(Input)',
|
|
57
|
+
* componentProps: { label: 'Email' },
|
|
58
|
+
* },
|
|
54
59
|
* ],
|
|
55
60
|
* },
|
|
56
61
|
* };
|
|
@@ -58,31 +63,26 @@ export interface JsonFormRendererProps<T> {
|
|
|
58
63
|
* type MyForm = { email: string };
|
|
59
64
|
*
|
|
60
65
|
* function MyFormPage() {
|
|
61
|
-
* //
|
|
62
|
-
* const
|
|
63
|
-
* form: { email: { value: '', component: Input, componentProps: { label: 'Email' } } },
|
|
64
|
-
* }), []);
|
|
65
|
-
*
|
|
66
|
-
* // registry с FormRoot-closure: компоненты, использующие форму, получают её
|
|
67
|
-
* // через componentProps closure (JsonFormRenderer НЕ имеет form-prop'а — это by-design,
|
|
68
|
-
* // т.к. JSON-схема статична, а форма runtime).
|
|
66
|
+
* // M1: модель — источник истины значений; листья схемы биндятся к её сигналам.
|
|
67
|
+
* const model = useMemo(() => createModel<MyForm>({ email: '' }), []);
|
|
69
68
|
* const registry = useMemo(() => defineRegistry((reg) => {
|
|
70
|
-
* reg.
|
|
71
|
-
* reg.
|
|
72
|
-
* reg.
|
|
69
|
+
* reg.component('Input', Input);
|
|
70
|
+
* reg.component('Box', Box);
|
|
71
|
+
* reg.component(FIELD_WRAPPER, FormField); // системная обёртка полей
|
|
73
72
|
* }), []);
|
|
74
73
|
*
|
|
74
|
+
* // Модель передаётся через провайдер (settings.model), НЕ пропом рендерера.
|
|
75
75
|
* return (
|
|
76
|
-
* <JsonRendererProvider settings={{ registry,
|
|
77
|
-
* <JsonFormRenderer schema={schema} />
|
|
76
|
+
* <JsonRendererProvider settings={{ registry, model }}>
|
|
77
|
+
* <JsonFormRenderer<MyForm> schema={schema} validate={import.meta.env.DEV} />
|
|
78
78
|
* </JsonRendererProvider>
|
|
79
79
|
* );
|
|
80
80
|
* }
|
|
81
81
|
* ```
|
|
82
82
|
*
|
|
83
|
-
* **Note**: `JsonFormRenderer` принимает ТОЛЬКО `{ schema, renderBehavior?, onSchemaReady? }`.
|
|
83
|
+
* **Note**: `JsonFormRenderer` принимает ТОЛЬКО `{ schema, renderBehavior?, onSchemaReady?, validate? }`.
|
|
84
84
|
* Под M1 модель (`FormModel`) передаётся через {@link JsonRendererProvider} settings (`model`);
|
|
85
|
-
* листья JSON-схемы биндятся к её сигналам конвертером
|
|
85
|
+
* листья JSON-схемы биндятся к её сигналам конвертером {@link createRenderSchemaFromJsonM1}.
|
|
86
86
|
*
|
|
87
87
|
* @see [docs/llms/01-overview.md](../../docs/llms/01-overview.md)
|
|
88
88
|
*/
|
|
@@ -4,5 +4,22 @@ export interface SchemaErrorPanelProps {
|
|
|
4
4
|
/** Человекочитаемые ошибки (`validateFormSchema().errors`). */
|
|
5
5
|
errors: string[];
|
|
6
6
|
}
|
|
7
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* Список ошибок схемы (path + message), с пометкой источника. Без внешних UI-зависимостей
|
|
9
|
+
* (инлайн-стили), помечен `role="alert"` и `data-testid="schema-error-panel"`.
|
|
10
|
+
*
|
|
11
|
+
* Обычно рендерится автоматически внутри {@link JsonFormRenderer} (при `validate` и невалидной
|
|
12
|
+
* схеме). Можно использовать напрямую для показа результата `validateFormSchema().errors`.
|
|
13
|
+
*
|
|
14
|
+
* @param props - {@link SchemaErrorPanelProps} (массив `errors`).
|
|
15
|
+
* @returns Панель со списком ошибок.
|
|
16
|
+
*
|
|
17
|
+
* @example Показать ошибки валидации схемы вручную
|
|
18
|
+
* ```tsx
|
|
19
|
+
* import { validateFormSchema } from '@reformer/renderer-json/validate';
|
|
20
|
+
*
|
|
21
|
+
* const { valid, errors } = validateFormSchema(schema, { registry });
|
|
22
|
+
* if (!valid) return <SchemaErrorPanel errors={errors} />;
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
8
25
|
export declare function SchemaErrorPanel({ errors }: SchemaErrorPanelProps): ReactNode;
|
|
@@ -30,10 +30,17 @@ export interface JsonRendererProviderProps {
|
|
|
30
30
|
* Поддерживает вложенность: внутренний провайдер сливается с внешним
|
|
31
31
|
* (внешний имеет приоритет в случае дублей имён в реестре).
|
|
32
32
|
*
|
|
33
|
-
* @example
|
|
33
|
+
* @example M1: реестр + модель через settings
|
|
34
34
|
* ```tsx
|
|
35
|
-
*
|
|
36
|
-
*
|
|
35
|
+
* // Модель — источник истины (M1); листья схемы биндятся к её сигналам.
|
|
36
|
+
* const model = useMemo(() => createModel<MyForm>({ email: '' }), []);
|
|
37
|
+
* const registry = useMemo(() => defineRegistry((reg) => {
|
|
38
|
+
* reg.component('Input', Input);
|
|
39
|
+
* reg.component(FIELD_WRAPPER, FormField);
|
|
40
|
+
* }), []);
|
|
41
|
+
*
|
|
42
|
+
* <JsonRendererProvider settings={{ registry, model }}>
|
|
43
|
+
* <JsonFormRenderer<MyForm> schema={schema} />
|
|
37
44
|
* </JsonRendererProvider>
|
|
38
45
|
* ```
|
|
39
46
|
*/
|
|
@@ -3,13 +3,52 @@ import { FormModel } from '@reformer/core';
|
|
|
3
3
|
import { JsonFormSchema } from '../types/json-schema';
|
|
4
4
|
import { ComponentRegistry } from '../registry/types';
|
|
5
5
|
/**
|
|
6
|
-
* Сырое дерево RenderNode из JSON (M1) — для `createForm({ model, schema })`.
|
|
6
|
+
* Сырое дерево RenderNode из JSON (M1) — для `createForm({ model, schema })`. Листья привязываются
|
|
7
|
+
* к сигналам модели (`'$model(path)'` → `model.signalAt`), компоненты/источники — из реестра.
|
|
8
|
+
*
|
|
9
|
+
* @typeParam T - Тип формы (форма данных модели).
|
|
10
|
+
* @param schema - JSON-схема формы ({@link JsonFormSchema}).
|
|
11
|
+
* @param registry - Реестр компонентов/источников (см. {@link defineRegistry}).
|
|
12
|
+
* @param model - Модель данных — источник значений (`FormModel`).
|
|
13
|
+
* @returns Корневой {@link RenderNode} — кладётся в `createForm({ schema })`.
|
|
14
|
+
*
|
|
15
|
+
* @example Собрать форму из JSON-схемы (M1)
|
|
16
|
+
* ```ts
|
|
17
|
+
* import { createForm } from '@reformer/core';
|
|
18
|
+
*
|
|
19
|
+
* const form = createForm<MyForm>({
|
|
20
|
+
* model,
|
|
21
|
+
* schema: convertJsonToM1Tree(jsonSchema, registry, model),
|
|
22
|
+
* behavior,
|
|
23
|
+
* });
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
7
26
|
* @group Converter
|
|
8
27
|
*/
|
|
9
28
|
export declare function convertJsonToM1Tree<T>(schema: JsonFormSchema, registry: ComponentRegistry, model: FormModel<T>): RenderNode<T>;
|
|
10
29
|
/**
|
|
11
30
|
* `RenderSchemaFn` из JSON (M1) — для `FormRenderer`/`JsonFormRenderer`. Листья привязываются к
|
|
12
31
|
* сигналам модели (`'$model(path)'` → `model.signalAt`), компоненты/источники — из реестра.
|
|
32
|
+
*
|
|
33
|
+
* В отличие от {@link convertJsonToM1Tree} (который возвращает готовое дерево), здесь результат —
|
|
34
|
+
* ленивая функция-фабрика дерева, как её ждёт `createRenderSchema`. Обычно вызывается внутри
|
|
35
|
+
* {@link JsonFormRenderer}; напрямую нужен для интеграции с `FormRenderer` без JSON-обёртки.
|
|
36
|
+
*
|
|
37
|
+
* @typeParam T - Тип формы.
|
|
38
|
+
* @param schema - JSON-схема формы ({@link JsonFormSchema}).
|
|
39
|
+
* @param registry - Реестр компонентов/источников (см. {@link defineRegistry}).
|
|
40
|
+
* @param model - Модель данных — источник значений (`FormModel`).
|
|
41
|
+
* @returns `RenderSchemaFn<T>` — фабрика {@link RenderNode}-дерева для `createRenderSchema`.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```ts
|
|
45
|
+
* import { createRenderSchema, FormRenderer } from '@reformer/renderer-react';
|
|
46
|
+
*
|
|
47
|
+
* const fn = createRenderSchemaFromJsonM1<MyForm>(jsonSchema, registry, model);
|
|
48
|
+
* const proxy = createRenderSchema<MyForm>(fn);
|
|
49
|
+
* <FormRenderer render={proxy} />;
|
|
50
|
+
* ```
|
|
51
|
+
*
|
|
13
52
|
* @group Converter
|
|
14
53
|
*/
|
|
15
54
|
export declare function createRenderSchemaFromJsonM1<T>(schema: JsonFormSchema, registry: ComponentRegistry, model: FormModel<T>): RenderSchemaFn<T>;
|
|
@@ -16,13 +16,10 @@ const y = (e) => n(e)?.op === "model", g = (e) => n(e)?.op === "component", S =
|
|
|
16
16
|
definitions: $
|
|
17
17
|
}, O = r;
|
|
18
18
|
function b(e) {
|
|
19
|
-
return e.names().filter((t) =>
|
|
20
|
-
const o = e.get(t)?.type;
|
|
21
|
-
return o === "field" || o === "container";
|
|
22
|
-
});
|
|
19
|
+
return e.names().filter((t) => e.get(t)?.type === "component");
|
|
23
20
|
}
|
|
24
21
|
function N(e) {
|
|
25
|
-
return e.names().filter((t) => e.get(t)?.type === "
|
|
22
|
+
return e.names().filter((t) => e.get(t)?.type === "dataSource");
|
|
26
23
|
}
|
|
27
24
|
const h = (e) => e.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
28
25
|
function j(e) {
|
package/dist/index.d.ts
CHANGED
|
@@ -19,4 +19,4 @@ export type { ComponentRegistry, ComponentMetadata, RegistryBuilder } from './re
|
|
|
19
19
|
export { JsonRendererProvider, useJsonRendererSettings } from './context/json-renderer-context';
|
|
20
20
|
export type { JsonRendererSettings, JsonRendererProviderProps, } from './context/json-renderer-context';
|
|
21
21
|
export { createRenderSchemaFromJsonM1, convertJsonToM1Tree, } from './converter/json-to-render-schema';
|
|
22
|
-
export { formSchemaMetaSchema, buildFormSchemaMetaSchema, getComponentNames,
|
|
22
|
+
export { formSchemaMetaSchema, buildFormSchemaMetaSchema, getComponentNames, getDataSourceNames, } from './schema';
|
package/dist/index.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { jsx as u, jsxs as M } from "react/jsx-runtime";
|
|
2
2
|
import { useContext as N, createContext as O, useMemo as g, useState as C, useEffect as F } from "react";
|
|
3
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-
|
|
5
|
-
import { c as rr, f as er, g as tr, d as nr } from "./index-
|
|
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
6
|
class h {
|
|
7
7
|
own = /* @__PURE__ */ new Map();
|
|
8
8
|
parent = null;
|
|
9
9
|
get(e) {
|
|
10
10
|
return this.own.get(e) ?? this.parent?.get(e);
|
|
11
11
|
}
|
|
12
|
-
|
|
12
|
+
getDataSource(e) {
|
|
13
13
|
const t = this.get(e);
|
|
14
|
-
if (!(!t || t.type !== "
|
|
14
|
+
if (!(!t || t.type !== "dataSource"))
|
|
15
15
|
return t.component;
|
|
16
16
|
}
|
|
17
17
|
has(e) {
|
|
@@ -34,20 +34,17 @@ class h {
|
|
|
34
34
|
function K(r) {
|
|
35
35
|
const e = new h();
|
|
36
36
|
return r({
|
|
37
|
-
|
|
38
|
-
e._set(n, { component: o, type: "
|
|
37
|
+
component(n, o, i) {
|
|
38
|
+
e._set(n, { component: o, type: "component", description: i });
|
|
39
39
|
},
|
|
40
|
-
|
|
41
|
-
e._set(n, { component: o, type: "
|
|
42
|
-
},
|
|
43
|
-
source(n, o, i) {
|
|
44
|
-
e._set(n, { component: o, type: "source", description: i });
|
|
40
|
+
dataSource(n, o, i) {
|
|
41
|
+
e._set(n, { component: o, type: "dataSource", description: i });
|
|
45
42
|
}
|
|
46
43
|
}), e;
|
|
47
44
|
}
|
|
48
|
-
const v = "$fieldWrapper",
|
|
45
|
+
const v = "$fieldWrapper", w = O({});
|
|
49
46
|
function Q({ settings: r, children: e }) {
|
|
50
|
-
const t = N(
|
|
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(() => {
|
|
51
48
|
const i = n?.get(v)?.component;
|
|
52
49
|
return {
|
|
53
50
|
...t,
|
|
@@ -56,10 +53,10 @@ function Q({ settings: r, children: e }) {
|
|
|
56
53
|
fieldWrapper: r.fieldWrapper ?? i ?? t.fieldWrapper
|
|
57
54
|
};
|
|
58
55
|
}, [t, r, n]);
|
|
59
|
-
return /* @__PURE__ */ u(
|
|
56
|
+
return /* @__PURE__ */ u(w.Provider, { value: o, children: e });
|
|
60
57
|
}
|
|
61
|
-
function
|
|
62
|
-
return N(
|
|
58
|
+
function D() {
|
|
59
|
+
return N(w);
|
|
63
60
|
}
|
|
64
61
|
function $(r) {
|
|
65
62
|
const e = r;
|
|
@@ -68,7 +65,7 @@ function $(r) {
|
|
|
68
65
|
function E(r) {
|
|
69
66
|
return f(r.value);
|
|
70
67
|
}
|
|
71
|
-
function
|
|
68
|
+
function W(r) {
|
|
72
69
|
return R(r.component) && !E(r) && !$(r);
|
|
73
70
|
}
|
|
74
71
|
function P(r, e) {
|
|
@@ -80,8 +77,8 @@ function P(r, e) {
|
|
|
80
77
|
throw new Error(
|
|
81
78
|
`Component "${t}" not found in registry. Available: ${e.names().join(", ")}`
|
|
82
79
|
);
|
|
83
|
-
if (n.type === "
|
|
84
|
-
throw new Error(`Entry "${t}" is a '
|
|
80
|
+
if (n.type === "dataSource")
|
|
81
|
+
throw new Error(`Entry "${t}" is a 'dataSource' and cannot be used as $component(...)`);
|
|
85
82
|
return n.component;
|
|
86
83
|
}
|
|
87
84
|
function L(r, e) {
|
|
@@ -92,22 +89,22 @@ function L(r, e) {
|
|
|
92
89
|
);
|
|
93
90
|
return t.component;
|
|
94
91
|
}
|
|
95
|
-
function
|
|
92
|
+
function I(r, e) {
|
|
96
93
|
return e.split(".").reduce((t, n) => t == null ? t : t[n], r);
|
|
97
94
|
}
|
|
98
|
-
function
|
|
95
|
+
function V(r) {
|
|
99
96
|
if (r === null || typeof r != "object") return !1;
|
|
100
97
|
const e = r;
|
|
101
98
|
return f(e.value) || f(e.array) || R(e.component);
|
|
102
99
|
}
|
|
103
|
-
function
|
|
100
|
+
function _(r) {
|
|
104
101
|
return r == null ? r : JSON.parse(JSON.stringify(r));
|
|
105
102
|
}
|
|
106
103
|
function b(r, e, t) {
|
|
107
104
|
if (A(r)) return L(m(r).arg, t);
|
|
108
105
|
if (R(r)) return P(r, t);
|
|
109
106
|
if (f(r)) return e.signalAt(m(r).arg);
|
|
110
|
-
if (
|
|
107
|
+
if (V(r)) return p(r, e, t);
|
|
111
108
|
if (Array.isArray(r)) return r.map((n) => b(n, e, t));
|
|
112
109
|
if (r !== null && typeof r == "object") {
|
|
113
110
|
const n = r, o = {};
|
|
@@ -116,7 +113,7 @@ function b(r, e, t) {
|
|
|
116
113
|
}
|
|
117
114
|
return r;
|
|
118
115
|
}
|
|
119
|
-
function
|
|
116
|
+
function S(r, e, t) {
|
|
120
117
|
if (!r) return;
|
|
121
118
|
const n = {};
|
|
122
119
|
for (const o of Object.keys(r)) n[o] = b(r[o], e, t);
|
|
@@ -124,13 +121,13 @@ function w(r, e, t) {
|
|
|
124
121
|
}
|
|
125
122
|
function p(r, e, t) {
|
|
126
123
|
if ($(r)) {
|
|
127
|
-
const n =
|
|
124
|
+
const n = I(e, m(r.array).arg), o = r.item.$template, i = r.initialValue;
|
|
128
125
|
return {
|
|
129
126
|
...r.selector ? { selector: r.selector } : {},
|
|
130
127
|
array: n,
|
|
131
|
-
initialValue: () => i ?
|
|
128
|
+
initialValue: () => i ? _(i) : {},
|
|
132
129
|
item: (y) => p(o, y, t),
|
|
133
|
-
componentProps:
|
|
130
|
+
componentProps: S(r.componentProps, e, t)
|
|
134
131
|
};
|
|
135
132
|
}
|
|
136
133
|
if (E(r)) {
|
|
@@ -139,14 +136,14 @@ function p(r, e, t) {
|
|
|
139
136
|
...r.selector ? { selector: r.selector } : {},
|
|
140
137
|
value: o,
|
|
141
138
|
component: P(r.component, t),
|
|
142
|
-
componentProps:
|
|
139
|
+
componentProps: S(r.componentProps, e, t)
|
|
143
140
|
};
|
|
144
141
|
}
|
|
145
|
-
if (
|
|
142
|
+
if (W(r))
|
|
146
143
|
return {
|
|
147
144
|
...r.selector ? { selector: r.selector } : {},
|
|
148
145
|
component: P(r.component, t),
|
|
149
|
-
componentProps:
|
|
146
|
+
componentProps: S(r.componentProps, e, t),
|
|
150
147
|
children: r.children?.map((n) => p(n, e, t))
|
|
151
148
|
};
|
|
152
149
|
throw new Error(`Invalid JSON node (M1): ${JSON.stringify(r)}`);
|
|
@@ -188,7 +185,7 @@ function X({
|
|
|
188
185
|
onSchemaReady: t,
|
|
189
186
|
validate: n = !1
|
|
190
187
|
}) {
|
|
191
|
-
const { registry: o, model: i, ...y } =
|
|
188
|
+
const { registry: o, model: i, ...y } = D(), [a, d] = C(
|
|
192
189
|
n ? void 0 : null
|
|
193
190
|
);
|
|
194
191
|
F(() => {
|
|
@@ -231,13 +228,13 @@ export {
|
|
|
231
228
|
K as defineRegistry,
|
|
232
229
|
er as formSchemaMetaSchema,
|
|
233
230
|
tr as getComponentNames,
|
|
234
|
-
nr as
|
|
231
|
+
nr as getDataSourceNames,
|
|
235
232
|
$ as isArrayNode,
|
|
236
233
|
R as isComponentOp,
|
|
237
|
-
|
|
234
|
+
W as isContainerNode,
|
|
238
235
|
A as isDataSourceOp,
|
|
239
236
|
E as isFieldNode,
|
|
240
237
|
f as isModelOp,
|
|
241
238
|
m as parseOperator,
|
|
242
|
-
|
|
239
|
+
D as useJsonRendererSettings
|
|
243
240
|
};
|
package/dist/operators.d.ts
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* JSON нельзя «вызвать функцию», поэтому привязки кодируются СТРОКАМИ функц-стиля с дискриминатором:
|
|
5
5
|
*
|
|
6
6
|
* - `"$model(path)"` — путь к полю/массиву модели: лист → `model.signalAt(path)`, массив → `model.<path>`.
|
|
7
|
-
* - `"$component(Name)"` — имя компонента в реестре (`reg.
|
|
8
|
-
* - `"$dataSource(NAME)"` — имя registry-source (`reg.
|
|
7
|
+
* - `"$component(Name)"` — имя компонента в реестре (`reg.component`).
|
|
8
|
+
* - `"$dataSource(NAME)"` — имя registry-source (`reg.dataSource`): options/itemLabel/константы/loading-компоненты.
|
|
9
9
|
*
|
|
10
10
|
* Схема остаётся чистым JSON (копируется в `.json`, приходит строкой с сервера). Типобезопасность
|
|
11
11
|
* на этапе компиляции даётся template-literal типами ({@link ModelOp} и т.д.) — литерал
|
|
@@ -30,6 +30,9 @@ export interface ParsedOperator {
|
|
|
30
30
|
* Разбор строки-оператора `"$op(arg)"`. Возвращает `null` для не-операторов (обычных строк),
|
|
31
31
|
* чтобы вызывающий оставил значение как есть.
|
|
32
32
|
*
|
|
33
|
+
* @param value - Проверяемое значение (не-строки сразу дают `null`).
|
|
34
|
+
* @returns {@link ParsedOperator} (`{ op, arg }`) либо `null`, если строка — не оператор.
|
|
35
|
+
*
|
|
33
36
|
* @example
|
|
34
37
|
* ```ts
|
|
35
38
|
* parseOperator('$model(loanType)'); // { op: 'model', arg: 'loanType' }
|
|
@@ -38,9 +41,45 @@ export interface ParsedOperator {
|
|
|
38
41
|
* ```
|
|
39
42
|
*/
|
|
40
43
|
export declare function parseOperator(value: unknown): ParsedOperator | null;
|
|
41
|
-
/**
|
|
44
|
+
/**
|
|
45
|
+
* Type-guard: строка — оператор `"$model(...)"` (привязка к полю/массиву модели).
|
|
46
|
+
*
|
|
47
|
+
* @param v - Проверяемое значение.
|
|
48
|
+
* @returns `true`, если `v` — {@link ModelOp}.
|
|
49
|
+
*
|
|
50
|
+
* @example Сузить тип значения prop до ModelOp
|
|
51
|
+
* ```ts
|
|
52
|
+
* if (isModelOp(value)) {
|
|
53
|
+
* const path = parseOperator(value)!.arg; // 'loanType'
|
|
54
|
+
* }
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
42
57
|
export declare const isModelOp: (v: unknown) => v is ModelOp;
|
|
43
|
-
/**
|
|
58
|
+
/**
|
|
59
|
+
* Type-guard: строка — оператор `"$component(...)"` (ссылка на компонент реестра).
|
|
60
|
+
*
|
|
61
|
+
* @param v - Проверяемое значение.
|
|
62
|
+
* @returns `true`, если `v` — {@link ComponentOp}.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```ts
|
|
66
|
+
* if (isComponentOp(node.component)) {
|
|
67
|
+
* const name = parseOperator(node.component)!.arg; // 'Select'
|
|
68
|
+
* }
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
44
71
|
export declare const isComponentOp: (v: unknown) => v is ComponentOp;
|
|
45
|
-
/**
|
|
72
|
+
/**
|
|
73
|
+
* Type-guard: строка — оператор `"$dataSource(...)"` (ссылка на registry-source).
|
|
74
|
+
*
|
|
75
|
+
* @param v - Проверяемое значение.
|
|
76
|
+
* @returns `true`, если `v` — {@link DataSourceOp}.
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```ts
|
|
80
|
+
* if (isDataSourceOp(props.options)) {
|
|
81
|
+
* const name = parseOperator(props.options)!.arg; // 'LOAN_TYPES'
|
|
82
|
+
* }
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
46
85
|
export declare const isDataSourceOp: (v: unknown) => v is DataSourceOp;
|
|
@@ -3,7 +3,7 @@ export declare class ComponentRegistryImpl implements ComponentRegistry {
|
|
|
3
3
|
private own;
|
|
4
4
|
private parent;
|
|
5
5
|
get(name: string): ComponentMetadata | undefined;
|
|
6
|
-
|
|
6
|
+
getDataSource<T = unknown>(name: string): T | undefined;
|
|
7
7
|
has(name: string): boolean;
|
|
8
8
|
names(): string[];
|
|
9
9
|
_set(name: string, metadata: ComponentMetadata): void;
|
|
@@ -15,7 +15,7 @@ export declare class ComponentRegistryImpl implements ComponentRegistry {
|
|
|
15
15
|
* Реестр обязателен для работы {@link JsonFormRenderer} — иначе компоненты,
|
|
16
16
|
* упомянутые в JSON-схеме, не отрезолвятся.
|
|
17
17
|
*
|
|
18
|
-
* @param fn - Builder-callback. Получает {@link RegistryBuilder} с методами `
|
|
18
|
+
* @param fn - Builder-callback. Получает {@link RegistryBuilder} с методами `component`, `dataSource`.
|
|
19
19
|
* @returns Готовый {@link ComponentRegistry}, который кладётся в `JsonRendererProvider`.
|
|
20
20
|
*
|
|
21
21
|
* @example
|
|
@@ -24,10 +24,10 @@ export declare class ComponentRegistryImpl implements ComponentRegistry {
|
|
|
24
24
|
* import { Input, Select, FormField } from '@reformer/ui-kit';
|
|
25
25
|
*
|
|
26
26
|
* const registry = defineRegistry((reg) => {
|
|
27
|
-
* reg.
|
|
28
|
-
* reg.
|
|
29
|
-
* reg.
|
|
30
|
-
* reg.
|
|
27
|
+
* reg.component('Input', Input);
|
|
28
|
+
* reg.component('Select', Select);
|
|
29
|
+
* reg.component(FIELD_WRAPPER, FormField);
|
|
30
|
+
* reg.dataSource('LOAN_TYPES', [
|
|
31
31
|
* { value: 'consumer', label: 'Потребительский' },
|
|
32
32
|
* { value: 'mortgage', label: 'Ипотека' },
|
|
33
33
|
* ]);
|
package/dist/registry/types.d.ts
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import { ComponentType } from 'react';
|
|
2
2
|
/**
|
|
3
|
-
* Запись реестра. Хранит сам компонент (или
|
|
3
|
+
* Запись реестра. Хранит сам компонент (или dataSource-значение) и его роль.
|
|
4
4
|
*
|
|
5
5
|
* @typeParam P - Пропсы компонента (для `field`/`container`).
|
|
6
6
|
*
|
|
7
7
|
* @example
|
|
8
8
|
* ```typescript
|
|
9
9
|
* const meta: ComponentMetadata = registry.get('Input')!;
|
|
10
|
-
* meta.type; // '
|
|
10
|
+
* meta.type; // 'component'
|
|
11
11
|
* ```
|
|
12
12
|
*/
|
|
13
13
|
export interface ComponentMetadata<P = any> {
|
|
14
14
|
component: ComponentType<P> | unknown;
|
|
15
|
-
type: '
|
|
15
|
+
type: 'component' | 'dataSource';
|
|
16
16
|
description?: string;
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
@@ -31,7 +31,7 @@ export interface ComponentMetadata<P = any> {
|
|
|
31
31
|
*/
|
|
32
32
|
export interface ComponentRegistry {
|
|
33
33
|
get(name: string): ComponentMetadata | undefined;
|
|
34
|
-
|
|
34
|
+
getDataSource<T = unknown>(name: string): T | undefined;
|
|
35
35
|
has(name: string): boolean;
|
|
36
36
|
names(): string[];
|
|
37
37
|
}
|
|
@@ -41,14 +41,20 @@ export interface ComponentRegistry {
|
|
|
41
41
|
* @example
|
|
42
42
|
* ```typescript
|
|
43
43
|
* defineRegistry((reg: RegistryBuilder) => {
|
|
44
|
-
* reg.
|
|
45
|
-
* reg.
|
|
46
|
-
* reg.
|
|
44
|
+
* reg.component('Input', Input);
|
|
45
|
+
* reg.component('Box', Box);
|
|
46
|
+
* reg.dataSource('LOAN_TYPES', LOAN_TYPES);
|
|
47
47
|
* });
|
|
48
48
|
* ```
|
|
49
49
|
*/
|
|
50
50
|
export interface RegistryBuilder {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
/**
|
|
52
|
+
* Зарегистрировать React-компонент под именем — доступен в схеме как
|
|
53
|
+
* `$component(name)`. Одним методом регистрируются и компоненты-листья
|
|
54
|
+
* (Input/Select), и контейнеры (Box/Section/FormField): роль узла (лист vs
|
|
55
|
+
* контейнер) определяется структурой узла схемы (`value` vs `children`),
|
|
56
|
+
* а не регистрацией.
|
|
57
|
+
*/
|
|
58
|
+
component<P>(name: string, component: ComponentType<P>, description?: string): void;
|
|
59
|
+
dataSource<T>(name: string, value: T, description?: string): void;
|
|
54
60
|
}
|
package/dist/schema/index.d.ts
CHANGED
|
@@ -1,10 +1,42 @@
|
|
|
1
1
|
import { ComponentRegistry } from '../registry/types';
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* Базовая мета-схема form-DSL (draft-07): структура узлов + синтаксис операторов, имена компонентов
|
|
4
|
+
* НЕ ограничены (паттерн `$component(...)` открыт). Для сужения до конкретного реестра —
|
|
5
|
+
* {@link buildFormSchemaMetaSchema}. Используется как `$schema` в IDE и как база валидатора.
|
|
6
|
+
*
|
|
7
|
+
* @example Подключить как `$schema` в JSON-файле схемы
|
|
8
|
+
* ```ts
|
|
9
|
+
* // record можно записать в файл и сослаться на него из "$schema" JSON-схемы.
|
|
10
|
+
* formSchemaMetaSchema.$schema; // 'http://json-schema.org/draft-07/schema#'
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
3
13
|
export declare const formSchemaMetaSchema: Record<string, unknown>;
|
|
4
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* Имена компонентов реестра (тип `component`) — то, что валидно в `$component(...)`.
|
|
16
|
+
*
|
|
17
|
+
* @param registry - Реестр (см. {@link defineRegistry}).
|
|
18
|
+
* @returns Массив имён компонентов.
|
|
19
|
+
*
|
|
20
|
+
* @example Сузить мета-схему до компонентов реестра
|
|
21
|
+
* ```ts
|
|
22
|
+
* const names = getComponentNames(registry); // ['Input', 'Select', 'Box', ...]
|
|
23
|
+
* const schema = buildFormSchemaMetaSchema({ componentNames: names });
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
5
26
|
export declare function getComponentNames(registry: ComponentRegistry): string[];
|
|
6
|
-
/**
|
|
7
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Имена registry-dataSource (`reg.dataSource`) — то, что валидно в `$dataSource(...)`:
|
|
29
|
+
* options/itemLabel/константы/loading-компоненты.
|
|
30
|
+
*
|
|
31
|
+
* @param registry - Реестр (см. {@link defineRegistry}).
|
|
32
|
+
* @returns Массив имён источников.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```ts
|
|
36
|
+
* getDataSourceNames(registry); // ['LOAN_TYPES', 'GENDERS', 'LoadingState', ...]
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export declare function getDataSourceNames(registry: ComponentRegistry): string[];
|
|
8
40
|
/**
|
|
9
41
|
* Конкретная мета-схема: базовая + (если заданы `componentNames`) сужение паттерна `$component(...)`
|
|
10
42
|
* до enum имён (`^\$component\((Input|Select|…)\)$`). `$dataSource`-имена JSON Schema'й не покрыть
|
|
@@ -68,9 +68,49 @@ export interface JsonFormSchema {
|
|
|
68
68
|
/** Корневой узел. */
|
|
69
69
|
root: JsonNode;
|
|
70
70
|
}
|
|
71
|
-
/**
|
|
71
|
+
/**
|
|
72
|
+
* Type-guard: узел — массив (`array: '$model(...)'` + `item.$template`). Проверять ПЕРВЫМ
|
|
73
|
+
* (лист/контейнер отсеиваются после, т.к. массив тоже несёт `$model`).
|
|
74
|
+
*
|
|
75
|
+
* @param node - Узел JSON-схемы.
|
|
76
|
+
* @returns `true`, если узел — {@link JsonArrayNode}.
|
|
77
|
+
*
|
|
78
|
+
* @example Сузить тип узла перед доступом к `item.$template`
|
|
79
|
+
* ```ts
|
|
80
|
+
* if (isArrayNode(node)) {
|
|
81
|
+
* node.array; // ModelOp
|
|
82
|
+
* node.item.$template; // JsonNode
|
|
83
|
+
* }
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
72
86
|
export declare function isArrayNode(node: JsonNode): node is JsonArrayNode;
|
|
73
|
-
/**
|
|
87
|
+
/**
|
|
88
|
+
* Type-guard: узел — лист (`value: '$model(...)'`).
|
|
89
|
+
*
|
|
90
|
+
* @param node - Узел JSON-схемы.
|
|
91
|
+
* @returns `true`, если узел — {@link JsonFieldNode}.
|
|
92
|
+
*
|
|
93
|
+
* @example Сузить тип узла перед доступом к `value`/`component`
|
|
94
|
+
* ```ts
|
|
95
|
+
* if (isFieldNode(node)) {
|
|
96
|
+
* node.value; // ModelOp
|
|
97
|
+
* node.component; // ComponentOp | undefined
|
|
98
|
+
* }
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
74
101
|
export declare function isFieldNode(node: JsonNode): node is JsonFieldNode;
|
|
75
|
-
/**
|
|
102
|
+
/**
|
|
103
|
+
* Type-guard: узел — контейнер (`component: '$component(...)'`, без `value`/`array`).
|
|
104
|
+
*
|
|
105
|
+
* @param node - Узел JSON-схемы.
|
|
106
|
+
* @returns `true`, если узел — {@link JsonContainerNode}.
|
|
107
|
+
*
|
|
108
|
+
* @example Сузить тип узла перед обходом `children`
|
|
109
|
+
* ```ts
|
|
110
|
+
* if (isContainerNode(node)) {
|
|
111
|
+
* node.component; // ComponentOp
|
|
112
|
+
* node.children?.forEach(walk);
|
|
113
|
+
* }
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
76
116
|
export declare function isContainerNode(node: JsonNode): node is JsonContainerNode;
|
package/dist/validate.d.ts
CHANGED
|
@@ -9,10 +9,16 @@ export interface FormSchemaValidationResult {
|
|
|
9
9
|
export interface ValidateFormSchemaOptions {
|
|
10
10
|
registry?: ComponentRegistry;
|
|
11
11
|
componentNames?: string[];
|
|
12
|
-
|
|
12
|
+
dataSourceNames?: string[];
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
15
|
-
* Валидирует form-DSL JSON-схему. Если передан `registry`, имена компонентов/source берутся из
|
|
15
|
+
* Валидирует form-DSL JSON-схему. Если передан `registry`, имена компонентов/source берутся из него
|
|
16
|
+
* (иначе можно задать `componentNames`/`dataSourceNames` явно). Тянет `ajv` — живёт в subpath
|
|
17
|
+
* `@reformer/renderer-json/validate`, чтобы не попадать в основной render-бандл.
|
|
18
|
+
*
|
|
19
|
+
* @param schema - Проверяемая JSON-схема (обычно {@link JsonFormSchema}, но принимает `unknown`).
|
|
20
|
+
* @param opts - {@link ValidateFormSchemaOptions}: `registry` либо явные списки имён.
|
|
21
|
+
* @returns {@link FormSchemaValidationResult} — `{ valid, errors }` (ошибки — путь + сообщение).
|
|
16
22
|
*
|
|
17
23
|
* @example
|
|
18
24
|
* ```ts
|
package/dist/validate.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { g as an, d as on, f as un, p as cn } from "./index-
|
|
1
|
+
import { g as an, d as on, f as un, p as cn } from "./index-BZLcW0SX.js";
|
|
2
2
|
function ln(e) {
|
|
3
3
|
return e && e.__esModule && Object.prototype.hasOwnProperty.call(e, "default") ? e.default : e;
|
|
4
4
|
}
|
|
@@ -4456,7 +4456,7 @@ function qt(e, r, S, d, g) {
|
|
|
4456
4456
|
qt(f, r ? `${r}.${s}` : s, S, d, g);
|
|
4457
4457
|
}
|
|
4458
4458
|
function gs(e, r = {}) {
|
|
4459
|
-
const S = r.componentNames ?? (r.registry ? an(r.registry) : void 0), d = r.
|
|
4459
|
+
const S = r.componentNames ?? (r.registry ? an(r.registry) : void 0), d = r.dataSourceNames ?? (r.registry ? on(r.registry) : void 0), g = [], f = new vs({ allErrors: !0 }).compile(un);
|
|
4460
4460
|
if (!f(e))
|
|
4461
4461
|
for (const c of f.errors ?? [])
|
|
4462
4462
|
g.push(`${c.instancePath || "/"} ${c.message ?? "invalid"}`.trim());
|