schema-components 1.12.11 → 1.13.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 +31 -0
- package/dist/core/adapter.d.mts +1 -1
- package/dist/core/adapter.mjs +37 -8
- package/dist/core/constraints.d.mts +16 -0
- package/dist/core/constraints.mjs +138 -0
- package/dist/core/merge.d.mts +32 -0
- package/dist/core/merge.mjs +96 -0
- package/dist/core/normalise.d.mts +40 -0
- package/dist/core/normalise.mjs +171 -0
- package/dist/core/openapi30.d.mts +38 -0
- package/dist/core/openapi30.mjs +223 -0
- package/dist/core/ref.d.mts +25 -0
- package/dist/core/ref.mjs +86 -0
- package/dist/core/renderer.d.mts +2 -2
- package/dist/core/renderer.mjs +8 -0
- package/dist/core/swagger2.d.mts +10 -0
- package/dist/core/swagger2.mjs +294 -0
- package/dist/core/typeInference.d.mts +2 -0
- package/dist/core/typeInference.mjs +1 -0
- package/dist/core/types.d.mts +2 -3
- package/dist/core/types.mjs +55 -2
- package/dist/core/version.d.mts +2 -0
- package/dist/core/version.mjs +79 -0
- package/dist/core/walkBuilders.d.mts +52 -0
- package/dist/core/walkBuilders.mjs +152 -0
- package/dist/core/walker.d.mts +3 -10
- package/dist/core/walker.mjs +143 -231
- package/dist/html/a11y.d.mts +5 -4
- package/dist/html/renderToHtml.d.mts +3 -3
- package/dist/html/renderToHtml.mjs +23 -379
- package/dist/html/renderToHtmlStream.d.mts +29 -47
- package/dist/html/renderToHtmlStream.mjs +33 -305
- package/dist/html/renderers.d.mts +14 -0
- package/dist/html/renderers.mjs +406 -0
- package/dist/html/streamRenderers.d.mts +13 -0
- package/dist/html/streamRenderers.mjs +243 -0
- package/dist/openapi/components.d.mts +2 -1
- package/dist/openapi/parser.d.mts +59 -2
- package/dist/openapi/parser.mjs +189 -8
- package/dist/react/SchemaComponent.d.mts +4 -2
- package/dist/react/SchemaComponent.mjs +39 -85
- package/dist/react/SchemaView.d.mts +2 -1
- package/dist/react/SchemaView.mjs +21 -9
- package/dist/react/fieldPath.d.mts +20 -0
- package/dist/react/fieldPath.mjs +81 -0
- package/dist/react/headless.d.mts +2 -4
- package/dist/react/headless.mjs +3 -492
- package/dist/react/headlessRenderers.d.mts +23 -0
- package/dist/react/headlessRenderers.mjs +507 -0
- package/dist/renderer-DseHeliw.d.mts +160 -0
- package/dist/themes/mantine.d.mts +1 -1
- package/dist/themes/mantine.mjs +2 -1
- package/dist/themes/mui.d.mts +1 -1
- package/dist/themes/mui.mjs +2 -1
- package/dist/themes/radix.d.mts +1 -1
- package/dist/themes/radix.mjs +2 -1
- package/dist/themes/shadcn.d.mts +1 -1
- package/dist/themes/shadcn.mjs +10 -6
- package/dist/typeInference-CRPqVwKu.d.mts +299 -0
- package/dist/types-ag2jYLqQ.d.mts +261 -0
- package/dist/version-CLchheaH.d.mts +40 -0
- package/package.json +1 -1
- package/dist/types-BJzEgJdX.d.mts +0 -335
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
import { h, raw, serialize } from "./html.mjs";
|
|
2
|
+
import { ariaDescribedByAttrs, ariaLabelAttrs, ariaReadonlyAttrs, ariaRequiredAttrs, buildHintElement, buildInputId, requiredIndicator } from "./a11y.mjs";
|
|
3
|
+
//#region src/html/renderers.ts
|
|
4
|
+
function dateInputType(format) {
|
|
5
|
+
if (format === "date") return "date";
|
|
6
|
+
if (format === "time") return "time";
|
|
7
|
+
if (format === "date-time" || format === "datetime") return "datetime-local";
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Normalise a dot-separated path into a valid, `sc-` prefixed HTML ID.
|
|
11
|
+
* Dots in paths (from nested objects) become hyphens.
|
|
12
|
+
*/
|
|
13
|
+
function fieldId(path) {
|
|
14
|
+
return `sc-${path.replace(/\./g, "-")}`;
|
|
15
|
+
}
|
|
16
|
+
function renderStringHtml(props) {
|
|
17
|
+
if (props.readOnly) return serialize(renderStringReadOnly(props));
|
|
18
|
+
return serialize(renderStringEditable(props));
|
|
19
|
+
}
|
|
20
|
+
function renderStringReadOnly(props) {
|
|
21
|
+
const strValue = typeof props.value === "string" ? props.value : void 0;
|
|
22
|
+
if (strValue === void 0 || strValue.length === 0) return h("span", {
|
|
23
|
+
class: "sc-value sc-value--empty",
|
|
24
|
+
...ariaReadonlyAttrs()
|
|
25
|
+
}, "—");
|
|
26
|
+
const format = props.constraints.format;
|
|
27
|
+
if (format === "email") return h("a", {
|
|
28
|
+
class: "sc-value",
|
|
29
|
+
href: `mailto:${strValue}`,
|
|
30
|
+
...ariaReadonlyAttrs()
|
|
31
|
+
}, strValue);
|
|
32
|
+
if (format === "uri" || format === "url") return h("a", {
|
|
33
|
+
class: "sc-value",
|
|
34
|
+
href: strValue,
|
|
35
|
+
...ariaReadonlyAttrs()
|
|
36
|
+
}, strValue);
|
|
37
|
+
return h("span", {
|
|
38
|
+
class: "sc-value",
|
|
39
|
+
...ariaReadonlyAttrs()
|
|
40
|
+
}, strValue);
|
|
41
|
+
}
|
|
42
|
+
function renderStringEditable(props) {
|
|
43
|
+
const strValue = typeof props.value === "string" ? props.value : "";
|
|
44
|
+
const format = props.constraints.format;
|
|
45
|
+
const inputType = dateInputType(format) ?? (format === "email" ? "email" : format === "uri" ? "url" : "text");
|
|
46
|
+
const id = fieldId(props.path);
|
|
47
|
+
const attrs = {
|
|
48
|
+
class: "sc-input",
|
|
49
|
+
id,
|
|
50
|
+
type: inputType,
|
|
51
|
+
name: id
|
|
52
|
+
};
|
|
53
|
+
if (!props.writeOnly) attrs.value = strValue;
|
|
54
|
+
if (typeof props.meta.description === "string") attrs.placeholder = props.meta.description;
|
|
55
|
+
if (props.constraints.minLength !== void 0) attrs.minlength = String(props.constraints.minLength);
|
|
56
|
+
if (props.constraints.maxLength !== void 0) attrs.maxlength = String(props.constraints.maxLength);
|
|
57
|
+
Object.assign(attrs, ariaRequiredAttrs(props.tree));
|
|
58
|
+
Object.assign(attrs, ariaDescribedByAttrs(id, props.constraints));
|
|
59
|
+
return h("input", attrs);
|
|
60
|
+
}
|
|
61
|
+
function renderNumberHtml(props) {
|
|
62
|
+
if (props.readOnly) return serialize(renderNumberReadOnly(props));
|
|
63
|
+
return serialize(renderNumberEditable(props));
|
|
64
|
+
}
|
|
65
|
+
function renderNumberReadOnly(props) {
|
|
66
|
+
if (typeof props.value !== "number") return h("span", {
|
|
67
|
+
class: "sc-value sc-value--empty",
|
|
68
|
+
...ariaReadonlyAttrs()
|
|
69
|
+
}, "—");
|
|
70
|
+
return h("span", {
|
|
71
|
+
class: "sc-value",
|
|
72
|
+
...ariaReadonlyAttrs()
|
|
73
|
+
}, props.value.toLocaleString());
|
|
74
|
+
}
|
|
75
|
+
function renderNumberEditable(props) {
|
|
76
|
+
const numValue = typeof props.value === "number" ? String(props.value) : "";
|
|
77
|
+
const id = fieldId(props.path);
|
|
78
|
+
const attrs = {
|
|
79
|
+
class: "sc-input",
|
|
80
|
+
id,
|
|
81
|
+
type: "number",
|
|
82
|
+
name: id
|
|
83
|
+
};
|
|
84
|
+
if (!props.writeOnly) attrs.value = numValue;
|
|
85
|
+
if (props.constraints.minimum !== void 0) attrs.min = String(props.constraints.minimum);
|
|
86
|
+
if (props.constraints.maximum !== void 0) attrs.max = String(props.constraints.maximum);
|
|
87
|
+
Object.assign(attrs, ariaRequiredAttrs(props.tree));
|
|
88
|
+
Object.assign(attrs, ariaDescribedByAttrs(id, props.constraints));
|
|
89
|
+
return h("input", attrs);
|
|
90
|
+
}
|
|
91
|
+
function renderBooleanHtml(props) {
|
|
92
|
+
if (props.readOnly) return serialize(renderBooleanReadOnly(props));
|
|
93
|
+
return serialize(renderBooleanEditable(props));
|
|
94
|
+
}
|
|
95
|
+
function renderBooleanReadOnly(props) {
|
|
96
|
+
if (typeof props.value !== "boolean") return h("span", {
|
|
97
|
+
class: "sc-value sc-value--empty",
|
|
98
|
+
...ariaReadonlyAttrs()
|
|
99
|
+
}, "—");
|
|
100
|
+
return h("span", {
|
|
101
|
+
class: "sc-value sc-value--boolean",
|
|
102
|
+
...ariaReadonlyAttrs()
|
|
103
|
+
}, props.value ? "Yes" : "No");
|
|
104
|
+
}
|
|
105
|
+
function renderBooleanEditable(props) {
|
|
106
|
+
const id = fieldId(props.path);
|
|
107
|
+
const attrs = {
|
|
108
|
+
class: "sc-input",
|
|
109
|
+
id,
|
|
110
|
+
type: "checkbox",
|
|
111
|
+
name: id
|
|
112
|
+
};
|
|
113
|
+
if (!props.writeOnly && props.value === true) attrs.checked = true;
|
|
114
|
+
Object.assign(attrs, ariaRequiredAttrs(props.tree));
|
|
115
|
+
Object.assign(attrs, ariaLabelAttrs(props.meta.description));
|
|
116
|
+
return h("input", attrs);
|
|
117
|
+
}
|
|
118
|
+
function renderEnumHtml(props) {
|
|
119
|
+
if (props.readOnly) return serialize(renderEnumReadOnly(props));
|
|
120
|
+
return serialize(renderEnumEditable(props));
|
|
121
|
+
}
|
|
122
|
+
function renderEnumReadOnly(props) {
|
|
123
|
+
const enumValue = typeof props.value === "string" ? props.value : "";
|
|
124
|
+
if (enumValue.length === 0) return h("span", {
|
|
125
|
+
class: "sc-value sc-value--empty",
|
|
126
|
+
...ariaReadonlyAttrs()
|
|
127
|
+
}, "—");
|
|
128
|
+
return h("span", {
|
|
129
|
+
class: "sc-value",
|
|
130
|
+
...ariaReadonlyAttrs()
|
|
131
|
+
}, enumValue);
|
|
132
|
+
}
|
|
133
|
+
function renderEnumEditable(props) {
|
|
134
|
+
const enumValue = typeof props.value === "string" ? props.value : "";
|
|
135
|
+
const id = fieldId(props.path);
|
|
136
|
+
const selectedValue = props.writeOnly ? "" : enumValue;
|
|
137
|
+
const optionNodes = [h("option", { value: "" }, "Select…"), ...(props.enumValues ?? []).map((v) => {
|
|
138
|
+
const display = v === null ? "null" : typeof v === "string" ? v : String(v);
|
|
139
|
+
const attrs = { value: display };
|
|
140
|
+
if (display === selectedValue) attrs.selected = true;
|
|
141
|
+
return h("option", attrs, display);
|
|
142
|
+
})];
|
|
143
|
+
const selectAttrs = {
|
|
144
|
+
class: "sc-input",
|
|
145
|
+
id,
|
|
146
|
+
name: id
|
|
147
|
+
};
|
|
148
|
+
Object.assign(selectAttrs, ariaRequiredAttrs(props.tree));
|
|
149
|
+
return h("select", selectAttrs, ...optionNodes);
|
|
150
|
+
}
|
|
151
|
+
function renderObjectHtml(props) {
|
|
152
|
+
return serialize(renderObjectNode(props));
|
|
153
|
+
}
|
|
154
|
+
function renderObjectNode(props) {
|
|
155
|
+
const fields = props.fields;
|
|
156
|
+
if (fields === void 0) return "";
|
|
157
|
+
const isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
158
|
+
const obj = isRecord(props.value) ? props.value : {};
|
|
159
|
+
const descriptionText = typeof props.meta.description === "string" ? props.meta.description : void 0;
|
|
160
|
+
const legend = descriptionText !== void 0 ? h("legend", {}, descriptionText) : void 0;
|
|
161
|
+
const sortedEntries = Object.entries(fields).sort((a, b) => {
|
|
162
|
+
return (typeof a[1].meta.order === "number" ? a[1].meta.order : Infinity) - (typeof b[1].meta.order === "number" ? b[1].meta.order : Infinity);
|
|
163
|
+
}).filter(([, field]) => field.meta.visible !== false);
|
|
164
|
+
if (props.readOnly) {
|
|
165
|
+
const children = [];
|
|
166
|
+
if (legend !== void 0) children.push(legend);
|
|
167
|
+
for (const [key, field] of sortedEntries) {
|
|
168
|
+
const label = typeof field.meta.description === "string" ? field.meta.description : key;
|
|
169
|
+
const childValue = obj[key];
|
|
170
|
+
const childHtml = props.renderChild(field, childValue, props.path ? `${props.path}.${key}` : key);
|
|
171
|
+
children.push(h("dt", { class: "sc-label" }, label));
|
|
172
|
+
children.push(h("dd", { class: "sc-value" }, raw(childHtml)));
|
|
173
|
+
}
|
|
174
|
+
const dlAttrs = { class: "sc-object" };
|
|
175
|
+
Object.assign(dlAttrs, ariaLabelAttrs(descriptionText));
|
|
176
|
+
return h("dl", dlAttrs, ...children);
|
|
177
|
+
}
|
|
178
|
+
const children = [];
|
|
179
|
+
if (legend !== void 0) children.push(legend);
|
|
180
|
+
for (const [key, field] of sortedEntries) {
|
|
181
|
+
const label = typeof field.meta.description === "string" ? field.meta.description : key;
|
|
182
|
+
const fieldId = buildInputId(props.path, key);
|
|
183
|
+
const childPath = props.path ? `${props.path}.${key}` : key;
|
|
184
|
+
const childValue = obj[key];
|
|
185
|
+
const childHtml = props.renderChild(field, childValue, childPath);
|
|
186
|
+
const required = requiredIndicator(field);
|
|
187
|
+
const labelContent = [label];
|
|
188
|
+
if (required !== void 0) labelContent.push(required);
|
|
189
|
+
const fieldChildren = [h("label", {
|
|
190
|
+
class: "sc-label",
|
|
191
|
+
for: fieldId
|
|
192
|
+
}, ...labelContent), raw(childHtml)];
|
|
193
|
+
const hint = buildHintElement(fieldId, field.constraints);
|
|
194
|
+
if (hint !== void 0) fieldChildren.push(hint);
|
|
195
|
+
children.push(h("div", { class: "sc-field" }, ...fieldChildren));
|
|
196
|
+
}
|
|
197
|
+
const fieldsetAttrs = { class: "sc-object" };
|
|
198
|
+
Object.assign(fieldsetAttrs, ariaLabelAttrs(descriptionText));
|
|
199
|
+
return h("fieldset", fieldsetAttrs, ...children);
|
|
200
|
+
}
|
|
201
|
+
function renderArrayHtml(props) {
|
|
202
|
+
return serialize(renderArrayNode(props));
|
|
203
|
+
}
|
|
204
|
+
function renderArrayNode(props) {
|
|
205
|
+
const arr = Array.isArray(props.value) ? props.value : [];
|
|
206
|
+
const element = props.element;
|
|
207
|
+
if (element === void 0) return "";
|
|
208
|
+
const items = arr.map((item) => {
|
|
209
|
+
return h("li", { class: "sc-item" }, raw(props.renderChild(element, item)));
|
|
210
|
+
});
|
|
211
|
+
if (props.readOnly) return h("ul", { class: "sc-array" }, ...items);
|
|
212
|
+
return h("div", { class: "sc-array" }, ...arr.map((item) => {
|
|
213
|
+
return h("div", {}, raw(props.renderChild(element, item)));
|
|
214
|
+
}));
|
|
215
|
+
}
|
|
216
|
+
function renderRecordHtml(props) {
|
|
217
|
+
return serialize(renderRecordNode(props));
|
|
218
|
+
}
|
|
219
|
+
function renderRecordNode(props) {
|
|
220
|
+
const isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
221
|
+
const obj = isRecord(props.value) ? props.value : {};
|
|
222
|
+
const valueType = props.valueType;
|
|
223
|
+
if (valueType === void 0) return "";
|
|
224
|
+
const attrs = {
|
|
225
|
+
class: "sc-record",
|
|
226
|
+
role: "group"
|
|
227
|
+
};
|
|
228
|
+
if (props.readOnly) {
|
|
229
|
+
const children = [];
|
|
230
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
231
|
+
const childHtml = props.renderChild(valueType, val, key);
|
|
232
|
+
children.push(h("dt", { class: "sc-label" }, key));
|
|
233
|
+
children.push(h("dd", { class: "sc-value" }, raw(childHtml)));
|
|
234
|
+
}
|
|
235
|
+
return h("dl", attrs, ...children);
|
|
236
|
+
}
|
|
237
|
+
const children = [];
|
|
238
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
239
|
+
const childHtml = props.renderChild(valueType, val, key);
|
|
240
|
+
children.push(h("div", { class: "sc-field" }, h("label", { class: "sc-label" }, key), raw(childHtml)));
|
|
241
|
+
}
|
|
242
|
+
return h("div", attrs, ...children);
|
|
243
|
+
}
|
|
244
|
+
function renderLiteralHtml(props) {
|
|
245
|
+
const values = props.literalValues;
|
|
246
|
+
if (values === void 0 || values.length === 0) return serialize(h("span", { class: "sc-value sc-value--empty" }, "—"));
|
|
247
|
+
return serialize(h("span", { class: "sc-value" }, values.map((v) => v === null ? "null" : String(v)).join(", ")));
|
|
248
|
+
}
|
|
249
|
+
function renderUnionHtml(props) {
|
|
250
|
+
const options = props.options;
|
|
251
|
+
if (options === void 0 || options.length === 0) {
|
|
252
|
+
if (props.value === void 0 || props.value === null) return serialize(h("span", { class: "sc-value sc-value--empty" }, "—"));
|
|
253
|
+
return serialize(h("span", { class: "sc-value" }, JSON.stringify(props.value)));
|
|
254
|
+
}
|
|
255
|
+
const matched = matchUnionOption(options, props.value);
|
|
256
|
+
if (matched !== void 0) return props.renderChild(matched, props.value);
|
|
257
|
+
const firstOption = options[0];
|
|
258
|
+
if (firstOption !== void 0) return props.renderChild(firstOption, props.value);
|
|
259
|
+
return serialize(h("span", { class: "sc-value sc-value--empty" }, "—"));
|
|
260
|
+
}
|
|
261
|
+
function renderDiscriminatedUnionHtml(props) {
|
|
262
|
+
const options = props.options;
|
|
263
|
+
const discriminator = props.discriminator;
|
|
264
|
+
if (options === void 0 || options.length === 0) {
|
|
265
|
+
if (props.value === void 0 || props.value === null) return serialize(h("span", { class: "sc-value sc-value--empty" }, "—"));
|
|
266
|
+
return serialize(h("span", { class: "sc-value" }, JSON.stringify(props.value)));
|
|
267
|
+
}
|
|
268
|
+
const isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
269
|
+
const obj = isRecord(props.value) ? props.value : {};
|
|
270
|
+
const discKey = discriminator ?? "";
|
|
271
|
+
const currentDiscriminatorValue = typeof obj[discKey] === "string" ? obj[discKey] : void 0;
|
|
272
|
+
const optionLabels = options.map((opt) => {
|
|
273
|
+
if (opt.type === "object") {
|
|
274
|
+
const discriminatorField = opt.fields[discKey];
|
|
275
|
+
if (discriminatorField?.type === "literal") {
|
|
276
|
+
const constVal = discriminatorField.literalValues[0];
|
|
277
|
+
if (typeof constVal === "string") return constVal;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return typeof opt.meta.title === "string" ? opt.meta.title : opt.type;
|
|
281
|
+
});
|
|
282
|
+
let activeIndex = 0;
|
|
283
|
+
if (currentDiscriminatorValue !== void 0) {
|
|
284
|
+
const found = optionLabels.indexOf(currentDiscriminatorValue);
|
|
285
|
+
if (found !== -1) activeIndex = found;
|
|
286
|
+
}
|
|
287
|
+
const activeOption = options[activeIndex];
|
|
288
|
+
if (props.readOnly) {
|
|
289
|
+
if (activeOption !== void 0) return props.renderChild(activeOption, props.value);
|
|
290
|
+
return serialize(h("span", { class: "sc-value sc-value--empty" }, "—"));
|
|
291
|
+
}
|
|
292
|
+
const panelId = `sc-${props.path}-panel`;
|
|
293
|
+
const children = [h("div", {
|
|
294
|
+
role: "tablist",
|
|
295
|
+
class: "sc-tabs",
|
|
296
|
+
"aria-label": "Select variant"
|
|
297
|
+
}, ...options.map((_opt, i) => {
|
|
298
|
+
return h("button", {
|
|
299
|
+
type: "button",
|
|
300
|
+
role: "tab",
|
|
301
|
+
class: i === activeIndex ? "sc-tab sc-tab--active" : "sc-tab",
|
|
302
|
+
id: `sc-${props.path}-tab-${String(i)}`,
|
|
303
|
+
"aria-selected": i === activeIndex ? "true" : void 0,
|
|
304
|
+
"aria-controls": panelId,
|
|
305
|
+
tabindex: i === activeIndex ? "0" : "-1"
|
|
306
|
+
}, optionLabels[i]);
|
|
307
|
+
}))];
|
|
308
|
+
if (activeOption !== void 0) {
|
|
309
|
+
const childHtml = props.renderChild(activeOption, props.value);
|
|
310
|
+
children.push(h("div", {
|
|
311
|
+
role: "tabpanel",
|
|
312
|
+
id: panelId,
|
|
313
|
+
"aria-labelledby": `sc-${props.path}-tab-${String(activeIndex)}`
|
|
314
|
+
}, raw(childHtml)));
|
|
315
|
+
}
|
|
316
|
+
return serialize(h("div", { class: "sc-discriminated-union" }, ...children));
|
|
317
|
+
}
|
|
318
|
+
function renderFileHtml(props) {
|
|
319
|
+
const id = fieldId(props.path);
|
|
320
|
+
const accept = props.constraints.mimeTypes?.join(",");
|
|
321
|
+
if (props.readOnly) return serialize(h("span", {
|
|
322
|
+
class: "sc-value",
|
|
323
|
+
id,
|
|
324
|
+
...ariaReadonlyAttrs()
|
|
325
|
+
}, "File field"));
|
|
326
|
+
const attrs = {
|
|
327
|
+
class: "sc-input",
|
|
328
|
+
id,
|
|
329
|
+
type: "file",
|
|
330
|
+
name: id
|
|
331
|
+
};
|
|
332
|
+
if (accept !== void 0) attrs.accept = accept;
|
|
333
|
+
Object.assign(attrs, ariaRequiredAttrs(props.tree));
|
|
334
|
+
if (typeof props.meta.description === "string") Object.assign(attrs, ariaLabelAttrs(props.meta.description));
|
|
335
|
+
return serialize(h("input", attrs));
|
|
336
|
+
}
|
|
337
|
+
function renderRecursiveHtml(props) {
|
|
338
|
+
const refTarget = props.refTarget ?? "";
|
|
339
|
+
return serialize(h("fieldset", { class: "sc-recursive" }, `↻ ${typeof props.meta.description === "string" ? props.meta.description : refTarget} (recursive)`));
|
|
340
|
+
}
|
|
341
|
+
function renderUnknownHtml(props) {
|
|
342
|
+
if (props.readOnly) {
|
|
343
|
+
if (props.value === void 0 || props.value === null) return serialize(h("span", { class: "sc-value sc-value--empty" }, "—"));
|
|
344
|
+
if (typeof props.value === "string") return serialize(h("span", { class: "sc-value" }, props.value));
|
|
345
|
+
return serialize(h("span", { class: "sc-value" }, JSON.stringify(props.value)));
|
|
346
|
+
}
|
|
347
|
+
const strValue = typeof props.value === "string" ? props.value : "";
|
|
348
|
+
const attrs = {
|
|
349
|
+
class: "sc-input",
|
|
350
|
+
type: "text",
|
|
351
|
+
name: props.path
|
|
352
|
+
};
|
|
353
|
+
if (!props.writeOnly) attrs.value = strValue;
|
|
354
|
+
return serialize(h("input", attrs));
|
|
355
|
+
}
|
|
356
|
+
function renderTupleHtml(props) {
|
|
357
|
+
const arr = Array.isArray(props.value) ? props.value : [];
|
|
358
|
+
const prefixItems = props.prefixItems;
|
|
359
|
+
if (prefixItems === void 0) return renderUnknownHtml(props);
|
|
360
|
+
const children = [];
|
|
361
|
+
for (let i = 0; i < prefixItems.length; i++) {
|
|
362
|
+
const itemValue = arr[i];
|
|
363
|
+
const element = prefixItems[i];
|
|
364
|
+
if (element === void 0) continue;
|
|
365
|
+
const childHtml = props.renderChild(element, itemValue, `[${String(i)}]`);
|
|
366
|
+
children.push(h("div", { class: "sc-tuple-item" }, h("span", { class: "sc-tuple-index" }, String(i)), raw(childHtml)));
|
|
367
|
+
}
|
|
368
|
+
return serialize(h("div", { class: "sc-tuple" }, ...children));
|
|
369
|
+
}
|
|
370
|
+
function renderConditionalHtml(props) {
|
|
371
|
+
const children = [];
|
|
372
|
+
if (props.ifClause !== void 0) children.push(h("div", { class: "sc-conditional-if" }, raw("if: ...")));
|
|
373
|
+
if (props.thenClause !== void 0) children.push(h("div", { class: "sc-conditional-then" }, raw("then: ...")));
|
|
374
|
+
if (props.elseClause !== void 0) children.push(h("div", { class: "sc-conditional-else" }, raw("else: ...")));
|
|
375
|
+
return serialize(h("div", { class: "sc-conditional" }, ...children));
|
|
376
|
+
}
|
|
377
|
+
function renderNegationHtml(props) {
|
|
378
|
+
return serialize(h("div", { class: "sc-negation" }, raw("not: ...")));
|
|
379
|
+
}
|
|
380
|
+
function matchUnionOption(options, value) {
|
|
381
|
+
if (typeof value === "string") return options.find((o) => o.type === "string" || o.type === "enum");
|
|
382
|
+
if (typeof value === "number") return options.find((o) => o.type === "number");
|
|
383
|
+
if (typeof value === "boolean") return options.find((o) => o.type === "boolean");
|
|
384
|
+
if (Array.isArray(value)) return options.find((o) => o.type === "array");
|
|
385
|
+
if (typeof value === "object" && value !== null) return options.find((o) => o.type === "object");
|
|
386
|
+
}
|
|
387
|
+
const defaultHtmlResolver = {
|
|
388
|
+
string: renderStringHtml,
|
|
389
|
+
number: renderNumberHtml,
|
|
390
|
+
boolean: renderBooleanHtml,
|
|
391
|
+
enum: renderEnumHtml,
|
|
392
|
+
object: renderObjectHtml,
|
|
393
|
+
array: renderArrayHtml,
|
|
394
|
+
tuple: renderTupleHtml,
|
|
395
|
+
record: renderRecordHtml,
|
|
396
|
+
literal: renderLiteralHtml,
|
|
397
|
+
union: renderUnionHtml,
|
|
398
|
+
discriminatedUnion: renderDiscriminatedUnionHtml,
|
|
399
|
+
conditional: renderConditionalHtml,
|
|
400
|
+
negation: renderNegationHtml,
|
|
401
|
+
recursive: renderRecursiveHtml,
|
|
402
|
+
file: renderFileHtml,
|
|
403
|
+
unknown: renderUnknownHtml
|
|
404
|
+
};
|
|
405
|
+
//#endregion
|
|
406
|
+
export { dateInputType, defaultHtmlResolver, fieldId, matchUnionOption };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { j as WalkedField } from "../types-ag2jYLqQ.mjs";
|
|
2
|
+
import { o as HtmlResolver } from "../renderer-DseHeliw.mjs";
|
|
3
|
+
import { HtmlElement } from "./html.mjs";
|
|
4
|
+
|
|
5
|
+
//#region src/html/streamRenderers.d.ts
|
|
6
|
+
declare function yieldOpen(el: HtmlElement): string;
|
|
7
|
+
declare function yieldClose(el: HtmlElement): string;
|
|
8
|
+
declare function renderLeaf(tree: WalkedField, value: unknown, mergedResolver: HtmlResolver, path: string): string;
|
|
9
|
+
declare function renderFieldSync(tree: WalkedField, value: unknown, mergedResolver: HtmlResolver, path: string, rawResolver: HtmlResolver): string;
|
|
10
|
+
declare function matchUnionOption(options: WalkedField[], value: unknown): WalkedField | undefined;
|
|
11
|
+
declare function streamField(tree: WalkedField, value: unknown, mergedResolver: HtmlResolver, path: string, rawResolver: HtmlResolver): Iterable<string, void, undefined>;
|
|
12
|
+
//#endregion
|
|
13
|
+
export { matchUnionOption, renderFieldSync, renderLeaf, streamField, yieldClose, yieldOpen };
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { isObject } from "../core/guards.mjs";
|
|
2
|
+
import { getHtmlRenderFn } from "../core/renderer.mjs";
|
|
3
|
+
import { VOID_ELEMENTS, h, raw, serialize, serializeAttributes } from "./html.mjs";
|
|
4
|
+
import { ariaLabelAttrs, buildHintElement, buildInputId, requiredIndicator } from "./a11y.mjs";
|
|
5
|
+
//#region src/html/streamRenderers.ts
|
|
6
|
+
function yieldOpen(el) {
|
|
7
|
+
const attrStr = serializeAttributes(el.attributes);
|
|
8
|
+
if (el.children.length === 0 && VOID_ELEMENTS.has(el.tag)) return `<${el.tag}${attrStr}>`;
|
|
9
|
+
return `<${el.tag}${attrStr}>`;
|
|
10
|
+
}
|
|
11
|
+
function yieldClose(el) {
|
|
12
|
+
if (VOID_ELEMENTS.has(el.tag)) return "";
|
|
13
|
+
return `</${el.tag}>`;
|
|
14
|
+
}
|
|
15
|
+
function renderLeaf(tree, value, mergedResolver, path) {
|
|
16
|
+
const renderFn = getHtmlRenderFn(tree.type, mergedResolver);
|
|
17
|
+
if (renderFn !== void 0) {
|
|
18
|
+
const props = {
|
|
19
|
+
value,
|
|
20
|
+
readOnly: tree.editability === "presentation",
|
|
21
|
+
writeOnly: tree.editability === "input",
|
|
22
|
+
meta: tree.meta,
|
|
23
|
+
constraints: tree.constraints,
|
|
24
|
+
path,
|
|
25
|
+
tree,
|
|
26
|
+
renderChild: () => ""
|
|
27
|
+
};
|
|
28
|
+
if (tree.type === "enum") props.enumValues = tree.enumValues;
|
|
29
|
+
if (tree.type === "literal") props.literalValues = tree.literalValues;
|
|
30
|
+
if (tree.type === "array" && tree.element !== void 0) props.element = tree.element;
|
|
31
|
+
if (tree.type === "object") props.fields = tree.fields;
|
|
32
|
+
if (tree.type === "union") props.options = tree.options;
|
|
33
|
+
if (tree.type === "discriminatedUnion") props.discriminator = tree.discriminator;
|
|
34
|
+
if (tree.type === "record") props.keyType = tree.keyType;
|
|
35
|
+
if (tree.type === "record") props.valueType = tree.valueType;
|
|
36
|
+
return renderFn(props);
|
|
37
|
+
}
|
|
38
|
+
if (value === void 0 || value === null) return serialize(h("span", { class: "sc-value sc-value--empty" }, "—"));
|
|
39
|
+
return serialize(h("span", { class: "sc-value" }, typeof value === "string" ? value : JSON.stringify(value)));
|
|
40
|
+
}
|
|
41
|
+
function renderFieldSync(tree, value, mergedResolver, path, rawResolver) {
|
|
42
|
+
return [...streamField(tree, value, mergedResolver, path, rawResolver)].join("");
|
|
43
|
+
}
|
|
44
|
+
function matchUnionOption(options, value) {
|
|
45
|
+
if (typeof value === "string") return options.find((o) => o.type === "string" || o.type === "enum");
|
|
46
|
+
if (typeof value === "number") return options.find((o) => o.type === "number");
|
|
47
|
+
if (typeof value === "boolean") return options.find((o) => o.type === "boolean");
|
|
48
|
+
if (Array.isArray(value)) return options.find((o) => o.type === "array");
|
|
49
|
+
if (typeof value === "object" && value !== null) return options.find((o) => o.type === "object");
|
|
50
|
+
}
|
|
51
|
+
function* streamField(tree, value, mergedResolver, path, rawResolver) {
|
|
52
|
+
const effectiveValue = value ?? tree.defaultValue;
|
|
53
|
+
const type = tree.type;
|
|
54
|
+
if (type === "string" || type === "number" || type === "boolean" || type === "enum" || type === "literal" || type === "file" || type === "unknown") {
|
|
55
|
+
yield renderLeaf(tree, effectiveValue, mergedResolver, path);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (type === "union") {
|
|
59
|
+
yield* streamUnion(tree, effectiveValue, mergedResolver, path, rawResolver);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (type === "discriminatedUnion") {
|
|
63
|
+
yield* streamDiscriminatedUnion(tree, value, mergedResolver, path, rawResolver);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (type === "object") {
|
|
67
|
+
yield* streamObject(tree, value, mergedResolver, path, rawResolver);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (type === "array") {
|
|
71
|
+
yield* streamArray(tree, value, mergedResolver, path, rawResolver);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (type === "record") {
|
|
75
|
+
yield* streamRecord(tree, value, mergedResolver, path, rawResolver);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
yield renderLeaf(tree, value, mergedResolver, path);
|
|
79
|
+
}
|
|
80
|
+
function* streamObject(tree, value, mergedResolver, path, rawResolver) {
|
|
81
|
+
if (tree.type !== "object") return;
|
|
82
|
+
const fields = tree.fields;
|
|
83
|
+
const obj = isObject(value) ? value : {};
|
|
84
|
+
const readOnly = tree.editability === "presentation";
|
|
85
|
+
const descriptionText = typeof tree.meta.description === "string" ? tree.meta.description : void 0;
|
|
86
|
+
const labelAttrs = ariaLabelAttrs(descriptionText);
|
|
87
|
+
if (readOnly) {
|
|
88
|
+
const dlAttrs = { class: "sc-object" };
|
|
89
|
+
Object.assign(dlAttrs, labelAttrs);
|
|
90
|
+
const dl = h("dl", dlAttrs);
|
|
91
|
+
const legend = descriptionText !== void 0 ? serialize(h("legend", {}, descriptionText)) : "";
|
|
92
|
+
yield `${yieldOpen(dl)}${legend}`;
|
|
93
|
+
for (const [key, field] of Object.entries(fields)) {
|
|
94
|
+
const label = typeof field.meta.description === "string" ? field.meta.description : key;
|
|
95
|
+
const childValue = obj[key];
|
|
96
|
+
const childHtml = renderFieldSync(field, childValue, mergedResolver, key, rawResolver);
|
|
97
|
+
yield `${serialize(h("dt", { class: "sc-label" }, label))}${serialize(h("dd", { class: "sc-value" }, raw(childHtml)))}`;
|
|
98
|
+
}
|
|
99
|
+
yield yieldClose(dl);
|
|
100
|
+
} else {
|
|
101
|
+
const fieldsetAttrs = { class: "sc-object" };
|
|
102
|
+
Object.assign(fieldsetAttrs, labelAttrs);
|
|
103
|
+
const fieldset = h("fieldset", fieldsetAttrs);
|
|
104
|
+
const legend = descriptionText !== void 0 ? serialize(h("legend", {}, descriptionText)) : "";
|
|
105
|
+
yield `${yieldOpen(fieldset)}${legend}`;
|
|
106
|
+
for (const [key, field] of Object.entries(fields)) {
|
|
107
|
+
const label = typeof field.meta.description === "string" ? field.meta.description : key;
|
|
108
|
+
const fieldId = buildInputId(path, key);
|
|
109
|
+
const childValue = obj[key];
|
|
110
|
+
const childChunks = [...streamField(field, childValue, mergedResolver, key, rawResolver)].join("");
|
|
111
|
+
const required = requiredIndicator(field);
|
|
112
|
+
const labelContent = [label];
|
|
113
|
+
if (required !== void 0) labelContent.push(required);
|
|
114
|
+
const fieldChildren = [h("label", {
|
|
115
|
+
class: "sc-label",
|
|
116
|
+
for: fieldId
|
|
117
|
+
}, ...labelContent), raw(childChunks)];
|
|
118
|
+
const hint = buildHintElement(key, field.constraints);
|
|
119
|
+
if (hint !== void 0) fieldChildren.push(hint);
|
|
120
|
+
yield serialize(h("div", { class: "sc-field" }, ...fieldChildren));
|
|
121
|
+
}
|
|
122
|
+
yield yieldClose(fieldset);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function* streamArray(tree, value, mergedResolver, path, rawResolver) {
|
|
126
|
+
const arr = Array.isArray(value) ? value : [];
|
|
127
|
+
if (tree.type !== "array") return;
|
|
128
|
+
const element = tree.element;
|
|
129
|
+
if (element === void 0) return;
|
|
130
|
+
const readOnly = tree.editability === "presentation";
|
|
131
|
+
const elementPath = typeof element.meta.description === "string" ? element.meta.description : "";
|
|
132
|
+
if (readOnly) {
|
|
133
|
+
const ul = h("ul", { class: "sc-array" });
|
|
134
|
+
yield yieldOpen(ul);
|
|
135
|
+
for (const item of arr) yield serialize(h("li", { class: "sc-item" }, raw(renderFieldSync(element, item, mergedResolver, elementPath, rawResolver))));
|
|
136
|
+
yield yieldClose(ul);
|
|
137
|
+
} else {
|
|
138
|
+
const div = h("div", { class: "sc-array" });
|
|
139
|
+
yield yieldOpen(div);
|
|
140
|
+
for (const item of arr) yield serialize(h("div", {}, raw(renderFieldSync(element, item, mergedResolver, elementPath, rawResolver))));
|
|
141
|
+
yield yieldClose(div);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function* streamRecord(tree, value, mergedResolver, path, rawResolver) {
|
|
145
|
+
const obj = isObject(value) ? value : {};
|
|
146
|
+
if (tree.type !== "record") return;
|
|
147
|
+
const valueType = tree.valueType;
|
|
148
|
+
const readOnly = tree.editability === "presentation";
|
|
149
|
+
const attrs = {
|
|
150
|
+
class: "sc-record",
|
|
151
|
+
role: "group"
|
|
152
|
+
};
|
|
153
|
+
if (readOnly) {
|
|
154
|
+
const dl = h("dl", attrs);
|
|
155
|
+
yield yieldOpen(dl);
|
|
156
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
157
|
+
const childHtml = renderFieldSync(valueType, val, mergedResolver, key, rawResolver);
|
|
158
|
+
yield `${serialize(h("dt", { class: "sc-label" }, key))}${serialize(h("dd", { class: "sc-value" }, raw(childHtml)))}`;
|
|
159
|
+
}
|
|
160
|
+
yield yieldClose(dl);
|
|
161
|
+
} else {
|
|
162
|
+
const container = h("div", attrs);
|
|
163
|
+
yield yieldOpen(container);
|
|
164
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
165
|
+
const childHtml = renderFieldSync(valueType, val, mergedResolver, key, rawResolver);
|
|
166
|
+
yield serialize(h("div", { class: "sc-field" }, h("label", { class: "sc-label" }, key), raw(childHtml)));
|
|
167
|
+
}
|
|
168
|
+
yield yieldClose(container);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function* streamUnion(tree, value, mergedResolver, path, rawResolver) {
|
|
172
|
+
const options = tree.type === "union" ? tree.options : void 0;
|
|
173
|
+
if (options === void 0 || options.length === 0) {
|
|
174
|
+
if (value === void 0 || value === null) yield serialize(h("span", { class: "sc-value sc-value--empty" }, "—"));
|
|
175
|
+
else yield serialize(h("span", { class: "sc-value" }, JSON.stringify(value)));
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
const target = matchUnionOption(options, value) ?? options[0];
|
|
179
|
+
if (target !== void 0) yield* streamField(target, value, mergedResolver, typeof target.meta.description === "string" ? target.meta.description : "", rawResolver);
|
|
180
|
+
else yield serialize(h("span", { class: "sc-value sc-value--empty" }, "—"));
|
|
181
|
+
}
|
|
182
|
+
function* streamDiscriminatedUnion(tree, value, mergedResolver, path, rawResolver) {
|
|
183
|
+
const options = tree.type === "discriminatedUnion" ? tree.options : void 0;
|
|
184
|
+
const discriminator = tree.type === "discriminatedUnion" ? tree.discriminator : void 0;
|
|
185
|
+
if (options === void 0 || options.length === 0) {
|
|
186
|
+
if (value === void 0 || value === null) yield serialize(h("span", { class: "sc-value sc-value--empty" }, "—"));
|
|
187
|
+
else yield serialize(h("span", { class: "sc-value" }, JSON.stringify(value)));
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
191
|
+
const obj = isRecord(value) ? value : {};
|
|
192
|
+
const discKey = discriminator ?? "";
|
|
193
|
+
const currentDiscriminatorValue = typeof obj[discKey] === "string" ? obj[discKey] : void 0;
|
|
194
|
+
const optionLabels = options.map((opt) => {
|
|
195
|
+
if (opt.type === "object") {
|
|
196
|
+
const discriminatorField = opt.fields[discKey];
|
|
197
|
+
if (discriminatorField?.type === "literal") {
|
|
198
|
+
const constVal = discriminatorField.literalValues[0];
|
|
199
|
+
if (typeof constVal === "string") return constVal;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return typeof opt.meta.title === "string" ? opt.meta.title : opt.type;
|
|
203
|
+
});
|
|
204
|
+
let activeIndex = 0;
|
|
205
|
+
if (currentDiscriminatorValue !== void 0) {
|
|
206
|
+
const found = optionLabels.indexOf(currentDiscriminatorValue);
|
|
207
|
+
if (found !== -1) activeIndex = found;
|
|
208
|
+
}
|
|
209
|
+
const activeOption = options[activeIndex];
|
|
210
|
+
if (tree.editability === "presentation") {
|
|
211
|
+
if (activeOption !== void 0) yield* streamField(activeOption, value, mergedResolver, typeof activeOption.meta.description === "string" ? activeOption.meta.description : "", rawResolver);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
const panelId = `sc-${path}-panel`;
|
|
215
|
+
const wrapper = h("div", { class: "sc-discriminated-union" });
|
|
216
|
+
yield yieldOpen(wrapper);
|
|
217
|
+
yield serialize(h("div", {
|
|
218
|
+
role: "tablist",
|
|
219
|
+
class: "sc-tabs",
|
|
220
|
+
"aria-label": "Select variant"
|
|
221
|
+
}, ...options.map((_opt, i) => {
|
|
222
|
+
return h("button", {
|
|
223
|
+
type: "button",
|
|
224
|
+
role: "tab",
|
|
225
|
+
class: i === activeIndex ? "sc-tab sc-tab--active" : "sc-tab",
|
|
226
|
+
id: `sc-${path}-tab-${String(i)}`,
|
|
227
|
+
"aria-selected": i === activeIndex ? "true" : void 0,
|
|
228
|
+
"aria-controls": panelId,
|
|
229
|
+
tabindex: i === activeIndex ? "0" : "-1"
|
|
230
|
+
}, optionLabels[i]);
|
|
231
|
+
})));
|
|
232
|
+
const panelOpen = h("div", {
|
|
233
|
+
role: "tabpanel",
|
|
234
|
+
id: panelId,
|
|
235
|
+
"aria-labelledby": `sc-${path}-tab-${String(activeIndex)}`
|
|
236
|
+
});
|
|
237
|
+
yield yieldOpen(panelOpen);
|
|
238
|
+
if (activeOption !== void 0) yield* streamField(activeOption, value, mergedResolver, typeof activeOption.meta.description === "string" ? activeOption.meta.description : "", rawResolver);
|
|
239
|
+
yield yieldClose(panelOpen);
|
|
240
|
+
yield yieldClose(wrapper);
|
|
241
|
+
}
|
|
242
|
+
//#endregion
|
|
243
|
+
export { matchUnionOption, renderFieldSync, renderLeaf, streamField, yieldClose, yieldOpen };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { u as FieldOverride, w as SchemaMeta } from "../types-ag2jYLqQ.mjs";
|
|
2
|
+
import { i as InferResponseFields, n as InferParameterOverrides, r as InferRequestBodyFields } from "../typeInference-CRPqVwKu.mjs";
|
|
2
3
|
import { WidgetMap } from "../react/SchemaComponent.mjs";
|
|
3
4
|
import { ReactNode } from "react";
|
|
4
5
|
|