schema-components 1.16.2 → 1.17.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/dist/core/renderer.d.mts +1 -1
- package/dist/html/a11y.d.mts +1 -1
- package/dist/html/renderToHtml.d.mts +1 -1
- package/dist/html/renderToHtmlStream.d.mts +1 -1
- package/dist/html/renderers.d.mts +1 -1
- package/dist/html/streamRenderers.d.mts +1 -1
- package/dist/openapi/components.mjs +42 -13
- package/dist/react/SchemaComponent.d.mts +32 -4
- package/dist/react/SchemaComponent.mjs +50 -15
- package/dist/react/SchemaView.d.mts +9 -2
- package/dist/react/SchemaView.mjs +17 -11
- package/dist/react/headless.d.mts +1 -1
- package/dist/react/headlessRenderers.d.mts +41 -2
- package/dist/react/headlessRenderers.mjs +171 -53
- package/dist/{renderer-BdSqllx5.d.mts → renderer-B3s8o2B8.d.mts} +10 -1
- package/dist/themes/mantine.d.mts +6 -1
- package/dist/themes/mantine.mjs +40 -8
- package/dist/themes/mui.d.mts +1 -1
- package/dist/themes/mui.mjs +17 -3
- package/dist/themes/radix.d.mts +1 -1
- package/dist/themes/radix.mjs +41 -9
- package/dist/themes/shadcn.d.mts +1 -1
- package/dist/themes/shadcn.mjs +22 -5
- package/package.json +5 -2
package/dist/core/renderer.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as HtmlRenderProps, c as RenderFunction, d as getRenderFunction, f as mergeHtmlResolvers, i as HtmlRenderFunction, l as RenderProps, m as typeToKey, n as BaseFieldProps, o as HtmlResolver, p as mergeResolvers, r as ComponentResolver, s as RESOLVER_KEYS, t as AllConstraints, u as getHtmlRenderFn } from "../renderer-
|
|
1
|
+
import { a as HtmlRenderProps, c as RenderFunction, d as getRenderFunction, f as mergeHtmlResolvers, i as HtmlRenderFunction, l as RenderProps, m as typeToKey, n as BaseFieldProps, o as HtmlResolver, p as mergeResolvers, r as ComponentResolver, s as RESOLVER_KEYS, t as AllConstraints, u as getHtmlRenderFn } from "../renderer-B3s8o2B8.mjs";
|
|
2
2
|
export { AllConstraints, BaseFieldProps, ComponentResolver, HtmlRenderFunction, HtmlRenderProps, HtmlResolver, RESOLVER_KEYS, RenderFunction, RenderProps, getHtmlRenderFn, getRenderFunction, mergeHtmlResolvers, mergeResolvers, typeToKey };
|
package/dist/html/a11y.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { M as WalkedField } from "../types-D_5ST7SS.mjs";
|
|
2
|
-
import { t as AllConstraints } from "../renderer-
|
|
2
|
+
import { t as AllConstraints } from "../renderer-B3s8o2B8.mjs";
|
|
3
3
|
import { HtmlAttributes, HtmlNode } from "./html.mjs";
|
|
4
4
|
|
|
5
5
|
//#region src/html/a11y.d.ts
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { M as WalkedField } from "../types-D_5ST7SS.mjs";
|
|
2
|
-
import { o as HtmlResolver } from "../renderer-
|
|
2
|
+
import { o as HtmlResolver } from "../renderer-B3s8o2B8.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/html/renderers.d.ts
|
|
5
5
|
declare function dateInputType(format: string | undefined): string | undefined;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { M as WalkedField } from "../types-D_5ST7SS.mjs";
|
|
2
|
-
import { o as HtmlResolver } from "../renderer-
|
|
2
|
+
import { o as HtmlResolver } from "../renderer-B3s8o2B8.mjs";
|
|
3
3
|
import { HtmlElement } from "./html.mjs";
|
|
4
4
|
|
|
5
5
|
//#region src/html/streamRenderers.d.ts
|
|
@@ -7,10 +7,24 @@ import { ApiLinks } from "./ApiLinks.mjs";
|
|
|
7
7
|
import { ApiResponseHeaders } from "./ApiResponseHeaders.mjs";
|
|
8
8
|
import { ApiSecurity } from "./ApiSecurity.mjs";
|
|
9
9
|
import { getLinks, getSecurityRequirements, getSecuritySchemes, listCallbacks } from "./parser.mjs";
|
|
10
|
-
import { renderField } from "../react/SchemaComponent.mjs";
|
|
10
|
+
import { joinPath, renderField, sanitisePrefix } from "../react/SchemaComponent.mjs";
|
|
11
11
|
import { getParsed, resolveOperation, resolveParameters, resolveRequestBody, resolveResponse, toDoc } from "./resolve.mjs";
|
|
12
12
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
13
|
+
import { useId } from "react";
|
|
13
14
|
//#region src/openapi/components.tsx
|
|
15
|
+
/**
|
|
16
|
+
* OpenAPI React components with type-safe generics.
|
|
17
|
+
*
|
|
18
|
+
* Render API operations, parameters, request bodies, and response schemas
|
|
19
|
+
* from OpenAPI 3.x documents. When the document is typed `as const`,
|
|
20
|
+
* the `fields` / `overrides` props get full autocomplete.
|
|
21
|
+
*
|
|
22
|
+
* Type safety is enforced at the outer component's props level via
|
|
23
|
+
* conditional types (InferRequestBodyFields, InferResponseFields,
|
|
24
|
+
* InferParameterOverrides). Internally, schemas are extracted and
|
|
25
|
+
* rendered via the walker + headless resolver directly, bypassing
|
|
26
|
+
* SchemaComponent to avoid deferred-conditional-type compatibility issues.
|
|
27
|
+
*/
|
|
14
28
|
function noop() {}
|
|
15
29
|
function renderSchema(schema, rootDocument, options) {
|
|
16
30
|
let jsonSchema;
|
|
@@ -32,8 +46,11 @@ function renderSchema(schema, rootDocument, options) {
|
|
|
32
46
|
rootDocument
|
|
33
47
|
};
|
|
34
48
|
const tree = walk(jsonSchema, walkOpts);
|
|
35
|
-
const
|
|
36
|
-
|
|
49
|
+
const makeRenderChild = (parentPath) => (childTree, childValue, childOnChange, pathSuffix) => {
|
|
50
|
+
const childPath = joinPath(parentPath, pathSuffix);
|
|
51
|
+
return renderField(childTree, childValue, childOnChange, void 0, makeRenderChild(childPath), childPath, options.widgets);
|
|
52
|
+
};
|
|
53
|
+
return renderField(tree, options.value, options.onChange ?? noop, void 0, makeRenderChild(options.rootPath), options.rootPath, options.widgets);
|
|
37
54
|
}
|
|
38
55
|
function ApiOperation({ schema: doc, path, method, requestBodyValue, onRequestBodyChange, responseValue, meta, requestBodyFields, widgets }) {
|
|
39
56
|
const rootDoc = toDoc(doc);
|
|
@@ -42,6 +59,7 @@ function ApiOperation({ schema: doc, path, method, requestBodyValue, onRequestBo
|
|
|
42
59
|
const securityReqs = getSecurityRequirements(parsed, path, method);
|
|
43
60
|
const securitySchemes = getSecuritySchemes(parsed);
|
|
44
61
|
const callbacks = listCallbacks(parsed, path, method);
|
|
62
|
+
const instancePrefix = sanitisePrefix(useId());
|
|
45
63
|
return /* @__PURE__ */ jsxs("section", {
|
|
46
64
|
"data-operation": `${method.toUpperCase()} ${path}`,
|
|
47
65
|
children: [
|
|
@@ -57,7 +75,8 @@ function ApiOperation({ schema: doc, path, method, requestBodyValue, onRequestBo
|
|
|
57
75
|
parameters: resolved.parameters,
|
|
58
76
|
rootDoc,
|
|
59
77
|
meta,
|
|
60
|
-
widgets
|
|
78
|
+
widgets,
|
|
79
|
+
idPrefix: joinPath(instancePrefix, "params")
|
|
61
80
|
})]
|
|
62
81
|
}),
|
|
63
82
|
resolved.requestBody?.schema !== void 0 && /* @__PURE__ */ jsxs("section", {
|
|
@@ -77,7 +96,8 @@ function ApiOperation({ schema: doc, path, method, requestBodyValue, onRequestBo
|
|
|
77
96
|
onChange: onRequestBodyChange,
|
|
78
97
|
fields: requestBodyFields,
|
|
79
98
|
meta,
|
|
80
|
-
widgets
|
|
99
|
+
widgets,
|
|
100
|
+
rootPath: joinPath(instancePrefix, "requestBody")
|
|
81
101
|
})
|
|
82
102
|
]
|
|
83
103
|
}),
|
|
@@ -90,7 +110,8 @@ function ApiOperation({ schema: doc, path, method, requestBodyValue, onRequestBo
|
|
|
90
110
|
meta,
|
|
91
111
|
widgets,
|
|
92
112
|
path,
|
|
93
|
-
method
|
|
113
|
+
method,
|
|
114
|
+
idPrefix: joinPath(instancePrefix, `response-${response.statusCode}`)
|
|
94
115
|
}, response.statusCode))]
|
|
95
116
|
})
|
|
96
117
|
]
|
|
@@ -99,6 +120,7 @@ function ApiOperation({ schema: doc, path, method, requestBodyValue, onRequestBo
|
|
|
99
120
|
function ApiParameters({ schema: doc, path, method, meta, overrides, widgets }) {
|
|
100
121
|
const rootDoc = toDoc(doc);
|
|
101
122
|
const params = resolveParameters(rootDoc, path, method);
|
|
123
|
+
const instancePrefix = sanitisePrefix(useId());
|
|
102
124
|
if (params.length === 0) return null;
|
|
103
125
|
return /* @__PURE__ */ jsxs("section", {
|
|
104
126
|
"data-parameters": true,
|
|
@@ -107,13 +129,15 @@ function ApiParameters({ schema: doc, path, method, meta, overrides, widgets })
|
|
|
107
129
|
rootDoc,
|
|
108
130
|
overrides,
|
|
109
131
|
meta,
|
|
110
|
-
widgets
|
|
132
|
+
widgets,
|
|
133
|
+
idPrefix: instancePrefix
|
|
111
134
|
})]
|
|
112
135
|
});
|
|
113
136
|
}
|
|
114
137
|
function ApiRequestBody({ schema: doc, path, method, value, onChange, meta, fields, widgets }) {
|
|
115
138
|
const rootDoc = toDoc(doc);
|
|
116
139
|
const requestBody = resolveRequestBody(rootDoc, path, method);
|
|
140
|
+
const instancePrefix = sanitisePrefix(useId());
|
|
117
141
|
if (requestBody?.schema === void 0) return null;
|
|
118
142
|
return /* @__PURE__ */ jsxs("section", {
|
|
119
143
|
"data-request-body": true,
|
|
@@ -132,7 +156,8 @@ function ApiRequestBody({ schema: doc, path, method, value, onChange, meta, fiel
|
|
|
132
156
|
onChange,
|
|
133
157
|
fields,
|
|
134
158
|
meta,
|
|
135
|
-
widgets
|
|
159
|
+
widgets,
|
|
160
|
+
rootPath: instancePrefix
|
|
136
161
|
})
|
|
137
162
|
]
|
|
138
163
|
});
|
|
@@ -140,6 +165,7 @@ function ApiRequestBody({ schema: doc, path, method, value, onChange, meta, fiel
|
|
|
140
165
|
function ApiResponse({ schema: doc, path, method, status, value, meta, fields, widgets }) {
|
|
141
166
|
const rootDoc = toDoc(doc);
|
|
142
167
|
const response = resolveResponse(rootDoc, path, method, status);
|
|
168
|
+
const instancePrefix = sanitisePrefix(useId());
|
|
143
169
|
if (response.schema === void 0) return /* @__PURE__ */ jsxs("div", {
|
|
144
170
|
"data-status": status,
|
|
145
171
|
children: [
|
|
@@ -156,7 +182,8 @@ function ApiResponse({ schema: doc, path, method, status, value, meta, fields, w
|
|
|
156
182
|
meta,
|
|
157
183
|
widgets,
|
|
158
184
|
path,
|
|
159
|
-
method
|
|
185
|
+
method,
|
|
186
|
+
idPrefix: instancePrefix
|
|
160
187
|
});
|
|
161
188
|
}
|
|
162
189
|
function OperationHeader({ operation }) {
|
|
@@ -173,7 +200,7 @@ function OperationHeader({ operation }) {
|
|
|
173
200
|
})
|
|
174
201
|
] });
|
|
175
202
|
}
|
|
176
|
-
function ParameterList({ parameters, rootDoc, overrides, meta, widgets }) {
|
|
203
|
+
function ParameterList({ parameters, rootDoc, overrides, meta, widgets, idPrefix }) {
|
|
177
204
|
return /* @__PURE__ */ jsx(Fragment, { children: parameters.map((param) => /* @__PURE__ */ jsxs("div", {
|
|
178
205
|
"data-parameter": param.name,
|
|
179
206
|
children: [
|
|
@@ -187,12 +214,13 @@ function ParameterList({ parameters, rootDoc, overrides, meta, widgets }) {
|
|
|
187
214
|
}),
|
|
188
215
|
renderSchema(param.schema ?? { type: "string" }, rootDoc, {
|
|
189
216
|
meta: buildParamMeta(param, overrides, meta),
|
|
190
|
-
widgets
|
|
217
|
+
widgets,
|
|
218
|
+
rootPath: joinPath(idPrefix, param.name)
|
|
191
219
|
})
|
|
192
220
|
]
|
|
193
221
|
}, param.name)) });
|
|
194
222
|
}
|
|
195
|
-
function ResponseCard({ response, rootDoc, value, fields, meta, widgets, path, method }) {
|
|
223
|
+
function ResponseCard({ response, rootDoc, value, fields, meta, widgets, path, method, idPrefix }) {
|
|
196
224
|
if (response.schema === void 0) return /* @__PURE__ */ jsxs("div", {
|
|
197
225
|
"data-status": response.statusCode,
|
|
198
226
|
children: [
|
|
@@ -217,7 +245,8 @@ function ResponseCard({ response, rootDoc, value, fields, meta, widgets, path, m
|
|
|
217
245
|
readOnly: true,
|
|
218
246
|
...meta
|
|
219
247
|
},
|
|
220
|
-
widgets
|
|
248
|
+
widgets,
|
|
249
|
+
rootPath: idPrefix
|
|
221
250
|
}),
|
|
222
251
|
/* @__PURE__ */ jsx(ApiResponseHeaders, { headers: response.headers }),
|
|
223
252
|
/* @__PURE__ */ jsx(ApiLinks, { links })
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { M as WalkedField, T as SchemaMeta, d as FieldOverrides, u as FieldOverride } from "../types-D_5ST7SS.mjs";
|
|
2
2
|
import { t as Diagnostic } from "../diagnostics-DzbZmcLI.mjs";
|
|
3
3
|
import { t as SchemaError } from "../errors-C5zRC2PU.mjs";
|
|
4
|
-
import { l as RenderProps, r as ComponentResolver } from "../renderer-
|
|
4
|
+
import { l as RenderProps, r as ComponentResolver } from "../renderer-B3s8o2B8.mjs";
|
|
5
5
|
import { c as ResolveOpenAPIRef, s as PathOfType, t as FromJSONSchema } from "../typeInference-k7FXfTVO.mjs";
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
import * as _$react_jsx_runtime0 from "react/jsx-runtime";
|
|
@@ -70,6 +70,13 @@ interface SchemaComponentProps<T = unknown, Ref extends string | undefined = und
|
|
|
70
70
|
description?: string;
|
|
71
71
|
/** Instance-scoped widgets — override context and global widgets. */
|
|
72
72
|
widgets?: WidgetMap;
|
|
73
|
+
/**
|
|
74
|
+
* Prefix used for every input `id`/label `htmlFor` in this component
|
|
75
|
+
* subtree. Defaults to a per-instance value from `useId()` so multiple
|
|
76
|
+
* `<SchemaComponent>` instances on the same page never collide. Override
|
|
77
|
+
* for deterministic ids in screenshot tests.
|
|
78
|
+
*/
|
|
79
|
+
idPrefix?: string;
|
|
73
80
|
}
|
|
74
81
|
declare function SchemaComponent<T = unknown, Ref extends string | undefined = undefined>({
|
|
75
82
|
schema: schemaInput,
|
|
@@ -86,9 +93,30 @@ declare function SchemaComponent<T = unknown, Ref extends string | undefined = u
|
|
|
86
93
|
readOnly,
|
|
87
94
|
writeOnly,
|
|
88
95
|
description,
|
|
89
|
-
widgets: instanceWidgets
|
|
96
|
+
widgets: instanceWidgets,
|
|
97
|
+
idPrefix
|
|
90
98
|
}: SchemaComponentProps<T, Ref>): ReactNode;
|
|
91
|
-
|
|
99
|
+
/**
|
|
100
|
+
* Default root-path sentinel used when no `idPrefix` is supplied AND the
|
|
101
|
+
* component is rendered outside a React tree (e.g. server-side bundling
|
|
102
|
+
* test harnesses). Production callers receive a `useId()`-derived prefix
|
|
103
|
+
* that is unique per instance.
|
|
104
|
+
*/
|
|
105
|
+
declare const ROOT_PATH = "root";
|
|
106
|
+
/**
|
|
107
|
+
* Append a child path suffix to a parent path. When the suffix is omitted
|
|
108
|
+
* (e.g. transparent wrappers like union options), the parent path is
|
|
109
|
+
* returned unchanged so the child inherits the parent's id.
|
|
110
|
+
*/
|
|
111
|
+
declare function joinPath(parent: string, suffix: string | undefined): string;
|
|
112
|
+
/**
|
|
113
|
+
* Normalise a `useId()` value into a DOM-id-safe prefix. React's `useId`
|
|
114
|
+
* returns values containing `:` characters (e.g. `«:r0:»`) which are
|
|
115
|
+
* invalid in CSS selectors. Replace any run of non-alphanumeric characters
|
|
116
|
+
* with a single hyphen and trim leading/trailing hyphens.
|
|
117
|
+
*/
|
|
118
|
+
declare function sanitisePrefix(value: string): string;
|
|
119
|
+
declare function renderField(tree: WalkedField, value: unknown, onChange: (v: unknown) => void, userResolver: ComponentResolver | undefined, renderChild: (tree: WalkedField, value: unknown, onChange: (v: unknown) => void, pathSuffix?: string) => ReactNode, path: string, instanceWidgets?: WidgetMap, contextWidgets?: WidgetMap, depth?: number): ReactNode;
|
|
92
120
|
/**
|
|
93
121
|
* Infer the schema's output type for SchemaField path inference.
|
|
94
122
|
*/
|
|
@@ -125,4 +153,4 @@ declare function SchemaField<T = unknown, Ref extends string | undefined = undef
|
|
|
125
153
|
onValidationError
|
|
126
154
|
}: SchemaFieldProps<T, Ref, P>): ReactNode;
|
|
127
155
|
//#endregion
|
|
128
|
-
export { SchemaComponent, SchemaComponentProps, SchemaField, SchemaFieldProps, SchemaProvider, WidgetMap, registerWidget, renderField };
|
|
156
|
+
export { ROOT_PATH, SchemaComponent, SchemaComponentProps, SchemaField, SchemaFieldProps, SchemaProvider, WidgetMap, joinPath, registerWidget, renderField, sanitisePrefix };
|
|
@@ -8,7 +8,7 @@ import { headlessResolver } from "./headless.mjs";
|
|
|
8
8
|
import { resolvePath, resolveValue, setNestedValue } from "./fieldPath.mjs";
|
|
9
9
|
import { z } from "zod";
|
|
10
10
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
11
|
-
import { createContext, isValidElement, useCallback, useContext, useMemo } from "react";
|
|
11
|
+
import { createContext, isValidElement, useCallback, useContext, useId, useMemo } from "react";
|
|
12
12
|
//#region src/react/SchemaComponent.tsx
|
|
13
13
|
/**
|
|
14
14
|
* <SchemaComponent> — renders UI from Zod, JSON Schema, or OpenAPI schemas.
|
|
@@ -46,9 +46,11 @@ const globalWidgets = /* @__PURE__ */ new Map();
|
|
|
46
46
|
function registerWidget(name, render) {
|
|
47
47
|
globalWidgets.set(name, render);
|
|
48
48
|
}
|
|
49
|
-
function SchemaComponent({ schema: schemaInput, ref: refInput, value, onChange, validate, onValidationError, onError, onDiagnostic, strict, fields, meta: componentMeta, readOnly, writeOnly, description, widgets: instanceWidgets }) {
|
|
49
|
+
function SchemaComponent({ schema: schemaInput, ref: refInput, value, onChange, validate, onValidationError, onError, onDiagnostic, strict, fields, meta: componentMeta, readOnly, writeOnly, description, widgets: instanceWidgets, idPrefix }) {
|
|
50
50
|
const userResolver = useContext(UserResolverContext);
|
|
51
51
|
const contextWidgets = useContext(WidgetsContext);
|
|
52
|
+
const generatedId = useId();
|
|
53
|
+
const rootPath = idPrefix ?? sanitisePrefix(generatedId);
|
|
52
54
|
const mergedMeta = useMemo(() => {
|
|
53
55
|
const merged = { ...componentMeta };
|
|
54
56
|
if (readOnly === true) merged.readOnly = true;
|
|
@@ -108,11 +110,40 @@ function SchemaComponent({ schema: schemaInput, ref: refInput, value, onChange,
|
|
|
108
110
|
...diagnostics !== void 0 ? { diagnostics } : {}
|
|
109
111
|
};
|
|
110
112
|
const tree = walk(jsonSchema, walkOptions);
|
|
111
|
-
const makeRenderChild = (currentDepth) => (childTree, childValue, childOnChange) => {
|
|
112
|
-
|
|
113
|
+
const makeRenderChild = (currentDepth, parentPath) => (childTree, childValue, childOnChange, pathSuffix) => {
|
|
114
|
+
const childPath = joinPath(parentPath, pathSuffix);
|
|
115
|
+
return renderField(childTree, childValue, childOnChange, userResolver, makeRenderChild(currentDepth + 1, childPath), childPath, instanceWidgets, contextWidgets, currentDepth + 1);
|
|
113
116
|
};
|
|
114
|
-
const renderChild = makeRenderChild(0);
|
|
115
|
-
return renderField(tree, value ?? tree.defaultValue, handleChange, userResolver, renderChild, instanceWidgets, contextWidgets, 0);
|
|
117
|
+
const renderChild = makeRenderChild(0, rootPath);
|
|
118
|
+
return renderField(tree, value ?? tree.defaultValue, handleChange, userResolver, renderChild, rootPath, instanceWidgets, contextWidgets, 0);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Default root-path sentinel used when no `idPrefix` is supplied AND the
|
|
122
|
+
* component is rendered outside a React tree (e.g. server-side bundling
|
|
123
|
+
* test harnesses). Production callers receive a `useId()`-derived prefix
|
|
124
|
+
* that is unique per instance.
|
|
125
|
+
*/
|
|
126
|
+
const ROOT_PATH = "root";
|
|
127
|
+
/**
|
|
128
|
+
* Append a child path suffix to a parent path. When the suffix is omitted
|
|
129
|
+
* (e.g. transparent wrappers like union options), the parent path is
|
|
130
|
+
* returned unchanged so the child inherits the parent's id.
|
|
131
|
+
*/
|
|
132
|
+
function joinPath(parent, suffix) {
|
|
133
|
+
if (suffix === void 0 || suffix.length === 0) return parent;
|
|
134
|
+
if (parent.length === 0) return suffix;
|
|
135
|
+
return `${parent}.${suffix}`;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Normalise a `useId()` value into a DOM-id-safe prefix. React's `useId`
|
|
139
|
+
* returns values containing `:` characters (e.g. `«:r0:»`) which are
|
|
140
|
+
* invalid in CSS selectors. Replace any run of non-alphanumeric characters
|
|
141
|
+
* with a single hyphen and trim leading/trailing hyphens.
|
|
142
|
+
*/
|
|
143
|
+
function sanitisePrefix(value) {
|
|
144
|
+
const sanitised = value.replace(/[^a-zA-Z0-9_]+/g, "-").replace(/^-+|-+$/g, "");
|
|
145
|
+
if (sanitised.length === 0) throw new Error(`Cannot derive a DOM-safe id prefix from "${value}". Pass an explicit idPrefix prop.`);
|
|
146
|
+
return sanitised;
|
|
116
147
|
}
|
|
117
148
|
function runValidation(zodSchema, jsonSchema, value) {
|
|
118
149
|
if (zodSchema !== void 0 && isObject(zodSchema)) {
|
|
@@ -134,7 +165,8 @@ function runValidation(zodSchema, jsonSchema, value) {
|
|
|
134
165
|
}
|
|
135
166
|
/** Maximum rendering depth before treating a field as recursive. */
|
|
136
167
|
const MAX_RENDER_DEPTH = 10;
|
|
137
|
-
function renderField(tree, value, onChange, userResolver, renderChild, instanceWidgets, contextWidgets, depth = 0) {
|
|
168
|
+
function renderField(tree, value, onChange, userResolver, renderChild, path, instanceWidgets, contextWidgets, depth = 0) {
|
|
169
|
+
if (path.length === 0) throw new Error("renderField requires a non-empty path. Pass ROOT_PATH for the root field and use renderChild's pathSuffix to derive child paths.");
|
|
138
170
|
if (depth >= MAX_RENDER_DEPTH) {
|
|
139
171
|
const refTarget = tree.type === "recursive" ? tree.refTarget : "";
|
|
140
172
|
return /* @__PURE__ */ jsx("fieldset", { children: /* @__PURE__ */ jsxs("em", { children: [
|
|
@@ -147,7 +179,7 @@ function renderField(tree, value, onChange, userResolver, renderChild, instanceW
|
|
|
147
179
|
if (typeof componentHint === "string") {
|
|
148
180
|
const widget = instanceWidgets?.get(componentHint) ?? contextWidgets?.get(componentHint) ?? globalWidgets.get(componentHint);
|
|
149
181
|
if (widget !== void 0) {
|
|
150
|
-
const result = widget(buildRenderProps(tree, value, onChange, renderChild));
|
|
182
|
+
const result = widget(buildRenderProps(tree, value, onChange, renderChild, path));
|
|
151
183
|
if (result !== void 0 && result !== null) {
|
|
152
184
|
if (isValidElement(result)) return result;
|
|
153
185
|
if (typeof result === "string" || typeof result === "number") return result;
|
|
@@ -160,7 +192,7 @@ function renderField(tree, value, onChange, userResolver, renderChild, instanceW
|
|
|
160
192
|
if (renderFn !== void 0) {
|
|
161
193
|
let result;
|
|
162
194
|
try {
|
|
163
|
-
result = renderFn(buildRenderProps(tree, value, onChange, renderChild));
|
|
195
|
+
result = renderFn(buildRenderProps(tree, value, onChange, renderChild, path));
|
|
164
196
|
} catch (err) {
|
|
165
197
|
throw new SchemaRenderError(err instanceof Error ? err.message : `Render function threw for type "${tree.type}"`, tree, tree.type, err);
|
|
166
198
|
}
|
|
@@ -171,7 +203,7 @@ function renderField(tree, value, onChange, userResolver, renderChild, instanceW
|
|
|
171
203
|
if (value === void 0 || value === null) return /* @__PURE__ */ jsx("span", { children: "—" });
|
|
172
204
|
return /* @__PURE__ */ jsx("span", { children: typeof value === "string" ? value : JSON.stringify(value) });
|
|
173
205
|
}
|
|
174
|
-
function buildRenderProps(tree, value, onChange, renderChild) {
|
|
206
|
+
function buildRenderProps(tree, value, onChange, renderChild, path) {
|
|
175
207
|
const props = {
|
|
176
208
|
value,
|
|
177
209
|
onChange,
|
|
@@ -179,7 +211,7 @@ function buildRenderProps(tree, value, onChange, renderChild) {
|
|
|
179
211
|
writeOnly: tree.editability === "input",
|
|
180
212
|
meta: tree.meta,
|
|
181
213
|
constraints: tree.constraints,
|
|
182
|
-
path
|
|
214
|
+
path,
|
|
183
215
|
tree,
|
|
184
216
|
renderChild
|
|
185
217
|
};
|
|
@@ -203,6 +235,7 @@ function buildRenderProps(tree, value, onChange, renderChild) {
|
|
|
203
235
|
function SchemaField({ path, schema: schemaInput, ref: refInput, value, onChange, meta: fieldMeta, validate, onValidationError }) {
|
|
204
236
|
const userResolver = useContext(UserResolverContext);
|
|
205
237
|
const contextWidgets = useContext(WidgetsContext);
|
|
238
|
+
const generatedId = useId();
|
|
206
239
|
let jsonSchema;
|
|
207
240
|
let zodSchema;
|
|
208
241
|
let rootMeta;
|
|
@@ -240,10 +273,12 @@ function SchemaField({ path, schema: schemaInput, ref: refInput, value, onChange
|
|
|
240
273
|
onChange,
|
|
241
274
|
onValidationError
|
|
242
275
|
]);
|
|
243
|
-
const makeRenderChild = (currentDepth) => (childTree, childValue, childOnChange) => {
|
|
244
|
-
|
|
276
|
+
const makeRenderChild = (currentDepth, parentPath) => (childTree, childValue, childOnChange, pathSuffix) => {
|
|
277
|
+
const childPath = joinPath(parentPath, pathSuffix);
|
|
278
|
+
return renderField(childTree, childValue, childOnChange, userResolver, makeRenderChild(currentDepth + 1, childPath), childPath, void 0, contextWidgets, currentDepth + 1);
|
|
245
279
|
};
|
|
246
|
-
|
|
280
|
+
const rootPath = joinPath(sanitisePrefix(generatedId), path);
|
|
281
|
+
return renderField(fieldTree, fieldValue, handleChange, userResolver, makeRenderChild(0, rootPath), rootPath, void 0, contextWidgets, 0);
|
|
247
282
|
}
|
|
248
283
|
/**
|
|
249
284
|
* Dispatch Zod errors to per-field onValidationError callbacks.
|
|
@@ -290,4 +325,4 @@ function detectNormalisationKind(err) {
|
|
|
290
325
|
return "unknown";
|
|
291
326
|
}
|
|
292
327
|
//#endregion
|
|
293
|
-
export { SchemaComponent, SchemaField, SchemaProvider, registerWidget, renderField };
|
|
328
|
+
export { ROOT_PATH, SchemaComponent, SchemaField, SchemaProvider, joinPath, registerWidget, renderField, sanitisePrefix };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { T as SchemaMeta } from "../types-D_5ST7SS.mjs";
|
|
2
2
|
import { t as Diagnostic } from "../diagnostics-DzbZmcLI.mjs";
|
|
3
|
-
import { r as ComponentResolver } from "../renderer-
|
|
3
|
+
import { r as ComponentResolver } from "../renderer-B3s8o2B8.mjs";
|
|
4
4
|
import { WidgetMap } from "./SchemaComponent.mjs";
|
|
5
5
|
import { ReactNode } from "react";
|
|
6
6
|
|
|
@@ -30,6 +30,12 @@ interface SchemaViewProps {
|
|
|
30
30
|
onDiagnostic?: (diagnostic: Diagnostic) => void;
|
|
31
31
|
/** When true, any diagnostic becomes a thrown error. */
|
|
32
32
|
strict?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Prefix used for every input `id`/label `htmlFor` in this view subtree.
|
|
35
|
+
* Defaults to a per-instance value from `useId()`; pass a deterministic
|
|
36
|
+
* value when stable ids matter (e.g. snapshot tests).
|
|
37
|
+
*/
|
|
38
|
+
idPrefix?: string;
|
|
33
39
|
}
|
|
34
40
|
/**
|
|
35
41
|
* Server-safe schema renderer — no hooks, no context, no state.
|
|
@@ -47,7 +53,8 @@ declare function SchemaView({
|
|
|
47
53
|
resolver,
|
|
48
54
|
widgets,
|
|
49
55
|
onDiagnostic,
|
|
50
|
-
strict
|
|
56
|
+
strict,
|
|
57
|
+
idPrefix
|
|
51
58
|
}: SchemaViewProps): ReactNode;
|
|
52
59
|
//#endregion
|
|
53
60
|
export { SchemaView, SchemaViewProps };
|
|
@@ -3,8 +3,9 @@ import { normaliseSchema } from "../core/adapter.mjs";
|
|
|
3
3
|
import { getRenderFunction, mergeResolvers } from "../core/renderer.mjs";
|
|
4
4
|
import { walk } from "../core/walker.mjs";
|
|
5
5
|
import { headlessResolver } from "./headless.mjs";
|
|
6
|
+
import { joinPath, sanitisePrefix } from "./SchemaComponent.mjs";
|
|
6
7
|
import { jsx } from "react/jsx-runtime";
|
|
7
|
-
import { createElement, isValidElement } from "react";
|
|
8
|
+
import { createElement, isValidElement, useId } from "react";
|
|
8
9
|
//#region src/react/SchemaView.tsx
|
|
9
10
|
/**
|
|
10
11
|
* React Server Component for read-only schema rendering.
|
|
@@ -37,7 +38,9 @@ function noop() {}
|
|
|
37
38
|
* Always renders in read-only mode. For editable forms, use
|
|
38
39
|
* `<SchemaComponent>` with `"use client"`.
|
|
39
40
|
*/
|
|
40
|
-
function SchemaView({ schema: schemaInput, ref: refInput, value, fields, meta: componentMeta, description, resolver, widgets, onDiagnostic, strict }) {
|
|
41
|
+
function SchemaView({ schema: schemaInput, ref: refInput, value, fields, meta: componentMeta, description, resolver, widgets, onDiagnostic, strict, idPrefix }) {
|
|
42
|
+
const generatedId = useId();
|
|
43
|
+
const rootPath = idPrefix ?? sanitisePrefix(generatedId);
|
|
41
44
|
const mergedMeta = {
|
|
42
45
|
...componentMeta,
|
|
43
46
|
readOnly: true
|
|
@@ -68,18 +71,21 @@ function SchemaView({ schema: schemaInput, ref: refInput, value, fields, meta: c
|
|
|
68
71
|
const tree = walk(jsonSchema, walkOptions);
|
|
69
72
|
const userResolver = resolver !== void 0 ? mergeResolvers(resolver, headlessResolver) : headlessResolver;
|
|
70
73
|
const MAX_SERVER_DEPTH = 10;
|
|
71
|
-
const makeRenderChild = (currentDepth) => (childTree, childValue) => {
|
|
74
|
+
const makeRenderChild = (currentDepth, parentPath) => (childTree, childValue, pathSuffix) => {
|
|
75
|
+
const childPath = joinPath(parentPath, pathSuffix);
|
|
72
76
|
if (currentDepth >= MAX_SERVER_DEPTH) return createElement("fieldset", null, createElement("em", null, `\u21bb ${typeof childTree.meta.description === "string" ? childTree.meta.description : childTree.type === "recursive" ? childTree.refTarget : "schema"} (recursive)`));
|
|
73
|
-
return renderFieldServer(childTree, childValue, userResolver, makeRenderChild(currentDepth + 1), widgets);
|
|
77
|
+
return renderFieldServer(childTree, childValue, userResolver, makeRenderChild(currentDepth + 1, childPath), childPath, widgets);
|
|
74
78
|
};
|
|
75
|
-
const renderChild = makeRenderChild(0);
|
|
76
|
-
return renderFieldServer(tree, value ?? tree.defaultValue, userResolver, renderChild, widgets);
|
|
79
|
+
const renderChild = makeRenderChild(0, rootPath);
|
|
80
|
+
return renderFieldServer(tree, value ?? tree.defaultValue, userResolver, renderChild, rootPath, widgets);
|
|
77
81
|
}
|
|
78
|
-
function renderFieldServer(tree, value, resolver, renderChild, widgets) {
|
|
82
|
+
function renderFieldServer(tree, value, resolver, renderChild, path, widgets) {
|
|
83
|
+
if (path.length === 0) throw new Error("renderFieldServer requires a non-empty path. Pass ROOT_PATH at the root and join children via joinPath().");
|
|
79
84
|
const componentHint = tree.meta.component;
|
|
80
85
|
if (typeof componentHint === "string") {
|
|
81
86
|
const widget = widgets?.get(componentHint);
|
|
82
87
|
if (widget !== void 0) {
|
|
88
|
+
const wrapRenderChild = (childTree, childValue, _childOnChange, pathSuffix) => renderChild(childTree, childValue, pathSuffix);
|
|
83
89
|
const result = widget({
|
|
84
90
|
value,
|
|
85
91
|
onChange: noop,
|
|
@@ -87,9 +93,9 @@ function renderFieldServer(tree, value, resolver, renderChild, widgets) {
|
|
|
87
93
|
writeOnly: false,
|
|
88
94
|
meta: tree.meta,
|
|
89
95
|
constraints: tree.constraints,
|
|
90
|
-
path
|
|
96
|
+
path,
|
|
91
97
|
tree,
|
|
92
|
-
renderChild:
|
|
98
|
+
renderChild: wrapRenderChild
|
|
93
99
|
});
|
|
94
100
|
if (result !== void 0 && result !== null) {
|
|
95
101
|
if (isValidElement(result)) return result;
|
|
@@ -106,9 +112,9 @@ function renderFieldServer(tree, value, resolver, renderChild, widgets) {
|
|
|
106
112
|
writeOnly: false,
|
|
107
113
|
meta: tree.meta,
|
|
108
114
|
constraints: tree.constraints,
|
|
109
|
-
path
|
|
115
|
+
path,
|
|
110
116
|
tree,
|
|
111
|
-
renderChild: (childTree, childValue) => renderChild(childTree, childValue)
|
|
117
|
+
renderChild: (childTree, childValue, _childOnChange, pathSuffix) => renderChild(childTree, childValue, pathSuffix)
|
|
112
118
|
};
|
|
113
119
|
if (tree.type === "enum") props.enumValues = tree.enumValues;
|
|
114
120
|
if (tree.type === "array" && tree.element !== void 0) props.element = tree.element;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { M as WalkedField } from "../types-D_5ST7SS.mjs";
|
|
2
|
+
import { l as RenderProps } from "../renderer-B3s8o2B8.mjs";
|
|
2
3
|
import { ReactNode } from "react";
|
|
3
4
|
|
|
4
5
|
//#region src/react/headlessRenderers.d.ts
|
|
@@ -7,17 +8,55 @@ import { ReactNode } from "react";
|
|
|
7
8
|
* Returns `null` for unrecognised values.
|
|
8
9
|
*/
|
|
9
10
|
declare function toReactNode(value: unknown): ReactNode;
|
|
11
|
+
/**
|
|
12
|
+
* Build a stable, unique input ID from the path.
|
|
13
|
+
* Used for `htmlFor`/`id` association between labels and inputs.
|
|
14
|
+
*
|
|
15
|
+
* Throws on an empty path: the previous "sc-field" fallback caused every
|
|
16
|
+
* input across a form to share the same id, breaking label-input pairing
|
|
17
|
+
* and screen reader navigation. Callers must thread a non-empty path
|
|
18
|
+
* (see `ROOT_PATH` and `joinPath` in `SchemaComponent.tsx`).
|
|
19
|
+
*
|
|
20
|
+
* Dots and bracket indices in paths are converted to hyphens to keep the
|
|
21
|
+
* id valid as a CSS selector and predictable in test queries.
|
|
22
|
+
*/
|
|
23
|
+
declare function inputId(path: string): string;
|
|
10
24
|
declare function renderString(props: RenderProps): ReactNode;
|
|
11
25
|
declare function renderNumber(props: RenderProps): ReactNode;
|
|
12
26
|
declare function renderBoolean(props: RenderProps): ReactNode;
|
|
13
27
|
declare function renderEnum(props: RenderProps): ReactNode;
|
|
14
28
|
declare function renderObject(props: RenderProps): ReactNode;
|
|
29
|
+
/**
|
|
30
|
+
* Compute the default value for a freshly added record entry based on the
|
|
31
|
+
* record's value-type schema. Mirrors the read of `defaultValue` used
|
|
32
|
+
* elsewhere in the renderer, falling back to a type-appropriate empty value.
|
|
33
|
+
*/
|
|
34
|
+
declare function defaultRecordValue(valueType: WalkedField): unknown;
|
|
35
|
+
/**
|
|
36
|
+
* Generate a unique, currently-unused key for a new record entry.
|
|
37
|
+
* Picks the first of `key`, `key-1`, `key-2`, … that is not in `existing`.
|
|
38
|
+
*/
|
|
39
|
+
declare function nextRecordKey(existing: readonly string[], base?: string): string;
|
|
40
|
+
/**
|
|
41
|
+
* Rename a key in an object while preserving insertion order. Returns the
|
|
42
|
+
* original object reference when the rename is a no-op (oldKey === newKey)
|
|
43
|
+
* or when newKey collides with an existing key.
|
|
44
|
+
*/
|
|
45
|
+
declare function renameRecordKey(obj: Record<string, unknown>, oldKey: string, newKey: string): Record<string, unknown>;
|
|
15
46
|
declare function renderRecord(props: RenderProps): ReactNode;
|
|
16
47
|
declare function renderArray(props: RenderProps): ReactNode;
|
|
17
48
|
declare function renderUnion(props: RenderProps): ReactNode;
|
|
18
49
|
declare function renderDiscriminatedUnion(props: RenderProps): ReactNode;
|
|
50
|
+
/**
|
|
51
|
+
* Pure helper: convert a tab index into the new value the discriminated
|
|
52
|
+
* union should emit. Returns `undefined` when the index is out of bounds.
|
|
53
|
+
*
|
|
54
|
+
* Extracted from `DiscriminatedUnionTabs` so the contract is unit-testable
|
|
55
|
+
* without rendering the tabs component (which relies on React hooks).
|
|
56
|
+
*/
|
|
57
|
+
declare function discriminatedUnionValueForTab(optionLabels: readonly string[], discKey: string, newIndex: number): Record<string, string> | undefined;
|
|
19
58
|
declare function renderFile(props: RenderProps): ReactNode;
|
|
20
59
|
declare function renderRecursive(props: RenderProps): ReactNode;
|
|
21
60
|
declare function renderUnknown(props: RenderProps): ReactNode;
|
|
22
61
|
//#endregion
|
|
23
|
-
export { renderArray, renderBoolean, renderDiscriminatedUnion, renderEnum, renderFile, renderNumber, renderObject, renderRecord, renderRecursive, renderString, renderUnion, renderUnknown, toReactNode };
|
|
62
|
+
export { defaultRecordValue, discriminatedUnionValueForTab, inputId, nextRecordKey, renameRecordKey, renderArray, renderBoolean, renderDiscriminatedUnion, renderEnum, renderFile, renderNumber, renderObject, renderRecord, renderRecursive, renderString, renderUnion, renderUnknown, toReactNode };
|