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
package/dist/html/html.d.mts
CHANGED
|
@@ -65,6 +65,7 @@ type AttrValue = string | number | boolean | undefined;
|
|
|
65
65
|
* arbitrary `data-*` and `aria-*` keys are allowed via index signature.
|
|
66
66
|
*/
|
|
67
67
|
type HtmlAttributes = Record<string, AttrValue>;
|
|
68
|
+
/** HTML5 void-element tag names — self-closing, must not carry children. */
|
|
68
69
|
declare const VOID_ELEMENTS: Set<string>;
|
|
69
70
|
/**
|
|
70
71
|
* Build an HTML element node.
|
|
@@ -103,7 +104,17 @@ declare function raw(html: string): HtmlRaw;
|
|
|
103
104
|
* @returns HTML string
|
|
104
105
|
*/
|
|
105
106
|
declare function serialize(node: HtmlNode): string;
|
|
107
|
+
/**
|
|
108
|
+
* Serialise a single {@link HtmlElement} to a string, taking care of
|
|
109
|
+
* void-element self-closing, attribute serialisation, and recursive
|
|
110
|
+
* child rendering. Use {@link serialize} for arbitrary nodes.
|
|
111
|
+
*/
|
|
106
112
|
declare function serializeElement(el: HtmlElement): string;
|
|
113
|
+
/**
|
|
114
|
+
* Serialise an attribute map to the `key="value"` form used inside an
|
|
115
|
+
* opening tag. `false` / `undefined` values are omitted; `true` renders
|
|
116
|
+
* as a boolean attribute (just the name).
|
|
117
|
+
*/
|
|
107
118
|
declare function serializeAttributes(attrs: HtmlAttributes): string;
|
|
108
119
|
/**
|
|
109
120
|
* Serialise an HTML node to chunks, yielded at natural element boundaries.
|
package/dist/html/html.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
//#region src/html/html.ts
|
|
2
|
+
/** HTML5 void-element tag names — self-closing, must not carry children. */
|
|
2
3
|
const VOID_ELEMENTS = new Set([
|
|
3
4
|
"area",
|
|
4
5
|
"base",
|
|
@@ -80,6 +81,11 @@ function serialize(node) {
|
|
|
80
81
|
if (node.tag === "") return node.children.map((child) => serialize(child)).join("");
|
|
81
82
|
return serializeElement(node);
|
|
82
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* Serialise a single {@link HtmlElement} to a string, taking care of
|
|
86
|
+
* void-element self-closing, attribute serialisation, and recursive
|
|
87
|
+
* child rendering. Use {@link serialize} for arbitrary nodes.
|
|
88
|
+
*/
|
|
83
89
|
function serializeElement(el) {
|
|
84
90
|
const attrs = serializeAttributes(el.attributes);
|
|
85
91
|
if (VOID_ELEMENTS.has(el.tag)) return `<${el.tag}${attrs}>`;
|
|
@@ -87,6 +93,11 @@ function serializeElement(el) {
|
|
|
87
93
|
const inner = el.children.map((child) => serialize(child)).join("");
|
|
88
94
|
return `<${el.tag}${attrs}>${inner}</${el.tag}>`;
|
|
89
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Serialise an attribute map to the `key="value"` form used inside an
|
|
98
|
+
* opening tag. `false` / `undefined` values are omitted; `true` renders
|
|
99
|
+
* as a boolean attribute (just the name).
|
|
100
|
+
*/
|
|
90
101
|
function serializeAttributes(attrs) {
|
|
91
102
|
const parts = [];
|
|
92
103
|
for (const [key, value] of Object.entries(attrs)) {
|
|
@@ -1,21 +1,42 @@
|
|
|
1
|
-
import { w as SchemaMeta } from "../types-
|
|
2
|
-
import {
|
|
1
|
+
import { w as SchemaMeta } from "../types-BrYbjC7_.mjs";
|
|
2
|
+
import { SchemaIoSide } from "../core/adapter.mjs";
|
|
3
|
+
import { HtmlResolver } from "../core/renderer.mjs";
|
|
4
|
+
import { RejectUnrepresentableZod } from "../core/typeInference.mjs";
|
|
5
|
+
import { a as InferredValue, t as InferFields } from "../inferValue-PPXWJpbN.mjs";
|
|
3
6
|
|
|
4
7
|
//#region src/html/renderToHtml.d.ts
|
|
5
8
|
/**
|
|
6
|
-
* Build the recursion-cap sentinel element
|
|
7
|
-
*
|
|
9
|
+
* Build the recursion-cap sentinel element used when the renderer
|
|
10
|
+
* encounters circular schema references. The label is interpolated via
|
|
11
|
+
* `h()` + `serialize` so any HTML in `meta.description` (which is
|
|
8
12
|
* schema-author content but can equally be sourced from user-supplied
|
|
9
13
|
* JSON Schema input) is escaped — never interpolated into raw markup.
|
|
14
|
+
*
|
|
15
|
+
* @group HTML
|
|
10
16
|
*/
|
|
11
17
|
declare function recursionSentinelHtml(label: string): string;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Options accepted by {@link renderToHtml}.
|
|
20
|
+
*
|
|
21
|
+
* The generic parameters mirror `<SchemaComponent>` so a typed
|
|
22
|
+
* `schema` argument drives typed `value`, `ref`, and `fields` options.
|
|
23
|
+
*
|
|
24
|
+
* @group HTML
|
|
25
|
+
*/
|
|
26
|
+
interface RenderToHtmlOptions<T = unknown, Ref extends string | undefined = undefined, Mode extends SchemaIoSide = "output"> {
|
|
27
|
+
/**
|
|
28
|
+
* The data value to render. Typed against `InferredValue<T, Ref, undefined, Mode>`
|
|
29
|
+
* so a typed `schema` argument drives the rendered value's shape.
|
|
30
|
+
*/
|
|
31
|
+
value?: InferredValue<T, Ref, undefined, Mode>;
|
|
15
32
|
/** For OpenAPI: a ref string like "#/components/schemas/User". */
|
|
16
|
-
ref?:
|
|
17
|
-
/**
|
|
18
|
-
|
|
33
|
+
ref?: Ref;
|
|
34
|
+
/**
|
|
35
|
+
* Per-field meta overrides — nested object mirroring schema shape.
|
|
36
|
+
* Typed against {@link InferFields} so a typed `schema` argument
|
|
37
|
+
* drives autocomplete on the override map.
|
|
38
|
+
*/
|
|
39
|
+
fields?: InferFields<T, Ref>;
|
|
19
40
|
/** Meta overrides applied to the root schema. */
|
|
20
41
|
meta?: SchemaMeta;
|
|
21
42
|
/** Force all fields read-only. */
|
|
@@ -28,12 +49,24 @@ interface RenderToHtmlOptions {
|
|
|
28
49
|
resolver?: HtmlResolver;
|
|
29
50
|
}
|
|
30
51
|
/**
|
|
31
|
-
* Render a schema to
|
|
52
|
+
* Render a schema to a semantic HTML string.
|
|
53
|
+
*
|
|
54
|
+
* Framework-agnostic alternative to the React rendering pipeline.
|
|
55
|
+
* Shares the same normalise → walk → render pipeline, but emits
|
|
56
|
+
* escaped HTML with `sc-` prefixed classes rather than ReactNodes.
|
|
57
|
+
* Pass `resolver` to plug in a custom HTML renderer.
|
|
32
58
|
*
|
|
59
|
+
* @group HTML
|
|
33
60
|
* @param schema - Zod schema, JSON Schema, or OpenAPI document
|
|
34
61
|
* @param options - Value, overrides, and resolver options
|
|
35
62
|
* @returns Semantic HTML string with `sc-` prefixed classes
|
|
63
|
+
* @example
|
|
64
|
+
* ```tsx
|
|
65
|
+
* import { renderToHtml } from "schema-components/html/renderToHtml";
|
|
66
|
+
*
|
|
67
|
+
* const html = renderToHtml(userSchema, { value: user, readOnly: true });
|
|
68
|
+
* ```
|
|
36
69
|
*/
|
|
37
|
-
declare function renderToHtml(schema:
|
|
70
|
+
declare function renderToHtml<T = unknown, Ref extends string | undefined = undefined, Mode extends SchemaIoSide = "output">(schema: RejectUnrepresentableZod<T>, options?: RenderToHtmlOptions<T, Ref, Mode>): string;
|
|
38
71
|
//#endregion
|
|
39
72
|
export { RenderToHtmlOptions, recursionSentinelHtml, renderToHtml };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { toRecordOrUndefined } from "../core/guards.mjs";
|
|
1
2
|
import "../core/limits.mjs";
|
|
2
3
|
import { normaliseSchema } from "../core/adapter.mjs";
|
|
3
4
|
import { getHtmlRenderFn, mergeHtmlResolvers } from "../core/renderer.mjs";
|
|
@@ -27,20 +28,35 @@ import { defaultHtmlResolver } from "./renderers.mjs";
|
|
|
27
28
|
* });
|
|
28
29
|
*/
|
|
29
30
|
/**
|
|
30
|
-
* Build the recursion-cap sentinel element
|
|
31
|
-
*
|
|
31
|
+
* Build the recursion-cap sentinel element used when the renderer
|
|
32
|
+
* encounters circular schema references. The label is interpolated via
|
|
33
|
+
* `h()` + `serialize` so any HTML in `meta.description` (which is
|
|
32
34
|
* schema-author content but can equally be sourced from user-supplied
|
|
33
35
|
* JSON Schema input) is escaped — never interpolated into raw markup.
|
|
36
|
+
*
|
|
37
|
+
* @group HTML
|
|
34
38
|
*/
|
|
35
39
|
function recursionSentinelHtml(label) {
|
|
36
40
|
return serialize(h("fieldset", { class: "sc-recursive" }, h("em", {}, `↻ ${label} (recursive)`)));
|
|
37
41
|
}
|
|
38
42
|
/**
|
|
39
|
-
* Render a schema to
|
|
43
|
+
* Render a schema to a semantic HTML string.
|
|
44
|
+
*
|
|
45
|
+
* Framework-agnostic alternative to the React rendering pipeline.
|
|
46
|
+
* Shares the same normalise → walk → render pipeline, but emits
|
|
47
|
+
* escaped HTML with `sc-` prefixed classes rather than ReactNodes.
|
|
48
|
+
* Pass `resolver` to plug in a custom HTML renderer.
|
|
40
49
|
*
|
|
50
|
+
* @group HTML
|
|
41
51
|
* @param schema - Zod schema, JSON Schema, or OpenAPI document
|
|
42
52
|
* @param options - Value, overrides, and resolver options
|
|
43
53
|
* @returns Semantic HTML string with `sc-` prefixed classes
|
|
54
|
+
* @example
|
|
55
|
+
* ```tsx
|
|
56
|
+
* import { renderToHtml } from "schema-components/html/renderToHtml";
|
|
57
|
+
*
|
|
58
|
+
* const html = renderToHtml(userSchema, { value: user, readOnly: true });
|
|
59
|
+
* ```
|
|
44
60
|
*/
|
|
45
61
|
function renderToHtml(schema, options = {}) {
|
|
46
62
|
const mergedMeta = { ...options.meta };
|
|
@@ -51,7 +67,7 @@ function renderToHtml(schema, options = {}) {
|
|
|
51
67
|
const tree = walk(jsonSchema, {
|
|
52
68
|
componentMeta: mergedMeta,
|
|
53
69
|
rootMeta,
|
|
54
|
-
fieldOverrides: options.fields,
|
|
70
|
+
fieldOverrides: toRecordOrUndefined(options.fields),
|
|
55
71
|
rootDocument
|
|
56
72
|
});
|
|
57
73
|
const resolver = options.resolver ?? defaultHtmlResolver;
|
|
@@ -1,14 +1,34 @@
|
|
|
1
|
-
import { w as SchemaMeta } from "../types-
|
|
2
|
-
import {
|
|
1
|
+
import { w as SchemaMeta } from "../types-BrYbjC7_.mjs";
|
|
2
|
+
import { SchemaIoSide } from "../core/adapter.mjs";
|
|
3
|
+
import { HtmlResolver } from "../core/renderer.mjs";
|
|
4
|
+
import { RejectUnrepresentableZod } from "../core/typeInference.mjs";
|
|
5
|
+
import { a as InferredValue, t as InferFields } from "../inferValue-PPXWJpbN.mjs";
|
|
3
6
|
|
|
4
7
|
//#region src/html/renderToHtmlStream.d.ts
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Options accepted by the streaming HTML renderers
|
|
10
|
+
* ({@link renderToHtmlChunks}, {@link renderToHtmlStream},
|
|
11
|
+
* {@link renderToHtmlReadable}).
|
|
12
|
+
*
|
|
13
|
+
* The generic parameters mirror `<SchemaComponent>` so a typed
|
|
14
|
+
* `schema` argument drives typed `value`, `ref`, and `fields` options.
|
|
15
|
+
*
|
|
16
|
+
* @group HTML
|
|
17
|
+
*/
|
|
18
|
+
interface StreamRenderOptions<T = unknown, Ref extends string | undefined = undefined, Mode extends SchemaIoSide = "output"> {
|
|
19
|
+
/**
|
|
20
|
+
* The data value to render. Typed against `InferredValue<T, Ref, undefined, Mode>`
|
|
21
|
+
* so a typed `schema` argument drives the rendered value's shape.
|
|
22
|
+
*/
|
|
23
|
+
value?: InferredValue<T, Ref, undefined, Mode>;
|
|
8
24
|
/** For OpenAPI: a ref string like "#/components/schemas/User". */
|
|
9
|
-
ref?:
|
|
10
|
-
/**
|
|
11
|
-
|
|
25
|
+
ref?: Ref;
|
|
26
|
+
/**
|
|
27
|
+
* Per-field meta overrides — nested object mirroring schema shape.
|
|
28
|
+
* Typed against {@link InferFields} so a typed `schema` argument
|
|
29
|
+
* drives autocomplete on the override map.
|
|
30
|
+
*/
|
|
31
|
+
fields?: InferFields<T, Ref>;
|
|
12
32
|
/** Meta overrides applied to the root schema. */
|
|
13
33
|
meta?: SchemaMeta;
|
|
14
34
|
/** Force all fields read-only. */
|
|
@@ -21,20 +41,45 @@ interface StreamRenderOptions {
|
|
|
21
41
|
resolver?: HtmlResolver;
|
|
22
42
|
}
|
|
23
43
|
/**
|
|
24
|
-
* Render a schema as
|
|
25
|
-
*
|
|
44
|
+
* Render a schema as a synchronous iterable of HTML string chunks. Each
|
|
45
|
+
* chunk is a self-contained HTML fragment, ready to write to a stream
|
|
46
|
+
* or concatenate into a single string. Use when the host can flush
|
|
47
|
+
* output incrementally but does not need cooperative scheduling.
|
|
48
|
+
*
|
|
49
|
+
* @group HTML
|
|
50
|
+
* @example
|
|
51
|
+
* ```tsx
|
|
52
|
+
* import { renderToHtmlChunks } from "schema-components/html/renderToHtmlStream";
|
|
53
|
+
*
|
|
54
|
+
* for (const chunk of renderToHtmlChunks(userSchema, { value: user })) {
|
|
55
|
+
* response.write(chunk);
|
|
56
|
+
* }
|
|
57
|
+
* ```
|
|
26
58
|
*/
|
|
27
|
-
declare function renderToHtmlChunks(schema:
|
|
59
|
+
declare function renderToHtmlChunks<T = unknown, Ref extends string | undefined = undefined, Mode extends SchemaIoSide = "output">(schema: RejectUnrepresentableZod<T>, options?: StreamRenderOptions<T, Ref, Mode>): Iterable<string>;
|
|
28
60
|
/**
|
|
29
|
-
* Render a schema as an async iterable of HTML string chunks.
|
|
30
|
-
*
|
|
31
|
-
*
|
|
61
|
+
* Render a schema as an async iterable of HTML string chunks. Yields
|
|
62
|
+
* back to the event loop between chunks via a microtask so other tasks
|
|
63
|
+
* (queued I/O, timers) can run between fragments.
|
|
64
|
+
*
|
|
65
|
+
* @group HTML
|
|
32
66
|
*/
|
|
33
|
-
declare function renderToHtmlStream(schema:
|
|
67
|
+
declare function renderToHtmlStream<T = unknown, Ref extends string | undefined = undefined, Mode extends SchemaIoSide = "output">(schema: RejectUnrepresentableZod<T>, options?: StreamRenderOptions<T, Ref, Mode>): AsyncIterable<string>;
|
|
34
68
|
/**
|
|
35
|
-
* Render a schema as a web `ReadableStream<string
|
|
36
|
-
*
|
|
69
|
+
* Render a schema as a web `ReadableStream<string>` so it can be piped
|
|
70
|
+
* into a `Response` body. Pulls chunks lazily from the synchronous
|
|
71
|
+
* chunk iterator under the hood.
|
|
72
|
+
*
|
|
73
|
+
* @group HTML
|
|
74
|
+
* @example
|
|
75
|
+
* ```tsx
|
|
76
|
+
* import { renderToHtmlReadable } from "schema-components/html/renderToHtmlStream";
|
|
77
|
+
*
|
|
78
|
+
* return new Response(renderToHtmlReadable(userSchema, { value: user }), {
|
|
79
|
+
* headers: { "content-type": "text/html" },
|
|
80
|
+
* });
|
|
81
|
+
* ```
|
|
37
82
|
*/
|
|
38
|
-
declare function renderToHtmlReadable(schema:
|
|
83
|
+
declare function renderToHtmlReadable<T = unknown, Ref extends string | undefined = undefined, Mode extends SchemaIoSide = "output">(schema: RejectUnrepresentableZod<T>, options?: StreamRenderOptions<T, Ref, Mode>): ReadableStream<string>;
|
|
39
84
|
//#endregion
|
|
40
85
|
export { StreamRenderOptions, renderToHtmlChunks, renderToHtmlReadable, renderToHtmlStream };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { toRecordOrUndefined } from "../core/guards.mjs";
|
|
1
2
|
import { normaliseSchema } from "../core/adapter.mjs";
|
|
2
3
|
import { mergeHtmlResolvers } from "../core/renderer.mjs";
|
|
3
4
|
import { walk } from "../core/walker.mjs";
|
|
@@ -19,17 +20,31 @@ import { streamField } from "./streamRenderers.mjs";
|
|
|
19
20
|
* - `renderToHtmlReadable(schema, options)` → web `ReadableStream<string>`
|
|
20
21
|
*/
|
|
21
22
|
/**
|
|
22
|
-
* Render a schema as
|
|
23
|
-
*
|
|
23
|
+
* Render a schema as a synchronous iterable of HTML string chunks. Each
|
|
24
|
+
* chunk is a self-contained HTML fragment, ready to write to a stream
|
|
25
|
+
* or concatenate into a single string. Use when the host can flush
|
|
26
|
+
* output incrementally but does not need cooperative scheduling.
|
|
27
|
+
*
|
|
28
|
+
* @group HTML
|
|
29
|
+
* @example
|
|
30
|
+
* ```tsx
|
|
31
|
+
* import { renderToHtmlChunks } from "schema-components/html/renderToHtmlStream";
|
|
32
|
+
*
|
|
33
|
+
* for (const chunk of renderToHtmlChunks(userSchema, { value: user })) {
|
|
34
|
+
* response.write(chunk);
|
|
35
|
+
* }
|
|
36
|
+
* ```
|
|
24
37
|
*/
|
|
25
38
|
function renderToHtmlChunks(schema, options = {}) {
|
|
26
39
|
const { tree, resolver } = prepareTree(schema, options);
|
|
27
40
|
return streamField(tree, options.value ?? tree.defaultValue, mergeHtmlResolvers(resolver, defaultHtmlResolver), "", resolver);
|
|
28
41
|
}
|
|
29
42
|
/**
|
|
30
|
-
* Render a schema as an async iterable of HTML string chunks.
|
|
31
|
-
*
|
|
32
|
-
*
|
|
43
|
+
* Render a schema as an async iterable of HTML string chunks. Yields
|
|
44
|
+
* back to the event loop between chunks via a microtask so other tasks
|
|
45
|
+
* (queued I/O, timers) can run between fragments.
|
|
46
|
+
*
|
|
47
|
+
* @group HTML
|
|
33
48
|
*/
|
|
34
49
|
async function* renderToHtmlStream(schema, options = {}) {
|
|
35
50
|
const { tree, resolver } = prepareTree(schema, options);
|
|
@@ -41,8 +56,19 @@ async function* renderToHtmlStream(schema, options = {}) {
|
|
|
41
56
|
}
|
|
42
57
|
}
|
|
43
58
|
/**
|
|
44
|
-
* Render a schema as a web `ReadableStream<string
|
|
45
|
-
*
|
|
59
|
+
* Render a schema as a web `ReadableStream<string>` so it can be piped
|
|
60
|
+
* into a `Response` body. Pulls chunks lazily from the synchronous
|
|
61
|
+
* chunk iterator under the hood.
|
|
62
|
+
*
|
|
63
|
+
* @group HTML
|
|
64
|
+
* @example
|
|
65
|
+
* ```tsx
|
|
66
|
+
* import { renderToHtmlReadable } from "schema-components/html/renderToHtmlStream";
|
|
67
|
+
*
|
|
68
|
+
* return new Response(renderToHtmlReadable(userSchema, { value: user }), {
|
|
69
|
+
* headers: { "content-type": "text/html" },
|
|
70
|
+
* });
|
|
71
|
+
* ```
|
|
46
72
|
*/
|
|
47
73
|
function renderToHtmlReadable(schema, options = {}) {
|
|
48
74
|
const { tree, resolver } = prepareTree(schema, options);
|
|
@@ -63,7 +89,7 @@ function prepareTree(schema, options) {
|
|
|
63
89
|
tree: walk(jsonSchema, {
|
|
64
90
|
componentMeta: mergedMeta,
|
|
65
91
|
rootMeta,
|
|
66
|
-
fieldOverrides: options.fields,
|
|
92
|
+
fieldOverrides: toRecordOrUndefined(options.fields),
|
|
67
93
|
rootDocument
|
|
68
94
|
}),
|
|
69
95
|
resolver: options.resolver ?? defaultHtmlResolver
|
|
@@ -1,39 +1,14 @@
|
|
|
1
|
+
import { HtmlResolver } from "../core/renderer.mjs";
|
|
1
2
|
import { dateInputType } from "../core/formats.mjs";
|
|
2
|
-
import { o as HtmlResolver } from "../renderer-Ul9taFYp.mjs";
|
|
3
3
|
import { matchUnionOption } from "../core/unionMatch.mjs";
|
|
4
4
|
|
|
5
5
|
//#region src/html/renderers.d.ts
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* The wrapper tolerates an empty path here (returning `sc-`) so that
|
|
13
|
-
* a leaf renderer at the schema root — `renderToHtml(z.string())` — has
|
|
14
|
-
* a usable id without throwing. Container renderers always thread a
|
|
15
|
-
* non-empty path through `renderChild`, so the empty-id fallback can
|
|
16
|
-
* never produce sibling collisions inside a structured form.
|
|
7
|
+
* Default HTML resolver used by `renderToHtml` and the streaming
|
|
8
|
+
* renderers when the consumer does not pass a custom resolver. Maps
|
|
9
|
+
* every `WalkedField` variant to a semantic HTML renderer built on the
|
|
10
|
+
* `h()` element builder.
|
|
17
11
|
*/
|
|
18
|
-
declare function fieldId(path: string): string;
|
|
19
|
-
/**
|
|
20
|
-
* Tab-panel id for a discriminated union at `path`. Delegates to the
|
|
21
|
-
* canonical `panelIdFor` from `core/idPath.ts` for the normal case so
|
|
22
|
-
* the sync, streaming, and React renderers all emit identical ids; falls
|
|
23
|
-
* back to a structurally-equivalent string when the renderer is invoked
|
|
24
|
-
* with an empty root path (a discriminated union at the schema root —
|
|
25
|
-
* see the `fieldId` doc comment for the wider context).
|
|
26
|
-
*
|
|
27
|
-
* Exported because `streamRenderers.ts` needs to derive identical ids
|
|
28
|
-
* — the panel id on the `<div role="tabpanel">` must match the
|
|
29
|
-
* `aria-controls` on every tab regardless of which pipeline rendered it.
|
|
30
|
-
*/
|
|
31
|
-
declare function panelId(path: string): string;
|
|
32
|
-
/**
|
|
33
|
-
* Tab id for tab `i` within a discriminated union at `path`. Mirror of
|
|
34
|
-
* `panelId` above — see its comment.
|
|
35
|
-
*/
|
|
36
|
-
declare function tabId(path: string, i: number): string;
|
|
37
12
|
declare const defaultHtmlResolver: HtmlResolver;
|
|
38
13
|
//#endregion
|
|
39
|
-
export { dateInputType, defaultHtmlResolver,
|
|
14
|
+
export { dateInputType, defaultHtmlResolver, matchUnionOption };
|
package/dist/html/renderers.mjs
CHANGED
|
@@ -9,56 +9,13 @@ import { displayJsonValue } from "../core/walkBuilders.mjs";
|
|
|
9
9
|
import { h, raw, serialize } from "./html.mjs";
|
|
10
10
|
import { ariaDescribedByAttrs, ariaLabelAttrs, ariaReadonlyAttrs, ariaRequiredAttrs, buildHintElement, buildInputId, requiredIndicator } from "./a11y.mjs";
|
|
11
11
|
//#region src/html/renderers.ts
|
|
12
|
-
/**
|
|
13
|
-
* Thin wrapper over `fieldDomId` from `core/idPath.ts`. Every render
|
|
14
|
-
* pipeline must derive ids from the same canonical normaliser so that
|
|
15
|
-
* `aria-controls`, `aria-labelledby`, and `htmlFor` references resolve
|
|
16
|
-
* consistently across the React, sync-HTML, and streaming-HTML outputs.
|
|
17
|
-
*
|
|
18
|
-
* The wrapper tolerates an empty path here (returning `sc-`) so that
|
|
19
|
-
* a leaf renderer at the schema root — `renderToHtml(z.string())` — has
|
|
20
|
-
* a usable id without throwing. Container renderers always thread a
|
|
21
|
-
* non-empty path through `renderChild`, so the empty-id fallback can
|
|
22
|
-
* never produce sibling collisions inside a structured form.
|
|
23
|
-
*/
|
|
24
|
-
function fieldId(path) {
|
|
25
|
-
if (path.length === 0) return "sc-";
|
|
26
|
-
return fieldDomId(path);
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Tab-panel id for a discriminated union at `path`. Delegates to the
|
|
30
|
-
* canonical `panelIdFor` from `core/idPath.ts` for the normal case so
|
|
31
|
-
* the sync, streaming, and React renderers all emit identical ids; falls
|
|
32
|
-
* back to a structurally-equivalent string when the renderer is invoked
|
|
33
|
-
* with an empty root path (a discriminated union at the schema root —
|
|
34
|
-
* see the `fieldId` doc comment for the wider context).
|
|
35
|
-
*
|
|
36
|
-
* Exported because `streamRenderers.ts` needs to derive identical ids
|
|
37
|
-
* — the panel id on the `<div role="tabpanel">` must match the
|
|
38
|
-
* `aria-controls` on every tab regardless of which pipeline rendered it.
|
|
39
|
-
*/
|
|
40
|
-
function panelId(path) {
|
|
41
|
-
if (path.length === 0) return `${fieldId(path)}-panel`;
|
|
42
|
-
return panelIdFor(path);
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* Tab id for tab `i` within a discriminated union at `path`. Mirror of
|
|
46
|
-
* `panelId` above — see its comment.
|
|
47
|
-
*/
|
|
48
|
-
function tabId(path, i) {
|
|
49
|
-
if (path.length === 0) return `${fieldId(path)}-tab-${String(i)}`;
|
|
50
|
-
return tabIdFor(path, i);
|
|
51
|
-
}
|
|
52
12
|
function renderStringHtml(props) {
|
|
53
13
|
if (props.readOnly) return serialize(renderStringReadOnly(props));
|
|
54
14
|
return serialize(renderStringEditable(props));
|
|
55
15
|
}
|
|
56
16
|
function renderStringReadOnly(props) {
|
|
57
17
|
const strValue = typeof props.value === "string" ? props.value : void 0;
|
|
58
|
-
if (strValue === void 0 || strValue.length === 0) return h("span", {
|
|
59
|
-
class: SC_CLASSES.valueEmpty,
|
|
60
|
-
...ariaReadonlyAttrs()
|
|
61
|
-
}, "—");
|
|
18
|
+
if (strValue === void 0 || strValue.length === 0) return h("span", { class: SC_CLASSES.valueEmpty }, "—");
|
|
62
19
|
const format = props.constraints.format;
|
|
63
20
|
if (format === "email" && isSafeMailtoAddress(strValue)) return h("a", {
|
|
64
21
|
class: SC_CLASSES.value,
|
|
@@ -70,22 +27,22 @@ function renderStringReadOnly(props) {
|
|
|
70
27
|
href: strValue,
|
|
71
28
|
...ariaReadonlyAttrs()
|
|
72
29
|
}, strValue);
|
|
73
|
-
return h("span", {
|
|
74
|
-
class: SC_CLASSES.value,
|
|
75
|
-
...ariaReadonlyAttrs()
|
|
76
|
-
}, strValue);
|
|
30
|
+
return h("span", { class: SC_CLASSES.value }, strValue);
|
|
77
31
|
}
|
|
78
32
|
function renderStringEditable(props) {
|
|
79
33
|
const strValue = typeof props.value === "string" ? props.value : "";
|
|
80
34
|
const format = props.constraints.format;
|
|
81
|
-
const
|
|
82
|
-
const
|
|
35
|
+
const dateType = dateInputType(format);
|
|
36
|
+
const isCredential = props.writeOnly && format === "password";
|
|
37
|
+
const inputType = dateType ?? (isCredential ? "password" : format === "email" ? "email" : format === "uri" ? "url" : "text");
|
|
38
|
+
const id = fieldDomId(props.path);
|
|
83
39
|
const attrs = {
|
|
84
40
|
class: SC_CLASSES.input,
|
|
85
41
|
id,
|
|
86
42
|
type: inputType,
|
|
87
43
|
name: id
|
|
88
44
|
};
|
|
45
|
+
if (isCredential) attrs.autocomplete = strValue.length > 0 ? "current-password" : "new-password";
|
|
89
46
|
if (!props.writeOnly) attrs.value = strValue;
|
|
90
47
|
if (typeof props.meta.description === "string") attrs.placeholder = props.meta.description;
|
|
91
48
|
if (props.constraints.minLength !== void 0) attrs.minlength = String(props.constraints.minLength);
|
|
@@ -99,24 +56,24 @@ function renderNumberHtml(props) {
|
|
|
99
56
|
return serialize(renderNumberEditable(props));
|
|
100
57
|
}
|
|
101
58
|
function renderNumberReadOnly(props) {
|
|
102
|
-
if (typeof props.value !== "number") return h("span", {
|
|
103
|
-
|
|
104
|
-
...ariaReadonlyAttrs()
|
|
105
|
-
}, "—");
|
|
106
|
-
return h("span", {
|
|
107
|
-
class: SC_CLASSES.value,
|
|
108
|
-
...ariaReadonlyAttrs()
|
|
109
|
-
}, props.value.toLocaleString());
|
|
59
|
+
if (typeof props.value !== "number") return h("span", { class: SC_CLASSES.valueEmpty }, "—");
|
|
60
|
+
return h("span", { class: SC_CLASSES.value }, props.value.toLocaleString());
|
|
110
61
|
}
|
|
111
62
|
function renderNumberEditable(props) {
|
|
112
63
|
const numValue = typeof props.value === "number" ? String(props.value) : "";
|
|
113
|
-
const id =
|
|
64
|
+
const id = fieldDomId(props.path);
|
|
65
|
+
const isInteger = props.tree.type === "number" ? props.tree.isInteger : false;
|
|
66
|
+
const inputMode = isInteger ? "numeric" : "decimal";
|
|
67
|
+
const multipleOf = props.constraints.multipleOf;
|
|
114
68
|
const attrs = {
|
|
115
69
|
class: SC_CLASSES.input,
|
|
116
70
|
id,
|
|
117
71
|
type: "number",
|
|
118
|
-
name: id
|
|
72
|
+
name: id,
|
|
73
|
+
inputmode: inputMode
|
|
119
74
|
};
|
|
75
|
+
if (multipleOf !== void 0) attrs.step = String(multipleOf);
|
|
76
|
+
else if (isInteger) attrs.step = "1";
|
|
120
77
|
if (!props.writeOnly) attrs.value = numValue;
|
|
121
78
|
if (props.constraints.minimum !== void 0) attrs.min = String(props.constraints.minimum);
|
|
122
79
|
if (props.constraints.maximum !== void 0) attrs.max = String(props.constraints.maximum);
|
|
@@ -129,17 +86,11 @@ function renderBooleanHtml(props) {
|
|
|
129
86
|
return serialize(renderBooleanEditable(props));
|
|
130
87
|
}
|
|
131
88
|
function renderBooleanReadOnly(props) {
|
|
132
|
-
if (typeof props.value !== "boolean") return h("span", {
|
|
133
|
-
|
|
134
|
-
...ariaReadonlyAttrs()
|
|
135
|
-
}, "—");
|
|
136
|
-
return h("span", {
|
|
137
|
-
class: "sc-value sc-value--boolean",
|
|
138
|
-
...ariaReadonlyAttrs()
|
|
139
|
-
}, props.value ? "Yes" : "No");
|
|
89
|
+
if (typeof props.value !== "boolean") return h("span", { class: SC_CLASSES.valueEmpty }, "—");
|
|
90
|
+
return h("span", { class: "sc-value sc-value--boolean" }, props.value ? "Yes" : "No");
|
|
140
91
|
}
|
|
141
92
|
function renderBooleanEditable(props) {
|
|
142
|
-
const id =
|
|
93
|
+
const id = fieldDomId(props.path);
|
|
143
94
|
const attrs = {
|
|
144
95
|
class: SC_CLASSES.input,
|
|
145
96
|
id,
|
|
@@ -157,18 +108,12 @@ function renderEnumHtml(props) {
|
|
|
157
108
|
}
|
|
158
109
|
function renderEnumReadOnly(props) {
|
|
159
110
|
const enumValue = typeof props.value === "string" ? props.value : "";
|
|
160
|
-
if (enumValue.length === 0) return h("span", {
|
|
161
|
-
|
|
162
|
-
...ariaReadonlyAttrs()
|
|
163
|
-
}, "—");
|
|
164
|
-
return h("span", {
|
|
165
|
-
class: SC_CLASSES.value,
|
|
166
|
-
...ariaReadonlyAttrs()
|
|
167
|
-
}, enumValue);
|
|
111
|
+
if (enumValue.length === 0) return h("span", { class: SC_CLASSES.valueEmpty }, "—");
|
|
112
|
+
return h("span", { class: SC_CLASSES.value }, enumValue);
|
|
168
113
|
}
|
|
169
114
|
function renderEnumEditable(props) {
|
|
170
115
|
const enumValue = typeof props.value === "string" ? props.value : "";
|
|
171
|
-
const id =
|
|
116
|
+
const id = fieldDomId(props.path);
|
|
172
117
|
const selectedValue = props.writeOnly ? "" : enumValue;
|
|
173
118
|
const enumValues = props.tree.type === "enum" ? props.tree.enumValues : [];
|
|
174
119
|
const optionNodes = [h("option", { value: "" }, `Select…`), ...enumValues.map((v) => {
|
|
@@ -268,8 +213,12 @@ function renderRecordNode(props) {
|
|
|
268
213
|
}
|
|
269
214
|
const children = [];
|
|
270
215
|
for (const [key, val] of Object.entries(obj)) {
|
|
216
|
+
const childInputId = buildInputId(props.path, key);
|
|
271
217
|
const childHtml = props.renderChild(valueType, val, key);
|
|
272
|
-
children.push(h("div", { class: SC_CLASSES.field }, h("label", {
|
|
218
|
+
children.push(h("div", { class: SC_CLASSES.field }, h("label", {
|
|
219
|
+
class: SC_CLASSES.label,
|
|
220
|
+
for: childInputId
|
|
221
|
+
}, key), raw(childHtml)));
|
|
273
222
|
}
|
|
274
223
|
return h("div", attrs, ...children);
|
|
275
224
|
}
|
|
@@ -307,14 +256,14 @@ function renderDiscriminatedUnionHtml(props) {
|
|
|
307
256
|
if (activeOption !== void 0) return props.renderChild(activeOption, props.value);
|
|
308
257
|
return serialize(h("span", { class: SC_CLASSES.valueEmpty }, "—"));
|
|
309
258
|
}
|
|
310
|
-
const tabPanelId =
|
|
259
|
+
const tabPanelId = panelIdFor(props.path);
|
|
311
260
|
const tabButtons = options.map((_opt, i) => {
|
|
312
261
|
return h("button", {
|
|
313
262
|
type: "button",
|
|
314
263
|
role: "tab",
|
|
315
264
|
class: i === activeIndex ? SC_CLASSES.tabActive : SC_CLASSES.tab,
|
|
316
|
-
id:
|
|
317
|
-
"aria-selected": i === activeIndex ? "true" :
|
|
265
|
+
id: tabIdFor(props.path, i),
|
|
266
|
+
"aria-selected": i === activeIndex ? "true" : "false",
|
|
318
267
|
"aria-controls": tabPanelId,
|
|
319
268
|
tabindex: i === activeIndex ? "0" : "-1"
|
|
320
269
|
}, optionLabels[i]);
|
|
@@ -322,25 +271,25 @@ function renderDiscriminatedUnionHtml(props) {
|
|
|
322
271
|
const children = [h("div", {
|
|
323
272
|
role: "tablist",
|
|
324
273
|
class: SC_CLASSES.tabs,
|
|
325
|
-
"aria-label": "Select variant"
|
|
274
|
+
"aria-label": "Select variant",
|
|
275
|
+
"aria-orientation": "horizontal"
|
|
326
276
|
}, ...tabButtons)];
|
|
327
277
|
if (activeOption !== void 0) {
|
|
328
278
|
const childHtml = props.renderChild(activeOption, props.value);
|
|
329
279
|
children.push(h("div", {
|
|
330
280
|
role: "tabpanel",
|
|
331
281
|
id: tabPanelId,
|
|
332
|
-
"aria-labelledby":
|
|
282
|
+
"aria-labelledby": tabIdFor(props.path, activeIndex)
|
|
333
283
|
}, raw(childHtml)));
|
|
334
284
|
}
|
|
335
285
|
return serialize(h("div", { class: SC_CLASSES.discriminatedUnion }, ...children));
|
|
336
286
|
}
|
|
337
287
|
function renderFileHtml(props) {
|
|
338
|
-
const id =
|
|
288
|
+
const id = fieldDomId(props.path);
|
|
339
289
|
const accept = props.constraints.mimeTypes?.join(",");
|
|
340
290
|
if (props.readOnly) return serialize(h("span", {
|
|
341
291
|
class: SC_CLASSES.value,
|
|
342
|
-
id
|
|
343
|
-
...ariaReadonlyAttrs()
|
|
292
|
+
id
|
|
344
293
|
}, "File field"));
|
|
345
294
|
const attrs = {
|
|
346
295
|
class: SC_CLASSES.input,
|
|
@@ -407,7 +356,7 @@ function renderNegationHtml(props) {
|
|
|
407
356
|
* The only valid value is `null`, so render an em-dash placeholder.
|
|
408
357
|
*/
|
|
409
358
|
function renderNullHtml(props) {
|
|
410
|
-
const id =
|
|
359
|
+
const id = fieldDomId(props.path);
|
|
411
360
|
return serialize(h("span", {
|
|
412
361
|
class: SC_CLASSES.valueEmpty,
|
|
413
362
|
id,
|
|
@@ -424,10 +373,15 @@ function renderNullHtml(props) {
|
|
|
424
373
|
function renderNeverHtml(props) {
|
|
425
374
|
return serialize(h("span", {
|
|
426
375
|
class: "sc-value sc-never",
|
|
427
|
-
id:
|
|
428
|
-
...ariaReadonlyAttrs()
|
|
376
|
+
id: fieldDomId(props.path)
|
|
429
377
|
}, h("em", {}, "never matches")));
|
|
430
378
|
}
|
|
379
|
+
/**
|
|
380
|
+
* Default HTML resolver used by `renderToHtml` and the streaming
|
|
381
|
+
* renderers when the consumer does not pass a custom resolver. Maps
|
|
382
|
+
* every `WalkedField` variant to a semantic HTML renderer built on the
|
|
383
|
+
* `h()` element builder.
|
|
384
|
+
*/
|
|
431
385
|
const defaultHtmlResolver = {
|
|
432
386
|
string: renderStringHtml,
|
|
433
387
|
number: renderNumberHtml,
|
|
@@ -448,4 +402,4 @@ const defaultHtmlResolver = {
|
|
|
448
402
|
unknown: renderUnknownHtml
|
|
449
403
|
};
|
|
450
404
|
//#endregion
|
|
451
|
-
export { dateInputType, defaultHtmlResolver,
|
|
405
|
+
export { dateInputType, defaultHtmlResolver, matchUnionOption };
|