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
@@ -6,9 +6,9 @@ import { sortFieldsByOrder } from "../core/fieldOrder.mjs";
6
6
  import { fieldDomId, panelIdFor, tabIdFor } from "../core/idPath.mjs";
7
7
  import { matchUnionOption, resolveDiscriminatedActive } from "../core/unionMatch.mjs";
8
8
  import { displayJsonValue } from "../core/walkBuilders.mjs";
9
- import { buildAriaAttrs } from "./a11y.mjs";
9
+ import { ariaLabel, buildAriaAttrs, buildHintInfo } from "./a11y.mjs";
10
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
10
11
  import { isValidElement, useCallback, useEffect, useRef } from "react";
11
- import { jsx, jsxs } from "react/jsx-runtime";
12
12
  //#region src/react/headlessRenderers.tsx
13
13
  /**
14
14
  * Headless renderer functions — one per schema type.
@@ -61,13 +61,13 @@ function formatTime(value) {
61
61
  function inputId(path) {
62
62
  return fieldDomId(path);
63
63
  }
64
+ /** Headless renderer for `StringField` — plain `<input>` / `<span>`. */
64
65
  function renderString(props) {
65
66
  const id = inputId(props.path);
66
67
  if (props.readOnly) {
67
68
  const strValue = typeof props.value === "string" ? props.value : void 0;
68
69
  if (strValue === void 0 || strValue.length === 0) return /* @__PURE__ */ jsx("span", {
69
70
  id,
70
- "aria-readonly": "true",
71
71
  children: "—"
72
72
  });
73
73
  const format = props.constraints.format;
@@ -85,45 +85,53 @@ function renderString(props) {
85
85
  });
86
86
  if (format === "date") return /* @__PURE__ */ jsx("span", {
87
87
  id,
88
- "aria-readonly": "true",
89
88
  children: formatDate(strValue) ?? strValue
90
89
  });
91
90
  if (format === "time") return /* @__PURE__ */ jsx("span", {
92
91
  id,
93
- "aria-readonly": "true",
94
92
  children: formatTime(strValue) ?? strValue
95
93
  });
96
94
  if (format === "date-time" || format === "datetime") return /* @__PURE__ */ jsx("span", {
97
95
  id,
98
- "aria-readonly": "true",
99
96
  children: formatDateTime(strValue) ?? strValue
100
97
  });
101
98
  return /* @__PURE__ */ jsx("span", {
102
99
  id,
103
- "aria-readonly": "true",
104
100
  children: strValue
105
101
  });
106
102
  }
107
103
  const strValue = typeof props.value === "string" ? props.value : "";
108
104
  const dateType = dateInputType(props.constraints.format);
109
105
  const ariaAttrs = buildAriaAttrs(props.tree);
110
- if (dateType !== void 0) return /* @__PURE__ */ jsx("input", {
111
- id,
112
- type: dateType,
113
- value: props.writeOnly ? "" : strValue,
114
- onChange: (e) => {
115
- props.onChange(e.target.value);
116
- },
117
- ...ariaAttrs
106
+ const hintInfo = buildHintInfo(id, props.constraints);
107
+ const renderHint = () => hintInfo === void 0 ? null : /* @__PURE__ */ jsx("small", {
108
+ id: hintInfo.id,
109
+ className: "sc-hint",
110
+ children: hintInfo.hint
118
111
  });
112
+ if (dateType !== void 0) {
113
+ const dateInput = /* @__PURE__ */ jsx("input", {
114
+ id,
115
+ type: dateType,
116
+ value: props.writeOnly ? "" : strValue,
117
+ onChange: (e) => {
118
+ props.onChange(e.target.value);
119
+ },
120
+ "aria-describedby": hintInfo?.ariaDescribedBy,
121
+ ...ariaAttrs
122
+ });
123
+ if (hintInfo === void 0) return dateInput;
124
+ return /* @__PURE__ */ jsxs(Fragment, { children: [dateInput, renderHint()] });
125
+ }
119
126
  if (props.tree.type === "enum" && props.tree.enumValues.length > 0) {
120
127
  const enumValues = props.tree.enumValues;
121
- return /* @__PURE__ */ jsxs("select", {
128
+ const select = /* @__PURE__ */ jsxs("select", {
122
129
  id,
123
130
  value: strValue,
124
131
  onChange: (e) => {
125
132
  props.onChange(e.target.value);
126
133
  },
134
+ "aria-describedby": hintInfo?.ariaDescribedBy,
127
135
  ...ariaAttrs,
128
136
  children: [/* @__PURE__ */ jsxs("option", {
129
137
  value: "",
@@ -136,10 +144,14 @@ function renderString(props) {
136
144
  }, display);
137
145
  })]
138
146
  });
147
+ if (hintInfo === void 0) return select;
148
+ return /* @__PURE__ */ jsxs(Fragment, { children: [select, renderHint()] });
139
149
  }
140
- return /* @__PURE__ */ jsx("input", {
150
+ const isCredential = props.writeOnly && props.constraints.format === "password";
151
+ const input = /* @__PURE__ */ jsx("input", {
141
152
  id,
142
- type: props.constraints.format === "email" ? "email" : props.constraints.format === "uri" ? "url" : "text",
153
+ type: isCredential ? "password" : props.constraints.format === "email" ? "email" : props.constraints.format === "uri" ? "url" : "text",
154
+ autoComplete: isCredential ? strValue.length > 0 ? "current-password" : "new-password" : void 0,
143
155
  value: props.writeOnly ? "" : strValue,
144
156
  onChange: (e) => {
145
157
  props.onChange(e.target.value);
@@ -147,48 +159,62 @@ function renderString(props) {
147
159
  placeholder: typeof props.meta.description === "string" ? props.meta.description : void 0,
148
160
  minLength: props.constraints.minLength,
149
161
  maxLength: props.constraints.maxLength,
162
+ "aria-describedby": hintInfo?.ariaDescribedBy,
150
163
  ...ariaAttrs
151
164
  });
165
+ if (hintInfo === void 0) return input;
166
+ return /* @__PURE__ */ jsxs(Fragment, { children: [input, renderHint()] });
152
167
  }
168
+ /** Headless renderer for `NumberField` — plain `<input type="number">`. */
153
169
  function renderNumber(props) {
154
170
  const id = inputId(props.path);
155
171
  if (props.readOnly) {
156
172
  if (typeof props.value !== "number") return /* @__PURE__ */ jsx("span", {
157
173
  id,
158
- "aria-readonly": "true",
159
174
  children: "—"
160
175
  });
161
176
  return /* @__PURE__ */ jsx("span", {
162
177
  id,
163
- "aria-readonly": "true",
164
178
  children: props.value.toLocaleString()
165
179
  });
166
180
  }
167
181
  const numValue = typeof props.value === "number" ? props.value : "";
168
182
  const ariaAttrs = buildAriaAttrs(props.tree);
169
- return /* @__PURE__ */ jsx("input", {
183
+ const hintInfo = buildHintInfo(id, props.constraints);
184
+ const isInteger = props.tree.type === "number" ? props.tree.isInteger : false;
185
+ const inputMode = isInteger ? "numeric" : "decimal";
186
+ const multipleOf = props.constraints.multipleOf;
187
+ const numberInput = /* @__PURE__ */ jsx("input", {
170
188
  id,
171
189
  type: "number",
190
+ inputMode,
191
+ step: multipleOf !== void 0 ? String(multipleOf) : isInteger ? "1" : void 0,
172
192
  value: props.writeOnly ? "" : numValue,
173
193
  onChange: (e) => {
174
194
  props.onChange(Number(e.target.value));
175
195
  },
176
196
  min: props.constraints.minimum,
177
197
  max: props.constraints.maximum,
198
+ "aria-describedby": hintInfo?.ariaDescribedBy,
178
199
  ...ariaAttrs
179
200
  });
201
+ if (hintInfo === void 0) return numberInput;
202
+ return /* @__PURE__ */ jsxs(Fragment, { children: [numberInput, /* @__PURE__ */ jsx("small", {
203
+ id: hintInfo.id,
204
+ className: "sc-hint",
205
+ children: hintInfo.hint
206
+ })] });
180
207
  }
208
+ /** Headless renderer for `BooleanField` — plain `<input type="checkbox">`. */
181
209
  function renderBoolean(props) {
182
210
  const id = inputId(props.path);
183
211
  if (props.readOnly) {
184
212
  if (typeof props.value !== "boolean") return /* @__PURE__ */ jsx("span", {
185
213
  id,
186
- "aria-readonly": "true",
187
214
  children: "—"
188
215
  });
189
216
  return /* @__PURE__ */ jsx("span", {
190
217
  id,
191
- "aria-readonly": "true",
192
218
  children: props.value ? "Yes" : "No"
193
219
  });
194
220
  }
@@ -203,22 +229,24 @@ function renderBoolean(props) {
203
229
  ...ariaAttrs
204
230
  });
205
231
  }
232
+ /** Headless renderer for `EnumField` — plain `<select>` listing each option. */
206
233
  function renderEnum(props) {
207
234
  const id = inputId(props.path);
208
235
  const enumValue = typeof props.value === "string" ? props.value : "";
209
236
  if (props.readOnly) return /* @__PURE__ */ jsx("span", {
210
237
  id,
211
- "aria-readonly": "true",
212
238
  children: enumValue || "—"
213
239
  });
214
240
  const ariaAttrs = buildAriaAttrs(props.tree);
241
+ const hintInfo = buildHintInfo(id, props.constraints);
215
242
  const enumValues = props.tree.type === "enum" ? props.tree.enumValues : [];
216
- return /* @__PURE__ */ jsxs("select", {
243
+ const select = /* @__PURE__ */ jsxs("select", {
217
244
  id,
218
245
  value: props.writeOnly ? "" : enumValue,
219
246
  onChange: (e) => {
220
247
  props.onChange(e.target.value);
221
248
  },
249
+ "aria-describedby": hintInfo?.ariaDescribedBy,
222
250
  ...ariaAttrs,
223
251
  children: [/* @__PURE__ */ jsxs("option", {
224
252
  value: "",
@@ -231,7 +259,14 @@ function renderEnum(props) {
231
259
  }, display);
232
260
  })]
233
261
  });
262
+ if (hintInfo === void 0) return select;
263
+ return /* @__PURE__ */ jsxs(Fragment, { children: [select, /* @__PURE__ */ jsx("small", {
264
+ id: hintInfo.id,
265
+ className: "sc-hint",
266
+ children: hintInfo.hint
267
+ })] });
234
268
  }
269
+ /** Headless renderer for `ObjectField` — `<fieldset>` per object with one child per property. */
235
270
  function renderObject(props) {
236
271
  if (props.tree.type !== "object") return null;
237
272
  const obj = isObject(props.value) ? props.value : {};
@@ -248,9 +283,9 @@ function renderObject(props) {
248
283
  };
249
284
  const child = toReactNode(props.renderChild(field, childValue, childOnChange, key));
250
285
  if (child === null || child === void 0) return null;
251
- return /* @__PURE__ */ jsxs("div", { children: [typeof field.meta.description === "string" && /* @__PURE__ */ jsxs("label", {
286
+ return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("label", {
252
287
  htmlFor: childId,
253
- children: [field.meta.description, field.isOptional === false && /* @__PURE__ */ jsxs("span", {
288
+ children: [typeof field.meta.description === "string" ? field.meta.description : key, field.isOptional === false && /* @__PURE__ */ jsxs("span", {
254
289
  "aria-hidden": "true",
255
290
  style: { color: "#dc2626" },
256
291
  children: [" ", "*"]
@@ -272,7 +307,17 @@ function defaultRecordValue(valueType) {
272
307
  case "array": return [];
273
308
  case "object":
274
309
  case "record": return {};
275
- default: return;
310
+ case "null": return null;
311
+ case "unknown":
312
+ case "enum":
313
+ case "literal":
314
+ case "tuple":
315
+ case "union":
316
+ case "discriminatedUnion":
317
+ case "conditional":
318
+ case "negation":
319
+ case "file":
320
+ case "never": return;
276
321
  }
277
322
  }
278
323
  /**
@@ -297,19 +342,17 @@ function renameRecordKey(obj, oldKey, newKey) {
297
342
  for (const [k, v] of Object.entries(obj)) renamed[k === oldKey ? newKey : k] = v;
298
343
  return renamed;
299
344
  }
345
+ /** Headless renderer for `RecordField` — editable key/value rows with add/remove controls. */
300
346
  function renderRecord(props) {
301
347
  if (props.tree.type !== "record") return null;
302
348
  const obj = isObject(props.value) ? props.value : {};
303
349
  const valueType = props.tree.valueType;
304
350
  const entries = Object.entries(obj);
305
351
  if (props.readOnly) {
306
- if (entries.length === 0) return /* @__PURE__ */ jsx("span", {
307
- "aria-readonly": "true",
308
- children: "—"
309
- });
352
+ if (entries.length === 0) return /* @__PURE__ */ jsx("span", { children: "—" });
310
353
  return /* @__PURE__ */ jsx("div", {
311
354
  role: "group",
312
- "aria-label": typeof props.meta.description === "string" ? props.meta.description : void 0,
355
+ "aria-label": ariaLabel(props.meta.description),
313
356
  children: entries.map(([key, value]) => {
314
357
  return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("label", {
315
358
  htmlFor: inputId(`${props.path}.${key}`),
@@ -345,7 +388,7 @@ function renderRecord(props) {
345
388
  };
346
389
  return /* @__PURE__ */ jsxs("div", {
347
390
  role: "group",
348
- "aria-label": props.meta.description ?? "Record",
391
+ "aria-label": ariaLabel(props.meta.description),
349
392
  children: [entries.map(([key, value]) => {
350
393
  return /* @__PURE__ */ jsxs("div", { children: [
351
394
  /* @__PURE__ */ jsx("input", {
@@ -377,25 +420,56 @@ function renderRecord(props) {
377
420
  })]
378
421
  });
379
422
  }
423
+ /** Headless renderer for `ArrayField` — ordered list with add/remove controls. */
380
424
  function renderArray(props) {
381
425
  if (props.tree.type !== "array") return null;
382
426
  const arr = Array.isArray(props.value) ? props.value : [];
383
427
  const element = props.tree.element;
384
428
  if (element === void 0) return null;
385
- if (arr.length === 0) return null;
386
- return /* @__PURE__ */ jsx("div", {
429
+ if (props.readOnly) {
430
+ if (arr.length === 0) return null;
431
+ return /* @__PURE__ */ jsx("ul", {
432
+ role: "group",
433
+ "aria-label": ariaLabel(props.meta.description),
434
+ children: arr.map((item, i) => /* @__PURE__ */ jsx("li", { children: toReactNode(props.renderChild(element, item, () => {}, `[${String(i)}]`)) }, String(i)))
435
+ });
436
+ }
437
+ const handleRemove = (index) => {
438
+ const next = arr.slice();
439
+ next.splice(index, 1);
440
+ props.onChange(next);
441
+ };
442
+ const handleAdd = () => {
443
+ const next = arr.slice();
444
+ next.push(defaultRecordValue(element));
445
+ props.onChange(next);
446
+ };
447
+ return /* @__PURE__ */ jsxs("div", {
387
448
  role: "group",
388
- "aria-label": props.meta.description ?? void 0,
389
- children: arr.map((item, i) => {
449
+ "aria-label": ariaLabel(props.meta.description),
450
+ children: [/* @__PURE__ */ jsx("ul", { children: arr.map((item, i) => {
390
451
  const childOnChange = (v) => {
391
- const next = arr.slice();
392
- next[i] = v;
393
- props.onChange(next);
452
+ const nextArr = arr.slice();
453
+ nextArr[i] = v;
454
+ props.onChange(nextArr);
394
455
  };
395
- return /* @__PURE__ */ jsx("div", { children: toReactNode(props.renderChild(element, item, childOnChange, `[${String(i)}]`)) }, String(i));
396
- })
456
+ return /* @__PURE__ */ jsxs("li", { children: [toReactNode(props.renderChild(element, item, childOnChange, `[${String(i)}]`)), /* @__PURE__ */ jsx("button", {
457
+ type: "button",
458
+ "aria-label": `Remove item ${String(i)}`,
459
+ onClick: () => {
460
+ handleRemove(i);
461
+ },
462
+ children: "Remove"
463
+ })] }, String(i));
464
+ }) }), /* @__PURE__ */ jsx("button", {
465
+ type: "button",
466
+ "aria-label": "Add item",
467
+ onClick: handleAdd,
468
+ children: "Add"
469
+ })]
397
470
  });
398
471
  }
472
+ /** Headless renderer for plain `UnionField` — picks the matching option and renders it. */
399
473
  function renderUnion(props) {
400
474
  const options = props.tree.type === "union" || props.tree.type === "discriminatedUnion" ? props.tree.options : void 0;
401
475
  if (options === void 0 || options.length === 0) {
@@ -408,6 +482,7 @@ function renderUnion(props) {
408
482
  if (firstOption !== void 0) return toReactNode(props.renderChild(firstOption, props.value, props.onChange));
409
483
  return /* @__PURE__ */ jsx("span", { children: "—" });
410
484
  }
485
+ /** Headless renderer for `DiscriminatedUnionField` — tabbed UI driven by the discriminator. */
411
486
  function renderDiscriminatedUnion(props) {
412
487
  if (props.tree.type !== "discriminatedUnion") {
413
488
  if (props.value === void 0 || props.value === null) return /* @__PURE__ */ jsx("span", { children: "—" });
@@ -497,6 +572,7 @@ function DiscriminatedUnionTabs({ options, optionLabels, activeIndex, path, disc
497
572
  return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", {
498
573
  role: "tablist",
499
574
  "aria-label": "Select variant",
575
+ "aria-orientation": "horizontal",
500
576
  style: {
501
577
  display: "flex",
502
578
  gap: "0.25rem",
@@ -510,7 +586,7 @@ function DiscriminatedUnionTabs({ options, optionLabels, activeIndex, path, disc
510
586
  type: "button",
511
587
  role: "tab",
512
588
  id: tabIdFor(path, i),
513
- "aria-selected": i === activeIndex ? "true" : void 0,
589
+ "aria-selected": i === activeIndex ? "true" : "false",
514
590
  "aria-controls": panelId,
515
591
  tabIndex: i === activeIndex ? 0 : -1,
516
592
  onClick: () => {
@@ -533,15 +609,17 @@ function DiscriminatedUnionTabs({ options, optionLabels, activeIndex, path, disc
533
609
  children: activeOption !== void 0 && toReactNode(props.renderChild(activeOption, props.value, props.onChange))
534
610
  })] });
535
611
  }
612
+ /** Headless renderer for `FileField` — plain `<input type="file">`. */
536
613
  function renderFile(props) {
537
614
  const id = inputId(props.path);
538
615
  const accept = props.constraints.mimeTypes?.join(",");
539
616
  if (props.readOnly) return /* @__PURE__ */ jsx("span", {
540
617
  id,
541
- "aria-readonly": "true",
542
618
  children: "File field"
543
619
  });
544
- return /* @__PURE__ */ jsx("input", {
620
+ const ariaAttrs = buildAriaAttrs(props.tree, props.meta.description);
621
+ const hintInfo = buildHintInfo(id, props.constraints);
622
+ const fileInput = /* @__PURE__ */ jsx("input", {
545
623
  id,
546
624
  type: "file",
547
625
  accept,
@@ -549,8 +627,15 @@ function renderFile(props) {
549
627
  const file = e.target.files?.[0];
550
628
  if (file !== void 0) props.onChange(file);
551
629
  },
552
- ...buildAriaAttrs(props.tree, props.meta.description)
630
+ "aria-describedby": hintInfo?.ariaDescribedBy,
631
+ ...ariaAttrs
553
632
  });
633
+ if (hintInfo === void 0) return fileInput;
634
+ return /* @__PURE__ */ jsxs(Fragment, { children: [fileInput, /* @__PURE__ */ jsx("small", {
635
+ id: hintInfo.id,
636
+ className: "sc-hint",
637
+ children: hintInfo.hint
638
+ })] });
554
639
  }
555
640
  /**
556
641
  * Render a literal field — `z.literal("a")` or `{ const: 5 }`.
@@ -565,12 +650,10 @@ function renderLiteral(props) {
565
650
  const values = props.tree.literalValues;
566
651
  if (values.length === 0) return /* @__PURE__ */ jsx("span", {
567
652
  id,
568
- "aria-readonly": "true",
569
653
  children: "—"
570
654
  });
571
655
  return /* @__PURE__ */ jsx("span", {
572
656
  id,
573
- "aria-readonly": "true",
574
657
  children: values.map((v) => displayJsonValue(v)).join(", ")
575
658
  });
576
659
  }
@@ -583,7 +666,6 @@ function renderLiteral(props) {
583
666
  function renderNull(props) {
584
667
  return /* @__PURE__ */ jsx("span", {
585
668
  id: inputId(props.path),
586
- "aria-readonly": "true",
587
669
  children: "—"
588
670
  });
589
671
  }
@@ -599,7 +681,6 @@ function renderNull(props) {
599
681
  function renderNever(props) {
600
682
  return /* @__PURE__ */ jsx("span", {
601
683
  id: inputId(props.path),
602
- "aria-readonly": "true",
603
684
  className: SC_CLASSES.never,
604
685
  children: /* @__PURE__ */ jsx("em", { children: "never matches" })
605
686
  });
@@ -621,7 +702,7 @@ function renderTuple(props) {
621
702
  const restCount = restItems !== void 0 ? Math.max(arr.length - prefixItems.length, 0) : 0;
622
703
  return /* @__PURE__ */ jsxs("div", {
623
704
  role: "group",
624
- "aria-label": props.meta.description ?? void 0,
705
+ "aria-label": ariaLabel(props.meta.description),
625
706
  children: [prefixItems.map((element, i) => {
626
707
  const itemValue = arr[i];
627
708
  const childOnChange = (v) => {
@@ -701,17 +782,16 @@ function renderNegation(props) {
701
782
  ]
702
783
  });
703
784
  }
785
+ /** Headless renderer for `UnknownField` — JSON-encoded fallback for unconstrained values. */
704
786
  function renderUnknown(props) {
705
787
  const id = inputId(props.path);
706
788
  if (props.readOnly) {
707
789
  if (props.value === void 0 || props.value === null) return /* @__PURE__ */ jsx("span", {
708
790
  id,
709
- "aria-readonly": "true",
710
791
  children: "—"
711
792
  });
712
793
  return /* @__PURE__ */ jsx("span", {
713
794
  id,
714
- "aria-readonly": "true",
715
795
  children: typeof props.value === "string" ? props.value : JSON.stringify(props.value)
716
796
  });
717
797
  }
@@ -1,4 +1,4 @@
1
- import { i as DiagnosticsOptions } from "./diagnostics-Cbwak-ZX.mjs";
1
+ import { i as DiagnosticsOptions } from "./diagnostics-BTrm3O6J.mjs";
2
2
 
3
3
  //#region src/core/ref.d.ts
4
4
  /**
@@ -12,6 +12,38 @@ declare const RECURSIVE_ANCHOR_SENTINEL = "__recursive__";
12
12
  * Resolver function for external $ref URIs.
13
13
  * Called with the URI portion (everything before `#`) of an external ref.
14
14
  * Returns the parsed document (JSON object) or undefined.
15
+ *
16
+ * ### Security warning — SSRF and local-file disclosure
17
+ *
18
+ * Consumers MUST validate the URI before fetching the target document.
19
+ * schema-components hands the resolver the raw `$ref` URI from the
20
+ * document — which is typically user-controlled — and any network or
21
+ * filesystem access the resolver performs runs with the host
22
+ * application's full privileges. An attacker-crafted schema that
23
+ * references an internal endpoint or a local filesystem path will
24
+ * happily exfiltrate or expose data the application never intended to
25
+ * surface.
26
+ *
27
+ * At a minimum the resolver should:
28
+ *
29
+ * - Refuse non-`https:` schemes by default. Permit `http:` only on an
30
+ * explicit allow-list. Refuse `file:`, `data:`, `javascript:`,
31
+ * `ftp:`, `gopher:`, and every other scheme outright.
32
+ * - Resolve the URI's hostname and refuse loopback addresses
33
+ * (`127.0.0.0/8`, `::1`), link-local addresses (`169.254.0.0/16`,
34
+ * `fe80::/10`), private ranges (`10.0.0.0/8`, `172.16.0.0/12`,
35
+ * `192.168.0.0/16`, `fc00::/7`), and cloud-metadata IPs
36
+ * (`169.254.169.254`, `fd00:ec2::254`).
37
+ * - Apply a strict allow-list of permitted hosts where possible.
38
+ * - Set request timeouts and a maximum response size.
39
+ * - Disable HTTP redirects, or re-validate the redirected URL against
40
+ * the same denylist before following.
41
+ * - Reject responses that are not `application/json` or
42
+ * `application/yaml`.
43
+ *
44
+ * schema-components performs no validation itself — that responsibility
45
+ * sits exclusively with the resolver implementation supplied by the
46
+ * caller.
15
47
  */
16
48
  type ExternalResolver = (uri: string) => unknown;
17
49
  /**
@@ -1,22 +1,64 @@
1
- import { r as ComponentResolver } from "../renderer-Ul9taFYp.mjs";
1
+ import { ComponentResolver } from "../core/renderer.mjs";
2
+ import { ElementType } from "react";
2
3
 
3
4
  //#region src/themes/mantine.d.ts
4
5
  /**
5
- * Register real Mantine components for the resolver to use.
6
- * Call once at app startup before rendering.
6
+ * Element types the Mantine resolver renders into. Every slot is the
7
+ * Mantine component the corresponding render function expects to find
8
+ * at the call site; consumers wire these in once via
9
+ * `createMantineResolver`.
7
10
  *
8
11
  * `Text` is required so read-only scalars render as a styled Mantine
9
12
  * `<Text>` element instead of a bare `<span>`, matching the visual
10
13
  * weight of the editable variants.
11
14
  */
12
- declare function registerMantineComponents(components: {
13
- TextInput: React.ElementType;
14
- NumberInput: React.ElementType;
15
- Switch: React.ElementType;
16
- Select: React.ElementType;
17
- Fieldset: React.ElementType;
18
- Text: React.ElementType;
19
- }): void;
15
+ interface MantineComponents {
16
+ TextInput: ElementType;
17
+ NumberInput: ElementType;
18
+ Switch: ElementType;
19
+ Select: ElementType;
20
+ Fieldset: ElementType;
21
+ Text: ElementType;
22
+ }
23
+ /**
24
+ * Build a Mantine-flavoured {@link ComponentResolver} bound to the
25
+ * supplied element types. Each render function captures the supplied
26
+ * components in a closure so two consumers can build different
27
+ * resolvers from the same package without leaking element types
28
+ * through module-level mutable state.
29
+ *
30
+ * Returns only the keys this theme actually overrides. The runtime
31
+ * `mergeResolvers` call inside `<SchemaComponent>` / `<SchemaView>`
32
+ * fills unset keys from `headlessResolver`, so variants this adapter
33
+ * leaves unset (literal, union, discriminatedUnion, array, record,
34
+ * file, unknown, …) still render via the headless fallback.
35
+ *
36
+ * @group Themes
37
+ */
38
+ declare function createMantineResolver(components: MantineComponents): ComponentResolver;
39
+ /**
40
+ * Component resolver mapping schema field types to Mantine primitives —
41
+ * `TextInput`, `NumberInput`, `Switch`, `Select`, `Fieldset`, `Text`.
42
+ *
43
+ * Built against minimal HTML stubs so the resolver is usable without
44
+ * wiring up `@mantine/core` first — production usage should call
45
+ * {@link createMantineResolver} with real Mantine element types.
46
+ *
47
+ * @group Themes
48
+ * @example
49
+ * ```tsx
50
+ * import { TextInput, NumberInput, Switch, Select, Fieldset, Text } from "@mantine/core";
51
+ * import { createMantineResolver } from "schema-components/themes/mantine";
52
+ *
53
+ * const mantineResolver = createMantineResolver({
54
+ * TextInput, NumberInput, Switch, Select, Fieldset, Text,
55
+ * });
56
+ *
57
+ * <SchemaProvider resolver={mantineResolver}>
58
+ * <SchemaComponent schema={userSchema} value={user} onChange={setUser} />
59
+ * </SchemaProvider>
60
+ * ```
61
+ */
20
62
  declare const mantineResolver: ComponentResolver;
21
63
  //#endregion
22
- export { mantineResolver, registerMantineComponents };
64
+ export { MantineComponents, createMantineResolver, mantineResolver };