schema-components 1.29.0 → 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 (90) hide show
  1. package/README.md +38 -16
  2. package/dist/core/adapter.d.mts +213 -3
  3. package/dist/core/adapter.mjs +1 -1
  4. package/dist/core/constraintHint.d.mts +15 -0
  5. package/dist/core/constraintHint.mjs +24 -0
  6. package/dist/core/constraints.d.mts +2 -2
  7. package/dist/core/constraints.mjs +1 -1
  8. package/dist/core/diagnostics.d.mts +1 -1
  9. package/dist/core/errors.d.mts +1 -1
  10. package/dist/core/fieldOrder.d.mts +1 -1
  11. package/dist/core/formats.d.mts +1 -1
  12. package/dist/core/idPath.d.mts +35 -5
  13. package/dist/core/idPath.mjs +79 -7
  14. package/dist/core/inferValue.d.mts +2 -0
  15. package/dist/core/inferValue.mjs +1 -0
  16. package/dist/core/limits.d.mts +1 -1
  17. package/dist/core/limits.mjs +5 -0
  18. package/dist/core/merge.d.mts +12 -1
  19. package/dist/core/merge.mjs +66 -3
  20. package/dist/core/normalise.d.mts +1 -1
  21. package/dist/core/normalise.mjs +1 -1
  22. package/dist/core/openapi30.mjs +1 -1
  23. package/dist/core/ref.d.mts +1 -1
  24. package/dist/core/refChain.d.mts +3 -4
  25. package/dist/core/refChain.mjs +2 -3
  26. package/dist/core/renderer.d.mts +199 -2
  27. package/dist/core/swagger2.d.mts +1 -1
  28. package/dist/core/swagger2.mjs +1 -1
  29. package/dist/core/typeInference.d.mts +3 -3
  30. package/dist/core/types.d.mts +1 -1
  31. package/dist/core/unionMatch.d.mts +1 -1
  32. package/dist/core/uri.d.mts +12 -4
  33. package/dist/core/uri.mjs +30 -4
  34. package/dist/core/walkBuilders.d.mts +18 -6
  35. package/dist/core/walkBuilders.mjs +3 -1
  36. package/dist/core/walker.d.mts +1 -1
  37. package/dist/core/walker.mjs +5 -0
  38. package/dist/{diagnostics-ByEzkjrA.d.mts → diagnostics-BTrm3O6J.d.mts} +1 -1
  39. package/dist/{errors-D8JndRwI.d.mts → errors-Dki7tji4.d.mts} +1 -1
  40. package/dist/html/a11y.d.mts +3 -7
  41. package/dist/html/a11y.mjs +1 -16
  42. package/dist/html/renderToHtml.d.mts +22 -9
  43. package/dist/html/renderToHtml.mjs +2 -1
  44. package/dist/html/renderToHtmlStream.d.mts +24 -11
  45. package/dist/html/renderToHtmlStream.mjs +2 -1
  46. package/dist/html/renderers.d.mts +2 -33
  47. package/dist/html/renderers.mjs +39 -91
  48. package/dist/html/streamRenderers.d.mts +3 -3
  49. package/dist/html/streamRenderers.mjs +13 -8
  50. package/dist/inferValue-PPXWJpbN.d.mts +77 -0
  51. package/dist/{limits-DswmqWuy.d.mts → limits-x4OiyJxh.d.mts} +5 -0
  52. package/dist/{normalise-Db1xaxgx.mjs → normalise-DB-Xtjmn.mjs} +43 -2
  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/ApiSecurity.mjs +21 -8
  58. package/dist/openapi/bundle.d.mts +31 -0
  59. package/dist/openapi/components.d.mts +41 -10
  60. package/dist/openapi/components.mjs +19 -13
  61. package/dist/openapi/parser.d.mts +13 -13
  62. package/dist/openapi/parser.mjs +12 -12
  63. package/dist/openapi/resolve.d.mts +38 -49
  64. package/dist/openapi/resolve.mjs +62 -56
  65. package/dist/react/SchemaComponent.d.mts +19 -95
  66. package/dist/react/SchemaComponent.mjs +12 -1
  67. package/dist/react/SchemaView.d.mts +11 -7
  68. package/dist/react/SchemaView.mjs +3 -1
  69. package/dist/react/a11y.d.mts +74 -7
  70. package/dist/react/a11y.mjs +67 -6
  71. package/dist/react/fieldPath.d.mts +16 -1
  72. package/dist/react/fieldPath.mjs +25 -1
  73. package/dist/react/fieldShell.d.mts +49 -0
  74. package/dist/react/fieldShell.mjs +37 -0
  75. package/dist/react/headless.d.mts +1 -1
  76. package/dist/react/headlessRenderers.d.mts +2 -2
  77. package/dist/react/headlessRenderers.mjs +123 -54
  78. package/dist/{ref-CPh8rKQ3.d.mts → ref-DdsbekXX.d.mts} +33 -1
  79. package/dist/themes/mantine.d.mts +36 -20
  80. package/dist/themes/mantine.mjs +179 -150
  81. package/dist/themes/mui.d.mts +47 -21
  82. package/dist/themes/mui.mjs +259 -222
  83. package/dist/themes/radix.d.mts +38 -23
  84. package/dist/themes/radix.mjs +208 -180
  85. package/dist/themes/shadcn.d.mts +6 -3
  86. package/dist/themes/shadcn.mjs +93 -93
  87. package/dist/{types-C2Ay1FEh.d.mts → types-BrYbjC7_.d.mts} +7 -0
  88. package/package.json +4 -1
  89. package/dist/adapter-DcWi4XXn.d.mts +0 -223
  90. package/dist/renderer-OaOz-n6-.d.mts +0 -185
@@ -6,8 +6,8 @@ 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";
10
- import { jsx, jsxs } from "react/jsx-runtime";
9
+ import { ariaLabel, buildAriaAttrs, buildHintInfo } from "./a11y.mjs";
10
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
11
11
  import { isValidElement, useCallback, useEffect, useRef } from "react";
12
12
  //#region src/react/headlessRenderers.tsx
13
13
  /**
@@ -68,7 +68,6 @@ function renderString(props) {
68
68
  const strValue = typeof props.value === "string" ? props.value : void 0;
69
69
  if (strValue === void 0 || strValue.length === 0) return /* @__PURE__ */ jsx("span", {
70
70
  id,
71
- "aria-readonly": "true",
72
71
  children: "—"
73
72
  });
74
73
  const format = props.constraints.format;
@@ -86,45 +85,53 @@ function renderString(props) {
86
85
  });
87
86
  if (format === "date") return /* @__PURE__ */ jsx("span", {
88
87
  id,
89
- "aria-readonly": "true",
90
88
  children: formatDate(strValue) ?? strValue
91
89
  });
92
90
  if (format === "time") return /* @__PURE__ */ jsx("span", {
93
91
  id,
94
- "aria-readonly": "true",
95
92
  children: formatTime(strValue) ?? strValue
96
93
  });
97
94
  if (format === "date-time" || format === "datetime") return /* @__PURE__ */ jsx("span", {
98
95
  id,
99
- "aria-readonly": "true",
100
96
  children: formatDateTime(strValue) ?? strValue
101
97
  });
102
98
  return /* @__PURE__ */ jsx("span", {
103
99
  id,
104
- "aria-readonly": "true",
105
100
  children: strValue
106
101
  });
107
102
  }
108
103
  const strValue = typeof props.value === "string" ? props.value : "";
109
104
  const dateType = dateInputType(props.constraints.format);
110
105
  const ariaAttrs = buildAriaAttrs(props.tree);
111
- if (dateType !== void 0) return /* @__PURE__ */ jsx("input", {
112
- id,
113
- type: dateType,
114
- value: props.writeOnly ? "" : strValue,
115
- onChange: (e) => {
116
- props.onChange(e.target.value);
117
- },
118
- ...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
119
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
+ }
120
126
  if (props.tree.type === "enum" && props.tree.enumValues.length > 0) {
121
127
  const enumValues = props.tree.enumValues;
122
- return /* @__PURE__ */ jsxs("select", {
128
+ const select = /* @__PURE__ */ jsxs("select", {
123
129
  id,
124
130
  value: strValue,
125
131
  onChange: (e) => {
126
132
  props.onChange(e.target.value);
127
133
  },
134
+ "aria-describedby": hintInfo?.ariaDescribedBy,
128
135
  ...ariaAttrs,
129
136
  children: [/* @__PURE__ */ jsxs("option", {
130
137
  value: "",
@@ -137,10 +144,14 @@ function renderString(props) {
137
144
  }, display);
138
145
  })]
139
146
  });
147
+ if (hintInfo === void 0) return select;
148
+ return /* @__PURE__ */ jsxs(Fragment, { children: [select, renderHint()] });
140
149
  }
141
- return /* @__PURE__ */ jsx("input", {
150
+ const isCredential = props.writeOnly && props.constraints.format === "password";
151
+ const input = /* @__PURE__ */ jsx("input", {
142
152
  id,
143
- 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,
144
155
  value: props.writeOnly ? "" : strValue,
145
156
  onChange: (e) => {
146
157
  props.onChange(e.target.value);
@@ -148,8 +159,11 @@ function renderString(props) {
148
159
  placeholder: typeof props.meta.description === "string" ? props.meta.description : void 0,
149
160
  minLength: props.constraints.minLength,
150
161
  maxLength: props.constraints.maxLength,
162
+ "aria-describedby": hintInfo?.ariaDescribedBy,
151
163
  ...ariaAttrs
152
164
  });
165
+ if (hintInfo === void 0) return input;
166
+ return /* @__PURE__ */ jsxs(Fragment, { children: [input, renderHint()] });
153
167
  }
154
168
  /** Headless renderer for `NumberField` — plain `<input type="number">`. */
155
169
  function renderNumber(props) {
@@ -157,28 +171,39 @@ function renderNumber(props) {
157
171
  if (props.readOnly) {
158
172
  if (typeof props.value !== "number") return /* @__PURE__ */ jsx("span", {
159
173
  id,
160
- "aria-readonly": "true",
161
174
  children: "—"
162
175
  });
163
176
  return /* @__PURE__ */ jsx("span", {
164
177
  id,
165
- "aria-readonly": "true",
166
178
  children: props.value.toLocaleString()
167
179
  });
168
180
  }
169
181
  const numValue = typeof props.value === "number" ? props.value : "";
170
182
  const ariaAttrs = buildAriaAttrs(props.tree);
171
- 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", {
172
188
  id,
173
189
  type: "number",
190
+ inputMode,
191
+ step: multipleOf !== void 0 ? String(multipleOf) : isInteger ? "1" : void 0,
174
192
  value: props.writeOnly ? "" : numValue,
175
193
  onChange: (e) => {
176
194
  props.onChange(Number(e.target.value));
177
195
  },
178
196
  min: props.constraints.minimum,
179
197
  max: props.constraints.maximum,
198
+ "aria-describedby": hintInfo?.ariaDescribedBy,
180
199
  ...ariaAttrs
181
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
+ })] });
182
207
  }
183
208
  /** Headless renderer for `BooleanField` — plain `<input type="checkbox">`. */
184
209
  function renderBoolean(props) {
@@ -186,12 +211,10 @@ function renderBoolean(props) {
186
211
  if (props.readOnly) {
187
212
  if (typeof props.value !== "boolean") return /* @__PURE__ */ jsx("span", {
188
213
  id,
189
- "aria-readonly": "true",
190
214
  children: "—"
191
215
  });
192
216
  return /* @__PURE__ */ jsx("span", {
193
217
  id,
194
- "aria-readonly": "true",
195
218
  children: props.value ? "Yes" : "No"
196
219
  });
197
220
  }
@@ -212,17 +235,18 @@ function renderEnum(props) {
212
235
  const enumValue = typeof props.value === "string" ? props.value : "";
213
236
  if (props.readOnly) return /* @__PURE__ */ jsx("span", {
214
237
  id,
215
- "aria-readonly": "true",
216
238
  children: enumValue || "—"
217
239
  });
218
240
  const ariaAttrs = buildAriaAttrs(props.tree);
241
+ const hintInfo = buildHintInfo(id, props.constraints);
219
242
  const enumValues = props.tree.type === "enum" ? props.tree.enumValues : [];
220
- return /* @__PURE__ */ jsxs("select", {
243
+ const select = /* @__PURE__ */ jsxs("select", {
221
244
  id,
222
245
  value: props.writeOnly ? "" : enumValue,
223
246
  onChange: (e) => {
224
247
  props.onChange(e.target.value);
225
248
  },
249
+ "aria-describedby": hintInfo?.ariaDescribedBy,
226
250
  ...ariaAttrs,
227
251
  children: [/* @__PURE__ */ jsxs("option", {
228
252
  value: "",
@@ -235,6 +259,12 @@ function renderEnum(props) {
235
259
  }, display);
236
260
  })]
237
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
+ })] });
238
268
  }
239
269
  /** Headless renderer for `ObjectField` — `<fieldset>` per object with one child per property. */
240
270
  function renderObject(props) {
@@ -253,9 +283,9 @@ function renderObject(props) {
253
283
  };
254
284
  const child = toReactNode(props.renderChild(field, childValue, childOnChange, key));
255
285
  if (child === null || child === void 0) return null;
256
- return /* @__PURE__ */ jsxs("div", { children: [typeof field.meta.description === "string" && /* @__PURE__ */ jsxs("label", {
286
+ return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("label", {
257
287
  htmlFor: childId,
258
- 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", {
259
289
  "aria-hidden": "true",
260
290
  style: { color: "#dc2626" },
261
291
  children: [" ", "*"]
@@ -277,7 +307,17 @@ function defaultRecordValue(valueType) {
277
307
  case "array": return [];
278
308
  case "object":
279
309
  case "record": return {};
280
- 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;
281
321
  }
282
322
  }
283
323
  /**
@@ -309,13 +349,10 @@ function renderRecord(props) {
309
349
  const valueType = props.tree.valueType;
310
350
  const entries = Object.entries(obj);
311
351
  if (props.readOnly) {
312
- if (entries.length === 0) return /* @__PURE__ */ jsx("span", {
313
- "aria-readonly": "true",
314
- children: "—"
315
- });
352
+ if (entries.length === 0) return /* @__PURE__ */ jsx("span", { children: "—" });
316
353
  return /* @__PURE__ */ jsx("div", {
317
354
  role: "group",
318
- "aria-label": typeof props.meta.description === "string" ? props.meta.description : void 0,
355
+ "aria-label": ariaLabel(props.meta.description),
319
356
  children: entries.map(([key, value]) => {
320
357
  return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("label", {
321
358
  htmlFor: inputId(`${props.path}.${key}`),
@@ -351,7 +388,7 @@ function renderRecord(props) {
351
388
  };
352
389
  return /* @__PURE__ */ jsxs("div", {
353
390
  role: "group",
354
- "aria-label": props.meta.description ?? "Record",
391
+ "aria-label": ariaLabel(props.meta.description),
355
392
  children: [entries.map(([key, value]) => {
356
393
  return /* @__PURE__ */ jsxs("div", { children: [
357
394
  /* @__PURE__ */ jsx("input", {
@@ -389,18 +426,47 @@ function renderArray(props) {
389
426
  const arr = Array.isArray(props.value) ? props.value : [];
390
427
  const element = props.tree.element;
391
428
  if (element === void 0) return null;
392
- if (arr.length === 0) return null;
393
- 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", {
394
448
  role: "group",
395
- "aria-label": props.meta.description ?? void 0,
396
- children: arr.map((item, i) => {
449
+ "aria-label": ariaLabel(props.meta.description),
450
+ children: [/* @__PURE__ */ jsx("ul", { children: arr.map((item, i) => {
397
451
  const childOnChange = (v) => {
398
- const next = arr.slice();
399
- next[i] = v;
400
- props.onChange(next);
452
+ const nextArr = arr.slice();
453
+ nextArr[i] = v;
454
+ props.onChange(nextArr);
401
455
  };
402
- return /* @__PURE__ */ jsx("div", { children: toReactNode(props.renderChild(element, item, childOnChange, `[${String(i)}]`)) }, String(i));
403
- })
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
+ })]
404
470
  });
405
471
  }
406
472
  /** Headless renderer for plain `UnionField` — picks the matching option and renders it. */
@@ -506,6 +572,7 @@ function DiscriminatedUnionTabs({ options, optionLabels, activeIndex, path, disc
506
572
  return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", {
507
573
  role: "tablist",
508
574
  "aria-label": "Select variant",
575
+ "aria-orientation": "horizontal",
509
576
  style: {
510
577
  display: "flex",
511
578
  gap: "0.25rem",
@@ -519,7 +586,7 @@ function DiscriminatedUnionTabs({ options, optionLabels, activeIndex, path, disc
519
586
  type: "button",
520
587
  role: "tab",
521
588
  id: tabIdFor(path, i),
522
- "aria-selected": i === activeIndex ? "true" : void 0,
589
+ "aria-selected": i === activeIndex ? "true" : "false",
523
590
  "aria-controls": panelId,
524
591
  tabIndex: i === activeIndex ? 0 : -1,
525
592
  onClick: () => {
@@ -548,10 +615,11 @@ function renderFile(props) {
548
615
  const accept = props.constraints.mimeTypes?.join(",");
549
616
  if (props.readOnly) return /* @__PURE__ */ jsx("span", {
550
617
  id,
551
- "aria-readonly": "true",
552
618
  children: "File field"
553
619
  });
554
- 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", {
555
623
  id,
556
624
  type: "file",
557
625
  accept,
@@ -559,8 +627,15 @@ function renderFile(props) {
559
627
  const file = e.target.files?.[0];
560
628
  if (file !== void 0) props.onChange(file);
561
629
  },
562
- ...buildAriaAttrs(props.tree, props.meta.description)
630
+ "aria-describedby": hintInfo?.ariaDescribedBy,
631
+ ...ariaAttrs
563
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
+ })] });
564
639
  }
565
640
  /**
566
641
  * Render a literal field — `z.literal("a")` or `{ const: 5 }`.
@@ -575,12 +650,10 @@ function renderLiteral(props) {
575
650
  const values = props.tree.literalValues;
576
651
  if (values.length === 0) return /* @__PURE__ */ jsx("span", {
577
652
  id,
578
- "aria-readonly": "true",
579
653
  children: "—"
580
654
  });
581
655
  return /* @__PURE__ */ jsx("span", {
582
656
  id,
583
- "aria-readonly": "true",
584
657
  children: values.map((v) => displayJsonValue(v)).join(", ")
585
658
  });
586
659
  }
@@ -593,7 +666,6 @@ function renderLiteral(props) {
593
666
  function renderNull(props) {
594
667
  return /* @__PURE__ */ jsx("span", {
595
668
  id: inputId(props.path),
596
- "aria-readonly": "true",
597
669
  children: "—"
598
670
  });
599
671
  }
@@ -609,7 +681,6 @@ function renderNull(props) {
609
681
  function renderNever(props) {
610
682
  return /* @__PURE__ */ jsx("span", {
611
683
  id: inputId(props.path),
612
- "aria-readonly": "true",
613
684
  className: SC_CLASSES.never,
614
685
  children: /* @__PURE__ */ jsx("em", { children: "never matches" })
615
686
  });
@@ -631,7 +702,7 @@ function renderTuple(props) {
631
702
  const restCount = restItems !== void 0 ? Math.max(arr.length - prefixItems.length, 0) : 0;
632
703
  return /* @__PURE__ */ jsxs("div", {
633
704
  role: "group",
634
- "aria-label": props.meta.description ?? void 0,
705
+ "aria-label": ariaLabel(props.meta.description),
635
706
  children: [prefixItems.map((element, i) => {
636
707
  const itemValue = arr[i];
637
708
  const childOnChange = (v) => {
@@ -717,12 +788,10 @@ function renderUnknown(props) {
717
788
  if (props.readOnly) {
718
789
  if (props.value === void 0 || props.value === null) return /* @__PURE__ */ jsx("span", {
719
790
  id,
720
- "aria-readonly": "true",
721
791
  children: "—"
722
792
  });
723
793
  return /* @__PURE__ */ jsx("span", {
724
794
  id,
725
- "aria-readonly": "true",
726
795
  children: typeof props.value === "string" ? props.value : JSON.stringify(props.value)
727
796
  });
728
797
  }
@@ -1,4 +1,4 @@
1
- import { i as DiagnosticsOptions } from "./diagnostics-ByEzkjrA.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,42 +1,58 @@
1
- import { r as ComponentResolver } from "../renderer-OaOz-n6-.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
- * Inject real Mantine element types into the resolver. Call once at
6
- * application startup before rendering any schema-driven component so
7
- * that {@link mantineResolver}'s renderers delegate to the supplied
8
- * Mantine primitives.
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`.
9
10
  *
10
11
  * `Text` is required so read-only scalars render as a styled Mantine
11
12
  * `<Text>` element instead of a bare `<span>`, matching the visual
12
13
  * weight of the editable variants.
14
+ */
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.
13
35
  *
14
36
  * @group Themes
15
37
  */
16
- declare function registerMantineComponents(components: {
17
- TextInput: React.ElementType;
18
- NumberInput: React.ElementType;
19
- Switch: React.ElementType;
20
- Select: React.ElementType;
21
- Fieldset: React.ElementType;
22
- Text: React.ElementType;
23
- }): void;
38
+ declare function createMantineResolver(components: MantineComponents): ComponentResolver;
24
39
  /**
25
40
  * Component resolver mapping schema field types to Mantine primitives —
26
41
  * `TextInput`, `NumberInput`, `Switch`, `Select`, `Fieldset`, `Text`.
27
42
  *
28
- * Pass to `SchemaProvider` to render schema-driven UI through Mantine.
29
- * Requires `@mantine/core` installed in the consuming project and a
30
- * one-time call to {@link registerMantineComponents} to inject the
31
- * actual Mantine element types.
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.
32
46
  *
33
47
  * @group Themes
34
48
  * @example
35
49
  * ```tsx
36
50
  * import { TextInput, NumberInput, Switch, Select, Fieldset, Text } from "@mantine/core";
37
- * import { mantineResolver, registerMantineComponents } from "schema-components/themes/mantine";
51
+ * import { createMantineResolver } from "schema-components/themes/mantine";
38
52
  *
39
- * registerMantineComponents({ TextInput, NumberInput, Switch, Select, Fieldset, Text });
53
+ * const mantineResolver = createMantineResolver({
54
+ * TextInput, NumberInput, Switch, Select, Fieldset, Text,
55
+ * });
40
56
  *
41
57
  * <SchemaProvider resolver={mantineResolver}>
42
58
  * <SchemaComponent schema={userSchema} value={user} onChange={setUser} />
@@ -45,4 +61,4 @@ declare function registerMantineComponents(components: {
45
61
  */
46
62
  declare const mantineResolver: ComponentResolver;
47
63
  //#endregion
48
- export { mantineResolver, registerMantineComponents };
64
+ export { MantineComponents, createMantineResolver, mantineResolver };