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,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
|
|
@@ -25,54 +31,35 @@ function toReactNode(value) {
|
|
|
25
31
|
}
|
|
26
32
|
function formatDateTime(value) {
|
|
27
33
|
if (typeof value !== "string" || value.length === 0) return void 0;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
return date.toLocaleString();
|
|
32
|
-
} catch {
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
34
|
+
const date = new Date(value);
|
|
35
|
+
if (isNaN(date.getTime())) return void 0;
|
|
36
|
+
return date.toLocaleString();
|
|
35
37
|
}
|
|
36
38
|
function formatDate(value) {
|
|
37
39
|
if (typeof value !== "string" || value.length === 0) return void 0;
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
return date.toLocaleDateString();
|
|
42
|
-
} catch {
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
40
|
+
const date = new Date(value);
|
|
41
|
+
if (isNaN(date.getTime())) return void 0;
|
|
42
|
+
return date.toLocaleDateString();
|
|
45
43
|
}
|
|
46
44
|
function formatTime(value) {
|
|
47
45
|
if (typeof value !== "string" || value.length === 0) return void 0;
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
return date.toLocaleTimeString();
|
|
52
|
-
} catch {
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
function dateInputType(format) {
|
|
57
|
-
if (format === "date") return "date";
|
|
58
|
-
if (format === "time") return "time";
|
|
59
|
-
if (format === "date-time" || format === "datetime") return "datetime-local";
|
|
46
|
+
const date = new Date(value);
|
|
47
|
+
if (isNaN(date.getTime())) return void 0;
|
|
48
|
+
return date.toLocaleTimeString();
|
|
60
49
|
}
|
|
61
50
|
/**
|
|
62
51
|
* Build a stable, unique input ID from the path.
|
|
63
|
-
* Used for `htmlFor`/`id` association between labels and inputs.
|
|
64
52
|
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
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.
|
|
69
58
|
*
|
|
70
|
-
*
|
|
71
|
-
* id valid as a CSS selector and predictable in test queries.
|
|
59
|
+
* Throws on an empty path; see `fieldDomId` for the rationale.
|
|
72
60
|
*/
|
|
73
61
|
function inputId(path) {
|
|
74
|
-
|
|
75
|
-
return `sc-${path.replace(/[.[\]]+/g, "-").replace(/-+$/g, "")}`;
|
|
62
|
+
return fieldDomId(path);
|
|
76
63
|
}
|
|
77
64
|
function renderString(props) {
|
|
78
65
|
const id = inputId(props.path);
|
|
@@ -119,8 +106,7 @@ function renderString(props) {
|
|
|
119
106
|
}
|
|
120
107
|
const strValue = typeof props.value === "string" ? props.value : "";
|
|
121
108
|
const dateType = dateInputType(props.constraints.format);
|
|
122
|
-
const ariaAttrs =
|
|
123
|
-
if (props.tree.isOptional === false) ariaAttrs["aria-required"] = "true";
|
|
109
|
+
const ariaAttrs = buildAriaAttrs(props.tree);
|
|
124
110
|
if (dateType !== void 0) return /* @__PURE__ */ jsx("input", {
|
|
125
111
|
id,
|
|
126
112
|
type: dateType,
|
|
@@ -143,7 +129,7 @@ function renderString(props) {
|
|
|
143
129
|
value: "",
|
|
144
130
|
children: ["Select", "…"]
|
|
145
131
|
}), enumValues.map((v) => {
|
|
146
|
-
const display =
|
|
132
|
+
const display = displayJsonValue(v);
|
|
147
133
|
return /* @__PURE__ */ jsx("option", {
|
|
148
134
|
value: display,
|
|
149
135
|
children: display
|
|
@@ -179,8 +165,7 @@ function renderNumber(props) {
|
|
|
179
165
|
});
|
|
180
166
|
}
|
|
181
167
|
const numValue = typeof props.value === "number" ? props.value : "";
|
|
182
|
-
const ariaAttrs =
|
|
183
|
-
if (props.tree.isOptional === false) ariaAttrs["aria-required"] = "true";
|
|
168
|
+
const ariaAttrs = buildAriaAttrs(props.tree);
|
|
184
169
|
return /* @__PURE__ */ jsx("input", {
|
|
185
170
|
id,
|
|
186
171
|
type: "number",
|
|
@@ -207,9 +192,7 @@ function renderBoolean(props) {
|
|
|
207
192
|
children: props.value ? "Yes" : "No"
|
|
208
193
|
});
|
|
209
194
|
}
|
|
210
|
-
const ariaAttrs =
|
|
211
|
-
if (props.tree.isOptional === false) ariaAttrs["aria-required"] = "true";
|
|
212
|
-
if (typeof props.meta.description === "string") ariaAttrs["aria-label"] = props.meta.description;
|
|
195
|
+
const ariaAttrs = buildAriaAttrs(props.tree, props.meta.description);
|
|
213
196
|
return /* @__PURE__ */ jsx("input", {
|
|
214
197
|
id,
|
|
215
198
|
type: "checkbox",
|
|
@@ -228,8 +211,7 @@ function renderEnum(props) {
|
|
|
228
211
|
"aria-readonly": "true",
|
|
229
212
|
children: enumValue || "—"
|
|
230
213
|
});
|
|
231
|
-
const ariaAttrs =
|
|
232
|
-
if (props.tree.isOptional === false) ariaAttrs["aria-required"] = "true";
|
|
214
|
+
const ariaAttrs = buildAriaAttrs(props.tree);
|
|
233
215
|
const enumValues = props.tree.type === "enum" ? props.tree.enumValues : [];
|
|
234
216
|
return /* @__PURE__ */ jsxs("select", {
|
|
235
217
|
id,
|
|
@@ -242,7 +224,7 @@ function renderEnum(props) {
|
|
|
242
224
|
value: "",
|
|
243
225
|
children: ["Select", "…"]
|
|
244
226
|
}), enumValues.map((v) => {
|
|
245
|
-
const display =
|
|
227
|
+
const display = displayJsonValue(v);
|
|
246
228
|
return /* @__PURE__ */ jsx("option", {
|
|
247
229
|
value: display,
|
|
248
230
|
children: display
|
|
@@ -327,7 +309,7 @@ function renderRecord(props) {
|
|
|
327
309
|
});
|
|
328
310
|
return /* @__PURE__ */ jsx("div", {
|
|
329
311
|
role: "group",
|
|
330
|
-
"aria-label": props.meta.description
|
|
312
|
+
"aria-label": typeof props.meta.description === "string" ? props.meta.description : void 0,
|
|
331
313
|
children: entries.map(([key, value]) => {
|
|
332
314
|
return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("label", {
|
|
333
315
|
htmlFor: inputId(`${props.path}.${key}`),
|
|
@@ -427,32 +409,16 @@ function renderUnion(props) {
|
|
|
427
409
|
return /* @__PURE__ */ jsx("span", { children: "—" });
|
|
428
410
|
}
|
|
429
411
|
function renderDiscriminatedUnion(props) {
|
|
430
|
-
|
|
431
|
-
const discriminator = props.tree.type === "discriminatedUnion" ? props.tree.discriminator : void 0;
|
|
432
|
-
if (options === void 0 || options.length === 0) {
|
|
412
|
+
if (props.tree.type !== "discriminatedUnion") {
|
|
433
413
|
if (props.value === void 0 || props.value === null) return /* @__PURE__ */ jsx("span", { children: "—" });
|
|
434
414
|
return /* @__PURE__ */ jsx("span", { children: JSON.stringify(props.value) });
|
|
435
415
|
}
|
|
436
|
-
const
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
if (opt.type === "object") {
|
|
441
|
-
const discriminatorField = opt.fields[discKey];
|
|
442
|
-
if (discriminatorField?.type === "literal") {
|
|
443
|
-
const constVal = discriminatorField.literalValues[0];
|
|
444
|
-
if (typeof constVal === "string") return constVal;
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
return typeof opt.meta.title === "string" ? opt.meta.title : opt.type;
|
|
448
|
-
});
|
|
449
|
-
let activeIndex = 0;
|
|
450
|
-
if (currentDiscriminatorValue !== void 0) {
|
|
451
|
-
const found = optionLabels.indexOf(currentDiscriminatorValue);
|
|
452
|
-
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) });
|
|
453
420
|
}
|
|
454
|
-
const activeOption = options
|
|
455
|
-
const panelId = inputId(props.path);
|
|
421
|
+
const { optionLabels, activeIndex, activeOption } = resolveDiscriminatedActive(options, discKey, isObject(props.value) ? props.value : void 0);
|
|
456
422
|
if (props.readOnly) {
|
|
457
423
|
if (activeOption !== void 0) return toReactNode(props.renderChild(activeOption, props.value, props.onChange));
|
|
458
424
|
return /* @__PURE__ */ jsx("span", { children: "—" });
|
|
@@ -461,7 +427,7 @@ function renderDiscriminatedUnion(props) {
|
|
|
461
427
|
options,
|
|
462
428
|
optionLabels,
|
|
463
429
|
activeIndex,
|
|
464
|
-
|
|
430
|
+
path: props.path,
|
|
465
431
|
discKey,
|
|
466
432
|
props
|
|
467
433
|
});
|
|
@@ -491,7 +457,8 @@ function discriminatedUnionValueForTab(optionLabels, discKey, newIndex) {
|
|
|
491
457
|
* "Automatic activation" means each arrow key both moves focus and
|
|
492
458
|
* activates the new tab in one step — selection and focus stay aligned.
|
|
493
459
|
*/
|
|
494
|
-
function DiscriminatedUnionTabs({ options, optionLabels, activeIndex,
|
|
460
|
+
function DiscriminatedUnionTabs({ options, optionLabels, activeIndex, path, discKey, props }) {
|
|
461
|
+
const panelId = panelIdFor(path);
|
|
495
462
|
const tabRefs = useRef([]);
|
|
496
463
|
const pendingFocusRef = useRef(false);
|
|
497
464
|
const handleTabChange = useCallback((newIndex) => {
|
|
@@ -542,9 +509,9 @@ function DiscriminatedUnionTabs({ options, optionLabels, activeIndex, panelId, d
|
|
|
542
509
|
},
|
|
543
510
|
type: "button",
|
|
544
511
|
role: "tab",
|
|
545
|
-
id:
|
|
512
|
+
id: tabIdFor(path, i),
|
|
546
513
|
"aria-selected": i === activeIndex ? "true" : void 0,
|
|
547
|
-
"aria-controls":
|
|
514
|
+
"aria-controls": panelId,
|
|
548
515
|
tabIndex: i === activeIndex ? 0 : -1,
|
|
549
516
|
onClick: () => {
|
|
550
517
|
handleTabChange(i);
|
|
@@ -561,8 +528,8 @@ function DiscriminatedUnionTabs({ options, optionLabels, activeIndex, panelId, d
|
|
|
561
528
|
}, String(i)))
|
|
562
529
|
}), /* @__PURE__ */ jsx("div", {
|
|
563
530
|
role: "tabpanel",
|
|
564
|
-
id:
|
|
565
|
-
"aria-labelledby":
|
|
531
|
+
id: panelId,
|
|
532
|
+
"aria-labelledby": tabIdFor(path, activeIndex),
|
|
566
533
|
children: activeOption !== void 0 && toReactNode(props.renderChild(activeOption, props.value, props.onChange))
|
|
567
534
|
})] });
|
|
568
535
|
}
|
|
@@ -574,9 +541,6 @@ function renderFile(props) {
|
|
|
574
541
|
"aria-readonly": "true",
|
|
575
542
|
children: "File field"
|
|
576
543
|
});
|
|
577
|
-
const ariaAttrs = {};
|
|
578
|
-
if (props.tree.isOptional === false) ariaAttrs["aria-required"] = "true";
|
|
579
|
-
if (typeof props.meta.description === "string") ariaAttrs["aria-label"] = props.meta.description;
|
|
580
544
|
return /* @__PURE__ */ jsx("input", {
|
|
581
545
|
id,
|
|
582
546
|
type: "file",
|
|
@@ -585,17 +549,9 @@ function renderFile(props) {
|
|
|
585
549
|
const file = e.target.files?.[0];
|
|
586
550
|
if (file !== void 0) props.onChange(file);
|
|
587
551
|
},
|
|
588
|
-
...
|
|
552
|
+
...buildAriaAttrs(props.tree, props.meta.description)
|
|
589
553
|
});
|
|
590
554
|
}
|
|
591
|
-
function renderRecursive(props) {
|
|
592
|
-
const refTarget = props.tree.type === "recursive" ? props.tree.refTarget : "";
|
|
593
|
-
return /* @__PURE__ */ jsx("fieldset", { children: /* @__PURE__ */ jsxs("em", { children: [
|
|
594
|
-
"↻ ",
|
|
595
|
-
typeof props.meta.description === "string" ? props.meta.description : refTarget,
|
|
596
|
-
" (recursive)"
|
|
597
|
-
] }) });
|
|
598
|
-
}
|
|
599
555
|
/**
|
|
600
556
|
* Render a literal field — `z.literal("a")` or `{ const: 5 }`.
|
|
601
557
|
*
|
|
@@ -615,7 +571,7 @@ function renderLiteral(props) {
|
|
|
615
571
|
return /* @__PURE__ */ jsx("span", {
|
|
616
572
|
id,
|
|
617
573
|
"aria-readonly": "true",
|
|
618
|
-
children: values.map((v) =>
|
|
574
|
+
children: values.map((v) => displayJsonValue(v)).join(", ")
|
|
619
575
|
});
|
|
620
576
|
}
|
|
621
577
|
/**
|
|
@@ -644,7 +600,7 @@ function renderNever(props) {
|
|
|
644
600
|
return /* @__PURE__ */ jsx("span", {
|
|
645
601
|
id: inputId(props.path),
|
|
646
602
|
"aria-readonly": "true",
|
|
647
|
-
className:
|
|
603
|
+
className: SC_CLASSES.never,
|
|
648
604
|
children: /* @__PURE__ */ jsx("em", { children: "never matches" })
|
|
649
605
|
});
|
|
650
606
|
}
|
|
@@ -698,10 +654,10 @@ function renderConditional(props) {
|
|
|
698
654
|
if (props.tree.type !== "conditional") return null;
|
|
699
655
|
const { ifClause, thenClause, elseClause } = props.tree;
|
|
700
656
|
return /* @__PURE__ */ jsxs("fieldset", {
|
|
701
|
-
className:
|
|
657
|
+
className: SC_CLASSES.conditional,
|
|
702
658
|
children: [
|
|
703
659
|
/* @__PURE__ */ jsxs("div", {
|
|
704
|
-
className:
|
|
660
|
+
className: SC_CLASSES.conditionalIf,
|
|
705
661
|
children: [
|
|
706
662
|
/* @__PURE__ */ jsx("strong", { children: "if:" }),
|
|
707
663
|
" ",
|
|
@@ -709,7 +665,7 @@ function renderConditional(props) {
|
|
|
709
665
|
]
|
|
710
666
|
}),
|
|
711
667
|
thenClause !== void 0 && /* @__PURE__ */ jsxs("div", {
|
|
712
|
-
className:
|
|
668
|
+
className: SC_CLASSES.conditionalThen,
|
|
713
669
|
children: [
|
|
714
670
|
/* @__PURE__ */ jsx("strong", { children: "then:" }),
|
|
715
671
|
" ",
|
|
@@ -717,7 +673,7 @@ function renderConditional(props) {
|
|
|
717
673
|
]
|
|
718
674
|
}),
|
|
719
675
|
elseClause !== void 0 && /* @__PURE__ */ jsxs("div", {
|
|
720
|
-
className:
|
|
676
|
+
className: SC_CLASSES.conditionalElse,
|
|
721
677
|
children: [
|
|
722
678
|
/* @__PURE__ */ jsx("strong", { children: "else:" }),
|
|
723
679
|
" ",
|
|
@@ -737,7 +693,7 @@ function renderConditional(props) {
|
|
|
737
693
|
function renderNegation(props) {
|
|
738
694
|
if (props.tree.type !== "negation") return null;
|
|
739
695
|
return /* @__PURE__ */ jsxs("fieldset", {
|
|
740
|
-
className:
|
|
696
|
+
className: SC_CLASSES.negation,
|
|
741
697
|
children: [
|
|
742
698
|
/* @__PURE__ */ jsx("strong", { children: "Must NOT match:" }),
|
|
743
699
|
" ",
|
|
@@ -769,12 +725,5 @@ function renderUnknown(props) {
|
|
|
769
725
|
}
|
|
770
726
|
});
|
|
771
727
|
}
|
|
772
|
-
function matchUnionOption(options, value) {
|
|
773
|
-
if (typeof value === "string") return options.find((o) => o.type === "string" || o.type === "enum");
|
|
774
|
-
if (typeof value === "number") return options.find((o) => o.type === "number");
|
|
775
|
-
if (typeof value === "boolean") return options.find((o) => o.type === "boolean");
|
|
776
|
-
if (Array.isArray(value)) return options.find((o) => o.type === "array");
|
|
777
|
-
if (typeof value === "object" && value !== null) return options.find((o) => o.type === "object");
|
|
778
|
-
}
|
|
779
728
|
//#endregion
|
|
780
|
-
export { defaultRecordValue, discriminatedUnionValueForTab, inputId, nextRecordKey, renameRecordKey, renderArray, renderBoolean, renderConditional, renderDiscriminatedUnion, renderEnum, renderFile, renderLiteral, renderNegation, renderNever, renderNull, renderNumber, renderObject, renderRecord,
|
|
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-
|
|
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 {
|
|
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 {
|
|
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
|
/**
|
|
@@ -140,7 +140,7 @@ interface HtmlResolver {
|
|
|
140
140
|
never?: HtmlRenderFunction;
|
|
141
141
|
unknown?: HtmlRenderFunction;
|
|
142
142
|
}
|
|
143
|
-
declare const RESOLVER_KEYS: readonly ["string", "number", "boolean", "null", "enum", "object", "array", "tuple", "record", "union", "discriminatedUnion", "conditional", "negation", "
|
|
143
|
+
declare const RESOLVER_KEYS: readonly ["string", "number", "boolean", "null", "enum", "object", "array", "tuple", "record", "union", "discriminatedUnion", "conditional", "negation", "literal", "file", "never", "unknown"];
|
|
144
144
|
type ResolverKey = (typeof RESOLVER_KEYS)[number];
|
|
145
145
|
/**
|
|
146
146
|
* Map a schema type to the resolver key that handles it.
|
package/dist/themes/mui.d.mts
CHANGED
package/dist/themes/radix.d.mts
CHANGED
package/dist/themes/shadcn.d.mts
CHANGED
package/dist/themes/shadcn.mjs
CHANGED
|
@@ -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 =
|
|
164
|
+
const display = displayJsonValue(v);
|
|
164
165
|
return /* @__PURE__ */ jsx("option", {
|
|
165
166
|
value: display,
|
|
166
167
|
children: display
|
|
@@ -59,7 +59,7 @@ type FieldOverride = Partial<SchemaMeta> & {
|
|
|
59
59
|
* All schema types the walker can produce.
|
|
60
60
|
* Used as the discriminant in the WalkedField tagged union.
|
|
61
61
|
*/
|
|
62
|
-
type SchemaType = "string" | "number" | "boolean" | "null" | "enum" | "literal" | "object" | "array" | "tuple" | "record" | "union" | "discriminatedUnion" | "conditional" | "negation" | "
|
|
62
|
+
type SchemaType = "string" | "number" | "boolean" | "null" | "enum" | "literal" | "object" | "array" | "tuple" | "record" | "union" | "discriminatedUnion" | "conditional" | "negation" | "file" | "never" | "unknown";
|
|
63
63
|
/** Constraints that apply to string schemas. */
|
|
64
64
|
interface StringConstraints {
|
|
65
65
|
minLength?: number;
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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";
|
|
@@ -243,12 +271,6 @@ interface FileField extends FieldBase {
|
|
|
243
271
|
type: "file";
|
|
244
272
|
constraints: FileConstraints;
|
|
245
273
|
}
|
|
246
|
-
interface RecursiveField extends FieldBase {
|
|
247
|
-
type: "recursive";
|
|
248
|
-
constraints: Record<string, never>;
|
|
249
|
-
/** The $ref string that would create the cycle (e.g. "#" or "#Node"). */
|
|
250
|
-
refTarget: string;
|
|
251
|
-
}
|
|
252
274
|
/** Schema position where `false` appears — the field cannot have any value. */
|
|
253
275
|
interface NeverField extends FieldBase {
|
|
254
276
|
type: "never";
|
|
@@ -262,7 +284,7 @@ interface UnknownField extends FieldBase {
|
|
|
262
284
|
* Tagged union of all schema field types produced by the walker.
|
|
263
285
|
* Use `field.type` to narrow to a specific variant.
|
|
264
286
|
*/
|
|
265
|
-
type WalkedField = StringField | NumberField | BooleanField | NullField | EnumField | LiteralField | ObjectField | ArrayField | TupleField | RecordField | UnionField | DiscriminatedUnionField | ConditionalField | NegationField |
|
|
287
|
+
type WalkedField = StringField | NumberField | BooleanField | NullField | EnumField | LiteralField | ObjectField | ArrayField | TupleField | RecordField | UnionField | DiscriminatedUnionField | ConditionalField | NegationField | NeverField | FileField | UnknownField;
|
|
266
288
|
declare function isStringField(field: WalkedField): field is StringField;
|
|
267
289
|
declare function isNumberField(field: WalkedField): field is NumberField;
|
|
268
290
|
declare function isBooleanField(field: WalkedField): field is BooleanField;
|
|
@@ -278,8 +300,7 @@ declare function isDiscriminatedUnionField(field: WalkedField): field is Discrim
|
|
|
278
300
|
declare function isConditionalField(field: WalkedField): field is ConditionalField;
|
|
279
301
|
declare function isNegationField(field: WalkedField): field is NegationField;
|
|
280
302
|
declare function isFileField(field: WalkedField): field is FileField;
|
|
281
|
-
declare function isRecursiveField(field: WalkedField): field is RecursiveField;
|
|
282
303
|
declare function isNeverField(field: WalkedField): field is NeverField;
|
|
283
304
|
declare function isUnknownField(field: WalkedField): field is UnknownField;
|
|
284
305
|
//#endregion
|
|
285
|
-
export {
|
|
306
|
+
export { UnknownField as A, isNeverField as B, RecordField as C, StringField as D, StringConstraints as E, isDiscriminatedUnionField as F, isStringField as G, isNumberField as H, isEnumField as I, isUnknownField as J, isTupleField as K, isFileField as L, isArrayField as M, isBooleanField as N, TupleField as O, isConditionalField as P, isLiteralField as R, ObjectField as S, SchemaType as T, isObjectField as U, isNullField as V, isRecordField as W, resolveEditability as Y, NeverField as _, DiscriminatedUnionField as a, NumberField as b, FieldBase as c, FieldOverrides as d, FileConstraints as f, NegationField as g, LiteralField as h, ConditionalField as i, WalkedField as j, UnionField as k, FieldConstraints as l, JsonObject as m, ArrayField as n, Editability as o, FileField as p, isUnionField as q, BooleanField as r, EnumField as s, ArrayConstraints as t, FieldOverride as u, NullField as v, SchemaMeta as w, ObjectConstraints as x, NumberConstraints as y, isNegationField as z };
|
|
@@ -56,7 +56,13 @@ interface OpenApiVersionInfo {
|
|
|
56
56
|
}
|
|
57
57
|
/**
|
|
58
58
|
* Detect the OpenAPI/Swagger version from a document.
|
|
59
|
-
*
|
|
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,10 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "schema-components",
|
|
3
|
-
"version": "1.
|
|
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
|
-
"main": "./dist/index.mjs",
|
|
7
|
-
"types": "./dist/index.d.mts",
|
|
8
6
|
"exports": {
|
|
9
7
|
"./styles.css": "./dist/html/styles.css",
|
|
10
8
|
"./core/*": {
|