schema-components 2.1.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +41 -6
  2. package/dist/{SchemaComponent-B__6-5-E.d.mts → SchemaComponent-CRgCVDhz.d.mts} +27 -15
  3. package/dist/{SchemaComponent-BxzzsHsK.mjs → SchemaComponent-Cga5oJfP.mjs} +3 -3
  4. package/dist/core/renderField.mjs +41 -7
  5. package/dist/html/streamRenderers.d.mts +12 -3
  6. package/dist/html/streamRenderers.mjs +56 -10
  7. package/dist/lit/SchemaComponent.d.mts +2 -2
  8. package/dist/lit/SchemaComponent.mjs +1 -1
  9. package/dist/lit/SchemaField.d.mts +1 -1
  10. package/dist/lit/SchemaField.mjs +1 -1
  11. package/dist/lit/SchemaView.mjs +1 -1
  12. package/dist/lit/defaultResolver.mjs +1 -1
  13. package/dist/lit/registry.mjs +1 -1
  14. package/dist/preact/SchemaComponent.d.mts +1 -1
  15. package/dist/react/SchemaComponent.d.mts +1 -1
  16. package/dist/react/SchemaComponent.mjs +6 -6
  17. package/dist/react/SchemaView.d.mts +16 -11
  18. package/dist/react/SchemaView.mjs +2 -2
  19. package/dist/solid/SchemaComponent.d.mts +6 -6
  20. package/dist/solid/SchemaComponent.mjs +1 -1
  21. package/dist/solid/SchemaField.d.mts +3 -3
  22. package/dist/solid/SchemaField.mjs +1 -1
  23. package/dist/solid/SchemaView.d.mts +5 -5
  24. package/dist/solid/SchemaView.mjs +1 -1
  25. package/package.json +5 -3
  26. package/src/svelte/SchemaComponent.svelte +3 -3
  27. package/src/svelte/SchemaField.svelte +3 -3
  28. package/src/svelte/SchemaView.svelte +3 -3
  29. package/src/vue/SchemaComponent.vue +274 -0
  30. package/src/vue/SchemaErrorBoundary.vue +60 -0
  31. package/src/vue/SchemaField.vue +178 -0
  32. package/src/vue/SchemaProvider.vue +39 -0
  33. package/src/vue/SchemaView.vue +198 -0
  34. package/src/vue/VNodeHost.ts +32 -0
  35. package/src/vue/contexts.ts +116 -0
  36. package/src/vue/eventTargets.ts +35 -0
  37. package/src/vue/headless.ts +61 -0
  38. package/src/vue/idPrefix.ts +79 -0
  39. package/src/vue/renderField.ts +182 -0
  40. package/src/vue/renderers.ts +1297 -0
  41. package/src/vue/resolver.ts +45 -0
  42. package/src/vue/types.ts +140 -0
  43. package/src/vue/vue-shim.d.ts +25 -0
  44. package/src/vue/widget.ts +51 -0
package/README.md CHANGED
@@ -117,6 +117,41 @@ For yarn, use the `resolutions` field with the same value.
117
117
 
118
118
  The test suite is parametrised with a `unit-preact` Vitest project that runs the same files under `preact/compat` aliasing. Run it via `pnpm test:preact` from the repo root, or directly with `pnpm exec vitest run --project=unit-preact` from `packages/core`. A small set of tests (the ones bound to the three known limitations above) fail intentionally; the remaining ~99% pass under both runtimes and establish the cross-runtime regression boundary.
119
119
 
120
+ ### Vue support
121
+
122
+ Vue 3.5+ is supported as an optional peer dependency. The Vue adapter ships as **source** under `schema-components/vue/*` — the `.vue` Single File Components are not pre-compiled into the published tarball. Consumers need a Vite or webpack toolchain with `@vitejs/plugin-vue` (or equivalent) to compile the SFCs at their own build step.
123
+
124
+ ```ts
125
+ // vite.config.ts
126
+ import { defineConfig } from "vite";
127
+ import vue from "@vitejs/plugin-vue";
128
+
129
+ export default defineConfig({
130
+ plugins: [vue()],
131
+ });
132
+ ```
133
+
134
+ ```vue
135
+ <script setup lang="ts">
136
+ import { ref } from "vue";
137
+ import SchemaComponent from "schema-components/vue/SchemaComponent.vue";
138
+ import { z } from "zod";
139
+
140
+ const userSchema = z.object({
141
+ name: z.string(),
142
+ email: z.email(),
143
+ });
144
+
145
+ const user = ref({ name: "Ada", email: "ada@example.com" });
146
+ </script>
147
+
148
+ <template>
149
+ <SchemaComponent :schema="userSchema" v-model="user" />
150
+ </template>
151
+ ```
152
+
153
+ The `./vue/*` export subpath resolves to the source tree (`src/vue/*.ts`) and `./vue/*.vue` resolves to the `.vue` SFCs under `src/vue/`. The same pattern is used by the Svelte adapter — both rely on the consumer's bundler to handle the framework-specific compilation step.
154
+
120
155
  ## `SchemaComponent`
121
156
 
122
157
  The single entry point. Accepts Zod schemas, JSON Schema objects, or OpenAPI documents:
@@ -130,8 +165,8 @@ import { SchemaComponent } from "schema-components/react/SchemaComponent";
130
165
  // JSON Schema object
131
166
  <SchemaComponent schema={{ type: "object", properties: { name: { type: "string" } } }} value={data} />
132
167
 
133
- // OpenAPI document + ref
134
- <SchemaComponent schema={openApiSpec} ref="#/components/schemas/User" value={data} />
168
+ // OpenAPI document + schemaRef
169
+ <SchemaComponent schema={openApiSpec} schemaRef="#/components/schemas/User" value={data} />
135
170
  ```
136
171
 
137
172
  ### Props
@@ -143,7 +178,7 @@ import { SchemaComponent } from "schema-components/react/SchemaComponent";
143
178
  | `onChange` | `(value: unknown) => void` | Callback when value changes |
144
179
  | `readOnly` | `boolean` | Force read-only presentation |
145
180
  | `writeOnly` | `boolean` | Force write-only (blank inputs) |
146
- | `ref` | `string` | JSON Pointer into OpenAPI document |
181
+ | `schemaRef` | `string` | JSON Pointer into OpenAPI document |
147
182
  | `fields` | `InferFields<T>` | Type-safe per-field overrides |
148
183
  | `widgets` | `WidgetMap` | Instance-scoped widget overrides |
149
184
  | `validate` | `boolean` | Enable Zod validation on change |
@@ -222,7 +257,7 @@ const jsonSchema = {
222
257
  }}
223
258
  />
224
259
 
225
- // OpenAPI as const + ref — full autocomplete
260
+ // OpenAPI as const + schemaRef — full autocomplete
226
261
  const spec = {
227
262
  openapi: "3.1.0",
228
263
  components: {
@@ -241,9 +276,9 @@ const spec = {
241
276
 
242
277
  <SchemaComponent
243
278
  schema={spec}
244
- ref="#/components/schemas/User"
279
+ schemaRef="#/components/schemas/User"
245
280
  fields={{
246
- id: { readOnly: true }, // ✓ inferred through ref
281
+ id: { readOnly: true }, // ✓ inferred through schemaRef
247
282
  }}
248
283
  />
249
284
  ```
@@ -66,7 +66,7 @@ declare function __clearGlobalWidgets(): void;
66
66
  *
67
67
  * @group Components
68
68
  */
69
- interface SchemaComponentProps<T = unknown, Ref extends string | undefined = undefined, Mode extends SchemaIoSide = "output"> {
69
+ interface SchemaComponentProps<T = unknown, SchemaRef extends string | undefined = undefined, Mode extends SchemaIoSide = "output"> {
70
70
  /**
71
71
  * Zod schema, JSON Schema object, or OpenAPI document.
72
72
  *
@@ -78,8 +78,16 @@ interface SchemaComponentProps<T = unknown, Ref extends string | undefined = und
78
78
  * — the static rejection surfaces the same failure at compile time.
79
79
  */
80
80
  schema: RejectUnrepresentableZod<T>;
81
- /** For OpenAPI: a ref string like "#/components/schemas/User" or "/users/post". */
82
- ref?: Ref;
81
+ /**
82
+ * For OpenAPI / JSON Schema documents: a `$ref` string pointing at
83
+ * the sub-schema to render — e.g. `"#/components/schemas/User"` or
84
+ * `"/users/post"`.
85
+ *
86
+ * Named `schemaRef` (not `ref`) so the prop survives the React /
87
+ * `preact/compat` `createElement` boundary, which strips the
88
+ * reserved `ref` name from the vnode prop bag.
89
+ */
90
+ schemaRef?: SchemaRef;
83
91
  /**
84
92
  * Which side of every transform / pipe / codec to render.
85
93
  *
@@ -103,8 +111,8 @@ interface SchemaComponentProps<T = unknown, Ref extends string | undefined = und
103
111
  io?: Mode;
104
112
  /**
105
113
  * Current value to render. Typed against
106
- * `InferSchemaValue<T, Ref, Mode>` so the prop tracks the schema's
107
- * inferred shape for the chosen `io` direction.
114
+ * `InferSchemaValue<T, SchemaRef, Mode>` so the prop tracks the
115
+ * schema's inferred shape for the chosen `io` direction.
108
116
  *
109
117
  * Falls back to `unknown` when the schema's value type cannot be
110
118
  * statically inferred (runtime `Record<string, unknown>` JSON
@@ -119,7 +127,7 @@ interface SchemaComponentProps<T = unknown, Ref extends string | undefined = und
119
127
  * <SchemaComponent schema={userSchema} value={user} readOnly />
120
128
  * ```
121
129
  */
122
- value?: InferSchemaValue<T, Ref, Mode>;
130
+ value?: InferSchemaValue<T, SchemaRef, Mode>;
123
131
  /**
124
132
  * Called when the value changes (editable fields). The parameter
125
133
  * shares the same shape as {@link SchemaComponentProps.value} so
@@ -129,7 +137,7 @@ interface SchemaComponentProps<T = unknown, Ref extends string | undefined = und
129
137
  * Falls back to `unknown` for schemas whose value type cannot be
130
138
  * statically inferred — see {@link SchemaComponentProps.value}.
131
139
  */
132
- onChange?: (value: InferSchemaValue<T, Ref, Mode>) => void;
140
+ onChange?: (value: InferSchemaValue<T, SchemaRef, Mode>) => void;
133
141
  /** Run schema.safeParse() on change and surface errors via onValidationError. */
134
142
  validate?: boolean;
135
143
  /** Called with the ZodError when validation fails. */
@@ -141,7 +149,7 @@ interface SchemaComponentProps<T = unknown, Ref extends string | undefined = und
141
149
  /** When true, any diagnostic becomes a thrown error. */
142
150
  strict?: boolean;
143
151
  /** Per-field meta overrides — nested object mirroring schema shape. */
144
- fields?: InferFields<T, Ref>;
152
+ fields?: InferFields<T, SchemaRef>;
145
153
  /** Meta overrides applied to the root schema. */
146
154
  meta?: SchemaMeta;
147
155
  /** Convenience: sets readOnly on all fields. */
@@ -183,7 +191,7 @@ interface SchemaComponentProps<T = unknown, Ref extends string | undefined = und
183
191
  * <SchemaComponent schema={userSchema} value={user} onChange={setUser} />
184
192
  * ```
185
193
  */
186
- declare function SchemaComponent<T = unknown, Ref extends string | undefined = undefined, Mode extends SchemaIoSide = "output">(props: SchemaComponentProps<T, Ref, Mode>): ReactNode;
194
+ declare function SchemaComponent<T = unknown, SchemaRef extends string | undefined = undefined, Mode extends SchemaIoSide = "output">(props: SchemaComponentProps<T, SchemaRef, Mode>): ReactNode;
187
195
  /**
188
196
  * Append a child path suffix to a parent path. When the suffix is omitted
189
197
  * (e.g. transparent wrappers like union options), the parent path is
@@ -229,7 +237,7 @@ type InferSchemaType<T> = T extends z.ZodType ? z.infer<T> : T extends object ?
229
237
  *
230
238
  * @group Components
231
239
  */
232
- interface SchemaFieldProps<T = unknown, Ref extends string | undefined = undefined, P extends string = PathOfType<InferSchemaType<T>> | (string extends PathOfType<InferSchemaType<T>> ? string : never)> {
240
+ interface SchemaFieldProps<T = unknown, SchemaRef extends string | undefined = undefined, P extends string = PathOfType<InferSchemaType<T>> | (string extends PathOfType<InferSchemaType<T>> ? string : never)> {
233
241
  /**
234
242
  * Dot-separated path to the field (e.g. "address.city").
235
243
  * When the schema is a Zod schema or typed `as const`, only valid
@@ -241,8 +249,12 @@ interface SchemaFieldProps<T = unknown, Ref extends string | undefined = undefin
241
249
  * unrepresentable-Zod rejection as {@link SchemaComponentProps.schema}.
242
250
  */
243
251
  schema: RejectUnrepresentableZod<T>;
244
- /** For OpenAPI: a ref string. */
245
- ref?: Ref;
252
+ /**
253
+ * For OpenAPI / JSON Schema documents: a `$ref` string. Named
254
+ * `schemaRef` (not `ref`) to avoid the React / `preact/compat`
255
+ * reserved prop name. See {@link SchemaComponentProps.schemaRef}.
256
+ */
257
+ schemaRef?: SchemaRef;
246
258
  /** Current value of the field at the given path. */
247
259
  value?: unknown;
248
260
  /** Called with the updated root value when this field changes. */
@@ -263,15 +275,15 @@ interface SchemaFieldProps<T = unknown, Ref extends string | undefined = undefin
263
275
  *
264
276
  * @group Components
265
277
  */
266
- declare function SchemaField<T = unknown, Ref extends string | undefined = undefined, P extends string = PathOfType<InferSchemaType<T>> | (string extends PathOfType<InferSchemaType<T>> ? string : never)>({
278
+ declare function SchemaField<T = unknown, SchemaRef extends string | undefined = undefined, P extends string = PathOfType<InferSchemaType<T>> | (string extends PathOfType<InferSchemaType<T>> ? string : never)>({
267
279
  path,
268
280
  schema: schemaInput,
269
- ref: refInput,
281
+ schemaRef: schemaRefInput,
270
282
  value,
271
283
  onChange,
272
284
  meta: fieldMeta,
273
285
  validate,
274
286
  onValidationError
275
- }: SchemaFieldProps<T, Ref, P>): ReactNode;
287
+ }: SchemaFieldProps<T, SchemaRef, P>): ReactNode;
276
288
  //#endregion
277
289
  export { SchemaProvider as a, registerWidget as c, SchemaFieldProps as i, renderField as l, SchemaComponentProps as n, __clearGlobalWidgets as o, SchemaField as r, joinPath as s, SchemaComponent as t, sanitisePrefix as u };
@@ -95,7 +95,7 @@ var SchemaField = class extends SchemaComponent {
95
95
  let rootMeta;
96
96
  let rootDocument;
97
97
  try {
98
- const normalised = normaliseSchema(this.schema, this.ref);
98
+ const normalised = normaliseSchema(this.schema, this.schemaRef);
99
99
  jsonSchema = normalised.jsonSchema;
100
100
  rootMeta = normalised.rootMeta;
101
101
  rootDocument = normalised.rootDocument;
@@ -504,7 +504,7 @@ var SchemaComponent = class extends LitElement {
504
504
  widgets: { attribute: false },
505
505
  fields: { attribute: false },
506
506
  meta: { attribute: false },
507
- ref: { attribute: false },
507
+ schemaRef: { attribute: false },
508
508
  io: { attribute: false },
509
509
  idPrefix: { attribute: false },
510
510
  onDiagnostic: { attribute: false },
@@ -533,7 +533,7 @@ var SchemaComponent = class extends LitElement {
533
533
  ...diagnosticsOptions !== void 0 ? { diagnostics: diagnosticsOptions } : {},
534
534
  ...this.io !== void 0 ? { io: this.io } : {}
535
535
  } : void 0;
536
- const normalised = normaliseSchema(this.schema, this.ref, normaliseOptions);
536
+ const normalised = normaliseSchema(this.schema, this.schemaRef, normaliseOptions);
537
537
  jsonSchema = normalised.jsonSchema;
538
538
  rootMeta = normalised.rootMeta;
539
539
  rootDocument = normalised.rootDocument;
@@ -6,13 +6,14 @@ import { SchemaRenderError } from "./errors.mjs";
6
6
  *
7
7
  * Centralises the dispatch loop shared by the React `SchemaComponent` /
8
8
  * `SchemaView` renderers, the synchronous HTML renderer in
9
- * `renderToHtml`, and (in the future) Vue / Solid / Svelte / Lit
10
- * adapters. The dispatcher is intentionally framework-agnostic: it
11
- * neither imports React nor produces HTML strings directly. Each
12
- * adapter supplies a small {@link DispatchConfig} describing how to
13
- * build per-field props, how to handle a successful or absent resolver
14
- * lookup, and (optionally) how to handle widget overrides and the
15
- * recursion-depth cap.
9
+ * `renderToHtml`, the streaming HTML renderer in `streamRenderers.ts`
10
+ * (for its leaf path see "Streaming integration" below), and (in
11
+ * the future) Vue / Solid / Svelte / Lit adapters. The dispatcher is
12
+ * intentionally framework-agnostic: it neither imports React nor
13
+ * produces HTML strings directly. Each adapter supplies a small
14
+ * {@link DispatchConfig} describing how to build per-field props, how
15
+ * to handle a successful or absent resolver lookup, and (optionally)
16
+ * how to handle widget overrides and the recursion-depth cap.
16
17
  *
17
18
  * The dispatch order is fixed and matches the historic React-side
18
19
  * behaviour so the React, HTML, and future adapters all observe the
@@ -32,6 +33,39 @@ import { SchemaRenderError } from "./errors.mjs";
32
33
  * The helpers that find render functions, merge resolvers, and build
33
34
  * the per-field props live in {@link "./renderer.ts"} and are reused
34
35
  * here — `core/renderField.ts` is purely the dispatch shell.
36
+ *
37
+ * # Streaming integration (design choice B)
38
+ *
39
+ * The streaming HTML renderer (`html/streamRenderers.ts` +
40
+ * `html/renderToHtmlStream.ts`) consumes this dispatcher for leaf
41
+ * field types — `string`, `number`, `boolean`, `enum`, `literal`,
42
+ * `file`, `unknown` — and for variants without a dedicated streaming
43
+ * generator (`null`, `tuple`, `conditional`, `negation`, `never`).
44
+ * Container types (`object`, `array`, `record`, `union`,
45
+ * `discriminatedUnion`) keep bespoke generator implementations
46
+ * because the dispatcher's single-output contract cannot express the
47
+ * "yield opening tag → recurse into children → yield closing tag"
48
+ * chunk-boundary semantics that streaming depends on.
49
+ *
50
+ * We deliberately chose this approach (the Phase 1 agent's "option
51
+ * B" — leaves dispatch through the shared loop, containers keep their
52
+ * own iteration) over the alternative of building a generator-output
53
+ * mode into the dispatcher itself. Approach B preserves the existing
54
+ * chunk boundaries byte-for-byte while still eliminating the duplicate
55
+ * resolver-lookup logic that previously lived in `renderLeaf`. A
56
+ * generator-aware dispatcher would require either a parallel "stream
57
+ * resolver" shape or a unified return type wide enough to cover both
58
+ * single-output and iterable cases — neither of which is justified by
59
+ * the small amount of dispatch logic the leaf path needs.
60
+ *
61
+ * The streaming `streamField` function performs its own depth check
62
+ * before invoking the dispatcher for leaf paths. The check appears
63
+ * textually in both places (streamField and this dispatcher) but at
64
+ * runtime fires exactly once per recursion step: streamField's guard
65
+ * filters the streaming path, and the dispatcher's guard remains in
66
+ * place for the sync HTML and React callers that do not pre-filter
67
+ * depth themselves. See `html/streamRenderers.ts` for the matching
68
+ * commentary.
35
69
  */
36
70
  /**
37
71
  * Framework-agnostic dispatch loop shared by the React, HTML, and
@@ -19,10 +19,19 @@ declare function yieldClose(el: HtmlElement): string;
19
19
  /**
20
20
  * Render a leaf {@link WalkedField} entirely as a single HTML chunk.
21
21
  * Used inside the streaming generators when descent into containers is
22
- * complete. Falls back to a `<span>`-wrapped value when no renderer is
23
- * registered for the field type.
22
+ * complete.
23
+ *
24
+ * Delegates to the framework-agnostic {@link dispatchRenderField}
25
+ * dispatcher so resolver lookup, the (unused) widget step, error
26
+ * wrapping, and the unresolved-type fallback share one implementation
27
+ * with the sync HTML renderer and the React adapter.
28
+ *
29
+ * @param depth - Current recursion depth — defaults to `0` so the
30
+ * public signature stays additive. Callers inside the streaming
31
+ * pipeline thread the live `currentDepth` so the dispatcher's
32
+ * internal depth check is consistent with the streamField gate.
24
33
  */
25
- declare function renderLeaf(tree: WalkedField, value: unknown, mergedResolver: HtmlResolver, path: string): string;
34
+ declare function renderLeaf(tree: WalkedField, value: unknown, mergedResolver: HtmlResolver, path: string, depth?: number): string;
26
35
  /**
27
36
  * Drain {@link streamField} into a single string. Used when a streamed
28
37
  * sub-tree needs to be embedded inside a non-streaming chunk (e.g. as
@@ -3,6 +3,7 @@ import "../core/limits.mjs";
3
3
  import { emitDiagnostic } from "../core/diagnostics.mjs";
4
4
  import { SC_CLASSES } from "../core/cssClasses.mjs";
5
5
  import { panelIdFor, tabIdFor } from "../core/idPath.mjs";
6
+ import { dispatchRenderField } from "../core/renderField.mjs";
6
7
  import { getHtmlRenderFn } from "../core/renderer.mjs";
7
8
  import { matchUnionOption, resolveDiscriminatedActive } from "../core/unionMatch.mjs";
8
9
  import { VOID_ELEMENTS, h, raw, serialize, serializeAttributes } from "./html.mjs";
@@ -29,14 +30,15 @@ function yieldClose(el) {
29
30
  return `</${el.tag}>`;
30
31
  }
31
32
  /**
32
- * Render a leaf {@link WalkedField} entirely as a single HTML chunk.
33
- * Used inside the streaming generators when descent into containers is
34
- * complete. Falls back to a `<span>`-wrapped value when no renderer is
35
- * registered for the field type.
33
+ * Build the per-leaf {@link HtmlRenderProps} bundle handed to resolver
34
+ * render functions and (in future) to widget renderers. The streaming
35
+ * pipeline never recurses through `renderChild` for leaves container
36
+ * recursion happens through the streamField generators — so the
37
+ * supplied `renderChild` is the constant `() => ""` stub matching the
38
+ * historic streaming-leaf shape.
36
39
  */
37
- function renderLeaf(tree, value, mergedResolver, path) {
38
- const renderFn = getHtmlRenderFn(tree.type, mergedResolver);
39
- if (renderFn !== void 0) return renderFn({
40
+ function buildLeafProps(tree, value, path) {
41
+ const props = {
40
42
  value,
41
43
  readOnly: tree.editability === "presentation",
42
44
  writeOnly: tree.editability === "input",
@@ -45,11 +47,55 @@ function renderLeaf(tree, value, mergedResolver, path) {
45
47
  path,
46
48
  tree,
47
49
  renderChild: () => ""
48
- });
50
+ };
51
+ if (tree.examples !== void 0) props.examples = tree.examples;
52
+ return props;
53
+ }
54
+ /**
55
+ * Build the streaming-friendly placeholder a {@link dispatchRenderField}
56
+ * fallback emits when no resolver handled the leaf type. The sync HTML
57
+ * dispatcher throws in this position — streaming must keep producing
58
+ * output, so the streaming adapter renders the same `<span>` shape
59
+ * `renderLeaf` historically produced for unresolved types.
60
+ */
61
+ function leafFallbackHtml(value) {
49
62
  if (value === void 0 || value === null) return serialize(h("span", { class: SC_CLASSES.valueEmpty }, "—"));
50
63
  return serialize(h("span", { class: SC_CLASSES.value }, typeof value === "string" ? value : JSON.stringify(value)));
51
64
  }
52
65
  /**
66
+ * Render a leaf {@link WalkedField} entirely as a single HTML chunk.
67
+ * Used inside the streaming generators when descent into containers is
68
+ * complete.
69
+ *
70
+ * Delegates to the framework-agnostic {@link dispatchRenderField}
71
+ * dispatcher so resolver lookup, the (unused) widget step, error
72
+ * wrapping, and the unresolved-type fallback share one implementation
73
+ * with the sync HTML renderer and the React adapter.
74
+ *
75
+ * @param depth - Current recursion depth — defaults to `0` so the
76
+ * public signature stays additive. Callers inside the streaming
77
+ * pipeline thread the live `currentDepth` so the dispatcher's
78
+ * internal depth check is consistent with the streamField gate.
79
+ */
80
+ function renderLeaf(tree, value, mergedResolver, path, depth = 0) {
81
+ return dispatchRenderField({
82
+ tree,
83
+ value,
84
+ path,
85
+ depth,
86
+ resolver: mergedResolver,
87
+ config: {
88
+ buildProps: (fieldTree, fieldPath) => buildLeafProps(fieldTree, value, fieldPath),
89
+ lookupRenderFn: (type, htmlResolver) => getHtmlRenderFn(type, htmlResolver),
90
+ recursionSentinel: (fieldTree) => {
91
+ return recursionSentinelHtml(typeof fieldTree.meta.description === "string" ? fieldTree.meta.description : "schema");
92
+ },
93
+ fallback: (_fieldTree, fieldValue) => leafFallbackHtml(fieldValue),
94
+ coerceResult: (result) => typeof result === "string" ? result : void 0
95
+ }
96
+ });
97
+ }
98
+ /**
53
99
  * Drain {@link streamField} into a single string. Used when a streamed
54
100
  * sub-tree needs to be embedded inside a non-streaming chunk (e.g. as
55
101
  * children of a parent element).
@@ -87,7 +133,7 @@ function* streamField(tree, value, mergedResolver, path, rawResolver, currentDep
87
133
  const effectiveValue = value ?? tree.defaultValue;
88
134
  const type = tree.type;
89
135
  if (type === "string" || type === "number" || type === "boolean" || type === "enum" || type === "literal" || type === "file" || type === "unknown") {
90
- yield renderLeaf(tree, effectiveValue, mergedResolver, path);
136
+ yield renderLeaf(tree, effectiveValue, mergedResolver, path, currentDepth);
91
137
  return;
92
138
  }
93
139
  if (type === "union") {
@@ -110,7 +156,7 @@ function* streamField(tree, value, mergedResolver, path, rawResolver, currentDep
110
156
  yield* streamRecord(tree, value, mergedResolver, path, rawResolver, currentDepth, diagnostics);
111
157
  return;
112
158
  }
113
- yield renderLeaf(tree, value, mergedResolver, path);
159
+ yield renderLeaf(tree, value, mergedResolver, path, currentDepth);
114
160
  }
115
161
  function* streamObject(tree, value, mergedResolver, path, rawResolver, currentDepth, diagnostics) {
116
162
  if (tree.type !== "object") return;
@@ -66,7 +66,7 @@ declare class SchemaComponent extends LitElement {
66
66
  meta: {
67
67
  attribute: boolean;
68
68
  };
69
- ref: {
69
+ schemaRef: {
70
70
  attribute: boolean;
71
71
  };
72
72
  io: {
@@ -94,7 +94,7 @@ declare class SchemaComponent extends LitElement {
94
94
  * values are seeded in the constructor.
95
95
  */
96
96
  schema: unknown;
97
- ref: string | undefined;
97
+ schemaRef: string | undefined;
98
98
  io: SchemaIoSide | undefined;
99
99
  value: unknown;
100
100
  resolver: LitComponentResolver | undefined;
@@ -1,2 +1,2 @@
1
- import { t as SchemaComponent } from "../SchemaComponent-BxzzsHsK.mjs";
1
+ import { t as SchemaComponent } from "../SchemaComponent-Cga5oJfP.mjs";
2
2
  export { SchemaComponent };
@@ -37,7 +37,7 @@ declare class SchemaField extends SchemaComponent {
37
37
  meta: {
38
38
  attribute: boolean;
39
39
  };
40
- ref: {
40
+ schemaRef: {
41
41
  attribute: boolean;
42
42
  };
43
43
  io: {
@@ -1,2 +1,2 @@
1
- import { o as SchemaField } from "../SchemaComponent-BxzzsHsK.mjs";
1
+ import { o as SchemaField } from "../SchemaComponent-Cga5oJfP.mjs";
2
2
  export { SchemaField };
@@ -1,2 +1,2 @@
1
- import { s as SchemaView } from "../SchemaComponent-BxzzsHsK.mjs";
1
+ import { s as SchemaView } from "../SchemaComponent-Cga5oJfP.mjs";
2
2
  export { SchemaView };
@@ -1,2 +1,2 @@
1
- import { n as TYPE_TO_CANONICAL_TAG, r as createDefaultLitResolver } from "../SchemaComponent-BxzzsHsK.mjs";
1
+ import { n as TYPE_TO_CANONICAL_TAG, r as createDefaultLitResolver } from "../SchemaComponent-Cga5oJfP.mjs";
2
2
  export { TYPE_TO_CANONICAL_TAG, createDefaultLitResolver };
@@ -1,2 +1,2 @@
1
- import { a as registerSchemaComponents, i as BUILT_IN_ELEMENTS } from "../SchemaComponent-BxzzsHsK.mjs";
1
+ import { a as registerSchemaComponents, i as BUILT_IN_ELEMENTS } from "../SchemaComponent-Cga5oJfP.mjs";
2
2
  export { BUILT_IN_ELEMENTS, registerSchemaComponents };
@@ -1,3 +1,3 @@
1
1
  import { a as InferredValue, i as InferredOutputValue, r as InferredInputValue, t as InferFields } from "../inferValue-eAnh50EM.mjs";
2
- import { a as SchemaProvider, c as registerWidget, i as SchemaFieldProps, n as SchemaComponentProps, r as SchemaField, t as SchemaComponent } from "../SchemaComponent-B__6-5-E.mjs";
2
+ import { a as SchemaProvider, c as registerWidget, i as SchemaFieldProps, n as SchemaComponentProps, r as SchemaField, t as SchemaComponent } from "../SchemaComponent-CRgCVDhz.mjs";
3
3
  export { type InferFields, type InferredInputValue, type InferredOutputValue, type InferredValue, SchemaComponent, type SchemaComponentProps, SchemaField, type SchemaFieldProps, SchemaProvider, registerWidget };
@@ -1,3 +1,3 @@
1
1
  import { a as InferredValue, i as InferredOutputValue, r as InferredInputValue, t as InferFields } from "../inferValue-eAnh50EM.mjs";
2
- import { a as SchemaProvider, c as registerWidget, i as SchemaFieldProps, l as renderField, n as SchemaComponentProps, o as __clearGlobalWidgets, r as SchemaField, s as joinPath, t as SchemaComponent, u as sanitisePrefix } from "../SchemaComponent-B__6-5-E.mjs";
2
+ import { a as SchemaProvider, c as registerWidget, i as SchemaFieldProps, l as renderField, n as SchemaComponentProps, o as __clearGlobalWidgets, r as SchemaField, s as joinPath, t as SchemaComponent, u as sanitisePrefix } from "../SchemaComponent-CRgCVDhz.mjs";
3
3
  export { InferFields, InferredInputValue, InferredOutputValue, InferredValue, SchemaComponent, SchemaComponentProps, SchemaField, SchemaFieldProps, SchemaProvider, __clearGlobalWidgets, joinPath, registerWidget, renderField, sanitisePrefix };
@@ -21,7 +21,7 @@ import { createContext, isValidElement, useCallback, useContext, useId, useMemo
21
21
  * The `fields` prop type is inferred from the `schema` prop:
22
22
  * - Zod schemas → `FieldOverrides<z.infer<T>>` (full autocomplete)
23
23
  * - JSON Schema `as const` → `FieldOverrides<FromJSONSchema<T>>` (full autocomplete)
24
- * - OpenAPI `as const` + `ref` → `FieldOverrides<ResolveOpenAPIRef<T, Ref>>`
24
+ * - OpenAPI `as const` + `schemaRef` → `FieldOverrides<ResolveOpenAPIRef<T, SchemaRef>>`
25
25
  * - Runtime schemas → `Record<string, FieldOverride>` (no autocomplete)
26
26
  */
27
27
  const UserResolverContext = createContext(void 0);
@@ -103,7 +103,7 @@ function __clearGlobalWidgets() {
103
103
  * ```
104
104
  */
105
105
  function SchemaComponent(props) {
106
- const { schema: schemaInput, ref: refInput, io, value, onChange, validate, onValidationError, onError, onDiagnostic, strict, fields, meta: componentMeta, readOnly, writeOnly, description, widgets: instanceWidgets, idPrefix } = props;
106
+ const { schema: schemaInput, schemaRef: schemaRefInput, io, value, onChange, validate, onValidationError, onError, onDiagnostic, strict, fields, meta: componentMeta, readOnly, writeOnly, description, widgets: instanceWidgets, idPrefix } = props;
107
107
  const userResolver = useContext(UserResolverContext);
108
108
  const contextWidgets = useContext(WidgetsContext);
109
109
  const generatedId = useId();
@@ -129,7 +129,7 @@ function SchemaComponent(props) {
129
129
  let rootMeta;
130
130
  let rootDocument;
131
131
  try {
132
- const normalised = normaliseSchema(schemaInput, refInput, diagnostics !== void 0 || io !== void 0 ? {
132
+ const normalised = normaliseSchema(schemaInput, schemaRefInput, diagnostics !== void 0 || io !== void 0 ? {
133
133
  ...diagnostics !== void 0 ? { diagnostics } : {},
134
134
  ...io !== void 0 ? { io } : {}
135
135
  } : void 0);
@@ -336,7 +336,7 @@ function renderField(tree, value, onChange, userResolver, renderChild, path, ins
336
336
  *
337
337
  * @group Components
338
338
  */
339
- function SchemaField({ path, schema: schemaInput, ref: refInput, value, onChange, meta: fieldMeta, validate, onValidationError }) {
339
+ function SchemaField({ path, schema: schemaInput, schemaRef: schemaRefInput, value, onChange, meta: fieldMeta, validate, onValidationError }) {
340
340
  const userResolver = useContext(UserResolverContext);
341
341
  const contextWidgets = useContext(WidgetsContext);
342
342
  const generatedId = useId();
@@ -345,7 +345,7 @@ function SchemaField({ path, schema: schemaInput, ref: refInput, value, onChange
345
345
  let rootMeta;
346
346
  let rootDocument;
347
347
  try {
348
- const normalised = normaliseSchema(schemaInput, refInput);
348
+ const normalised = normaliseSchema(schemaInput, schemaRefInput);
349
349
  jsonSchema = normalised.jsonSchema;
350
350
  zodSchema = normalised.zodSchema;
351
351
  rootMeta = normalised.rootMeta;
@@ -389,7 +389,7 @@ function SchemaField({ path, schema: schemaInput, ref: refInput, value, onChange
389
389
  * Walks the fields override tree and matches errors by path prefix.
390
390
  *
391
391
  * The runtime shape of `fields` is always `Record<string, FieldOverride>`
392
- * after `InferFields<T, Ref>` is erased — the typed variants
392
+ * after `InferFields<T, SchemaRef>` is erased — the typed variants
393
393
  * (`FieldOverrides<U>`) and the loose `Record<string, FieldOverride>`
394
394
  * fallback share the same structural shape, so the dispatch logic only
395
395
  * needs the loose record. The previous parameter union
@@ -16,7 +16,7 @@ import { ReactNode } from "react";
16
16
  *
17
17
  * @group Components
18
18
  */
19
- interface SchemaViewProps<T = unknown, Ref extends string | undefined = undefined, Mode extends SchemaIoSide = "output"> {
19
+ interface SchemaViewProps<T = unknown, SchemaRef extends string | undefined = undefined, Mode extends SchemaIoSide = "output"> {
20
20
  /**
21
21
  * Zod schema, JSON Schema object, or OpenAPI document.
22
22
  *
@@ -25,8 +25,13 @@ interface SchemaViewProps<T = unknown, Ref extends string | undefined = undefine
25
25
  * {@link RejectUnrepresentableZod}.
26
26
  */
27
27
  schema: RejectUnrepresentableZod<T>;
28
- /** For OpenAPI: a ref string like "#/components/schemas/User". */
29
- ref?: Ref;
28
+ /**
29
+ * For OpenAPI / JSON Schema documents: a `$ref` string like
30
+ * `"#/components/schemas/User"`. Named `schemaRef` (not `ref`) to
31
+ * avoid the React / `preact/compat` reserved prop name. See
32
+ * {@link SchemaComponentProps.schemaRef}.
33
+ */
34
+ schemaRef?: SchemaRef;
30
35
  /**
31
36
  * Which side of every transform / pipe / codec to render. Mirrors
32
37
  * `<SchemaComponent io>`. Defaults to `"output"` — the inferred
@@ -39,18 +44,18 @@ interface SchemaViewProps<T = unknown, Ref extends string | undefined = undefine
39
44
  io?: Mode;
40
45
  /**
41
46
  * Current value to render. Typed against
42
- * `InferSchemaValue<T, Ref, Mode>` so the prop tracks the schema's
43
- * inferred shape for the chosen `io` direction. Falls back to
44
- * `unknown` for runtime schemas where the value type cannot be
47
+ * `InferSchemaValue<T, SchemaRef, Mode>` so the prop tracks the
48
+ * schema's inferred shape for the chosen `io` direction. Falls back
49
+ * to `unknown` for runtime schemas where the value type cannot be
45
50
  * statically inferred.
46
51
  */
47
- value?: InferredValue<T, Ref, undefined, Mode>;
52
+ value?: InferredValue<T, SchemaRef, undefined, Mode>;
48
53
  /**
49
54
  * Per-field meta overrides — nested object mirroring schema shape.
50
55
  * Typed against {@link InferFields} so a typed `schema` prop drives
51
56
  * autocomplete on the override map, matching `<SchemaComponent>`.
52
57
  */
53
- fields?: InferFields<T, Ref>;
58
+ fields?: InferFields<T, SchemaRef>;
54
59
  /** Meta overrides applied to the root schema. */
55
60
  meta?: SchemaMeta;
56
61
  /** Convenience: sets description on the root. */
@@ -96,9 +101,9 @@ interface SchemaViewProps<T = unknown, Ref extends string | undefined = undefine
96
101
  * }
97
102
  * ```
98
103
  */
99
- declare function SchemaView<T = unknown, Ref extends string | undefined = undefined, Mode extends SchemaIoSide = "output">({
104
+ declare function SchemaView<T = unknown, SchemaRef extends string | undefined = undefined, Mode extends SchemaIoSide = "output">({
100
105
  schema: schemaInput,
101
- ref: refInput,
106
+ schemaRef: schemaRefInput,
102
107
  io,
103
108
  value,
104
109
  fields,
@@ -109,6 +114,6 @@ declare function SchemaView<T = unknown, Ref extends string | undefined = undefi
109
114
  onDiagnostic,
110
115
  strict,
111
116
  idPrefix
112
- }: SchemaViewProps<T, Ref, Mode>): ReactNode;
117
+ }: SchemaViewProps<T, SchemaRef, Mode>): ReactNode;
113
118
  //#endregion
114
119
  export { SchemaView, SchemaViewProps };
@@ -58,7 +58,7 @@ import { isValidElement, useId } from "react";
58
58
  * }
59
59
  * ```
60
60
  */
61
- function SchemaView({ schema: schemaInput, ref: refInput, io, value, fields, meta: componentMeta, description, resolver, widgets, onDiagnostic, strict, idPrefix }) {
61
+ function SchemaView({ schema: schemaInput, schemaRef: schemaRefInput, io, value, fields, meta: componentMeta, description, resolver, widgets, onDiagnostic, strict, idPrefix }) {
62
62
  const generatedId = useId();
63
63
  const rootPath = idPrefix ?? sanitisePrefix(generatedId);
64
64
  const mergedMeta = {
@@ -74,7 +74,7 @@ function SchemaView({ schema: schemaInput, ref: refInput, io, value, fields, met
74
74
  let rootMeta;
75
75
  let rootDocument;
76
76
  try {
77
- const normalised = normaliseSchema(schemaInput, refInput, diagnostics !== void 0 || io !== void 0 ? {
77
+ const normalised = normaliseSchema(schemaInput, schemaRefInput, diagnostics !== void 0 || io !== void 0 ? {
78
78
  ...diagnostics !== void 0 ? { diagnostics } : {},
79
79
  ...io !== void 0 ? { io } : {}
80
80
  } : void 0);