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.
Files changed (91) hide show
  1. package/README.md +3 -1
  2. package/dist/core/adapter.d.mts +115 -4
  3. package/dist/core/adapter.mjs +405 -75
  4. package/dist/core/constraints.d.mts +2 -2
  5. package/dist/core/constraints.mjs +0 -7
  6. package/dist/core/cssClasses.d.mts +52 -0
  7. package/dist/core/cssClasses.mjs +51 -0
  8. package/dist/core/diagnostics.d.mts +1 -1
  9. package/dist/core/errors.d.mts +1 -1
  10. package/dist/core/errors.mjs +5 -13
  11. package/dist/core/fieldOrder.d.mts +1 -1
  12. package/dist/core/formats.d.mts +30 -2
  13. package/dist/core/formats.mjs +33 -1
  14. package/dist/core/idPath.d.mts +54 -0
  15. package/dist/core/idPath.mjs +66 -0
  16. package/dist/core/limits.d.mts +2 -0
  17. package/dist/core/limits.mjs +23 -0
  18. package/dist/core/merge.d.mts +10 -1
  19. package/dist/core/merge.mjs +49 -10
  20. package/dist/core/normalise.d.mts +40 -3
  21. package/dist/core/normalise.mjs +2 -2
  22. package/dist/core/openapi30.d.mts +15 -1
  23. package/dist/core/openapi30.mjs +2 -2
  24. package/dist/core/openapiConstants.d.mts +67 -0
  25. package/dist/core/openapiConstants.mjs +90 -0
  26. package/dist/core/ref.d.mts +2 -2
  27. package/dist/core/ref.mjs +85 -6
  28. package/dist/core/refChain.d.mts +70 -0
  29. package/dist/core/refChain.mjs +44 -0
  30. package/dist/core/renderer.d.mts +1 -1
  31. package/dist/core/renderer.mjs +0 -2
  32. package/dist/core/swagger2.d.mts +1 -1
  33. package/dist/core/swagger2.mjs +1 -1
  34. package/dist/core/typeInference.d.mts +982 -2
  35. package/dist/core/types.d.mts +2 -2
  36. package/dist/core/types.mjs +1 -4
  37. package/dist/core/unionMatch.d.mts +36 -0
  38. package/dist/core/unionMatch.mjs +53 -0
  39. package/dist/core/version.d.mts +1 -1
  40. package/dist/core/version.mjs +29 -17
  41. package/dist/core/walkBuilders.d.mts +23 -4
  42. package/dist/core/walkBuilders.mjs +27 -7
  43. package/dist/core/walker.d.mts +1 -1
  44. package/dist/core/walker.mjs +123 -47
  45. package/dist/{diagnostics-CbBPsxSt.d.mts → diagnostics-BS2kaUyE.d.mts} +1 -1
  46. package/dist/{errors-QEwOtQAA.d.mts → errors-g_MCTQel.d.mts} +10 -16
  47. package/dist/html/a11y.d.mts +9 -4
  48. package/dist/html/a11y.mjs +10 -12
  49. package/dist/html/renderToHtml.d.mts +10 -3
  50. package/dist/html/renderToHtml.mjs +13 -3
  51. package/dist/html/renderToHtmlStream.d.mts +2 -2
  52. package/dist/html/renderToHtmlStream.mjs +12 -1
  53. package/dist/html/renderers.d.mts +43 -8
  54. package/dist/html/renderers.mjs +136 -116
  55. package/dist/html/streamRenderers.d.mts +6 -6
  56. package/dist/html/streamRenderers.mjs +129 -89
  57. package/dist/limits-Cw5QZND8.d.mts +29 -0
  58. package/dist/{normalise-DaSrnr8g.mjs → normalise-DCYp06Sr.mjs} +770 -227
  59. package/dist/openapi/ApiCallbacks.d.mts +1 -1
  60. package/dist/openapi/ApiLinks.d.mts +1 -1
  61. package/dist/openapi/ApiResponseHeaders.d.mts +1 -1
  62. package/dist/openapi/ApiSecurity.d.mts +1 -1
  63. package/dist/openapi/ApiSecurity.mjs +16 -2
  64. package/dist/openapi/components.d.mts +234 -23
  65. package/dist/openapi/components.mjs +183 -52
  66. package/dist/openapi/parser.d.mts +9 -8
  67. package/dist/openapi/parser.mjs +252 -70
  68. package/dist/openapi/resolve.d.mts +31 -15
  69. package/dist/openapi/resolve.mjs +260 -40
  70. package/dist/react/SchemaComponent.d.mts +126 -36
  71. package/dist/react/SchemaComponent.mjs +95 -57
  72. package/dist/react/SchemaView.d.mts +30 -10
  73. package/dist/react/SchemaView.mjs +2 -2
  74. package/dist/react/a11y.d.mts +21 -0
  75. package/dist/react/a11y.mjs +24 -0
  76. package/dist/react/fieldPath.d.mts +1 -1
  77. package/dist/react/headless.d.mts +1 -1
  78. package/dist/react/headless.mjs +1 -2
  79. package/dist/react/headlessRenderers.d.mts +9 -11
  80. package/dist/react/headlessRenderers.mjs +51 -102
  81. package/dist/{ref-si8ViYun.d.mts → ref-DjLEKa_E.d.mts} +38 -3
  82. package/dist/{renderer-DI6ZYf7a.d.mts → renderer-CXJ8y0qw.d.mts} +2 -2
  83. package/dist/themes/mantine.d.mts +1 -1
  84. package/dist/themes/mui.d.mts +1 -1
  85. package/dist/themes/radix.d.mts +1 -1
  86. package/dist/themes/shadcn.d.mts +1 -1
  87. package/dist/themes/shadcn.mjs +2 -1
  88. package/dist/{types-BnxPEElk.d.mts → types-BTB73MB8.d.mts} +35 -14
  89. package/dist/{version-D-u7aMfy.d.mts → version-BFTVLsdb.d.mts} +7 -1
  90. package/package.json +1 -3
  91. 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({ schema: schemaInput, ref: refInput, value, onChange, validate, onValidationError, onError, onDiagnostic, strict, fields, meta: componentMeta, readOnly, writeOnly, description, widgets: instanceWidgets, idPrefix }) {
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
- const error = runValidation(zodSchema, jsonSchema, nextValue, onDiagnostic);
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(fields, error);
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
- fields,
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: fields,
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 safeParseFn = zodSchema.safeParse;
152
- if (isCallable(safeParseFn)) {
153
- const result = safeParseFn(value);
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
- emitFromJsonSchemaDiagnostic(err, onDiagnostic);
163
- return;
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 ROOT_PATH for the root field and use renderChild's pathSuffix to derive child paths.");
198
- if (depth >= MAX_RENDER_DEPTH) {
199
- const refTarget = tree.type === "recursive" ? tree.refTarget : "";
200
- return /* @__PURE__ */ jsx("fieldset", { children: /* @__PURE__ */ jsxs("em", { children: [
201
- " ",
202
- (typeof tree.meta.description === "string" ? tree.meta.description : refTarget) || "schema",
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 overrides = toRecordOrUndefined(fields);
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 { ROOT_PATH, SchemaComponent, SchemaField, SchemaProvider, joinPath, registerWidget, renderField, sanitisePrefix };
355
+ export { SchemaComponent, SchemaField, SchemaProvider, joinPath, registerWidget, renderField, sanitisePrefix };
@@ -1,16 +1,36 @@
1
- import { T as SchemaMeta } from "../types-BnxPEElk.mjs";
2
- import { t as Diagnostic } from "../diagnostics-CbBPsxSt.mjs";
3
- import { r as ComponentResolver } from "../renderer-DI6ZYf7a.mjs";
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
- /** Zod schema, JSON Schema object, or OpenAPI document. */
10
- schema: unknown;
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?: string;
13
- /** Current value to render. */
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 >= 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)`));
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 };
@@ -1,4 +1,4 @@
1
- import { M as WalkedField } from "../types-BnxPEElk.mjs";
1
+ import { j as WalkedField } from "../types-BTB73MB8.mjs";
2
2
 
3
3
  //#region src/react/fieldPath.d.ts
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { r as ComponentResolver } from "../renderer-DI6ZYf7a.mjs";
1
+ import { r as ComponentResolver } from "../renderer-CXJ8y0qw.mjs";
2
2
 
3
3
  //#region src/react/headless.d.ts
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { renderArray, renderBoolean, renderConditional, renderDiscriminatedUnion, renderEnum, renderFile, renderLiteral, renderNegation, renderNever, renderNull, renderNumber, renderObject, renderRecord, renderRecursive, renderString, renderTuple, renderUnion, renderUnknown } from "./headlessRenderers.mjs";
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 { M as WalkedField } from "../types-BnxPEElk.mjs";
2
- import { l as RenderProps } from "../renderer-DI6ZYf7a.mjs";
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
- * 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`).
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
- * 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.
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, renderRecursive, renderString, renderTuple, renderUnion, renderUnknown, toReactNode };
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 };