@vertz/ui 0.2.0 → 0.2.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 (35) hide show
  1. package/README.md +339 -857
  2. package/dist/css/public.d.ts +24 -27
  3. package/dist/css/public.js +5 -1
  4. package/dist/form/public.d.ts +94 -38
  5. package/dist/form/public.js +5 -3
  6. package/dist/index.d.ts +696 -167
  7. package/dist/index.js +461 -84
  8. package/dist/internals.d.ts +192 -23
  9. package/dist/internals.js +151 -102
  10. package/dist/jsx-runtime/index.d.ts +44 -17
  11. package/dist/jsx-runtime/index.js +26 -7
  12. package/dist/query/public.d.ts +62 -7
  13. package/dist/query/public.js +12 -4
  14. package/dist/router/public.d.ts +186 -26
  15. package/dist/router/public.js +22 -7
  16. package/dist/shared/{chunk-f1ynwam4.js → chunk-0p5f7gmg.js} +155 -32
  17. package/dist/shared/{chunk-j8vzvne3.js → chunk-9e92w0wt.js} +4 -1
  18. package/dist/shared/{chunk-xd9d7q5p.js → chunk-cq7xg4xe.js} +59 -10
  19. package/dist/shared/chunk-g4rch80a.js +33 -0
  20. package/dist/shared/{chunk-pgymxpn1.js → chunk-hrd0mft1.js} +136 -34
  21. package/dist/shared/chunk-nmjyj8p9.js +290 -0
  22. package/dist/shared/chunk-pp3a6xbn.js +483 -0
  23. package/dist/shared/chunk-prj7nm08.js +67 -0
  24. package/dist/shared/chunk-q6cpe5k7.js +230 -0
  25. package/dist/shared/chunk-ryb49346.js +374 -0
  26. package/dist/shared/chunk-v3yyf79g.js +48 -0
  27. package/dist/shared/chunk-vx0kzack.js +103 -0
  28. package/dist/shared/chunk-wv6kkj1w.js +464 -0
  29. package/dist/test/index.d.ts +67 -6
  30. package/dist/test/index.js +4 -3
  31. package/package.json +13 -8
  32. package/dist/shared/chunk-bp3v6s9j.js +0 -62
  33. package/dist/shared/chunk-d8h2eh8d.js +0 -141
  34. package/dist/shared/chunk-tsdpgmks.js +0 -98
  35. package/dist/shared/chunk-zbbvx05f.js +0 -202
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- /** A single child value: DOM node, string, number, null, undefined, or nested array. */
2
- type ChildValue = Node | string | number | null | undefined | ChildValue[];
1
+ /** A single child value: DOM node, string, number, null, undefined, thunk, or nested array. */
2
+ type ChildValue = Node | string | number | null | undefined | ChildValue[] | (() => ChildValue);
3
3
  /** A function that returns children (slot accessor). */
4
4
  type ChildrenAccessor = () => ChildValue;
5
5
  /**
@@ -7,18 +7,88 @@ type ChildrenAccessor = () => ChildValue;
7
7
  * Strings and numbers are converted to Text nodes.
8
8
  * Null and undefined are filtered out.
9
9
  * Arrays are flattened recursively.
10
+ * Thunks (functions) are called and their results re-resolved.
10
11
  */
11
- declare function resolveChildren(value: ChildValue): Node[];
12
+ declare function resolveChildren(value: ChildValue, _depth?: number): Node[];
12
13
  /**
13
14
  * Create a children resolver from a children accessor.
14
15
  * Returns a function that, when called, resolves the children
15
16
  * to a flat array of DOM nodes.
16
17
  */
17
18
  declare function children(accessor: ChildrenAccessor): () => Node[];
19
+ /**
20
+ * A reactive signal that holds a value and notifies subscribers on change.
21
+ */
22
+ interface Signal<T> {
23
+ /** Get the current value and subscribe to changes (when inside a tracking context). */
24
+ get value(): T;
25
+ /** Set the current value and notify subscribers if changed. */
26
+ set value(newValue: T);
27
+ /** Read the current value without subscribing (no tracking). */
28
+ peek(): T;
29
+ /** Manually notify all subscribers (useful after mutating the value in place). */
30
+ notify(): void;
31
+ }
32
+ /**
33
+ * A read-only reactive value derived from other signals.
34
+ */
35
+ interface ReadonlySignal<T> {
36
+ /** Get the current value and subscribe to changes. */
37
+ readonly value: T;
38
+ /** Read the current value without subscribing. */
39
+ peek(): T;
40
+ }
41
+ /**
42
+ * Unwraps a ReadonlySignal to its value type.
43
+ * Used by signal APIs (like query()) to expose plain values in TypeScript
44
+ * while the compiler auto-unwraps them at runtime.
45
+ *
46
+ * @example
47
+ * type UnwrappedData = Unwrapped<ReadonlySignal<Task | undefined>>; // → Task | undefined
48
+ */
49
+ type Unwrapped<T> = T extends ReadonlySignal<infer U> ? U : T;
50
+ /**
51
+ * Unwraps all signal properties of an object type.
52
+ * Properties that are signals become their inner value type.
53
+ * Non-signal properties and primitive types pass through unchanged.
54
+ *
55
+ * Used by `useContext` to present context values without the Signal wrapper.
56
+ *
57
+ * @example
58
+ * type Settings = { theme: Signal<string>; setTheme: (t: string) => void };
59
+ * type Unwrapped = UnwrapSignals<Settings>; // { theme: string; setTheme: (t: string) => void }
60
+ */
61
+ type UnwrapSignals<T> = T extends object ? { [K in keyof T] : Unwrapped<T[K]> } : T;
62
+ /**
63
+ * A computed signal — lazily evaluated, cached, and automatically re-computed
64
+ * when dependencies change.
65
+ */
66
+ interface Computed<T> extends ReadonlySignal<T> {
67
+ /** Get the current value, re-computing if dirty. Subscribes in tracking context. */
68
+ readonly value: T;
69
+ /** Read the current value without subscribing. */
70
+ peek(): T;
71
+ }
72
+ /** Dispose function returned by effect(). */
73
+ type DisposeFn = () => void;
74
+ /**
75
+ * Props for the JSX pattern of Context.Provider.
76
+ *
77
+ * `children` accepts both raw values (what TypeScript sees in JSX) and
78
+ * thunks (what the compiler produces). At compile time the compiler wraps
79
+ * JSX children in `() => ...`, but TypeScript checks the pre-compilation
80
+ * source where children are plain elements.
81
+ */
82
+ interface ProviderJsxProps<T> {
83
+ value: T;
84
+ children: (() => unknown) | unknown;
85
+ }
18
86
  /** A context object created by `createContext`. */
19
87
  interface Context<T> {
20
- /** Provide a value to all `useContext` calls within the scope. */
21
- Provider: (value: T, fn: () => void) => void;
88
+ /** Provide a value via callback pattern. */
89
+ Provider(value: T, fn: () => void): void;
90
+ /** Provide a value via JSX pattern (single-arg object with children thunk). */
91
+ Provider(props: ProviderJsxProps<T>): HTMLElement;
22
92
  /** @internal — current value stack */
23
93
  _stack: T[];
24
94
  /** @internal — default value */
@@ -27,15 +97,20 @@ interface Context<T> {
27
97
  /**
28
98
  * Create a context with an optional default value.
29
99
  * Returns an object with a `Provider` function.
100
+ *
101
+ * The optional `__stableId` parameter is injected by the compiler for HMR
102
+ * support. When provided, the context object is cached in a global registry
103
+ * so that bundle re-evaluation returns the same object — preserving identity
104
+ * for ContextScope Map lookups. Users never pass this parameter directly.
30
105
  */
31
- declare function createContext<T>(defaultValue?: T): Context<T>;
106
+ declare function createContext<T>(defaultValue?: T, __stableId?: string): Context<T>;
32
107
  /**
33
108
  * Retrieve the current value from the nearest Provider.
34
109
  * Checks the synchronous call stack first, then falls back to
35
110
  * the captured context scope (for async callbacks like watch/effect).
36
111
  * Returns the default value if no Provider is active.
37
112
  */
38
- declare function useContext<T>(ctx: Context<T>): T | undefined;
113
+ declare function useContext<T>(ctx: Context<T>): UnwrapSignals<T> | undefined;
39
114
  /** Props for the ErrorBoundary component. */
40
115
  interface ErrorBoundaryProps {
41
116
  /** Function that returns the children to render. */
@@ -57,16 +132,28 @@ interface ErrorBoundaryProps {
57
132
  declare function ErrorBoundary(props: ErrorBoundaryProps): Node;
58
133
  /**
59
134
  * Runs callback once on mount. Never re-executes.
60
- * Supports `onCleanup` inside for teardown on unmount.
135
+ * Return a function to register cleanup that runs on unmount.
136
+ *
137
+ * ```tsx
138
+ * onMount(() => {
139
+ * const id = setInterval(() => seconds++, 1000);
140
+ * return () => clearInterval(id);
141
+ * });
142
+ * ```
61
143
  */
62
- declare function onMount(callback: () => void): void;
144
+ declare function onMount2(callback: () => (() => void) | void): void;
145
+ interface PresenceProps {
146
+ when: boolean;
147
+ children: () => HTMLElement;
148
+ }
63
149
  /**
64
- * Watches a dependency accessor and runs callback whenever it changes.
65
- * Always takes TWO arguments: a dependency accessor and a callback.
66
- * Runs callback immediately with current value.
67
- * Before each re-run, any `onCleanup` from previous run executes first.
150
+ * Presence component for mount/unmount animations.
151
+ * Defers unmounting until CSS exit animations complete.
152
+ *
153
+ * Props are accessed as getters (not destructured) so the compiler-generated
154
+ * reactive getters are tracked by domEffect.
68
155
  */
69
- declare function watch<T>(dep: () => T, callback: (value: T) => void): void;
156
+ declare function Presence(props: PresenceProps): Node;
70
157
  /** A ref container for DOM element access. */
71
158
  interface Ref<T> {
72
159
  current: T | undefined;
@@ -96,17 +183,51 @@ interface SuspenseProps {
96
183
  * globally via queueMicrotask to avoid silent swallowing.
97
184
  */
98
185
  declare function Suspense(props: SuspenseProps): Node;
186
+ declare const ANIMATION_DURATION: string;
187
+ declare const ANIMATION_EASING: string;
188
+ declare const fadeIn: string;
189
+ declare const fadeOut: string;
190
+ declare const zoomIn: string;
191
+ declare const zoomOut: string;
192
+ declare const slideInFromTop: string;
193
+ declare const slideInFromBottom: string;
194
+ declare const slideOutToTop: string;
195
+ declare const slideOutToBottom: string;
196
+ declare const slideInFromLeft: string;
197
+ declare const slideInFromRight: string;
198
+ declare const slideOutToLeft: string;
199
+ declare const slideOutToRight: string;
200
+ declare const accordionDown: string;
201
+ declare const accordionUp: string;
202
+ /** A raw CSS declaration: { property, value } for styles that can't be expressed as shorthands. */
203
+ interface RawDeclaration {
204
+ property: string;
205
+ value: string;
206
+ }
207
+ /** A value within a nested selector array: shorthand string or raw declaration. */
208
+ type StyleValue = string | RawDeclaration;
99
209
  /** A style entry in the array: either a shorthand string or an object for nested selectors. */
100
- type StyleEntry = string | Record<string, string[]>;
210
+ type StyleEntry = string | Record<string, StyleValue[]>;
101
211
  /** Input to css(): a record of named style blocks. */
102
212
  type CSSInput = Record<string, StyleEntry[]>;
103
- /** Output of css(): a record of block names to class names, plus extracted CSS. */
104
- interface CSSOutput {
105
- /** Map of block name → generated class name. */
106
- classNames: Record<string, string>;
107
- /** The extracted CSS string. */
108
- css: string;
109
- }
213
+ /** Output of css(): block names as top-level properties, plus non-enumerable `css`. */
214
+ type CSSOutput<T extends CSSInput = CSSInput> = { readonly [K in keyof T & string] : string } & {
215
+ readonly css: string;
216
+ };
217
+ /**
218
+ * Inject CSS text into the document head via a <style> tag.
219
+ * Only runs in browser environments. Deduplicates by CSS content.
220
+ *
221
+ * In SSR, document.head is freshly created per request by installDomShim().
222
+ * The module-level dedup Set would incorrectly block injection on request 2+
223
+ * since the Set persists across requests while document.head is replaced.
224
+ * We bypass dedup when __SSR_URL__ is set (SSR context).
225
+ */
226
+ declare function injectCSS(cssText: string): void;
227
+ /** Reset injected styles tracking. Used in tests. */
228
+ declare function resetInjectedStyles(): void;
229
+ /** Get all CSS strings that have been injected. Used by SSR to collect styles. */
230
+ declare function getInjectedCSS(): string[];
110
231
  /**
111
232
  * Process a css() call and produce class names + extracted CSS.
112
233
  *
@@ -118,9 +239,9 @@ interface CSSOutput {
118
239
  *
119
240
  * @param input - Named style blocks.
120
241
  * @param filePath - Source file path for deterministic hashing.
121
- * @returns Class names map and extracted CSS.
242
+ * @returns Object with block names as keys (class name strings) and non-enumerable `css` property.
122
243
  */
123
- declare function css(input: CSSInput, filePath?: string): CSSOutput;
244
+ declare function css<T extends CSSInput>(input: T & { [K in keyof T & "css"]? : never }, filePath?: string): CSSOutput<T>;
124
245
  /** Input to globalCss(): selector → property-value map. */
125
246
  type GlobalCSSInput = Record<string, Record<string, string>>;
126
247
  /** Output of globalCss(): extracted CSS string. */
@@ -139,6 +260,54 @@ interface GlobalCSSOutput {
139
260
  */
140
261
  declare function globalCss(input: GlobalCSSInput): GlobalCSSOutput;
141
262
  /**
263
+ * Register a CSS @keyframes animation and return its name.
264
+ * The CSS is injected into the DOM (deduped by injectCSS).
265
+ */
266
+ declare function keyframes(name: string, frames: Record<string, Record<string, string>>): string;
267
+ /**
268
+ * Tailwind v4 oklch color palettes.
269
+ *
270
+ * All 22 palettes with shades 50-950 in oklch format.
271
+ * Sourced from: https://github.com/tailwindlabs/tailwindcss/blob/main/packages/tailwindcss/theme.css
272
+ */
273
+ interface ColorPalette {
274
+ 50: string;
275
+ 100: string;
276
+ 200: string;
277
+ 300: string;
278
+ 400: string;
279
+ 500: string;
280
+ 600: string;
281
+ 700: string;
282
+ 800: string;
283
+ 900: string;
284
+ 950: string;
285
+ }
286
+ declare const palettes: Readonly<{
287
+ slate: ColorPalette;
288
+ gray: ColorPalette;
289
+ zinc: ColorPalette;
290
+ neutral: ColorPalette;
291
+ stone: ColorPalette;
292
+ red: ColorPalette;
293
+ orange: ColorPalette;
294
+ amber: ColorPalette;
295
+ yellow: ColorPalette;
296
+ lime: ColorPalette;
297
+ green: ColorPalette;
298
+ emerald: ColorPalette;
299
+ teal: ColorPalette;
300
+ cyan: ColorPalette;
301
+ sky: ColorPalette;
302
+ blue: ColorPalette;
303
+ indigo: ColorPalette;
304
+ violet: ColorPalette;
305
+ purple: ColorPalette;
306
+ fuchsia: ColorPalette;
307
+ pink: ColorPalette;
308
+ rose: ColorPalette;
309
+ }>;
310
+ /**
142
311
  * Convert an array of shorthand strings into a CSS properties object
143
312
  * suitable for inline styles.
144
313
  *
@@ -192,34 +361,27 @@ declare function defineTheme(input: ThemeInput): Theme;
192
361
  * @returns Compiled CSS and token list.
193
362
  */
194
363
  declare function compileTheme(theme: Theme): CompiledTheme;
195
- /**
196
- * ThemeProvider — Sets `data-theme` attribute for contextual token switching.
197
- *
198
- * Creates a wrapper div element with the appropriate `data-theme` attribute
199
- * so that contextual CSS custom properties (generated by compileTheme) resolve
200
- * to the correct variant values.
201
- *
202
- * Usage:
203
- * ```ts
204
- * ThemeProvider({ theme: 'dark', children: [myApp] });
205
- * ```
206
- */
207
364
  /** A child node: either a DOM Node or a string (text content). */
208
365
  type ThemeChild = Node | string;
209
366
  /** Props for ThemeProvider. */
210
367
  interface ThemeProviderProps {
211
368
  /** The theme variant name (e.g., 'light', 'dark'). Defaults to 'light'. */
212
369
  theme?: string;
213
- /** Child elements to render inside the provider. */
214
- children: ThemeChild[];
370
+ /**
371
+ * Child elements to render inside the provider.
372
+ *
373
+ * Accepts a single child (what TypeScript sees in JSX), an array, or a
374
+ * thunk (what the compiler produces after wrapping JSX children).
375
+ */
376
+ children: ThemeChild | ThemeChild[] | (() => ThemeChild | ThemeChild[]);
215
377
  }
216
378
  /**
217
379
  * Create a wrapper div with `data-theme` attribute for theme switching.
218
380
  *
219
- * @param props - Theme and children.
220
- * @returns A div element with `data-theme` set and children appended.
381
+ * Uses __element() directly (instead of jsx()) so the hydration cursor
382
+ * walker can claim the existing SSR node during mount().
221
383
  */
222
- declare function ThemeProvider(props: ThemeProviderProps): HTMLDivElement;
384
+ declare function ThemeProvider({ theme, children }: ThemeProviderProps): HTMLElement;
223
385
  /** A record of variant names to their possible values (each value maps to style entries). */
224
386
  type VariantDefinitions = Record<string, Record<string, StyleEntry[]>>;
225
387
  /** Extract the variant props type from a variant definitions object. */
@@ -253,45 +415,116 @@ interface VariantFunction<V extends VariantDefinitions> {
253
415
  */
254
416
  declare function variants<V extends VariantDefinitions>(config: VariantsConfig<V>): VariantFunction<V>;
255
417
  /**
256
- * A reactive signal that holds a value and notifies subscribers on change.
418
+ * Brand symbol for render nodes.
419
+ * SSR nodes add this to their prototype for fast identification.
420
+ * Browser DOM nodes use the `instanceof Node` fallback in `isRenderNode`.
257
421
  */
258
- interface Signal<T> {
259
- /** Get the current value and subscribe to changes (when inside a tracking context). */
260
- get value(): T;
261
- /** Set the current value and notify subscribers if changed. */
262
- set value(newValue: T);
263
- /** Read the current value without subscribing (no tracking). */
264
- peek(): T;
265
- /** Manually notify all subscribers (useful after mutating the value in place). */
266
- notify(): void;
422
+ declare const RENDER_NODE_BRAND: unique symbol;
423
+ interface RenderNode {}
424
+ interface RenderElement extends RenderNode {
425
+ setAttribute(name: string, value: string): void;
426
+ removeAttribute(name: string): void;
427
+ getAttribute(name: string): string | null;
428
+ style: {
429
+ display: string;
430
+ [key: string]: any;
431
+ };
432
+ classList: {
433
+ add(cls: string): void;
434
+ remove(cls: string): void;
435
+ };
436
+ addEventListener(event: string, handler: EventListener): void;
437
+ removeEventListener(event: string, handler: EventListener): void;
438
+ }
439
+ interface RenderText extends RenderNode {
440
+ data: string;
441
+ }
442
+ interface RenderAdapter {
443
+ createElement(tag: string): RenderElement;
444
+ createElementNS(ns: string, tag: string): RenderElement;
445
+ createTextNode(text: string): RenderText;
446
+ createComment(text: string): RenderNode;
447
+ createDocumentFragment(): RenderNode;
448
+ isNode(value: unknown): value is RenderNode;
267
449
  }
268
450
  /**
269
- * A read-only reactive value derived from other signals.
451
+ * Type guard: checks if a value is a RenderNode.
452
+ * Fast path: brand check for SSR nodes.
453
+ * Fallback: instanceof Node for browser DOM nodes.
270
454
  */
271
- interface ReadonlySignal<T> {
272
- /** Get the current value and subscribe to changes. */
273
- readonly value: T;
274
- /** Read the current value without subscribing. */
275
- peek(): T;
276
- }
455
+ declare function isRenderNode(value: unknown): value is RenderNode;
277
456
  /**
278
- * A computed signal lazily evaluated, cached, and automatically re-computed
279
- * when dependencies change.
457
+ * Get the current render adapter.
458
+ * Auto-detects DOMAdapter if document exists and no adapter has been set.
280
459
  */
281
- interface Computed<T> extends ReadonlySignal<T> {
282
- /** Get the current value, re-computing if dirty. Subscribes in tracking context. */
283
- readonly value: T;
284
- /** Read the current value without subscribing. */
285
- peek(): T;
460
+ declare function getAdapter(): RenderAdapter;
461
+ /**
462
+ * Set the current render adapter.
463
+ * Pass null to reset to auto-detect.
464
+ */
465
+ declare function setAdapter(adapter: RenderAdapter | null): void;
466
+ /**
467
+ * Create a DOM adapter that delegates to real browser DOM APIs.
468
+ * Zero overhead — no branding, no wrapping.
469
+ * Browser `Node` instances pass `isRenderNode()` via the `instanceof Node` fallback.
470
+ */
471
+ declare function createDOMAdapter(): RenderAdapter;
472
+ /**
473
+ * Create a DOM element with optional static properties.
474
+ *
475
+ * This is a compiler output target — the compiler generates calls
476
+ * to __element for each JSX element.
477
+ */
478
+ declare function __element<K extends keyof HTMLElementTagNameMap>(tag: K, props?: Record<string, string>): HTMLElementTagNameMap[K];
479
+ declare function __element(tag: string, props?: Record<string, string>): Element;
480
+ /**
481
+ * Append a child to a parent node.
482
+ * During hydration, this is a no-op — the child is already in the DOM.
483
+ * During CSR, delegates to appendChild.
484
+ *
485
+ * Compiler output target — replaces direct `parent.appendChild(child)`.
486
+ */
487
+ declare function __append(parent: Node, child: Node): void;
488
+ /**
489
+ * Create a static text node.
490
+ * During hydration, claims an existing text node from the SSR output.
491
+ * During CSR, creates a new text node.
492
+ *
493
+ * Compiler output target — replaces `document.createTextNode(str)`.
494
+ */
495
+ declare function __staticText(text: string): Text;
496
+ /**
497
+ * Push the hydration cursor into an element's children.
498
+ * Compiler output target — emitted around child construction.
499
+ */
500
+ declare function __enterChildren(el: Element): void;
501
+ /**
502
+ * Pop the hydration cursor back to the parent scope.
503
+ * Compiler output target — emitted after all children are appended.
504
+ */
505
+ declare function __exitChildren(): void;
506
+ interface FieldState<T = unknown> {
507
+ error: Signal<string | undefined>;
508
+ dirty: Signal<boolean>;
509
+ touched: Signal<boolean>;
510
+ value: Signal<T>;
511
+ setValue: (value: T) => void;
512
+ reset: () => void;
286
513
  }
287
- /** Dispose function returned by effect(). */
288
- type DisposeFn = () => void;
514
+ declare function createFieldState<T>(_name: string, initialValue?: T): FieldState<T>;
515
+ import { Result } from "@vertz/fetch";
289
516
  /**
290
517
  * Minimal schema interface compatible with @vertz/schema.
291
- * Any object with a `parse(data: unknown): T` method satisfies this.
518
+ * Any object with a `parse(data: unknown): Result` method satisfies this.
292
519
  */
293
520
  interface FormSchema<T> {
294
- parse(data: unknown): T;
521
+ parse(data: unknown): {
522
+ ok: true;
523
+ data: T;
524
+ } | {
525
+ ok: false;
526
+ error: unknown;
527
+ };
295
528
  }
296
529
  /** Result of a validation attempt. */
297
530
  type ValidationResult<T> = {
@@ -319,56 +552,87 @@ interface SdkMethod<
319
552
  TBody,
320
553
  TResult
321
554
  > {
322
- (body: TBody): Promise<TResult>;
555
+ (body: TBody): PromiseLike<Result<TResult, Error>>;
323
556
  url: string;
324
557
  method: string;
558
+ meta?: {
559
+ bodySchema?: FormSchema<TBody>;
560
+ };
325
561
  }
326
- /** Options for creating a form instance. */
327
- interface FormOptions<TBody> {
328
- /** Explicit schema for client-side validation before submission. */
329
- schema: FormSchema<TBody>;
562
+ /**
563
+ * An SDK method with embedded schema metadata.
564
+ * Generated by `@vertz/codegen` — carries `.meta.bodySchema` for auto-validation.
565
+ */
566
+ interface SdkMethodWithMeta<
567
+ TBody,
568
+ TResult
569
+ > extends SdkMethod<TBody, TResult> {
570
+ meta: {
571
+ bodySchema: FormSchema<TBody>;
572
+ };
330
573
  }
331
- /** Callbacks for form submission. Both are optional per spec. */
332
- interface SubmitCallbacks<TResult> {
333
- onSuccess?: (result: TResult) => void;
334
- onError?: (errors: Record<string, string>) => void;
574
+ /** Reserved property names that cannot be used as field names on FormInstance. */
575
+ type ReservedFormNames = "submitting" | "dirty" | "valid" | "action" | "method" | "onSubmit" | "reset" | "setFieldError" | "submit" | "__bindElement";
576
+ /** Mapped type providing FieldState for each field in TBody. */
577
+ type FieldAccessors<TBody> = { [K in keyof TBody] : FieldState<TBody[K]> };
578
+ /** Base properties available on every form instance. */
579
+ interface FormBaseProperties<TBody> {
580
+ action: string;
581
+ method: string;
582
+ onSubmit: (e: Event) => Promise<void>;
583
+ reset: () => void;
584
+ setFieldError: (field: keyof TBody & string, message: string) => void;
585
+ submit: (formData?: FormData) => Promise<void>;
586
+ submitting: Signal<boolean>;
587
+ dirty: ReadonlySignal<boolean>;
588
+ valid: ReadonlySignal<boolean>;
589
+ __bindElement: (el: HTMLFormElement) => void;
335
590
  }
336
- /** A form instance bound to an SDK method. */
337
- interface FormInstance<
591
+ /**
592
+ * A form instance bound to an SDK method.
593
+ * Combines base properties with per-field reactive state via Proxy.
594
+ * If TBody has any key that conflicts with ReservedFormNames, it produces a type error.
595
+ * TResult is used by form() overloads for return type inference.
596
+ */
597
+ type FormInstance<
598
+ TBody,
599
+ _TResult
600
+ > = keyof TBody & ReservedFormNames extends never ? FormBaseProperties<TBody> & FieldAccessors<TBody> : {
601
+ __error: `Field name conflicts with reserved form property: ${keyof TBody & ReservedFormNames & string}`;
602
+ };
603
+ /** Options for creating a form instance. */
604
+ interface FormOptions<
338
605
  TBody,
339
606
  TResult
340
607
  > {
341
- /** Returns `{ action, method }` for progressive enhancement in HTML forms. */
342
- attrs(): {
343
- action: string;
344
- method: string;
345
- };
346
- /** Reactive signal indicating whether a submission is in progress. */
347
- submitting: Signal<boolean>;
348
- /**
349
- * Returns an event handler that extracts FormData, validates, and submits.
350
- * Assignable to onSubmit: `onSubmit={userForm.handleSubmit({ onSuccess })}`.
351
- *
352
- * Can also be called directly with FormData for non-DOM usage:
353
- * `userForm.handleSubmit({ onSuccess })(formData)`.
354
- */
355
- handleSubmit(callbacks?: SubmitCallbacks<TResult>): (formDataOrEvent: FormData | Event) => Promise<void>;
356
- /** Returns the error message for a field reactively, or undefined if no error. Type-safe field names. */
357
- error(field: keyof TBody & string): string | undefined;
608
+ /** Explicit schema for client-side validation before submission. */
609
+ schema?: FormSchema<TBody>;
610
+ /** Initial values for form fields. */
611
+ initial?: Partial<TBody> | (() => Partial<TBody>);
612
+ /** Callback invoked after a successful submission. */
613
+ onSuccess?: (result: TResult) => void;
614
+ /** Callback invoked when validation or submission fails. */
615
+ onError?: (errors: Record<string, string>) => void;
616
+ /** When true, reset the form after a successful submission. */
617
+ resetOnSuccess?: boolean;
358
618
  }
359
619
  /**
360
- * Create a form instance bound to an SDK method with schema validation.
620
+ * Create a form instance bound to an SDK method.
361
621
  *
362
- * The form provides:
363
- * - `attrs()` for progressive enhancement (returns action/method from SDK metadata)
364
- * - `handleSubmit()` returns an event handler for FormData extraction, validation, and SDK submission
365
- * - `error()` for reactive field-level error access
366
- * - `submitting` signal for loading state
622
+ * The form provides direct properties for progressive enhancement (action, method, onSubmit),
623
+ * per-field reactive state via Proxy, and submission handling with validation.
624
+ *
625
+ * When the SDK method has `.meta.bodySchema` (generated by `@vertz/codegen`),
626
+ * the schema option is optional. When the SDK method lacks `.meta`, the schema option is required.
367
627
  */
368
628
  declare function form<
369
629
  TBody,
370
630
  TResult
371
- >(sdkMethod: SdkMethod<TBody, TResult>, options: FormOptions<TBody>): FormInstance<TBody, TResult>;
631
+ >(sdkMethod: SdkMethodWithMeta<TBody, TResult>, options?: FormOptions<TBody, TResult>): FormInstance<TBody, TResult>;
632
+ declare function form<
633
+ TBody,
634
+ TResult
635
+ >(sdkMethod: SdkMethod<TBody, TResult>, options: Required<Pick<FormOptions<TBody, TResult>, "schema">> & FormOptions<TBody, TResult>): FormInstance<TBody, TResult>;
372
636
  /** Options for formDataToObject conversion. */
373
637
  interface FormDataOptions {
374
638
  /** When true, coerces numeric strings to numbers and "true"/"false" to booleans. */
@@ -395,7 +659,8 @@ type ComponentRegistry = Record<string, ComponentLoader>;
395
659
  * Client entry point for atomic per-component hydration.
396
660
  *
397
661
  * Scans the DOM for elements with `data-v-id` markers placed by the SSR pass,
398
- * deserializes their props, and applies the appropriate hydration strategy.
662
+ * deserializes their props, and applies automatic hydration based on viewport
663
+ * proximity (IntersectionObserver with 200px rootMargin).
399
664
  *
400
665
  * Components without `data-v-id` markers are static and ship zero JS.
401
666
  *
@@ -405,30 +670,40 @@ type ComponentRegistry = Record<string, ComponentLoader>;
405
670
  */
406
671
  declare function hydrate(registry: ComponentRegistry): void;
407
672
  /**
408
- * Hydration strategies determine WHEN a component gets hydrated.
409
- *
410
- * - eager: immediately on page load
411
- * - lazy (default): when element becomes visible (IntersectionObserver, 200px rootMargin)
412
- * - interaction: on first user event (click, focus, pointerenter)
413
- * - idle: during browser idle time (requestIdleCallback, falls back to setTimeout)
414
- * - media(query): when a CSS media query matches
415
- * - visible: when element enters the viewport (IntersectionObserver, no rootMargin)
673
+ * Options for mounting an app to the DOM.
416
674
  */
417
- /** Eager -- hydrate immediately on page load. */
418
- declare function eagerStrategy(el: Element, hydrateFn: () => void): void;
419
- /** Lazy (default) -- hydrate when element becomes visible via IntersectionObserver with 200px rootMargin. */
420
- declare function lazyStrategy(el: Element, hydrateFn: () => void): void;
421
- /** Interaction -- hydrate on first user event (click, focus, pointerenter). */
422
- declare function interactionStrategy(el: Element, hydrateFn: () => void): void;
423
- /** Idle -- hydrate during browser idle time via requestIdleCallback. Falls back to setTimeout(fn, 0). */
424
- declare function idleStrategy(el: Element, hydrateFn: () => void): void;
675
+ interface MountOptions {
676
+ /** Theme definition for CSS vars */
677
+ theme?: Theme;
678
+ /** Global CSS strings to inject */
679
+ styles?: string[];
680
+ /** Callback after mount completes */
681
+ onMount?: (root: HTMLElement) => void;
682
+ }
425
683
  /**
426
- * Media -- hydrate when a CSS media query matches.
427
- * Returns a strategy function bound to the given query string.
684
+ * Handle returned from mount() for controlling the mounted app.
428
685
  */
429
- declare function mediaStrategy(query: string): (el: Element, hydrateFn: () => void) => void;
430
- /** Visible -- hydrate when element enters the viewport via IntersectionObserver (no rootMargin). */
431
- declare function visibleStrategy(el: Element, hydrateFn: () => void): void;
686
+ interface MountHandle {
687
+ /** Unmount the app and cleanup */
688
+ unmount: () => void;
689
+ /** Root HTMLElement */
690
+ root: HTMLElement;
691
+ }
692
+ /**
693
+ * Mount an app to a DOM element.
694
+ *
695
+ * Uses tolerant hydration automatically: if the root element has SSR content,
696
+ * it walks the existing DOM and attaches reactivity without re-creating nodes.
697
+ * If the root is empty (CSR), it renders from scratch.
698
+ *
699
+ * @param app - App function that returns an HTMLElement
700
+ * @param selector - CSS selector string or HTMLElement
701
+ * @param options - Mount options (theme, styles, onMount, etc.)
702
+ * @returns MountHandle with unmount function and root element
703
+ */
704
+ declare function mount<AppFn extends () => Element>(app: AppFn, selector: string | HTMLElement, options?: MountOptions): MountHandle;
705
+ import { QueryDescriptor as QueryDescriptor2 } from "@vertz/fetch";
706
+ import { isQueryDescriptor } from "@vertz/fetch";
432
707
  /**
433
708
  * Interface for cache stores used by query().
434
709
  * Consumers can provide custom implementations (e.g. LRU, persistent storage).
@@ -437,7 +712,9 @@ interface CacheStore<T = unknown> {
437
712
  get(key: string): T | undefined;
438
713
  set(key: string, value: T): void;
439
714
  delete(key: string): void;
715
+ clear?(): void;
440
716
  }
717
+ import { QueryDescriptor } from "@vertz/fetch";
441
718
  /** Options for query(). */
442
719
  interface QueryOptions<T> {
443
720
  /** Pre-populated data — skips the initial fetch when provided. */
@@ -450,15 +727,22 @@ interface QueryOptions<T> {
450
727
  key?: string;
451
728
  /** Custom cache store. Defaults to a shared in-memory Map. */
452
729
  cache?: CacheStore<T>;
730
+ /** Timeout in ms for SSR data loading. Default: 300. Set to 0 to disable. */
731
+ ssrTimeout?: number;
453
732
  }
454
733
  /** The reactive object returned by query(). */
455
- interface QueryResult<T> {
734
+ interface QueryResult<
735
+ T,
736
+ E = unknown
737
+ > {
456
738
  /** The fetched data, or undefined while loading. */
457
- readonly data: ReadonlySignal<T | undefined>;
458
- /** True while a fetch is in progress. */
459
- readonly loading: ReadonlySignal<boolean>;
739
+ readonly data: Unwrapped<ReadonlySignal<T | undefined>>;
740
+ /** True only on the initial load (no data yet). False during revalidation. */
741
+ readonly loading: Unwrapped<ReadonlySignal<boolean>>;
742
+ /** True when refetching while stale data is already available. */
743
+ readonly revalidating: Unwrapped<ReadonlySignal<boolean>>;
460
744
  /** The error from the latest failed fetch, or undefined. */
461
- readonly error: ReadonlySignal<unknown>;
745
+ readonly error: Unwrapped<ReadonlySignal<E | undefined>>;
462
746
  /** Manually trigger a refetch (clears cache for this key). */
463
747
  refetch: () => void;
464
748
  /** Alias for refetch — revalidate the cached data. */
@@ -472,11 +756,44 @@ interface QueryResult<T> {
472
756
  * The thunk is wrapped in an effect so that when reactive dependencies
473
757
  * used *before* the async call change, the query automatically re-fetches.
474
758
  *
475
- * @param thunk - An async function that returns the data.
759
+ * @param source - A QueryDescriptor or an async function that returns the data.
476
760
  * @param options - Optional configuration.
477
761
  * @returns A QueryResult with reactive signals for data, loading, and error.
478
762
  */
763
+ declare function query<
764
+ T,
765
+ E
766
+ >(descriptor: QueryDescriptor<T, E>, options?: Omit<QueryOptions<T>, "key">): QueryResult<T, E>;
479
767
  declare function query<T>(thunk: () => Promise<T>, options?: QueryOptions<T>): QueryResult<T>;
768
+ interface QueryMatchHandlers<
769
+ T,
770
+ E
771
+ > {
772
+ loading: () => Node | null;
773
+ error: (error: E) => Node | null;
774
+ data: (data: T) => Node | null;
775
+ }
776
+ /**
777
+ * Pattern-match on a QueryResult's exclusive state.
778
+ *
779
+ * Returns a stable `<span style="display:contents">` wrapper that internally
780
+ * manages branch switching (loading/error/data) via a reactive effect.
781
+ * The same wrapper is returned for repeated calls with the same queryResult
782
+ * (cached via WeakMap), enabling __child's stable-node optimization.
783
+ *
784
+ * Priority: loading → error → data.
785
+ *
786
+ * `loading` only fires on the initial load (no data yet).
787
+ * When revalidating with existing data, the `data` handler receives the
788
+ * current data. Access `query.revalidating` from the component scope for
789
+ * revalidation state.
790
+ */
791
+ declare function queryMatch<
792
+ T,
793
+ E
794
+ >(queryResult: QueryResult<T, E>, handlers: QueryMatchHandlers<T, E>): HTMLElement & {
795
+ dispose: DisposeFn;
796
+ };
480
797
  /**
481
798
  * Template literal type utility that extracts route parameter names from a path pattern.
482
799
  *
@@ -504,9 +821,34 @@ type ExtractParams<T extends string> = [ExtractParamsFromSegments<WithoutWildcar
504
821
  } : Record<string, never> : HasWildcard<T> extends true ? { [K in ExtractParamsFromSegments<WithoutWildcard<T>>] : string } & {
505
822
  "*": string;
506
823
  } : { [K in ExtractParamsFromSegments<WithoutWildcard<T>>] : string };
824
+ /**
825
+ * Convert a route pattern to the union of URL shapes it accepts.
826
+ * - Static: `'/'` → `'/'`
827
+ * - Param: `'/tasks/:id'` → `` `/tasks/${string}` ``
828
+ * - Wildcard: `'/files/*'` → `` `/files/${string}` ``
829
+ * - Multi: `'/users/:id/posts/:postId'` → `` `/users/${string}/posts/${string}` ``
830
+ * - Fallback: `string` → `string` (backward compat)
831
+ */
832
+ type PathWithParams<T extends string> = T extends `${infer Before}*` ? `${PathWithParams<Before>}${string}` : T extends `${infer Before}:${string}/${infer After}` ? `${Before}${string}/${PathWithParams<`${After}`>}` : T extends `${infer Before}:${string}` ? `${Before}${string}` : T;
833
+ /**
834
+ * Union of all valid URL shapes for a route map.
835
+ * Maps each route pattern key through `PathWithParams` to produce the accepted URL shapes.
836
+ *
837
+ * Example:
838
+ * ```
839
+ * RoutePaths<{ '/': ..., '/tasks/:id': ... }> = '/' | `/tasks/${string}`
840
+ * ```
841
+ */
842
+ type RoutePaths<TRouteMap extends Record<string, unknown>> = { [K in keyof TRouteMap & string] : PathWithParams<K> }[keyof TRouteMap & string];
507
843
  /** Simple schema interface for search param parsing. */
508
844
  interface SearchParamSchema<T> {
509
- parse(data: unknown): T;
845
+ parse(data: unknown): {
846
+ ok: true;
847
+ data: T;
848
+ } | {
849
+ ok: false;
850
+ error: unknown;
851
+ };
510
852
  }
511
853
  /** A route configuration for a single path. */
512
854
  interface RouteConfig<
@@ -534,6 +876,46 @@ interface RouteConfig<
534
876
  interface RouteDefinitionMap {
535
877
  [pattern: string]: RouteConfig;
536
878
  }
879
+ /**
880
+ * Loose route config used as the generic constraint for `defineRoutes`.
881
+ * Uses `Record<string, string>` for loader params so any concrete loader
882
+ * that accesses string params (e.g., `params.id`) satisfies the constraint.
883
+ */
884
+ interface RouteConfigLike {
885
+ component: () => Node | Promise<{
886
+ default: () => Node;
887
+ }>;
888
+ /**
889
+ * Method syntax (`loader?(ctx): R`) is intentional — it enables **bivariant**
890
+ * parameter checking under `strictFunctionTypes`. Property syntax
891
+ * (`loader?: (ctx) => R`) would be contravariant, causing `RouteConfig<string>`
892
+ * (whose loader has `params: Record<string, never>`) to fail assignability
893
+ * against this constraint's `params: Record<string, string>`.
894
+ */
895
+ loader?(ctx: {
896
+ params: Record<string, string>;
897
+ signal: AbortSignal;
898
+ }): unknown;
899
+ errorComponent?: (error: Error) => Node;
900
+ searchParams?: SearchParamSchema<unknown>;
901
+ children?: Record<string, RouteConfigLike>;
902
+ }
903
+ /**
904
+ * Phantom branded array that carries the route map type `T`.
905
+ * The `__routes` property never exists at runtime — it is a type-level
906
+ * marker used to thread the developer's literal route keys through
907
+ * `createRouter`, `useRouter`, etc.
908
+ */
909
+ type TypedRoutes<T extends Record<string, RouteConfigLike> = RouteDefinitionMap> = CompiledRoute[] & {
910
+ readonly __routes: T;
911
+ };
912
+ /**
913
+ * Extract the route map type from `TypedRoutes<T>`.
914
+ * If `T` is not a `TypedRoutes`, returns `T` as-is (passthrough).
915
+ *
916
+ * Usage: `useRouter<InferRouteMap<typeof routes>>()`
917
+ */
918
+ type InferRouteMap<T> = T extends TypedRoutes<infer R> ? R : T;
537
919
  /** Internal compiled route. */
538
920
  interface CompiledRoute {
539
921
  /** The original path pattern. */
@@ -579,17 +961,30 @@ type LoaderData<T> = T extends {
579
961
  * Define routes from a configuration map.
580
962
  * Returns an array of compiled routes preserving definition order.
581
963
  */
582
- declare function defineRoutes(map: RouteDefinitionMap): CompiledRoute[];
583
- /** Props for the Link component. */
584
- interface LinkProps {
964
+ declare function defineRoutes<const T extends Record<string, RouteConfigLike>>(map: T): TypedRoutes<T>;
965
+ /**
966
+ * Props for the Link component.
967
+ *
968
+ * Generic over the route map `T`. Defaults to `RouteDefinitionMap` (string
969
+ * index signature) for backward compatibility — unparameterized `LinkProps`
970
+ * accepts any string href.
971
+ */
972
+ interface LinkProps<T extends Record<string, RouteConfigLike> = RouteDefinitionMap> {
585
973
  /** The target URL path. */
586
- href: string;
587
- /** Text or content for the link. */
588
- children: string;
974
+ href: RoutePaths<T>;
975
+ /** Text or content for the link. Thunk may return string or Text node. */
976
+ children: string | (() => string | Node);
589
977
  /** Class applied when the link's href matches the current path. */
590
978
  activeClass?: string;
591
979
  /** Static class name for the anchor element. */
592
980
  className?: string;
981
+ /** Prefetch strategy. 'hover' triggers server pre-fetch on mouseenter/focus. */
982
+ prefetch?: "hover";
983
+ }
984
+ /** Options for createLink(). */
985
+ interface LinkFactoryOptions {
986
+ /** Callback fired when a link wants to prefetch its target URL. */
987
+ onPrefetch?: (url: string) => void;
593
988
  }
594
989
  /**
595
990
  * Create a Link component factory bound to the router's state.
@@ -598,14 +993,43 @@ interface LinkProps {
598
993
  * @param navigate - Navigation function from the router
599
994
  * @returns A Link component function
600
995
  */
601
- declare function createLink(currentPath: ReadonlySignal<string>, navigate: (url: string) => void): (props: LinkProps) => HTMLAnchorElement;
996
+ declare function createLink(currentPath: ReadonlySignal<string>, navigate: (url: string) => void, factoryOptions?: LinkFactoryOptions): (props: LinkProps) => HTMLAnchorElement;
602
997
  /** Options for router.navigate(). */
603
998
  interface NavigateOptions {
604
999
  /** Use history.replaceState instead of pushState. */
605
1000
  replace?: boolean;
606
1001
  }
607
- /** The router instance returned by createRouter. */
608
- interface Router {
1002
+ /** Handle returned by prefetchNavData for cancellation. */
1003
+ interface PrefetchHandle {
1004
+ abort: () => void;
1005
+ /** Resolves when SSE stream completes (data or done event). */
1006
+ done?: Promise<void>;
1007
+ }
1008
+ /** Options for createRouter(). */
1009
+ interface RouterOptions {
1010
+ /** Enable server-side navigation pre-fetch. When true, uses default timeout. */
1011
+ serverNav?: boolean | {
1012
+ timeout?: number;
1013
+ };
1014
+ /** @internal — injected for testing. Production uses the real module. */
1015
+ _prefetchNavData?: (url: string, options?: {
1016
+ timeout?: number;
1017
+ }) => PrefetchHandle;
1018
+ }
1019
+ /**
1020
+ * The router instance returned by createRouter.
1021
+ *
1022
+ * Generic over the route map `T`. Defaults to `RouteDefinitionMap` (string
1023
+ * index signature) for backward compatibility — unparameterized `Router`
1024
+ * accepts any string in `navigate()`.
1025
+ *
1026
+ * Method syntax on `navigate`, `revalidate`, and `dispose` enables bivariant
1027
+ * parameter checking under `strictFunctionTypes`. This means `Router<T>` is
1028
+ * assignable to `Router` (the unparameterized default), which is required for
1029
+ * storing typed routers in the `RouterContext` without contravariance errors.
1030
+ * At call sites, TypeScript still enforces the `RoutePaths<T>` constraint.
1031
+ */
1032
+ interface Router<T extends Record<string, RouteConfigLike> = RouteDefinitionMap> {
609
1033
  /** Current matched route (reactive signal). */
610
1034
  current: Signal<RouteMatch | null>;
611
1035
  /** Loader data from the current route's loaders (reactive signal). */
@@ -615,34 +1039,52 @@ interface Router {
615
1039
  /** Parsed search params from the current route (reactive signal). */
616
1040
  searchParams: Signal<Record<string, unknown>>;
617
1041
  /** Navigate to a new URL path. */
618
- navigate: (url: string, options?: NavigateOptions) => Promise<void>;
1042
+ navigate(url: RoutePaths<T>, options?: NavigateOptions): Promise<void>;
619
1043
  /** Re-run all loaders for the current route. */
620
- revalidate: () => Promise<void>;
1044
+ revalidate(): Promise<void>;
621
1045
  /** Remove popstate listener and clean up the router. */
622
- dispose: () => void;
1046
+ dispose(): void;
623
1047
  }
624
1048
  /**
1049
+ * Convenience alias for a typed router.
1050
+ * `TypedRouter<T>` is identical to `Router<T>` — it exists for readability
1051
+ * when the generic parameter makes the intent clearer.
1052
+ */
1053
+ type TypedRouter<T extends Record<string, RouteConfigLike> = RouteDefinitionMap> = Router<T>;
1054
+ /**
625
1055
  * Create a router instance.
626
1056
  *
627
1057
  * @param routes - Compiled route list from defineRoutes()
628
1058
  * @param initialUrl - The initial URL to match (optional; auto-detects from window.location or __SSR_URL__)
629
1059
  * @returns Router instance with reactive state and navigation methods
630
1060
  */
631
- declare function createRouter(routes: CompiledRoute[], initialUrl?: string): Router;
1061
+ declare function createRouter<T extends Record<string, RouteConfigLike> = RouteDefinitionMap>(routes: TypedRoutes<T>, initialUrl?: string, options?: RouterOptions): Router<T>;
632
1062
  /** Context value for the Outlet. */
633
- interface OutletContext {
634
- /** The child component factory to render, or undefined if no child. */
635
- childComponent: (() => Node) | undefined;
636
- /** The nesting depth (for debugging/tracking). */
637
- depth: number;
1063
+ interface OutletContextValue {
1064
+ /** Reactive child component factory (may return async module). */
1065
+ childComponent: Signal<(() => Node | Promise<{
1066
+ default: () => Node;
1067
+ }>) | undefined>;
1068
+ /** Router instance for restoring context in async resolution. */
1069
+ router: Router;
638
1070
  }
1071
+ /** Shared context used by RouterView and Outlet. */
1072
+ declare const OutletContext: Context<OutletContextValue>;
639
1073
  /**
640
- * Create an Outlet component bound to a specific outlet context.
1074
+ * Outlet component renders the nested child route.
641
1075
  *
642
- * @param outletCtx - The context that holds the child component
643
- * @returns An Outlet component function
1076
+ * Must be called inside a layout component rendered by RouterView.
1077
+ * Reads from OutletContext to determine which child to render.
644
1078
  */
645
- declare function createOutlet(outletCtx: Context<OutletContext>): () => Node;
1079
+ declare function Outlet(): Node;
1080
+ declare const RouterContext: Context<Router>;
1081
+ declare function useRouter<T extends Record<string, RouteConfigLike> = RouteDefinitionMap>(): UnwrapSignals<Router<T>>;
1082
+ declare function useParams<TPath extends string = string>(): ExtractParams<TPath>;
1083
+ interface RouterViewProps {
1084
+ router: Router;
1085
+ fallback?: () => Node;
1086
+ }
1087
+ declare function RouterView({ router, fallback }: RouterViewProps): HTMLElement;
646
1088
  /**
647
1089
  * Parse URLSearchParams into a typed object, optionally through a schema.
648
1090
  *
@@ -668,12 +1110,6 @@ declare class DisposalScopeError extends Error {
668
1110
  constructor();
669
1111
  }
670
1112
  /**
671
- * Register a cleanup function with the current disposal scope.
672
- * Throws `DisposalScopeError` if no scope is active — fail-fast
673
- * so developers know their cleanup callback was not registered.
674
- */
675
- declare function onCleanup(fn: DisposeFn): void;
676
- /**
677
1113
  * Group multiple signal writes into a single update flush.
678
1114
  * Nested batches are supported — only the outermost batch triggers the flush.
679
1115
  */
@@ -688,13 +1124,106 @@ declare function signal<T>(initial: T): Signal<T>;
688
1124
  */
689
1125
  declare function computed<T>(fn: () => T): Computed<T>;
690
1126
  /**
691
- * Create a reactive effect that re-runs whenever its dependencies change.
692
- * Returns a dispose function to stop the effect.
693
- */
694
- declare function effect(fn: () => void): DisposeFn;
695
- /**
696
1127
  * Execute a function without tracking any signal reads.
697
1128
  * Useful for reading signals without creating subscriptions.
698
1129
  */
699
1130
  declare function untrack<T>(fn: () => T): T;
700
- export { watch, visibleStrategy, variants, validate, useSearchParams, useContext, untrack, signal, s, resolveChildren, ref, query, parseSearchParams, onMount, onCleanup, mediaStrategy, lazyStrategy, interactionStrategy, idleStrategy, hydrate, globalCss, formDataToObject, form, effect, eagerStrategy, defineTheme, defineRoutes, css, createRouter, createOutlet, createLink, createContext, computed, compileTheme, children, batch, VariantsConfig, VariantProps, VariantFunction, ValidationResult, ThemeProviderProps, ThemeProvider, ThemeInput, Theme, SuspenseProps, Suspense, SubmitCallbacks, StyleEntry, Signal, SearchParamSchema, SdkMethod, Router, RouteMatch, RouteDefinitionMap, RouteConfig, Ref, ReadonlySignal, QueryResult, QueryOptions, OutletContext, NavigateOptions, MatchedRoute, LoaderData, LinkProps, GlobalCSSOutput, GlobalCSSInput, FormSchema, FormOptions, FormInstance, FormDataOptions, ExtractParams, ErrorBoundaryProps, ErrorBoundary, DisposeFn, DisposalScopeError, Context, Computed, ComponentRegistry, ComponentLoader, ComponentFunction, CompiledTheme, CompiledRoute, ChildrenAccessor, ChildValue, CacheStore, CSSOutput, CSSInput };
1131
+ /**
1132
+ * Serialized format for EntityStore - used for SSR transfer and hydration.
1133
+ */
1134
+ interface SerializedStore {
1135
+ /** Entity data keyed by type → id → entity */
1136
+ entities: Record<string, Record<string, unknown>>;
1137
+ /** Query result indices (optional) */
1138
+ queries?: Record<string, {
1139
+ ids: string[];
1140
+ nextCursor?: string | null;
1141
+ }>;
1142
+ }
1143
+ /**
1144
+ * Options for EntityStore constructor.
1145
+ */
1146
+ interface EntityStoreOptions {
1147
+ /** Initial data to hydrate from (SSR). */
1148
+ initialData?: SerializedStore;
1149
+ }
1150
+ /**
1151
+ * EntityStore - Normalized, signal-backed entity cache for @vertz/ui.
1152
+ *
1153
+ * Stores entities by type and ID, with signal-per-entity reactivity.
1154
+ * Supports field-level merge, SSR hydration, and query result indices.
1155
+ */
1156
+ declare class EntityStore {
1157
+ private _entities;
1158
+ private _typeListeners;
1159
+ private _queryIndices;
1160
+ constructor(options?: EntityStoreOptions);
1161
+ /**
1162
+ * Read a single entity. Returns a signal that updates on merge.
1163
+ * Returns the same signal instance on repeated calls (identity stability).
1164
+ */
1165
+ get<T>(type: string, id: string): ReadonlySignal<T | undefined>;
1166
+ /**
1167
+ * Read multiple entities by IDs. Returns a computed signal of the array.
1168
+ * Returns a NEW computed signal each call (not cached).
1169
+ */
1170
+ getMany<T>(type: string, ids: string[]): ReadonlySignal<(T | undefined)[]>;
1171
+ /**
1172
+ * Merge one or more entities into the store.
1173
+ * Field-level merge with shallow diff - only updates signals if data changed.
1174
+ * Uses batch() to coalesce multiple updates into single reactive flush.
1175
+ * Uses untrack() to prevent circular re-triggering when called from effects.
1176
+ */
1177
+ merge<T extends {
1178
+ id: string;
1179
+ }>(type: string, data: T | T[]): void;
1180
+ /**
1181
+ * Remove an entity from the store.
1182
+ * Triggers type change listeners and removes from query indices.
1183
+ */
1184
+ remove(type: string, id: string): void;
1185
+ /**
1186
+ * Subscribe to type-level changes (create/delete, not field updates).
1187
+ * Returns an unsubscribe function.
1188
+ */
1189
+ onTypeChange(type: string, callback: () => void): () => void;
1190
+ /**
1191
+ * Check if an entity exists in the store.
1192
+ */
1193
+ has(type: string, id: string): boolean;
1194
+ /**
1195
+ * Get count of entities for a type.
1196
+ */
1197
+ size(type: string): number;
1198
+ /**
1199
+ * Serialize the store for SSR transfer.
1200
+ */
1201
+ dehydrate(): SerializedStore;
1202
+ /**
1203
+ * Hydrate from serialized data. Merges into existing store (doesn't replace).
1204
+ */
1205
+ hydrate(data: SerializedStore): void;
1206
+ private _getOrCreateTypeMap;
1207
+ private _getOrCreateListeners;
1208
+ private _notifyTypeChange;
1209
+ }
1210
+ /**
1211
+ * Create a pre-populated EntityStore for testing.
1212
+ *
1213
+ * @param data - Entity data keyed by type → id → entity
1214
+ * @returns A new EntityStore with the data already merged
1215
+ *
1216
+ * @example
1217
+ * ```ts
1218
+ * const store = createTestStore({
1219
+ * User: {
1220
+ * '1': { id: '1', name: 'Alice' },
1221
+ * '2': { id: '2', name: 'Bob' }
1222
+ * }
1223
+ * });
1224
+ *
1225
+ * expect(store.get('User', '1').value).toEqual({ id: '1', name: 'Alice' });
1226
+ * ```
1227
+ */
1228
+ declare function createTestStore(data: Record<string, Record<string, unknown>>): EntityStore;
1229
+ export { zoomOut, zoomIn, variants, validate, useSearchParams, useRouter, useParams, useContext, untrack, slideOutToTop, slideOutToRight, slideOutToLeft, slideOutToBottom, slideInFromTop, slideInFromRight, slideInFromLeft, slideInFromBottom, signal, setAdapter, s, resolveChildren, resetInjectedStyles, ref, queryMatch, query, parseSearchParams, palettes, onMount2 as onMount, mount, keyframes, isRenderNode, isQueryDescriptor, injectCSS, hydrate, globalCss, getInjectedCSS, getAdapter, formDataToObject, form, fadeOut, fadeIn, defineTheme, defineRoutes, css, createTestStore, createRouter, createLink, createFieldState, createDOMAdapter, createContext, computed, compileTheme, children, batch, accordionUp, accordionDown, __staticText, __exitChildren, __enterChildren, __element, __append, VariantsConfig, VariantProps, VariantFunction, ValidationResult, UnwrapSignals, TypedRoutes, TypedRouter, ThemeProviderProps, ThemeProvider, ThemeInput, Theme, SuspenseProps, Suspense, StyleValue, StyleEntry, Signal, SerializedStore, SearchParamSchema, SdkMethodWithMeta, SdkMethod, RouterViewProps, RouterView, RouterOptions, RouterContext, Router, RoutePaths, RouteMatch, RouteDefinitionMap, RouteConfig, RenderText, RenderNode, RenderElement, RenderAdapter, Ref, ReadonlySignal, RawDeclaration, RENDER_NODE_BRAND, QueryResult, QueryOptions, QueryMatchHandlers, QueryDescriptor2 as QueryDescriptor, PresenceProps, Presence, PathWithParams, OutletContextValue, OutletContext, Outlet, NavigateOptions, MountOptions, MountHandle, MatchedRoute, LoaderData, LinkProps, LinkFactoryOptions, InferRouteMap, GlobalCSSOutput, GlobalCSSInput, FormSchema, FormOptions, FormInstance, FormDataOptions, FieldState, ExtractParams, ErrorBoundaryProps, ErrorBoundary, EntityStoreOptions, EntityStore, DisposeFn, DisposalScopeError, Context, Computed, ComponentRegistry, ComponentLoader, ComponentFunction, CompiledTheme, CompiledRoute, ColorPalette, ChildrenAccessor, ChildValue, CacheStore, CSSOutput, CSSInput, ANIMATION_EASING, ANIMATION_DURATION };