schema-components 2.0.2 → 2.1.1

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 (197) hide show
  1. package/README.md +133 -1
  2. package/dist/SchemaComponent-B__6-5-E.d.mts +277 -0
  3. package/dist/SchemaComponent-BxzzsHsK.mjs +668 -0
  4. package/dist/adapter-ktQaheWB.d.mts +213 -0
  5. package/dist/constructorTypes-BdCiMS6e.d.mts +30 -0
  6. package/dist/core/adapter.d.mts +3 -213
  7. package/dist/core/constraintHint.d.mts +1 -1
  8. package/dist/core/constraints.d.mts +2 -2
  9. package/dist/core/contexts.d.mts +71 -0
  10. package/dist/core/contexts.mjs +1 -0
  11. package/dist/core/diagnostics.d.mts +1 -1
  12. package/dist/core/errors.d.mts +1 -1
  13. package/dist/core/fieldOrder.d.mts +1 -1
  14. package/dist/{react → core}/fieldPath.d.mts +2 -2
  15. package/dist/{react → core}/fieldPath.mjs +3 -3
  16. package/dist/core/formats.d.mts +1 -1
  17. package/dist/core/inferValue.d.mts +1 -1
  18. package/dist/core/limits.d.mts +1 -1
  19. package/dist/core/merge.d.mts +1 -1
  20. package/dist/core/normalise.d.mts +2 -2
  21. package/dist/core/ref.d.mts +1 -1
  22. package/dist/core/renderField.d.mts +147 -0
  23. package/dist/core/renderField.mjs +115 -0
  24. package/dist/core/renderer.d.mts +2 -199
  25. package/dist/core/swagger2.d.mts +1 -1
  26. package/dist/core/typeInference.d.mts +1 -982
  27. package/dist/core/types.d.mts +1 -1
  28. package/dist/core/unionMatch.d.mts +1 -1
  29. package/dist/core/version.d.mts +1 -1
  30. package/dist/core/walkBuilders.d.mts +3 -3
  31. package/dist/core/walker.d.mts +1 -1
  32. package/dist/{errors-Dki7tji4.d.mts → errors-DbaI04x2.d.mts} +1 -1
  33. package/dist/html/a11y.d.mts +2 -2
  34. package/dist/html/renderToHtml.d.mts +5 -5
  35. package/dist/html/renderToHtml.mjs +33 -18
  36. package/dist/html/renderToHtmlStream.d.mts +5 -5
  37. package/dist/html/renderers.d.mts +1 -1
  38. package/dist/html/streamRenderers.d.mts +15 -6
  39. package/dist/html/streamRenderers.mjs +56 -10
  40. package/dist/{inferValue-Ce-PviSD.d.mts → inferValue-eAnh50EM.d.mts} +3 -3
  41. package/dist/lit/SchemaComponent.d.mts +125 -0
  42. package/dist/lit/SchemaComponent.mjs +2 -0
  43. package/dist/lit/SchemaField.d.mts +65 -0
  44. package/dist/lit/SchemaField.mjs +2 -0
  45. package/dist/lit/SchemaView.d.mts +14 -0
  46. package/dist/lit/SchemaView.mjs +2 -0
  47. package/dist/lit/constructorTypes.d.mts +2 -0
  48. package/dist/lit/constructorTypes.mjs +1 -0
  49. package/dist/lit/contexts.d.mts +78 -0
  50. package/dist/lit/contexts.mjs +238 -0
  51. package/dist/lit/defaultResolver.d.mts +33 -0
  52. package/dist/lit/defaultResolver.mjs +2 -0
  53. package/dist/lit/registry.d.mts +66 -0
  54. package/dist/lit/registry.mjs +2 -0
  55. package/dist/lit/renderers/baseElement.d.mts +131 -0
  56. package/dist/lit/renderers/baseElement.mjs +109 -0
  57. package/dist/lit/renderers/recordHelpers.d.mts +25 -0
  58. package/dist/lit/renderers/recordHelpers.mjs +55 -0
  59. package/dist/lit/renderers/scArray.d.mts +14 -0
  60. package/dist/lit/renderers/scArray.mjs +86 -0
  61. package/dist/lit/renderers/scBoolean.d.mts +15 -0
  62. package/dist/lit/renderers/scBoolean.mjs +47 -0
  63. package/dist/lit/renderers/scConditional.d.mts +23 -0
  64. package/dist/lit/renderers/scConditional.mjs +65 -0
  65. package/dist/lit/renderers/scDiscriminated.d.mts +23 -0
  66. package/dist/lit/renderers/scDiscriminated.mjs +138 -0
  67. package/dist/lit/renderers/scEnum.d.mts +16 -0
  68. package/dist/lit/renderers/scEnum.mjs +66 -0
  69. package/dist/lit/renderers/scFile.d.mts +15 -0
  70. package/dist/lit/renderers/scFile.mjs +53 -0
  71. package/dist/lit/renderers/scLiteralNullNever.d.mts +30 -0
  72. package/dist/lit/renderers/scLiteralNullNever.mjs +57 -0
  73. package/dist/lit/renderers/scNumber.d.mts +15 -0
  74. package/dist/lit/renderers/scNumber.mjs +64 -0
  75. package/dist/lit/renderers/scObject.d.mts +14 -0
  76. package/dist/lit/renderers/scObject.mjs +57 -0
  77. package/dist/lit/renderers/scRecord.d.mts +14 -0
  78. package/dist/lit/renderers/scRecord.mjs +112 -0
  79. package/dist/lit/renderers/scString.d.mts +19 -0
  80. package/dist/lit/renderers/scString.mjs +165 -0
  81. package/dist/lit/renderers/scTuple.d.mts +14 -0
  82. package/dist/lit/renderers/scTuple.mjs +58 -0
  83. package/dist/lit/renderers/scUnion.d.mts +14 -0
  84. package/dist/lit/renderers/scUnion.mjs +44 -0
  85. package/dist/lit/renderers/scUnknown.d.mts +15 -0
  86. package/dist/lit/renderers/scUnknown.mjs +45 -0
  87. package/dist/lit/ssr.d.mts +37 -0
  88. package/dist/lit/ssr.mjs +9565 -0
  89. package/dist/lit/types.d.mts +2 -0
  90. package/dist/lit/types.mjs +1 -0
  91. package/dist/lit/widget.d.mts +71 -0
  92. package/dist/lit/widget.mjs +87 -0
  93. package/dist/openapi/ApiCallbacks.d.mts +1 -1
  94. package/dist/openapi/ApiLinks.d.mts +1 -1
  95. package/dist/openapi/ApiResponseHeaders.d.mts +1 -1
  96. package/dist/openapi/ApiSecurity.d.mts +1 -1
  97. package/dist/openapi/components.d.mts +4 -4
  98. package/dist/openapi/parser.d.mts +2 -2
  99. package/dist/openapi/resolve.d.mts +1 -1
  100. package/dist/preact/SchemaComponent.d.mts +3 -0
  101. package/dist/preact/SchemaComponent.mjs +26 -0
  102. package/dist/preact/SchemaErrorBoundary.d.mts +2 -0
  103. package/dist/preact/SchemaErrorBoundary.mjs +20 -0
  104. package/dist/preact/SchemaView.d.mts +2 -0
  105. package/dist/preact/SchemaView.mjs +22 -0
  106. package/dist/preact/headless.d.mts +2 -0
  107. package/dist/preact/headless.mjs +18 -0
  108. package/dist/react/SchemaComponent.d.mts +3 -270
  109. package/dist/react/SchemaComponent.mjs +41 -32
  110. package/dist/react/SchemaView.d.mts +6 -6
  111. package/dist/react/SchemaView.mjs +32 -29
  112. package/dist/react/a11y.d.mts +2 -2
  113. package/dist/react/fieldShell.d.mts +1 -1
  114. package/dist/react/headless.d.mts +1 -1
  115. package/dist/react/headlessRenderers.d.mts +2 -2
  116. package/dist/{ref-DdsbekXX.d.mts → ref-DWrQG1Er.d.mts} +1 -1
  117. package/dist/renderer-ab9E52Bp.d.mts +245 -0
  118. package/dist/solid/SchemaComponent.d.mts +136 -0
  119. package/dist/solid/SchemaComponent.mjs +391 -0
  120. package/dist/solid/SchemaErrorBoundary.d.mts +38 -0
  121. package/dist/solid/SchemaErrorBoundary.mjs +57 -0
  122. package/dist/solid/SchemaField.d.mts +40 -0
  123. package/dist/solid/SchemaField.mjs +113 -0
  124. package/dist/solid/SchemaView.d.mts +54 -0
  125. package/dist/solid/SchemaView.mjs +168 -0
  126. package/dist/solid/a11y.d.mts +70 -0
  127. package/dist/solid/a11y.mjs +71 -0
  128. package/dist/solid/contexts.d.mts +37 -0
  129. package/dist/solid/contexts.mjs +66 -0
  130. package/dist/solid/headless.d.mts +10 -0
  131. package/dist/solid/headless.mjs +27 -0
  132. package/dist/solid/renderers.d.mts +79 -0
  133. package/dist/solid/renderers.mjs +840 -0
  134. package/dist/solid/types.d.mts +90 -0
  135. package/dist/solid/types.mjs +1 -0
  136. package/dist/solid/widget.d.mts +29 -0
  137. package/dist/solid/widget.mjs +35 -0
  138. package/dist/themes/mantine.d.mts +1 -1
  139. package/dist/themes/mui.d.mts +1 -1
  140. package/dist/themes/radix.d.mts +1 -1
  141. package/dist/themes/shadcn.d.mts +1 -1
  142. package/dist/typeInference-Y8tNEQJk.d.mts +983 -0
  143. package/dist/types-BCy7K3nk.d.mts +125 -0
  144. package/package.json +73 -1
  145. package/src/svelte/SchemaComponent.svelte +427 -0
  146. package/src/svelte/SchemaErrorBoundary.svelte +66 -0
  147. package/src/svelte/SchemaField.svelte +216 -0
  148. package/src/svelte/SchemaProvider.svelte +46 -0
  149. package/src/svelte/SchemaView.svelte +244 -0
  150. package/src/svelte/a11y.ts +112 -0
  151. package/src/svelte/contexts.ts +79 -0
  152. package/src/svelte/dispatch.ts +267 -0
  153. package/src/svelte/headless.ts +73 -0
  154. package/src/svelte/headlessFns.ts +124 -0
  155. package/src/svelte/renderers/Array.svelte +98 -0
  156. package/src/svelte/renderers/Boolean.svelte +43 -0
  157. package/src/svelte/renderers/Conditional.svelte +67 -0
  158. package/src/svelte/renderers/DiscriminatedUnion.svelte +197 -0
  159. package/src/svelte/renderers/Enum.svelte +53 -0
  160. package/src/svelte/renderers/Fallback.svelte +24 -0
  161. package/src/svelte/renderers/File.svelte +46 -0
  162. package/src/svelte/renderers/Literal.svelte +29 -0
  163. package/src/svelte/renderers/Mount.svelte +24 -0
  164. package/src/svelte/renderers/Negation.svelte +35 -0
  165. package/src/svelte/renderers/Never.svelte +24 -0
  166. package/src/svelte/renderers/Null.svelte +19 -0
  167. package/src/svelte/renderers/Number.svelte +68 -0
  168. package/src/svelte/renderers/Object.svelte +74 -0
  169. package/src/svelte/renderers/Record.svelte +134 -0
  170. package/src/svelte/renderers/RecursionSentinel.svelte +27 -0
  171. package/src/svelte/renderers/String.svelte +152 -0
  172. package/src/svelte/renderers/Tuple.svelte +84 -0
  173. package/src/svelte/renderers/Union.svelte +49 -0
  174. package/src/svelte/renderers/Unknown.svelte +42 -0
  175. package/src/svelte/svelte-modules.d.ts +25 -0
  176. package/src/svelte/types.ts +238 -0
  177. package/src/svelte/widget.ts +62 -0
  178. package/src/vue/SchemaComponent.vue +274 -0
  179. package/src/vue/SchemaErrorBoundary.vue +60 -0
  180. package/src/vue/SchemaField.vue +178 -0
  181. package/src/vue/SchemaProvider.vue +39 -0
  182. package/src/vue/SchemaView.vue +198 -0
  183. package/src/vue/VNodeHost.ts +32 -0
  184. package/src/vue/contexts.ts +116 -0
  185. package/src/vue/eventTargets.ts +35 -0
  186. package/src/vue/headless.ts +61 -0
  187. package/src/vue/idPrefix.ts +79 -0
  188. package/src/vue/renderField.ts +182 -0
  189. package/src/vue/renderers.ts +1297 -0
  190. package/src/vue/resolver.ts +45 -0
  191. package/src/vue/types.ts +140 -0
  192. package/src/vue/vue-shim.d.ts +25 -0
  193. package/src/vue/widget.ts +51 -0
  194. /package/dist/{diagnostics-BTrm3O6J.d.mts → diagnostics-mftUZI7c.d.mts} +0 -0
  195. /package/dist/{limits-x4OiyJxh.d.mts → limits-Vv9hUbI_.d.mts} +0 -0
  196. /package/dist/{types-BrYbjC7_.d.mts → types-BBQaEPfE.d.mts} +0 -0
  197. /package/dist/{version-DL8U5RuA.d.mts → version-BEBx10ND.d.mts} +0 -0
@@ -0,0 +1,238 @@
1
+ /**
2
+ * Svelte 5 adapter type surface for schema-components.
3
+ *
4
+ * Mirrors the shape of `core/renderer.ts`'s React-flavoured
5
+ * {@link "../core/renderer.ts".RenderProps} but adapts it for Svelte's
6
+ * compile-time component model: renderers are not plain functions
7
+ * returning VNodes — they are component constructors. The dispatcher
8
+ * therefore packages each "render this field" call as a
9
+ * {@link SvelteRenderDescriptor} pairing the component with the props
10
+ * it should be instantiated against; downstream the active container
11
+ * component (e.g. `Object.svelte`) materialises the descriptor via
12
+ * `<svelte:component this={component} {...props} />`.
13
+ *
14
+ * The shapes intentionally diverge from the React adapter on two axes:
15
+ *
16
+ * 1. `renderChild` returns a {@link SvelteRenderDescriptor} rather
17
+ * than a `ReactNode`. Svelte cannot directly embed an object
18
+ * synthesised at render time the way React embeds a JSX node — but
19
+ * it can `<svelte:component>` a `{ component, props }` pair.
20
+ * 2. There is no synthetic event system, so `onChange` is plumbed
21
+ * directly into the per-field props and invoked from raw DOM
22
+ * handlers (`onchange`, `oninput`) inside each `.svelte` file.
23
+ *
24
+ * The public consumer pattern is `<SchemaComponent schema value onChange?\>` —
25
+ * Svelte's `bind:value` ergonomics are deliberately not forced.
26
+ * The function-style `onChange` callback was chosen so the
27
+ * adapter behaves identically across server-rendered (`SchemaView`),
28
+ * controlled-input, and uncontrolled-input call sites; consumers that
29
+ * prefer `bind:value` can wire it externally:
30
+ *
31
+ * ```svelte
32
+ * <SchemaComponent {schema} bind:value />
33
+ * ```
34
+ *
35
+ * which Svelte transparently translates into an `onChange` that mutates
36
+ * the bound rune-backed reference.
37
+ *
38
+ * @group Framework Adapters
39
+ */
40
+
41
+ import type { Component } from "svelte";
42
+ import type {
43
+ BaseFieldProps,
44
+ BaseRenderProps,
45
+ RenderFunction,
46
+ } from "../core/renderer.ts";
47
+ import type { WalkedField } from "../core/types.ts";
48
+
49
+ /**
50
+ * Descriptor produced by {@link SvelteRenderProps.renderChild} and by
51
+ * the dispatcher when materialising a single field. Pairs the Svelte
52
+ * component constructor with the props it should be instantiated
53
+ * against so a parent renderer can mount it via
54
+ * `<svelte:component this={component} {...props} />`.
55
+ *
56
+ * Returning a descriptor (rather than a rendered DOM node) keeps the
57
+ * adapter compatible with Svelte's compile-time component model — the
58
+ * dispatcher does not own a DOM mount point and cannot fabricate
59
+ * rendered output the way React's `renderField` returns a `ReactNode`.
60
+ *
61
+ * `null` indicates "render nothing" — used for empty arrays in
62
+ * read-only mode and for the recursion-cap sentinel placeholder when
63
+ * the caller opts to suppress it.
64
+ */
65
+ export interface SvelteRenderDescriptor {
66
+ /** Svelte component constructor to mount. */
67
+ readonly component: SvelteComponentConstructor;
68
+ /** Props to pass to the component instance. */
69
+ readonly props: SvelteRenderProps;
70
+ }
71
+
72
+ /**
73
+ * The raw Svelte 5 component constructor type. Aliased so consumers
74
+ * have a single name to import — `Component<SvelteRenderProps>` is
75
+ * exact, but reading {@link SvelteComponentConstructor} at call sites
76
+ * keeps the framework dependency localised to this module.
77
+ */
78
+ export type SvelteComponentConstructor = Component<SvelteRenderProps>;
79
+
80
+ /**
81
+ * Props passed to every Svelte 5 renderer component.
82
+ *
83
+ * Specialisation of {@link BaseRenderProps} with
84
+ * `Output = SvelteRenderDescriptor | null`. Each renderer receives
85
+ * these as `$props()` — the per-field data, the editability flags,
86
+ * the constraint bundle, and the `renderChild` factory it should
87
+ * invoke for nested structures (object fields, array elements, union
88
+ * options, …).
89
+ *
90
+ * Mirrors the React {@link "../core/renderer.ts".RenderProps} shape
91
+ * — `onChange` for value propagation, four-argument `renderChild`
92
+ * for recursive descent.
93
+ */
94
+ export interface SvelteRenderProps extends BaseRenderProps<SvelteRenderDescriptor | null> {
95
+ /** Callback to update the field value. */
96
+ onChange: (value: unknown) => void;
97
+ /**
98
+ * Render a child field. Container renderers (object, array,
99
+ * tuple, record, union, discriminated union, conditional,
100
+ * negation) call this and mount the returned descriptor via
101
+ * `<svelte:component this={component} {...props} />`.
102
+ *
103
+ * @param tree - The walked field tree for the child.
104
+ * @param value - The child's current value.
105
+ * @param onChange - Callback receiving the child's next value.
106
+ * @param pathSuffix - Path segment from the parent (e.g. "city",
107
+ * "[0]"). Joined to the parent's path with a dot, or
108
+ * substituted when the parent acts as a transparent wrapper
109
+ * (union options). Required for every container — without it
110
+ * children inherit no path and `fieldDomId()` will throw.
111
+ */
112
+ renderChild: (
113
+ tree: WalkedField,
114
+ value: unknown,
115
+ onChange: (v: unknown) => void,
116
+ pathSuffix?: string
117
+ ) => SvelteRenderDescriptor | null;
118
+ }
119
+
120
+ /**
121
+ * Signature for a render function attached to a
122
+ * {@link SvelteComponentResolver}.
123
+ *
124
+ * Unlike React — where `RenderFunction` directly produces a
125
+ * `ReactNode` — the Svelte equivalent produces a
126
+ * {@link SvelteRenderDescriptor}. The descriptor pairs a component
127
+ * constructor with the per-field props and is mounted by the parent
128
+ * renderer via `<svelte:component>`. This indirection is the price
129
+ * of Svelte's compile-time component model: the dispatcher cannot
130
+ * fabricate rendered DOM at runtime, so it returns a recipe for the
131
+ * parent to mount.
132
+ *
133
+ * Specialisation of the generic
134
+ * {@link "../core/renderer.ts".RenderFunction | RenderFunction} from
135
+ * `core/renderer.ts` with
136
+ * `Output = SvelteRenderDescriptor | null` and
137
+ * `Props = SvelteRenderProps`.
138
+ */
139
+ export type SvelteRenderFunction = RenderFunction<
140
+ SvelteRenderDescriptor | null,
141
+ SvelteRenderProps
142
+ >;
143
+
144
+ /**
145
+ * Helper: wrap a Svelte component constructor into the
146
+ * "render function" shape consumed by the dispatcher. Pairs the
147
+ * supplied component with the per-field props.
148
+ *
149
+ * Used by {@link "./headless.ts".headlessSvelteResolver} to register
150
+ * one constructor per schema type, and exposed publicly so theme
151
+ * adapter authors can compose their own resolver from `.svelte`
152
+ * files without re-implementing the wrapper.
153
+ *
154
+ * @param component - A Svelte 5 component constructor accepting
155
+ * {@link SvelteRenderProps}.
156
+ * @returns A {@link SvelteRenderFunction} that, given props, returns
157
+ * the descriptor `{ component, props }`.
158
+ */
159
+ export function makeSvelteRenderer(
160
+ component: SvelteComponentConstructor
161
+ ): SvelteRenderFunction {
162
+ return (props) => ({ component, props });
163
+ }
164
+
165
+ /**
166
+ * Theme adapter — maps every schema field type to a Svelte
167
+ * {@link SvelteRenderFunction}. Unset keys fall back to the headless
168
+ * resolver.
169
+ *
170
+ * Pass to {@link "./contexts.ts".resolverContext} (via the
171
+ * `SchemaProvider` Svelte component) so a single theme drives every
172
+ * schema render in a subtree.
173
+ *
174
+ * Structurally parallel to
175
+ * {@link "../core/renderer.ts".ComponentResolver} for React, but
176
+ * each value is a {@link SvelteRenderFunction} returning a
177
+ * {@link SvelteRenderDescriptor} rather than a render function
178
+ * returning a `ReactNode`.
179
+ */
180
+ export interface SvelteComponentResolver {
181
+ string?: SvelteRenderFunction;
182
+ number?: SvelteRenderFunction;
183
+ boolean?: SvelteRenderFunction;
184
+ null?: SvelteRenderFunction;
185
+ enum?: SvelteRenderFunction;
186
+ object?: SvelteRenderFunction;
187
+ array?: SvelteRenderFunction;
188
+ tuple?: SvelteRenderFunction;
189
+ record?: SvelteRenderFunction;
190
+ union?: SvelteRenderFunction;
191
+ discriminatedUnion?: SvelteRenderFunction;
192
+ conditional?: SvelteRenderFunction;
193
+ negation?: SvelteRenderFunction;
194
+ literal?: SvelteRenderFunction;
195
+ file?: SvelteRenderFunction;
196
+ never?: SvelteRenderFunction;
197
+ unknown?: SvelteRenderFunction;
198
+ }
199
+
200
+ /**
201
+ * Widget map — maps component hints (from `.meta({ component })`) to
202
+ * Svelte {@link SvelteRenderFunction}s. Mirrors the React
203
+ * {@link "../core/renderer.ts".WidgetMap} but each value is a
204
+ * Svelte-flavoured render function (typically produced via
205
+ * {@link makeSvelteRenderer}).
206
+ *
207
+ * Scoped at three levels in the Svelte adapter:
208
+ *
209
+ * 1. **Per-instance** — `widgets` prop on `<SchemaComponent>`
210
+ * 2. **Context-scoped** — `widgets` prop on `<SchemaProvider>`
211
+ * 3. **Global** — `registerWidget()` (app-wide defaults)
212
+ */
213
+ export type SvelteWidgetMap = ReadonlyMap<string, SvelteRenderFunction>;
214
+
215
+ /**
216
+ * Compile-time assertion that {@link SvelteRenderFunction} is a
217
+ * specialisation of the generic {@link RenderFunction} contract from
218
+ * `core/renderer.ts`. Exercised by the type-level test in
219
+ * `tests/svelte/typeTest.svelte.unit.test.ts` — a regression on the
220
+ * alignment fails compilation rather than silently producing
221
+ * incompatible adapters.
222
+ *
223
+ * @internal
224
+ */
225
+ export type __SvelteRenderFunctionMatchesGenericRenderFunction =
226
+ SvelteRenderFunction extends RenderFunction<
227
+ SvelteRenderDescriptor | null,
228
+ SvelteRenderProps
229
+ >
230
+ ? true
231
+ : false;
232
+
233
+ /**
234
+ * Re-export of {@link BaseFieldProps} so consumers writing custom
235
+ * Svelte renderers can import a single type covering the schema-data
236
+ * shape without crossing the framework adapter boundary.
237
+ */
238
+ export type { BaseFieldProps };
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Global widget registry for the Svelte adapter.
3
+ *
4
+ * Mirrors the React adapter's `registerWidget()` / `globalWidgets`
5
+ * pair in `react/SchemaComponent.tsx`. Each registered entry is a
6
+ * Svelte component constructor (`Component<SvelteRenderProps>`)
7
+ * matched against `.meta({ component })` hints on a walked field.
8
+ *
9
+ * Resolution order (from {@link "./SchemaComponent.svelte" |
10
+ * SchemaComponent}'s dispatch wiring): instance widgets → context
11
+ * widgets → global widgets → resolver render fn → headless fallback.
12
+ *
13
+ * The global registry is module-level mutable state; tests should
14
+ * clear it with {@link __clearGlobalWidgets} to avoid leaking
15
+ * registrations across cases.
16
+ *
17
+ * @group Framework Adapters
18
+ */
19
+
20
+ import type { SvelteRenderFunction } from "./types.ts";
21
+
22
+ const globalWidgets = new Map<string, SvelteRenderFunction>();
23
+
24
+ /**
25
+ * Register a Svelte widget globally. The widget is resolved when a
26
+ * schema field has `.meta({ component: name })` and no per-instance
27
+ * or context-scoped widget map provides a matching entry.
28
+ *
29
+ * For scoped registration, supply the `widgets` prop on
30
+ * `<SchemaComponent>` or `<SchemaProvider>` instead.
31
+ */
32
+ export function registerWidget(
33
+ name: string,
34
+ component: SvelteRenderFunction
35
+ ): void {
36
+ globalWidgets.set(name, component);
37
+ }
38
+
39
+ /**
40
+ * Look up a globally registered Svelte widget by hint name. Returns
41
+ * `undefined` when nothing matches — callers fall back to the
42
+ * resolver chain.
43
+ *
44
+ * @internal Used by the Svelte dispatcher wiring inside
45
+ * `SchemaComponent.svelte`; not part of the public surface.
46
+ */
47
+ export function lookupGlobalWidget(
48
+ name: string
49
+ ): SvelteRenderFunction | undefined {
50
+ return globalWidgets.get(name);
51
+ }
52
+
53
+ /**
54
+ * Clear every globally registered Svelte widget. Intended for test
55
+ * isolation — `registerWidget` writes to module-level state and that
56
+ * state otherwise leaks across test cases.
57
+ *
58
+ * @internal
59
+ */
60
+ export function __clearGlobalWidgets(): void {
61
+ globalWidgets.clear();
62
+ }
@@ -0,0 +1,274 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * `<SchemaComponent>` — Vue counterpart of the React `<SchemaComponent>`.
4
+ *
5
+ * Auto-detects the input format, normalises to JSON Schema via the
6
+ * adapter, walks the JSON Schema tree, and delegates per-field
7
+ * rendering to the {@link VueComponentResolver} supplied via
8
+ * `<SchemaProvider>` — falling back to the headless renderer when no
9
+ * provider is present.
10
+ *
11
+ * `onChange` semantics: the component accepts BOTH a `v-model`
12
+ * binding (Vue-idiomatic — `modelValue` prop +
13
+ * `update:modelValue` emit) and an explicit `onChange` callback prop
14
+ * (matching the React adapter). It also emits a `change` event for
15
+ * Vue authors who prefer event listeners. All three surfaces fire
16
+ * together so consumers may use whichever idiom suits them.
17
+ *
18
+ * @group Components
19
+ */
20
+ import { computed, h, toRaw, type VNode } from "vue";
21
+ import { walk } from "../core/walker.ts";
22
+ import type { WalkOptions } from "../core/walkBuilders.ts";
23
+ import { normaliseSchema } from "../core/adapter.ts";
24
+ import type { SchemaMeta, WalkedField } from "../core/types.ts";
25
+ import type { Diagnostic, DiagnosticsOptions } from "../core/diagnostics.ts";
26
+ import { SchemaNormalisationError } from "../core/errors.ts";
27
+ import { toRecordOrUndefined } from "../core/guards.ts";
28
+ import { VueResolverContext, VueWidgetsContext } from "./contexts.ts";
29
+ import { vueRenderField } from "./renderField.ts";
30
+ import { deriveIdPrefix, joinPath } from "./idPrefix.ts";
31
+ import type {
32
+ VueComponentResolver,
33
+ VueRenderProps,
34
+ VueWidgetMap,
35
+ } from "./types.ts";
36
+ import { VNodeHost } from "./VNodeHost.ts";
37
+
38
+ const props = withDefaults(
39
+ defineProps<{
40
+ /** Zod schema, JSON Schema object, or OpenAPI document. */
41
+ schema: unknown;
42
+ /** For OpenAPI: a ref string like `#/components/schemas/User`. */
43
+ refPath?: string;
44
+ /** v-model binding for the current value. */
45
+ modelValue?: unknown;
46
+ /**
47
+ * Explicit `onChange` callback — wired in parallel with
48
+ * `update:modelValue` so consumers may use either surface.
49
+ */
50
+ onChange?: (value: unknown) => void;
51
+ /** Convenience: sets `readOnly` on all fields. */
52
+ readOnly?: boolean;
53
+ /** Convenience: sets `writeOnly` on all fields. */
54
+ writeOnly?: boolean;
55
+ /** Convenience: sets `description` on the root. */
56
+ description?: string;
57
+ /** Meta overrides applied to the root schema. */
58
+ meta?: SchemaMeta;
59
+ /** Per-field meta overrides — nested object mirroring schema shape. */
60
+ fields?: Record<string, unknown>;
61
+ /** Theme resolver. Overrides the context resolver when supplied. */
62
+ resolver?: VueComponentResolver;
63
+ /** Instance-scoped widgets — override context and global widgets. */
64
+ widgets?: VueWidgetMap;
65
+ /** Deterministic id prefix. Defaults to a per-instance `useId()` value. */
66
+ idPrefix?: string;
67
+ /** Called with each diagnostic emitted during schema processing. */
68
+ onDiagnostic?: (diagnostic: Diagnostic) => void;
69
+ /** When `true`, any diagnostic becomes a thrown error. */
70
+ strict?: boolean;
71
+ /** Called when schema normalisation fails. */
72
+ onError?: (error: SchemaNormalisationError) => void;
73
+ }>(),
74
+ {
75
+ refPath: undefined,
76
+ modelValue: undefined,
77
+ onChange: undefined,
78
+ readOnly: false,
79
+ writeOnly: false,
80
+ description: undefined,
81
+ meta: () => ({}),
82
+ fields: undefined,
83
+ resolver: undefined,
84
+ widgets: undefined,
85
+ idPrefix: undefined,
86
+ onDiagnostic: undefined,
87
+ strict: false,
88
+ onError: undefined,
89
+ }
90
+ );
91
+
92
+ const emit = defineEmits<{
93
+ "update:modelValue": [value: unknown];
94
+ change: [value: unknown];
95
+ }>();
96
+
97
+ // Consume the resolver and widget contexts. Both ports return
98
+ // `undefined` when no provider is mounted in scope — the dispatcher
99
+ // then falls through to the headless resolver.
100
+ const contextResolver = VueResolverContext.consume();
101
+ const contextWidgets = VueWidgetsContext.consume();
102
+
103
+ const rootPath = computed(() => deriveIdPrefix(props.idPrefix));
104
+
105
+ const mergedMeta = computed<SchemaMeta>(() => {
106
+ const merged: SchemaMeta = { ...props.meta };
107
+ if (props.readOnly) merged.readOnly = true;
108
+ if (props.writeOnly) merged.writeOnly = true;
109
+ if (props.description !== undefined) merged.description = props.description;
110
+ return merged;
111
+ });
112
+
113
+ const diagnostics = computed<DiagnosticsOptions | undefined>(() => {
114
+ if (props.onDiagnostic === undefined && !props.strict) return undefined;
115
+ const opts: DiagnosticsOptions = {};
116
+ if (props.onDiagnostic !== undefined) opts.diagnostics = props.onDiagnostic;
117
+ if (props.strict) opts.strict = true;
118
+ return opts;
119
+ });
120
+
121
+ interface Normalised {
122
+ jsonSchema: Record<string, unknown>;
123
+ rootMeta: SchemaMeta | undefined;
124
+ rootDocument: Record<string, unknown>;
125
+ error?: SchemaNormalisationError;
126
+ }
127
+
128
+ const normalised = computed<Normalised>(() => {
129
+ try {
130
+ const opts =
131
+ diagnostics.value !== undefined
132
+ ? { diagnostics: diagnostics.value }
133
+ : undefined;
134
+ // Vue wraps every reactive prop in a Proxy. Zod 4 schemas
135
+ // carry non-configurable internal data members (`_zod`) that
136
+ // the default reactivity proxy cannot mirror — accessing them
137
+ // through the proxy throws. `toRaw` recovers the original
138
+ // object before passing it into `normaliseSchema`, which does
139
+ // not need (and should not see) Vue's reactivity layer. The
140
+ // same fix is applied to `props.modelValue` further down for
141
+ // consistency with the React adapter, which receives raw
142
+ // values directly.
143
+ const rawSchema = toRaw(props.schema);
144
+ const result = normaliseSchema(rawSchema, props.refPath, opts);
145
+ return {
146
+ jsonSchema: result.jsonSchema,
147
+ rootMeta: result.rootMeta,
148
+ rootDocument: result.rootDocument,
149
+ };
150
+ } catch (err) {
151
+ const error =
152
+ err instanceof SchemaNormalisationError
153
+ ? err
154
+ : new SchemaNormalisationError(
155
+ err instanceof Error
156
+ ? err.message
157
+ : "Failed to normalise schema",
158
+ toRaw(props.schema),
159
+ "unknown"
160
+ );
161
+ return {
162
+ jsonSchema: {},
163
+ rootMeta: undefined,
164
+ rootDocument: {},
165
+ error,
166
+ };
167
+ }
168
+ });
169
+
170
+ const tree = computed<WalkedField | undefined>(() => {
171
+ const n = normalised.value;
172
+ if (n.error !== undefined) return undefined;
173
+ const walkOptions: WalkOptions = {
174
+ componentMeta: mergedMeta.value,
175
+ rootDocument: n.rootDocument,
176
+ };
177
+ if (n.rootMeta !== undefined) walkOptions.rootMeta = n.rootMeta;
178
+ const fieldsRecord = toRecordOrUndefined(props.fields);
179
+ if (fieldsRecord !== undefined) walkOptions.fieldOverrides = fieldsRecord;
180
+ if (diagnostics.value !== undefined)
181
+ walkOptions.diagnostics = diagnostics.value;
182
+ return walk(n.jsonSchema, walkOptions);
183
+ });
184
+
185
+ const effectiveValue = computed<unknown>(() => {
186
+ if (props.modelValue !== undefined) return props.modelValue;
187
+ return tree.value?.defaultValue;
188
+ });
189
+
190
+ function handleChange(next: unknown): void {
191
+ emit("update:modelValue", next);
192
+ // Vue auto-wires any `onChange="…"` template attribute as a
193
+ // `change` event listener (the same `on<Event>` convention React
194
+ // uses for synthetic events). To avoid invoking the same handler
195
+ // twice — once via `emit("change", …)` and once via
196
+ // `props.onChange(…)` — we emit the event when no explicit prop
197
+ // handler was supplied, and call the prop directly otherwise.
198
+ // Both paths are observable to consumers but never overlap.
199
+ if (props.onChange !== undefined) {
200
+ props.onChange(next);
201
+ } else {
202
+ emit("change", next);
203
+ }
204
+ }
205
+
206
+ /**
207
+ * Build the recursive `renderChild` closure. Each invocation increments
208
+ * the depth counter so the dispatcher's `MAX_RENDER_DEPTH` cap fires
209
+ * on truly recursive structures rather than on shallow trees.
210
+ */
211
+ function makeRenderChild(
212
+ currentDepth: number,
213
+ parentPath: string
214
+ ): VueRenderProps["renderChild"] {
215
+ return (
216
+ childTree: WalkedField,
217
+ childValue: unknown,
218
+ childOnChange: (v: unknown) => void,
219
+ pathSuffix?: string
220
+ ) => {
221
+ const childPath = joinPath(parentPath, pathSuffix);
222
+ return vueRenderField(
223
+ childTree,
224
+ childValue,
225
+ childOnChange,
226
+ props.resolver ?? contextResolver,
227
+ makeRenderChild(currentDepth + 1, childPath),
228
+ childPath,
229
+ props.widgets,
230
+ contextWidgets,
231
+ currentDepth + 1
232
+ );
233
+ };
234
+ }
235
+
236
+ /**
237
+ * Reactive root VNode. Recomputed whenever any reactive dependency
238
+ * (props, contexts, tree) changes. The template renders it via
239
+ * `<component :is="rootVNode">` — Vue accepts a VNode object as the
240
+ * target of `:is`, which lets us drive the render from a render
241
+ * function while keeping the SFC `<template>` block as the single
242
+ * mount point.
243
+ */
244
+ const rootVNode = computed<VNode>(() => {
245
+ const t = tree.value;
246
+ if (t === undefined) {
247
+ const err = normalised.value.error;
248
+ if (err !== undefined) {
249
+ if (props.onError !== undefined) {
250
+ props.onError(err);
251
+ return h("span", { style: { display: "none" } });
252
+ }
253
+ throw err;
254
+ }
255
+ return h("span", { style: { display: "none" } });
256
+ }
257
+ const renderChild = makeRenderChild(0, rootPath.value);
258
+ return vueRenderField(
259
+ t,
260
+ effectiveValue.value,
261
+ handleChange,
262
+ props.resolver ?? contextResolver,
263
+ renderChild,
264
+ rootPath.value,
265
+ props.widgets,
266
+ contextWidgets,
267
+ 0
268
+ );
269
+ });
270
+ </script>
271
+
272
+ <template>
273
+ <VNodeHost :node="rootVNode" />
274
+ </template>
@@ -0,0 +1,60 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * `<SchemaErrorBoundary>` — Vue counterpart of the React
4
+ * `SchemaErrorBoundary`.
5
+ *
6
+ * Uses Vue 3's `onErrorCaptured` lifecycle hook
7
+ * (https://vuejs.org/api/composition-api-lifecycle.html#onerrorcaptured)
8
+ * to catch render-time errors thrown by any descendant — including
9
+ * the dispatcher-wrapped `SchemaRenderError` from theme adapters that
10
+ * throw inside their render function. Returning `false` from
11
+ * `onErrorCaptured` halts further propagation up the component tree.
12
+ *
13
+ * The fallback slot is invoked with the captured error and a `reset`
14
+ * callback. Calling `reset()` clears the captured error so the
15
+ * children re-render (e.g. after fixing a bad schema prop).
16
+ *
17
+ * Like the React boundary, this captures render-time and lifecycle
18
+ * errors but NOT errors thrown from event handlers (Vue routes those
19
+ * through a separate `errorHandler` on the app instance) or async
20
+ * code that escapes the component tree.
21
+ *
22
+ * @group Components
23
+ */
24
+ import { onErrorCaptured, ref } from "vue";
25
+
26
+ const captured = ref<Error | undefined>(undefined);
27
+
28
+ defineSlots<{
29
+ /**
30
+ * Default slot — rendered when no error has been captured.
31
+ */
32
+ default(): unknown;
33
+ /**
34
+ * Fallback slot — invoked with the captured error and a `reset`
35
+ * callback. Use it to render an error UI; call `reset` to clear
36
+ * the captured state and let the children re-render.
37
+ */
38
+ fallback(props: { error: Error; reset: () => void }): unknown;
39
+ }>();
40
+
41
+ onErrorCaptured((err) => {
42
+ captured.value =
43
+ err instanceof Error ? err : new Error("Unknown render error");
44
+ return false;
45
+ });
46
+
47
+ function reset(): void {
48
+ captured.value = undefined;
49
+ }
50
+ </script>
51
+
52
+ <template>
53
+ <slot
54
+ v-if="captured !== undefined"
55
+ name="fallback"
56
+ :error="captured"
57
+ :reset="reset"
58
+ />
59
+ <slot v-else />
60
+ </template>