schema-components 1.29.0 → 2.0.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 (90) hide show
  1. package/README.md +38 -16
  2. package/dist/core/adapter.d.mts +213 -3
  3. package/dist/core/adapter.mjs +1 -1
  4. package/dist/core/constraintHint.d.mts +15 -0
  5. package/dist/core/constraintHint.mjs +24 -0
  6. package/dist/core/constraints.d.mts +2 -2
  7. package/dist/core/constraints.mjs +1 -1
  8. package/dist/core/diagnostics.d.mts +1 -1
  9. package/dist/core/errors.d.mts +1 -1
  10. package/dist/core/fieldOrder.d.mts +1 -1
  11. package/dist/core/formats.d.mts +1 -1
  12. package/dist/core/idPath.d.mts +35 -5
  13. package/dist/core/idPath.mjs +79 -7
  14. package/dist/core/inferValue.d.mts +2 -0
  15. package/dist/core/inferValue.mjs +1 -0
  16. package/dist/core/limits.d.mts +1 -1
  17. package/dist/core/limits.mjs +5 -0
  18. package/dist/core/merge.d.mts +12 -1
  19. package/dist/core/merge.mjs +66 -3
  20. package/dist/core/normalise.d.mts +1 -1
  21. package/dist/core/normalise.mjs +1 -1
  22. package/dist/core/openapi30.mjs +1 -1
  23. package/dist/core/ref.d.mts +1 -1
  24. package/dist/core/refChain.d.mts +3 -4
  25. package/dist/core/refChain.mjs +2 -3
  26. package/dist/core/renderer.d.mts +199 -2
  27. package/dist/core/swagger2.d.mts +1 -1
  28. package/dist/core/swagger2.mjs +1 -1
  29. package/dist/core/typeInference.d.mts +3 -3
  30. package/dist/core/types.d.mts +1 -1
  31. package/dist/core/unionMatch.d.mts +1 -1
  32. package/dist/core/uri.d.mts +12 -4
  33. package/dist/core/uri.mjs +30 -4
  34. package/dist/core/walkBuilders.d.mts +18 -6
  35. package/dist/core/walkBuilders.mjs +3 -1
  36. package/dist/core/walker.d.mts +1 -1
  37. package/dist/core/walker.mjs +5 -0
  38. package/dist/{diagnostics-ByEzkjrA.d.mts → diagnostics-BTrm3O6J.d.mts} +1 -1
  39. package/dist/{errors-D8JndRwI.d.mts → errors-Dki7tji4.d.mts} +1 -1
  40. package/dist/html/a11y.d.mts +3 -7
  41. package/dist/html/a11y.mjs +1 -16
  42. package/dist/html/renderToHtml.d.mts +22 -9
  43. package/dist/html/renderToHtml.mjs +2 -1
  44. package/dist/html/renderToHtmlStream.d.mts +24 -11
  45. package/dist/html/renderToHtmlStream.mjs +2 -1
  46. package/dist/html/renderers.d.mts +2 -33
  47. package/dist/html/renderers.mjs +39 -91
  48. package/dist/html/streamRenderers.d.mts +3 -3
  49. package/dist/html/streamRenderers.mjs +13 -8
  50. package/dist/inferValue-PPXWJpbN.d.mts +77 -0
  51. package/dist/{limits-DswmqWuy.d.mts → limits-x4OiyJxh.d.mts} +5 -0
  52. package/dist/{normalise-Db1xaxgx.mjs → normalise-DB-Xtjmn.mjs} +43 -2
  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/ApiSecurity.mjs +21 -8
  58. package/dist/openapi/bundle.d.mts +31 -0
  59. package/dist/openapi/components.d.mts +41 -10
  60. package/dist/openapi/components.mjs +19 -13
  61. package/dist/openapi/parser.d.mts +13 -13
  62. package/dist/openapi/parser.mjs +12 -12
  63. package/dist/openapi/resolve.d.mts +38 -49
  64. package/dist/openapi/resolve.mjs +62 -56
  65. package/dist/react/SchemaComponent.d.mts +19 -95
  66. package/dist/react/SchemaComponent.mjs +12 -1
  67. package/dist/react/SchemaView.d.mts +11 -7
  68. package/dist/react/SchemaView.mjs +3 -1
  69. package/dist/react/a11y.d.mts +74 -7
  70. package/dist/react/a11y.mjs +67 -6
  71. package/dist/react/fieldPath.d.mts +16 -1
  72. package/dist/react/fieldPath.mjs +25 -1
  73. package/dist/react/fieldShell.d.mts +49 -0
  74. package/dist/react/fieldShell.mjs +37 -0
  75. package/dist/react/headless.d.mts +1 -1
  76. package/dist/react/headlessRenderers.d.mts +2 -2
  77. package/dist/react/headlessRenderers.mjs +123 -54
  78. package/dist/{ref-CPh8rKQ3.d.mts → ref-DdsbekXX.d.mts} +33 -1
  79. package/dist/themes/mantine.d.mts +36 -20
  80. package/dist/themes/mantine.mjs +179 -150
  81. package/dist/themes/mui.d.mts +47 -21
  82. package/dist/themes/mui.mjs +259 -222
  83. package/dist/themes/radix.d.mts +38 -23
  84. package/dist/themes/radix.mjs +208 -180
  85. package/dist/themes/shadcn.d.mts +6 -3
  86. package/dist/themes/shadcn.mjs +93 -93
  87. package/dist/{types-C2Ay1FEh.d.mts → types-BrYbjC7_.d.mts} +7 -0
  88. package/package.json +4 -1
  89. package/dist/adapter-DcWi4XXn.d.mts +0 -223
  90. package/dist/renderer-OaOz-n6-.d.mts +0 -185
@@ -1,21 +1,88 @@
1
- import { j as WalkedField } from "../types-C2Ay1FEh.mjs";
1
+ import { j as WalkedField } from "../types-BrYbjC7_.mjs";
2
+ import { AllConstraints } from "../core/renderer.mjs";
2
3
 
3
4
  //#region src/react/a11y.d.ts
4
5
  /**
5
6
  * Build the ARIA attribute bundle for a renderer.
6
7
  *
7
8
  * - `aria-required="true"` whenever the field is non-optional.
9
+ * - `aria-describedby=<hint-id>` whenever a constraint hint applies.
8
10
  * - `aria-label=<description>` when a non-empty description is supplied.
9
11
  *
10
12
  * Returns a plain `Record<string, string>` (rather than a typed
11
13
  * attribute interface) so callers can spread the result into any JSX
12
14
  * element type without per-element TypeScript widening.
13
15
  *
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()`.
16
+ * `inputId` and `constraints` together control whether
17
+ * `aria-describedby` is emitted. Pass `undefined` for `constraints`
18
+ * when the renderer never emits a constraint hint (e.g. boolean
19
+ * checkboxes); the helper then skips the attribute entirely. When the
20
+ * caller does emit a hint via `buildHint(...)`, the `aria-describedby`
21
+ * id matches `hintIdFor(inputId)` so the wire-up holds end-to-end.
18
22
  */
19
- declare function buildAriaAttrs(tree: WalkedField, description?: unknown): Record<string, string>;
23
+ declare function buildAriaAttrs(tree: WalkedField, description?: unknown, inputId?: string, constraints?: AllConstraints): Record<string, string>;
24
+ /**
25
+ * Description for a constraint hint emitted alongside an input.
26
+ *
27
+ * Returned by {@link constraintHint} when the field carries one or
28
+ * more constraint keywords the user should be told about (min/max,
29
+ * pattern, item count, …). Theme adapters render this as a `<small>`
30
+ * element wired to the input via `aria-describedby`.
31
+ */
32
+ interface Hint {
33
+ /** DOM id matching {@link hintIdFor}(inputId) on the host input. */
34
+ readonly id: string;
35
+ /** Human-readable hint text. */
36
+ readonly text: string;
37
+ }
38
+ /**
39
+ * Derive the constraint-hint descriptor for a field at `inputId`. Returns
40
+ * `undefined` when the field has no constraint worth announcing — callers
41
+ * skip rendering the `<small>` element entirely rather than emitting an
42
+ * empty node.
43
+ *
44
+ * Shares its text builder with `html/a11y.ts` so the two renderers
45
+ * always announce the same constraint copy for the same schema field.
46
+ */
47
+ declare function constraintHint(inputId: string, constraints: AllConstraints): Hint | undefined;
48
+ /**
49
+ * True when the supplied field is non-optional and therefore deserves
50
+ * a visual required indicator alongside its label.
51
+ *
52
+ * Exposed as a predicate rather than a JSX element so theme adapters
53
+ * can render the indicator in whatever element type matches their
54
+ * design language (`<span>`, `<sup>`, an icon component, …).
55
+ */
56
+ declare function isFieldRequired(tree: WalkedField): boolean;
57
+ /**
58
+ * Narrow `meta.description` (typed `unknown`) to a string value safe to
59
+ * pass into JSX `aria-label`. Returns `undefined` for non-string or
60
+ * empty-string descriptions so React drops the attribute rather than
61
+ * stringifying e.g. `{}` to `"[object Object]"`.
62
+ */
63
+ declare function ariaLabel(description: unknown): string | undefined;
64
+ /**
65
+ * Structured constraint-hint data for the React renderers.
66
+ *
67
+ * The HTML pipeline emits an inline `<small class="sc-hint">` element
68
+ * next to each input and references it through `aria-describedby`. The
69
+ * React pipeline mirrors that contract: the input takes the
70
+ * `ariaDescribedBy` id, the renderer emits a sibling `<small id={...}>`
71
+ * whose text is `hint`. Returns `undefined` when the field has no
72
+ * advertise-able constraints (`constraintHint` returns `undefined`) so
73
+ * callers can skip both the attribute and the element cleanly.
74
+ */
75
+ interface HintInfo {
76
+ readonly id: string;
77
+ readonly hint: string;
78
+ readonly ariaDescribedBy: string;
79
+ }
80
+ /**
81
+ * Build {@link HintInfo} for a field at `inputId` given its declared
82
+ * constraints. Returns `undefined` when no constraint message would be
83
+ * produced — the React renderers then skip emitting the hint element
84
+ * entirely so consumers don't see an empty `<small>`.
85
+ */
86
+ declare function buildHintInfo(inputId: string, constraints: AllConstraints): HintInfo | undefined;
20
87
  //#endregion
21
- export { buildAriaAttrs };
88
+ export { Hint, HintInfo, ariaLabel, buildAriaAttrs, buildHintInfo, constraintHint, isFieldRequired };
@@ -1,24 +1,85 @@
1
+ import { constraintHint as constraintHint$1 } from "../core/constraintHint.mjs";
2
+ import { hintIdFor } from "../core/idPath.mjs";
1
3
  //#region src/react/a11y.ts
2
4
  /**
3
5
  * Build the ARIA attribute bundle for a renderer.
4
6
  *
5
7
  * - `aria-required="true"` whenever the field is non-optional.
8
+ * - `aria-describedby=<hint-id>` whenever a constraint hint applies.
6
9
  * - `aria-label=<description>` when a non-empty description is supplied.
7
10
  *
8
11
  * Returns a plain `Record<string, string>` (rather than a typed
9
12
  * attribute interface) so callers can spread the result into any JSX
10
13
  * element type without per-element TypeScript widening.
11
14
  *
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()`.
15
+ * `inputId` and `constraints` together control whether
16
+ * `aria-describedby` is emitted. Pass `undefined` for `constraints`
17
+ * when the renderer never emits a constraint hint (e.g. boolean
18
+ * checkboxes); the helper then skips the attribute entirely. When the
19
+ * caller does emit a hint via `buildHint(...)`, the `aria-describedby`
20
+ * id matches `hintIdFor(inputId)` so the wire-up holds end-to-end.
16
21
  */
17
- function buildAriaAttrs(tree, description) {
22
+ function buildAriaAttrs(tree, description, inputId, constraints) {
18
23
  const attrs = {};
19
24
  if (tree.isOptional === false) attrs["aria-required"] = "true";
25
+ if (inputId !== void 0 && constraints !== void 0 && constraintHint$1(constraints) !== void 0) attrs["aria-describedby"] = hintIdFor(inputId);
20
26
  if (typeof description === "string" && description.length > 0) attrs["aria-label"] = description;
21
27
  return attrs;
22
28
  }
29
+ /**
30
+ * Derive the constraint-hint descriptor for a field at `inputId`. Returns
31
+ * `undefined` when the field has no constraint worth announcing — callers
32
+ * skip rendering the `<small>` element entirely rather than emitting an
33
+ * empty node.
34
+ *
35
+ * Shares its text builder with `html/a11y.ts` so the two renderers
36
+ * always announce the same constraint copy for the same schema field.
37
+ */
38
+ function constraintHint(inputId, constraints) {
39
+ const text = constraintHint$1(constraints);
40
+ if (text === void 0) return void 0;
41
+ return {
42
+ id: hintIdFor(inputId),
43
+ text
44
+ };
45
+ }
46
+ /**
47
+ * True when the supplied field is non-optional and therefore deserves
48
+ * a visual required indicator alongside its label.
49
+ *
50
+ * Exposed as a predicate rather than a JSX element so theme adapters
51
+ * can render the indicator in whatever element type matches their
52
+ * design language (`<span>`, `<sup>`, an icon component, …).
53
+ */
54
+ function isFieldRequired(tree) {
55
+ return tree.isOptional === false;
56
+ }
57
+ /**
58
+ * Narrow `meta.description` (typed `unknown`) to a string value safe to
59
+ * pass into JSX `aria-label`. Returns `undefined` for non-string or
60
+ * empty-string descriptions so React drops the attribute rather than
61
+ * stringifying e.g. `{}` to `"[object Object]"`.
62
+ */
63
+ function ariaLabel(description) {
64
+ if (typeof description !== "string") return void 0;
65
+ if (description.length === 0) return void 0;
66
+ return description;
67
+ }
68
+ /**
69
+ * Build {@link HintInfo} for a field at `inputId` given its declared
70
+ * constraints. Returns `undefined` when no constraint message would be
71
+ * produced — the React renderers then skip emitting the hint element
72
+ * entirely so consumers don't see an empty `<small>`.
73
+ */
74
+ function buildHintInfo(inputId, constraints) {
75
+ const hint = constraintHint$1(constraints);
76
+ if (hint === void 0) return void 0;
77
+ const id = hintIdFor(inputId);
78
+ return {
79
+ id,
80
+ hint,
81
+ ariaDescribedBy: id
82
+ };
83
+ }
23
84
  //#endregion
24
- export { buildAriaAttrs };
85
+ export { ariaLabel, buildAriaAttrs, buildHintInfo, constraintHint, isFieldRequired };
@@ -1,4 +1,4 @@
1
- import { j as WalkedField } from "../types-C2Ay1FEh.mjs";
1
+ import { j as WalkedField } from "../types-BrYbjC7_.mjs";
2
2
 
3
3
  //#region src/react/fieldPath.d.ts
4
4
  /**
@@ -9,11 +9,26 @@ declare function resolvePath(tree: WalkedField, path: string): WalkedField | und
9
9
  /**
10
10
  * Resolve a dot-separated path through a data value.
11
11
  * Supports array index notation: `field[0]`.
12
+ *
13
+ * Path segments naming a prototype-polluting property (`__proto__`,
14
+ * `constructor`, `prototype`) refuse to resolve and return `undefined`.
15
+ * Without the refusal, an attacker-supplied path would read
16
+ * `Object.prototype` (or similar) and surface fields injected into the
17
+ * runtime prototype chain as if they belonged to the user's data.
12
18
  */
13
19
  declare function resolveValue(root: unknown, path: string): unknown;
14
20
  /**
15
21
  * Set a value at a dot-separated path, producing a new root object.
16
22
  * Does not mutate the input — returns a shallow-updated copy at each level.
23
+ *
24
+ * Refuses paths whose segments name a prototype-polluting property
25
+ * (`__proto__`, `constructor`, `prototype`). Such a path could otherwise
26
+ * mutate `Object.prototype` (or similar) through the assignment, planting
27
+ * fields visible to every plain object in the runtime. The input `root`
28
+ * is returned unchanged so the caller's onChange handler treats the
29
+ * write as a no-op rather than propagating a poisoned state. This
30
+ * matches the silent-refusal semantics of `dereference` in `core/ref.ts`
31
+ * when a JSON Pointer segment names a prototype-polluting key.
17
32
  */
18
33
  declare function setNestedValue(root: unknown, path: string, leafValue: unknown): unknown;
19
34
  //#endregion
@@ -1,4 +1,5 @@
1
1
  import { isObject } from "../core/guards.mjs";
2
+ import { isPrototypePollutingKey } from "../core/uri.mjs";
2
3
  //#region src/react/fieldPath.ts
3
4
  /**
4
5
  * Resolve a dot-separated path through a WalkedField tree.
@@ -26,6 +27,12 @@ function resolvePath(tree, path) {
26
27
  /**
27
28
  * Resolve a dot-separated path through a data value.
28
29
  * Supports array index notation: `field[0]`.
30
+ *
31
+ * Path segments naming a prototype-polluting property (`__proto__`,
32
+ * `constructor`, `prototype`) refuse to resolve and return `undefined`.
33
+ * Without the refusal, an attacker-supplied path would read
34
+ * `Object.prototype` (or similar) and surface fields injected into the
35
+ * runtime prototype chain as if they belonged to the user's data.
29
36
  */
30
37
  function resolveValue(root, path) {
31
38
  if (path.length === 0) return root;
@@ -36,21 +43,38 @@ function resolveValue(root, path) {
36
43
  const bracketMatch = /^(.+)\[(\d+)\]$/.exec(part);
37
44
  if (bracketMatch?.[1] !== void 0 && bracketMatch[2] !== void 0) {
38
45
  const key = bracketMatch[1];
46
+ if (isPrototypePollutingKey(key)) return void 0;
39
47
  const index = Number(bracketMatch[2]);
40
48
  const arr = current[key];
41
49
  if (Array.isArray(arr)) current = arr[index];
42
50
  else return;
43
- } else current = current[part];
51
+ } else {
52
+ if (isPrototypePollutingKey(part)) return void 0;
53
+ current = current[part];
54
+ }
44
55
  }
45
56
  return current;
46
57
  }
47
58
  /**
48
59
  * Set a value at a dot-separated path, producing a new root object.
49
60
  * Does not mutate the input — returns a shallow-updated copy at each level.
61
+ *
62
+ * Refuses paths whose segments name a prototype-polluting property
63
+ * (`__proto__`, `constructor`, `prototype`). Such a path could otherwise
64
+ * mutate `Object.prototype` (or similar) through the assignment, planting
65
+ * fields visible to every plain object in the runtime. The input `root`
66
+ * is returned unchanged so the caller's onChange handler treats the
67
+ * write as a no-op rather than propagating a poisoned state. This
68
+ * matches the silent-refusal semantics of `dereference` in `core/ref.ts`
69
+ * when a JSON Pointer segment names a prototype-polluting key.
50
70
  */
51
71
  function setNestedValue(root, path, leafValue) {
52
72
  if (path.length === 0) return leafValue;
53
73
  const parts = path.split(".");
74
+ for (const part of parts) {
75
+ const bracketMatch = /^(.+)\[(\d+)\]$/.exec(part);
76
+ if (isPrototypePollutingKey(bracketMatch?.[1] !== void 0 && bracketMatch[2] !== void 0 ? bracketMatch[1] : part)) return root;
77
+ }
54
78
  const result = isObject(root) ? { ...root } : {};
55
79
  let current = result;
56
80
  for (let i = 0; i < parts.length; i++) {
@@ -0,0 +1,49 @@
1
+ import { RenderProps } from "../core/renderer.mjs";
2
+ import { ReactNode } from "react";
3
+
4
+ //#region src/react/fieldShell.d.ts
5
+ /**
6
+ * Render-time inputs to {@link FieldShell}. The shell does not depend
7
+ * on a theme; it consumes only the field metadata the walker has
8
+ * already produced.
9
+ */
10
+ interface FieldShellProps {
11
+ /** The walked field props passed to the theme's render function. */
12
+ readonly props: RenderProps;
13
+ /** Stable DOM id for the host input. */
14
+ readonly inputId: string;
15
+ /**
16
+ * Children-as-function. Receives the ARIA attribute bundle so the
17
+ * caller can spread the attributes onto whatever element it
18
+ * produces; the shell does not inject them because the spread
19
+ * point varies between libraries (`inputProps`, top-level, slot
20
+ * props, …).
21
+ */
22
+ readonly children: (ariaAttrs: Record<string, string>) => ReactNode;
23
+ /**
24
+ * Optional override for the label text. Defaults to
25
+ * `props.meta.description` when undefined.
26
+ */
27
+ readonly label?: string;
28
+ /**
29
+ * When true, suppress the wrapping `<label>` element. Useful for
30
+ * adapters whose host primitive (e.g. MUI's `TextField`) already
31
+ * renders its own label.
32
+ */
33
+ readonly hideLabel?: boolean;
34
+ }
35
+ /**
36
+ * Compose label, host primitive, and constraint hint around a render
37
+ * function supplied by the theme adapter. Returns plain JSX — no
38
+ * theme-specific element types — so the same shell works under
39
+ * shadcn, MUI, Mantine, Radix, or any custom theme.
40
+ */
41
+ declare function FieldShell({
42
+ props,
43
+ inputId,
44
+ children,
45
+ label,
46
+ hideLabel
47
+ }: FieldShellProps): ReactNode;
48
+ //#endregion
49
+ export { FieldShell, FieldShellProps };
@@ -0,0 +1,37 @@
1
+ import { buildAriaAttrs, constraintHint, isFieldRequired } from "./a11y.mjs";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ //#region src/react/fieldShell.tsx
4
+ /**
5
+ * Compose label, host primitive, and constraint hint around a render
6
+ * function supplied by the theme adapter. Returns plain JSX — no
7
+ * theme-specific element types — so the same shell works under
8
+ * shadcn, MUI, Mantine, Radix, or any custom theme.
9
+ */
10
+ function FieldShell({ props, inputId, children, label, hideLabel }) {
11
+ const description = typeof label === "string" ? label : typeof props.meta.description === "string" ? props.meta.description : void 0;
12
+ const required = isFieldRequired(props.tree);
13
+ const hint = constraintHint(inputId, props.constraints);
14
+ const ariaAttrs = buildAriaAttrs(props.tree, description, inputId, props.constraints);
15
+ return /* @__PURE__ */ jsxs("div", {
16
+ className: "sc-field",
17
+ children: [
18
+ hideLabel !== true && description !== void 0 && /* @__PURE__ */ jsxs("label", {
19
+ htmlFor: inputId,
20
+ children: [description, required && /* @__PURE__ */ jsxs("span", {
21
+ "aria-hidden": "true",
22
+ className: "sc-required",
23
+ style: { color: "#dc2626" },
24
+ children: [" ", "*"]
25
+ })]
26
+ }),
27
+ children(ariaAttrs),
28
+ hint !== void 0 && /* @__PURE__ */ jsx("small", {
29
+ className: "sc-hint",
30
+ id: hint.id,
31
+ children: hint.text
32
+ })
33
+ ]
34
+ });
35
+ }
36
+ //#endregion
37
+ export { FieldShell };
@@ -1,4 +1,4 @@
1
- import { r as ComponentResolver } from "../renderer-OaOz-n6-.mjs";
1
+ import { ComponentResolver } from "../core/renderer.mjs";
2
2
 
3
3
  //#region src/react/headless.d.ts
4
4
  /**
@@ -1,5 +1,5 @@
1
- import { j as WalkedField } from "../types-C2Ay1FEh.mjs";
2
- import { l as RenderProps } from "../renderer-OaOz-n6-.mjs";
1
+ import { j as WalkedField } from "../types-BrYbjC7_.mjs";
2
+ import { RenderProps } from "../core/renderer.mjs";
3
3
  import { ReactNode } from "react";
4
4
 
5
5
  //#region src/react/headlessRenderers.d.ts