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.
Files changed (63) hide show
  1. package/README.md +31 -0
  2. package/dist/core/adapter.d.mts +1 -1
  3. package/dist/core/adapter.mjs +37 -8
  4. package/dist/core/constraints.d.mts +16 -0
  5. package/dist/core/constraints.mjs +138 -0
  6. package/dist/core/merge.d.mts +32 -0
  7. package/dist/core/merge.mjs +96 -0
  8. package/dist/core/normalise.d.mts +40 -0
  9. package/dist/core/normalise.mjs +171 -0
  10. package/dist/core/openapi30.d.mts +38 -0
  11. package/dist/core/openapi30.mjs +223 -0
  12. package/dist/core/ref.d.mts +25 -0
  13. package/dist/core/ref.mjs +86 -0
  14. package/dist/core/renderer.d.mts +2 -2
  15. package/dist/core/renderer.mjs +8 -0
  16. package/dist/core/swagger2.d.mts +10 -0
  17. package/dist/core/swagger2.mjs +294 -0
  18. package/dist/core/typeInference.d.mts +2 -0
  19. package/dist/core/typeInference.mjs +1 -0
  20. package/dist/core/types.d.mts +2 -3
  21. package/dist/core/types.mjs +55 -2
  22. package/dist/core/version.d.mts +2 -0
  23. package/dist/core/version.mjs +79 -0
  24. package/dist/core/walkBuilders.d.mts +52 -0
  25. package/dist/core/walkBuilders.mjs +152 -0
  26. package/dist/core/walker.d.mts +3 -10
  27. package/dist/core/walker.mjs +143 -231
  28. package/dist/html/a11y.d.mts +5 -4
  29. package/dist/html/renderToHtml.d.mts +3 -3
  30. package/dist/html/renderToHtml.mjs +23 -379
  31. package/dist/html/renderToHtmlStream.d.mts +29 -47
  32. package/dist/html/renderToHtmlStream.mjs +33 -305
  33. package/dist/html/renderers.d.mts +14 -0
  34. package/dist/html/renderers.mjs +406 -0
  35. package/dist/html/streamRenderers.d.mts +13 -0
  36. package/dist/html/streamRenderers.mjs +243 -0
  37. package/dist/openapi/components.d.mts +2 -1
  38. package/dist/openapi/parser.d.mts +59 -2
  39. package/dist/openapi/parser.mjs +189 -8
  40. package/dist/react/SchemaComponent.d.mts +4 -2
  41. package/dist/react/SchemaComponent.mjs +39 -85
  42. package/dist/react/SchemaView.d.mts +2 -1
  43. package/dist/react/SchemaView.mjs +21 -9
  44. package/dist/react/fieldPath.d.mts +20 -0
  45. package/dist/react/fieldPath.mjs +81 -0
  46. package/dist/react/headless.d.mts +2 -4
  47. package/dist/react/headless.mjs +3 -492
  48. package/dist/react/headlessRenderers.d.mts +23 -0
  49. package/dist/react/headlessRenderers.mjs +507 -0
  50. package/dist/renderer-DseHeliw.d.mts +160 -0
  51. package/dist/themes/mantine.d.mts +1 -1
  52. package/dist/themes/mantine.mjs +2 -1
  53. package/dist/themes/mui.d.mts +1 -1
  54. package/dist/themes/mui.mjs +2 -1
  55. package/dist/themes/radix.d.mts +1 -1
  56. package/dist/themes/radix.mjs +2 -1
  57. package/dist/themes/shadcn.d.mts +1 -1
  58. package/dist/themes/shadcn.mjs +10 -6
  59. package/dist/typeInference-CRPqVwKu.d.mts +299 -0
  60. package/dist/types-ag2jYLqQ.d.mts +261 -0
  61. package/dist/version-CLchheaH.d.mts +40 -0
  62. package/package.json +1 -1
  63. package/dist/types-BJzEgJdX.d.mts +0 -335
@@ -1,10 +1,8 @@
1
- import { isObject } from "../core/guards.mjs";
2
1
  import { normaliseSchema } from "../core/adapter.mjs";
3
- import { getHtmlRenderFn, mergeHtmlResolvers } from "../core/renderer.mjs";
2
+ import { mergeHtmlResolvers } from "../core/renderer.mjs";
4
3
  import { walk } from "../core/walker.mjs";
5
- import { VOID_ELEMENTS, h, raw, serialize, serializeAttributes } from "./html.mjs";
6
- import { ariaLabelAttrs, buildHintElement, buildInputId, requiredIndicator } from "./a11y.mjs";
7
- import { defaultHtmlResolver } from "./renderToHtml.mjs";
4
+ import { defaultHtmlResolver } from "./renderers.mjs";
5
+ import { streamField } from "./streamRenderers.mjs";
8
6
  //#region src/html/renderToHtmlStream.ts
9
7
  /**
10
8
  * Streaming HTML renderer — yields HTML chunks incrementally.
@@ -19,91 +17,41 @@ import { defaultHtmlResolver } from "./renderToHtml.mjs";
19
17
  * - `renderToHtmlChunks(schema, options)` → sync `Iterable<string>`
20
18
  * - `renderToHtmlStream(schema, options)` → async `AsyncIterable<string>`
21
19
  * - `renderToHtmlReadable(schema, options)` → web `ReadableStream<string>`
22
- *
23
- * Chunk boundaries:
24
- * - Object: opening tag, one chunk per field, closing tag
25
- * - Array: opening tag, one chunk per item, closing tag
26
- * - Record: opening tag, one chunk per entry, closing tag
27
- * - Leaf types (string, number, boolean, enum, literal, unknown):
28
- * rendered entirely as one chunk
29
- *
30
- * All HTML construction uses `h()` from `html.ts` — the streaming module
31
- * manually yields the opening tag, then children, then the closing tag.
32
- */
33
- /**
34
- * Yield the opening tag of an element (e.g. `<fieldset class="sc-object">`).
35
- * For void elements, yields the complete self-closing tag.
36
20
  */
37
- function yieldOpen(el) {
38
- const attrs = serializeAttributes(el.attributes);
39
- return `<${el.tag}${attrs}>`;
40
- }
41
21
  /**
42
- * Yield the closing tag of an element (e.g. `</fieldset>`).
43
- * Returns empty string for void elements.
22
+ * Render a schema as an iterable of HTML string chunks.
23
+ * Each chunk is a self-contained HTML fragment.
44
24
  */
45
- function yieldClose(el) {
46
- if (VOID_ELEMENTS.has(el.tag)) return "";
47
- return `</${el.tag}>`;
48
- }
49
- /**
50
- * Render a schema to HTML string chunks, yielded incrementally.
51
- *
52
- * Each yielded chunk is a self-contained HTML fragment. Concatenating
53
- * all chunks produces the same output as `renderToHtml`.
54
- *
55
- * @returns Sync iterable of HTML string chunks
56
- */
57
- function* renderToHtmlChunks(schema, options = {}) {
58
- const tree = prepareTree(schema, options);
59
- const resolver = options.resolver ?? defaultHtmlResolver;
60
- const mergedResolver = mergeHtmlResolvers(resolver, defaultHtmlResolver);
61
- yield* streamField(tree, options.value ?? tree.defaultValue, mergedResolver, "", resolver);
25
+ function renderToHtmlChunks(schema, options = {}) {
26
+ const { tree, resolver } = prepareTree(schema, options);
27
+ return streamField(tree, options.value ?? tree.defaultValue, mergeHtmlResolvers(resolver, defaultHtmlResolver), "", resolver);
62
28
  }
63
29
  /**
64
- * Render a schema to HTML string chunks asynchronously.
65
- *
66
- * Identical chunk boundaries to `renderToHtmlChunks` but yields via
67
- * an async generator. Use with `for await...of` or pipe to a response.
68
- *
69
- * @returns Async iterable of HTML string chunks
30
+ * Render a schema as an async iterable of HTML string chunks.
31
+ * Yields `undefined` between chunks to allow the event loop to process
32
+ * other tasks (cooperative scheduling).
70
33
  */
71
34
  async function* renderToHtmlStream(schema, options = {}) {
72
- const tree = prepareTree(schema, options);
73
- const resolver = options.resolver ?? defaultHtmlResolver;
35
+ const { tree, resolver } = prepareTree(schema, options);
36
+ const effectiveValue = options.value ?? tree.defaultValue;
74
37
  const mergedResolver = mergeHtmlResolvers(resolver, defaultHtmlResolver);
75
- for (const chunk of streamField(tree, options.value, mergedResolver, "", resolver)) {
38
+ for (const chunk of streamField(tree, effectiveValue, mergedResolver, "", resolver)) {
76
39
  yield chunk;
77
40
  await schedulerYield();
78
41
  }
79
42
  }
80
43
  /**
81
- * No-op await that yields control to the event loop.
82
- */
83
- function schedulerYield() {
84
- return Promise.resolve(void 0);
85
- }
86
- /**
87
- * Render a schema to a web `ReadableStream<string>`.
88
- *
89
- * ```ts
90
- * return new Response(renderToHtmlReadable(schema, { value }), {
91
- * headers: { "Content-Type": "text/html" },
92
- * });
93
- * ```
44
+ * Render a schema as a web `ReadableStream<string>`.
45
+ * Uses the async rendering pipeline internally.
94
46
  */
95
47
  function renderToHtmlReadable(schema, options = {}) {
96
- const iterator = renderToHtmlChunks(schema, options)[Symbol.iterator]();
97
- return new ReadableStream({
98
- pull(controller) {
99
- const result = iterator.next();
100
- if (result.done) controller.close();
101
- else controller.enqueue(result.value);
102
- },
103
- cancel() {
104
- if (iterator.return !== void 0) iterator.return();
105
- }
106
- });
48
+ const { tree, resolver } = prepareTree(schema, options);
49
+ const iterator = streamField(tree, options.value ?? tree.defaultValue, mergeHtmlResolvers(resolver, defaultHtmlResolver), "", resolver)[Symbol.iterator]();
50
+ return new ReadableStream({ pull(controller) {
51
+ const { value, done } = iterator.next();
52
+ if (done) controller.close();
53
+ else controller.enqueue(value);
54
+ } });
107
55
  }
108
56
  function prepareTree(schema, options) {
109
57
  const mergedMeta = { ...options.meta };
@@ -111,238 +59,18 @@ function prepareTree(schema, options) {
111
59
  if (options.writeOnly === true) mergedMeta.writeOnly = true;
112
60
  if (options.description !== void 0) mergedMeta.description = options.description;
113
61
  const { jsonSchema, rootMeta, rootDocument } = normaliseSchema(schema, options.ref);
114
- return walk(jsonSchema, {
115
- componentMeta: mergedMeta,
116
- rootMeta,
117
- fieldOverrides: options.fields,
118
- rootDocument
119
- });
120
- }
121
- function* streamField(tree, value, mergedResolver, path, rawResolver) {
122
- const effectiveValue = value ?? tree.defaultValue;
123
- const type = tree.type;
124
- if (type === "string" || type === "number" || type === "boolean" || type === "enum" || type === "literal" || type === "file" || type === "unknown") {
125
- yield renderLeaf(tree, effectiveValue, mergedResolver, path);
126
- return;
127
- }
128
- if (type === "union") {
129
- yield* streamUnion(tree, effectiveValue, mergedResolver, path, rawResolver);
130
- return;
131
- }
132
- if (type === "discriminatedUnion") {
133
- yield* streamDiscriminatedUnion(tree, value, mergedResolver, path, rawResolver);
134
- return;
135
- }
136
- if (type === "object") {
137
- yield* streamObject(tree, value, mergedResolver, path, rawResolver);
138
- return;
139
- }
140
- if (type === "array") {
141
- yield* streamArray(tree, value, mergedResolver, path, rawResolver);
142
- return;
143
- }
144
- if (type === "record") {
145
- yield* streamRecord(tree, value, mergedResolver, path, rawResolver);
146
- return;
147
- }
148
- yield renderLeaf(tree, value, mergedResolver, path);
149
- }
150
- function* streamObject(tree, value, mergedResolver, path, rawResolver) {
151
- const fields = tree.fields;
152
- if (fields === void 0) return;
153
- const obj = isObject(value) ? value : {};
154
- const readOnly = tree.editability === "presentation";
155
- const descriptionText = typeof tree.meta.description === "string" ? tree.meta.description : void 0;
156
- const labelAttrs = ariaLabelAttrs(descriptionText);
157
- if (readOnly) {
158
- const dlAttrs = { class: "sc-object" };
159
- Object.assign(dlAttrs, labelAttrs);
160
- const dl = h("dl", dlAttrs);
161
- const legend = descriptionText !== void 0 ? serialize(h("legend", {}, descriptionText)) : "";
162
- yield `${yieldOpen(dl)}${legend}`;
163
- for (const [key, field] of Object.entries(fields)) {
164
- const label = typeof field.meta.description === "string" ? field.meta.description : key;
165
- const childValue = obj[key];
166
- const childHtml = renderFieldSync(field, childValue, mergedResolver, key, rawResolver);
167
- yield `${serialize(h("dt", { class: "sc-label" }, label))}${serialize(h("dd", { class: "sc-value" }, raw(childHtml)))}`;
168
- }
169
- yield yieldClose(dl);
170
- } else {
171
- const fieldsetAttrs = { class: "sc-object" };
172
- Object.assign(fieldsetAttrs, labelAttrs);
173
- const fieldset = h("fieldset", fieldsetAttrs);
174
- const legend = descriptionText !== void 0 ? serialize(h("legend", {}, descriptionText)) : "";
175
- yield `${yieldOpen(fieldset)}${legend}`;
176
- for (const [key, field] of Object.entries(fields)) {
177
- const label = typeof field.meta.description === "string" ? field.meta.description : key;
178
- const fieldId = buildInputId(path, key);
179
- const childValue = obj[key];
180
- const childChunks = [...streamField(field, childValue, mergedResolver, key, rawResolver)].join("");
181
- const required = requiredIndicator(field);
182
- const labelContent = [label];
183
- if (required !== void 0) labelContent.push(required);
184
- const fieldChildren = [h("label", {
185
- class: "sc-label",
186
- for: fieldId
187
- }, ...labelContent), raw(childChunks)];
188
- const hint = buildHintElement(key, field.constraints);
189
- if (hint !== void 0) fieldChildren.push(hint);
190
- yield serialize(h("div", { class: "sc-field" }, ...fieldChildren));
191
- }
192
- yield yieldClose(fieldset);
193
- }
194
- }
195
- function* streamArray(tree, value, mergedResolver, path, rawResolver) {
196
- const arr = Array.isArray(value) ? value : [];
197
- const element = tree.element;
198
- if (element === void 0) return;
199
- const readOnly = tree.editability === "presentation";
200
- const elementPath = typeof element.meta.description === "string" ? element.meta.description : "";
201
- if (readOnly) {
202
- const ul = h("ul", { class: "sc-array" });
203
- yield yieldOpen(ul);
204
- for (const item of arr) yield serialize(h("li", { class: "sc-item" }, raw(renderFieldSync(element, item, mergedResolver, elementPath, rawResolver))));
205
- yield yieldClose(ul);
206
- } else {
207
- const div = h("div", { class: "sc-array" });
208
- yield yieldOpen(div);
209
- for (const item of arr) yield serialize(h("div", {}, raw(renderFieldSync(element, item, mergedResolver, elementPath, rawResolver))));
210
- yield yieldClose(div);
211
- }
212
- }
213
- function* streamRecord(tree, value, mergedResolver, path, rawResolver) {
214
- const obj = isObject(value) ? value : {};
215
- const valueType = tree.valueType;
216
- if (valueType === void 0) return;
217
- const readOnly = tree.editability === "presentation";
218
- const attrs = {
219
- class: "sc-record",
220
- role: "group"
62
+ return {
63
+ tree: walk(jsonSchema, {
64
+ componentMeta: mergedMeta,
65
+ rootMeta,
66
+ fieldOverrides: options.fields,
67
+ rootDocument
68
+ }),
69
+ resolver: options.resolver ?? defaultHtmlResolver
221
70
  };
222
- if (readOnly) {
223
- const dl = h("dl", attrs);
224
- yield yieldOpen(dl);
225
- for (const [key, val] of Object.entries(obj)) {
226
- const childHtml = renderFieldSync(valueType, val, mergedResolver, key, rawResolver);
227
- yield `${serialize(h("dt", { class: "sc-label" }, key))}${serialize(h("dd", { class: "sc-value" }, raw(childHtml)))}`;
228
- }
229
- yield yieldClose(dl);
230
- } else {
231
- const container = h("div", attrs);
232
- yield yieldOpen(container);
233
- for (const [key, val] of Object.entries(obj)) {
234
- const childHtml = renderFieldSync(valueType, val, mergedResolver, key, rawResolver);
235
- yield serialize(h("div", { class: "sc-field" }, h("label", { class: "sc-label" }, key), raw(childHtml)));
236
- }
237
- yield yieldClose(container);
238
- }
239
- }
240
- function* streamUnion(tree, value, mergedResolver, path, rawResolver) {
241
- const options = tree.options;
242
- if (options === void 0 || options.length === 0) {
243
- if (value === void 0 || value === null) yield serialize(h("span", { class: "sc-value sc-value--empty" }, "—"));
244
- else yield serialize(h("span", { class: "sc-value" }, JSON.stringify(value)));
245
- return;
246
- }
247
- const target = matchUnionOption(options, value) ?? options[0];
248
- if (target !== void 0) yield* streamField(target, value, mergedResolver, typeof target.meta.description === "string" ? target.meta.description : "", rawResolver);
249
- else yield serialize(h("span", { class: "sc-value sc-value--empty" }, "—"));
250
71
  }
251
- function* streamDiscriminatedUnion(tree, value, mergedResolver, path, rawResolver) {
252
- const options = tree.options;
253
- const discriminator = tree.discriminator;
254
- if (options === void 0 || options.length === 0) {
255
- if (value === void 0 || value === null) yield serialize(h("span", { class: "sc-value sc-value--empty" }, "—"));
256
- else yield serialize(h("span", { class: "sc-value" }, JSON.stringify(value)));
257
- return;
258
- }
259
- const isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
260
- const obj = isRecord(value) ? value : {};
261
- const discKey = discriminator ?? "";
262
- const currentDiscriminatorValue = typeof obj[discKey] === "string" ? obj[discKey] : void 0;
263
- const optionLabels = options.map((opt) => {
264
- const discriminatorField = opt.fields?.[discKey];
265
- if (discriminatorField !== void 0) {
266
- const constVal = discriminatorField.literalValues?.[0];
267
- if (typeof constVal === "string") return constVal;
268
- }
269
- return typeof opt.meta.title === "string" ? opt.meta.title : opt.type;
270
- });
271
- let activeIndex = 0;
272
- if (currentDiscriminatorValue !== void 0) {
273
- const found = optionLabels.indexOf(currentDiscriminatorValue);
274
- if (found !== -1) activeIndex = found;
275
- }
276
- const activeOption = options[activeIndex];
277
- if (tree.editability === "presentation") {
278
- if (activeOption !== void 0) yield* streamField(activeOption, value, mergedResolver, typeof activeOption.meta.description === "string" ? activeOption.meta.description : "", rawResolver);
279
- return;
280
- }
281
- const panelId = `sc-${path}-panel`;
282
- const wrapper = h("div", { class: "sc-discriminated-union" });
283
- yield yieldOpen(wrapper);
284
- yield serialize(h("div", {
285
- role: "tablist",
286
- class: "sc-tabs",
287
- "aria-label": "Select variant"
288
- }, ...options.map((_opt, i) => {
289
- return h("button", {
290
- type: "button",
291
- role: "tab",
292
- class: i === activeIndex ? "sc-tab sc-tab--active" : "sc-tab",
293
- id: `sc-${path}-tab-${String(i)}`,
294
- "aria-selected": i === activeIndex ? "true" : void 0,
295
- "aria-controls": panelId,
296
- tabindex: i === activeIndex ? "0" : "-1"
297
- }, optionLabels[i]);
298
- })));
299
- const panelOpen = h("div", {
300
- role: "tabpanel",
301
- id: panelId,
302
- "aria-labelledby": `sc-${path}-tab-${String(activeIndex)}`
303
- });
304
- yield yieldOpen(panelOpen);
305
- if (activeOption !== void 0) yield* streamField(activeOption, value, mergedResolver, typeof activeOption.meta.description === "string" ? activeOption.meta.description : "", rawResolver);
306
- yield yieldClose(panelOpen);
307
- yield yieldClose(wrapper);
308
- }
309
- function renderLeaf(tree, value, mergedResolver, path) {
310
- const renderFn = getHtmlRenderFn(tree.type, mergedResolver);
311
- if (renderFn !== void 0) {
312
- const props = {
313
- value,
314
- readOnly: tree.editability === "presentation",
315
- writeOnly: tree.editability === "input",
316
- meta: tree.meta,
317
- constraints: tree.constraints,
318
- path,
319
- tree,
320
- renderChild: () => ""
321
- };
322
- if (tree.enumValues !== void 0) props.enumValues = tree.enumValues;
323
- if (tree.element !== void 0) props.element = tree.element;
324
- if (tree.fields !== void 0) props.fields = tree.fields;
325
- if (tree.options !== void 0) props.options = tree.options;
326
- if (tree.discriminator !== void 0) props.discriminator = tree.discriminator;
327
- if (tree.keyType !== void 0) props.keyType = tree.keyType;
328
- if (tree.valueType !== void 0) props.valueType = tree.valueType;
329
- return renderFn(props);
330
- }
331
- if (value === void 0 || value === null) return serialize(h("span", { class: "sc-value sc-value--empty" }, "—"));
332
- return serialize(h("span", { class: "sc-value" }, typeof value === "string" ? value : JSON.stringify(value)));
333
- }
334
- /**
335
- * Render a field synchronously to a string, recursively streaming children.
336
- */
337
- function renderFieldSync(tree, value, mergedResolver, path, rawResolver) {
338
- return [...streamField(tree, value, mergedResolver, path, rawResolver)].join("");
339
- }
340
- function matchUnionOption(options, value) {
341
- if (typeof value === "string") return options.find((o) => o.type === "string" || o.type === "enum");
342
- if (typeof value === "number") return options.find((o) => o.type === "number");
343
- if (typeof value === "boolean") return options.find((o) => o.type === "boolean");
344
- if (Array.isArray(value)) return options.find((o) => o.type === "array");
345
- if (typeof value === "object" && value !== null) return options.find((o) => o.type === "object");
72
+ function schedulerYield() {
73
+ return new Promise((resolve) => setTimeout(resolve, 0));
346
74
  }
347
75
  //#endregion
348
76
  export { renderToHtmlChunks, renderToHtmlReadable, renderToHtmlStream };
@@ -0,0 +1,14 @@
1
+ import { j as WalkedField } from "../types-ag2jYLqQ.mjs";
2
+ import { o as HtmlResolver } from "../renderer-DseHeliw.mjs";
3
+
4
+ //#region src/html/renderers.d.ts
5
+ declare function dateInputType(format: string | undefined): string | undefined;
6
+ /**
7
+ * Normalise a dot-separated path into a valid, `sc-` prefixed HTML ID.
8
+ * Dots in paths (from nested objects) become hyphens.
9
+ */
10
+ declare function fieldId(path: string): string;
11
+ declare function matchUnionOption(options: WalkedField[], value: unknown): WalkedField | undefined;
12
+ declare const defaultHtmlResolver: HtmlResolver;
13
+ //#endregion
14
+ export { dateInputType, defaultHtmlResolver, fieldId, matchUnionOption };