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.
Files changed (91) hide show
  1. package/README.md +3 -1
  2. package/dist/core/adapter.d.mts +115 -4
  3. package/dist/core/adapter.mjs +405 -75
  4. package/dist/core/constraints.d.mts +2 -2
  5. package/dist/core/constraints.mjs +0 -7
  6. package/dist/core/cssClasses.d.mts +52 -0
  7. package/dist/core/cssClasses.mjs +51 -0
  8. package/dist/core/diagnostics.d.mts +1 -1
  9. package/dist/core/errors.d.mts +1 -1
  10. package/dist/core/errors.mjs +5 -13
  11. package/dist/core/fieldOrder.d.mts +1 -1
  12. package/dist/core/formats.d.mts +30 -2
  13. package/dist/core/formats.mjs +33 -1
  14. package/dist/core/idPath.d.mts +54 -0
  15. package/dist/core/idPath.mjs +66 -0
  16. package/dist/core/limits.d.mts +2 -0
  17. package/dist/core/limits.mjs +23 -0
  18. package/dist/core/merge.d.mts +10 -1
  19. package/dist/core/merge.mjs +49 -10
  20. package/dist/core/normalise.d.mts +40 -3
  21. package/dist/core/normalise.mjs +2 -2
  22. package/dist/core/openapi30.d.mts +15 -1
  23. package/dist/core/openapi30.mjs +2 -2
  24. package/dist/core/openapiConstants.d.mts +67 -0
  25. package/dist/core/openapiConstants.mjs +90 -0
  26. package/dist/core/ref.d.mts +2 -2
  27. package/dist/core/ref.mjs +85 -6
  28. package/dist/core/refChain.d.mts +70 -0
  29. package/dist/core/refChain.mjs +44 -0
  30. package/dist/core/renderer.d.mts +1 -1
  31. package/dist/core/renderer.mjs +0 -2
  32. package/dist/core/swagger2.d.mts +1 -1
  33. package/dist/core/swagger2.mjs +1 -1
  34. package/dist/core/typeInference.d.mts +982 -2
  35. package/dist/core/types.d.mts +2 -2
  36. package/dist/core/types.mjs +1 -4
  37. package/dist/core/unionMatch.d.mts +36 -0
  38. package/dist/core/unionMatch.mjs +53 -0
  39. package/dist/core/version.d.mts +1 -1
  40. package/dist/core/version.mjs +29 -17
  41. package/dist/core/walkBuilders.d.mts +23 -4
  42. package/dist/core/walkBuilders.mjs +27 -7
  43. package/dist/core/walker.d.mts +1 -1
  44. package/dist/core/walker.mjs +123 -47
  45. package/dist/{diagnostics-CbBPsxSt.d.mts → diagnostics-BS2kaUyE.d.mts} +1 -1
  46. package/dist/{errors-QEwOtQAA.d.mts → errors-g_MCTQel.d.mts} +10 -16
  47. package/dist/html/a11y.d.mts +9 -4
  48. package/dist/html/a11y.mjs +10 -12
  49. package/dist/html/renderToHtml.d.mts +10 -3
  50. package/dist/html/renderToHtml.mjs +13 -3
  51. package/dist/html/renderToHtmlStream.d.mts +2 -2
  52. package/dist/html/renderToHtmlStream.mjs +12 -1
  53. package/dist/html/renderers.d.mts +43 -8
  54. package/dist/html/renderers.mjs +136 -116
  55. package/dist/html/streamRenderers.d.mts +6 -6
  56. package/dist/html/streamRenderers.mjs +129 -89
  57. package/dist/limits-Cw5QZND8.d.mts +29 -0
  58. package/dist/{normalise-DaSrnr8g.mjs → normalise-DCYp06Sr.mjs} +770 -227
  59. package/dist/openapi/ApiCallbacks.d.mts +1 -1
  60. package/dist/openapi/ApiLinks.d.mts +1 -1
  61. package/dist/openapi/ApiResponseHeaders.d.mts +1 -1
  62. package/dist/openapi/ApiSecurity.d.mts +1 -1
  63. package/dist/openapi/ApiSecurity.mjs +16 -2
  64. package/dist/openapi/components.d.mts +234 -23
  65. package/dist/openapi/components.mjs +183 -52
  66. package/dist/openapi/parser.d.mts +9 -8
  67. package/dist/openapi/parser.mjs +252 -70
  68. package/dist/openapi/resolve.d.mts +31 -15
  69. package/dist/openapi/resolve.mjs +260 -40
  70. package/dist/react/SchemaComponent.d.mts +126 -36
  71. package/dist/react/SchemaComponent.mjs +95 -57
  72. package/dist/react/SchemaView.d.mts +30 -10
  73. package/dist/react/SchemaView.mjs +2 -2
  74. package/dist/react/a11y.d.mts +21 -0
  75. package/dist/react/a11y.mjs +24 -0
  76. package/dist/react/fieldPath.d.mts +1 -1
  77. package/dist/react/headless.d.mts +1 -1
  78. package/dist/react/headless.mjs +1 -2
  79. package/dist/react/headlessRenderers.d.mts +9 -11
  80. package/dist/react/headlessRenderers.mjs +51 -102
  81. package/dist/{ref-si8ViYun.d.mts → ref-DjLEKa_E.d.mts} +38 -3
  82. package/dist/{renderer-DI6ZYf7a.d.mts → renderer-CXJ8y0qw.d.mts} +2 -2
  83. package/dist/themes/mantine.d.mts +1 -1
  84. package/dist/themes/mui.d.mts +1 -1
  85. package/dist/themes/radix.d.mts +1 -1
  86. package/dist/themes/shadcn.d.mts +1 -1
  87. package/dist/themes/shadcn.mjs +2 -1
  88. package/dist/{types-BnxPEElk.d.mts → types-BTB73MB8.d.mts} +35 -14
  89. package/dist/{version-D-u7aMfy.d.mts → version-BFTVLsdb.d.mts} +7 -1
  90. package/package.json +1 -3
  91. package/dist/typeInference-Bxw3NOG1.d.mts +0 -647
@@ -1,20 +1,64 @@
1
+ import { isObject } from "../core/guards.mjs";
2
+ import { dateInputType } from "../core/formats.mjs";
1
3
  import { isSafeHyperlink, isSafeMailtoAddress } from "../core/uri.mjs";
4
+ import { SC_CLASSES } from "../core/cssClasses.mjs";
2
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";
3
9
  import { h, raw, serialize } from "./html.mjs";
4
10
  import { ariaDescribedByAttrs, ariaLabelAttrs, ariaReadonlyAttrs, ariaRequiredAttrs, buildHintElement, buildInputId, requiredIndicator } from "./a11y.mjs";
5
11
  //#region src/html/renderers.ts
6
- function dateInputType(format) {
7
- if (format === "date") return "date";
8
- if (format === "time") return "time";
9
- if (format === "date-time" || format === "datetime") return "datetime-local";
10
- }
11
12
  /**
12
- * Normalise a structural path into a valid, `sc-` prefixed HTML ID.
13
- * Dots (object nesting) and brackets (array indices) become hyphens so
14
- * the id remains a valid CSS selector and predictable in test queries.
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-`) for the
19
+ * sole reason that a leaf renderer at the schema root would otherwise
20
+ * throw — `renderToHtml(z.string())` is a rare but valid call shape.
21
+ * Container renderers thread a non-empty path through `renderChild`, so
22
+ * the empty-id fallback can never produce sibling collisions inside a
23
+ * structured form.
24
+ *
25
+ * TODO(round7-integration): once `renderToHtml` always threads a stable
26
+ * root path (e.g. `"$"`) into the leaf renderers, drop this wrapper and
27
+ * call `fieldDomId` directly so the throw fires as designed.
15
28
  */
16
29
  function fieldId(path) {
17
- return `sc-${path.replace(/[.[\]]+/g, "-").replace(/-+$/g, "")}`;
30
+ if (path.length === 0) return "sc-";
31
+ return fieldDomId(path);
32
+ }
33
+ /**
34
+ * Tab-panel id for a discriminated union at `path`. Delegates to the
35
+ * canonical `panelIdFor` from `core/idPath.ts` for the normal case so
36
+ * the sync, streaming, and React renderers all emit identical ids; falls
37
+ * back to a structurally-equivalent string when the renderer is invoked
38
+ * with an empty root path (a discriminated union at the schema root —
39
+ * see the `fieldId` doc comment for the wider context).
40
+ *
41
+ * Exported because `streamRenderers.ts` needs to derive identical ids
42
+ * — the panel id on the `<div role="tabpanel">` must match the
43
+ * `aria-controls` on every tab regardless of which pipeline rendered it.
44
+ *
45
+ * TODO(round7-integration): drop the empty-path branch once `renderToHtml`
46
+ * threads a stable root path so `panelIdFor` can be called directly.
47
+ */
48
+ function panelId(path) {
49
+ if (path.length === 0) return `${fieldId(path)}-panel`;
50
+ return panelIdFor(path);
51
+ }
52
+ /**
53
+ * Tab id for tab `i` within a discriminated union at `path`. Mirror of
54
+ * `panelId` above — see its comment.
55
+ *
56
+ * TODO(round7-integration): drop the empty-path branch once `renderToHtml`
57
+ * threads a stable root path so `tabIdFor` can be called directly.
58
+ */
59
+ function tabId(path, i) {
60
+ if (path.length === 0) return `${fieldId(path)}-tab-${String(i)}`;
61
+ return tabIdFor(path, i);
18
62
  }
19
63
  function renderStringHtml(props) {
20
64
  if (props.readOnly) return serialize(renderStringReadOnly(props));
@@ -23,22 +67,22 @@ function renderStringHtml(props) {
23
67
  function renderStringReadOnly(props) {
24
68
  const strValue = typeof props.value === "string" ? props.value : void 0;
25
69
  if (strValue === void 0 || strValue.length === 0) return h("span", {
26
- class: "sc-value sc-value--empty",
70
+ class: SC_CLASSES.valueEmpty,
27
71
  ...ariaReadonlyAttrs()
28
72
  }, "—");
29
73
  const format = props.constraints.format;
30
74
  if (format === "email" && isSafeMailtoAddress(strValue)) return h("a", {
31
- class: "sc-value",
75
+ class: SC_CLASSES.value,
32
76
  href: `mailto:${strValue}`,
33
77
  ...ariaReadonlyAttrs()
34
78
  }, strValue);
35
79
  if ((format === "uri" || format === "url") && isSafeHyperlink(strValue)) return h("a", {
36
- class: "sc-value",
80
+ class: SC_CLASSES.value,
37
81
  href: strValue,
38
82
  ...ariaReadonlyAttrs()
39
83
  }, strValue);
40
84
  return h("span", {
41
- class: "sc-value",
85
+ class: SC_CLASSES.value,
42
86
  ...ariaReadonlyAttrs()
43
87
  }, strValue);
44
88
  }
@@ -48,7 +92,7 @@ function renderStringEditable(props) {
48
92
  const inputType = dateInputType(format) ?? (format === "email" ? "email" : format === "uri" ? "url" : "text");
49
93
  const id = fieldId(props.path);
50
94
  const attrs = {
51
- class: "sc-input",
95
+ class: SC_CLASSES.input,
52
96
  id,
53
97
  type: inputType,
54
98
  name: id
@@ -67,11 +111,11 @@ function renderNumberHtml(props) {
67
111
  }
68
112
  function renderNumberReadOnly(props) {
69
113
  if (typeof props.value !== "number") return h("span", {
70
- class: "sc-value sc-value--empty",
114
+ class: SC_CLASSES.valueEmpty,
71
115
  ...ariaReadonlyAttrs()
72
116
  }, "—");
73
117
  return h("span", {
74
- class: "sc-value",
118
+ class: SC_CLASSES.value,
75
119
  ...ariaReadonlyAttrs()
76
120
  }, props.value.toLocaleString());
77
121
  }
@@ -79,7 +123,7 @@ function renderNumberEditable(props) {
79
123
  const numValue = typeof props.value === "number" ? String(props.value) : "";
80
124
  const id = fieldId(props.path);
81
125
  const attrs = {
82
- class: "sc-input",
126
+ class: SC_CLASSES.input,
83
127
  id,
84
128
  type: "number",
85
129
  name: id
@@ -97,7 +141,7 @@ function renderBooleanHtml(props) {
97
141
  }
98
142
  function renderBooleanReadOnly(props) {
99
143
  if (typeof props.value !== "boolean") return h("span", {
100
- class: "sc-value sc-value--empty",
144
+ class: SC_CLASSES.valueEmpty,
101
145
  ...ariaReadonlyAttrs()
102
146
  }, "—");
103
147
  return h("span", {
@@ -108,7 +152,7 @@ function renderBooleanReadOnly(props) {
108
152
  function renderBooleanEditable(props) {
109
153
  const id = fieldId(props.path);
110
154
  const attrs = {
111
- class: "sc-input",
155
+ class: SC_CLASSES.input,
112
156
  id,
113
157
  type: "checkbox",
114
158
  name: id
@@ -125,11 +169,11 @@ function renderEnumHtml(props) {
125
169
  function renderEnumReadOnly(props) {
126
170
  const enumValue = typeof props.value === "string" ? props.value : "";
127
171
  if (enumValue.length === 0) return h("span", {
128
- class: "sc-value sc-value--empty",
172
+ class: SC_CLASSES.valueEmpty,
129
173
  ...ariaReadonlyAttrs()
130
174
  }, "—");
131
175
  return h("span", {
132
- class: "sc-value",
176
+ class: SC_CLASSES.value,
133
177
  ...ariaReadonlyAttrs()
134
178
  }, enumValue);
135
179
  }
@@ -138,14 +182,14 @@ function renderEnumEditable(props) {
138
182
  const id = fieldId(props.path);
139
183
  const selectedValue = props.writeOnly ? "" : enumValue;
140
184
  const enumValues = props.tree.type === "enum" ? props.tree.enumValues : [];
141
- const optionNodes = [h("option", { value: "" }, "Select…"), ...enumValues.map((v) => {
142
- const display = v === null ? "null" : typeof v === "string" ? v : String(v);
185
+ const optionNodes = [h("option", { value: "" }, `Select…`), ...enumValues.map((v) => {
186
+ const display = displayJsonValue(v);
143
187
  const attrs = { value: display };
144
188
  if (display === selectedValue) attrs.selected = true;
145
189
  return h("option", attrs, display);
146
190
  })];
147
191
  const selectAttrs = {
148
- class: "sc-input",
192
+ class: SC_CLASSES.input,
149
193
  id,
150
194
  name: id
151
195
  };
@@ -158,8 +202,7 @@ function renderObjectHtml(props) {
158
202
  function renderObjectNode(props) {
159
203
  if (props.tree.type !== "object") return "";
160
204
  const fields = props.tree.fields;
161
- const isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
162
- const obj = isRecord(props.value) ? props.value : {};
205
+ const obj = isObject(props.value) ? props.value : {};
163
206
  const descriptionText = typeof props.meta.description === "string" ? props.meta.description : void 0;
164
207
  const legend = descriptionText !== void 0 ? h("legend", {}, descriptionText) : void 0;
165
208
  const sortedEntries = sortFieldsByOrder(fields).filter(([, field]) => field.meta.visible !== false);
@@ -170,10 +213,10 @@ function renderObjectNode(props) {
170
213
  const label = typeof field.meta.description === "string" ? field.meta.description : key;
171
214
  const childValue = obj[key];
172
215
  const childHtml = props.renderChild(field, childValue, key);
173
- children.push(h("dt", { class: "sc-label" }, label));
174
- children.push(h("dd", { class: "sc-value" }, raw(childHtml)));
216
+ children.push(h("dt", { class: SC_CLASSES.label }, label));
217
+ children.push(h("dd", { class: SC_CLASSES.value }, raw(childHtml)));
175
218
  }
176
- const dlAttrs = { class: "sc-object" };
219
+ const dlAttrs = { class: SC_CLASSES.object };
177
220
  Object.assign(dlAttrs, ariaLabelAttrs(descriptionText));
178
221
  return h("dl", dlAttrs, ...children);
179
222
  }
@@ -188,14 +231,14 @@ function renderObjectNode(props) {
188
231
  const labelContent = [label];
189
232
  if (required !== void 0) labelContent.push(required);
190
233
  const fieldChildren = [h("label", {
191
- class: "sc-label",
234
+ class: SC_CLASSES.label,
192
235
  for: fieldId
193
236
  }, ...labelContent), raw(childHtml)];
194
237
  const hint = buildHintElement(fieldId, field.constraints);
195
238
  if (hint !== void 0) fieldChildren.push(hint);
196
- children.push(h("div", { class: "sc-field" }, ...fieldChildren));
239
+ children.push(h("div", { class: SC_CLASSES.field }, ...fieldChildren));
197
240
  }
198
- const fieldsetAttrs = { class: "sc-object" };
241
+ const fieldsetAttrs = { class: SC_CLASSES.object };
199
242
  Object.assign(fieldsetAttrs, ariaLabelAttrs(descriptionText));
200
243
  return h("fieldset", fieldsetAttrs, ...children);
201
244
  }
@@ -207,124 +250,111 @@ function renderArrayNode(props) {
207
250
  const element = props.tree.type === "array" ? props.tree.element : void 0;
208
251
  if (element === void 0) return "";
209
252
  const childHtmls = arr.map((item, i) => props.renderChild(element, item, `[${String(i)}]`));
210
- if (props.readOnly) return h("ul", { class: "sc-array" }, ...childHtmls.map((childHtml) => h("li", { class: "sc-item" }, raw(childHtml))));
211
- return h("div", { class: "sc-array" }, ...childHtmls.map((childHtml) => h("div", {}, raw(childHtml))));
253
+ if (props.readOnly) {
254
+ const items = childHtmls.map((childHtml) => h("li", { class: "sc-item" }, raw(childHtml)));
255
+ return h("ul", { class: SC_CLASSES.array }, ...items);
256
+ }
257
+ const divItems = childHtmls.map((childHtml) => h("div", {}, raw(childHtml)));
258
+ return h("div", { class: SC_CLASSES.array }, ...divItems);
212
259
  }
213
260
  function renderRecordHtml(props) {
214
261
  return serialize(renderRecordNode(props));
215
262
  }
216
263
  function renderRecordNode(props) {
217
264
  if (props.tree.type !== "record") return "";
218
- const isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
219
- const obj = isRecord(props.value) ? props.value : {};
265
+ const obj = isObject(props.value) ? props.value : {};
220
266
  const valueType = props.tree.valueType;
221
267
  const attrs = {
222
- class: "sc-record",
268
+ class: SC_CLASSES.record,
223
269
  role: "group"
224
270
  };
225
271
  if (props.readOnly) {
226
272
  const children = [];
227
273
  for (const [key, val] of Object.entries(obj)) {
228
274
  const childHtml = props.renderChild(valueType, val, key);
229
- children.push(h("dt", { class: "sc-label" }, key));
230
- children.push(h("dd", { class: "sc-value" }, raw(childHtml)));
275
+ children.push(h("dt", { class: SC_CLASSES.label }, key));
276
+ children.push(h("dd", { class: SC_CLASSES.value }, raw(childHtml)));
231
277
  }
232
278
  return h("dl", attrs, ...children);
233
279
  }
234
280
  const children = [];
235
281
  for (const [key, val] of Object.entries(obj)) {
236
282
  const childHtml = props.renderChild(valueType, val, key);
237
- children.push(h("div", { class: "sc-field" }, h("label", { class: "sc-label" }, key), raw(childHtml)));
283
+ children.push(h("div", { class: SC_CLASSES.field }, h("label", { class: SC_CLASSES.label }, key), raw(childHtml)));
238
284
  }
239
285
  return h("div", attrs, ...children);
240
286
  }
241
287
  function renderLiteralHtml(props) {
242
- if (props.tree.type !== "literal") return serialize(h("span", { class: "sc-value sc-value--empty" }, "—"));
288
+ if (props.tree.type !== "literal") return serialize(h("span", { class: SC_CLASSES.valueEmpty }, "—"));
243
289
  const values = props.tree.literalValues;
244
- if (values.length === 0) return serialize(h("span", { class: "sc-value sc-value--empty" }, "—"));
245
- return serialize(h("span", { class: "sc-value" }, values.map((v) => v === null ? "null" : String(v)).join(", ")));
290
+ if (values.length === 0) return serialize(h("span", { class: SC_CLASSES.valueEmpty }, "—"));
291
+ const display = values.map((v) => displayJsonValue(v)).join(", ");
292
+ return serialize(h("span", { class: SC_CLASSES.value }, display));
246
293
  }
247
294
  function renderUnionHtml(props) {
248
295
  const options = props.tree.type === "union" || props.tree.type === "discriminatedUnion" ? props.tree.options : void 0;
249
296
  if (options === void 0 || options.length === 0) {
250
- if (props.value === void 0 || props.value === null) return serialize(h("span", { class: "sc-value sc-value--empty" }, "—"));
251
- return serialize(h("span", { class: "sc-value" }, JSON.stringify(props.value)));
297
+ if (props.value === void 0 || props.value === null) return serialize(h("span", { class: SC_CLASSES.valueEmpty }, "—"));
298
+ return serialize(h("span", { class: SC_CLASSES.value }, JSON.stringify(props.value)));
252
299
  }
253
300
  const matched = matchUnionOption(options, props.value);
254
301
  if (matched !== void 0) return props.renderChild(matched, props.value);
255
302
  const firstOption = options[0];
256
303
  if (firstOption !== void 0) return props.renderChild(firstOption, props.value);
257
- return serialize(h("span", { class: "sc-value sc-value--empty" }, "—"));
304
+ return serialize(h("span", { class: SC_CLASSES.valueEmpty }, "—"));
258
305
  }
259
306
  function renderDiscriminatedUnionHtml(props) {
260
- const options = props.tree.type === "discriminatedUnion" ? props.tree.options : void 0;
261
- const discriminator = props.tree.type === "discriminatedUnion" ? props.tree.discriminator : void 0;
262
- if (options === void 0 || options.length === 0) {
263
- if (props.value === void 0 || props.value === null) return serialize(h("span", { class: "sc-value sc-value--empty" }, "—"));
264
- return serialize(h("span", { class: "sc-value" }, JSON.stringify(props.value)));
307
+ if (props.tree.type !== "discriminatedUnion") {
308
+ if (props.value === void 0 || props.value === null) return serialize(h("span", { class: SC_CLASSES.valueEmpty }, "—"));
309
+ return serialize(h("span", { class: SC_CLASSES.value }, JSON.stringify(props.value)));
265
310
  }
266
- const isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
267
- const obj = isRecord(props.value) ? props.value : {};
268
- const discKey = discriminator ?? "";
269
- const currentDiscriminatorValue = typeof obj[discKey] === "string" ? obj[discKey] : void 0;
270
- const optionLabels = options.map((opt) => {
271
- if (opt.type === "object") {
272
- const discriminatorField = opt.fields[discKey];
273
- if (discriminatorField?.type === "literal") {
274
- const constVal = discriminatorField.literalValues[0];
275
- if (typeof constVal === "string") return constVal;
276
- }
277
- }
278
- return typeof opt.meta.title === "string" ? opt.meta.title : opt.type;
279
- });
280
- let activeIndex = 0;
281
- if (currentDiscriminatorValue !== void 0) {
282
- const found = optionLabels.indexOf(currentDiscriminatorValue);
283
- if (found !== -1) activeIndex = found;
311
+ const { options, discriminator } = props.tree;
312
+ if (options.length === 0) {
313
+ if (props.value === void 0 || props.value === null) return serialize(h("span", { class: SC_CLASSES.valueEmpty }, "—"));
314
+ return serialize(h("span", { class: SC_CLASSES.value }, JSON.stringify(props.value)));
284
315
  }
285
- const activeOption = options[activeIndex];
316
+ const { optionLabels, activeIndex, activeOption } = resolveDiscriminatedActive(options, discriminator, isObject(props.value) ? props.value : void 0);
286
317
  if (props.readOnly) {
287
318
  if (activeOption !== void 0) return props.renderChild(activeOption, props.value);
288
- return serialize(h("span", { class: "sc-value sc-value--empty" }, "—"));
319
+ return serialize(h("span", { class: SC_CLASSES.valueEmpty }, "—"));
289
320
  }
290
- const baseId = fieldId(props.path);
291
- const panelId = `${baseId}-panel`;
292
- const tabId = (i) => `${baseId}-tab-${String(i)}`;
293
- const children = [h("div", {
294
- role: "tablist",
295
- class: "sc-tabs",
296
- "aria-label": "Select variant"
297
- }, ...options.map((_opt, i) => {
321
+ const tabPanelId = panelId(props.path);
322
+ const tabButtons = options.map((_opt, i) => {
298
323
  return h("button", {
299
324
  type: "button",
300
325
  role: "tab",
301
- class: i === activeIndex ? "sc-tab sc-tab--active" : "sc-tab",
302
- id: tabId(i),
326
+ class: i === activeIndex ? SC_CLASSES.tabActive : SC_CLASSES.tab,
327
+ id: tabId(props.path, i),
303
328
  "aria-selected": i === activeIndex ? "true" : void 0,
304
- "aria-controls": panelId,
329
+ "aria-controls": tabPanelId,
305
330
  tabindex: i === activeIndex ? "0" : "-1"
306
331
  }, optionLabels[i]);
307
- }))];
332
+ });
333
+ const children = [h("div", {
334
+ role: "tablist",
335
+ class: SC_CLASSES.tabs,
336
+ "aria-label": "Select variant"
337
+ }, ...tabButtons)];
308
338
  if (activeOption !== void 0) {
309
339
  const childHtml = props.renderChild(activeOption, props.value);
310
340
  children.push(h("div", {
311
341
  role: "tabpanel",
312
- id: panelId,
313
- "aria-labelledby": tabId(activeIndex)
342
+ id: tabPanelId,
343
+ "aria-labelledby": tabId(props.path, activeIndex)
314
344
  }, raw(childHtml)));
315
345
  }
316
- return serialize(h("div", { class: "sc-discriminated-union" }, ...children));
346
+ return serialize(h("div", { class: SC_CLASSES.discriminatedUnion }, ...children));
317
347
  }
318
348
  function renderFileHtml(props) {
319
349
  const id = fieldId(props.path);
320
350
  const accept = props.constraints.mimeTypes?.join(",");
321
351
  if (props.readOnly) return serialize(h("span", {
322
- class: "sc-value",
352
+ class: SC_CLASSES.value,
323
353
  id,
324
354
  ...ariaReadonlyAttrs()
325
355
  }, "File field"));
326
356
  const attrs = {
327
- class: "sc-input",
357
+ class: SC_CLASSES.input,
328
358
  id,
329
359
  type: "file",
330
360
  name: id
@@ -334,21 +364,18 @@ function renderFileHtml(props) {
334
364
  if (typeof props.meta.description === "string") Object.assign(attrs, ariaLabelAttrs(props.meta.description));
335
365
  return serialize(h("input", attrs));
336
366
  }
337
- function renderRecursiveHtml(props) {
338
- const refTarget = props.tree.type === "recursive" ? props.tree.refTarget : "";
339
- return serialize(h("fieldset", { class: "sc-recursive" }, `↻ ${typeof props.meta.description === "string" ? props.meta.description : refTarget} (recursive)`));
340
- }
341
367
  function renderUnknownHtml(props) {
342
368
  if (props.readOnly) {
343
- if (props.value === void 0 || props.value === null) return serialize(h("span", { class: "sc-value sc-value--empty" }, "—"));
344
- if (typeof props.value === "string") return serialize(h("span", { class: "sc-value" }, props.value));
345
- return serialize(h("span", { class: "sc-value" }, JSON.stringify(props.value)));
369
+ if (props.value === void 0 || props.value === null) return serialize(h("span", { class: SC_CLASSES.valueEmpty }, "—"));
370
+ if (typeof props.value === "string") return serialize(h("span", { class: SC_CLASSES.value }, props.value));
371
+ return serialize(h("span", { class: SC_CLASSES.value }, JSON.stringify(props.value)));
346
372
  }
347
373
  const strValue = typeof props.value === "string" ? props.value : "";
374
+ const name = props.path;
348
375
  const attrs = {
349
- class: "sc-input",
376
+ class: SC_CLASSES.input,
350
377
  type: "text",
351
- name: props.path
378
+ name
352
379
  };
353
380
  if (!props.writeOnly) attrs.value = strValue;
354
381
  return serialize(h("input", attrs));
@@ -364,26 +391,26 @@ function renderTupleHtml(props) {
364
391
  const element = prefixItems[i];
365
392
  if (element === void 0) continue;
366
393
  const childHtml = props.renderChild(element, itemValue, `[${String(i)}]`);
367
- children.push(h("div", { class: "sc-tuple-item" }, h("span", { class: "sc-tuple-index" }, String(i)), raw(childHtml)));
394
+ children.push(h("div", { class: SC_CLASSES.tupleItem }, h("span", { class: SC_CLASSES.tupleIndex }, String(i)), raw(childHtml)));
368
395
  }
369
396
  if (restItems !== void 0) for (let i = prefixItems.length; i < arr.length; i++) {
370
397
  const itemValue = arr[i];
371
398
  const childHtml = props.renderChild(restItems, itemValue, `[${String(i)}]`);
372
- children.push(h("div", { class: "sc-tuple-item sc-tuple-rest" }, h("span", { class: "sc-tuple-index" }, String(i)), raw(childHtml)));
399
+ children.push(h("div", { class: `${SC_CLASSES.tupleItem} ${SC_CLASSES.tupleRest}` }, h("span", { class: SC_CLASSES.tupleIndex }, String(i)), raw(childHtml)));
373
400
  }
374
- return serialize(h("div", { class: "sc-tuple" }, ...children));
401
+ return serialize(h("div", { class: SC_CLASSES.tuple }, ...children));
375
402
  }
376
403
  function renderConditionalHtml(props) {
377
404
  const children = [];
378
405
  if (props.tree.type === "conditional") {
379
- children.push(h("div", { class: "sc-conditional-if" }, raw("if: ...")));
380
- if (props.tree.thenClause !== void 0) children.push(h("div", { class: "sc-conditional-then" }, raw("then: ...")));
381
- if (props.tree.elseClause !== void 0) children.push(h("div", { class: "sc-conditional-else" }, raw("else: ...")));
406
+ children.push(h("div", { class: SC_CLASSES.conditionalIf }, raw("if: ...")));
407
+ if (props.tree.thenClause !== void 0) children.push(h("div", { class: SC_CLASSES.conditionalThen }, raw("then: ...")));
408
+ if (props.tree.elseClause !== void 0) children.push(h("div", { class: SC_CLASSES.conditionalElse }, raw("else: ...")));
382
409
  }
383
- return serialize(h("div", { class: "sc-conditional" }, ...children));
410
+ return serialize(h("div", { class: SC_CLASSES.conditional }, ...children));
384
411
  }
385
412
  function renderNegationHtml(props) {
386
- return serialize(h("div", { class: "sc-negation" }, raw("not: ...")));
413
+ return serialize(h("div", { class: SC_CLASSES.negation }, raw("not: ...")));
387
414
  }
388
415
  /**
389
416
  * Render a null field — `z.null()` or `{ type: "null" }`.
@@ -391,9 +418,10 @@ function renderNegationHtml(props) {
391
418
  * The only valid value is `null`, so render an em-dash placeholder.
392
419
  */
393
420
  function renderNullHtml(props) {
421
+ const id = fieldId(props.path);
394
422
  return serialize(h("span", {
395
- class: "sc-value sc-value--empty",
396
- id: fieldId(props.path),
423
+ class: SC_CLASSES.valueEmpty,
424
+ id,
397
425
  ...ariaReadonlyAttrs()
398
426
  }, "—"));
399
427
  }
@@ -411,13 +439,6 @@ function renderNeverHtml(props) {
411
439
  ...ariaReadonlyAttrs()
412
440
  }, h("em", {}, "never matches")));
413
441
  }
414
- function matchUnionOption(options, value) {
415
- if (typeof value === "string") return options.find((o) => o.type === "string" || o.type === "enum");
416
- if (typeof value === "number") return options.find((o) => o.type === "number");
417
- if (typeof value === "boolean") return options.find((o) => o.type === "boolean");
418
- if (Array.isArray(value)) return options.find((o) => o.type === "array");
419
- if (typeof value === "object" && value !== null) return options.find((o) => o.type === "object");
420
- }
421
442
  const defaultHtmlResolver = {
422
443
  string: renderStringHtml,
423
444
  number: renderNumberHtml,
@@ -433,10 +454,9 @@ const defaultHtmlResolver = {
433
454
  discriminatedUnion: renderDiscriminatedUnionHtml,
434
455
  conditional: renderConditionalHtml,
435
456
  negation: renderNegationHtml,
436
- recursive: renderRecursiveHtml,
437
457
  file: renderFileHtml,
438
458
  never: renderNeverHtml,
439
459
  unknown: renderUnknownHtml
440
460
  };
441
461
  //#endregion
442
- export { dateInputType, defaultHtmlResolver, fieldId, matchUnionOption };
462
+ export { dateInputType, defaultHtmlResolver, fieldId, matchUnionOption, panelId, tabId };
@@ -1,13 +1,13 @@
1
- import { M as WalkedField } from "../types-BnxPEElk.mjs";
2
- import { o as HtmlResolver } from "../renderer-DI6ZYf7a.mjs";
1
+ import { j as WalkedField } from "../types-BTB73MB8.mjs";
2
+ import { i as DiagnosticsOptions } from "../diagnostics-BS2kaUyE.mjs";
3
+ import { o as HtmlResolver } from "../renderer-CXJ8y0qw.mjs";
3
4
  import { HtmlElement } from "./html.mjs";
4
5
 
5
6
  //#region src/html/streamRenderers.d.ts
6
7
  declare function yieldOpen(el: HtmlElement): string;
7
8
  declare function yieldClose(el: HtmlElement): string;
8
9
  declare function renderLeaf(tree: WalkedField, value: unknown, mergedResolver: HtmlResolver, path: string): string;
9
- declare function renderFieldSync(tree: WalkedField, value: unknown, mergedResolver: HtmlResolver, path: string, rawResolver: HtmlResolver): string;
10
- declare function matchUnionOption(options: WalkedField[], value: unknown): WalkedField | undefined;
11
- declare function streamField(tree: WalkedField, value: unknown, mergedResolver: HtmlResolver, path: string, rawResolver: HtmlResolver): Iterable<string, void, undefined>;
10
+ declare function renderFieldSync(tree: WalkedField, value: unknown, mergedResolver: HtmlResolver, path: string, rawResolver: HtmlResolver, currentDepth: number, diagnostics: DiagnosticsOptions | undefined): string;
11
+ declare function streamField(tree: WalkedField, value: unknown, mergedResolver: HtmlResolver, path: string, rawResolver: HtmlResolver, currentDepth?: number, diagnostics?: DiagnosticsOptions): Iterable<string, void, undefined>;
12
12
  //#endregion
13
- export { matchUnionOption, renderFieldSync, renderLeaf, streamField, yieldClose, yieldOpen };
13
+ export { renderFieldSync, renderLeaf, streamField, yieldClose, yieldOpen };