@vertz/ui 0.2.0 → 0.2.2

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 (36) 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 +754 -167
  7. package/dist/index.js +606 -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 +73 -7
  13. package/dist/query/public.js +12 -4
  14. package/dist/router/public.d.ts +199 -26
  15. package/dist/router/public.js +22 -7
  16. package/dist/shared/chunk-0xcmwgdb.js +288 -0
  17. package/dist/shared/{chunk-j8vzvne3.js → chunk-9e92w0wt.js} +4 -1
  18. package/dist/shared/chunk-g4rch80a.js +33 -0
  19. package/dist/shared/chunk-hh0dhmb4.js +528 -0
  20. package/dist/shared/{chunk-pgymxpn1.js → chunk-hrd0mft1.js} +136 -34
  21. package/dist/shared/chunk-jrtrk5z4.js +125 -0
  22. package/dist/shared/chunk-ka5ked7n.js +188 -0
  23. package/dist/shared/chunk-n91rwj2r.js +483 -0
  24. package/dist/shared/chunk-prj7nm08.js +67 -0
  25. package/dist/shared/chunk-q6cpe5k7.js +230 -0
  26. package/dist/shared/{chunk-f1ynwam4.js → chunk-qacth5ah.js} +162 -36
  27. package/dist/shared/chunk-ryb49346.js +374 -0
  28. package/dist/shared/chunk-v3yyf79g.js +48 -0
  29. package/dist/test/index.d.ts +67 -6
  30. package/dist/test/index.js +4 -3
  31. package/package.json +14 -9
  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-xd9d7q5p.js +0 -115
  36. 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,94 @@ 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
+ * A snapshot of context values at a point in time.
76
+ * Each Provider creates a new scope that inherits from the parent.
77
+ * Effects capture this scope so that useContext works in async callbacks.
78
+ */
79
+ type ContextScope = Map<Context<unknown>, unknown>;
80
+ /**
81
+ * Props for the JSX pattern of Context.Provider.
82
+ *
83
+ * `children` accepts both raw values (what TypeScript sees in JSX) and
84
+ * thunks (what the compiler produces). At compile time the compiler wraps
85
+ * JSX children in `() => ...`, but TypeScript checks the pre-compilation
86
+ * source where children are plain elements.
87
+ */
88
+ interface ProviderJsxProps<T> {
89
+ value: T;
90
+ children: (() => unknown) | unknown;
91
+ }
18
92
  /** A context object created by `createContext`. */
19
93
  interface Context<T> {
20
- /** Provide a value to all `useContext` calls within the scope. */
21
- Provider: (value: T, fn: () => void) => void;
94
+ /** Provide a value via callback pattern. */
95
+ Provider(value: T, fn: () => void): void;
96
+ /** Provide a value via JSX pattern (single-arg object with children thunk). */
97
+ Provider(props: ProviderJsxProps<T>): HTMLElement;
22
98
  /** @internal — current value stack */
23
99
  _stack: T[];
24
100
  /** @internal — default value */
@@ -27,15 +103,20 @@ interface Context<T> {
27
103
  /**
28
104
  * Create a context with an optional default value.
29
105
  * Returns an object with a `Provider` function.
106
+ *
107
+ * The optional `__stableId` parameter is injected by the compiler for HMR
108
+ * support. When provided, the context object is cached in a global registry
109
+ * so that bundle re-evaluation returns the same object — preserving identity
110
+ * for ContextScope Map lookups. Users never pass this parameter directly.
30
111
  */
31
- declare function createContext<T>(defaultValue?: T): Context<T>;
112
+ declare function createContext<T>(defaultValue?: T, __stableId?: string): Context<T>;
32
113
  /**
33
114
  * Retrieve the current value from the nearest Provider.
34
115
  * Checks the synchronous call stack first, then falls back to
35
116
  * the captured context scope (for async callbacks like watch/effect).
36
117
  * Returns the default value if no Provider is active.
37
118
  */
38
- declare function useContext<T>(ctx: Context<T>): T | undefined;
119
+ declare function useContext<T>(ctx: Context<T>): UnwrapSignals<T> | undefined;
39
120
  /** Props for the ErrorBoundary component. */
40
121
  interface ErrorBoundaryProps {
41
122
  /** Function that returns the children to render. */
@@ -57,16 +138,28 @@ interface ErrorBoundaryProps {
57
138
  declare function ErrorBoundary(props: ErrorBoundaryProps): Node;
58
139
  /**
59
140
  * Runs callback once on mount. Never re-executes.
60
- * Supports `onCleanup` inside for teardown on unmount.
141
+ * Return a function to register cleanup that runs on unmount.
142
+ *
143
+ * ```tsx
144
+ * onMount(() => {
145
+ * const id = setInterval(() => seconds++, 1000);
146
+ * return () => clearInterval(id);
147
+ * });
148
+ * ```
61
149
  */
62
- declare function onMount(callback: () => void): void;
150
+ declare function onMount2(callback: () => (() => void) | void): void;
151
+ interface PresenceProps {
152
+ when: boolean;
153
+ children: () => HTMLElement;
154
+ }
63
155
  /**
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.
156
+ * Presence component for mount/unmount animations.
157
+ * Defers unmounting until CSS exit animations complete.
158
+ *
159
+ * Props are accessed as getters (not destructured) so the compiler-generated
160
+ * reactive getters are tracked by domEffect.
68
161
  */
69
- declare function watch<T>(dep: () => T, callback: (value: T) => void): void;
162
+ declare function Presence(props: PresenceProps): Node;
70
163
  /** A ref container for DOM element access. */
71
164
  interface Ref<T> {
72
165
  current: T | undefined;
@@ -96,17 +189,51 @@ interface SuspenseProps {
96
189
  * globally via queueMicrotask to avoid silent swallowing.
97
190
  */
98
191
  declare function Suspense(props: SuspenseProps): Node;
192
+ declare const ANIMATION_DURATION: string;
193
+ declare const ANIMATION_EASING: string;
194
+ declare const fadeIn: string;
195
+ declare const fadeOut: string;
196
+ declare const zoomIn: string;
197
+ declare const zoomOut: string;
198
+ declare const slideInFromTop: string;
199
+ declare const slideInFromBottom: string;
200
+ declare const slideOutToTop: string;
201
+ declare const slideOutToBottom: string;
202
+ declare const slideInFromLeft: string;
203
+ declare const slideInFromRight: string;
204
+ declare const slideOutToLeft: string;
205
+ declare const slideOutToRight: string;
206
+ declare const accordionDown: string;
207
+ declare const accordionUp: string;
208
+ /** A raw CSS declaration: { property, value } for styles that can't be expressed as shorthands. */
209
+ interface RawDeclaration {
210
+ property: string;
211
+ value: string;
212
+ }
213
+ /** A value within a nested selector array: shorthand string or raw declaration. */
214
+ type StyleValue = string | RawDeclaration;
99
215
  /** A style entry in the array: either a shorthand string or an object for nested selectors. */
100
- type StyleEntry = string | Record<string, string[]>;
216
+ type StyleEntry = string | Record<string, StyleValue[]>;
101
217
  /** Input to css(): a record of named style blocks. */
102
218
  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
- }
219
+ /** Output of css(): block names as top-level properties, plus non-enumerable `css`. */
220
+ type CSSOutput<T extends CSSInput = CSSInput> = { readonly [K in keyof T & string] : string } & {
221
+ readonly css: string;
222
+ };
223
+ /**
224
+ * Inject CSS text into the document head via a <style> tag.
225
+ * Only runs in browser environments. Deduplicates by CSS content.
226
+ *
227
+ * In SSR, document.head is freshly created per request by installDomShim().
228
+ * The module-level dedup Set would incorrectly block injection on request 2+
229
+ * since the Set persists across requests while document.head is replaced.
230
+ * We bypass dedup when __SSR_URL__ is set (SSR context).
231
+ */
232
+ declare function injectCSS(cssText: string): void;
233
+ /** Reset injected styles tracking. Used in tests. */
234
+ declare function resetInjectedStyles(): void;
235
+ /** Get all CSS strings that have been injected. Used by SSR to collect styles. */
236
+ declare function getInjectedCSS(): string[];
110
237
  /**
111
238
  * Process a css() call and produce class names + extracted CSS.
112
239
  *
@@ -118,9 +245,9 @@ interface CSSOutput {
118
245
  *
119
246
  * @param input - Named style blocks.
120
247
  * @param filePath - Source file path for deterministic hashing.
121
- * @returns Class names map and extracted CSS.
248
+ * @returns Object with block names as keys (class name strings) and non-enumerable `css` property.
122
249
  */
123
- declare function css(input: CSSInput, filePath?: string): CSSOutput;
250
+ declare function css<T extends CSSInput>(input: T & { [K in keyof T & "css"]? : never }, filePath?: string): CSSOutput<T>;
124
251
  /** Input to globalCss(): selector → property-value map. */
125
252
  type GlobalCSSInput = Record<string, Record<string, string>>;
126
253
  /** Output of globalCss(): extracted CSS string. */
@@ -139,6 +266,54 @@ interface GlobalCSSOutput {
139
266
  */
140
267
  declare function globalCss(input: GlobalCSSInput): GlobalCSSOutput;
141
268
  /**
269
+ * Register a CSS @keyframes animation and return its name.
270
+ * The CSS is injected into the DOM (deduped by injectCSS).
271
+ */
272
+ declare function keyframes(name: string, frames: Record<string, Record<string, string>>): string;
273
+ /**
274
+ * Tailwind v4 oklch color palettes.
275
+ *
276
+ * All 22 palettes with shades 50-950 in oklch format.
277
+ * Sourced from: https://github.com/tailwindlabs/tailwindcss/blob/main/packages/tailwindcss/theme.css
278
+ */
279
+ interface ColorPalette {
280
+ 50: string;
281
+ 100: string;
282
+ 200: string;
283
+ 300: string;
284
+ 400: string;
285
+ 500: string;
286
+ 600: string;
287
+ 700: string;
288
+ 800: string;
289
+ 900: string;
290
+ 950: string;
291
+ }
292
+ declare const palettes: Readonly<{
293
+ slate: ColorPalette;
294
+ gray: ColorPalette;
295
+ zinc: ColorPalette;
296
+ neutral: ColorPalette;
297
+ stone: ColorPalette;
298
+ red: ColorPalette;
299
+ orange: ColorPalette;
300
+ amber: ColorPalette;
301
+ yellow: ColorPalette;
302
+ lime: ColorPalette;
303
+ green: ColorPalette;
304
+ emerald: ColorPalette;
305
+ teal: ColorPalette;
306
+ cyan: ColorPalette;
307
+ sky: ColorPalette;
308
+ blue: ColorPalette;
309
+ indigo: ColorPalette;
310
+ violet: ColorPalette;
311
+ purple: ColorPalette;
312
+ fuchsia: ColorPalette;
313
+ pink: ColorPalette;
314
+ rose: ColorPalette;
315
+ }>;
316
+ /**
142
317
  * Convert an array of shorthand strings into a CSS properties object
143
318
  * suitable for inline styles.
144
319
  *
@@ -192,34 +367,27 @@ declare function defineTheme(input: ThemeInput): Theme;
192
367
  * @returns Compiled CSS and token list.
193
368
  */
194
369
  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
370
  /** A child node: either a DOM Node or a string (text content). */
208
371
  type ThemeChild = Node | string;
209
372
  /** Props for ThemeProvider. */
210
373
  interface ThemeProviderProps {
211
374
  /** The theme variant name (e.g., 'light', 'dark'). Defaults to 'light'. */
212
375
  theme?: string;
213
- /** Child elements to render inside the provider. */
214
- children: ThemeChild[];
376
+ /**
377
+ * Child elements to render inside the provider.
378
+ *
379
+ * Accepts a single child (what TypeScript sees in JSX), an array, or a
380
+ * thunk (what the compiler produces after wrapping JSX children).
381
+ */
382
+ children: ThemeChild | ThemeChild[] | (() => ThemeChild | ThemeChild[]);
215
383
  }
216
384
  /**
217
385
  * Create a wrapper div with `data-theme` attribute for theme switching.
218
386
  *
219
- * @param props - Theme and children.
220
- * @returns A div element with `data-theme` set and children appended.
387
+ * Uses __element() directly (instead of jsx()) so the hydration cursor
388
+ * walker can claim the existing SSR node during mount().
221
389
  */
222
- declare function ThemeProvider(props: ThemeProviderProps): HTMLDivElement;
390
+ declare function ThemeProvider({ theme, children }: ThemeProviderProps): HTMLElement;
223
391
  /** A record of variant names to their possible values (each value maps to style entries). */
224
392
  type VariantDefinitions = Record<string, Record<string, StyleEntry[]>>;
225
393
  /** Extract the variant props type from a variant definitions object. */
@@ -252,46 +420,145 @@ interface VariantFunction<V extends VariantDefinitions> {
252
420
  * @returns A function that accepts variant props and returns a className string.
253
421
  */
254
422
  declare function variants<V extends VariantDefinitions>(config: VariantsConfig<V>): VariantFunction<V>;
423
+ declare const DialogStackContext: Context<DialogStack>;
424
+ declare function useDialogStack(): DialogStack;
425
+ interface DialogHandle<TResult> {
426
+ close(...args: void extends TResult ? [] : [result: TResult]): void;
427
+ }
428
+ type DialogComponent<
429
+ TResult,
430
+ TProps = Record<string, never>
431
+ > = (props: TProps & {
432
+ dialog: DialogHandle<TResult>;
433
+ }) => Node;
434
+ declare class DialogDismissedError extends Error {
435
+ constructor();
436
+ }
437
+ interface DialogStack {
438
+ open<
439
+ TResult,
440
+ TProps
441
+ >(component: DialogComponent<TResult, TProps>, props: TProps): Promise<TResult>;
442
+ /** @internal — used by useDialogStack() to pass captured context scope */
443
+ openWithScope<
444
+ TResult,
445
+ TProps
446
+ >(component: DialogComponent<TResult, TProps>, props: TProps, scope: ContextScope | null): Promise<TResult>;
447
+ readonly size: number;
448
+ closeAll(): void;
449
+ }
450
+ declare function createDialogStack(container: HTMLElement): DialogStack;
255
451
  /**
256
- * A reactive signal that holds a value and notifies subscribers on change.
452
+ * Brand symbol for render nodes.
453
+ * SSR nodes add this to their prototype for fast identification.
454
+ * Browser DOM nodes use the `instanceof Node` fallback in `isRenderNode`.
257
455
  */
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;
456
+ declare const RENDER_NODE_BRAND: unique symbol;
457
+ interface RenderNode {}
458
+ interface RenderElement extends RenderNode {
459
+ setAttribute(name: string, value: string): void;
460
+ removeAttribute(name: string): void;
461
+ getAttribute(name: string): string | null;
462
+ style: {
463
+ display: string;
464
+ [key: string]: any;
465
+ };
466
+ classList: {
467
+ add(cls: string): void;
468
+ remove(cls: string): void;
469
+ };
470
+ addEventListener(event: string, handler: EventListener): void;
471
+ removeEventListener(event: string, handler: EventListener): void;
472
+ }
473
+ interface RenderText extends RenderNode {
474
+ data: string;
475
+ }
476
+ interface RenderAdapter {
477
+ createElement(tag: string): RenderElement;
478
+ createElementNS(ns: string, tag: string): RenderElement;
479
+ createTextNode(text: string): RenderText;
480
+ createComment(text: string): RenderNode;
481
+ createDocumentFragment(): RenderNode;
482
+ isNode(value: unknown): value is RenderNode;
267
483
  }
268
484
  /**
269
- * A read-only reactive value derived from other signals.
485
+ * Type guard: checks if a value is a RenderNode.
486
+ * Fast path: brand check for SSR nodes.
487
+ * Fallback: instanceof Node for browser DOM nodes.
270
488
  */
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
- }
489
+ declare function isRenderNode(value: unknown): value is RenderNode;
277
490
  /**
278
- * A computed signal lazily evaluated, cached, and automatically re-computed
279
- * when dependencies change.
491
+ * Get the current render adapter.
492
+ * Auto-detects DOMAdapter if document exists and no adapter has been set.
280
493
  */
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;
494
+ declare function getAdapter(): RenderAdapter;
495
+ /**
496
+ * Set the current render adapter.
497
+ * Pass null to reset to auto-detect.
498
+ */
499
+ declare function setAdapter(adapter: RenderAdapter | null): void;
500
+ /**
501
+ * Create a DOM adapter that delegates to real browser DOM APIs.
502
+ * Zero overhead — no branding, no wrapping.
503
+ * Browser `Node` instances pass `isRenderNode()` via the `instanceof Node` fallback.
504
+ */
505
+ declare function createDOMAdapter(): RenderAdapter;
506
+ /**
507
+ * Create a DOM element with optional static properties.
508
+ *
509
+ * This is a compiler output target — the compiler generates calls
510
+ * to __element for each JSX element.
511
+ */
512
+ declare function __element<K extends keyof HTMLElementTagNameMap>(tag: K, props?: Record<string, string>): HTMLElementTagNameMap[K];
513
+ declare function __element(tag: string, props?: Record<string, string>): Element;
514
+ /**
515
+ * Append a child to a parent node.
516
+ * During hydration, this is a no-op — the child is already in the DOM.
517
+ * During CSR, delegates to appendChild.
518
+ *
519
+ * Compiler output target — replaces direct `parent.appendChild(child)`.
520
+ */
521
+ declare function __append(parent: Node, child: Node): void;
522
+ /**
523
+ * Create a static text node.
524
+ * During hydration, claims an existing text node from the SSR output.
525
+ * During CSR, creates a new text node.
526
+ *
527
+ * Compiler output target — replaces `document.createTextNode(str)`.
528
+ */
529
+ declare function __staticText(text: string): Text;
530
+ /**
531
+ * Push the hydration cursor into an element's children.
532
+ * Compiler output target — emitted around child construction.
533
+ */
534
+ declare function __enterChildren(el: Element): void;
535
+ /**
536
+ * Pop the hydration cursor back to the parent scope.
537
+ * Compiler output target — emitted after all children are appended.
538
+ */
539
+ declare function __exitChildren(): void;
540
+ interface FieldState<T = unknown> {
541
+ error: Signal<string | undefined>;
542
+ dirty: Signal<boolean>;
543
+ touched: Signal<boolean>;
544
+ value: Signal<T>;
545
+ setValue: (value: T) => void;
546
+ reset: () => void;
286
547
  }
287
- /** Dispose function returned by effect(). */
288
- type DisposeFn = () => void;
548
+ declare function createFieldState<T>(_name: string, initialValue?: T): FieldState<T>;
549
+ import { Result } from "@vertz/fetch";
289
550
  /**
290
551
  * Minimal schema interface compatible with @vertz/schema.
291
- * Any object with a `parse(data: unknown): T` method satisfies this.
552
+ * Any object with a `parse(data: unknown): Result` method satisfies this.
292
553
  */
293
554
  interface FormSchema<T> {
294
- parse(data: unknown): T;
555
+ parse(data: unknown): {
556
+ ok: true;
557
+ data: T;
558
+ } | {
559
+ ok: false;
560
+ error: unknown;
561
+ };
295
562
  }
296
563
  /** Result of a validation attempt. */
297
564
  type ValidationResult<T> = {
@@ -319,56 +586,87 @@ interface SdkMethod<
319
586
  TBody,
320
587
  TResult
321
588
  > {
322
- (body: TBody): Promise<TResult>;
589
+ (body: TBody): PromiseLike<Result<TResult, Error>>;
323
590
  url: string;
324
591
  method: string;
592
+ meta?: {
593
+ bodySchema?: FormSchema<TBody>;
594
+ };
325
595
  }
326
- /** Options for creating a form instance. */
327
- interface FormOptions<TBody> {
328
- /** Explicit schema for client-side validation before submission. */
329
- schema: FormSchema<TBody>;
596
+ /**
597
+ * An SDK method with embedded schema metadata.
598
+ * Generated by `@vertz/codegen` — carries `.meta.bodySchema` for auto-validation.
599
+ */
600
+ interface SdkMethodWithMeta<
601
+ TBody,
602
+ TResult
603
+ > extends SdkMethod<TBody, TResult> {
604
+ meta: {
605
+ bodySchema: FormSchema<TBody>;
606
+ };
330
607
  }
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;
608
+ /** Reserved property names that cannot be used as field names on FormInstance. */
609
+ type ReservedFormNames = "submitting" | "dirty" | "valid" | "action" | "method" | "onSubmit" | "reset" | "setFieldError" | "submit" | "__bindElement";
610
+ /** Mapped type providing FieldState for each field in TBody. */
611
+ type FieldAccessors<TBody> = { [K in keyof TBody] : FieldState<TBody[K]> };
612
+ /** Base properties available on every form instance. */
613
+ interface FormBaseProperties<TBody> {
614
+ action: string;
615
+ method: string;
616
+ onSubmit: (e: Event) => Promise<void>;
617
+ reset: () => void;
618
+ setFieldError: (field: keyof TBody & string, message: string) => void;
619
+ submit: (formData?: FormData) => Promise<void>;
620
+ submitting: Signal<boolean>;
621
+ dirty: ReadonlySignal<boolean>;
622
+ valid: ReadonlySignal<boolean>;
623
+ __bindElement: (el: HTMLFormElement) => void;
335
624
  }
336
- /** A form instance bound to an SDK method. */
337
- interface FormInstance<
625
+ /**
626
+ * A form instance bound to an SDK method.
627
+ * Combines base properties with per-field reactive state via Proxy.
628
+ * If TBody has any key that conflicts with ReservedFormNames, it produces a type error.
629
+ * TResult is used by form() overloads for return type inference.
630
+ */
631
+ type FormInstance<
632
+ TBody,
633
+ _TResult
634
+ > = keyof TBody & ReservedFormNames extends never ? FormBaseProperties<TBody> & FieldAccessors<TBody> : {
635
+ __error: `Field name conflicts with reserved form property: ${keyof TBody & ReservedFormNames & string}`;
636
+ };
637
+ /** Options for creating a form instance. */
638
+ interface FormOptions<
338
639
  TBody,
339
640
  TResult
340
641
  > {
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;
642
+ /** Explicit schema for client-side validation before submission. */
643
+ schema?: FormSchema<TBody>;
644
+ /** Initial values for form fields. */
645
+ initial?: Partial<TBody> | (() => Partial<TBody>);
646
+ /** Callback invoked after a successful submission. */
647
+ onSuccess?: (result: TResult) => void;
648
+ /** Callback invoked when validation or submission fails. */
649
+ onError?: (errors: Record<string, string>) => void;
650
+ /** When true, reset the form after a successful submission. */
651
+ resetOnSuccess?: boolean;
358
652
  }
359
653
  /**
360
- * Create a form instance bound to an SDK method with schema validation.
654
+ * Create a form instance bound to an SDK method.
655
+ *
656
+ * The form provides direct properties for progressive enhancement (action, method, onSubmit),
657
+ * per-field reactive state via Proxy, and submission handling with validation.
361
658
  *
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
659
+ * When the SDK method has `.meta.bodySchema` (generated by `@vertz/codegen`),
660
+ * the schema option is optional. When the SDK method lacks `.meta`, the schema option is required.
367
661
  */
368
662
  declare function form<
369
663
  TBody,
370
664
  TResult
371
- >(sdkMethod: SdkMethod<TBody, TResult>, options: FormOptions<TBody>): FormInstance<TBody, TResult>;
665
+ >(sdkMethod: SdkMethodWithMeta<TBody, TResult>, options?: FormOptions<TBody, TResult>): FormInstance<TBody, TResult>;
666
+ declare function form<
667
+ TBody,
668
+ TResult
669
+ >(sdkMethod: SdkMethod<TBody, TResult>, options: Required<Pick<FormOptions<TBody, TResult>, "schema">> & FormOptions<TBody, TResult>): FormInstance<TBody, TResult>;
372
670
  /** Options for formDataToObject conversion. */
373
671
  interface FormDataOptions {
374
672
  /** When true, coerces numeric strings to numbers and "true"/"false" to booleans. */
@@ -395,7 +693,8 @@ type ComponentRegistry = Record<string, ComponentLoader>;
395
693
  * Client entry point for atomic per-component hydration.
396
694
  *
397
695
  * 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.
696
+ * deserializes their props, and applies automatic hydration based on viewport
697
+ * proximity (IntersectionObserver with 200px rootMargin).
399
698
  *
400
699
  * Components without `data-v-id` markers are static and ship zero JS.
401
700
  *
@@ -405,30 +704,40 @@ type ComponentRegistry = Record<string, ComponentLoader>;
405
704
  */
406
705
  declare function hydrate(registry: ComponentRegistry): void;
407
706
  /**
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)
707
+ * Options for mounting an app to the DOM.
708
+ */
709
+ interface MountOptions {
710
+ /** Theme definition for CSS vars */
711
+ theme?: Theme;
712
+ /** Global CSS strings to inject */
713
+ styles?: string[];
714
+ /** Callback after mount completes */
715
+ onMount?: (root: HTMLElement) => void;
716
+ }
717
+ /**
718
+ * Handle returned from mount() for controlling the mounted app.
416
719
  */
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;
720
+ interface MountHandle {
721
+ /** Unmount the app and cleanup */
722
+ unmount: () => void;
723
+ /** Root HTMLElement */
724
+ root: HTMLElement;
725
+ }
425
726
  /**
426
- * Media -- hydrate when a CSS media query matches.
427
- * Returns a strategy function bound to the given query string.
727
+ * Mount an app to a DOM element.
728
+ *
729
+ * Uses tolerant hydration automatically: if the root element has SSR content,
730
+ * it walks the existing DOM and attaches reactivity without re-creating nodes.
731
+ * If the root is empty (CSR), it renders from scratch.
732
+ *
733
+ * @param app - App function that returns an HTMLElement
734
+ * @param selector - CSS selector string or HTMLElement
735
+ * @param options - Mount options (theme, styles, onMount, etc.)
736
+ * @returns MountHandle with unmount function and root element
428
737
  */
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;
738
+ declare function mount<AppFn extends () => Element>(app: AppFn, selector: string | HTMLElement, options?: MountOptions): MountHandle;
739
+ import { QueryDescriptor as QueryDescriptor2 } from "@vertz/fetch";
740
+ import { isQueryDescriptor } from "@vertz/fetch";
432
741
  /**
433
742
  * Interface for cache stores used by query().
434
743
  * Consumers can provide custom implementations (e.g. LRU, persistent storage).
@@ -437,7 +746,9 @@ interface CacheStore<T = unknown> {
437
746
  get(key: string): T | undefined;
438
747
  set(key: string, value: T): void;
439
748
  delete(key: string): void;
749
+ clear?(): void;
440
750
  }
751
+ import { QueryDescriptor } from "@vertz/fetch";
441
752
  /** Options for query(). */
442
753
  interface QueryOptions<T> {
443
754
  /** Pre-populated data — skips the initial fetch when provided. */
@@ -450,15 +761,33 @@ interface QueryOptions<T> {
450
761
  key?: string;
451
762
  /** Custom cache store. Defaults to a shared in-memory Map. */
452
763
  cache?: CacheStore<T>;
764
+ /** Timeout in ms for SSR data loading. Default: 300. Set to 0 to disable. */
765
+ ssrTimeout?: number;
766
+ /**
767
+ * Polling interval in ms, or a function for dynamic intervals.
768
+ *
769
+ * - `number` — fixed interval in ms
770
+ * - `false` or `0` — disabled
771
+ * - `(data, iteration) => number | false` — called after each fetch to
772
+ * determine the next interval. Return `false` to stop polling.
773
+ * `iteration` counts polls since the last start/restart (resets to 0
774
+ * when the function returns `false`).
775
+ */
776
+ refetchInterval?: number | false | ((data: T | undefined, iteration: number) => number | false);
453
777
  }
454
778
  /** The reactive object returned by query(). */
455
- interface QueryResult<T> {
779
+ interface QueryResult<
780
+ T,
781
+ E = unknown
782
+ > {
456
783
  /** 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>;
784
+ readonly data: Unwrapped<ReadonlySignal<T | undefined>>;
785
+ /** True only on the initial load (no data yet). False during revalidation. */
786
+ readonly loading: Unwrapped<ReadonlySignal<boolean>>;
787
+ /** True when refetching while stale data is already available. */
788
+ readonly revalidating: Unwrapped<ReadonlySignal<boolean>>;
460
789
  /** The error from the latest failed fetch, or undefined. */
461
- readonly error: ReadonlySignal<unknown>;
790
+ readonly error: Unwrapped<ReadonlySignal<E | undefined>>;
462
791
  /** Manually trigger a refetch (clears cache for this key). */
463
792
  refetch: () => void;
464
793
  /** Alias for refetch — revalidate the cached data. */
@@ -472,11 +801,44 @@ interface QueryResult<T> {
472
801
  * The thunk is wrapped in an effect so that when reactive dependencies
473
802
  * used *before* the async call change, the query automatically re-fetches.
474
803
  *
475
- * @param thunk - An async function that returns the data.
804
+ * @param source - A QueryDescriptor or an async function that returns the data.
476
805
  * @param options - Optional configuration.
477
806
  * @returns A QueryResult with reactive signals for data, loading, and error.
478
807
  */
808
+ declare function query<
809
+ T,
810
+ E
811
+ >(descriptor: QueryDescriptor<T, E>, options?: Omit<QueryOptions<T>, "key">): QueryResult<T, E>;
479
812
  declare function query<T>(thunk: () => Promise<T>, options?: QueryOptions<T>): QueryResult<T>;
813
+ interface QueryMatchHandlers<
814
+ T,
815
+ E
816
+ > {
817
+ loading: () => Node | null;
818
+ error: (error: E) => Node | null;
819
+ data: (data: T) => Node | null;
820
+ }
821
+ /**
822
+ * Pattern-match on a QueryResult's exclusive state.
823
+ *
824
+ * Returns a stable `<span style="display:contents">` wrapper that internally
825
+ * manages branch switching (loading/error/data) via a reactive effect.
826
+ * The same wrapper is returned for repeated calls with the same queryResult
827
+ * (cached via WeakMap), enabling __child's stable-node optimization.
828
+ *
829
+ * Priority: loading → error → data.
830
+ *
831
+ * `loading` only fires on the initial load (no data yet).
832
+ * When revalidating with existing data, the `data` handler receives the
833
+ * current data. Access `query.revalidating` from the component scope for
834
+ * revalidation state.
835
+ */
836
+ declare function queryMatch<
837
+ T,
838
+ E
839
+ >(queryResult: QueryResult<T, E>, handlers: QueryMatchHandlers<T, E>): HTMLElement & {
840
+ dispose: DisposeFn;
841
+ };
480
842
  /**
481
843
  * Template literal type utility that extracts route parameter names from a path pattern.
482
844
  *
@@ -504,9 +866,34 @@ type ExtractParams<T extends string> = [ExtractParamsFromSegments<WithoutWildcar
504
866
  } : Record<string, never> : HasWildcard<T> extends true ? { [K in ExtractParamsFromSegments<WithoutWildcard<T>>] : string } & {
505
867
  "*": string;
506
868
  } : { [K in ExtractParamsFromSegments<WithoutWildcard<T>>] : string };
869
+ /**
870
+ * Convert a route pattern to the union of URL shapes it accepts.
871
+ * - Static: `'/'` → `'/'`
872
+ * - Param: `'/tasks/:id'` → `` `/tasks/${string}` ``
873
+ * - Wildcard: `'/files/*'` → `` `/files/${string}` ``
874
+ * - Multi: `'/users/:id/posts/:postId'` → `` `/users/${string}/posts/${string}` ``
875
+ * - Fallback: `string` → `string` (backward compat)
876
+ */
877
+ 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;
878
+ /**
879
+ * Union of all valid URL shapes for a route map.
880
+ * Maps each route pattern key through `PathWithParams` to produce the accepted URL shapes.
881
+ *
882
+ * Example:
883
+ * ```
884
+ * RoutePaths<{ '/': ..., '/tasks/:id': ... }> = '/' | `/tasks/${string}`
885
+ * ```
886
+ */
887
+ type RoutePaths<TRouteMap extends Record<string, unknown>> = { [K in keyof TRouteMap & string] : PathWithParams<K> }[keyof TRouteMap & string];
507
888
  /** Simple schema interface for search param parsing. */
508
889
  interface SearchParamSchema<T> {
509
- parse(data: unknown): T;
890
+ parse(data: unknown): {
891
+ ok: true;
892
+ data: T;
893
+ } | {
894
+ ok: false;
895
+ error: unknown;
896
+ };
510
897
  }
511
898
  /** A route configuration for a single path. */
512
899
  interface RouteConfig<
@@ -534,6 +921,46 @@ interface RouteConfig<
534
921
  interface RouteDefinitionMap {
535
922
  [pattern: string]: RouteConfig;
536
923
  }
924
+ /**
925
+ * Loose route config used as the generic constraint for `defineRoutes`.
926
+ * Uses `Record<string, string>` for loader params so any concrete loader
927
+ * that accesses string params (e.g., `params.id`) satisfies the constraint.
928
+ */
929
+ interface RouteConfigLike {
930
+ component: () => Node | Promise<{
931
+ default: () => Node;
932
+ }>;
933
+ /**
934
+ * Method syntax (`loader?(ctx): R`) is intentional — it enables **bivariant**
935
+ * parameter checking under `strictFunctionTypes`. Property syntax
936
+ * (`loader?: (ctx) => R`) would be contravariant, causing `RouteConfig<string>`
937
+ * (whose loader has `params: Record<string, never>`) to fail assignability
938
+ * against this constraint's `params: Record<string, string>`.
939
+ */
940
+ loader?(ctx: {
941
+ params: Record<string, string>;
942
+ signal: AbortSignal;
943
+ }): unknown;
944
+ errorComponent?: (error: Error) => Node;
945
+ searchParams?: SearchParamSchema<unknown>;
946
+ children?: Record<string, RouteConfigLike>;
947
+ }
948
+ /**
949
+ * Phantom branded array that carries the route map type `T`.
950
+ * The `__routes` property never exists at runtime — it is a type-level
951
+ * marker used to thread the developer's literal route keys through
952
+ * `createRouter`, `useRouter`, etc.
953
+ */
954
+ type TypedRoutes<T extends Record<string, RouteConfigLike> = RouteDefinitionMap> = CompiledRoute[] & {
955
+ readonly __routes: T;
956
+ };
957
+ /**
958
+ * Extract the route map type from `TypedRoutes<T>`.
959
+ * If `T` is not a `TypedRoutes`, returns `T` as-is (passthrough).
960
+ *
961
+ * Usage: `useRouter<InferRouteMap<typeof routes>>()`
962
+ */
963
+ type InferRouteMap<T> = T extends TypedRoutes<infer R> ? R : T;
537
964
  /** Internal compiled route. */
538
965
  interface CompiledRoute {
539
966
  /** The original path pattern. */
@@ -579,17 +1006,30 @@ type LoaderData<T> = T extends {
579
1006
  * Define routes from a configuration map.
580
1007
  * Returns an array of compiled routes preserving definition order.
581
1008
  */
582
- declare function defineRoutes(map: RouteDefinitionMap): CompiledRoute[];
583
- /** Props for the Link component. */
584
- interface LinkProps {
1009
+ declare function defineRoutes<const T extends Record<string, RouteConfigLike>>(map: T): TypedRoutes<T>;
1010
+ /**
1011
+ * Props for the Link component.
1012
+ *
1013
+ * Generic over the route map `T`. Defaults to `RouteDefinitionMap` (string
1014
+ * index signature) for backward compatibility — unparameterized `LinkProps`
1015
+ * accepts any string href.
1016
+ */
1017
+ interface LinkProps<T extends Record<string, RouteConfigLike> = RouteDefinitionMap> {
585
1018
  /** The target URL path. */
586
- href: string;
587
- /** Text or content for the link. */
588
- children: string;
1019
+ href: RoutePaths<T>;
1020
+ /** Text or content for the link. Thunk may return string or Text node. */
1021
+ children: string | (() => string | Node);
589
1022
  /** Class applied when the link's href matches the current path. */
590
1023
  activeClass?: string;
591
1024
  /** Static class name for the anchor element. */
592
1025
  className?: string;
1026
+ /** Prefetch strategy. 'hover' triggers server pre-fetch on mouseenter/focus. */
1027
+ prefetch?: "hover";
1028
+ }
1029
+ /** Options for createLink(). */
1030
+ interface LinkFactoryOptions {
1031
+ /** Callback fired when a link wants to prefetch its target URL. */
1032
+ onPrefetch?: (url: string) => void;
593
1033
  }
594
1034
  /**
595
1035
  * Create a Link component factory bound to the router's state.
@@ -598,14 +1038,45 @@ interface LinkProps {
598
1038
  * @param navigate - Navigation function from the router
599
1039
  * @returns A Link component function
600
1040
  */
601
- declare function createLink(currentPath: ReadonlySignal<string>, navigate: (url: string) => void): (props: LinkProps) => HTMLAnchorElement;
1041
+ declare function createLink(currentPath: ReadonlySignal<string>, navigate: (url: string) => void, factoryOptions?: LinkFactoryOptions): (props: LinkProps) => HTMLAnchorElement;
602
1042
  /** Options for router.navigate(). */
603
1043
  interface NavigateOptions {
604
1044
  /** Use history.replaceState instead of pushState. */
605
1045
  replace?: boolean;
606
1046
  }
607
- /** The router instance returned by createRouter. */
608
- interface Router {
1047
+ /** Handle returned by prefetchNavData for cancellation. */
1048
+ interface PrefetchHandle {
1049
+ abort: () => void;
1050
+ /** Resolves when SSE stream completes (data or done event). */
1051
+ done?: Promise<void>;
1052
+ /** Resolves when the first SSE event of any type arrives. */
1053
+ firstEvent?: Promise<void>;
1054
+ }
1055
+ /** Options for createRouter(). */
1056
+ interface RouterOptions {
1057
+ /** Enable server-side navigation pre-fetch. When true, uses default timeout. */
1058
+ serverNav?: boolean | {
1059
+ timeout?: number;
1060
+ };
1061
+ /** @internal — injected for testing. Production uses the real module. */
1062
+ _prefetchNavData?: (url: string, options?: {
1063
+ timeout?: number;
1064
+ }) => PrefetchHandle;
1065
+ }
1066
+ /**
1067
+ * The router instance returned by createRouter.
1068
+ *
1069
+ * Generic over the route map `T`. Defaults to `RouteDefinitionMap` (string
1070
+ * index signature) for backward compatibility — unparameterized `Router`
1071
+ * accepts any string in `navigate()`.
1072
+ *
1073
+ * Method syntax on `navigate`, `revalidate`, and `dispose` enables bivariant
1074
+ * parameter checking under `strictFunctionTypes`. This means `Router<T>` is
1075
+ * assignable to `Router` (the unparameterized default), which is required for
1076
+ * storing typed routers in the `RouterContext` without contravariance errors.
1077
+ * At call sites, TypeScript still enforces the `RoutePaths<T>` constraint.
1078
+ */
1079
+ interface Router<T extends Record<string, RouteConfigLike> = RouteDefinitionMap> {
609
1080
  /** Current matched route (reactive signal). */
610
1081
  current: Signal<RouteMatch | null>;
611
1082
  /** Loader data from the current route's loaders (reactive signal). */
@@ -615,34 +1086,63 @@ interface Router {
615
1086
  /** Parsed search params from the current route (reactive signal). */
616
1087
  searchParams: Signal<Record<string, unknown>>;
617
1088
  /** Navigate to a new URL path. */
618
- navigate: (url: string, options?: NavigateOptions) => Promise<void>;
1089
+ navigate(url: RoutePaths<T>, options?: NavigateOptions): Promise<void>;
619
1090
  /** Re-run all loaders for the current route. */
620
- revalidate: () => Promise<void>;
1091
+ revalidate(): Promise<void>;
621
1092
  /** Remove popstate listener and clean up the router. */
622
- dispose: () => void;
1093
+ dispose(): void;
623
1094
  }
624
1095
  /**
1096
+ * Convenience alias for a typed router.
1097
+ * `TypedRouter<T>` is identical to `Router<T>` — it exists for readability
1098
+ * when the generic parameter makes the intent clearer.
1099
+ */
1100
+ type TypedRouter<T extends Record<string, RouteConfigLike> = RouteDefinitionMap> = Router<T>;
1101
+ /**
625
1102
  * Create a router instance.
626
1103
  *
627
1104
  * @param routes - Compiled route list from defineRoutes()
628
1105
  * @param initialUrl - The initial URL to match (optional; auto-detects from window.location or __SSR_URL__)
629
1106
  * @returns Router instance with reactive state and navigation methods
630
1107
  */
631
- declare function createRouter(routes: CompiledRoute[], initialUrl?: string): Router;
1108
+ declare function createRouter<T extends Record<string, RouteConfigLike> = RouteDefinitionMap>(routes: TypedRoutes<T>, initialUrl?: string, options?: RouterOptions): Router<T>;
632
1109
  /** 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;
1110
+ interface OutletContextValue {
1111
+ /** Reactive child component factory (may return async module). */
1112
+ childComponent: Signal<(() => Node | Promise<{
1113
+ default: () => Node;
1114
+ }>) | undefined>;
1115
+ /** Router instance for restoring context in async resolution. */
1116
+ router: Router;
1117
+ }
1118
+ /** Shared context used by RouterView and Outlet. */
1119
+ declare const OutletContext: Context<OutletContextValue>;
1120
+ /**
1121
+ * Outlet component — renders the nested child route.
1122
+ *
1123
+ * Must be called inside a layout component rendered by RouterView.
1124
+ * Reads from OutletContext to determine which child to render.
1125
+ */
1126
+ declare function Outlet(): Node;
1127
+ declare const RouterContext: Context<Router>;
1128
+ declare function useRouter<T extends Record<string, RouteConfigLike> = RouteDefinitionMap>(): UnwrapSignals<Router<T>>;
1129
+ declare function useParams<TPath extends string = string>(): ExtractParams<TPath>;
1130
+ interface RouterViewProps {
1131
+ router: Router;
1132
+ fallback?: () => Node;
638
1133
  }
639
1134
  /**
640
- * Create an Outlet component bound to a specific outlet context.
1135
+ * Renders the matched route's component inside a container div.
1136
+ *
1137
+ * Handles sync and async (lazy-loaded) components, stale resolution guards,
1138
+ * page cleanup on navigation, and RouterContext propagation.
641
1139
  *
642
- * @param outletCtx - The context that holds the child component
643
- * @returns An Outlet component function
1140
+ * Uses __element() so the container is claimed from SSR during hydration.
1141
+ * On the first hydration render, children are already in the DOM — the
1142
+ * domEffect runs the component factory (to attach reactivity/event handlers)
1143
+ * but skips clearing the container.
644
1144
  */
645
- declare function createOutlet(outletCtx: Context<OutletContext>): () => Node;
1145
+ declare function RouterView({ router, fallback }: RouterViewProps): HTMLElement;
646
1146
  /**
647
1147
  * Parse URLSearchParams into a typed object, optionally through a schema.
648
1148
  *
@@ -668,12 +1168,6 @@ declare class DisposalScopeError extends Error {
668
1168
  constructor();
669
1169
  }
670
1170
  /**
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
1171
  * Group multiple signal writes into a single update flush.
678
1172
  * Nested batches are supported — only the outermost batch triggers the flush.
679
1173
  */
@@ -688,13 +1182,106 @@ declare function signal<T>(initial: T): Signal<T>;
688
1182
  */
689
1183
  declare function computed<T>(fn: () => T): Computed<T>;
690
1184
  /**
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
1185
  * Execute a function without tracking any signal reads.
697
1186
  * Useful for reading signals without creating subscriptions.
698
1187
  */
699
1188
  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 };
1189
+ /**
1190
+ * Serialized format for EntityStore - used for SSR transfer and hydration.
1191
+ */
1192
+ interface SerializedStore {
1193
+ /** Entity data keyed by type → id → entity */
1194
+ entities: Record<string, Record<string, unknown>>;
1195
+ /** Query result indices (optional) */
1196
+ queries?: Record<string, {
1197
+ ids: string[];
1198
+ nextCursor?: string | null;
1199
+ }>;
1200
+ }
1201
+ /**
1202
+ * Options for EntityStore constructor.
1203
+ */
1204
+ interface EntityStoreOptions {
1205
+ /** Initial data to hydrate from (SSR). */
1206
+ initialData?: SerializedStore;
1207
+ }
1208
+ /**
1209
+ * EntityStore - Normalized, signal-backed entity cache for @vertz/ui.
1210
+ *
1211
+ * Stores entities by type and ID, with signal-per-entity reactivity.
1212
+ * Supports field-level merge, SSR hydration, and query result indices.
1213
+ */
1214
+ declare class EntityStore {
1215
+ private _entities;
1216
+ private _typeListeners;
1217
+ private _queryIndices;
1218
+ constructor(options?: EntityStoreOptions);
1219
+ /**
1220
+ * Read a single entity. Returns a signal that updates on merge.
1221
+ * Returns the same signal instance on repeated calls (identity stability).
1222
+ */
1223
+ get<T>(type: string, id: string): ReadonlySignal<T | undefined>;
1224
+ /**
1225
+ * Read multiple entities by IDs. Returns a computed signal of the array.
1226
+ * Returns a NEW computed signal each call (not cached).
1227
+ */
1228
+ getMany<T>(type: string, ids: string[]): ReadonlySignal<(T | undefined)[]>;
1229
+ /**
1230
+ * Merge one or more entities into the store.
1231
+ * Field-level merge with shallow diff - only updates signals if data changed.
1232
+ * Uses batch() to coalesce multiple updates into single reactive flush.
1233
+ * Uses untrack() to prevent circular re-triggering when called from effects.
1234
+ */
1235
+ merge<T extends {
1236
+ id: string;
1237
+ }>(type: string, data: T | T[]): void;
1238
+ /**
1239
+ * Remove an entity from the store.
1240
+ * Triggers type change listeners and removes from query indices.
1241
+ */
1242
+ remove(type: string, id: string): void;
1243
+ /**
1244
+ * Subscribe to type-level changes (create/delete, not field updates).
1245
+ * Returns an unsubscribe function.
1246
+ */
1247
+ onTypeChange(type: string, callback: () => void): () => void;
1248
+ /**
1249
+ * Check if an entity exists in the store.
1250
+ */
1251
+ has(type: string, id: string): boolean;
1252
+ /**
1253
+ * Get count of entities for a type.
1254
+ */
1255
+ size(type: string): number;
1256
+ /**
1257
+ * Serialize the store for SSR transfer.
1258
+ */
1259
+ dehydrate(): SerializedStore;
1260
+ /**
1261
+ * Hydrate from serialized data. Merges into existing store (doesn't replace).
1262
+ */
1263
+ hydrate(data: SerializedStore): void;
1264
+ private _getOrCreateTypeMap;
1265
+ private _getOrCreateListeners;
1266
+ private _notifyTypeChange;
1267
+ }
1268
+ /**
1269
+ * Create a pre-populated EntityStore for testing.
1270
+ *
1271
+ * @param data - Entity data keyed by type → id → entity
1272
+ * @returns A new EntityStore with the data already merged
1273
+ *
1274
+ * @example
1275
+ * ```ts
1276
+ * const store = createTestStore({
1277
+ * User: {
1278
+ * '1': { id: '1', name: 'Alice' },
1279
+ * '2': { id: '2', name: 'Bob' }
1280
+ * }
1281
+ * });
1282
+ *
1283
+ * expect(store.get('User', '1').value).toEqual({ id: '1', name: 'Alice' });
1284
+ * ```
1285
+ */
1286
+ declare function createTestStore(data: Record<string, Record<string, unknown>>): EntityStore;
1287
+ export { zoomOut, zoomIn, variants, validate, useSearchParams, useRouter, useParams, useDialogStack, 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, createDialogStack, 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, DialogStackContext, DialogStack, DialogHandle, DialogDismissedError, DialogComponent, Context, Computed, ComponentRegistry, ComponentLoader, ComponentFunction, CompiledTheme, CompiledRoute, ColorPalette, ChildrenAccessor, ChildValue, CacheStore, CSSOutput, CSSInput, ANIMATION_EASING, ANIMATION_DURATION };