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.
- package/README.md +38 -16
- package/dist/core/adapter.d.mts +213 -3
- package/dist/core/adapter.mjs +21 -2
- package/dist/core/constraintHint.d.mts +15 -0
- package/dist/core/constraintHint.mjs +24 -0
- package/dist/core/constraints.d.mts +34 -2
- package/dist/core/constraints.mjs +33 -1
- package/dist/core/cssClasses.d.mts +1 -0
- package/dist/core/diagnostics.d.mts +1 -1
- package/dist/core/errors.d.mts +1 -1
- package/dist/core/errors.mjs +22 -12
- package/dist/core/fieldOrder.d.mts +1 -1
- package/dist/core/formats.d.mts +7 -1
- package/dist/core/formats.mjs +6 -0
- package/dist/core/idPath.d.mts +35 -5
- package/dist/core/idPath.mjs +79 -7
- package/dist/core/inferValue.d.mts +2 -0
- package/dist/core/inferValue.mjs +1 -0
- package/dist/core/limits.d.mts +1 -1
- package/dist/core/limits.mjs +6 -0
- package/dist/core/merge.d.mts +22 -1
- package/dist/core/merge.mjs +66 -3
- package/dist/core/normalise.d.mts +17 -2
- package/dist/core/normalise.mjs +1 -1
- package/dist/core/openapi30.mjs +1 -1
- package/dist/core/openapiConstants.d.mts +1 -0
- package/dist/core/ref.d.mts +1 -1
- package/dist/core/refChain.d.mts +3 -4
- package/dist/core/refChain.mjs +2 -3
- package/dist/core/renderer.d.mts +199 -2
- package/dist/core/renderer.mjs +5 -0
- package/dist/core/swagger2.d.mts +1 -1
- package/dist/core/swagger2.mjs +1 -1
- package/dist/core/typeInference.d.mts +3 -3
- package/dist/core/types.d.mts +1 -1
- package/dist/core/types.mjs +17 -0
- package/dist/core/unionMatch.d.mts +1 -1
- package/dist/core/uri.d.mts +12 -4
- package/dist/core/uri.mjs +30 -4
- package/dist/core/version.d.mts +1 -1
- package/dist/core/walkBuilders.d.mts +63 -6
- package/dist/core/walkBuilders.mjs +33 -1
- package/dist/core/walker.d.mts +14 -1
- package/dist/core/walker.mjs +18 -0
- package/dist/{diagnostics-Cbwak-ZX.d.mts → diagnostics-BTrm3O6J.d.mts} +9 -1
- package/dist/{errors-DQSIK4n1.d.mts → errors-Dki7tji4.d.mts} +23 -13
- package/dist/html/a11y.d.mts +3 -7
- package/dist/html/a11y.mjs +1 -16
- package/dist/html/html.d.mts +11 -0
- package/dist/html/html.mjs +11 -0
- package/dist/html/renderToHtml.d.mts +45 -12
- package/dist/html/renderToHtml.mjs +20 -4
- package/dist/html/renderToHtmlStream.d.mts +63 -18
- package/dist/html/renderToHtmlStream.mjs +34 -8
- package/dist/html/renderers.d.mts +6 -31
- package/dist/html/renderers.mjs +45 -91
- package/dist/html/streamRenderers.d.mts +31 -3
- package/dist/html/streamRenderers.mjs +41 -8
- package/dist/inferValue-PPXWJpbN.d.mts +77 -0
- package/dist/{limits-DJhgx5Ay.d.mts → limits-x4OiyJxh.d.mts} +6 -0
- package/dist/{normalise-Db1xaxgx.mjs → normalise-DB-Xtjmn.mjs} +43 -2
- package/dist/openapi/ApiCallbacks.d.mts +13 -1
- package/dist/openapi/ApiCallbacks.mjs +7 -0
- package/dist/openapi/ApiLinks.d.mts +13 -1
- package/dist/openapi/ApiLinks.mjs +7 -0
- package/dist/openapi/ApiResponseHeaders.d.mts +13 -1
- package/dist/openapi/ApiResponseHeaders.mjs +7 -0
- package/dist/openapi/ApiSecurity.d.mts +14 -1
- package/dist/openapi/ApiSecurity.mjs +29 -8
- package/dist/openapi/bundle.d.mts +31 -0
- package/dist/openapi/components.d.mts +135 -20
- package/dist/openapi/components.mjs +90 -15
- package/dist/openapi/parser.d.mts +140 -13
- package/dist/openapi/parser.mjs +84 -12
- package/dist/openapi/resolve.d.mts +42 -47
- package/dist/openapi/resolve.mjs +62 -56
- package/dist/react/SchemaComponent.d.mts +90 -88
- package/dist/react/SchemaComponent.mjs +74 -2
- package/dist/react/SchemaErrorBoundary.d.mts +18 -1
- package/dist/react/SchemaErrorBoundary.mjs +13 -1
- package/dist/react/SchemaView.d.mts +39 -11
- package/dist/react/SchemaView.mjs +23 -6
- package/dist/react/a11y.d.mts +74 -7
- package/dist/react/a11y.mjs +67 -6
- package/dist/react/fieldPath.d.mts +16 -1
- package/dist/react/fieldPath.mjs +25 -1
- package/dist/react/fieldShell.d.mts +49 -0
- package/dist/react/fieldShell.mjs +37 -0
- package/dist/react/headless.d.mts +1 -1
- package/dist/react/headlessRenderers.d.mts +13 -2
- package/dist/react/headlessRenderers.mjs +134 -54
- package/dist/{ref-TdeMfaV_.d.mts → ref-DdsbekXX.d.mts} +33 -1
- package/dist/themes/mantine.d.mts +54 -12
- package/dist/themes/mantine.mjs +195 -140
- package/dist/themes/mui.d.mts +64 -11
- package/dist/themes/mui.mjs +277 -213
- package/dist/themes/radix.d.mts +67 -15
- package/dist/themes/radix.mjs +235 -170
- package/dist/themes/shadcn.d.mts +25 -1
- package/dist/themes/shadcn.mjs +112 -91
- package/dist/{types-BTB73MB8.d.mts → types-BrYbjC7_.d.mts} +30 -0
- package/dist/{version-ZzL5R6cS.d.mts → version-DL8U5RuA.d.mts} +6 -0
- package/package.json +8 -1
- package/dist/adapter-DqlAnZ_w.d.mts +0 -172
- 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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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: [
|
|
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
|
-
|
|
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":
|
|
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
|
|
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 (
|
|
386
|
-
|
|
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
|
|
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
|
|
392
|
-
|
|
393
|
-
props.onChange(
|
|
452
|
+
const nextArr = arr.slice();
|
|
453
|
+
nextArr[i] = v;
|
|
454
|
+
props.onChange(nextArr);
|
|
394
455
|
};
|
|
395
|
-
return /* @__PURE__ */
|
|
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" :
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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-
|
|
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 {
|
|
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
|
-
*
|
|
6
|
-
*
|
|
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
|
-
|
|
13
|
-
TextInput:
|
|
14
|
-
NumberInput:
|
|
15
|
-
Switch:
|
|
16
|
-
Select:
|
|
17
|
-
Fieldset:
|
|
18
|
-
Text:
|
|
19
|
-
}
|
|
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 {
|
|
64
|
+
export { MantineComponents, createMantineResolver, mantineResolver };
|