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.
- package/README.md +133 -1
- package/dist/SchemaComponent-B__6-5-E.d.mts +277 -0
- package/dist/SchemaComponent-BxzzsHsK.mjs +668 -0
- package/dist/adapter-ktQaheWB.d.mts +213 -0
- package/dist/constructorTypes-BdCiMS6e.d.mts +30 -0
- package/dist/core/adapter.d.mts +3 -213
- package/dist/core/constraintHint.d.mts +1 -1
- package/dist/core/constraints.d.mts +2 -2
- package/dist/core/contexts.d.mts +71 -0
- package/dist/core/contexts.mjs +1 -0
- package/dist/core/diagnostics.d.mts +1 -1
- package/dist/core/errors.d.mts +1 -1
- package/dist/core/fieldOrder.d.mts +1 -1
- package/dist/{react → core}/fieldPath.d.mts +2 -2
- package/dist/{react → core}/fieldPath.mjs +3 -3
- package/dist/core/formats.d.mts +1 -1
- package/dist/core/inferValue.d.mts +1 -1
- package/dist/core/limits.d.mts +1 -1
- package/dist/core/merge.d.mts +1 -1
- package/dist/core/normalise.d.mts +2 -2
- package/dist/core/ref.d.mts +1 -1
- package/dist/core/renderField.d.mts +147 -0
- package/dist/core/renderField.mjs +115 -0
- package/dist/core/renderer.d.mts +2 -199
- package/dist/core/swagger2.d.mts +1 -1
- package/dist/core/typeInference.d.mts +1 -982
- package/dist/core/types.d.mts +1 -1
- package/dist/core/unionMatch.d.mts +1 -1
- package/dist/core/version.d.mts +1 -1
- package/dist/core/walkBuilders.d.mts +3 -3
- package/dist/core/walker.d.mts +1 -1
- package/dist/{errors-Dki7tji4.d.mts → errors-DbaI04x2.d.mts} +1 -1
- package/dist/html/a11y.d.mts +2 -2
- package/dist/html/renderToHtml.d.mts +5 -5
- package/dist/html/renderToHtml.mjs +33 -18
- package/dist/html/renderToHtmlStream.d.mts +5 -5
- package/dist/html/renderers.d.mts +1 -1
- package/dist/html/streamRenderers.d.mts +15 -6
- package/dist/html/streamRenderers.mjs +56 -10
- package/dist/{inferValue-Ce-PviSD.d.mts → inferValue-eAnh50EM.d.mts} +3 -3
- package/dist/lit/SchemaComponent.d.mts +125 -0
- package/dist/lit/SchemaComponent.mjs +2 -0
- package/dist/lit/SchemaField.d.mts +65 -0
- package/dist/lit/SchemaField.mjs +2 -0
- package/dist/lit/SchemaView.d.mts +14 -0
- package/dist/lit/SchemaView.mjs +2 -0
- package/dist/lit/constructorTypes.d.mts +2 -0
- package/dist/lit/constructorTypes.mjs +1 -0
- package/dist/lit/contexts.d.mts +78 -0
- package/dist/lit/contexts.mjs +238 -0
- package/dist/lit/defaultResolver.d.mts +33 -0
- package/dist/lit/defaultResolver.mjs +2 -0
- package/dist/lit/registry.d.mts +66 -0
- package/dist/lit/registry.mjs +2 -0
- package/dist/lit/renderers/baseElement.d.mts +131 -0
- package/dist/lit/renderers/baseElement.mjs +109 -0
- package/dist/lit/renderers/recordHelpers.d.mts +25 -0
- package/dist/lit/renderers/recordHelpers.mjs +55 -0
- package/dist/lit/renderers/scArray.d.mts +14 -0
- package/dist/lit/renderers/scArray.mjs +86 -0
- package/dist/lit/renderers/scBoolean.d.mts +15 -0
- package/dist/lit/renderers/scBoolean.mjs +47 -0
- package/dist/lit/renderers/scConditional.d.mts +23 -0
- package/dist/lit/renderers/scConditional.mjs +65 -0
- package/dist/lit/renderers/scDiscriminated.d.mts +23 -0
- package/dist/lit/renderers/scDiscriminated.mjs +138 -0
- package/dist/lit/renderers/scEnum.d.mts +16 -0
- package/dist/lit/renderers/scEnum.mjs +66 -0
- package/dist/lit/renderers/scFile.d.mts +15 -0
- package/dist/lit/renderers/scFile.mjs +53 -0
- package/dist/lit/renderers/scLiteralNullNever.d.mts +30 -0
- package/dist/lit/renderers/scLiteralNullNever.mjs +57 -0
- package/dist/lit/renderers/scNumber.d.mts +15 -0
- package/dist/lit/renderers/scNumber.mjs +64 -0
- package/dist/lit/renderers/scObject.d.mts +14 -0
- package/dist/lit/renderers/scObject.mjs +57 -0
- package/dist/lit/renderers/scRecord.d.mts +14 -0
- package/dist/lit/renderers/scRecord.mjs +112 -0
- package/dist/lit/renderers/scString.d.mts +19 -0
- package/dist/lit/renderers/scString.mjs +165 -0
- package/dist/lit/renderers/scTuple.d.mts +14 -0
- package/dist/lit/renderers/scTuple.mjs +58 -0
- package/dist/lit/renderers/scUnion.d.mts +14 -0
- package/dist/lit/renderers/scUnion.mjs +44 -0
- package/dist/lit/renderers/scUnknown.d.mts +15 -0
- package/dist/lit/renderers/scUnknown.mjs +45 -0
- package/dist/lit/ssr.d.mts +37 -0
- package/dist/lit/ssr.mjs +9565 -0
- package/dist/lit/types.d.mts +2 -0
- package/dist/lit/types.mjs +1 -0
- package/dist/lit/widget.d.mts +71 -0
- package/dist/lit/widget.mjs +87 -0
- package/dist/openapi/ApiCallbacks.d.mts +1 -1
- package/dist/openapi/ApiLinks.d.mts +1 -1
- package/dist/openapi/ApiResponseHeaders.d.mts +1 -1
- package/dist/openapi/ApiSecurity.d.mts +1 -1
- package/dist/openapi/components.d.mts +4 -4
- package/dist/openapi/parser.d.mts +2 -2
- package/dist/openapi/resolve.d.mts +1 -1
- package/dist/preact/SchemaComponent.d.mts +3 -0
- package/dist/preact/SchemaComponent.mjs +26 -0
- package/dist/preact/SchemaErrorBoundary.d.mts +2 -0
- package/dist/preact/SchemaErrorBoundary.mjs +20 -0
- package/dist/preact/SchemaView.d.mts +2 -0
- package/dist/preact/SchemaView.mjs +22 -0
- package/dist/preact/headless.d.mts +2 -0
- package/dist/preact/headless.mjs +18 -0
- package/dist/react/SchemaComponent.d.mts +3 -270
- package/dist/react/SchemaComponent.mjs +41 -32
- package/dist/react/SchemaView.d.mts +6 -6
- package/dist/react/SchemaView.mjs +32 -29
- package/dist/react/a11y.d.mts +2 -2
- package/dist/react/fieldShell.d.mts +1 -1
- package/dist/react/headless.d.mts +1 -1
- package/dist/react/headlessRenderers.d.mts +2 -2
- package/dist/{ref-DdsbekXX.d.mts → ref-DWrQG1Er.d.mts} +1 -1
- package/dist/renderer-ab9E52Bp.d.mts +245 -0
- package/dist/solid/SchemaComponent.d.mts +136 -0
- package/dist/solid/SchemaComponent.mjs +391 -0
- package/dist/solid/SchemaErrorBoundary.d.mts +38 -0
- package/dist/solid/SchemaErrorBoundary.mjs +57 -0
- package/dist/solid/SchemaField.d.mts +40 -0
- package/dist/solid/SchemaField.mjs +113 -0
- package/dist/solid/SchemaView.d.mts +54 -0
- package/dist/solid/SchemaView.mjs +168 -0
- package/dist/solid/a11y.d.mts +70 -0
- package/dist/solid/a11y.mjs +71 -0
- package/dist/solid/contexts.d.mts +37 -0
- package/dist/solid/contexts.mjs +66 -0
- package/dist/solid/headless.d.mts +10 -0
- package/dist/solid/headless.mjs +27 -0
- package/dist/solid/renderers.d.mts +79 -0
- package/dist/solid/renderers.mjs +840 -0
- package/dist/solid/types.d.mts +90 -0
- package/dist/solid/types.mjs +1 -0
- package/dist/solid/widget.d.mts +29 -0
- package/dist/solid/widget.mjs +35 -0
- package/dist/themes/mantine.d.mts +1 -1
- package/dist/themes/mui.d.mts +1 -1
- package/dist/themes/radix.d.mts +1 -1
- package/dist/themes/shadcn.d.mts +1 -1
- package/dist/typeInference-Y8tNEQJk.d.mts +983 -0
- package/dist/types-BCy7K3nk.d.mts +125 -0
- package/package.json +73 -1
- package/src/svelte/SchemaComponent.svelte +427 -0
- package/src/svelte/SchemaErrorBoundary.svelte +66 -0
- package/src/svelte/SchemaField.svelte +216 -0
- package/src/svelte/SchemaProvider.svelte +46 -0
- package/src/svelte/SchemaView.svelte +244 -0
- package/src/svelte/a11y.ts +112 -0
- package/src/svelte/contexts.ts +79 -0
- package/src/svelte/dispatch.ts +267 -0
- package/src/svelte/headless.ts +73 -0
- package/src/svelte/headlessFns.ts +124 -0
- package/src/svelte/renderers/Array.svelte +98 -0
- package/src/svelte/renderers/Boolean.svelte +43 -0
- package/src/svelte/renderers/Conditional.svelte +67 -0
- package/src/svelte/renderers/DiscriminatedUnion.svelte +197 -0
- package/src/svelte/renderers/Enum.svelte +53 -0
- package/src/svelte/renderers/Fallback.svelte +24 -0
- package/src/svelte/renderers/File.svelte +46 -0
- package/src/svelte/renderers/Literal.svelte +29 -0
- package/src/svelte/renderers/Mount.svelte +24 -0
- package/src/svelte/renderers/Negation.svelte +35 -0
- package/src/svelte/renderers/Never.svelte +24 -0
- package/src/svelte/renderers/Null.svelte +19 -0
- package/src/svelte/renderers/Number.svelte +68 -0
- package/src/svelte/renderers/Object.svelte +74 -0
- package/src/svelte/renderers/Record.svelte +134 -0
- package/src/svelte/renderers/RecursionSentinel.svelte +27 -0
- package/src/svelte/renderers/String.svelte +152 -0
- package/src/svelte/renderers/Tuple.svelte +84 -0
- package/src/svelte/renderers/Union.svelte +49 -0
- package/src/svelte/renderers/Unknown.svelte +42 -0
- package/src/svelte/svelte-modules.d.ts +25 -0
- package/src/svelte/types.ts +238 -0
- package/src/svelte/widget.ts +62 -0
- package/src/vue/SchemaComponent.vue +274 -0
- package/src/vue/SchemaErrorBoundary.vue +60 -0
- package/src/vue/SchemaField.vue +178 -0
- package/src/vue/SchemaProvider.vue +39 -0
- package/src/vue/SchemaView.vue +198 -0
- package/src/vue/VNodeHost.ts +32 -0
- package/src/vue/contexts.ts +116 -0
- package/src/vue/eventTargets.ts +35 -0
- package/src/vue/headless.ts +61 -0
- package/src/vue/idPrefix.ts +79 -0
- package/src/vue/renderField.ts +182 -0
- package/src/vue/renderers.ts +1297 -0
- package/src/vue/resolver.ts +45 -0
- package/src/vue/types.ts +140 -0
- package/src/vue/vue-shim.d.ts +25 -0
- package/src/vue/widget.ts +51 -0
- /package/dist/{diagnostics-BTrm3O6J.d.mts → diagnostics-mftUZI7c.d.mts} +0 -0
- /package/dist/{limits-x4OiyJxh.d.mts → limits-Vv9hUbI_.d.mts} +0 -0
- /package/dist/{types-BrYbjC7_.d.mts → types-BBQaEPfE.d.mts} +0 -0
- /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>
|