schema-components 2.0.2 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +98 -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 +81 -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 +3 -3
- 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 +71 -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/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,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Svelte 5 implementation of `core/contexts.ts`'s {@link ContextPort}
|
|
3
|
+
* for the resolver and widget registry.
|
|
4
|
+
*
|
|
5
|
+
* Wires the abstract provide / consume port to Svelte's native
|
|
6
|
+
* `setContext()` / `getContext()` primitives (re-exported from
|
|
7
|
+
* `svelte`). Each port is keyed by a `Symbol` so multiple framework
|
|
8
|
+
* adapters can coexist in the same module graph without aliasing —
|
|
9
|
+
* Svelte's context keys are object-identity-compared.
|
|
10
|
+
*
|
|
11
|
+
* Unlike the React `<Provider>` wrapper, Svelte's context model is
|
|
12
|
+
* "set on the current component instance, read from any descendant" —
|
|
13
|
+
* the {@link ContextPort.provide} call therefore does not wrap
|
|
14
|
+
* `children`. Instead, it sets the value on the component currently
|
|
15
|
+
* being initialised; descendants call {@link ContextPort.consume} to
|
|
16
|
+
* read it. The `children` argument is accepted purely to satisfy the
|
|
17
|
+
* port contract; it is returned unchanged so callers that pass a
|
|
18
|
+
* snippet or other ad-hoc representation can still receive their
|
|
19
|
+
* input back.
|
|
20
|
+
*
|
|
21
|
+
* @group Framework Adapters
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { setContext, getContext } from "svelte";
|
|
25
|
+
import type { ContextPort } from "../core/contexts.ts";
|
|
26
|
+
import type { SvelteComponentResolver, SvelteWidgetMap } from "./types.ts";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Symbol key for the resolver context. Identity-compared by Svelte's
|
|
30
|
+
* context API — never aliases against the widget context or any other
|
|
31
|
+
* port.
|
|
32
|
+
*/
|
|
33
|
+
const RESOLVER_CONTEXT_KEY = Symbol("schema-components/svelte:resolver");
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Symbol key for the widgets context. Sibling to
|
|
37
|
+
* {@link RESOLVER_CONTEXT_KEY}.
|
|
38
|
+
*/
|
|
39
|
+
const WIDGETS_CONTEXT_KEY = Symbol("schema-components/svelte:widgets");
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Context port for the active {@link SvelteComponentResolver}.
|
|
43
|
+
*
|
|
44
|
+
* The {@link ContextPort.consume} side returns `undefined` when no
|
|
45
|
+
* resolver has been provided in the current component subtree — the
|
|
46
|
+
* Svelte dispatcher then falls through to the headless resolver,
|
|
47
|
+
* matching the React adapter's behaviour with its
|
|
48
|
+
* `UserResolverContext` default value.
|
|
49
|
+
*/
|
|
50
|
+
export const resolverContext: ContextPort<SvelteComponentResolver | undefined> =
|
|
51
|
+
{
|
|
52
|
+
provide(value, children) {
|
|
53
|
+
setContext(RESOLVER_CONTEXT_KEY, value);
|
|
54
|
+
return children;
|
|
55
|
+
},
|
|
56
|
+
consume() {
|
|
57
|
+
return getContext<SvelteComponentResolver | undefined>(
|
|
58
|
+
RESOLVER_CONTEXT_KEY
|
|
59
|
+
);
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Context port for the active {@link SvelteWidgetMap}.
|
|
65
|
+
*
|
|
66
|
+
* The {@link ContextPort.consume} side returns `undefined` when no
|
|
67
|
+
* widget map has been provided — the dispatcher then falls through to
|
|
68
|
+
* the per-instance and global widget registries before consulting the
|
|
69
|
+
* resolver chain.
|
|
70
|
+
*/
|
|
71
|
+
export const widgetsContext: ContextPort<SvelteWidgetMap | undefined> = {
|
|
72
|
+
provide(value, children) {
|
|
73
|
+
setContext(WIDGETS_CONTEXT_KEY, value);
|
|
74
|
+
return children;
|
|
75
|
+
},
|
|
76
|
+
consume() {
|
|
77
|
+
return getContext<SvelteWidgetMap | undefined>(WIDGETS_CONTEXT_KEY);
|
|
78
|
+
},
|
|
79
|
+
};
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Svelte-flavoured wrappers around `core/renderField.ts`'s
|
|
3
|
+
* framework-agnostic dispatcher.
|
|
4
|
+
*
|
|
5
|
+
* The dispatcher is the same one used by the React adapter — see
|
|
6
|
+
* `core/renderField.ts`. This module supplies the Svelte-specific
|
|
7
|
+
* {@link "../core/renderField.ts".DispatchConfig | DispatchConfig}:
|
|
8
|
+
* the `buildProps` factory, the `lookupRenderFn` against a
|
|
9
|
+
* {@link SvelteComponentResolver}, the per-step result coercion,
|
|
10
|
+
* and the recursion-cap sentinel.
|
|
11
|
+
*
|
|
12
|
+
* Centralising the wiring lets both `SchemaComponent.svelte` and
|
|
13
|
+
* `SchemaView.svelte` plug into the dispatcher without duplicating
|
|
14
|
+
* the closures, and keeps the dispatch order (depth cap → widget →
|
|
15
|
+
* resolver → fallback) identical across editable and read-only call
|
|
16
|
+
* sites.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import {
|
|
20
|
+
buildRenderProps,
|
|
21
|
+
RESOLVER_KEYS,
|
|
22
|
+
type RenderProps,
|
|
23
|
+
} from "../core/renderer.ts";
|
|
24
|
+
import { dispatchRenderField } from "../core/renderField.ts";
|
|
25
|
+
import { SchemaRenderError } from "../core/errors.ts";
|
|
26
|
+
import type { WalkedField } from "../core/types.ts";
|
|
27
|
+
import { headlessSvelteResolver } from "./headless.ts";
|
|
28
|
+
import { lookupGlobalWidget } from "./widget.ts";
|
|
29
|
+
import type {
|
|
30
|
+
SvelteComponentConstructor,
|
|
31
|
+
SvelteComponentResolver,
|
|
32
|
+
SvelteRenderDescriptor,
|
|
33
|
+
SvelteRenderProps,
|
|
34
|
+
SvelteWidgetMap,
|
|
35
|
+
} from "./types.ts";
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Merge a user-supplied {@link SvelteComponentResolver} on top of
|
|
39
|
+
* the headless resolver. Mirrors `core/renderer.ts`'s
|
|
40
|
+
* `mergeResolvers` for the Svelte-flavoured resolver shape — user
|
|
41
|
+
* values win, fallback fills gaps.
|
|
42
|
+
*
|
|
43
|
+
* Implemented directly rather than delegating to the generic
|
|
44
|
+
* `mergeResolvers` because the latter is typed against
|
|
45
|
+
* `ComponentResolver` — the React-flavoured resolver whose values
|
|
46
|
+
* are typed `RenderFunction\<unknown, RenderProps\>`. The per-key
|
|
47
|
+
* behaviour is identical: each
|
|
48
|
+
* {@link RESOLVER_KEYS} entry is taken from `user` if defined,
|
|
49
|
+
* otherwise from `fallback`. The single source of truth on which
|
|
50
|
+
* keys exist is {@link RESOLVER_KEYS}, so adding a new
|
|
51
|
+
* `WalkedField` variant in `core/types.ts` automatically threads
|
|
52
|
+
* through the Svelte merge.
|
|
53
|
+
*/
|
|
54
|
+
export function mergeSvelteResolvers(
|
|
55
|
+
user: SvelteComponentResolver,
|
|
56
|
+
fallback: SvelteComponentResolver
|
|
57
|
+
): SvelteComponentResolver {
|
|
58
|
+
const merged: SvelteComponentResolver = {};
|
|
59
|
+
for (const key of RESOLVER_KEYS) {
|
|
60
|
+
const fn = user[key] ?? fallback[key];
|
|
61
|
+
if (fn !== undefined) {
|
|
62
|
+
merged[key] = fn;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return merged;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Dispatch a single field through the Svelte dispatch chain.
|
|
70
|
+
*
|
|
71
|
+
* Looks up the matching {@link SvelteRenderFunction} in the supplied
|
|
72
|
+
* resolver (with the headless resolver as fallback), invokes it with
|
|
73
|
+
* the per-field props, and returns the resulting
|
|
74
|
+
* {@link SvelteRenderDescriptor} for the parent renderer to mount.
|
|
75
|
+
*
|
|
76
|
+
* Widget overrides (`.meta({ component: name })`) are resolved
|
|
77
|
+
* against the supplied instance → context → global chain before the
|
|
78
|
+
* resolver. The dispatch order matches the React adapter exactly:
|
|
79
|
+
*
|
|
80
|
+
* 1. Depth cap (`MAX_RENDER_DEPTH`) — returns the recursion
|
|
81
|
+
* sentinel descriptor.
|
|
82
|
+
* 2. Widget override — instance map → context map → global
|
|
83
|
+
* registry.
|
|
84
|
+
* 3. Resolver render function for `tree.type`.
|
|
85
|
+
* 4. Fallback — emits an em-dash / stringified-value descriptor.
|
|
86
|
+
*/
|
|
87
|
+
export function renderFieldSvelte(
|
|
88
|
+
tree: WalkedField,
|
|
89
|
+
value: unknown,
|
|
90
|
+
onChange: (v: unknown) => void,
|
|
91
|
+
userResolver: SvelteComponentResolver | undefined,
|
|
92
|
+
renderChild: SvelteRenderProps["renderChild"],
|
|
93
|
+
path: string,
|
|
94
|
+
instanceWidgets: SvelteWidgetMap | undefined,
|
|
95
|
+
contextWidgets: SvelteWidgetMap | undefined,
|
|
96
|
+
depth: number,
|
|
97
|
+
fallbackComponent: SvelteComponentConstructor,
|
|
98
|
+
sentinelComponent: SvelteComponentConstructor
|
|
99
|
+
): SvelteRenderDescriptor | null {
|
|
100
|
+
if (path.length === 0) {
|
|
101
|
+
throw new Error(
|
|
102
|
+
"renderFieldSvelte requires a non-empty path. Pass the root " +
|
|
103
|
+
"path for the root field and use renderChild's pathSuffix " +
|
|
104
|
+
"to derive child paths."
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const resolver: SvelteComponentResolver =
|
|
109
|
+
userResolver !== undefined
|
|
110
|
+
? mergeSvelteResolvers(userResolver, headlessSvelteResolver)
|
|
111
|
+
: headlessSvelteResolver;
|
|
112
|
+
|
|
113
|
+
return dispatchRenderField<
|
|
114
|
+
SvelteRenderProps,
|
|
115
|
+
SvelteRenderDescriptor | null,
|
|
116
|
+
SvelteComponentResolver
|
|
117
|
+
>({
|
|
118
|
+
tree,
|
|
119
|
+
value,
|
|
120
|
+
path,
|
|
121
|
+
depth,
|
|
122
|
+
resolver,
|
|
123
|
+
config: {
|
|
124
|
+
buildProps: (fieldTree, fieldPath) => {
|
|
125
|
+
// The React-flavoured `buildRenderProps` returns
|
|
126
|
+
// `RenderProps`. Svelte's `SvelteRenderProps` shares
|
|
127
|
+
// every field except the `renderChild` return type
|
|
128
|
+
// (which is `SvelteRenderDescriptor | null` here vs.
|
|
129
|
+
// `unknown` for React). Adapt by re-wrapping the
|
|
130
|
+
// child render function we hold to satisfy the
|
|
131
|
+
// structural shape `buildRenderProps` expects, then
|
|
132
|
+
// re-cast on output. The cast is contained to this
|
|
133
|
+
// one boundary; no runtime reshaping required.
|
|
134
|
+
const reactShapedChild: RenderProps["renderChild"] = (
|
|
135
|
+
childTree,
|
|
136
|
+
childValue,
|
|
137
|
+
childOnChange,
|
|
138
|
+
pathSuffix
|
|
139
|
+
) =>
|
|
140
|
+
renderChild(
|
|
141
|
+
childTree,
|
|
142
|
+
childValue,
|
|
143
|
+
childOnChange,
|
|
144
|
+
pathSuffix
|
|
145
|
+
);
|
|
146
|
+
const reactProps = buildRenderProps(
|
|
147
|
+
fieldTree,
|
|
148
|
+
value,
|
|
149
|
+
onChange,
|
|
150
|
+
reactShapedChild,
|
|
151
|
+
fieldPath
|
|
152
|
+
);
|
|
153
|
+
// Re-shape onto SvelteRenderProps. The structural
|
|
154
|
+
// overlap with `RenderProps` is total apart from the
|
|
155
|
+
// `renderChild` return type; we substitute the
|
|
156
|
+
// Svelte-shaped function so consumers receive the
|
|
157
|
+
// proper output narrowing.
|
|
158
|
+
const svelteProps: SvelteRenderProps = {
|
|
159
|
+
...reactProps,
|
|
160
|
+
renderChild,
|
|
161
|
+
};
|
|
162
|
+
return svelteProps;
|
|
163
|
+
},
|
|
164
|
+
lookupRenderFn: (type, mergedResolver) => mergedResolver[type],
|
|
165
|
+
...(instanceWidgets !== undefined || contextWidgets !== undefined
|
|
166
|
+
? {
|
|
167
|
+
lookupWidget: (name: string) =>
|
|
168
|
+
instanceWidgets?.get(name) ??
|
|
169
|
+
contextWidgets?.get(name) ??
|
|
170
|
+
lookupGlobalWidget(name),
|
|
171
|
+
}
|
|
172
|
+
: {
|
|
173
|
+
lookupWidget: (name: string) => lookupGlobalWidget(name),
|
|
174
|
+
}),
|
|
175
|
+
recursionSentinel: (fieldTree) => ({
|
|
176
|
+
component: sentinelComponent,
|
|
177
|
+
props: buildSentinelProps(
|
|
178
|
+
fieldTree,
|
|
179
|
+
value,
|
|
180
|
+
onChange,
|
|
181
|
+
path,
|
|
182
|
+
renderChild
|
|
183
|
+
),
|
|
184
|
+
}),
|
|
185
|
+
fallback: (fieldTree, fieldValue) => ({
|
|
186
|
+
component: fallbackComponent,
|
|
187
|
+
props: buildSentinelProps(
|
|
188
|
+
fieldTree,
|
|
189
|
+
fieldValue,
|
|
190
|
+
onChange,
|
|
191
|
+
path,
|
|
192
|
+
renderChild
|
|
193
|
+
),
|
|
194
|
+
}),
|
|
195
|
+
coerceResult: (result, step) => {
|
|
196
|
+
// Widget step — undefined / null falls through to
|
|
197
|
+
// the resolver. Anything else is treated as a
|
|
198
|
+
// descriptor (or null short-circuit).
|
|
199
|
+
if (step === "widget") {
|
|
200
|
+
if (result === undefined || result === null)
|
|
201
|
+
return undefined;
|
|
202
|
+
if (isDescriptor(result)) return result;
|
|
203
|
+
return undefined;
|
|
204
|
+
}
|
|
205
|
+
// Resolver step — undefined falls through to the
|
|
206
|
+
// fallback; null short-circuits with "render
|
|
207
|
+
// nothing" (matches React's empty-array suppression
|
|
208
|
+
// path).
|
|
209
|
+
if (result === undefined) return undefined;
|
|
210
|
+
if (result === null) return null;
|
|
211
|
+
if (isDescriptor(result)) return result;
|
|
212
|
+
return undefined;
|
|
213
|
+
},
|
|
214
|
+
wrapRenderError: (err, fieldTree, fieldPath) =>
|
|
215
|
+
new SchemaRenderError(
|
|
216
|
+
err instanceof Error
|
|
217
|
+
? err.message
|
|
218
|
+
: `Render function threw for type "${fieldTree.type}" at "${fieldPath}"`,
|
|
219
|
+
fieldTree,
|
|
220
|
+
fieldTree.type,
|
|
221
|
+
err
|
|
222
|
+
),
|
|
223
|
+
},
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Build the per-field props passed to the recursion-sentinel /
|
|
229
|
+
* fallback Svelte components. Both stages need the full
|
|
230
|
+
* {@link SvelteRenderProps} shape (so the component can read
|
|
231
|
+
* `value`, `meta`, etc.) so the helper centralises the construction.
|
|
232
|
+
*/
|
|
233
|
+
function buildSentinelProps(
|
|
234
|
+
tree: WalkedField,
|
|
235
|
+
value: unknown,
|
|
236
|
+
onChange: (v: unknown) => void,
|
|
237
|
+
path: string,
|
|
238
|
+
renderChild: SvelteRenderProps["renderChild"]
|
|
239
|
+
): SvelteRenderProps {
|
|
240
|
+
return {
|
|
241
|
+
value,
|
|
242
|
+
readOnly: tree.editability === "presentation",
|
|
243
|
+
writeOnly: tree.editability === "input",
|
|
244
|
+
meta: tree.meta,
|
|
245
|
+
constraints: tree.constraints,
|
|
246
|
+
path,
|
|
247
|
+
tree,
|
|
248
|
+
onChange,
|
|
249
|
+
renderChild,
|
|
250
|
+
...(tree.examples !== undefined ? { examples: tree.examples } : {}),
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Type guard narrowing an unknown widget / resolver return value to
|
|
256
|
+
* a {@link SvelteRenderDescriptor}. The shape check is structural:
|
|
257
|
+
* any object with a callable `component` and a `props` field passes.
|
|
258
|
+
*
|
|
259
|
+
* Strict enough that an accidentally-returned `ReactNode` or HTML
|
|
260
|
+
* string is rejected before the dispatcher hands it to `<Mount>`.
|
|
261
|
+
*/
|
|
262
|
+
function isDescriptor(value: unknown): value is SvelteRenderDescriptor {
|
|
263
|
+
if (typeof value !== "object" || value === null) return false;
|
|
264
|
+
if (!("component" in value)) return false;
|
|
265
|
+
if (!("props" in value)) return false;
|
|
266
|
+
return typeof value.component === "function";
|
|
267
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Svelte 5 headless renderer — the default
|
|
3
|
+
* {@link SvelteComponentResolver} implementation.
|
|
4
|
+
*
|
|
5
|
+
* Produces plain HTML elements (through one `.svelte` component per
|
|
6
|
+
* schema type) for every field variant the walker can emit. Theme
|
|
7
|
+
* adapters override entries by supplying their own resolver to the
|
|
8
|
+
* `<SchemaProvider>` component.
|
|
9
|
+
*
|
|
10
|
+
* The headless resolver wires each entry through
|
|
11
|
+
* {@link makeSvelteRenderer} — which pairs the component constructor
|
|
12
|
+
* with the per-field props the dispatcher computes, returning a
|
|
13
|
+
* {@link SvelteRenderDescriptor} that the parent container renderer
|
|
14
|
+
* mounts via `<Mount descriptor={…} />`.
|
|
15
|
+
*
|
|
16
|
+
* Mirror of `react/headless.tsx` — same schema-type coverage, same
|
|
17
|
+
* accessibility wiring, same fallback policy. Every `WalkedField`
|
|
18
|
+
* variant the walker can emit has a registered renderer here;
|
|
19
|
+
* missing a registration would cause `getRenderFunction` to return
|
|
20
|
+
* `undefined` and the field to render as nothing, so the resolver
|
|
21
|
+
* registration below is the single source of completeness.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { makeSvelteRenderer } from "./types.ts";
|
|
25
|
+
import type { SvelteComponentResolver } from "./types.ts";
|
|
26
|
+
import StringSvelte from "./renderers/String.svelte";
|
|
27
|
+
import NumberSvelte from "./renderers/Number.svelte";
|
|
28
|
+
import BooleanSvelte from "./renderers/Boolean.svelte";
|
|
29
|
+
import EnumSvelte from "./renderers/Enum.svelte";
|
|
30
|
+
import ObjectSvelte from "./renderers/Object.svelte";
|
|
31
|
+
import ArraySvelte from "./renderers/Array.svelte";
|
|
32
|
+
import TupleSvelte from "./renderers/Tuple.svelte";
|
|
33
|
+
import RecordSvelte from "./renderers/Record.svelte";
|
|
34
|
+
import UnionSvelte from "./renderers/Union.svelte";
|
|
35
|
+
import DiscriminatedUnionSvelte from "./renderers/DiscriminatedUnion.svelte";
|
|
36
|
+
import LiteralSvelte from "./renderers/Literal.svelte";
|
|
37
|
+
import NullSvelte from "./renderers/Null.svelte";
|
|
38
|
+
import NeverSvelte from "./renderers/Never.svelte";
|
|
39
|
+
import ConditionalSvelte from "./renderers/Conditional.svelte";
|
|
40
|
+
import NegationSvelte from "./renderers/Negation.svelte";
|
|
41
|
+
import FileSvelte from "./renderers/File.svelte";
|
|
42
|
+
import UnknownSvelte from "./renderers/Unknown.svelte";
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Default {@link SvelteComponentResolver} used by `<SchemaComponent>`
|
|
46
|
+
* / `<SchemaView>` when no theme adapter is provided via
|
|
47
|
+
* `<SchemaProvider>`.
|
|
48
|
+
*
|
|
49
|
+
* Each entry pairs a schema type with the Svelte component
|
|
50
|
+
* constructor that should render it. {@link makeSvelteRenderer}
|
|
51
|
+
* wraps the constructor into the
|
|
52
|
+
* `(props) =\> SvelteRenderDescriptor` shape consumed by the
|
|
53
|
+
* dispatcher.
|
|
54
|
+
*/
|
|
55
|
+
export const headlessSvelteResolver: SvelteComponentResolver = {
|
|
56
|
+
string: makeSvelteRenderer(StringSvelte),
|
|
57
|
+
number: makeSvelteRenderer(NumberSvelte),
|
|
58
|
+
boolean: makeSvelteRenderer(BooleanSvelte),
|
|
59
|
+
null: makeSvelteRenderer(NullSvelte),
|
|
60
|
+
enum: makeSvelteRenderer(EnumSvelte),
|
|
61
|
+
object: makeSvelteRenderer(ObjectSvelte),
|
|
62
|
+
array: makeSvelteRenderer(ArraySvelte),
|
|
63
|
+
tuple: makeSvelteRenderer(TupleSvelte),
|
|
64
|
+
record: makeSvelteRenderer(RecordSvelte),
|
|
65
|
+
union: makeSvelteRenderer(UnionSvelte),
|
|
66
|
+
discriminatedUnion: makeSvelteRenderer(DiscriminatedUnionSvelte),
|
|
67
|
+
conditional: makeSvelteRenderer(ConditionalSvelte),
|
|
68
|
+
negation: makeSvelteRenderer(NegationSvelte),
|
|
69
|
+
literal: makeSvelteRenderer(LiteralSvelte),
|
|
70
|
+
file: makeSvelteRenderer(FileSvelte),
|
|
71
|
+
never: makeSvelteRenderer(NeverSvelte),
|
|
72
|
+
unknown: makeSvelteRenderer(UnknownSvelte),
|
|
73
|
+
};
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helper functions shared by the Svelte 5 headless renderers.
|
|
3
|
+
*
|
|
4
|
+
* Ports the framework-agnostic helpers attached to
|
|
5
|
+
* `react/headlessRenderers.tsx`:
|
|
6
|
+
*
|
|
7
|
+
* - {@link defaultRecordValue} — type-appropriate "new entry" value
|
|
8
|
+
* for `RecordField` / `ArrayField`'s Add button.
|
|
9
|
+
* - {@link nextRecordKey} — collision-free key generator for the
|
|
10
|
+
* Record "Add entry" button.
|
|
11
|
+
* - {@link renameRecordKey} — insertion-order-preserving rename.
|
|
12
|
+
* - {@link discriminatedUnionValueForTab} — tab-index → emitted
|
|
13
|
+
* value mapper for the discriminated-union tabs widget.
|
|
14
|
+
*
|
|
15
|
+
* These contain no rendering logic — they are pure functions over
|
|
16
|
+
* the walked field tree and JS values, so the React and Svelte
|
|
17
|
+
* adapters share the same behaviour without depending on each
|
|
18
|
+
* other's rendering surface.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import type { WalkedField } from "../core/types.ts";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Compute the default value for a freshly added record / array
|
|
25
|
+
* entry based on the value-type schema. Falls back to a
|
|
26
|
+
* type-appropriate empty value when the schema does not declare a
|
|
27
|
+
* default.
|
|
28
|
+
*
|
|
29
|
+
* The switch is exhaustive over `WalkedField.type` so a new schema
|
|
30
|
+
* variant added to the walker forces a deliberate choice of default
|
|
31
|
+
* here rather than silently producing `undefined`.
|
|
32
|
+
*/
|
|
33
|
+
export function defaultRecordValue(valueType: WalkedField): unknown {
|
|
34
|
+
if (valueType.defaultValue !== undefined) return valueType.defaultValue;
|
|
35
|
+
switch (valueType.type) {
|
|
36
|
+
case "string":
|
|
37
|
+
return "";
|
|
38
|
+
case "number":
|
|
39
|
+
return 0;
|
|
40
|
+
case "boolean":
|
|
41
|
+
return false;
|
|
42
|
+
case "array":
|
|
43
|
+
return [];
|
|
44
|
+
case "object":
|
|
45
|
+
case "record":
|
|
46
|
+
return {};
|
|
47
|
+
case "null":
|
|
48
|
+
return null;
|
|
49
|
+
case "unknown":
|
|
50
|
+
case "enum":
|
|
51
|
+
case "literal":
|
|
52
|
+
case "tuple":
|
|
53
|
+
case "union":
|
|
54
|
+
case "discriminatedUnion":
|
|
55
|
+
case "conditional":
|
|
56
|
+
case "negation":
|
|
57
|
+
case "file":
|
|
58
|
+
case "never":
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Generate a unique, currently-unused key for a new record entry.
|
|
65
|
+
* Picks the first of `key`, `key-1`, `key-2`, … that is not in
|
|
66
|
+
* `existing`.
|
|
67
|
+
*/
|
|
68
|
+
export function nextRecordKey(
|
|
69
|
+
existing: readonly string[],
|
|
70
|
+
base = "key"
|
|
71
|
+
): string {
|
|
72
|
+
if (!existing.includes(base)) return base;
|
|
73
|
+
let i = 1;
|
|
74
|
+
while (existing.includes(`${base}-${String(i)}`)) i += 1;
|
|
75
|
+
return `${base}-${String(i)}`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Rename a key in an object while preserving insertion order.
|
|
80
|
+
* Returns the original object reference when the rename is a no-op
|
|
81
|
+
* (`oldKey === newKey`) or when `newKey` collides with an existing
|
|
82
|
+
* key — the renderer uses reference equality to skip an unnecessary
|
|
83
|
+
* `props.onChange` call.
|
|
84
|
+
*/
|
|
85
|
+
export function renameRecordKey(
|
|
86
|
+
obj: Record<string, unknown>,
|
|
87
|
+
oldKey: string,
|
|
88
|
+
newKey: string
|
|
89
|
+
): Record<string, unknown> {
|
|
90
|
+
if (oldKey === newKey) return obj;
|
|
91
|
+
if (newKey in obj && newKey !== oldKey) return obj;
|
|
92
|
+
const renamed: Record<string, unknown> = {};
|
|
93
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
94
|
+
renamed[k === oldKey ? newKey : k] = v;
|
|
95
|
+
}
|
|
96
|
+
return renamed;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Pure helper: convert a tab index into the new value the
|
|
101
|
+
* discriminated union should emit. Returns `undefined` when the index
|
|
102
|
+
* is out of bounds — callers skip the `onChange` call entirely.
|
|
103
|
+
*
|
|
104
|
+
* Extracted so the contract is unit-testable without rendering the
|
|
105
|
+
* tabs component.
|
|
106
|
+
*/
|
|
107
|
+
export function discriminatedUnionValueForTab(
|
|
108
|
+
optionLabels: readonly string[],
|
|
109
|
+
discKey: string,
|
|
110
|
+
newIndex: number
|
|
111
|
+
): Record<string, string> | undefined {
|
|
112
|
+
const label = optionLabels[newIndex];
|
|
113
|
+
if (label === undefined) return undefined;
|
|
114
|
+
return { [discKey]: label };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Wrap an index into a valid tab index using floored modulo. Used
|
|
119
|
+
* by the discriminated-union keyboard handler to wrap arrow-key
|
|
120
|
+
* navigation at the extremes.
|
|
121
|
+
*/
|
|
122
|
+
export function wrapTabIndex(index: number, total: number): number {
|
|
123
|
+
return ((index % total) + total) % total;
|
|
124
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Headless Svelte 5 renderer for `ArrayField` — ordered list with
|
|
3
|
+
add / remove controls. Mirror of
|
|
4
|
+
`react/headlessRenderers.tsx :: renderArray`.
|
|
5
|
+
|
|
6
|
+
Read-only mode renders the list without controls; an empty array
|
|
7
|
+
produces no list so leaf nodes in recursive schemas don't get
|
|
8
|
+
orphaned "Children" labels.
|
|
9
|
+
|
|
10
|
+
Editable mode wraps each item in `<li>` with a Remove button and
|
|
11
|
+
appends an "Add item" button at the foot. `<button type="button">`
|
|
12
|
+
keeps the controls keyboard-accessible (Space / Enter) without
|
|
13
|
+
custom key handlers and stops them accidentally submitting any
|
|
14
|
+
enclosing form.
|
|
15
|
+
-->
|
|
16
|
+
<script lang="ts">
|
|
17
|
+
import type { SvelteRenderProps } from "../types.ts";
|
|
18
|
+
import { ariaLabel } from "../a11y.ts";
|
|
19
|
+
import { defaultRecordValue } from "../headlessFns.ts";
|
|
20
|
+
import Mount from "./Mount.svelte";
|
|
21
|
+
|
|
22
|
+
const props = $props<SvelteRenderProps>();
|
|
23
|
+
|
|
24
|
+
const arr = $derived<unknown[]>(
|
|
25
|
+
Array.isArray(props.value) ? props.value : []
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
const element = $derived(
|
|
29
|
+
props.tree.type === "array" ? props.tree.element : undefined
|
|
30
|
+
);
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
{#if props.tree.type !== "array" || element === undefined}
|
|
34
|
+
{#if !props.readOnly}
|
|
35
|
+
<!-- defensive: misconfigured resolver wired this component into a non-array slot. -->
|
|
36
|
+
{/if}
|
|
37
|
+
{:else if props.readOnly}
|
|
38
|
+
{#if arr.length > 0}
|
|
39
|
+
<ul role="group" aria-label={ariaLabel(props.meta.description)}>
|
|
40
|
+
{#each arr as item, i (i)}
|
|
41
|
+
{@const child = props.renderChild(
|
|
42
|
+
element,
|
|
43
|
+
item,
|
|
44
|
+
() => {
|
|
45
|
+
/* read-only: noop */
|
|
46
|
+
},
|
|
47
|
+
`[${String(i)}]`
|
|
48
|
+
)}
|
|
49
|
+
{#if child !== null}
|
|
50
|
+
<li>
|
|
51
|
+
<Mount descriptor={child} />
|
|
52
|
+
</li>
|
|
53
|
+
{/if}
|
|
54
|
+
{/each}
|
|
55
|
+
</ul>
|
|
56
|
+
{/if}
|
|
57
|
+
{:else}
|
|
58
|
+
{@const handleRemove = (index: number) => {
|
|
59
|
+
const next = arr.slice();
|
|
60
|
+
next.splice(index, 1);
|
|
61
|
+
props.onChange(next);
|
|
62
|
+
}}
|
|
63
|
+
{@const handleAdd = () => {
|
|
64
|
+
const next = arr.slice();
|
|
65
|
+
next.push(defaultRecordValue(element));
|
|
66
|
+
props.onChange(next);
|
|
67
|
+
}}
|
|
68
|
+
<div role="group" aria-label={ariaLabel(props.meta.description)}>
|
|
69
|
+
<ul>
|
|
70
|
+
{#each arr as item, i (i)}
|
|
71
|
+
{@const childOnChange = (v: unknown) => {
|
|
72
|
+
const nextArr = arr.slice();
|
|
73
|
+
nextArr[i] = v;
|
|
74
|
+
props.onChange(nextArr);
|
|
75
|
+
}}
|
|
76
|
+
{@const child = props.renderChild(
|
|
77
|
+
element,
|
|
78
|
+
item,
|
|
79
|
+
childOnChange,
|
|
80
|
+
`[${String(i)}]`
|
|
81
|
+
)}
|
|
82
|
+
<li>
|
|
83
|
+
{#if child !== null}
|
|
84
|
+
<Mount descriptor={child} />
|
|
85
|
+
{/if}
|
|
86
|
+
<button
|
|
87
|
+
type="button"
|
|
88
|
+
aria-label={`Remove item ${String(i)}`}
|
|
89
|
+
onclick={() => handleRemove(i)}>Remove</button
|
|
90
|
+
>
|
|
91
|
+
</li>
|
|
92
|
+
{/each}
|
|
93
|
+
</ul>
|
|
94
|
+
<button type="button" aria-label="Add item" onclick={handleAdd}
|
|
95
|
+
>Add</button
|
|
96
|
+
>
|
|
97
|
+
</div>
|
|
98
|
+
{/if}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Headless Svelte 5 renderer for `BooleanField` — plain
|
|
3
|
+
`<input type="checkbox">` mirror of
|
|
4
|
+
`react/headlessRenderers.tsx :: renderBoolean`.
|
|
5
|
+
|
|
6
|
+
Read-only mode renders "Yes" / "No" through a `<span>`, falling
|
|
7
|
+
back to an em-dash for non-boolean values. Editable mode applies
|
|
8
|
+
the description as an `aria-label` via `buildAriaAttrs` — there
|
|
9
|
+
is no separate placeholder slot on a checkbox, so the description
|
|
10
|
+
drives the accessible name instead.
|
|
11
|
+
-->
|
|
12
|
+
<script lang="ts">
|
|
13
|
+
import type { SvelteRenderProps } from "../types.ts";
|
|
14
|
+
import { fieldDomId } from "../../core/idPath.ts";
|
|
15
|
+
import { EM_DASH } from "../../core/cssClasses.ts";
|
|
16
|
+
import { buildAriaAttrs } from "../a11y.ts";
|
|
17
|
+
|
|
18
|
+
const props = $props<SvelteRenderProps>();
|
|
19
|
+
|
|
20
|
+
const id = $derived(fieldDomId(props.path));
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
{#if props.readOnly}
|
|
24
|
+
{#if typeof props.value !== "boolean"}
|
|
25
|
+
<span {id}>{EM_DASH}</span>
|
|
26
|
+
{:else}
|
|
27
|
+
<span {id}>{props.value ? "Yes" : "No"}</span>
|
|
28
|
+
{/if}
|
|
29
|
+
{:else}
|
|
30
|
+
{@const ariaAttrs = buildAriaAttrs(props.tree, props.meta.description)}
|
|
31
|
+
<input
|
|
32
|
+
{id}
|
|
33
|
+
type="checkbox"
|
|
34
|
+
checked={props.writeOnly ? false : props.value === true}
|
|
35
|
+
onchange={(e) => {
|
|
36
|
+
const target = e.currentTarget;
|
|
37
|
+
if (target instanceof HTMLInputElement) {
|
|
38
|
+
props.onChange(target.checked);
|
|
39
|
+
}
|
|
40
|
+
}}
|
|
41
|
+
{...ariaAttrs}
|
|
42
|
+
/>
|
|
43
|
+
{/if}
|