schema-components 1.21.0 → 1.23.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/README.md +3 -1
- package/dist/core/adapter.d.mts +115 -4
- package/dist/core/adapter.mjs +405 -75
- package/dist/core/constraints.d.mts +2 -2
- package/dist/core/constraints.mjs +0 -7
- package/dist/core/cssClasses.d.mts +52 -0
- package/dist/core/cssClasses.mjs +51 -0
- package/dist/core/diagnostics.d.mts +1 -1
- package/dist/core/errors.d.mts +1 -1
- package/dist/core/errors.mjs +5 -13
- package/dist/core/fieldOrder.d.mts +1 -1
- package/dist/core/formats.d.mts +30 -2
- package/dist/core/formats.mjs +33 -1
- package/dist/core/idPath.d.mts +54 -0
- package/dist/core/idPath.mjs +66 -0
- package/dist/core/limits.d.mts +2 -0
- package/dist/core/limits.mjs +23 -0
- package/dist/core/merge.d.mts +10 -1
- package/dist/core/merge.mjs +49 -10
- package/dist/core/normalise.d.mts +40 -3
- package/dist/core/normalise.mjs +2 -2
- package/dist/core/openapi30.d.mts +15 -1
- package/dist/core/openapi30.mjs +2 -2
- package/dist/core/openapiConstants.d.mts +67 -0
- package/dist/core/openapiConstants.mjs +90 -0
- package/dist/core/ref.d.mts +2 -2
- package/dist/core/ref.mjs +85 -6
- package/dist/core/refChain.d.mts +70 -0
- package/dist/core/refChain.mjs +44 -0
- package/dist/core/renderer.d.mts +1 -1
- package/dist/core/renderer.mjs +0 -2
- package/dist/core/swagger2.d.mts +1 -1
- package/dist/core/swagger2.mjs +1 -1
- package/dist/core/typeInference.d.mts +982 -2
- package/dist/core/types.d.mts +2 -2
- package/dist/core/types.mjs +1 -4
- package/dist/core/unionMatch.d.mts +36 -0
- package/dist/core/unionMatch.mjs +53 -0
- package/dist/core/version.d.mts +1 -1
- package/dist/core/version.mjs +29 -17
- package/dist/core/walkBuilders.d.mts +23 -4
- package/dist/core/walkBuilders.mjs +27 -7
- package/dist/core/walker.d.mts +1 -1
- package/dist/core/walker.mjs +123 -47
- package/dist/{diagnostics-CbBPsxSt.d.mts → diagnostics-BS2kaUyE.d.mts} +1 -1
- package/dist/{errors-QEwOtQAA.d.mts → errors-g_MCTQel.d.mts} +10 -16
- package/dist/html/a11y.d.mts +9 -4
- package/dist/html/a11y.mjs +10 -12
- package/dist/html/renderToHtml.d.mts +10 -3
- package/dist/html/renderToHtml.mjs +13 -3
- package/dist/html/renderToHtmlStream.d.mts +2 -2
- package/dist/html/renderToHtmlStream.mjs +12 -1
- package/dist/html/renderers.d.mts +43 -8
- package/dist/html/renderers.mjs +136 -116
- package/dist/html/streamRenderers.d.mts +6 -6
- package/dist/html/streamRenderers.mjs +129 -89
- package/dist/limits-Cw5QZND8.d.mts +29 -0
- package/dist/{normalise-DaSrnr8g.mjs → normalise-DCYp06Sr.mjs} +770 -227
- package/dist/openapi/ApiCallbacks.d.mts +1 -1
- package/dist/openapi/ApiLinks.d.mts +1 -1
- package/dist/openapi/ApiResponseHeaders.d.mts +1 -1
- package/dist/openapi/ApiSecurity.d.mts +1 -1
- package/dist/openapi/ApiSecurity.mjs +16 -2
- package/dist/openapi/components.d.mts +234 -23
- package/dist/openapi/components.mjs +183 -52
- package/dist/openapi/parser.d.mts +9 -8
- package/dist/openapi/parser.mjs +252 -70
- package/dist/openapi/resolve.d.mts +31 -15
- package/dist/openapi/resolve.mjs +260 -40
- package/dist/react/SchemaComponent.d.mts +126 -36
- package/dist/react/SchemaComponent.mjs +95 -57
- package/dist/react/SchemaView.d.mts +30 -10
- package/dist/react/SchemaView.mjs +2 -2
- package/dist/react/a11y.d.mts +21 -0
- package/dist/react/a11y.mjs +24 -0
- package/dist/react/fieldPath.d.mts +1 -1
- package/dist/react/headless.d.mts +1 -1
- package/dist/react/headless.mjs +1 -2
- package/dist/react/headlessRenderers.d.mts +9 -11
- package/dist/react/headlessRenderers.mjs +51 -102
- package/dist/{ref-si8ViYun.d.mts → ref-DjLEKa_E.d.mts} +38 -3
- package/dist/{renderer-DI6ZYf7a.d.mts → renderer-CXJ8y0qw.d.mts} +2 -2
- package/dist/themes/mantine.d.mts +1 -1
- package/dist/themes/mui.d.mts +1 -1
- package/dist/themes/radix.d.mts +1 -1
- package/dist/themes/shadcn.d.mts +1 -1
- package/dist/themes/shadcn.mjs +2 -1
- package/dist/{types-BnxPEElk.d.mts → types-BTB73MB8.d.mts} +35 -14
- package/dist/{version-D-u7aMfy.d.mts → version-BFTVLsdb.d.mts} +7 -1
- package/package.json +1 -3
- package/dist/typeInference-Bxw3NOG1.d.mts +0 -647
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { isObject, toRecordOrUndefined } from "../core/guards.mjs";
|
|
2
|
+
import { getProperty, isObject, toRecordOrUndefined } from "../core/guards.mjs";
|
|
3
|
+
import "../core/limits.mjs";
|
|
3
4
|
import { SchemaFieldError, SchemaNormalisationError, SchemaRenderError } from "../core/errors.mjs";
|
|
4
5
|
import { normaliseSchema } from "../core/adapter.mjs";
|
|
5
6
|
import { buildRenderProps, getRenderFunction, mergeResolvers } from "../core/renderer.mjs";
|
|
@@ -46,7 +47,8 @@ const globalWidgets = /* @__PURE__ */ new Map();
|
|
|
46
47
|
function registerWidget(name, render) {
|
|
47
48
|
globalWidgets.set(name, render);
|
|
48
49
|
}
|
|
49
|
-
function SchemaComponent(
|
|
50
|
+
function SchemaComponent(props) {
|
|
51
|
+
const { schema: schemaInput, ref: refInput, value, onChange, validate, onValidationError, onError, onDiagnostic, strict, fields, meta: componentMeta, readOnly, writeOnly, description, widgets: instanceWidgets, idPrefix } = props;
|
|
50
52
|
const userResolver = useContext(UserResolverContext);
|
|
51
53
|
const contextWidgets = useContext(WidgetsContext);
|
|
52
54
|
const generatedId = useId();
|
|
@@ -85,12 +87,23 @@ function SchemaComponent({ schema: schemaInput, ref: refInput, value, onChange,
|
|
|
85
87
|
}
|
|
86
88
|
throw error;
|
|
87
89
|
}
|
|
90
|
+
const fieldsRecord = toRecordOrUndefined(fields);
|
|
88
91
|
const handleChange = useCallback((nextValue) => {
|
|
89
92
|
if (validate) {
|
|
90
|
-
|
|
93
|
+
let error;
|
|
94
|
+
try {
|
|
95
|
+
error = runValidation(zodSchema, jsonSchema, nextValue, onDiagnostic);
|
|
96
|
+
} catch (err) {
|
|
97
|
+
const normalised = err instanceof SchemaNormalisationError ? err : new SchemaNormalisationError(err instanceof Error ? err.message : "Fallback validation failed", schemaInput, "zod-conversion-failed", void 0, err);
|
|
98
|
+
if (onError !== void 0) {
|
|
99
|
+
onError(normalised);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
throw normalised;
|
|
103
|
+
}
|
|
91
104
|
if (error !== void 0) {
|
|
92
105
|
onValidationError?.(error);
|
|
93
|
-
dispatchFieldErrors(
|
|
106
|
+
dispatchFieldErrors(fieldsRecord, error);
|
|
94
107
|
}
|
|
95
108
|
}
|
|
96
109
|
onChange?.(nextValue);
|
|
@@ -100,13 +113,15 @@ function SchemaComponent({ schema: schemaInput, ref: refInput, value, onChange,
|
|
|
100
113
|
jsonSchema,
|
|
101
114
|
onChange,
|
|
102
115
|
onValidationError,
|
|
103
|
-
|
|
104
|
-
onDiagnostic
|
|
116
|
+
fieldsRecord,
|
|
117
|
+
onDiagnostic,
|
|
118
|
+
onError,
|
|
119
|
+
schemaInput
|
|
105
120
|
]);
|
|
106
121
|
const walkOptions = {
|
|
107
122
|
componentMeta: mergedMeta,
|
|
108
123
|
rootMeta,
|
|
109
|
-
fieldOverrides:
|
|
124
|
+
fieldOverrides: fieldsRecord,
|
|
110
125
|
rootDocument,
|
|
111
126
|
...diagnostics !== void 0 ? { diagnostics } : {}
|
|
112
127
|
};
|
|
@@ -119,20 +134,19 @@ function SchemaComponent({ schema: schemaInput, ref: refInput, value, onChange,
|
|
|
119
134
|
return renderField(tree, value ?? tree.defaultValue, handleChange, userResolver, renderChild, rootPath, instanceWidgets, contextWidgets, 0);
|
|
120
135
|
}
|
|
121
136
|
/**
|
|
122
|
-
* Default root-path sentinel used when no `idPrefix` is supplied AND the
|
|
123
|
-
* component is rendered outside a React tree (e.g. server-side bundling
|
|
124
|
-
* test harnesses). Production callers receive a `useId()`-derived prefix
|
|
125
|
-
* that is unique per instance.
|
|
126
|
-
*/
|
|
127
|
-
const ROOT_PATH = "root";
|
|
128
|
-
/**
|
|
129
137
|
* Append a child path suffix to a parent path. When the suffix is omitted
|
|
130
138
|
* (e.g. transparent wrappers like union options), the parent path is
|
|
131
139
|
* returned unchanged so the child inherits the parent's id.
|
|
140
|
+
*
|
|
141
|
+
* Bracketed array indices like `[0]` append directly so `tags` + `[0]`
|
|
142
|
+
* becomes `tags[0]` rather than `tags.[0]` — matching the canonical form
|
|
143
|
+
* used by `html/a11y.ts` `joinPath` and `react/fieldPath.ts` `resolvePath`,
|
|
144
|
+
* which already parses bracket notation when navigating WalkedField trees.
|
|
132
145
|
*/
|
|
133
146
|
function joinPath(parent, suffix) {
|
|
134
147
|
if (suffix === void 0 || suffix.length === 0) return parent;
|
|
135
148
|
if (parent.length === 0) return suffix;
|
|
149
|
+
if (suffix.startsWith("[")) return `${parent}${suffix}`;
|
|
136
150
|
return `${parent}.${suffix}`;
|
|
137
151
|
}
|
|
138
152
|
/**
|
|
@@ -146,11 +160,26 @@ function sanitisePrefix(value) {
|
|
|
146
160
|
if (sanitised.length === 0) throw new Error(`Cannot derive a DOM-safe id prefix from "${value}". Pass an explicit idPrefix prop.`);
|
|
147
161
|
return sanitised;
|
|
148
162
|
}
|
|
163
|
+
/**
|
|
164
|
+
* Run validation against the supplied value.
|
|
165
|
+
*
|
|
166
|
+
* Returns the validation error (Zod error or equivalent) on failure, or
|
|
167
|
+
* `undefined` when the value is valid OR when the fallback validation
|
|
168
|
+
* path was skipped because a diagnostic sink absorbed the conversion
|
|
169
|
+
* failure.
|
|
170
|
+
*
|
|
171
|
+
* Throws `SchemaNormalisationError` (kind `zod-conversion-failed`) when
|
|
172
|
+
* the JSON-Schema → Zod fallback is taken AND no diagnostic sink is
|
|
173
|
+
* wired up. The project's no-silent-fallback rule requires the failure
|
|
174
|
+
* to surface somewhere — diagnostics if the consumer opted in, an error
|
|
175
|
+
* otherwise — so the caller can route it through `onError` / an error
|
|
176
|
+
* boundary rather than have validation quietly disappear.
|
|
177
|
+
*/
|
|
149
178
|
function runValidation(zodSchema, jsonSchema, value, onDiagnostic) {
|
|
150
179
|
if (zodSchema !== void 0 && isObject(zodSchema)) {
|
|
151
|
-
const
|
|
152
|
-
if (isCallable(
|
|
153
|
-
const result =
|
|
180
|
+
const validateFn = isCodecSchema(zodSchema) ? zodSchema.safeEncode : zodSchema.safeParse;
|
|
181
|
+
if (isCallable(validateFn)) {
|
|
182
|
+
const result = validateFn(value);
|
|
154
183
|
if (isObject(result) && "success" in result && result.success !== true) return result.error;
|
|
155
184
|
return;
|
|
156
185
|
}
|
|
@@ -159,8 +188,16 @@ function runValidation(zodSchema, jsonSchema, value, onDiagnostic) {
|
|
|
159
188
|
try {
|
|
160
189
|
parsed = z.fromJSONSchema(jsonSchema);
|
|
161
190
|
} catch (err) {
|
|
162
|
-
|
|
163
|
-
|
|
191
|
+
if (onDiagnostic !== void 0) {
|
|
192
|
+
onDiagnostic({
|
|
193
|
+
code: "unsupported-type",
|
|
194
|
+
message: `Skipping fallback validation: z.fromJSONSchema could not round-trip the normalised JSON Schema. Original message: ${err instanceof Error ? err.message : "z.fromJSONSchema threw a non-Error value"}`,
|
|
195
|
+
pointer: "",
|
|
196
|
+
detail: { source: "z.fromJSONSchema" }
|
|
197
|
+
});
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
throw new SchemaNormalisationError(`Fallback validation failed: z.fromJSONSchema could not round-trip the normalised JSON Schema. Original message: ${err instanceof Error ? err.message : "z.fromJSONSchema threw a non-Error value"}`, jsonSchema, "zod-conversion-failed", void 0, err);
|
|
164
201
|
}
|
|
165
202
|
if (isObject(parsed)) {
|
|
166
203
|
const safeParseFn = parsed.safeParse;
|
|
@@ -170,39 +207,13 @@ function runValidation(zodSchema, jsonSchema, value, onDiagnostic) {
|
|
|
170
207
|
}
|
|
171
208
|
}
|
|
172
209
|
}
|
|
173
|
-
/**
|
|
174
|
-
* Emit a diagnostic when `z.fromJSONSchema` refuses to round-trip the
|
|
175
|
-
* already-normalised JSON Schema. The diagnostic reuses the existing
|
|
176
|
-
* `unsupported-type` code because the failure mode is the same — a
|
|
177
|
-
* keyword/structure Zod cannot represent — and the consumer should be
|
|
178
|
-
* able to opt in to noticing it via the existing diagnostics channel.
|
|
179
|
-
*
|
|
180
|
-
* Best-effort: when no `onDiagnostic` sink is configured we still swallow
|
|
181
|
-
* the throw (the alternative would crash the React render for a
|
|
182
|
-
* non-essential validation step), matching the silent-fallback contract
|
|
183
|
-
* the rest of the diagnostics system uses.
|
|
184
|
-
*/
|
|
185
|
-
function emitFromJsonSchemaDiagnostic(err, onDiagnostic) {
|
|
186
|
-
if (onDiagnostic === void 0) return;
|
|
187
|
-
onDiagnostic({
|
|
188
|
-
code: "unsupported-type",
|
|
189
|
-
message: `Skipping fallback validation: z.fromJSONSchema could not round-trip the normalised JSON Schema. Original message: ${err instanceof Error ? err.message : "z.fromJSONSchema threw a non-Error value"}`,
|
|
190
|
-
pointer: "",
|
|
191
|
-
detail: { source: "z.fromJSONSchema" }
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
/** Maximum rendering depth before treating a field as recursive. */
|
|
195
|
-
const MAX_RENDER_DEPTH = 10;
|
|
196
210
|
function renderField(tree, value, onChange, userResolver, renderChild, path, instanceWidgets, contextWidgets, depth = 0) {
|
|
197
|
-
if (path.length === 0) throw new Error("renderField requires a non-empty path. Pass
|
|
198
|
-
if (depth >=
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
" (recursive)"
|
|
204
|
-
] }) });
|
|
205
|
-
}
|
|
211
|
+
if (path.length === 0) throw new Error("renderField requires a non-empty path. Pass the root path (derived from `idPrefix` or `useId()`) for the root field, and use renderChild's pathSuffix to derive child paths.");
|
|
212
|
+
if (depth >= 10) return /* @__PURE__ */ jsx("fieldset", { children: /* @__PURE__ */ jsxs("em", { children: [
|
|
213
|
+
"↻ ",
|
|
214
|
+
typeof tree.meta.description === "string" ? tree.meta.description : "schema",
|
|
215
|
+
" (recursive)"
|
|
216
|
+
] }) });
|
|
206
217
|
const componentHint = tree.meta.component;
|
|
207
218
|
if (typeof componentHint === "string") {
|
|
208
219
|
const widget = instanceWidgets?.get(componentHint) ?? contextWidgets?.get(componentHint) ?? globalWidgets.get(componentHint);
|
|
@@ -257,12 +268,11 @@ function SchemaField({ path, schema: schemaInput, ref: refInput, value, onChange
|
|
|
257
268
|
if (fieldTree === void 0) throw new SchemaFieldError(`Field not found: ${path}`, schemaInput, path);
|
|
258
269
|
const fieldValue = resolveValue(value, path);
|
|
259
270
|
const handleChange = useCallback((nextFieldValue) => {
|
|
271
|
+
const newRootValue = setNestedValue(value, path, nextFieldValue);
|
|
260
272
|
if (validate) {
|
|
261
|
-
const newRootValue = setNestedValue(value, path, nextFieldValue);
|
|
262
273
|
const error = runValidation(zodSchema, jsonSchema, newRootValue);
|
|
263
274
|
if (error !== void 0) onValidationError?.(error);
|
|
264
275
|
}
|
|
265
|
-
const newRootValue = setNestedValue(value, path, nextFieldValue);
|
|
266
276
|
onChange?.(newRootValue);
|
|
267
277
|
}, [
|
|
268
278
|
validate,
|
|
@@ -283,15 +293,22 @@ function SchemaField({ path, schema: schemaInput, ref: refInput, value, onChange
|
|
|
283
293
|
/**
|
|
284
294
|
* Dispatch Zod errors to per-field onValidationError callbacks.
|
|
285
295
|
* Walks the fields override tree and matches errors by path prefix.
|
|
296
|
+
*
|
|
297
|
+
* The runtime shape of `fields` is always `Record<string, FieldOverride>`
|
|
298
|
+
* after `InferFields<T, Ref>` is erased — the typed variants
|
|
299
|
+
* (`FieldOverrides<U>`) and the loose `Record<string, FieldOverride>`
|
|
300
|
+
* fallback share the same structural shape, so the dispatch logic only
|
|
301
|
+
* needs the loose record. The previous parameter union
|
|
302
|
+
* (`Record<string, FieldOverride> | FieldOverrides<unknown> |
|
|
303
|
+
* undefined`) collapsed because `FieldOverrides<unknown>` reduces to
|
|
304
|
+
* `{}`, contributing no extra precision while adding noise to readers.
|
|
286
305
|
*/
|
|
287
306
|
function dispatchFieldErrors(fields, error) {
|
|
288
307
|
if (fields === void 0 || !isObject(error)) return;
|
|
289
308
|
if (!("issues" in error)) return;
|
|
290
309
|
const issues = error.issues;
|
|
291
310
|
if (!Array.isArray(issues)) return;
|
|
292
|
-
const
|
|
293
|
-
if (overrides === void 0) return;
|
|
294
|
-
for (const [key, override] of Object.entries(overrides)) {
|
|
311
|
+
for (const [key, override] of Object.entries(fields)) {
|
|
295
312
|
if (override === void 0 || typeof override !== "object") continue;
|
|
296
313
|
if (override === null) continue;
|
|
297
314
|
if (!("onValidationError" in override)) continue;
|
|
@@ -313,5 +330,26 @@ function isFieldErrorCallback(value) {
|
|
|
313
330
|
function isCallable(value) {
|
|
314
331
|
return typeof value === "function";
|
|
315
332
|
}
|
|
333
|
+
/**
|
|
334
|
+
* True when `value` is a Zod schema implemented as a codec.
|
|
335
|
+
*
|
|
336
|
+
* Detection looks for `"$ZodCodec"` in the schema's `_zod.traits`
|
|
337
|
+
* Set, mirroring the runtime detection used inside
|
|
338
|
+
* `core/adapter.ts` (`isCodecSchema` there) and Zod's own
|
|
339
|
+
* `isTransforming` helper. Duplicated here rather than imported
|
|
340
|
+
* because adapter.ts does not export the helper and is outside this
|
|
341
|
+
* fix-cycle's owned files.
|
|
342
|
+
*
|
|
343
|
+
* TODO(round7-integration): replace with an `import` once
|
|
344
|
+
* `isCodecSchema` is exported from `core/adapter.ts` (or promoted to
|
|
345
|
+
* `core/guards.ts`) by a coordinating commit.
|
|
346
|
+
*/
|
|
347
|
+
function isCodecSchema(value) {
|
|
348
|
+
const zod = getProperty(value, "_zod");
|
|
349
|
+
if (!isObject(zod)) return false;
|
|
350
|
+
const traits = zod.traits;
|
|
351
|
+
if (traits instanceof Set) return traits.has("$ZodCodec");
|
|
352
|
+
return false;
|
|
353
|
+
}
|
|
316
354
|
//#endregion
|
|
317
|
-
export {
|
|
355
|
+
export { SchemaComponent, SchemaField, SchemaProvider, joinPath, registerWidget, renderField, sanitisePrefix };
|
|
@@ -1,16 +1,36 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as Diagnostic } from "../diagnostics-
|
|
3
|
-
import { r as ComponentResolver } from "../renderer-
|
|
1
|
+
import { w as SchemaMeta } from "../types-BTB73MB8.mjs";
|
|
2
|
+
import { t as Diagnostic } from "../diagnostics-BS2kaUyE.mjs";
|
|
3
|
+
import { r as ComponentResolver } from "../renderer-CXJ8y0qw.mjs";
|
|
4
|
+
import { RejectUnrepresentableZod } from "../core/typeInference.mjs";
|
|
4
5
|
import { WidgetMap } from "./SchemaComponent.mjs";
|
|
5
6
|
import { ReactNode } from "react";
|
|
6
7
|
|
|
7
8
|
//#region src/react/SchemaView.d.ts
|
|
8
|
-
interface SchemaViewProps {
|
|
9
|
-
/**
|
|
10
|
-
|
|
9
|
+
interface SchemaViewProps<T = unknown, Ref extends string | undefined = undefined> {
|
|
10
|
+
/**
|
|
11
|
+
* Zod schema, JSON Schema object, or OpenAPI document.
|
|
12
|
+
*
|
|
13
|
+
* Subject to the same compile-time rejection of unrepresentable
|
|
14
|
+
* Zod 4 types as {@link SchemaComponentProps.schema} — see
|
|
15
|
+
* {@link RejectUnrepresentableZod}.
|
|
16
|
+
*/
|
|
17
|
+
schema: RejectUnrepresentableZod<T>;
|
|
11
18
|
/** For OpenAPI: a ref string like "#/components/schemas/User". */
|
|
12
|
-
ref?:
|
|
13
|
-
/**
|
|
19
|
+
ref?: Ref;
|
|
20
|
+
/**
|
|
21
|
+
* Current value to render.
|
|
22
|
+
*
|
|
23
|
+
* TYPE BOUNDARY NOTE: mirrors `SchemaComponentProps.value` — kept
|
|
24
|
+
* as `unknown` so the same boundary holds for both the editable
|
|
25
|
+
* (`SchemaComponent`) and read-only (`SchemaView`) entry points.
|
|
26
|
+
* The {@link InferredOutputValue} alias is the recommended way
|
|
27
|
+
* for callers to narrow on the consumer side.
|
|
28
|
+
*
|
|
29
|
+
* TODO(round7-integration): promote to
|
|
30
|
+
* `InferSchemaValue<T, Ref, "output">` alongside the matching
|
|
31
|
+
* `SchemaComponent` change. See the type-boundary note on
|
|
32
|
+
* `SchemaComponentProps.value` for the migration coordination.
|
|
33
|
+
*/
|
|
14
34
|
value?: unknown;
|
|
15
35
|
/** Per-field meta overrides. */
|
|
16
36
|
fields?: Record<string, unknown>;
|
|
@@ -44,7 +64,7 @@ interface SchemaViewProps {
|
|
|
44
64
|
* Always renders in read-only mode. For editable forms, use
|
|
45
65
|
* `<SchemaComponent>` with `"use client"`.
|
|
46
66
|
*/
|
|
47
|
-
declare function SchemaView({
|
|
67
|
+
declare function SchemaView<T = unknown, Ref extends string | undefined = undefined>({
|
|
48
68
|
schema: schemaInput,
|
|
49
69
|
ref: refInput,
|
|
50
70
|
value,
|
|
@@ -56,6 +76,6 @@ declare function SchemaView({
|
|
|
56
76
|
onDiagnostic,
|
|
57
77
|
strict,
|
|
58
78
|
idPrefix
|
|
59
|
-
}: SchemaViewProps): ReactNode;
|
|
79
|
+
}: SchemaViewProps<T, Ref>): ReactNode;
|
|
60
80
|
//#endregion
|
|
61
81
|
export { SchemaView, SchemaViewProps };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import "../core/limits.mjs";
|
|
1
2
|
import { SchemaNormalisationError, SchemaRenderError } from "../core/errors.mjs";
|
|
2
3
|
import { normaliseSchema } from "../core/adapter.mjs";
|
|
3
4
|
import { buildRenderProps, getRenderFunction, mergeResolvers } from "../core/renderer.mjs";
|
|
@@ -74,10 +75,9 @@ function SchemaView({ schema: schemaInput, ref: refInput, value, fields, meta: c
|
|
|
74
75
|
};
|
|
75
76
|
const tree = walk(jsonSchema, walkOptions);
|
|
76
77
|
const userResolver = resolver !== void 0 ? mergeResolvers(resolver, headlessResolver) : headlessResolver;
|
|
77
|
-
const MAX_SERVER_DEPTH = 10;
|
|
78
78
|
const makeRenderChild = (currentDepth, parentPath) => (childTree, childValue, pathSuffix) => {
|
|
79
79
|
const childPath = joinPath(parentPath, pathSuffix);
|
|
80
|
-
if (currentDepth >=
|
|
80
|
+
if (currentDepth >= 10) return createElement("fieldset", null, createElement("em", null, `\u21bb ${typeof childTree.meta.description === "string" ? childTree.meta.description : "schema"} (recursive)`));
|
|
81
81
|
return renderFieldServer(childTree, childValue, userResolver, makeRenderChild(currentDepth + 1, childPath), childPath, widgets);
|
|
82
82
|
};
|
|
83
83
|
const renderChild = makeRenderChild(0, rootPath);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { j as WalkedField } from "../types-BTB73MB8.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/react/a11y.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Build the ARIA attribute bundle for a renderer.
|
|
6
|
+
*
|
|
7
|
+
* - `aria-required="true"` whenever the field is non-optional.
|
|
8
|
+
* - `aria-label=<description>` when a non-empty description is supplied.
|
|
9
|
+
*
|
|
10
|
+
* Returns a plain `Record<string, string>` (rather than a typed
|
|
11
|
+
* attribute interface) so callers can spread the result into any JSX
|
|
12
|
+
* element type without per-element TypeScript widening.
|
|
13
|
+
*
|
|
14
|
+
* Each helper from `html/a11y.ts` returns its corresponding fragment
|
|
15
|
+
* separately. The React renderers merge them into a single object
|
|
16
|
+
* here because the headless renderers compose one attribute bag per
|
|
17
|
+
* `<input>` element rather than threading multiple bags through `h()`.
|
|
18
|
+
*/
|
|
19
|
+
declare function buildAriaAttrs(tree: WalkedField, description?: unknown): Record<string, string>;
|
|
20
|
+
//#endregion
|
|
21
|
+
export { buildAriaAttrs };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
//#region src/react/a11y.ts
|
|
2
|
+
/**
|
|
3
|
+
* Build the ARIA attribute bundle for a renderer.
|
|
4
|
+
*
|
|
5
|
+
* - `aria-required="true"` whenever the field is non-optional.
|
|
6
|
+
* - `aria-label=<description>` when a non-empty description is supplied.
|
|
7
|
+
*
|
|
8
|
+
* Returns a plain `Record<string, string>` (rather than a typed
|
|
9
|
+
* attribute interface) so callers can spread the result into any JSX
|
|
10
|
+
* element type without per-element TypeScript widening.
|
|
11
|
+
*
|
|
12
|
+
* Each helper from `html/a11y.ts` returns its corresponding fragment
|
|
13
|
+
* separately. The React renderers merge them into a single object
|
|
14
|
+
* here because the headless renderers compose one attribute bag per
|
|
15
|
+
* `<input>` element rather than threading multiple bags through `h()`.
|
|
16
|
+
*/
|
|
17
|
+
function buildAriaAttrs(tree, description) {
|
|
18
|
+
const attrs = {};
|
|
19
|
+
if (tree.isOptional === false) attrs["aria-required"] = "true";
|
|
20
|
+
if (typeof description === "string" && description.length > 0) attrs["aria-label"] = description;
|
|
21
|
+
return attrs;
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
export { buildAriaAttrs };
|
package/dist/react/headless.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { renderArray, renderBoolean, renderConditional, renderDiscriminatedUnion, renderEnum, renderFile, renderLiteral, renderNegation, renderNever, renderNull, renderNumber, renderObject, renderRecord,
|
|
1
|
+
import { renderArray, renderBoolean, renderConditional, renderDiscriminatedUnion, renderEnum, renderFile, renderLiteral, renderNegation, renderNever, renderNull, renderNumber, renderObject, renderRecord, renderString, renderTuple, renderUnion, renderUnknown } from "./headlessRenderers.mjs";
|
|
2
2
|
//#region src/react/headless.tsx
|
|
3
3
|
/**
|
|
4
4
|
* The headless resolver uses props.renderChild for recursive rendering.
|
|
@@ -27,7 +27,6 @@ const headlessResolver = {
|
|
|
27
27
|
negation: renderNegation,
|
|
28
28
|
literal: renderLiteral,
|
|
29
29
|
file: renderFile,
|
|
30
|
-
recursive: renderRecursive,
|
|
31
30
|
never: renderNever,
|
|
32
31
|
unknown: renderUnknown
|
|
33
32
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { l as RenderProps } from "../renderer-
|
|
1
|
+
import { j as WalkedField } from "../types-BTB73MB8.mjs";
|
|
2
|
+
import { l as RenderProps } from "../renderer-CXJ8y0qw.mjs";
|
|
3
3
|
import { ReactNode } from "react";
|
|
4
4
|
|
|
5
5
|
//#region src/react/headlessRenderers.d.ts
|
|
@@ -10,15 +10,14 @@ import { ReactNode } from "react";
|
|
|
10
10
|
declare function toReactNode(value: unknown): ReactNode;
|
|
11
11
|
/**
|
|
12
12
|
* Build a stable, unique input ID from the path.
|
|
13
|
-
* Used for `htmlFor`/`id` association between labels and inputs.
|
|
14
13
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
14
|
+
* Re-exported alias for {@link fieldDomId} so external themes (shadcn,
|
|
15
|
+
* MUI, mantine, radix) keep importing `inputId` from the React entry
|
|
16
|
+
* point. Both the React and HTML renderers must derive the same id from
|
|
17
|
+
* the same path — `fieldDomId` in `core/idPath.ts` is the single
|
|
18
|
+
* source-of-truth.
|
|
19
19
|
*
|
|
20
|
-
*
|
|
21
|
-
* id valid as a CSS selector and predictable in test queries.
|
|
20
|
+
* Throws on an empty path; see `fieldDomId` for the rationale.
|
|
22
21
|
*/
|
|
23
22
|
declare function inputId(path: string): string;
|
|
24
23
|
declare function renderString(props: RenderProps): ReactNode;
|
|
@@ -56,7 +55,6 @@ declare function renderDiscriminatedUnion(props: RenderProps): ReactNode;
|
|
|
56
55
|
*/
|
|
57
56
|
declare function discriminatedUnionValueForTab(optionLabels: readonly string[], discKey: string, newIndex: number): Record<string, string> | undefined;
|
|
58
57
|
declare function renderFile(props: RenderProps): ReactNode;
|
|
59
|
-
declare function renderRecursive(props: RenderProps): ReactNode;
|
|
60
58
|
/**
|
|
61
59
|
* Render a literal field — `z.literal("a")` or `{ const: 5 }`.
|
|
62
60
|
*
|
|
@@ -110,4 +108,4 @@ declare function renderConditional(props: RenderProps): ReactNode;
|
|
|
110
108
|
declare function renderNegation(props: RenderProps): ReactNode;
|
|
111
109
|
declare function renderUnknown(props: RenderProps): ReactNode;
|
|
112
110
|
//#endregion
|
|
113
|
-
export { defaultRecordValue, discriminatedUnionValueForTab, inputId, nextRecordKey, renameRecordKey, renderArray, renderBoolean, renderConditional, renderDiscriminatedUnion, renderEnum, renderFile, renderLiteral, renderNegation, renderNever, renderNull, renderNumber, renderObject, renderRecord,
|
|
111
|
+
export { defaultRecordValue, discriminatedUnionValueForTab, inputId, nextRecordKey, renameRecordKey, renderArray, renderBoolean, renderConditional, renderDiscriminatedUnion, renderEnum, renderFile, renderLiteral, renderNegation, renderNever, renderNull, renderNumber, renderObject, renderRecord, renderString, renderTuple, renderUnion, renderUnknown, toReactNode };
|