schema-components 1.3.3 → 1.5.0
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/CHANGELOG.md +16 -0
- package/README.md +63 -3
- package/dist/openapi/components.d.mts +17 -4
- package/dist/openapi/components.mjs +26 -16
- package/dist/react/SchemaComponent.d.mts +27 -4
- package/dist/react/SchemaComponent.mjs +25 -11
- package/dist/react/SchemaView.d.mts +5 -1
- package/dist/react/SchemaView.mjs +25 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
## [1.5.0](https://github.com/Mearman/schema-components/compare/v1.4.0...v1.5.0) (2026-05-14)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
* add storybook stories for widgets, SchemaView, defaults, editability, and records ([e36720e](https://github.com/Mearman/schema-components/commit/e36720e09edcba11374eca77394f000a62261309))
|
|
6
|
+
|
|
7
|
+
## [1.4.0](https://github.com/Mearman/schema-components/compare/v1.3.3...v1.4.0) (2026-05-14)
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* add scoped widget resolution at instance, context, and global levels ([b330baf](https://github.com/Mearman/schema-components/commit/b330baf183d00bebf999108f16c9dabd40c243be))
|
|
12
|
+
|
|
13
|
+
### Documentation
|
|
14
|
+
|
|
15
|
+
* update README with scoped widget resolution documentation ([21ac50d](https://github.com/Mearman/schema-components/commit/21ac50dc9a55144fc319fcf482ffad6a16585a27))
|
|
16
|
+
|
|
1
17
|
## [1.3.3](https://github.com/Mearman/schema-components/compare/v1.3.2...v1.3.3) (2026-05-14)
|
|
2
18
|
|
|
3
19
|
### Refactoring
|
package/README.md
CHANGED
|
@@ -434,7 +434,15 @@ Custom resolvers fall back to the default for any type you don't override.
|
|
|
434
434
|
|
|
435
435
|
## Custom widgets
|
|
436
436
|
|
|
437
|
-
|
|
437
|
+
Widgets let you override rendering for specific fields using `.meta({ component: name })`. Three scopes are available, checked in order:
|
|
438
|
+
|
|
439
|
+
1. **Instance** — `widgets` prop on `<SchemaComponent>`
|
|
440
|
+
2. **Context** — `widgets` prop on `<SchemaProvider>`
|
|
441
|
+
3. **Global** — `registerWidget()` for app-wide defaults
|
|
442
|
+
|
|
443
|
+
If none match, the theme adapter or headless default handles the field.
|
|
444
|
+
|
|
445
|
+
### Global registration
|
|
438
446
|
|
|
439
447
|
```tsx
|
|
440
448
|
import { registerWidget } from "schema-components/react/SchemaComponent";
|
|
@@ -443,13 +451,65 @@ registerWidget("richtext", ({ value, onChange }) => (
|
|
|
443
451
|
<RichTextEditor value={value} onChange={onChange} />
|
|
444
452
|
));
|
|
445
453
|
|
|
446
|
-
// In schema
|
|
447
454
|
const schema = z.object({
|
|
448
455
|
bio: z.string().meta({ component: "richtext" }),
|
|
449
456
|
});
|
|
450
457
|
```
|
|
451
458
|
|
|
452
|
-
|
|
459
|
+
### Context-scoped widgets
|
|
460
|
+
|
|
461
|
+
Share widgets across a subtree via `<SchemaProvider>`:
|
|
462
|
+
|
|
463
|
+
```tsx
|
|
464
|
+
import { SchemaProvider } from "schema-components/react/SchemaComponent";
|
|
465
|
+
import type { WidgetMap } from "schema-components/react/SchemaComponent";
|
|
466
|
+
|
|
467
|
+
const adminWidgets: WidgetMap = new Map([
|
|
468
|
+
["richtext", ({ value, onChange }) => <RichTextEditor value={value} onChange={onChange} />],
|
|
469
|
+
["avatar", ({ value, onChange }) => <AvatarUploader value={value} onChange={onChange} />],
|
|
470
|
+
]);
|
|
471
|
+
|
|
472
|
+
<SchemaProvider resolver={shadcnResolver} widgets={adminWidgets}>
|
|
473
|
+
<SchemaComponent schema={userSchema} value={user} onChange={setUser} />
|
|
474
|
+
<SchemaComponent schema={profileSchema} value={profile} onChange={setProfile} />
|
|
475
|
+
</SchemaProvider>
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
### Instance-scoped widgets
|
|
479
|
+
|
|
480
|
+
Override widgets for a single form:
|
|
481
|
+
|
|
482
|
+
```tsx
|
|
483
|
+
const formWidgets: WidgetMap = new Map([
|
|
484
|
+
["richtext", ({ value, onChange }) => <SimpleTextarea value={value} onChange={onChange} />],
|
|
485
|
+
]);
|
|
486
|
+
|
|
487
|
+
<SchemaComponent schema={formSchema} value={form} widgets={formWidgets} />
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
### Resolution order
|
|
491
|
+
|
|
492
|
+
```.meta({ component }) hint → instance widgets → context widgets → global registerWidget() → theme adapter → headless default
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
Instance overrides context. Context overrides global. Unhinted fields skip the widget layer entirely.
|
|
496
|
+
|
|
497
|
+
### `WidgetMap` type
|
|
498
|
+
|
|
499
|
+
```tsx
|
|
500
|
+
import type { WidgetMap } from "schema-components/react/SchemaComponent";
|
|
501
|
+
|
|
502
|
+
// ReadonlyMap<string, (props: RenderProps) => unknown>
|
|
503
|
+
const widgets: WidgetMap = new Map([
|
|
504
|
+
["name", (props) => <MyInput {...props} />],
|
|
505
|
+
]);
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
Server Components: `<SchemaView>` accepts a `widgets` prop directly (no React context available):
|
|
509
|
+
|
|
510
|
+
```tsx
|
|
511
|
+
<SchemaView schema={schema} value={data} widgets={serverWidgets} />
|
|
512
|
+
```
|
|
453
513
|
|
|
454
514
|
## Validation
|
|
455
515
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { c as InferResponseFields, m as SchemaMeta, o as InferParameterOverrides, r as FieldOverride, s as InferRequestBodyFields } from "../types-DDCD6Xnx.mjs";
|
|
2
|
+
import { WidgetMap } from "../react/SchemaComponent.mjs";
|
|
2
3
|
import { ReactNode } from "react";
|
|
3
4
|
|
|
4
5
|
//#region src/openapi/components.d.ts
|
|
@@ -11,6 +12,8 @@ interface ApiOperationProps<Doc = unknown, Path extends string = string, Method
|
|
|
11
12
|
responseValue?: unknown;
|
|
12
13
|
meta?: SchemaMeta;
|
|
13
14
|
requestBodyFields?: Doc extends Record<string, unknown> ? InferRequestBodyFields<Doc, Path, Method> : Record<string, FieldOverride>;
|
|
15
|
+
/** Instance-scoped widgets. */
|
|
16
|
+
widgets?: WidgetMap;
|
|
14
17
|
}
|
|
15
18
|
declare function ApiOperation<Doc = unknown, Path extends string = string, Method extends string = string>({
|
|
16
19
|
schema: doc,
|
|
@@ -20,7 +23,8 @@ declare function ApiOperation<Doc = unknown, Path extends string = string, Metho
|
|
|
20
23
|
onRequestBodyChange,
|
|
21
24
|
responseValue,
|
|
22
25
|
meta,
|
|
23
|
-
requestBodyFields
|
|
26
|
+
requestBodyFields,
|
|
27
|
+
widgets
|
|
24
28
|
}: ApiOperationProps<Doc, Path, Method>): ReactNode;
|
|
25
29
|
interface ApiParametersProps<Doc = unknown, Path extends string = string, Method extends string = string> {
|
|
26
30
|
schema: Doc;
|
|
@@ -28,13 +32,16 @@ interface ApiParametersProps<Doc = unknown, Path extends string = string, Method
|
|
|
28
32
|
method: Method;
|
|
29
33
|
meta?: SchemaMeta;
|
|
30
34
|
overrides?: Doc extends Record<string, unknown> ? InferParameterOverrides<Doc, Path, Method> : Record<string, FieldOverride>;
|
|
35
|
+
/** Instance-scoped widgets. */
|
|
36
|
+
widgets?: WidgetMap;
|
|
31
37
|
}
|
|
32
38
|
declare function ApiParameters<Doc = unknown, Path extends string = string, Method extends string = string>({
|
|
33
39
|
schema: doc,
|
|
34
40
|
path,
|
|
35
41
|
method,
|
|
36
42
|
meta,
|
|
37
|
-
overrides
|
|
43
|
+
overrides,
|
|
44
|
+
widgets
|
|
38
45
|
}: ApiParametersProps<Doc, Path, Method>): ReactNode;
|
|
39
46
|
interface ApiRequestBodyProps<Doc = unknown, Path extends string = string, Method extends string = string> {
|
|
40
47
|
schema: Doc;
|
|
@@ -44,6 +51,8 @@ interface ApiRequestBodyProps<Doc = unknown, Path extends string = string, Metho
|
|
|
44
51
|
onChange?: (value: unknown) => void;
|
|
45
52
|
meta?: SchemaMeta;
|
|
46
53
|
fields?: Doc extends Record<string, unknown> ? InferRequestBodyFields<Doc, Path, Method> : Record<string, FieldOverride>;
|
|
54
|
+
/** Instance-scoped widgets. */
|
|
55
|
+
widgets?: WidgetMap;
|
|
47
56
|
}
|
|
48
57
|
declare function ApiRequestBody<Doc = unknown, Path extends string = string, Method extends string = string>({
|
|
49
58
|
schema: doc,
|
|
@@ -52,7 +61,8 @@ declare function ApiRequestBody<Doc = unknown, Path extends string = string, Met
|
|
|
52
61
|
value,
|
|
53
62
|
onChange,
|
|
54
63
|
meta,
|
|
55
|
-
fields
|
|
64
|
+
fields,
|
|
65
|
+
widgets
|
|
56
66
|
}: ApiRequestBodyProps<Doc, Path, Method>): ReactNode;
|
|
57
67
|
interface ApiResponseProps<Doc = unknown, Path extends string = string, Method extends string = string, Status extends string = string> {
|
|
58
68
|
schema: Doc;
|
|
@@ -62,6 +72,8 @@ interface ApiResponseProps<Doc = unknown, Path extends string = string, Method e
|
|
|
62
72
|
value?: unknown;
|
|
63
73
|
meta?: SchemaMeta;
|
|
64
74
|
fields?: Doc extends Record<string, unknown> ? InferResponseFields<Doc, Path, Method, Status> : Record<string, FieldOverride>;
|
|
75
|
+
/** Instance-scoped widgets. */
|
|
76
|
+
widgets?: WidgetMap;
|
|
65
77
|
}
|
|
66
78
|
declare function ApiResponse<Doc = unknown, Path extends string = string, Method extends string = string, Status extends string = string>({
|
|
67
79
|
schema: doc,
|
|
@@ -70,7 +82,8 @@ declare function ApiResponse<Doc = unknown, Path extends string = string, Method
|
|
|
70
82
|
status,
|
|
71
83
|
value,
|
|
72
84
|
meta,
|
|
73
|
-
fields
|
|
85
|
+
fields,
|
|
86
|
+
widgets
|
|
74
87
|
}: ApiResponseProps<Doc, Path, Method, Status>): ReactNode;
|
|
75
88
|
//#endregion
|
|
76
89
|
export { ApiOperation, ApiOperationProps, ApiParameters, ApiParametersProps, ApiRequestBody, ApiRequestBodyProps, ApiResponse, ApiResponseProps };
|
|
@@ -27,10 +27,10 @@ function renderSchema(schema, rootDocument, options) {
|
|
|
27
27
|
rootDocument
|
|
28
28
|
};
|
|
29
29
|
const tree = walk(jsonSchema, walkOpts);
|
|
30
|
-
const renderChild = (childTree, childValue, childOnChange) => renderField(childTree, childValue, childOnChange, void 0, renderChild);
|
|
31
|
-
return renderField(tree, options.value, options.onChange ?? noop, void 0, renderChild);
|
|
30
|
+
const renderChild = (childTree, childValue, childOnChange) => renderField(childTree, childValue, childOnChange, void 0, renderChild, options.widgets);
|
|
31
|
+
return renderField(tree, options.value, options.onChange ?? noop, void 0, renderChild, options.widgets);
|
|
32
32
|
}
|
|
33
|
-
function ApiOperation({ schema: doc, path, method, requestBodyValue, onRequestBodyChange, responseValue, meta, requestBodyFields }) {
|
|
33
|
+
function ApiOperation({ schema: doc, path, method, requestBodyValue, onRequestBodyChange, responseValue, meta, requestBodyFields, widgets }) {
|
|
34
34
|
const rootDoc = toDoc(doc);
|
|
35
35
|
const resolved = resolveOperation(rootDoc, path, method);
|
|
36
36
|
return /* @__PURE__ */ jsxs("section", {
|
|
@@ -42,7 +42,8 @@ function ApiOperation({ schema: doc, path, method, requestBodyValue, onRequestBo
|
|
|
42
42
|
children: [/* @__PURE__ */ jsx("h4", { children: "Parameters" }), /* @__PURE__ */ jsx(ParameterList, {
|
|
43
43
|
parameters: resolved.parameters,
|
|
44
44
|
rootDoc,
|
|
45
|
-
meta
|
|
45
|
+
meta,
|
|
46
|
+
widgets
|
|
46
47
|
})]
|
|
47
48
|
}),
|
|
48
49
|
resolved.requestBody?.schema !== void 0 && /* @__PURE__ */ jsxs("section", {
|
|
@@ -61,7 +62,8 @@ function ApiOperation({ schema: doc, path, method, requestBodyValue, onRequestBo
|
|
|
61
62
|
value: requestBodyValue,
|
|
62
63
|
onChange: onRequestBodyChange,
|
|
63
64
|
fields: requestBodyFields,
|
|
64
|
-
meta
|
|
65
|
+
meta,
|
|
66
|
+
widgets
|
|
65
67
|
})
|
|
66
68
|
]
|
|
67
69
|
}),
|
|
@@ -71,13 +73,14 @@ function ApiOperation({ schema: doc, path, method, requestBodyValue, onRequestBo
|
|
|
71
73
|
response,
|
|
72
74
|
rootDoc,
|
|
73
75
|
value: responseValue,
|
|
74
|
-
meta
|
|
76
|
+
meta,
|
|
77
|
+
widgets
|
|
75
78
|
}, response.statusCode))]
|
|
76
79
|
})
|
|
77
80
|
]
|
|
78
81
|
});
|
|
79
82
|
}
|
|
80
|
-
function ApiParameters({ schema: doc, path, method, meta, overrides }) {
|
|
83
|
+
function ApiParameters({ schema: doc, path, method, meta, overrides, widgets }) {
|
|
81
84
|
const rootDoc = toDoc(doc);
|
|
82
85
|
const params = resolveParameters(rootDoc, path, method);
|
|
83
86
|
if (params.length === 0) return null;
|
|
@@ -87,11 +90,12 @@ function ApiParameters({ schema: doc, path, method, meta, overrides }) {
|
|
|
87
90
|
parameters: params,
|
|
88
91
|
rootDoc,
|
|
89
92
|
overrides,
|
|
90
|
-
meta
|
|
93
|
+
meta,
|
|
94
|
+
widgets
|
|
91
95
|
})]
|
|
92
96
|
});
|
|
93
97
|
}
|
|
94
|
-
function ApiRequestBody({ schema: doc, path, method, value, onChange, meta, fields }) {
|
|
98
|
+
function ApiRequestBody({ schema: doc, path, method, value, onChange, meta, fields, widgets }) {
|
|
95
99
|
const rootDoc = toDoc(doc);
|
|
96
100
|
const requestBody = resolveRequestBody(rootDoc, path, method);
|
|
97
101
|
if (requestBody?.schema === void 0) return null;
|
|
@@ -111,12 +115,13 @@ function ApiRequestBody({ schema: doc, path, method, value, onChange, meta, fiel
|
|
|
111
115
|
value,
|
|
112
116
|
onChange,
|
|
113
117
|
fields,
|
|
114
|
-
meta
|
|
118
|
+
meta,
|
|
119
|
+
widgets
|
|
115
120
|
})
|
|
116
121
|
]
|
|
117
122
|
});
|
|
118
123
|
}
|
|
119
|
-
function ApiResponse({ schema: doc, path, method, status, value, meta, fields }) {
|
|
124
|
+
function ApiResponse({ schema: doc, path, method, status, value, meta, fields, widgets }) {
|
|
120
125
|
const rootDoc = toDoc(doc);
|
|
121
126
|
const response = resolveResponse(rootDoc, path, method, status);
|
|
122
127
|
if (response.schema === void 0) return /* @__PURE__ */ jsxs("div", {
|
|
@@ -132,7 +137,8 @@ function ApiResponse({ schema: doc, path, method, status, value, meta, fields })
|
|
|
132
137
|
rootDoc,
|
|
133
138
|
value,
|
|
134
139
|
fields,
|
|
135
|
-
meta
|
|
140
|
+
meta,
|
|
141
|
+
widgets
|
|
136
142
|
});
|
|
137
143
|
}
|
|
138
144
|
function OperationHeader({ operation }) {
|
|
@@ -149,7 +155,7 @@ function OperationHeader({ operation }) {
|
|
|
149
155
|
})
|
|
150
156
|
] });
|
|
151
157
|
}
|
|
152
|
-
function ParameterList({ parameters, rootDoc, overrides, meta }) {
|
|
158
|
+
function ParameterList({ parameters, rootDoc, overrides, meta, widgets }) {
|
|
153
159
|
return /* @__PURE__ */ jsx(Fragment, { children: parameters.map((param) => /* @__PURE__ */ jsxs("div", {
|
|
154
160
|
"data-parameter": param.name,
|
|
155
161
|
children: [
|
|
@@ -161,11 +167,14 @@ function ParameterList({ parameters, rootDoc, overrides, meta }) {
|
|
|
161
167
|
"data-description": true,
|
|
162
168
|
children: param.description
|
|
163
169
|
}),
|
|
164
|
-
renderSchema(param.schema ?? { type: "string" }, rootDoc, {
|
|
170
|
+
renderSchema(param.schema ?? { type: "string" }, rootDoc, {
|
|
171
|
+
meta: buildParamMeta(param, overrides, meta),
|
|
172
|
+
widgets
|
|
173
|
+
})
|
|
165
174
|
]
|
|
166
175
|
}, param.name)) });
|
|
167
176
|
}
|
|
168
|
-
function ResponseCard({ response, rootDoc, value, fields, meta }) {
|
|
177
|
+
function ResponseCard({ response, rootDoc, value, fields, meta, widgets }) {
|
|
169
178
|
if (response.schema === void 0) return /* @__PURE__ */ jsxs("div", {
|
|
170
179
|
"data-status": response.statusCode,
|
|
171
180
|
children: [
|
|
@@ -185,7 +194,8 @@ function ResponseCard({ response, rootDoc, value, fields, meta }) {
|
|
|
185
194
|
meta: {
|
|
186
195
|
readOnly: true,
|
|
187
196
|
...meta
|
|
188
|
-
}
|
|
197
|
+
},
|
|
198
|
+
widgets
|
|
189
199
|
})
|
|
190
200
|
]
|
|
191
201
|
});
|
|
@@ -5,13 +5,33 @@ import { ReactNode } from "react";
|
|
|
5
5
|
import * as _$react_jsx_runtime0 from "react/jsx-runtime";
|
|
6
6
|
|
|
7
7
|
//#region src/react/SchemaComponent.d.ts
|
|
8
|
+
/**
|
|
9
|
+
* Widget map — maps component hints (from `.meta({ component })`) to render
|
|
10
|
+
* functions. Scoped at three levels:
|
|
11
|
+
*
|
|
12
|
+
* 1. **Per-instance** — `widgets` prop on `<SchemaComponent>`
|
|
13
|
+
* 2. **Context-scoped** — `widgets` prop on `<SchemaProvider>`
|
|
14
|
+
* 3. **Global** — `registerWidget()` (app-wide defaults)
|
|
15
|
+
*
|
|
16
|
+
* Resolution order: instance → context → global → resolver → headless.
|
|
17
|
+
*/
|
|
18
|
+
type WidgetMap = ReadonlyMap<string, (props: RenderProps) => unknown>;
|
|
8
19
|
declare function SchemaProvider({
|
|
9
20
|
resolver,
|
|
21
|
+
widgets,
|
|
10
22
|
children
|
|
11
23
|
}: {
|
|
12
|
-
resolver: ComponentResolver;
|
|
24
|
+
resolver: ComponentResolver; /** Scoped widgets available to all SchemaComponents in this subtree. */
|
|
25
|
+
widgets?: WidgetMap;
|
|
13
26
|
children: ReactNode;
|
|
14
27
|
}): _$react_jsx_runtime0.JSX.Element;
|
|
28
|
+
/**
|
|
29
|
+
* Register a widget globally. The widget is resolved when a schema field
|
|
30
|
+
* has `.meta({ component: name })`.
|
|
31
|
+
*
|
|
32
|
+
* For scoped registration, use the `widgets` prop on `<SchemaComponent>`
|
|
33
|
+
* or `<SchemaProvider>` instead.
|
|
34
|
+
*/
|
|
15
35
|
declare function registerWidget(name: string, render: (props: RenderProps) => unknown): void;
|
|
16
36
|
type InferFields<T, Ref extends string | undefined> = T extends z.ZodType ? FieldOverrides<z.infer<T>> : T extends {
|
|
17
37
|
openapi: unknown;
|
|
@@ -41,6 +61,8 @@ interface SchemaComponentProps<T = unknown, Ref extends string | undefined = und
|
|
|
41
61
|
writeOnly?: boolean;
|
|
42
62
|
/** Convenience: sets description on the root. */
|
|
43
63
|
description?: string;
|
|
64
|
+
/** Instance-scoped widgets — override context and global widgets. */
|
|
65
|
+
widgets?: WidgetMap;
|
|
44
66
|
}
|
|
45
67
|
declare function SchemaComponent<T = unknown, Ref extends string | undefined = undefined>({
|
|
46
68
|
schema: schemaInput,
|
|
@@ -54,9 +76,10 @@ declare function SchemaComponent<T = unknown, Ref extends string | undefined = u
|
|
|
54
76
|
meta: componentMeta,
|
|
55
77
|
readOnly,
|
|
56
78
|
writeOnly,
|
|
57
|
-
description
|
|
79
|
+
description,
|
|
80
|
+
widgets: instanceWidgets
|
|
58
81
|
}: SchemaComponentProps<T, Ref>): ReactNode;
|
|
59
|
-
declare function renderField(tree: WalkedField, value: unknown, onChange: (v: unknown) => void, userResolver: ComponentResolver | undefined, renderChild: (tree: WalkedField, value: unknown, onChange: (v: unknown) => void) => ReactNode): ReactNode;
|
|
82
|
+
declare function renderField(tree: WalkedField, value: unknown, onChange: (v: unknown) => void, userResolver: ComponentResolver | undefined, renderChild: (tree: WalkedField, value: unknown, onChange: (v: unknown) => void) => ReactNode, instanceWidgets?: WidgetMap, contextWidgets?: WidgetMap): ReactNode;
|
|
60
83
|
/**
|
|
61
84
|
* Infer the schema's output type for SchemaField path inference.
|
|
62
85
|
*/
|
|
@@ -93,4 +116,4 @@ declare function SchemaField<T = unknown, Ref extends string | undefined = undef
|
|
|
93
116
|
onValidationError
|
|
94
117
|
}: SchemaFieldProps<T, Ref, P>): ReactNode;
|
|
95
118
|
//#endregion
|
|
96
|
-
export { SchemaComponent, SchemaComponentProps, SchemaField, SchemaFieldProps, SchemaProvider, registerWidget, renderField };
|
|
119
|
+
export { SchemaComponent, SchemaComponentProps, SchemaField, SchemaFieldProps, SchemaProvider, WidgetMap, registerWidget, renderField };
|
|
@@ -23,18 +23,31 @@ import { jsx } from "react/jsx-runtime";
|
|
|
23
23
|
* - Runtime schemas → Record<string, FieldOverride> (no autocomplete)
|
|
24
24
|
*/
|
|
25
25
|
const UserResolverContext = createContext(void 0);
|
|
26
|
-
|
|
26
|
+
const WidgetsContext = createContext(void 0);
|
|
27
|
+
function SchemaProvider({ resolver, widgets, children }) {
|
|
27
28
|
return /* @__PURE__ */ jsx(UserResolverContext.Provider, {
|
|
28
29
|
value: resolver,
|
|
29
|
-
children
|
|
30
|
+
children: /* @__PURE__ */ jsx(WidgetsContext.Provider, {
|
|
31
|
+
value: widgets,
|
|
32
|
+
children
|
|
33
|
+
})
|
|
30
34
|
});
|
|
31
35
|
}
|
|
32
|
-
|
|
36
|
+
/** Global widget registry — app-wide defaults. */
|
|
37
|
+
const globalWidgets = /* @__PURE__ */ new Map();
|
|
38
|
+
/**
|
|
39
|
+
* Register a widget globally. The widget is resolved when a schema field
|
|
40
|
+
* has `.meta({ component: name })`.
|
|
41
|
+
*
|
|
42
|
+
* For scoped registration, use the `widgets` prop on `<SchemaComponent>`
|
|
43
|
+
* or `<SchemaProvider>` instead.
|
|
44
|
+
*/
|
|
33
45
|
function registerWidget(name, render) {
|
|
34
|
-
|
|
46
|
+
globalWidgets.set(name, render);
|
|
35
47
|
}
|
|
36
|
-
function SchemaComponent({ schema: schemaInput, ref: refInput, value, onChange, validate, onValidationError, onError, fields, meta: componentMeta, readOnly, writeOnly, description }) {
|
|
48
|
+
function SchemaComponent({ schema: schemaInput, ref: refInput, value, onChange, validate, onValidationError, onError, fields, meta: componentMeta, readOnly, writeOnly, description, widgets: instanceWidgets }) {
|
|
37
49
|
const userResolver = useContext(UserResolverContext);
|
|
50
|
+
const contextWidgets = useContext(WidgetsContext);
|
|
38
51
|
const mergedMeta = useMemo(() => {
|
|
39
52
|
const merged = { ...componentMeta };
|
|
40
53
|
if (readOnly === true) merged.readOnly = true;
|
|
@@ -82,9 +95,9 @@ function SchemaComponent({ schema: schemaInput, ref: refInput, value, onChange,
|
|
|
82
95
|
rootDocument
|
|
83
96
|
});
|
|
84
97
|
const renderChild = (childTree, childValue, childOnChange) => {
|
|
85
|
-
return renderField(childTree, childValue, childOnChange, userResolver, renderChild);
|
|
98
|
+
return renderField(childTree, childValue, childOnChange, userResolver, renderChild, instanceWidgets, contextWidgets);
|
|
86
99
|
};
|
|
87
|
-
return renderField(tree, value ?? tree.defaultValue, handleChange, userResolver, renderChild);
|
|
100
|
+
return renderField(tree, value ?? tree.defaultValue, handleChange, userResolver, renderChild, instanceWidgets, contextWidgets);
|
|
88
101
|
}
|
|
89
102
|
function runValidation(zodSchema, jsonSchema, value, onError) {
|
|
90
103
|
if (zodSchema !== void 0 && isObject(zodSchema)) {
|
|
@@ -107,10 +120,10 @@ function runValidation(zodSchema, jsonSchema, value, onError) {
|
|
|
107
120
|
}
|
|
108
121
|
}
|
|
109
122
|
}
|
|
110
|
-
function renderField(tree, value, onChange, userResolver, renderChild) {
|
|
123
|
+
function renderField(tree, value, onChange, userResolver, renderChild, instanceWidgets, contextWidgets) {
|
|
111
124
|
const componentHint = tree.meta.component;
|
|
112
125
|
if (typeof componentHint === "string") {
|
|
113
|
-
const widget =
|
|
126
|
+
const widget = instanceWidgets?.get(componentHint) ?? contextWidgets?.get(componentHint) ?? globalWidgets.get(componentHint);
|
|
114
127
|
if (widget !== void 0) {
|
|
115
128
|
const result = widget(buildRenderProps(tree, value, onChange, renderChild));
|
|
116
129
|
if (result !== void 0 && result !== null) {
|
|
@@ -160,6 +173,7 @@ function buildRenderProps(tree, value, onChange, renderChild) {
|
|
|
160
173
|
}
|
|
161
174
|
function SchemaField({ path, schema: schemaInput, ref: refInput, value, onChange, meta: fieldMeta, validate, onValidationError }) {
|
|
162
175
|
const userResolver = useContext(UserResolverContext);
|
|
176
|
+
const contextWidgets = useContext(WidgetsContext);
|
|
163
177
|
let jsonSchema;
|
|
164
178
|
let zodSchema;
|
|
165
179
|
let rootMeta;
|
|
@@ -197,9 +211,9 @@ function SchemaField({ path, schema: schemaInput, ref: refInput, value, onChange
|
|
|
197
211
|
onValidationError
|
|
198
212
|
]);
|
|
199
213
|
const renderChild = (childTree, childValue, childOnChange) => {
|
|
200
|
-
return renderField(childTree, childValue, childOnChange, userResolver, renderChild);
|
|
214
|
+
return renderField(childTree, childValue, childOnChange, userResolver, renderChild, void 0, contextWidgets);
|
|
201
215
|
};
|
|
202
|
-
return renderField(fieldTree, fieldValue, handleChange, userResolver, renderChild);
|
|
216
|
+
return renderField(fieldTree, fieldValue, handleChange, userResolver, renderChild, void 0, contextWidgets);
|
|
203
217
|
}
|
|
204
218
|
function resolvePath(tree, path) {
|
|
205
219
|
if (path.length === 0) return tree;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { b as ComponentResolver, m as SchemaMeta } from "../types-DDCD6Xnx.mjs";
|
|
2
|
+
import { WidgetMap } from "./SchemaComponent.mjs";
|
|
2
3
|
import { ReactNode } from "react";
|
|
3
4
|
|
|
4
5
|
//#region src/react/SchemaView.d.ts
|
|
@@ -21,6 +22,8 @@ interface SchemaViewProps {
|
|
|
21
22
|
* Falls back to the headless resolver if omitted.
|
|
22
23
|
*/
|
|
23
24
|
resolver?: ComponentResolver;
|
|
25
|
+
/** Instance-scoped widgets. */
|
|
26
|
+
widgets?: WidgetMap;
|
|
24
27
|
}
|
|
25
28
|
/**
|
|
26
29
|
* Server-safe schema renderer — no hooks, no context, no state.
|
|
@@ -35,7 +38,8 @@ declare function SchemaView({
|
|
|
35
38
|
fields,
|
|
36
39
|
meta: componentMeta,
|
|
37
40
|
description,
|
|
38
|
-
resolver
|
|
41
|
+
resolver,
|
|
42
|
+
widgets
|
|
39
43
|
}: SchemaViewProps): ReactNode;
|
|
40
44
|
//#endregion
|
|
41
45
|
export { SchemaView, SchemaViewProps };
|
|
@@ -37,7 +37,7 @@ function noop() {}
|
|
|
37
37
|
* Always renders in read-only mode. For editable forms, use
|
|
38
38
|
* `<SchemaComponent>` with `"use client"`.
|
|
39
39
|
*/
|
|
40
|
-
function SchemaView({ schema: schemaInput, ref: refInput, value, fields, meta: componentMeta, description, resolver }) {
|
|
40
|
+
function SchemaView({ schema: schemaInput, ref: refInput, value, fields, meta: componentMeta, description, resolver, widgets }) {
|
|
41
41
|
const mergedMeta = {
|
|
42
42
|
...componentMeta,
|
|
43
43
|
readOnly: true
|
|
@@ -61,10 +61,31 @@ function SchemaView({ schema: schemaInput, ref: refInput, value, fields, meta: c
|
|
|
61
61
|
rootDocument
|
|
62
62
|
});
|
|
63
63
|
const userResolver = resolver !== void 0 ? mergeResolvers(resolver, headlessResolver) : headlessResolver;
|
|
64
|
-
const renderChild = (childTree, childValue) => renderFieldServer(childTree, childValue, userResolver, renderChild);
|
|
65
|
-
return renderFieldServer(tree, value ?? tree.defaultValue, userResolver, renderChild);
|
|
64
|
+
const renderChild = (childTree, childValue) => renderFieldServer(childTree, childValue, userResolver, renderChild, widgets);
|
|
65
|
+
return renderFieldServer(tree, value ?? tree.defaultValue, userResolver, renderChild, widgets);
|
|
66
66
|
}
|
|
67
|
-
function renderFieldServer(tree, value, resolver, renderChild) {
|
|
67
|
+
function renderFieldServer(tree, value, resolver, renderChild, widgets) {
|
|
68
|
+
const componentHint = tree.meta.component;
|
|
69
|
+
if (typeof componentHint === "string") {
|
|
70
|
+
const widget = widgets?.get(componentHint);
|
|
71
|
+
if (widget !== void 0) {
|
|
72
|
+
const result = widget({
|
|
73
|
+
value,
|
|
74
|
+
onChange: noop,
|
|
75
|
+
readOnly: true,
|
|
76
|
+
writeOnly: false,
|
|
77
|
+
meta: tree.meta,
|
|
78
|
+
constraints: tree.constraints,
|
|
79
|
+
path: "",
|
|
80
|
+
tree,
|
|
81
|
+
renderChild: (childTree, childValue) => renderChild(childTree, childValue)
|
|
82
|
+
});
|
|
83
|
+
if (result !== void 0 && result !== null) {
|
|
84
|
+
if (isValidElement(result)) return result;
|
|
85
|
+
if (typeof result === "string" || typeof result === "number") return result;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
68
89
|
const renderFn = getRenderFunction(tree.type, resolver);
|
|
69
90
|
if (renderFn !== void 0) {
|
|
70
91
|
const props = {
|