schema-components 1.22.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 (82) hide show
  1. package/README.md +3 -1
  2. package/dist/core/adapter.d.mts +97 -3
  3. package/dist/core/adapter.mjs +260 -111
  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 +9 -2
  13. package/dist/core/formats.mjs +12 -1
  14. package/dist/core/idPath.d.mts +54 -0
  15. package/dist/core/idPath.mjs +66 -0
  16. package/dist/core/merge.d.mts +10 -1
  17. package/dist/core/merge.mjs +49 -10
  18. package/dist/core/normalise.d.mts +14 -3
  19. package/dist/core/normalise.mjs +2 -2
  20. package/dist/core/openapi30.d.mts +15 -1
  21. package/dist/core/openapi30.mjs +2 -2
  22. package/dist/core/openapiConstants.d.mts +67 -0
  23. package/dist/core/openapiConstants.mjs +90 -0
  24. package/dist/core/ref.d.mts +2 -2
  25. package/dist/core/ref.mjs +84 -6
  26. package/dist/core/refChain.d.mts +70 -0
  27. package/dist/core/refChain.mjs +44 -0
  28. package/dist/core/renderer.d.mts +1 -1
  29. package/dist/core/swagger2.d.mts +1 -1
  30. package/dist/core/swagger2.mjs +1 -1
  31. package/dist/core/typeInference.d.mts +982 -2
  32. package/dist/core/types.d.mts +1 -1
  33. package/dist/core/unionMatch.d.mts +36 -0
  34. package/dist/core/unionMatch.mjs +53 -0
  35. package/dist/core/version.d.mts +1 -1
  36. package/dist/core/version.mjs +29 -17
  37. package/dist/core/walkBuilders.d.mts +23 -4
  38. package/dist/core/walkBuilders.mjs +27 -7
  39. package/dist/core/walker.d.mts +1 -1
  40. package/dist/core/walker.mjs +44 -45
  41. package/dist/{diagnostics-D0QCYGv0.d.mts → diagnostics-BS2kaUyE.d.mts} +1 -1
  42. package/dist/{errors-DpFwqs5C.d.mts → errors-g_MCTQel.d.mts} +9 -15
  43. package/dist/html/a11y.d.mts +9 -4
  44. package/dist/html/a11y.mjs +10 -19
  45. package/dist/html/renderToHtml.d.mts +2 -2
  46. package/dist/html/renderToHtmlStream.d.mts +2 -2
  47. package/dist/html/renderToHtmlStream.mjs +12 -1
  48. package/dist/html/renderers.d.mts +43 -8
  49. package/dist/html/renderers.mjs +136 -111
  50. package/dist/html/streamRenderers.d.mts +4 -5
  51. package/dist/html/streamRenderers.mjs +40 -61
  52. package/dist/{normalise-DVEJQmF7.mjs → normalise-DCYp06Sr.mjs} +352 -162
  53. package/dist/openapi/ApiCallbacks.d.mts +1 -1
  54. package/dist/openapi/ApiLinks.d.mts +1 -1
  55. package/dist/openapi/ApiResponseHeaders.d.mts +1 -1
  56. package/dist/openapi/ApiSecurity.d.mts +1 -1
  57. package/dist/openapi/components.d.mts +116 -37
  58. package/dist/openapi/components.mjs +54 -37
  59. package/dist/openapi/parser.d.mts +9 -8
  60. package/dist/openapi/parser.mjs +234 -84
  61. package/dist/openapi/resolve.d.mts +20 -11
  62. package/dist/openapi/resolve.mjs +133 -73
  63. package/dist/react/SchemaComponent.d.mts +32 -7
  64. package/dist/react/SchemaComponent.mjs +45 -21
  65. package/dist/react/SchemaView.d.mts +30 -10
  66. package/dist/react/a11y.d.mts +21 -0
  67. package/dist/react/a11y.mjs +24 -0
  68. package/dist/react/fieldPath.d.mts +1 -1
  69. package/dist/react/headless.d.mts +1 -1
  70. package/dist/react/headlessRenderers.d.mts +8 -9
  71. package/dist/react/headlessRenderers.mjs +41 -72
  72. package/dist/{ref-D-_JBZkF.d.mts → ref-DjLEKa_E.d.mts} +38 -3
  73. package/dist/{renderer-BaRlQIuN.d.mts → renderer-CXJ8y0qw.d.mts} +1 -1
  74. package/dist/themes/mantine.d.mts +1 -1
  75. package/dist/themes/mui.d.mts +1 -1
  76. package/dist/themes/radix.d.mts +1 -1
  77. package/dist/themes/shadcn.d.mts +1 -1
  78. package/dist/themes/shadcn.mjs +2 -1
  79. package/dist/{types-BrRMV0en.d.mts → types-BTB73MB8.d.mts} +32 -4
  80. package/dist/{version-D2jfdX6E.d.mts → version-BFTVLsdb.d.mts} +7 -1
  81. package/package.json +1 -1
  82. package/dist/typeInference-DkcUHfaM.d.mts +0 -982
@@ -1,6 +1,12 @@
1
1
  import { isObject } from "../core/guards.mjs";
2
+ import { dateInputType } from "../core/formats.mjs";
2
3
  import { isSafeHyperlink, isSafeMailtoAddress } from "../core/uri.mjs";
4
+ import { SC_CLASSES } from "../core/cssClasses.mjs";
3
5
  import { sortFieldsByOrder } from "../core/fieldOrder.mjs";
6
+ import { fieldDomId, panelIdFor, tabIdFor } from "../core/idPath.mjs";
7
+ import { matchUnionOption, resolveDiscriminatedActive } from "../core/unionMatch.mjs";
8
+ import { displayJsonValue } from "../core/walkBuilders.mjs";
9
+ import { buildAriaAttrs } from "./a11y.mjs";
4
10
  import { jsx, jsxs } from "react/jsx-runtime";
5
11
  import { isValidElement, useCallback, useEffect, useRef } from "react";
6
12
  //#region src/react/headlessRenderers.tsx
@@ -41,26 +47,19 @@ function formatTime(value) {
41
47
  if (isNaN(date.getTime())) return void 0;
42
48
  return date.toLocaleTimeString();
43
49
  }
44
- function dateInputType(format) {
45
- if (format === "date") return "date";
46
- if (format === "time") return "time";
47
- if (format === "date-time" || format === "datetime") return "datetime-local";
48
- }
49
50
  /**
50
51
  * Build a stable, unique input ID from the path.
51
- * Used for `htmlFor`/`id` association between labels and inputs.
52
52
  *
53
- * Throws on an empty path: the previous "sc-field" fallback caused every
54
- * input across a form to share the same id, breaking label-input pairing
55
- * and screen reader navigation. Callers must thread a non-empty path
56
- * (see `ROOT_PATH` and `joinPath` in `SchemaComponent.tsx`).
53
+ * Re-exported alias for {@link fieldDomId} so external themes (shadcn,
54
+ * MUI, mantine, radix) keep importing `inputId` from the React entry
55
+ * point. Both the React and HTML renderers must derive the same id from
56
+ * the same path `fieldDomId` in `core/idPath.ts` is the single
57
+ * source-of-truth.
57
58
  *
58
- * Dots and bracket indices in paths are converted to hyphens to keep the
59
- * id valid as a CSS selector and predictable in test queries.
59
+ * Throws on an empty path; see `fieldDomId` for the rationale.
60
60
  */
61
61
  function inputId(path) {
62
- if (path.length === 0) throw new Error("inputId requires a non-empty path. Pass ROOT_PATH for the root field and use renderChild's pathSuffix to derive child paths.");
63
- return `sc-${path.replace(/[.[\]]+/g, "-").replace(/-+$/g, "")}`;
62
+ return fieldDomId(path);
64
63
  }
65
64
  function renderString(props) {
66
65
  const id = inputId(props.path);
@@ -107,8 +106,7 @@ function renderString(props) {
107
106
  }
108
107
  const strValue = typeof props.value === "string" ? props.value : "";
109
108
  const dateType = dateInputType(props.constraints.format);
110
- const ariaAttrs = {};
111
- if (props.tree.isOptional === false) ariaAttrs["aria-required"] = "true";
109
+ const ariaAttrs = buildAriaAttrs(props.tree);
112
110
  if (dateType !== void 0) return /* @__PURE__ */ jsx("input", {
113
111
  id,
114
112
  type: dateType,
@@ -131,7 +129,7 @@ function renderString(props) {
131
129
  value: "",
132
130
  children: ["Select", "…"]
133
131
  }), enumValues.map((v) => {
134
- const display = v === null ? "null" : typeof v === "string" ? v : String(v);
132
+ const display = displayJsonValue(v);
135
133
  return /* @__PURE__ */ jsx("option", {
136
134
  value: display,
137
135
  children: display
@@ -167,8 +165,7 @@ function renderNumber(props) {
167
165
  });
168
166
  }
169
167
  const numValue = typeof props.value === "number" ? props.value : "";
170
- const ariaAttrs = {};
171
- if (props.tree.isOptional === false) ariaAttrs["aria-required"] = "true";
168
+ const ariaAttrs = buildAriaAttrs(props.tree);
172
169
  return /* @__PURE__ */ jsx("input", {
173
170
  id,
174
171
  type: "number",
@@ -195,9 +192,7 @@ function renderBoolean(props) {
195
192
  children: props.value ? "Yes" : "No"
196
193
  });
197
194
  }
198
- const ariaAttrs = {};
199
- if (props.tree.isOptional === false) ariaAttrs["aria-required"] = "true";
200
- if (typeof props.meta.description === "string") ariaAttrs["aria-label"] = props.meta.description;
195
+ const ariaAttrs = buildAriaAttrs(props.tree, props.meta.description);
201
196
  return /* @__PURE__ */ jsx("input", {
202
197
  id,
203
198
  type: "checkbox",
@@ -216,8 +211,7 @@ function renderEnum(props) {
216
211
  "aria-readonly": "true",
217
212
  children: enumValue || "—"
218
213
  });
219
- const ariaAttrs = {};
220
- if (props.tree.isOptional === false) ariaAttrs["aria-required"] = "true";
214
+ const ariaAttrs = buildAriaAttrs(props.tree);
221
215
  const enumValues = props.tree.type === "enum" ? props.tree.enumValues : [];
222
216
  return /* @__PURE__ */ jsxs("select", {
223
217
  id,
@@ -230,7 +224,7 @@ function renderEnum(props) {
230
224
  value: "",
231
225
  children: ["Select", "…"]
232
226
  }), enumValues.map((v) => {
233
- const display = v === null ? "null" : typeof v === "string" ? v : String(v);
227
+ const display = displayJsonValue(v);
234
228
  return /* @__PURE__ */ jsx("option", {
235
229
  value: display,
236
230
  children: display
@@ -315,7 +309,7 @@ function renderRecord(props) {
315
309
  });
316
310
  return /* @__PURE__ */ jsx("div", {
317
311
  role: "group",
318
- "aria-label": props.meta.description ?? "Record",
312
+ "aria-label": typeof props.meta.description === "string" ? props.meta.description : void 0,
319
313
  children: entries.map(([key, value]) => {
320
314
  return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("label", {
321
315
  htmlFor: inputId(`${props.path}.${key}`),
@@ -415,32 +409,16 @@ function renderUnion(props) {
415
409
  return /* @__PURE__ */ jsx("span", { children: "—" });
416
410
  }
417
411
  function renderDiscriminatedUnion(props) {
418
- const options = props.tree.type === "discriminatedUnion" ? props.tree.options : void 0;
419
- const discriminator = props.tree.type === "discriminatedUnion" ? props.tree.discriminator : void 0;
420
- if (options === void 0 || options.length === 0) {
412
+ if (props.tree.type !== "discriminatedUnion") {
421
413
  if (props.value === void 0 || props.value === null) return /* @__PURE__ */ jsx("span", { children: "—" });
422
414
  return /* @__PURE__ */ jsx("span", { children: JSON.stringify(props.value) });
423
415
  }
424
- const obj = isObject(props.value) ? props.value : {};
425
- const discKey = discriminator ?? "";
426
- const currentDiscriminatorValue = typeof obj[discKey] === "string" ? obj[discKey] : void 0;
427
- const optionLabels = options.map((opt) => {
428
- if (opt.type === "object") {
429
- const discriminatorField = opt.fields[discKey];
430
- if (discriminatorField?.type === "literal") {
431
- const constVal = discriminatorField.literalValues[0];
432
- if (typeof constVal === "string") return constVal;
433
- }
434
- }
435
- return typeof opt.meta.title === "string" ? opt.meta.title : opt.type;
436
- });
437
- let activeIndex = 0;
438
- if (currentDiscriminatorValue !== void 0) {
439
- const found = optionLabels.indexOf(currentDiscriminatorValue);
440
- if (found !== -1) activeIndex = found;
416
+ const { options, discriminator: discKey } = props.tree;
417
+ if (options.length === 0) {
418
+ if (props.value === void 0 || props.value === null) return /* @__PURE__ */ jsx("span", { children: "—" });
419
+ return /* @__PURE__ */ jsx("span", { children: JSON.stringify(props.value) });
441
420
  }
442
- const activeOption = options[activeIndex];
443
- const panelId = inputId(props.path);
421
+ const { optionLabels, activeIndex, activeOption } = resolveDiscriminatedActive(options, discKey, isObject(props.value) ? props.value : void 0);
444
422
  if (props.readOnly) {
445
423
  if (activeOption !== void 0) return toReactNode(props.renderChild(activeOption, props.value, props.onChange));
446
424
  return /* @__PURE__ */ jsx("span", { children: "—" });
@@ -449,7 +427,7 @@ function renderDiscriminatedUnion(props) {
449
427
  options,
450
428
  optionLabels,
451
429
  activeIndex,
452
- panelId,
430
+ path: props.path,
453
431
  discKey,
454
432
  props
455
433
  });
@@ -479,7 +457,8 @@ function discriminatedUnionValueForTab(optionLabels, discKey, newIndex) {
479
457
  * "Automatic activation" means each arrow key both moves focus and
480
458
  * activates the new tab in one step — selection and focus stay aligned.
481
459
  */
482
- function DiscriminatedUnionTabs({ options, optionLabels, activeIndex, panelId, discKey, props }) {
460
+ function DiscriminatedUnionTabs({ options, optionLabels, activeIndex, path, discKey, props }) {
461
+ const panelId = panelIdFor(path);
483
462
  const tabRefs = useRef([]);
484
463
  const pendingFocusRef = useRef(false);
485
464
  const handleTabChange = useCallback((newIndex) => {
@@ -530,9 +509,9 @@ function DiscriminatedUnionTabs({ options, optionLabels, activeIndex, panelId, d
530
509
  },
531
510
  type: "button",
532
511
  role: "tab",
533
- id: `${panelId}-tab-${String(i)}`,
512
+ id: tabIdFor(path, i),
534
513
  "aria-selected": i === activeIndex ? "true" : void 0,
535
- "aria-controls": `${panelId}-panel`,
514
+ "aria-controls": panelId,
536
515
  tabIndex: i === activeIndex ? 0 : -1,
537
516
  onClick: () => {
538
517
  handleTabChange(i);
@@ -549,8 +528,8 @@ function DiscriminatedUnionTabs({ options, optionLabels, activeIndex, panelId, d
549
528
  }, String(i)))
550
529
  }), /* @__PURE__ */ jsx("div", {
551
530
  role: "tabpanel",
552
- id: `${panelId}-panel`,
553
- "aria-labelledby": `${panelId}-tab-${String(activeIndex)}`,
531
+ id: panelId,
532
+ "aria-labelledby": tabIdFor(path, activeIndex),
554
533
  children: activeOption !== void 0 && toReactNode(props.renderChild(activeOption, props.value, props.onChange))
555
534
  })] });
556
535
  }
@@ -562,9 +541,6 @@ function renderFile(props) {
562
541
  "aria-readonly": "true",
563
542
  children: "File field"
564
543
  });
565
- const ariaAttrs = {};
566
- if (props.tree.isOptional === false) ariaAttrs["aria-required"] = "true";
567
- if (typeof props.meta.description === "string") ariaAttrs["aria-label"] = props.meta.description;
568
544
  return /* @__PURE__ */ jsx("input", {
569
545
  id,
570
546
  type: "file",
@@ -573,7 +549,7 @@ function renderFile(props) {
573
549
  const file = e.target.files?.[0];
574
550
  if (file !== void 0) props.onChange(file);
575
551
  },
576
- ...ariaAttrs
552
+ ...buildAriaAttrs(props.tree, props.meta.description)
577
553
  });
578
554
  }
579
555
  /**
@@ -595,7 +571,7 @@ function renderLiteral(props) {
595
571
  return /* @__PURE__ */ jsx("span", {
596
572
  id,
597
573
  "aria-readonly": "true",
598
- children: values.map((v) => v === null ? "null" : String(v)).join(", ")
574
+ children: values.map((v) => displayJsonValue(v)).join(", ")
599
575
  });
600
576
  }
601
577
  /**
@@ -624,7 +600,7 @@ function renderNever(props) {
624
600
  return /* @__PURE__ */ jsx("span", {
625
601
  id: inputId(props.path),
626
602
  "aria-readonly": "true",
627
- className: "sc-never",
603
+ className: SC_CLASSES.never,
628
604
  children: /* @__PURE__ */ jsx("em", { children: "never matches" })
629
605
  });
630
606
  }
@@ -678,10 +654,10 @@ function renderConditional(props) {
678
654
  if (props.tree.type !== "conditional") return null;
679
655
  const { ifClause, thenClause, elseClause } = props.tree;
680
656
  return /* @__PURE__ */ jsxs("fieldset", {
681
- className: "sc-conditional",
657
+ className: SC_CLASSES.conditional,
682
658
  children: [
683
659
  /* @__PURE__ */ jsxs("div", {
684
- className: "sc-conditional-if",
660
+ className: SC_CLASSES.conditionalIf,
685
661
  children: [
686
662
  /* @__PURE__ */ jsx("strong", { children: "if:" }),
687
663
  " ",
@@ -689,7 +665,7 @@ function renderConditional(props) {
689
665
  ]
690
666
  }),
691
667
  thenClause !== void 0 && /* @__PURE__ */ jsxs("div", {
692
- className: "sc-conditional-then",
668
+ className: SC_CLASSES.conditionalThen,
693
669
  children: [
694
670
  /* @__PURE__ */ jsx("strong", { children: "then:" }),
695
671
  " ",
@@ -697,7 +673,7 @@ function renderConditional(props) {
697
673
  ]
698
674
  }),
699
675
  elseClause !== void 0 && /* @__PURE__ */ jsxs("div", {
700
- className: "sc-conditional-else",
676
+ className: SC_CLASSES.conditionalElse,
701
677
  children: [
702
678
  /* @__PURE__ */ jsx("strong", { children: "else:" }),
703
679
  " ",
@@ -717,7 +693,7 @@ function renderConditional(props) {
717
693
  function renderNegation(props) {
718
694
  if (props.tree.type !== "negation") return null;
719
695
  return /* @__PURE__ */ jsxs("fieldset", {
720
- className: "sc-negation",
696
+ className: SC_CLASSES.negation,
721
697
  children: [
722
698
  /* @__PURE__ */ jsx("strong", { children: "Must NOT match:" }),
723
699
  " ",
@@ -749,12 +725,5 @@ function renderUnknown(props) {
749
725
  }
750
726
  });
751
727
  }
752
- function matchUnionOption(options, value) {
753
- if (typeof value === "string") return options.find((o) => o.type === "string" || o.type === "enum");
754
- if (typeof value === "number") return options.find((o) => o.type === "number");
755
- if (typeof value === "boolean") return options.find((o) => o.type === "boolean");
756
- if (Array.isArray(value)) return options.find((o) => o.type === "array");
757
- if (typeof value === "object" && value !== null) return options.find((o) => o.type === "object");
758
- }
759
728
  //#endregion
760
729
  export { defaultRecordValue, discriminatedUnionValueForTab, inputId, nextRecordKey, renameRecordKey, renderArray, renderBoolean, renderConditional, renderDiscriminatedUnion, renderEnum, renderFile, renderLiteral, renderNegation, renderNever, renderNull, renderNumber, renderObject, renderRecord, renderString, renderTuple, renderUnion, renderUnknown, toReactNode };
@@ -1,6 +1,13 @@
1
- import { i as DiagnosticsOptions } from "./diagnostics-D0QCYGv0.mjs";
1
+ import { i as DiagnosticsOptions } from "./diagnostics-BS2kaUyE.mjs";
2
2
 
3
3
  //#region src/core/ref.d.ts
4
+ /**
5
+ * The canonical recursive `$anchor` name synthesised by the Draft
6
+ * 2019-09 `$recursiveAnchor: true` rewrite. Re-exported here so the
7
+ * collision check in {@link findAnchor} stays aligned with the
8
+ * rewriter in `core/normalise.ts`.
9
+ */
10
+ declare const RECURSIVE_ANCHOR_SENTINEL = "__recursive__";
4
11
  /**
5
12
  * Resolver function for external $ref URIs.
6
13
  * Called with the URI portion (everything before `#`) of an external ref.
@@ -33,17 +40,45 @@ declare function resolveRef(schema: Record<string, unknown>, rootDocument: Recor
33
40
  /**
34
41
  * Dereference a JSON Pointer fragment (`#/path/to/schema`) or an
35
42
  * `$anchor` (`#SomeName`) against a root document.
43
+ *
44
+ * Returns the resolved sub-schema, which may be a JSON object or — per
45
+ * Draft 06+ — a boolean (`true` for the always-valid schema, `false`
46
+ * for the never-valid schema). Returns `undefined` when the pointer or
47
+ * anchor cannot be resolved.
48
+ *
49
+ * JSON Pointer segments are percent-decoded per RFC 6901 §6 before the
50
+ * `~1`/`~0` token expansion; this allows pointers such as
51
+ * `#/paths/~1pets%20store` to resolve a path containing a literal space.
36
52
  */
37
- declare function dereference(ref: string, root: Record<string, unknown>): Record<string, unknown> | undefined;
53
+ declare function dereference(ref: string, root: Record<string, unknown>): Record<string, unknown> | boolean | undefined;
38
54
  /**
39
55
  * Recursively scan a schema document for a `$anchor` matching the given name.
40
56
  * Returns the schema object containing the anchor, or undefined.
41
57
  *
58
+ * Per JSON Schema 2020-12 §8.2, `$anchor` is scoped to the resource
59
+ * defined by the nearest enclosing `$id`. A bare DFS would happily
60
+ * cross resource boundaries and resolve to an anchor declared in an
61
+ * unrelated sub-resource — that violates the spec and produces wrong
62
+ * walker input when two sub-schemas use the same anchor name within
63
+ * their own `$id` scope.
64
+ *
65
+ * The walk skips into any sub-tree that introduces a new `$id` value:
66
+ * such a sub-tree is a separate resource and its `$anchor`s belong to
67
+ * that resource, not the caller's. Anchors declared at the same `$id`
68
+ * scope (or in nested sub-schemas without their own `$id`) remain
69
+ * reachable.
70
+ *
42
71
  * The optional `visited` set guards against shared object references and
43
72
  * cycles introduced by the OpenAPI bundler's `structuredClone`-based
44
73
  * inlining of external refs. Without it a recursive document would stack
45
74
  * overflow before reaching the matching anchor.
75
+ *
76
+ * When `crossResourceBoundary` is `true` the walker is currently
77
+ * recursing into a sub-tree that introduced its own `$id`; we still
78
+ * recurse so a nested `$anchor` declared inside that same sub-resource
79
+ * is reachable from the caller that owns that resource, but we skip
80
+ * further nested resources for the same reason as above.
46
81
  */
47
82
  declare function findAnchor(node: unknown, anchorName: string, visited?: WeakSet<object>): Record<string, unknown> | undefined;
48
83
  //#endregion
49
- export { findAnchor as a, dereference as i, RefOptions as n, resolveRef as o, countDistinctRefs as r, ExternalResolver as t };
84
+ export { dereference as a, countDistinctRefs as i, RECURSIVE_ANCHOR_SENTINEL as n, findAnchor as o, RefOptions as r, resolveRef as s, ExternalResolver as t };
@@ -1,4 +1,4 @@
1
- import { E as StringConstraints, f as FileConstraints, j as WalkedField, t as ArrayConstraints, w as SchemaMeta, x as ObjectConstraints, y as NumberConstraints } from "./types-BrRMV0en.mjs";
1
+ import { E as StringConstraints, f as FileConstraints, j as WalkedField, t as ArrayConstraints, w as SchemaMeta, x as ObjectConstraints, y as NumberConstraints } from "./types-BTB73MB8.mjs";
2
2
 
3
3
  //#region src/core/renderer.d.ts
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { r as ComponentResolver } from "../renderer-BaRlQIuN.mjs";
1
+ import { r as ComponentResolver } from "../renderer-CXJ8y0qw.mjs";
2
2
 
3
3
  //#region src/themes/mantine.d.ts
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { r as ComponentResolver } from "../renderer-BaRlQIuN.mjs";
1
+ import { r as ComponentResolver } from "../renderer-CXJ8y0qw.mjs";
2
2
 
3
3
  //#region src/themes/mui.d.ts
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { r as ComponentResolver } from "../renderer-BaRlQIuN.mjs";
1
+ import { r as ComponentResolver } from "../renderer-CXJ8y0qw.mjs";
2
2
 
3
3
  //#region src/themes/radix.d.ts
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { r as ComponentResolver } from "../renderer-BaRlQIuN.mjs";
1
+ import { r as ComponentResolver } from "../renderer-CXJ8y0qw.mjs";
2
2
 
3
3
  //#region src/themes/shadcn.d.ts
4
4
  declare const shadcnResolver: ComponentResolver;
@@ -1,5 +1,6 @@
1
1
  import { toRecord } from "../core/guards.mjs";
2
2
  import { sortFieldsByOrder } from "../core/fieldOrder.mjs";
3
+ import { displayJsonValue } from "../core/walkBuilders.mjs";
3
4
  import { inputId, toReactNode } from "../react/headlessRenderers.mjs";
4
5
  import { headlessResolver } from "../react/headless.mjs";
5
6
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -160,7 +161,7 @@ function renderEnumInput(props) {
160
161
  value: "",
161
162
  children: ["Select", "…"]
162
163
  }), props.tree.type === "enum" ? props.tree.enumValues.map((v) => {
163
- const display = v === null ? "null" : typeof v === "string" ? v : String(v);
164
+ const display = displayJsonValue(v);
164
165
  return /* @__PURE__ */ jsx("option", {
165
166
  value: display,
166
167
  children: display
@@ -86,8 +86,6 @@ interface ArrayConstraints {
86
86
  uniqueItems?: boolean;
87
87
  minContains?: number;
88
88
  maxContains?: number;
89
- /** Constraint schema for unevaluated items. */
90
- unevaluatedItems?: Record<string, unknown>;
91
89
  }
92
90
  /** Constraints that apply to object schemas. */
93
91
  interface ObjectConstraints {
@@ -138,12 +136,24 @@ interface NullField extends FieldBase {
138
136
  interface EnumField extends FieldBase {
139
137
  type: "enum";
140
138
  constraints: Record<string, never>;
141
- enumValues: (string | number | boolean | null)[];
139
+ /**
140
+ * Enum values from JSON Schema `enum`. Per Draft 2020-12 §6.1.2,
141
+ * `enum` accepts an array of arbitrary JSON values — not only
142
+ * primitives. Object and array values are preserved verbatim.
143
+ */
144
+ enumValues: unknown[];
142
145
  }
143
146
  interface LiteralField extends FieldBase {
144
147
  type: "literal";
145
148
  constraints: Record<string, never>;
146
- literalValues: (string | number | boolean | null)[];
149
+ /**
150
+ * Const values from JSON Schema `const`. Per Draft 2020-12 §6.1.3,
151
+ * `const` accepts any JSON value (object or array included), not
152
+ * only primitives. The walker emits a single-element array because
153
+ * `const` is scalar by definition; the field shape mirrors
154
+ * `EnumField` for renderer symmetry.
155
+ */
156
+ literalValues: unknown[];
147
157
  }
148
158
  interface ObjectField extends FieldBase {
149
159
  type: "object";
@@ -182,6 +192,13 @@ interface ArrayField extends FieldBase {
182
192
  contains?: WalkedField;
183
193
  /** Walked schema for unevaluated items. */
184
194
  unevaluatedItems?: WalkedField;
195
+ /**
196
+ * Whether `unevaluatedItems` is explicitly `false` (no extras
197
+ * permitted beyond the items evaluated by `items`/`prefixItems`/
198
+ * `contains`). Parallel to `additionalPropertiesClosed` and
199
+ * `unevaluatedPropertiesClosed` on `ObjectField`.
200
+ */
201
+ unevaluatedItemsClosed?: boolean;
185
202
  }
186
203
  interface TupleField extends FieldBase {
187
204
  type: "tuple";
@@ -200,6 +217,17 @@ interface TupleField extends FieldBase {
200
217
  * element schemas to require the presence of a specific element.
201
218
  */
202
219
  contains?: WalkedField;
220
+ /**
221
+ * Walked schema for `unevaluatedItems` adjacent to `prefixItems`.
222
+ * Permits additional items only when they satisfy this schema.
223
+ */
224
+ unevaluatedItems?: WalkedField;
225
+ /**
226
+ * Whether `unevaluatedItems` is explicitly `false` on a tuple. With
227
+ * `prefixItems` declared, this forbids any items beyond the prefix
228
+ * length.
229
+ */
230
+ unevaluatedItemsClosed?: boolean;
203
231
  }
204
232
  interface RecordField extends FieldBase {
205
233
  type: "record";
@@ -56,7 +56,13 @@ interface OpenApiVersionInfo {
56
56
  }
57
57
  /**
58
58
  * Detect the OpenAPI/Swagger version from a document.
59
- * Returns `undefined` for documents that are not OpenAPI or Swagger.
59
+ *
60
+ * Returns `undefined` when the document declares neither `swagger` nor
61
+ * `openapi` strings, or when the declared version string is malformed
62
+ * (missing parts or non-numeric segments). `Number("abc")` yields `NaN`,
63
+ * which `parts[i] ?? default` does NOT replace — nullish coalescing only
64
+ * substitutes `null`/`undefined` — so any unparseable segment surfaces
65
+ * as `undefined` rather than a silent fabricated default.
60
66
  */
61
67
  declare function detectOpenApiVersion(doc: Record<string, unknown>): OpenApiVersionInfo | undefined;
62
68
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "schema-components",
3
- "version": "1.22.0",
3
+ "version": "1.23.0",
4
4
  "description": "React components that render UI from Zod schemas, JSON Schema, and OpenAPI documents",
5
5
  "type": "module",
6
6
  "exports": {