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
@@ -65,6 +65,7 @@ type AttrValue = string | number | boolean | undefined;
65
65
  * arbitrary `data-*` and `aria-*` keys are allowed via index signature.
66
66
  */
67
67
  type HtmlAttributes = Record<string, AttrValue>;
68
+ /** HTML5 void-element tag names — self-closing, must not carry children. */
68
69
  declare const VOID_ELEMENTS: Set<string>;
69
70
  /**
70
71
  * Build an HTML element node.
@@ -103,7 +104,17 @@ declare function raw(html: string): HtmlRaw;
103
104
  * @returns HTML string
104
105
  */
105
106
  declare function serialize(node: HtmlNode): string;
107
+ /**
108
+ * Serialise a single {@link HtmlElement} to a string, taking care of
109
+ * void-element self-closing, attribute serialisation, and recursive
110
+ * child rendering. Use {@link serialize} for arbitrary nodes.
111
+ */
106
112
  declare function serializeElement(el: HtmlElement): string;
113
+ /**
114
+ * Serialise an attribute map to the `key="value"` form used inside an
115
+ * opening tag. `false` / `undefined` values are omitted; `true` renders
116
+ * as a boolean attribute (just the name).
117
+ */
107
118
  declare function serializeAttributes(attrs: HtmlAttributes): string;
108
119
  /**
109
120
  * Serialise an HTML node to chunks, yielded at natural element boundaries.
@@ -1,4 +1,5 @@
1
1
  //#region src/html/html.ts
2
+ /** HTML5 void-element tag names — self-closing, must not carry children. */
2
3
  const VOID_ELEMENTS = new Set([
3
4
  "area",
4
5
  "base",
@@ -80,6 +81,11 @@ function serialize(node) {
80
81
  if (node.tag === "") return node.children.map((child) => serialize(child)).join("");
81
82
  return serializeElement(node);
82
83
  }
84
+ /**
85
+ * Serialise a single {@link HtmlElement} to a string, taking care of
86
+ * void-element self-closing, attribute serialisation, and recursive
87
+ * child rendering. Use {@link serialize} for arbitrary nodes.
88
+ */
83
89
  function serializeElement(el) {
84
90
  const attrs = serializeAttributes(el.attributes);
85
91
  if (VOID_ELEMENTS.has(el.tag)) return `<${el.tag}${attrs}>`;
@@ -87,6 +93,11 @@ function serializeElement(el) {
87
93
  const inner = el.children.map((child) => serialize(child)).join("");
88
94
  return `<${el.tag}${attrs}>${inner}</${el.tag}>`;
89
95
  }
96
+ /**
97
+ * Serialise an attribute map to the `key="value"` form used inside an
98
+ * opening tag. `false` / `undefined` values are omitted; `true` renders
99
+ * as a boolean attribute (just the name).
100
+ */
90
101
  function serializeAttributes(attrs) {
91
102
  const parts = [];
92
103
  for (const [key, value] of Object.entries(attrs)) {
@@ -1,21 +1,42 @@
1
- import { w as SchemaMeta } from "../types-BTB73MB8.mjs";
2
- import { o as HtmlResolver } from "../renderer-Ul9taFYp.mjs";
1
+ import { w as SchemaMeta } from "../types-BrYbjC7_.mjs";
2
+ import { SchemaIoSide } from "../core/adapter.mjs";
3
+ import { HtmlResolver } from "../core/renderer.mjs";
4
+ import { RejectUnrepresentableZod } from "../core/typeInference.mjs";
5
+ import { a as InferredValue, t as InferFields } from "../inferValue-PPXWJpbN.mjs";
3
6
 
4
7
  //#region src/html/renderToHtml.d.ts
5
8
  /**
6
- * Build the recursion-cap sentinel element. The label is interpolated
7
- * via `h()` + `serialize` so any HTML in `meta.description` (which is
9
+ * Build the recursion-cap sentinel element used when the renderer
10
+ * encounters circular schema references. The label is interpolated via
11
+ * `h()` + `serialize` so any HTML in `meta.description` (which is
8
12
  * schema-author content but can equally be sourced from user-supplied
9
13
  * JSON Schema input) is escaped — never interpolated into raw markup.
14
+ *
15
+ * @group HTML
10
16
  */
11
17
  declare function recursionSentinelHtml(label: string): string;
12
- interface RenderToHtmlOptions {
13
- /** The data value to render. */
14
- value?: unknown;
18
+ /**
19
+ * Options accepted by {@link renderToHtml}.
20
+ *
21
+ * The generic parameters mirror `<SchemaComponent>` so a typed
22
+ * `schema` argument drives typed `value`, `ref`, and `fields` options.
23
+ *
24
+ * @group HTML
25
+ */
26
+ interface RenderToHtmlOptions<T = unknown, Ref extends string | undefined = undefined, Mode extends SchemaIoSide = "output"> {
27
+ /**
28
+ * The data value to render. Typed against `InferredValue<T, Ref, undefined, Mode>`
29
+ * so a typed `schema` argument drives the rendered value's shape.
30
+ */
31
+ value?: InferredValue<T, Ref, undefined, Mode>;
15
32
  /** For OpenAPI: a ref string like "#/components/schemas/User". */
16
- ref?: string;
17
- /** Per-field meta overrides. */
18
- fields?: Record<string, unknown>;
33
+ ref?: Ref;
34
+ /**
35
+ * Per-field meta overrides — nested object mirroring schema shape.
36
+ * Typed against {@link InferFields} so a typed `schema` argument
37
+ * drives autocomplete on the override map.
38
+ */
39
+ fields?: InferFields<T, Ref>;
19
40
  /** Meta overrides applied to the root schema. */
20
41
  meta?: SchemaMeta;
21
42
  /** Force all fields read-only. */
@@ -28,12 +49,24 @@ interface RenderToHtmlOptions {
28
49
  resolver?: HtmlResolver;
29
50
  }
30
51
  /**
31
- * Render a schema to an HTML string.
52
+ * Render a schema to a semantic HTML string.
53
+ *
54
+ * Framework-agnostic alternative to the React rendering pipeline.
55
+ * Shares the same normalise → walk → render pipeline, but emits
56
+ * escaped HTML with `sc-` prefixed classes rather than ReactNodes.
57
+ * Pass `resolver` to plug in a custom HTML renderer.
32
58
  *
59
+ * @group HTML
33
60
  * @param schema - Zod schema, JSON Schema, or OpenAPI document
34
61
  * @param options - Value, overrides, and resolver options
35
62
  * @returns Semantic HTML string with `sc-` prefixed classes
63
+ * @example
64
+ * ```tsx
65
+ * import { renderToHtml } from "schema-components/html/renderToHtml";
66
+ *
67
+ * const html = renderToHtml(userSchema, { value: user, readOnly: true });
68
+ * ```
36
69
  */
37
- declare function renderToHtml(schema: unknown, options?: RenderToHtmlOptions): string;
70
+ declare function renderToHtml<T = unknown, Ref extends string | undefined = undefined, Mode extends SchemaIoSide = "output">(schema: RejectUnrepresentableZod<T>, options?: RenderToHtmlOptions<T, Ref, Mode>): string;
38
71
  //#endregion
39
72
  export { RenderToHtmlOptions, recursionSentinelHtml, renderToHtml };
@@ -1,3 +1,4 @@
1
+ import { toRecordOrUndefined } from "../core/guards.mjs";
1
2
  import "../core/limits.mjs";
2
3
  import { normaliseSchema } from "../core/adapter.mjs";
3
4
  import { getHtmlRenderFn, mergeHtmlResolvers } from "../core/renderer.mjs";
@@ -27,20 +28,35 @@ import { defaultHtmlResolver } from "./renderers.mjs";
27
28
  * });
28
29
  */
29
30
  /**
30
- * Build the recursion-cap sentinel element. The label is interpolated
31
- * via `h()` + `serialize` so any HTML in `meta.description` (which is
31
+ * Build the recursion-cap sentinel element used when the renderer
32
+ * encounters circular schema references. The label is interpolated via
33
+ * `h()` + `serialize` so any HTML in `meta.description` (which is
32
34
  * schema-author content but can equally be sourced from user-supplied
33
35
  * JSON Schema input) is escaped — never interpolated into raw markup.
36
+ *
37
+ * @group HTML
34
38
  */
35
39
  function recursionSentinelHtml(label) {
36
40
  return serialize(h("fieldset", { class: "sc-recursive" }, h("em", {}, `↻ ${label} (recursive)`)));
37
41
  }
38
42
  /**
39
- * Render a schema to an HTML string.
43
+ * Render a schema to a semantic HTML string.
44
+ *
45
+ * Framework-agnostic alternative to the React rendering pipeline.
46
+ * Shares the same normalise → walk → render pipeline, but emits
47
+ * escaped HTML with `sc-` prefixed classes rather than ReactNodes.
48
+ * Pass `resolver` to plug in a custom HTML renderer.
40
49
  *
50
+ * @group HTML
41
51
  * @param schema - Zod schema, JSON Schema, or OpenAPI document
42
52
  * @param options - Value, overrides, and resolver options
43
53
  * @returns Semantic HTML string with `sc-` prefixed classes
54
+ * @example
55
+ * ```tsx
56
+ * import { renderToHtml } from "schema-components/html/renderToHtml";
57
+ *
58
+ * const html = renderToHtml(userSchema, { value: user, readOnly: true });
59
+ * ```
44
60
  */
45
61
  function renderToHtml(schema, options = {}) {
46
62
  const mergedMeta = { ...options.meta };
@@ -51,7 +67,7 @@ function renderToHtml(schema, options = {}) {
51
67
  const tree = walk(jsonSchema, {
52
68
  componentMeta: mergedMeta,
53
69
  rootMeta,
54
- fieldOverrides: options.fields,
70
+ fieldOverrides: toRecordOrUndefined(options.fields),
55
71
  rootDocument
56
72
  });
57
73
  const resolver = options.resolver ?? defaultHtmlResolver;
@@ -1,14 +1,34 @@
1
- import { w as SchemaMeta } from "../types-BTB73MB8.mjs";
2
- import { o as HtmlResolver } from "../renderer-Ul9taFYp.mjs";
1
+ import { w as SchemaMeta } from "../types-BrYbjC7_.mjs";
2
+ import { SchemaIoSide } from "../core/adapter.mjs";
3
+ import { HtmlResolver } from "../core/renderer.mjs";
4
+ import { RejectUnrepresentableZod } from "../core/typeInference.mjs";
5
+ import { a as InferredValue, t as InferFields } from "../inferValue-PPXWJpbN.mjs";
3
6
 
4
7
  //#region src/html/renderToHtmlStream.d.ts
5
- interface StreamRenderOptions {
6
- /** The data value to render. */
7
- value?: unknown;
8
+ /**
9
+ * Options accepted by the streaming HTML renderers
10
+ * ({@link renderToHtmlChunks}, {@link renderToHtmlStream},
11
+ * {@link renderToHtmlReadable}).
12
+ *
13
+ * The generic parameters mirror `<SchemaComponent>` so a typed
14
+ * `schema` argument drives typed `value`, `ref`, and `fields` options.
15
+ *
16
+ * @group HTML
17
+ */
18
+ interface StreamRenderOptions<T = unknown, Ref extends string | undefined = undefined, Mode extends SchemaIoSide = "output"> {
19
+ /**
20
+ * The data value to render. Typed against `InferredValue<T, Ref, undefined, Mode>`
21
+ * so a typed `schema` argument drives the rendered value's shape.
22
+ */
23
+ value?: InferredValue<T, Ref, undefined, Mode>;
8
24
  /** For OpenAPI: a ref string like "#/components/schemas/User". */
9
- ref?: string;
10
- /** Per-field meta overrides. */
11
- fields?: Record<string, unknown>;
25
+ ref?: Ref;
26
+ /**
27
+ * Per-field meta overrides — nested object mirroring schema shape.
28
+ * Typed against {@link InferFields} so a typed `schema` argument
29
+ * drives autocomplete on the override map.
30
+ */
31
+ fields?: InferFields<T, Ref>;
12
32
  /** Meta overrides applied to the root schema. */
13
33
  meta?: SchemaMeta;
14
34
  /** Force all fields read-only. */
@@ -21,20 +41,45 @@ interface StreamRenderOptions {
21
41
  resolver?: HtmlResolver;
22
42
  }
23
43
  /**
24
- * Render a schema as an iterable of HTML string chunks.
25
- * Each chunk is a self-contained HTML fragment.
44
+ * Render a schema as a synchronous iterable of HTML string chunks. Each
45
+ * chunk is a self-contained HTML fragment, ready to write to a stream
46
+ * or concatenate into a single string. Use when the host can flush
47
+ * output incrementally but does not need cooperative scheduling.
48
+ *
49
+ * @group HTML
50
+ * @example
51
+ * ```tsx
52
+ * import { renderToHtmlChunks } from "schema-components/html/renderToHtmlStream";
53
+ *
54
+ * for (const chunk of renderToHtmlChunks(userSchema, { value: user })) {
55
+ * response.write(chunk);
56
+ * }
57
+ * ```
26
58
  */
27
- declare function renderToHtmlChunks(schema: unknown, options?: StreamRenderOptions): Iterable<string>;
59
+ declare function renderToHtmlChunks<T = unknown, Ref extends string | undefined = undefined, Mode extends SchemaIoSide = "output">(schema: RejectUnrepresentableZod<T>, options?: StreamRenderOptions<T, Ref, Mode>): Iterable<string>;
28
60
  /**
29
- * Render a schema as an async iterable of HTML string chunks.
30
- * Yields `undefined` between chunks to allow the event loop to process
31
- * other tasks (cooperative scheduling).
61
+ * Render a schema as an async iterable of HTML string chunks. Yields
62
+ * back to the event loop between chunks via a microtask so other tasks
63
+ * (queued I/O, timers) can run between fragments.
64
+ *
65
+ * @group HTML
32
66
  */
33
- declare function renderToHtmlStream(schema: unknown, options?: StreamRenderOptions): AsyncIterable<string>;
67
+ declare function renderToHtmlStream<T = unknown, Ref extends string | undefined = undefined, Mode extends SchemaIoSide = "output">(schema: RejectUnrepresentableZod<T>, options?: StreamRenderOptions<T, Ref, Mode>): AsyncIterable<string>;
34
68
  /**
35
- * Render a schema as a web `ReadableStream<string>`.
36
- * Uses the async rendering pipeline internally.
69
+ * Render a schema as a web `ReadableStream<string>` so it can be piped
70
+ * into a `Response` body. Pulls chunks lazily from the synchronous
71
+ * chunk iterator under the hood.
72
+ *
73
+ * @group HTML
74
+ * @example
75
+ * ```tsx
76
+ * import { renderToHtmlReadable } from "schema-components/html/renderToHtmlStream";
77
+ *
78
+ * return new Response(renderToHtmlReadable(userSchema, { value: user }), {
79
+ * headers: { "content-type": "text/html" },
80
+ * });
81
+ * ```
37
82
  */
38
- declare function renderToHtmlReadable(schema: unknown, options?: StreamRenderOptions): ReadableStream<string>;
83
+ declare function renderToHtmlReadable<T = unknown, Ref extends string | undefined = undefined, Mode extends SchemaIoSide = "output">(schema: RejectUnrepresentableZod<T>, options?: StreamRenderOptions<T, Ref, Mode>): ReadableStream<string>;
39
84
  //#endregion
40
85
  export { StreamRenderOptions, renderToHtmlChunks, renderToHtmlReadable, renderToHtmlStream };
@@ -1,3 +1,4 @@
1
+ import { toRecordOrUndefined } from "../core/guards.mjs";
1
2
  import { normaliseSchema } from "../core/adapter.mjs";
2
3
  import { mergeHtmlResolvers } from "../core/renderer.mjs";
3
4
  import { walk } from "../core/walker.mjs";
@@ -19,17 +20,31 @@ import { streamField } from "./streamRenderers.mjs";
19
20
  * - `renderToHtmlReadable(schema, options)` → web `ReadableStream<string>`
20
21
  */
21
22
  /**
22
- * Render a schema as an iterable of HTML string chunks.
23
- * Each chunk is a self-contained HTML fragment.
23
+ * Render a schema as a synchronous iterable of HTML string chunks. Each
24
+ * chunk is a self-contained HTML fragment, ready to write to a stream
25
+ * or concatenate into a single string. Use when the host can flush
26
+ * output incrementally but does not need cooperative scheduling.
27
+ *
28
+ * @group HTML
29
+ * @example
30
+ * ```tsx
31
+ * import { renderToHtmlChunks } from "schema-components/html/renderToHtmlStream";
32
+ *
33
+ * for (const chunk of renderToHtmlChunks(userSchema, { value: user })) {
34
+ * response.write(chunk);
35
+ * }
36
+ * ```
24
37
  */
25
38
  function renderToHtmlChunks(schema, options = {}) {
26
39
  const { tree, resolver } = prepareTree(schema, options);
27
40
  return streamField(tree, options.value ?? tree.defaultValue, mergeHtmlResolvers(resolver, defaultHtmlResolver), "", resolver);
28
41
  }
29
42
  /**
30
- * Render a schema as an async iterable of HTML string chunks.
31
- * Yields `undefined` between chunks to allow the event loop to process
32
- * other tasks (cooperative scheduling).
43
+ * Render a schema as an async iterable of HTML string chunks. Yields
44
+ * back to the event loop between chunks via a microtask so other tasks
45
+ * (queued I/O, timers) can run between fragments.
46
+ *
47
+ * @group HTML
33
48
  */
34
49
  async function* renderToHtmlStream(schema, options = {}) {
35
50
  const { tree, resolver } = prepareTree(schema, options);
@@ -41,8 +56,19 @@ async function* renderToHtmlStream(schema, options = {}) {
41
56
  }
42
57
  }
43
58
  /**
44
- * Render a schema as a web `ReadableStream<string>`.
45
- * Uses the async rendering pipeline internally.
59
+ * Render a schema as a web `ReadableStream<string>` so it can be piped
60
+ * into a `Response` body. Pulls chunks lazily from the synchronous
61
+ * chunk iterator under the hood.
62
+ *
63
+ * @group HTML
64
+ * @example
65
+ * ```tsx
66
+ * import { renderToHtmlReadable } from "schema-components/html/renderToHtmlStream";
67
+ *
68
+ * return new Response(renderToHtmlReadable(userSchema, { value: user }), {
69
+ * headers: { "content-type": "text/html" },
70
+ * });
71
+ * ```
46
72
  */
47
73
  function renderToHtmlReadable(schema, options = {}) {
48
74
  const { tree, resolver } = prepareTree(schema, options);
@@ -63,7 +89,7 @@ function prepareTree(schema, options) {
63
89
  tree: walk(jsonSchema, {
64
90
  componentMeta: mergedMeta,
65
91
  rootMeta,
66
- fieldOverrides: options.fields,
92
+ fieldOverrides: toRecordOrUndefined(options.fields),
67
93
  rootDocument
68
94
  }),
69
95
  resolver: options.resolver ?? defaultHtmlResolver
@@ -1,39 +1,14 @@
1
+ import { HtmlResolver } from "../core/renderer.mjs";
1
2
  import { dateInputType } from "../core/formats.mjs";
2
- import { o as HtmlResolver } from "../renderer-Ul9taFYp.mjs";
3
3
  import { matchUnionOption } from "../core/unionMatch.mjs";
4
4
 
5
5
  //#region src/html/renderers.d.ts
6
6
  /**
7
- * Thin wrapper over `fieldDomId` from `core/idPath.ts`. Every render
8
- * pipeline must derive ids from the same canonical normaliser so that
9
- * `aria-controls`, `aria-labelledby`, and `htmlFor` references resolve
10
- * consistently across the React, sync-HTML, and streaming-HTML outputs.
11
- *
12
- * The wrapper tolerates an empty path here (returning `sc-`) so that
13
- * a leaf renderer at the schema root — `renderToHtml(z.string())` — has
14
- * a usable id without throwing. Container renderers always thread a
15
- * non-empty path through `renderChild`, so the empty-id fallback can
16
- * never produce sibling collisions inside a structured form.
7
+ * Default HTML resolver used by `renderToHtml` and the streaming
8
+ * renderers when the consumer does not pass a custom resolver. Maps
9
+ * every `WalkedField` variant to a semantic HTML renderer built on the
10
+ * `h()` element builder.
17
11
  */
18
- declare function fieldId(path: string): string;
19
- /**
20
- * Tab-panel id for a discriminated union at `path`. Delegates to the
21
- * canonical `panelIdFor` from `core/idPath.ts` for the normal case so
22
- * the sync, streaming, and React renderers all emit identical ids; falls
23
- * back to a structurally-equivalent string when the renderer is invoked
24
- * with an empty root path (a discriminated union at the schema root —
25
- * see the `fieldId` doc comment for the wider context).
26
- *
27
- * Exported because `streamRenderers.ts` needs to derive identical ids
28
- * — the panel id on the `<div role="tabpanel">` must match the
29
- * `aria-controls` on every tab regardless of which pipeline rendered it.
30
- */
31
- declare function panelId(path: string): string;
32
- /**
33
- * Tab id for tab `i` within a discriminated union at `path`. Mirror of
34
- * `panelId` above — see its comment.
35
- */
36
- declare function tabId(path: string, i: number): string;
37
12
  declare const defaultHtmlResolver: HtmlResolver;
38
13
  //#endregion
39
- export { dateInputType, defaultHtmlResolver, fieldId, matchUnionOption, panelId, tabId };
14
+ export { dateInputType, defaultHtmlResolver, matchUnionOption };
@@ -9,56 +9,13 @@ import { displayJsonValue } from "../core/walkBuilders.mjs";
9
9
  import { h, raw, serialize } from "./html.mjs";
10
10
  import { ariaDescribedByAttrs, ariaLabelAttrs, ariaReadonlyAttrs, ariaRequiredAttrs, buildHintElement, buildInputId, requiredIndicator } from "./a11y.mjs";
11
11
  //#region src/html/renderers.ts
12
- /**
13
- * Thin wrapper over `fieldDomId` from `core/idPath.ts`. Every render
14
- * pipeline must derive ids from the same canonical normaliser so that
15
- * `aria-controls`, `aria-labelledby`, and `htmlFor` references resolve
16
- * consistently across the React, sync-HTML, and streaming-HTML outputs.
17
- *
18
- * The wrapper tolerates an empty path here (returning `sc-`) so that
19
- * a leaf renderer at the schema root — `renderToHtml(z.string())` — has
20
- * a usable id without throwing. Container renderers always thread a
21
- * non-empty path through `renderChild`, so the empty-id fallback can
22
- * never produce sibling collisions inside a structured form.
23
- */
24
- function fieldId(path) {
25
- if (path.length === 0) return "sc-";
26
- return fieldDomId(path);
27
- }
28
- /**
29
- * Tab-panel id for a discriminated union at `path`. Delegates to the
30
- * canonical `panelIdFor` from `core/idPath.ts` for the normal case so
31
- * the sync, streaming, and React renderers all emit identical ids; falls
32
- * back to a structurally-equivalent string when the renderer is invoked
33
- * with an empty root path (a discriminated union at the schema root —
34
- * see the `fieldId` doc comment for the wider context).
35
- *
36
- * Exported because `streamRenderers.ts` needs to derive identical ids
37
- * — the panel id on the `<div role="tabpanel">` must match the
38
- * `aria-controls` on every tab regardless of which pipeline rendered it.
39
- */
40
- function panelId(path) {
41
- if (path.length === 0) return `${fieldId(path)}-panel`;
42
- return panelIdFor(path);
43
- }
44
- /**
45
- * Tab id for tab `i` within a discriminated union at `path`. Mirror of
46
- * `panelId` above — see its comment.
47
- */
48
- function tabId(path, i) {
49
- if (path.length === 0) return `${fieldId(path)}-tab-${String(i)}`;
50
- return tabIdFor(path, i);
51
- }
52
12
  function renderStringHtml(props) {
53
13
  if (props.readOnly) return serialize(renderStringReadOnly(props));
54
14
  return serialize(renderStringEditable(props));
55
15
  }
56
16
  function renderStringReadOnly(props) {
57
17
  const strValue = typeof props.value === "string" ? props.value : void 0;
58
- if (strValue === void 0 || strValue.length === 0) return h("span", {
59
- class: SC_CLASSES.valueEmpty,
60
- ...ariaReadonlyAttrs()
61
- }, "—");
18
+ if (strValue === void 0 || strValue.length === 0) return h("span", { class: SC_CLASSES.valueEmpty }, "—");
62
19
  const format = props.constraints.format;
63
20
  if (format === "email" && isSafeMailtoAddress(strValue)) return h("a", {
64
21
  class: SC_CLASSES.value,
@@ -70,22 +27,22 @@ function renderStringReadOnly(props) {
70
27
  href: strValue,
71
28
  ...ariaReadonlyAttrs()
72
29
  }, strValue);
73
- return h("span", {
74
- class: SC_CLASSES.value,
75
- ...ariaReadonlyAttrs()
76
- }, strValue);
30
+ return h("span", { class: SC_CLASSES.value }, strValue);
77
31
  }
78
32
  function renderStringEditable(props) {
79
33
  const strValue = typeof props.value === "string" ? props.value : "";
80
34
  const format = props.constraints.format;
81
- const inputType = dateInputType(format) ?? (format === "email" ? "email" : format === "uri" ? "url" : "text");
82
- const id = fieldId(props.path);
35
+ const dateType = dateInputType(format);
36
+ const isCredential = props.writeOnly && format === "password";
37
+ const inputType = dateType ?? (isCredential ? "password" : format === "email" ? "email" : format === "uri" ? "url" : "text");
38
+ const id = fieldDomId(props.path);
83
39
  const attrs = {
84
40
  class: SC_CLASSES.input,
85
41
  id,
86
42
  type: inputType,
87
43
  name: id
88
44
  };
45
+ if (isCredential) attrs.autocomplete = strValue.length > 0 ? "current-password" : "new-password";
89
46
  if (!props.writeOnly) attrs.value = strValue;
90
47
  if (typeof props.meta.description === "string") attrs.placeholder = props.meta.description;
91
48
  if (props.constraints.minLength !== void 0) attrs.minlength = String(props.constraints.minLength);
@@ -99,24 +56,24 @@ function renderNumberHtml(props) {
99
56
  return serialize(renderNumberEditable(props));
100
57
  }
101
58
  function renderNumberReadOnly(props) {
102
- if (typeof props.value !== "number") return h("span", {
103
- class: SC_CLASSES.valueEmpty,
104
- ...ariaReadonlyAttrs()
105
- }, "—");
106
- return h("span", {
107
- class: SC_CLASSES.value,
108
- ...ariaReadonlyAttrs()
109
- }, props.value.toLocaleString());
59
+ if (typeof props.value !== "number") return h("span", { class: SC_CLASSES.valueEmpty }, "—");
60
+ return h("span", { class: SC_CLASSES.value }, props.value.toLocaleString());
110
61
  }
111
62
  function renderNumberEditable(props) {
112
63
  const numValue = typeof props.value === "number" ? String(props.value) : "";
113
- const id = fieldId(props.path);
64
+ const id = fieldDomId(props.path);
65
+ const isInteger = props.tree.type === "number" ? props.tree.isInteger : false;
66
+ const inputMode = isInteger ? "numeric" : "decimal";
67
+ const multipleOf = props.constraints.multipleOf;
114
68
  const attrs = {
115
69
  class: SC_CLASSES.input,
116
70
  id,
117
71
  type: "number",
118
- name: id
72
+ name: id,
73
+ inputmode: inputMode
119
74
  };
75
+ if (multipleOf !== void 0) attrs.step = String(multipleOf);
76
+ else if (isInteger) attrs.step = "1";
120
77
  if (!props.writeOnly) attrs.value = numValue;
121
78
  if (props.constraints.minimum !== void 0) attrs.min = String(props.constraints.minimum);
122
79
  if (props.constraints.maximum !== void 0) attrs.max = String(props.constraints.maximum);
@@ -129,17 +86,11 @@ function renderBooleanHtml(props) {
129
86
  return serialize(renderBooleanEditable(props));
130
87
  }
131
88
  function renderBooleanReadOnly(props) {
132
- if (typeof props.value !== "boolean") return h("span", {
133
- class: SC_CLASSES.valueEmpty,
134
- ...ariaReadonlyAttrs()
135
- }, "—");
136
- return h("span", {
137
- class: "sc-value sc-value--boolean",
138
- ...ariaReadonlyAttrs()
139
- }, props.value ? "Yes" : "No");
89
+ if (typeof props.value !== "boolean") return h("span", { class: SC_CLASSES.valueEmpty }, "—");
90
+ return h("span", { class: "sc-value sc-value--boolean" }, props.value ? "Yes" : "No");
140
91
  }
141
92
  function renderBooleanEditable(props) {
142
- const id = fieldId(props.path);
93
+ const id = fieldDomId(props.path);
143
94
  const attrs = {
144
95
  class: SC_CLASSES.input,
145
96
  id,
@@ -157,18 +108,12 @@ function renderEnumHtml(props) {
157
108
  }
158
109
  function renderEnumReadOnly(props) {
159
110
  const enumValue = typeof props.value === "string" ? props.value : "";
160
- if (enumValue.length === 0) return h("span", {
161
- class: SC_CLASSES.valueEmpty,
162
- ...ariaReadonlyAttrs()
163
- }, "—");
164
- return h("span", {
165
- class: SC_CLASSES.value,
166
- ...ariaReadonlyAttrs()
167
- }, enumValue);
111
+ if (enumValue.length === 0) return h("span", { class: SC_CLASSES.valueEmpty }, "—");
112
+ return h("span", { class: SC_CLASSES.value }, enumValue);
168
113
  }
169
114
  function renderEnumEditable(props) {
170
115
  const enumValue = typeof props.value === "string" ? props.value : "";
171
- const id = fieldId(props.path);
116
+ const id = fieldDomId(props.path);
172
117
  const selectedValue = props.writeOnly ? "" : enumValue;
173
118
  const enumValues = props.tree.type === "enum" ? props.tree.enumValues : [];
174
119
  const optionNodes = [h("option", { value: "" }, `Select…`), ...enumValues.map((v) => {
@@ -268,8 +213,12 @@ function renderRecordNode(props) {
268
213
  }
269
214
  const children = [];
270
215
  for (const [key, val] of Object.entries(obj)) {
216
+ const childInputId = buildInputId(props.path, key);
271
217
  const childHtml = props.renderChild(valueType, val, key);
272
- children.push(h("div", { class: SC_CLASSES.field }, h("label", { class: SC_CLASSES.label }, key), raw(childHtml)));
218
+ children.push(h("div", { class: SC_CLASSES.field }, h("label", {
219
+ class: SC_CLASSES.label,
220
+ for: childInputId
221
+ }, key), raw(childHtml)));
273
222
  }
274
223
  return h("div", attrs, ...children);
275
224
  }
@@ -307,14 +256,14 @@ function renderDiscriminatedUnionHtml(props) {
307
256
  if (activeOption !== void 0) return props.renderChild(activeOption, props.value);
308
257
  return serialize(h("span", { class: SC_CLASSES.valueEmpty }, "—"));
309
258
  }
310
- const tabPanelId = panelId(props.path);
259
+ const tabPanelId = panelIdFor(props.path);
311
260
  const tabButtons = options.map((_opt, i) => {
312
261
  return h("button", {
313
262
  type: "button",
314
263
  role: "tab",
315
264
  class: i === activeIndex ? SC_CLASSES.tabActive : SC_CLASSES.tab,
316
- id: tabId(props.path, i),
317
- "aria-selected": i === activeIndex ? "true" : void 0,
265
+ id: tabIdFor(props.path, i),
266
+ "aria-selected": i === activeIndex ? "true" : "false",
318
267
  "aria-controls": tabPanelId,
319
268
  tabindex: i === activeIndex ? "0" : "-1"
320
269
  }, optionLabels[i]);
@@ -322,25 +271,25 @@ function renderDiscriminatedUnionHtml(props) {
322
271
  const children = [h("div", {
323
272
  role: "tablist",
324
273
  class: SC_CLASSES.tabs,
325
- "aria-label": "Select variant"
274
+ "aria-label": "Select variant",
275
+ "aria-orientation": "horizontal"
326
276
  }, ...tabButtons)];
327
277
  if (activeOption !== void 0) {
328
278
  const childHtml = props.renderChild(activeOption, props.value);
329
279
  children.push(h("div", {
330
280
  role: "tabpanel",
331
281
  id: tabPanelId,
332
- "aria-labelledby": tabId(props.path, activeIndex)
282
+ "aria-labelledby": tabIdFor(props.path, activeIndex)
333
283
  }, raw(childHtml)));
334
284
  }
335
285
  return serialize(h("div", { class: SC_CLASSES.discriminatedUnion }, ...children));
336
286
  }
337
287
  function renderFileHtml(props) {
338
- const id = fieldId(props.path);
288
+ const id = fieldDomId(props.path);
339
289
  const accept = props.constraints.mimeTypes?.join(",");
340
290
  if (props.readOnly) return serialize(h("span", {
341
291
  class: SC_CLASSES.value,
342
- id,
343
- ...ariaReadonlyAttrs()
292
+ id
344
293
  }, "File field"));
345
294
  const attrs = {
346
295
  class: SC_CLASSES.input,
@@ -407,7 +356,7 @@ function renderNegationHtml(props) {
407
356
  * The only valid value is `null`, so render an em-dash placeholder.
408
357
  */
409
358
  function renderNullHtml(props) {
410
- const id = fieldId(props.path);
359
+ const id = fieldDomId(props.path);
411
360
  return serialize(h("span", {
412
361
  class: SC_CLASSES.valueEmpty,
413
362
  id,
@@ -424,10 +373,15 @@ function renderNullHtml(props) {
424
373
  function renderNeverHtml(props) {
425
374
  return serialize(h("span", {
426
375
  class: "sc-value sc-never",
427
- id: fieldId(props.path),
428
- ...ariaReadonlyAttrs()
376
+ id: fieldDomId(props.path)
429
377
  }, h("em", {}, "never matches")));
430
378
  }
379
+ /**
380
+ * Default HTML resolver used by `renderToHtml` and the streaming
381
+ * renderers when the consumer does not pass a custom resolver. Maps
382
+ * every `WalkedField` variant to a semantic HTML renderer built on the
383
+ * `h()` element builder.
384
+ */
431
385
  const defaultHtmlResolver = {
432
386
  string: renderStringHtml,
433
387
  number: renderNumberHtml,
@@ -448,4 +402,4 @@ const defaultHtmlResolver = {
448
402
  unknown: renderUnknownHtml
449
403
  };
450
404
  //#endregion
451
- export { dateInputType, defaultHtmlResolver, fieldId, matchUnionOption, panelId, tabId };
405
+ export { dateInputType, defaultHtmlResolver, matchUnionOption };