/** * Svelte 5 adapter type surface for schema-components. * * Mirrors the shape of `core/renderer.ts`'s React-flavoured * {@link "../core/renderer.ts".RenderProps} but adapts it for Svelte's * compile-time component model: renderers are not plain functions * returning VNodes — they are component constructors. The dispatcher * therefore packages each "render this field" call as a * {@link SvelteRenderDescriptor} pairing the component with the props * it should be instantiated against; downstream the active container * component (e.g. `Object.svelte`) materialises the descriptor via * ``. * * The shapes intentionally diverge from the React adapter on two axes: * * 1. `renderChild` returns a {@link SvelteRenderDescriptor} rather * than a `ReactNode`. Svelte cannot directly embed an object * synthesised at render time the way React embeds a JSX node — but * it can `` a `{ component, props }` pair. * 2. There is no synthetic event system, so `onChange` is plumbed * directly into the per-field props and invoked from raw DOM * handlers (`onchange`, `oninput`) inside each `.svelte` file. * * The public consumer pattern is `` — * Svelte's `bind:value` ergonomics are deliberately not forced. * The function-style `onChange` callback was chosen so the * adapter behaves identically across server-rendered (`SchemaView`), * controlled-input, and uncontrolled-input call sites; consumers that * prefer `bind:value` can wire it externally: * * ```svelte * * ``` * * which Svelte transparently translates into an `onChange` that mutates * the bound rune-backed reference. * * @group Framework Adapters */ import type { Component } from "svelte"; import type { BaseFieldProps, BaseRenderProps, RenderFunction, } from "../core/renderer.ts"; import type { WalkedField } from "../core/types.ts"; /** * Descriptor produced by {@link SvelteRenderProps.renderChild} and by * the dispatcher when materialising a single field. Pairs the Svelte * component constructor with the props it should be instantiated * against so a parent renderer can mount it via * ``. * * Returning a descriptor (rather than a rendered DOM node) keeps the * adapter compatible with Svelte's compile-time component model — the * dispatcher does not own a DOM mount point and cannot fabricate * rendered output the way React's `renderField` returns a `ReactNode`. * * `null` indicates "render nothing" — used for empty arrays in * read-only mode and for the recursion-cap sentinel placeholder when * the caller opts to suppress it. */ export interface SvelteRenderDescriptor { /** Svelte component constructor to mount. */ readonly component: SvelteComponentConstructor; /** Props to pass to the component instance. */ readonly props: SvelteRenderProps; } /** * The raw Svelte 5 component constructor type. Aliased so consumers * have a single name to import — `Component` is * exact, but reading {@link SvelteComponentConstructor} at call sites * keeps the framework dependency localised to this module. */ export type SvelteComponentConstructor = Component; /** * Props passed to every Svelte 5 renderer component. * * Specialisation of {@link BaseRenderProps} with * `Output = SvelteRenderDescriptor | null`. Each renderer receives * these as `$props()` — the per-field data, the editability flags, * the constraint bundle, and the `renderChild` factory it should * invoke for nested structures (object fields, array elements, union * options, …). * * Mirrors the React {@link "../core/renderer.ts".RenderProps} shape * — `onChange` for value propagation, four-argument `renderChild` * for recursive descent. */ export interface SvelteRenderProps extends BaseRenderProps { /** Callback to update the field value. */ onChange: (value: unknown) => void; /** * Render a child field. Container renderers (object, array, * tuple, record, union, discriminated union, conditional, * negation) call this and mount the returned descriptor via * ``. * * @param tree - The walked field tree for the child. * @param value - The child's current value. * @param onChange - Callback receiving the child's next value. * @param pathSuffix - Path segment from the parent (e.g. "city", * "[0]"). Joined to the parent's path with a dot, or * substituted when the parent acts as a transparent wrapper * (union options). Required for every container — without it * children inherit no path and `fieldDomId()` will throw. */ renderChild: ( tree: WalkedField, value: unknown, onChange: (v: unknown) => void, pathSuffix?: string ) => SvelteRenderDescriptor | null; } /** * Signature for a render function attached to a * {@link SvelteComponentResolver}. * * Unlike React — where `RenderFunction` directly produces a * `ReactNode` — the Svelte equivalent produces a * {@link SvelteRenderDescriptor}. The descriptor pairs a component * constructor with the per-field props and is mounted by the parent * renderer via ``. This indirection is the price * of Svelte's compile-time component model: the dispatcher cannot * fabricate rendered DOM at runtime, so it returns a recipe for the * parent to mount. * * Specialisation of the generic * {@link "../core/renderer.ts".RenderFunction | RenderFunction} from * `core/renderer.ts` with * `Output = SvelteRenderDescriptor | null` and * `Props = SvelteRenderProps`. */ export type SvelteRenderFunction = RenderFunction< SvelteRenderDescriptor | null, SvelteRenderProps >; /** * Helper: wrap a Svelte component constructor into the * "render function" shape consumed by the dispatcher. Pairs the * supplied component with the per-field props. * * Used by {@link "./headless.ts".headlessSvelteResolver} to register * one constructor per schema type, and exposed publicly so theme * adapter authors can compose their own resolver from `.svelte` * files without re-implementing the wrapper. * * @param component - A Svelte 5 component constructor accepting * {@link SvelteRenderProps}. * @returns A {@link SvelteRenderFunction} that, given props, returns * the descriptor `{ component, props }`. */ export function makeSvelteRenderer( component: SvelteComponentConstructor ): SvelteRenderFunction { return (props) => ({ component, props }); } /** * Theme adapter — maps every schema field type to a Svelte * {@link SvelteRenderFunction}. Unset keys fall back to the headless * resolver. * * Pass to {@link "./contexts.ts".resolverContext} (via the * `SchemaProvider` Svelte component) so a single theme drives every * schema render in a subtree. * * Structurally parallel to * {@link "../core/renderer.ts".ComponentResolver} for React, but * each value is a {@link SvelteRenderFunction} returning a * {@link SvelteRenderDescriptor} rather than a render function * returning a `ReactNode`. */ export interface SvelteComponentResolver { string?: SvelteRenderFunction; number?: SvelteRenderFunction; boolean?: SvelteRenderFunction; null?: SvelteRenderFunction; enum?: SvelteRenderFunction; object?: SvelteRenderFunction; array?: SvelteRenderFunction; tuple?: SvelteRenderFunction; record?: SvelteRenderFunction; union?: SvelteRenderFunction; discriminatedUnion?: SvelteRenderFunction; conditional?: SvelteRenderFunction; negation?: SvelteRenderFunction; literal?: SvelteRenderFunction; file?: SvelteRenderFunction; never?: SvelteRenderFunction; unknown?: SvelteRenderFunction; } /** * Widget map — maps component hints (from `.meta({ component })`) to * Svelte {@link SvelteRenderFunction}s. Mirrors the React * {@link "../core/renderer.ts".WidgetMap} but each value is a * Svelte-flavoured render function (typically produced via * {@link makeSvelteRenderer}). * * Scoped at three levels in the Svelte adapter: * * 1. **Per-instance** — `widgets` prop on `` * 2. **Context-scoped** — `widgets` prop on `` * 3. **Global** — `registerWidget()` (app-wide defaults) */ export type SvelteWidgetMap = ReadonlyMap; /** * Compile-time assertion that {@link SvelteRenderFunction} is a * specialisation of the generic {@link RenderFunction} contract from * `core/renderer.ts`. Exercised by the type-level test in * `tests/svelte/typeTest.svelte.unit.test.ts` — a regression on the * alignment fails compilation rather than silently producing * incompatible adapters. * * @internal */ export type __SvelteRenderFunctionMatchesGenericRenderFunction = SvelteRenderFunction extends RenderFunction< SvelteRenderDescriptor | null, SvelteRenderProps > ? true : false; /** * Re-export of {@link BaseFieldProps} so consumers writing custom * Svelte renderers can import a single type covering the schema-data * shape without crossing the framework adapter boundary. */ export type { BaseFieldProps };