schema-components 1.28.2 → 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 (105) hide show
  1. package/README.md +38 -16
  2. package/dist/core/adapter.d.mts +213 -3
  3. package/dist/core/adapter.mjs +21 -2
  4. package/dist/core/constraintHint.d.mts +15 -0
  5. package/dist/core/constraintHint.mjs +24 -0
  6. package/dist/core/constraints.d.mts +34 -2
  7. package/dist/core/constraints.mjs +33 -1
  8. package/dist/core/cssClasses.d.mts +1 -0
  9. package/dist/core/diagnostics.d.mts +1 -1
  10. package/dist/core/errors.d.mts +1 -1
  11. package/dist/core/errors.mjs +22 -12
  12. package/dist/core/fieldOrder.d.mts +1 -1
  13. package/dist/core/formats.d.mts +7 -1
  14. package/dist/core/formats.mjs +6 -0
  15. package/dist/core/idPath.d.mts +35 -5
  16. package/dist/core/idPath.mjs +79 -7
  17. package/dist/core/inferValue.d.mts +2 -0
  18. package/dist/core/inferValue.mjs +1 -0
  19. package/dist/core/limits.d.mts +1 -1
  20. package/dist/core/limits.mjs +6 -0
  21. package/dist/core/merge.d.mts +22 -1
  22. package/dist/core/merge.mjs +66 -3
  23. package/dist/core/normalise.d.mts +17 -2
  24. package/dist/core/normalise.mjs +1 -1
  25. package/dist/core/openapi30.mjs +1 -1
  26. package/dist/core/openapiConstants.d.mts +1 -0
  27. package/dist/core/ref.d.mts +1 -1
  28. package/dist/core/refChain.d.mts +3 -4
  29. package/dist/core/refChain.mjs +2 -3
  30. package/dist/core/renderer.d.mts +199 -2
  31. package/dist/core/renderer.mjs +5 -0
  32. package/dist/core/swagger2.d.mts +1 -1
  33. package/dist/core/swagger2.mjs +1 -1
  34. package/dist/core/typeInference.d.mts +3 -3
  35. package/dist/core/types.d.mts +1 -1
  36. package/dist/core/types.mjs +17 -0
  37. package/dist/core/unionMatch.d.mts +1 -1
  38. package/dist/core/uri.d.mts +12 -4
  39. package/dist/core/uri.mjs +30 -4
  40. package/dist/core/version.d.mts +1 -1
  41. package/dist/core/walkBuilders.d.mts +63 -6
  42. package/dist/core/walkBuilders.mjs +33 -1
  43. package/dist/core/walker.d.mts +14 -1
  44. package/dist/core/walker.mjs +18 -0
  45. package/dist/{diagnostics-Cbwak-ZX.d.mts → diagnostics-BTrm3O6J.d.mts} +9 -1
  46. package/dist/{errors-DQSIK4n1.d.mts → errors-Dki7tji4.d.mts} +23 -13
  47. package/dist/html/a11y.d.mts +3 -7
  48. package/dist/html/a11y.mjs +1 -16
  49. package/dist/html/html.d.mts +11 -0
  50. package/dist/html/html.mjs +11 -0
  51. package/dist/html/renderToHtml.d.mts +45 -12
  52. package/dist/html/renderToHtml.mjs +20 -4
  53. package/dist/html/renderToHtmlStream.d.mts +63 -18
  54. package/dist/html/renderToHtmlStream.mjs +34 -8
  55. package/dist/html/renderers.d.mts +6 -31
  56. package/dist/html/renderers.mjs +45 -91
  57. package/dist/html/streamRenderers.d.mts +31 -3
  58. package/dist/html/streamRenderers.mjs +41 -8
  59. package/dist/inferValue-PPXWJpbN.d.mts +77 -0
  60. package/dist/{limits-DJhgx5Ay.d.mts → limits-x4OiyJxh.d.mts} +6 -0
  61. package/dist/{normalise-Db1xaxgx.mjs → normalise-DB-Xtjmn.mjs} +43 -2
  62. package/dist/openapi/ApiCallbacks.d.mts +13 -1
  63. package/dist/openapi/ApiCallbacks.mjs +7 -0
  64. package/dist/openapi/ApiLinks.d.mts +13 -1
  65. package/dist/openapi/ApiLinks.mjs +7 -0
  66. package/dist/openapi/ApiResponseHeaders.d.mts +13 -1
  67. package/dist/openapi/ApiResponseHeaders.mjs +7 -0
  68. package/dist/openapi/ApiSecurity.d.mts +14 -1
  69. package/dist/openapi/ApiSecurity.mjs +29 -8
  70. package/dist/openapi/bundle.d.mts +31 -0
  71. package/dist/openapi/components.d.mts +135 -20
  72. package/dist/openapi/components.mjs +90 -15
  73. package/dist/openapi/parser.d.mts +140 -13
  74. package/dist/openapi/parser.mjs +84 -12
  75. package/dist/openapi/resolve.d.mts +42 -47
  76. package/dist/openapi/resolve.mjs +62 -56
  77. package/dist/react/SchemaComponent.d.mts +90 -88
  78. package/dist/react/SchemaComponent.mjs +74 -2
  79. package/dist/react/SchemaErrorBoundary.d.mts +18 -1
  80. package/dist/react/SchemaErrorBoundary.mjs +13 -1
  81. package/dist/react/SchemaView.d.mts +39 -11
  82. package/dist/react/SchemaView.mjs +23 -6
  83. package/dist/react/a11y.d.mts +74 -7
  84. package/dist/react/a11y.mjs +67 -6
  85. package/dist/react/fieldPath.d.mts +16 -1
  86. package/dist/react/fieldPath.mjs +25 -1
  87. package/dist/react/fieldShell.d.mts +49 -0
  88. package/dist/react/fieldShell.mjs +37 -0
  89. package/dist/react/headless.d.mts +1 -1
  90. package/dist/react/headlessRenderers.d.mts +13 -2
  91. package/dist/react/headlessRenderers.mjs +134 -54
  92. package/dist/{ref-TdeMfaV_.d.mts → ref-DdsbekXX.d.mts} +33 -1
  93. package/dist/themes/mantine.d.mts +54 -12
  94. package/dist/themes/mantine.mjs +195 -140
  95. package/dist/themes/mui.d.mts +64 -11
  96. package/dist/themes/mui.mjs +277 -213
  97. package/dist/themes/radix.d.mts +67 -15
  98. package/dist/themes/radix.mjs +235 -170
  99. package/dist/themes/shadcn.d.mts +25 -1
  100. package/dist/themes/shadcn.mjs +112 -91
  101. package/dist/{types-BTB73MB8.d.mts → types-BrYbjC7_.d.mts} +30 -0
  102. package/dist/{version-ZzL5R6cS.d.mts → version-DL8U5RuA.d.mts} +6 -0
  103. package/package.json +8 -1
  104. package/dist/adapter-DqlAnZ_w.d.mts +0 -172
  105. package/dist/renderer-Ul9taFYp.d.mts +0 -169
@@ -1,12 +1,21 @@
1
- import { w as SchemaMeta } from "../types-BTB73MB8.mjs";
2
- import { t as Diagnostic } from "../diagnostics-Cbwak-ZX.mjs";
3
- import { i as SchemaIoSide } from "../adapter-DqlAnZ_w.mjs";
4
- import { r as ComponentResolver } from "../renderer-Ul9taFYp.mjs";
1
+ import { w as SchemaMeta } from "../types-BrYbjC7_.mjs";
2
+ import { t as Diagnostic } from "../diagnostics-BTrm3O6J.mjs";
3
+ import { SchemaIoSide } from "../core/adapter.mjs";
4
+ import { ComponentResolver, WidgetMap } from "../core/renderer.mjs";
5
5
  import { RejectUnrepresentableZod } from "../core/typeInference.mjs";
6
- import { InferredValue, WidgetMap } from "./SchemaComponent.mjs";
6
+ import { a as InferredValue, t as InferFields } from "../inferValue-PPXWJpbN.mjs";
7
7
  import { ReactNode } from "react";
8
8
 
9
9
  //#region src/react/SchemaView.d.ts
10
+ /**
11
+ * Props accepted by {@link SchemaView}.
12
+ *
13
+ * Mirrors `SchemaComponentProps` for the read-only / RSC path — no
14
+ * `onChange`, no `validate`, and the theme is supplied via the
15
+ * `resolver` prop because Server Components cannot read React context.
16
+ *
17
+ * @group Components
18
+ */
10
19
  interface SchemaViewProps<T = unknown, Ref extends string | undefined = undefined, Mode extends SchemaIoSide = "output"> {
11
20
  /**
12
21
  * Zod schema, JSON Schema object, or OpenAPI document.
@@ -35,8 +44,12 @@ interface SchemaViewProps<T = unknown, Ref extends string | undefined = undefine
35
44
  * schemas where the value type cannot be statically inferred.
36
45
  */
37
46
  value?: InferredValue<T, Ref, undefined, Mode>;
38
- /** Per-field meta overrides. */
39
- fields?: Record<string, unknown>;
47
+ /**
48
+ * Per-field meta overrides — nested object mirroring schema shape.
49
+ * Typed against {@link InferFields} so a typed `schema` prop drives
50
+ * autocomplete on the override map, matching `<SchemaComponent>`.
51
+ */
52
+ fields?: InferFields<T, Ref>;
40
53
  /** Meta overrides applied to the root schema. */
41
54
  meta?: SchemaMeta;
42
55
  /** Convenience: sets description on the root. */
@@ -61,11 +74,26 @@ interface SchemaViewProps<T = unknown, Ref extends string | undefined = undefine
61
74
  idPrefix?: string;
62
75
  }
63
76
  /**
64
- * Server-safe schema renderer no context and no state. The only hook
65
- * called is `useId()`, which is RSC-safe.
77
+ * Read-only schema renderer that is safe to use inside a React Server
78
+ * Component.
79
+ *
80
+ * Uses no context, state, or effects — the only hook called is
81
+ * `useId()`, which is RSC-safe. Always renders read-only; pair with
82
+ * `SchemaComponent` (which requires `"use client"`) when an editable
83
+ * form is required. Because Server Components cannot read React
84
+ * context, the theme adapter is passed via the `resolver` prop rather
85
+ * than `SchemaProvider`.
86
+ *
87
+ * @group Components
88
+ * @example
89
+ * ```tsx
90
+ * import { SchemaView } from "schema-components/react/SchemaView";
66
91
  *
67
- * Always renders in read-only mode. For editable forms, use
68
- * `<SchemaComponent>` with `"use client"`.
92
+ * export default async function Page() {
93
+ * const user = await getUser();
94
+ * return <SchemaView schema={userSchema} value={user} />;
95
+ * }
96
+ * ```
69
97
  */
70
98
  declare function SchemaView<T = unknown, Ref extends string | undefined = undefined, Mode extends SchemaIoSide = "output">({
71
99
  schema: schemaInput,
@@ -1,3 +1,4 @@
1
+ import { toRecordOrUndefined } from "../core/guards.mjs";
1
2
  import "../core/limits.mjs";
2
3
  import { SchemaNormalisationError, SchemaRenderError } from "../core/errors.mjs";
3
4
  import { normaliseSchema } from "../core/adapter.mjs";
@@ -5,8 +6,8 @@ import { buildRenderProps, getRenderFunction, mergeResolvers } from "../core/ren
5
6
  import { walk } from "../core/walker.mjs";
6
7
  import { headlessResolver } from "./headless.mjs";
7
8
  import { joinPath, sanitisePrefix } from "./SchemaComponent.mjs";
8
- import { createElement, isValidElement, useId } from "react";
9
9
  import { jsx } from "react/jsx-runtime";
10
+ import { createElement, isValidElement, useId } from "react";
10
11
  //#region src/react/SchemaView.tsx
11
12
  /**
12
13
  * React Server Component for read-only schema rendering.
@@ -36,11 +37,26 @@ import { jsx } from "react/jsx-runtime";
36
37
  * is passed explicitly.
37
38
  */
38
39
  /**
39
- * Server-safe schema renderer no context and no state. The only hook
40
- * called is `useId()`, which is RSC-safe.
40
+ * Read-only schema renderer that is safe to use inside a React Server
41
+ * Component.
42
+ *
43
+ * Uses no context, state, or effects — the only hook called is
44
+ * `useId()`, which is RSC-safe. Always renders read-only; pair with
45
+ * `SchemaComponent` (which requires `"use client"`) when an editable
46
+ * form is required. Because Server Components cannot read React
47
+ * context, the theme adapter is passed via the `resolver` prop rather
48
+ * than `SchemaProvider`.
49
+ *
50
+ * @group Components
51
+ * @example
52
+ * ```tsx
53
+ * import { SchemaView } from "schema-components/react/SchemaView";
41
54
  *
42
- * Always renders in read-only mode. For editable forms, use
43
- * `<SchemaComponent>` with `"use client"`.
55
+ * export default async function Page() {
56
+ * const user = await getUser();
57
+ * return <SchemaView schema={userSchema} value={user} />;
58
+ * }
59
+ * ```
44
60
  */
45
61
  function SchemaView({ schema: schemaInput, ref: refInput, io, value, fields, meta: componentMeta, description, resolver, widgets, onDiagnostic, strict, idPrefix }) {
46
62
  const generatedId = useId();
@@ -69,10 +85,11 @@ function SchemaView({ schema: schemaInput, ref: refInput, io, value, fields, met
69
85
  if (err instanceof SchemaNormalisationError) throw err;
70
86
  throw new SchemaNormalisationError(err instanceof Error ? err.message : "Failed to normalise schema", schemaInput, "unknown");
71
87
  }
88
+ const fieldsRecord = toRecordOrUndefined(fields);
72
89
  const walkOptions = {
73
90
  componentMeta: mergedMeta,
74
91
  rootMeta,
75
- fieldOverrides: fields,
92
+ fieldOverrides: fieldsRecord,
76
93
  rootDocument,
77
94
  ...diagnostics !== void 0 ? { diagnostics } : {}
78
95
  };
@@ -1,21 +1,88 @@
1
- import { j as WalkedField } from "../types-BTB73MB8.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-BTB73MB8.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-Ul9taFYp.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-BTB73MB8.mjs";
2
- import { l as RenderProps } from "../renderer-Ul9taFYp.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
@@ -20,10 +20,15 @@ declare function toReactNode(value: unknown): ReactNode;
20
20
  * Throws on an empty path; see `fieldDomId` for the rationale.
21
21
  */
22
22
  declare function inputId(path: string): string;
23
+ /** Headless renderer for `StringField` — plain `<input>` / `<span>`. */
23
24
  declare function renderString(props: RenderProps): ReactNode;
25
+ /** Headless renderer for `NumberField` — plain `<input type="number">`. */
24
26
  declare function renderNumber(props: RenderProps): ReactNode;
27
+ /** Headless renderer for `BooleanField` — plain `<input type="checkbox">`. */
25
28
  declare function renderBoolean(props: RenderProps): ReactNode;
29
+ /** Headless renderer for `EnumField` — plain `<select>` listing each option. */
26
30
  declare function renderEnum(props: RenderProps): ReactNode;
31
+ /** Headless renderer for `ObjectField` — `<fieldset>` per object with one child per property. */
27
32
  declare function renderObject(props: RenderProps): ReactNode;
28
33
  /**
29
34
  * Compute the default value for a freshly added record entry based on the
@@ -42,9 +47,13 @@ declare function nextRecordKey(existing: readonly string[], base?: string): stri
42
47
  * or when newKey collides with an existing key.
43
48
  */
44
49
  declare function renameRecordKey(obj: Record<string, unknown>, oldKey: string, newKey: string): Record<string, unknown>;
50
+ /** Headless renderer for `RecordField` — editable key/value rows with add/remove controls. */
45
51
  declare function renderRecord(props: RenderProps): ReactNode;
52
+ /** Headless renderer for `ArrayField` — ordered list with add/remove controls. */
46
53
  declare function renderArray(props: RenderProps): ReactNode;
54
+ /** Headless renderer for plain `UnionField` — picks the matching option and renders it. */
47
55
  declare function renderUnion(props: RenderProps): ReactNode;
56
+ /** Headless renderer for `DiscriminatedUnionField` — tabbed UI driven by the discriminator. */
48
57
  declare function renderDiscriminatedUnion(props: RenderProps): ReactNode;
49
58
  /**
50
59
  * Pure helper: convert a tab index into the new value the discriminated
@@ -54,6 +63,7 @@ declare function renderDiscriminatedUnion(props: RenderProps): ReactNode;
54
63
  * without rendering the tabs component (which relies on React hooks).
55
64
  */
56
65
  declare function discriminatedUnionValueForTab(optionLabels: readonly string[], discKey: string, newIndex: number): Record<string, string> | undefined;
66
+ /** Headless renderer for `FileField` — plain `<input type="file">`. */
57
67
  declare function renderFile(props: RenderProps): ReactNode;
58
68
  /**
59
69
  * Render a literal field — `z.literal("a")` or `{ const: 5 }`.
@@ -106,6 +116,7 @@ declare function renderConditional(props: RenderProps): ReactNode;
106
116
  * beneath an explanatory preamble.
107
117
  */
108
118
  declare function renderNegation(props: RenderProps): ReactNode;
119
+ /** Headless renderer for `UnknownField` — JSON-encoded fallback for unconstrained values. */
109
120
  declare function renderUnknown(props: RenderProps): ReactNode;
110
121
  //#endregion
111
122
  export { defaultRecordValue, discriminatedUnionValueForTab, inputId, nextRecordKey, renameRecordKey, renderArray, renderBoolean, renderConditional, renderDiscriminatedUnion, renderEnum, renderFile, renderLiteral, renderNegation, renderNever, renderNull, renderNumber, renderObject, renderRecord, renderString, renderTuple, renderUnion, renderUnknown, toReactNode };