schema-components 1.22.0 → 1.24.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 (82) hide show
  1. package/README.md +3 -1
  2. package/dist/core/adapter.d.mts +97 -3
  3. package/dist/core/adapter.mjs +260 -111
  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 +9 -2
  13. package/dist/core/formats.mjs +12 -1
  14. package/dist/core/idPath.d.mts +54 -0
  15. package/dist/core/idPath.mjs +66 -0
  16. package/dist/core/merge.d.mts +10 -1
  17. package/dist/core/merge.mjs +49 -10
  18. package/dist/core/normalise.d.mts +14 -3
  19. package/dist/core/normalise.mjs +2 -2
  20. package/dist/core/openapi30.d.mts +15 -1
  21. package/dist/core/openapi30.mjs +2 -2
  22. package/dist/core/openapiConstants.d.mts +67 -0
  23. package/dist/core/openapiConstants.mjs +90 -0
  24. package/dist/core/ref.d.mts +2 -2
  25. package/dist/core/ref.mjs +83 -6
  26. package/dist/core/refChain.d.mts +70 -0
  27. package/dist/core/refChain.mjs +44 -0
  28. package/dist/core/renderer.d.mts +1 -1
  29. package/dist/core/swagger2.d.mts +1 -1
  30. package/dist/core/swagger2.mjs +1 -1
  31. package/dist/core/typeInference.d.mts +982 -2
  32. package/dist/core/types.d.mts +1 -1
  33. package/dist/core/unionMatch.d.mts +36 -0
  34. package/dist/core/unionMatch.mjs +53 -0
  35. package/dist/core/version.d.mts +1 -1
  36. package/dist/core/version.mjs +29 -17
  37. package/dist/core/walkBuilders.d.mts +23 -4
  38. package/dist/core/walkBuilders.mjs +27 -7
  39. package/dist/core/walker.d.mts +1 -1
  40. package/dist/core/walker.mjs +44 -45
  41. package/dist/{diagnostics-D0QCYGv0.d.mts → diagnostics-Cbwak-ZX.d.mts} +1 -1
  42. package/dist/{errors-DpFwqs5C.d.mts → errors-g_MCTQel.d.mts} +9 -15
  43. package/dist/html/a11y.d.mts +9 -4
  44. package/dist/html/a11y.mjs +10 -19
  45. package/dist/html/renderToHtml.d.mts +2 -2
  46. package/dist/html/renderToHtmlStream.d.mts +2 -2
  47. package/dist/html/renderToHtmlStream.mjs +12 -1
  48. package/dist/html/renderers.d.mts +32 -8
  49. package/dist/html/renderers.mjs +125 -111
  50. package/dist/html/streamRenderers.d.mts +4 -5
  51. package/dist/html/streamRenderers.mjs +40 -61
  52. package/dist/{normalise-DVEJQmF7.mjs → normalise-DCYp06Sr.mjs} +352 -162
  53. package/dist/openapi/ApiCallbacks.d.mts +1 -1
  54. package/dist/openapi/ApiLinks.d.mts +1 -1
  55. package/dist/openapi/ApiResponseHeaders.d.mts +1 -1
  56. package/dist/openapi/ApiSecurity.d.mts +1 -1
  57. package/dist/openapi/components.d.mts +116 -37
  58. package/dist/openapi/components.mjs +54 -37
  59. package/dist/openapi/parser.d.mts +19 -9
  60. package/dist/openapi/parser.mjs +262 -86
  61. package/dist/openapi/resolve.d.mts +20 -11
  62. package/dist/openapi/resolve.mjs +135 -75
  63. package/dist/react/SchemaComponent.d.mts +32 -7
  64. package/dist/react/SchemaComponent.mjs +45 -21
  65. package/dist/react/SchemaView.d.mts +30 -10
  66. package/dist/react/a11y.d.mts +21 -0
  67. package/dist/react/a11y.mjs +24 -0
  68. package/dist/react/fieldPath.d.mts +1 -1
  69. package/dist/react/headless.d.mts +1 -1
  70. package/dist/react/headlessRenderers.d.mts +8 -9
  71. package/dist/react/headlessRenderers.mjs +41 -72
  72. package/dist/{ref-D-_JBZkF.d.mts → ref-DCDuswPe.d.mts} +38 -3
  73. package/dist/{renderer-BaRlQIuN.d.mts → renderer-CXJ8y0qw.d.mts} +1 -1
  74. package/dist/themes/mantine.d.mts +1 -1
  75. package/dist/themes/mui.d.mts +1 -1
  76. package/dist/themes/radix.d.mts +1 -1
  77. package/dist/themes/shadcn.d.mts +1 -1
  78. package/dist/themes/shadcn.mjs +2 -1
  79. package/dist/{types-BrRMV0en.d.mts → types-BTB73MB8.d.mts} +32 -4
  80. package/dist/{version-D2jfdX6E.d.mts → version-BFTVLsdb.d.mts} +7 -1
  81. package/package.json +1 -1
  82. package/dist/typeInference-DkcUHfaM.d.mts +0 -982
@@ -1,20 +1,53 @@
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-`) 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.
15
23
  */
16
24
  function fieldId(path) {
17
- return `sc-${path.replace(/[.[\]]+/g, "-").replace(/-+$/g, "")}`;
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);
18
51
  }
19
52
  function renderStringHtml(props) {
20
53
  if (props.readOnly) return serialize(renderStringReadOnly(props));
@@ -23,22 +56,22 @@ function renderStringHtml(props) {
23
56
  function renderStringReadOnly(props) {
24
57
  const strValue = typeof props.value === "string" ? props.value : void 0;
25
58
  if (strValue === void 0 || strValue.length === 0) return h("span", {
26
- class: "sc-value sc-value--empty",
59
+ class: SC_CLASSES.valueEmpty,
27
60
  ...ariaReadonlyAttrs()
28
61
  }, "—");
29
62
  const format = props.constraints.format;
30
63
  if (format === "email" && isSafeMailtoAddress(strValue)) return h("a", {
31
- class: "sc-value",
64
+ class: SC_CLASSES.value,
32
65
  href: `mailto:${strValue}`,
33
66
  ...ariaReadonlyAttrs()
34
67
  }, strValue);
35
68
  if ((format === "uri" || format === "url") && isSafeHyperlink(strValue)) return h("a", {
36
- class: "sc-value",
69
+ class: SC_CLASSES.value,
37
70
  href: strValue,
38
71
  ...ariaReadonlyAttrs()
39
72
  }, strValue);
40
73
  return h("span", {
41
- class: "sc-value",
74
+ class: SC_CLASSES.value,
42
75
  ...ariaReadonlyAttrs()
43
76
  }, strValue);
44
77
  }
@@ -48,7 +81,7 @@ function renderStringEditable(props) {
48
81
  const inputType = dateInputType(format) ?? (format === "email" ? "email" : format === "uri" ? "url" : "text");
49
82
  const id = fieldId(props.path);
50
83
  const attrs = {
51
- class: "sc-input",
84
+ class: SC_CLASSES.input,
52
85
  id,
53
86
  type: inputType,
54
87
  name: id
@@ -67,11 +100,11 @@ function renderNumberHtml(props) {
67
100
  }
68
101
  function renderNumberReadOnly(props) {
69
102
  if (typeof props.value !== "number") return h("span", {
70
- class: "sc-value sc-value--empty",
103
+ class: SC_CLASSES.valueEmpty,
71
104
  ...ariaReadonlyAttrs()
72
105
  }, "—");
73
106
  return h("span", {
74
- class: "sc-value",
107
+ class: SC_CLASSES.value,
75
108
  ...ariaReadonlyAttrs()
76
109
  }, props.value.toLocaleString());
77
110
  }
@@ -79,7 +112,7 @@ function renderNumberEditable(props) {
79
112
  const numValue = typeof props.value === "number" ? String(props.value) : "";
80
113
  const id = fieldId(props.path);
81
114
  const attrs = {
82
- class: "sc-input",
115
+ class: SC_CLASSES.input,
83
116
  id,
84
117
  type: "number",
85
118
  name: id
@@ -97,7 +130,7 @@ function renderBooleanHtml(props) {
97
130
  }
98
131
  function renderBooleanReadOnly(props) {
99
132
  if (typeof props.value !== "boolean") return h("span", {
100
- class: "sc-value sc-value--empty",
133
+ class: SC_CLASSES.valueEmpty,
101
134
  ...ariaReadonlyAttrs()
102
135
  }, "—");
103
136
  return h("span", {
@@ -108,7 +141,7 @@ function renderBooleanReadOnly(props) {
108
141
  function renderBooleanEditable(props) {
109
142
  const id = fieldId(props.path);
110
143
  const attrs = {
111
- class: "sc-input",
144
+ class: SC_CLASSES.input,
112
145
  id,
113
146
  type: "checkbox",
114
147
  name: id
@@ -125,11 +158,11 @@ function renderEnumHtml(props) {
125
158
  function renderEnumReadOnly(props) {
126
159
  const enumValue = typeof props.value === "string" ? props.value : "";
127
160
  if (enumValue.length === 0) return h("span", {
128
- class: "sc-value sc-value--empty",
161
+ class: SC_CLASSES.valueEmpty,
129
162
  ...ariaReadonlyAttrs()
130
163
  }, "—");
131
164
  return h("span", {
132
- class: "sc-value",
165
+ class: SC_CLASSES.value,
133
166
  ...ariaReadonlyAttrs()
134
167
  }, enumValue);
135
168
  }
@@ -138,14 +171,14 @@ function renderEnumEditable(props) {
138
171
  const id = fieldId(props.path);
139
172
  const selectedValue = props.writeOnly ? "" : enumValue;
140
173
  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);
174
+ const optionNodes = [h("option", { value: "" }, `Select…`), ...enumValues.map((v) => {
175
+ const display = displayJsonValue(v);
143
176
  const attrs = { value: display };
144
177
  if (display === selectedValue) attrs.selected = true;
145
178
  return h("option", attrs, display);
146
179
  })];
147
180
  const selectAttrs = {
148
- class: "sc-input",
181
+ class: SC_CLASSES.input,
149
182
  id,
150
183
  name: id
151
184
  };
@@ -158,8 +191,7 @@ function renderObjectHtml(props) {
158
191
  function renderObjectNode(props) {
159
192
  if (props.tree.type !== "object") return "";
160
193
  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 : {};
194
+ const obj = isObject(props.value) ? props.value : {};
163
195
  const descriptionText = typeof props.meta.description === "string" ? props.meta.description : void 0;
164
196
  const legend = descriptionText !== void 0 ? h("legend", {}, descriptionText) : void 0;
165
197
  const sortedEntries = sortFieldsByOrder(fields).filter(([, field]) => field.meta.visible !== false);
@@ -170,10 +202,10 @@ function renderObjectNode(props) {
170
202
  const label = typeof field.meta.description === "string" ? field.meta.description : key;
171
203
  const childValue = obj[key];
172
204
  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)));
205
+ children.push(h("dt", { class: SC_CLASSES.label }, label));
206
+ children.push(h("dd", { class: SC_CLASSES.value }, raw(childHtml)));
175
207
  }
176
- const dlAttrs = { class: "sc-object" };
208
+ const dlAttrs = { class: SC_CLASSES.object };
177
209
  Object.assign(dlAttrs, ariaLabelAttrs(descriptionText));
178
210
  return h("dl", dlAttrs, ...children);
179
211
  }
@@ -188,14 +220,14 @@ function renderObjectNode(props) {
188
220
  const labelContent = [label];
189
221
  if (required !== void 0) labelContent.push(required);
190
222
  const fieldChildren = [h("label", {
191
- class: "sc-label",
223
+ class: SC_CLASSES.label,
192
224
  for: fieldId
193
225
  }, ...labelContent), raw(childHtml)];
194
226
  const hint = buildHintElement(fieldId, field.constraints);
195
227
  if (hint !== void 0) fieldChildren.push(hint);
196
- children.push(h("div", { class: "sc-field" }, ...fieldChildren));
228
+ children.push(h("div", { class: SC_CLASSES.field }, ...fieldChildren));
197
229
  }
198
- const fieldsetAttrs = { class: "sc-object" };
230
+ const fieldsetAttrs = { class: SC_CLASSES.object };
199
231
  Object.assign(fieldsetAttrs, ariaLabelAttrs(descriptionText));
200
232
  return h("fieldset", fieldsetAttrs, ...children);
201
233
  }
@@ -207,124 +239,111 @@ function renderArrayNode(props) {
207
239
  const element = props.tree.type === "array" ? props.tree.element : void 0;
208
240
  if (element === void 0) return "";
209
241
  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))));
242
+ if (props.readOnly) {
243
+ const items = childHtmls.map((childHtml) => h("li", { class: "sc-item" }, raw(childHtml)));
244
+ return h("ul", { class: SC_CLASSES.array }, ...items);
245
+ }
246
+ const divItems = childHtmls.map((childHtml) => h("div", {}, raw(childHtml)));
247
+ return h("div", { class: SC_CLASSES.array }, ...divItems);
212
248
  }
213
249
  function renderRecordHtml(props) {
214
250
  return serialize(renderRecordNode(props));
215
251
  }
216
252
  function renderRecordNode(props) {
217
253
  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 : {};
254
+ const obj = isObject(props.value) ? props.value : {};
220
255
  const valueType = props.tree.valueType;
221
256
  const attrs = {
222
- class: "sc-record",
257
+ class: SC_CLASSES.record,
223
258
  role: "group"
224
259
  };
225
260
  if (props.readOnly) {
226
261
  const children = [];
227
262
  for (const [key, val] of Object.entries(obj)) {
228
263
  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)));
264
+ children.push(h("dt", { class: SC_CLASSES.label }, key));
265
+ children.push(h("dd", { class: SC_CLASSES.value }, raw(childHtml)));
231
266
  }
232
267
  return h("dl", attrs, ...children);
233
268
  }
234
269
  const children = [];
235
270
  for (const [key, val] of Object.entries(obj)) {
236
271
  const childHtml = props.renderChild(valueType, val, key);
237
- children.push(h("div", { class: "sc-field" }, h("label", { class: "sc-label" }, key), raw(childHtml)));
272
+ children.push(h("div", { class: SC_CLASSES.field }, h("label", { class: SC_CLASSES.label }, key), raw(childHtml)));
238
273
  }
239
274
  return h("div", attrs, ...children);
240
275
  }
241
276
  function renderLiteralHtml(props) {
242
- if (props.tree.type !== "literal") return serialize(h("span", { class: "sc-value sc-value--empty" }, "—"));
277
+ if (props.tree.type !== "literal") return serialize(h("span", { class: SC_CLASSES.valueEmpty }, "—"));
243
278
  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(", ")));
279
+ if (values.length === 0) return serialize(h("span", { class: SC_CLASSES.valueEmpty }, "—"));
280
+ const display = values.map((v) => displayJsonValue(v)).join(", ");
281
+ return serialize(h("span", { class: SC_CLASSES.value }, display));
246
282
  }
247
283
  function renderUnionHtml(props) {
248
284
  const options = props.tree.type === "union" || props.tree.type === "discriminatedUnion" ? props.tree.options : void 0;
249
285
  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)));
286
+ if (props.value === void 0 || props.value === null) return serialize(h("span", { class: SC_CLASSES.valueEmpty }, "—"));
287
+ return serialize(h("span", { class: SC_CLASSES.value }, JSON.stringify(props.value)));
252
288
  }
253
289
  const matched = matchUnionOption(options, props.value);
254
290
  if (matched !== void 0) return props.renderChild(matched, props.value);
255
291
  const firstOption = options[0];
256
292
  if (firstOption !== void 0) return props.renderChild(firstOption, props.value);
257
- return serialize(h("span", { class: "sc-value sc-value--empty" }, "—"));
293
+ return serialize(h("span", { class: SC_CLASSES.valueEmpty }, "—"));
258
294
  }
259
295
  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)));
296
+ if (props.tree.type !== "discriminatedUnion") {
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)));
265
299
  }
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;
300
+ const { options, discriminator } = props.tree;
301
+ if (options.length === 0) {
302
+ if (props.value === void 0 || props.value === null) return serialize(h("span", { class: SC_CLASSES.valueEmpty }, "—"));
303
+ return serialize(h("span", { class: SC_CLASSES.value }, JSON.stringify(props.value)));
284
304
  }
285
- const activeOption = options[activeIndex];
305
+ const { optionLabels, activeIndex, activeOption } = resolveDiscriminatedActive(options, discriminator, isObject(props.value) ? props.value : void 0);
286
306
  if (props.readOnly) {
287
307
  if (activeOption !== void 0) return props.renderChild(activeOption, props.value);
288
- return serialize(h("span", { class: "sc-value sc-value--empty" }, "—"));
308
+ return serialize(h("span", { class: SC_CLASSES.valueEmpty }, "—"));
289
309
  }
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) => {
310
+ const tabPanelId = panelId(props.path);
311
+ const tabButtons = options.map((_opt, i) => {
298
312
  return h("button", {
299
313
  type: "button",
300
314
  role: "tab",
301
- class: i === activeIndex ? "sc-tab sc-tab--active" : "sc-tab",
302
- id: tabId(i),
315
+ class: i === activeIndex ? SC_CLASSES.tabActive : SC_CLASSES.tab,
316
+ id: tabId(props.path, i),
303
317
  "aria-selected": i === activeIndex ? "true" : void 0,
304
- "aria-controls": panelId,
318
+ "aria-controls": tabPanelId,
305
319
  tabindex: i === activeIndex ? "0" : "-1"
306
320
  }, optionLabels[i]);
307
- }))];
321
+ });
322
+ const children = [h("div", {
323
+ role: "tablist",
324
+ class: SC_CLASSES.tabs,
325
+ "aria-label": "Select variant"
326
+ }, ...tabButtons)];
308
327
  if (activeOption !== void 0) {
309
328
  const childHtml = props.renderChild(activeOption, props.value);
310
329
  children.push(h("div", {
311
330
  role: "tabpanel",
312
- id: panelId,
313
- "aria-labelledby": tabId(activeIndex)
331
+ id: tabPanelId,
332
+ "aria-labelledby": tabId(props.path, activeIndex)
314
333
  }, raw(childHtml)));
315
334
  }
316
- return serialize(h("div", { class: "sc-discriminated-union" }, ...children));
335
+ return serialize(h("div", { class: SC_CLASSES.discriminatedUnion }, ...children));
317
336
  }
318
337
  function renderFileHtml(props) {
319
338
  const id = fieldId(props.path);
320
339
  const accept = props.constraints.mimeTypes?.join(",");
321
340
  if (props.readOnly) return serialize(h("span", {
322
- class: "sc-value",
341
+ class: SC_CLASSES.value,
323
342
  id,
324
343
  ...ariaReadonlyAttrs()
325
344
  }, "File field"));
326
345
  const attrs = {
327
- class: "sc-input",
346
+ class: SC_CLASSES.input,
328
347
  id,
329
348
  type: "file",
330
349
  name: id
@@ -336,15 +355,16 @@ function renderFileHtml(props) {
336
355
  }
337
356
  function renderUnknownHtml(props) {
338
357
  if (props.readOnly) {
339
- if (props.value === void 0 || props.value === null) return serialize(h("span", { class: "sc-value sc-value--empty" }, "—"));
340
- if (typeof props.value === "string") return serialize(h("span", { class: "sc-value" }, props.value));
341
- return serialize(h("span", { class: "sc-value" }, JSON.stringify(props.value)));
358
+ if (props.value === void 0 || props.value === null) return serialize(h("span", { class: SC_CLASSES.valueEmpty }, "—"));
359
+ if (typeof props.value === "string") return serialize(h("span", { class: SC_CLASSES.value }, props.value));
360
+ return serialize(h("span", { class: SC_CLASSES.value }, JSON.stringify(props.value)));
342
361
  }
343
362
  const strValue = typeof props.value === "string" ? props.value : "";
363
+ const name = props.path;
344
364
  const attrs = {
345
- class: "sc-input",
365
+ class: SC_CLASSES.input,
346
366
  type: "text",
347
- name: props.path
367
+ name
348
368
  };
349
369
  if (!props.writeOnly) attrs.value = strValue;
350
370
  return serialize(h("input", attrs));
@@ -360,26 +380,26 @@ function renderTupleHtml(props) {
360
380
  const element = prefixItems[i];
361
381
  if (element === void 0) continue;
362
382
  const childHtml = props.renderChild(element, itemValue, `[${String(i)}]`);
363
- children.push(h("div", { class: "sc-tuple-item" }, h("span", { class: "sc-tuple-index" }, String(i)), raw(childHtml)));
383
+ children.push(h("div", { class: SC_CLASSES.tupleItem }, h("span", { class: SC_CLASSES.tupleIndex }, String(i)), raw(childHtml)));
364
384
  }
365
385
  if (restItems !== void 0) for (let i = prefixItems.length; i < arr.length; i++) {
366
386
  const itemValue = arr[i];
367
387
  const childHtml = props.renderChild(restItems, itemValue, `[${String(i)}]`);
368
- children.push(h("div", { class: "sc-tuple-item sc-tuple-rest" }, h("span", { class: "sc-tuple-index" }, String(i)), raw(childHtml)));
388
+ children.push(h("div", { class: `${SC_CLASSES.tupleItem} ${SC_CLASSES.tupleRest}` }, h("span", { class: SC_CLASSES.tupleIndex }, String(i)), raw(childHtml)));
369
389
  }
370
- return serialize(h("div", { class: "sc-tuple" }, ...children));
390
+ return serialize(h("div", { class: SC_CLASSES.tuple }, ...children));
371
391
  }
372
392
  function renderConditionalHtml(props) {
373
393
  const children = [];
374
394
  if (props.tree.type === "conditional") {
375
- children.push(h("div", { class: "sc-conditional-if" }, raw("if: ...")));
376
- if (props.tree.thenClause !== void 0) children.push(h("div", { class: "sc-conditional-then" }, raw("then: ...")));
377
- if (props.tree.elseClause !== void 0) children.push(h("div", { class: "sc-conditional-else" }, raw("else: ...")));
395
+ children.push(h("div", { class: SC_CLASSES.conditionalIf }, raw("if: ...")));
396
+ if (props.tree.thenClause !== void 0) children.push(h("div", { class: SC_CLASSES.conditionalThen }, raw("then: ...")));
397
+ if (props.tree.elseClause !== void 0) children.push(h("div", { class: SC_CLASSES.conditionalElse }, raw("else: ...")));
378
398
  }
379
- return serialize(h("div", { class: "sc-conditional" }, ...children));
399
+ return serialize(h("div", { class: SC_CLASSES.conditional }, ...children));
380
400
  }
381
401
  function renderNegationHtml(props) {
382
- return serialize(h("div", { class: "sc-negation" }, raw("not: ...")));
402
+ return serialize(h("div", { class: SC_CLASSES.negation }, raw("not: ...")));
383
403
  }
384
404
  /**
385
405
  * Render a null field — `z.null()` or `{ type: "null" }`.
@@ -387,9 +407,10 @@ function renderNegationHtml(props) {
387
407
  * The only valid value is `null`, so render an em-dash placeholder.
388
408
  */
389
409
  function renderNullHtml(props) {
410
+ const id = fieldId(props.path);
390
411
  return serialize(h("span", {
391
- class: "sc-value sc-value--empty",
392
- id: fieldId(props.path),
412
+ class: SC_CLASSES.valueEmpty,
413
+ id,
393
414
  ...ariaReadonlyAttrs()
394
415
  }, "—"));
395
416
  }
@@ -407,13 +428,6 @@ function renderNeverHtml(props) {
407
428
  ...ariaReadonlyAttrs()
408
429
  }, h("em", {}, "never matches")));
409
430
  }
410
- function matchUnionOption(options, value) {
411
- if (typeof value === "string") return options.find((o) => o.type === "string" || o.type === "enum");
412
- if (typeof value === "number") return options.find((o) => o.type === "number");
413
- if (typeof value === "boolean") return options.find((o) => o.type === "boolean");
414
- if (Array.isArray(value)) return options.find((o) => o.type === "array");
415
- if (typeof value === "object" && value !== null) return options.find((o) => o.type === "object");
416
- }
417
431
  const defaultHtmlResolver = {
418
432
  string: renderStringHtml,
419
433
  number: renderNumberHtml,
@@ -434,4 +448,4 @@ const defaultHtmlResolver = {
434
448
  unknown: renderUnknownHtml
435
449
  };
436
450
  //#endregion
437
- export { dateInputType, defaultHtmlResolver, fieldId, matchUnionOption };
451
+ export { dateInputType, defaultHtmlResolver, fieldId, matchUnionOption, panelId, tabId };
@@ -1,6 +1,6 @@
1
- import { j as WalkedField } from "../types-BrRMV0en.mjs";
2
- import { i as DiagnosticsOptions } from "../diagnostics-D0QCYGv0.mjs";
3
- import { o as HtmlResolver } from "../renderer-BaRlQIuN.mjs";
1
+ import { j as WalkedField } from "../types-BTB73MB8.mjs";
2
+ import { i as DiagnosticsOptions } from "../diagnostics-Cbwak-ZX.mjs";
3
+ import { o as HtmlResolver } from "../renderer-CXJ8y0qw.mjs";
4
4
  import { HtmlElement } from "./html.mjs";
5
5
 
6
6
  //#region src/html/streamRenderers.d.ts
@@ -8,7 +8,6 @@ declare function yieldOpen(el: HtmlElement): string;
8
8
  declare function yieldClose(el: HtmlElement): string;
9
9
  declare function renderLeaf(tree: WalkedField, value: unknown, mergedResolver: HtmlResolver, path: string): string;
10
10
  declare function renderFieldSync(tree: WalkedField, value: unknown, mergedResolver: HtmlResolver, path: string, rawResolver: HtmlResolver, currentDepth: number, diagnostics: DiagnosticsOptions | undefined): string;
11
- declare function matchUnionOption(options: WalkedField[], value: unknown): WalkedField | undefined;
12
11
  declare function streamField(tree: WalkedField, value: unknown, mergedResolver: HtmlResolver, path: string, rawResolver: HtmlResolver, currentDepth?: number, diagnostics?: DiagnosticsOptions): Iterable<string, void, undefined>;
13
12
  //#endregion
14
- export { matchUnionOption, renderFieldSync, renderLeaf, streamField, yieldClose, yieldOpen };
13
+ export { renderFieldSync, renderLeaf, streamField, yieldClose, yieldOpen };