@estjs/template 0.0.15 → 0.0.16-beta.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.
@@ -1,253 +1,235 @@
1
1
  import { Signal, Computed } from '@estjs/signals';
2
+ import { S as Scope } from './internal-Bz6h0aPa.cjs';
3
+ export { I as InjectionKey, i as inject, p as provide } from './internal-Bz6h0aPa.cjs';
4
+ import { normalizeClassName } from '@estjs/shared';
2
5
 
3
- /**
4
- * InjectionKey is a unique identifier for provided values.
5
- * Using Symbol ensures type safety and prevents key collisions.
6
- */
7
- interface InjectionKey<T> extends Symbol {
6
+ declare enum COMPONENT_STATE {
7
+ /** Initial state */
8
+ INITIAL = 0,
9
+ /** Mounting */
10
+ MOUNTING = 1,
11
+ /** MOUNTED */
12
+ MOUNTED = 2,
13
+ /** Updating */
14
+ UPDATING = 3,
15
+ /** Destroying */
16
+ DESTROYING = 4,
17
+ /** destroy */
18
+ DESTROYED = 5
8
19
  }
9
- /**
10
- * Provide a value in the current scope.
11
- * The value can be injected by this scope or any descendant scope.
12
- *
13
- * @param key - The injection key
14
- * @param value - The value to provide
15
- */
16
- declare function provide<T>(key: InjectionKey<T> | string | number, value: T): void;
17
- /**
18
- * Inject a value from the scope hierarchy.
19
- * Traverses up the parent chain until finding a matching key.
20
- *
21
- * @param key - The injection key
22
- * @param defaultValue - Default value if key is not found
23
- * @returns The injected value or default value
24
- */
25
- declare function inject<T>(key: InjectionKey<T> | string | number, defaultValue?: T): T;
26
-
27
- interface Scope {
28
- readonly id: number;
29
- parent: Scope | null;
30
- children: Scope[] | null;
31
- provides: Map<InjectionKey<unknown> | string | number | symbol, unknown> | null;
32
- cleanup: (() => void)[] | null;
33
- onMount: (() => void | Promise<void>)[] | null;
34
- onUpdate: (() => void | Promise<void>)[] | null;
35
- onDestroy: (() => void | Promise<void>)[] | null;
36
- isMounted: boolean;
37
- isDestroyed: boolean;
20
+ declare enum COMPONENT_TYPE {
21
+ NORMAL = "normal",
22
+ FRAGMENT = "fragment",
23
+ PORTAL = "portal",
24
+ SUSPENSE = "suspense",
25
+ FOR = "for"
38
26
  }
39
- /**
40
- * Get the currently active scope.
41
- * @returns The active scope or null if none is active
42
- */
43
- declare function getActiveScope(): Scope | null;
44
- /**
45
- * Set the active scope (internal use).
46
- * @param scope - The scope to set as active
47
- */
48
- declare function setActiveScope(scope: Scope | null): void;
49
- declare function createScope(parent?: Scope | null): Scope;
50
- /**
51
- * Run a function within a scope, ensuring proper cleanup.
52
- * The previous active scope is restored even if the function throws.
53
- *
54
- * @param scope - The scope to run within
55
- * @param fn - The function to execute
56
- * @returns The return value of the function
57
- */
58
- declare function runWithScope<T>(scope: Scope, fn: () => T): T;
59
- /**
60
- * Dispose a scope and recursively dispose all child scopes.
61
- * Performs the following cleanup in order:
62
- * 1. Recursively disposes all children (depth-first)
63
- * 2. Executes destroy lifecycle hooks
64
- * 3. Executes registered cleanup functions
65
- * 4. Removes scope from parent's children list
66
- * 5. Clears all internal collections and resets state
67
- *
68
- * Safe to call multiple times (idempotent).
69
- *
70
- * @param scope - The scope to dispose
71
- *
72
- * @example
73
- * ```ts
74
- * const scope = createScope(parent);
75
- * // ... use scope ...
76
- * disposeScope(scope); // Cleanup everything
77
- * ```
78
- */
79
- declare function disposeScope(scope: Scope): void;
80
- /**
81
- * Register a cleanup function to be executed when the scope is disposed.
82
- * Useful for cleaning up timers, subscriptions, event listeners, etc.
83
- *
84
- * Cleanup functions are executed in LIFO order (last registered, first executed).
85
- * Cleanup errors don't prevent other cleanups from running.
86
- *
87
- * @param fn - The cleanup function to register
88
- *
89
- * @throws Error in dev mode if called outside a scope
90
- *
91
- * @example
92
- * ```ts
93
- * const timerId = setInterval(() => {}, 1000);
94
- * onCleanup(() => clearInterval(timerId));
95
- * ```
96
- */
97
- declare function onCleanup(fn: () => void): void;
98
-
99
- declare const NORMAL_COMPONENT: unique symbol;
100
27
 
101
28
  type AnyNode = Node | Component<any> | Element | string | number | boolean | null | undefined | AnyNode[] | (() => AnyNode) | Signal<AnyNode> | Computed<AnyNode>;
102
29
  type ComponentProps = Record<string, unknown>;
103
30
  type ComponentFn<P = ComponentProps> = (props: P) => AnyNode;
104
31
 
105
- declare class Component<P extends ComponentProps = ComponentProps> {
106
- component: ComponentFn<P>;
32
+ declare class Component<P extends ComponentProps = {}> {
33
+ readonly component: ComponentFn<P>;
107
34
  props: P;
108
- protected renderedNodes: AnyNode[];
109
- protected scope: Scope | null;
110
- protected parentNode: Node | undefined;
35
+ readonly [COMPONENT_TYPE.NORMAL] = true;
36
+ scope: Scope | null;
37
+ state: COMPONENT_STATE;
111
38
  beforeNode: Node | undefined;
112
- private reactiveProps;
113
- private _propSnapshots;
114
- readonly key: string | undefined;
115
- protected state: number;
116
- protected parentScope: Scope | null;
117
- readonly [NORMAL_COMPONENT] = true;
118
- get isConnected(): boolean;
119
- get firstChild(): Node | undefined;
39
+ renderedNodes: Node[];
40
+ firstChild: Node | undefined;
41
+ protected parentNode: Node | undefined;
42
+ private readonly parentScope;
43
+ private readonly reactiveProps;
44
+ private rootEventCleanups;
45
+ private rootRefCleanup?;
120
46
  constructor(component: ComponentFn<P>, props?: P);
47
+ /**
48
+ * Mount the component into `parentNode` (optionally before `beforeNode`).
49
+ * If already rendered, the existing DOM is re-inserted without re-running
50
+ * the component function.
51
+ */
121
52
  mount(parentNode: Node, beforeNode?: Node): AnyNode[];
122
- update<T extends ComponentProps>(prevNode: Component<T>): Component<T>;
123
53
  /**
124
- * Update reactive props by comparing with current values
54
+ * Re-install props into the same `reactiveProps` container (preserving
55
+ * any closures already holding a reference to it) and re-apply
56
+ * refs/events against the current root element.
125
57
  */
126
- private _updateReactiveProps;
127
- private unwrapRenderResult;
128
- forceUpdate(): void;
58
+ update(props: P): void;
129
59
  /**
130
- * Get anchor node for insertion
60
+ * Tear down and re-mount the component at its current insertion point.
61
+ * No-op if the component has never been mounted.
131
62
  */
132
- private _getAnchorNode;
63
+ forceUpdate(): void;
133
64
  /**
134
- * Destroy component
65
+ * Dispose the scope, remove all rendered nodes, and clear bookkeeping.
66
+ * Idempotent: subsequent calls are no-ops.
135
67
  */
136
68
  destroy(): void;
137
- applyProps(props: P): void;
69
+ /**
70
+ * Apply props that bind to the root DOM element rather than flowing into
71
+ * the component body: `ref` (signal/function) and `onXxx` event handlers.
72
+ * The render-facing `reactiveProps` already has those keys; here we just
73
+ * wire them to the actual DOM node.
74
+ */
75
+ private syncSpecialProps;
76
+ /**
77
+ * Remove all listeners/ref bindings currently attached to the root element.
78
+ */
79
+ private releaseSpecialProps;
80
+ /**
81
+ * Bind the root ref prop and return a cleanup that restores the previous ref state.
82
+ */
83
+ private bindRootRef;
138
84
  }
139
85
  /**
140
- * check if a node is a component
141
- * @param {unknown} node - the node to check
142
- * @returns {boolean} true if the node is a component, false otherwise
86
+ * Check if a value is a Component instance.
143
87
  */
144
88
  declare function isComponent(node: unknown): node is Component;
145
89
  /**
146
- * create a component
147
- * @param {Function} componentFn - the component function
148
- * @param {ComponentProps} props - the component props
149
- * @returns {Component} the component
90
+ * Wrap a component function in a Component instance, or pass an existing
91
+ * Component instance through unchanged.
150
92
  */
151
93
  declare function createComponent<P extends ComponentProps>(componentFn: ComponentFn<P>, props?: P): Component<P>;
152
94
 
153
95
  /**
154
- * Create a template factory function from HTML string
96
+ * Create a template factory function from HTML string.
155
97
  *
156
98
  * This function creates a reusable template factory that efficiently clones
157
- * DOM nodes from the provided HTML string. The template is parsed once and
99
+ * DOM nodes from the provided HTML string. The template is parsed once.
158
100
  *
159
- * @param html - The HTML string to create template from
160
- * @returns Factory function that returns a cloned node of the template
161
- * @throws {Error} When template content is empty or invalid
162
-
101
+ * Security note: `template(html)` is a raw HTML entrypoint. The caller is
102
+ * responsible for ensuring `html` is trusted and not derived from unsanitized
103
+ * user input.
104
+ *
105
+ * @param html - The HTML string to create template from.
106
+ * @returns Factory function that returns a cloned node of the template.
107
+ * @throws {Error} When template content is empty or invalid.
108
+ *
109
+ * @example
110
+ * ```typescript
111
+ * const buttonTemplate = template('<button>Click me</button>');
112
+ * const button1 = buttonTemplate(); // Creates first button instance
113
+ * const button2 = buttonTemplate(); // Creates second button instance
114
+ * ```
163
115
  */
164
116
  declare function template(html: string): () => Node;
165
117
  /**
166
- * Create and mount an application with the specified component
118
+ * Create and mount an application with the specified component.
167
119
  *
168
120
  * This function initializes an application by mounting a root component
169
121
  * to a target DOM element. It handles target validation and cleanup.
170
122
  *
171
- * @param component - The root component function to mount
172
- * @param target - CSS selector string or DOM element to mount to
173
- * @returns The mount root component instance, or undefined if target not found
123
+ * @param component - The root component function to mount.
124
+ * @param target - CSS selector string or DOM element to mount to.
125
+ * @returns Object with root component and unmount function.
126
+ *
127
+ * @example
128
+ * ```typescript
129
+ * const App = () => template('<div>Hello World</div>')
130
+ * const app = createApp(App, '#root');
131
+ *
132
+ * // Or with DOM element
133
+ * const container = document.getElementById('app');
134
+ * const app = createApp(App, container);
135
+ * ```
174
136
  */
175
- declare function createApp<P extends ComponentProps = {}>(component: ComponentFn<P> | Component<P>, target: string | Element): Component<P> | undefined;
137
+ declare function createApp<P extends ComponentProps = {}>(component: ComponentFn<P>, target: string | Element): {
138
+ root: Component<{}> | undefined;
139
+ unmount: () => void;
140
+ } | undefined;
141
+ declare function hydrate<P extends ComponentProps = {}>(component: ComponentFn<P>, target: string | Element): {
142
+ root: Component<{}> | undefined;
143
+ unmount: () => void;
144
+ } | undefined;
176
145
 
177
- /**
178
- * Lifecycle hook type: returns void or a Promise that resolves when complete.
179
- * Hooks can perform cleanup by returning a cleanup function.
180
- */
181
146
  type LifecycleHook = () => void | Promise<void>;
182
147
  /**
183
148
  * Register a mount lifecycle hook.
184
- * Runs after component is mounted and virtual tree is committed.
185
- * If the scope is already mounted, the hook executes immediately.
149
+ * If the scope is already mounted, the hook is executed immediately.
186
150
  *
187
- * @throws Error in dev mode if called outside a scope
188
- * @example
189
- * ```tsx
190
- * onMount(() => {
191
- * console.log('Component mounted');
192
- * return () => console.log('Cleanup');
193
- * });
194
- * ```
151
+ * @param hook - The hook function to register.
152
+ * @returns {void}
195
153
  */
196
154
  declare function onMount(hook: LifecycleHook): void;
197
155
  /**
198
156
  * Register an update lifecycle hook.
199
- * Runs whenever the component re-renders due to prop or state changes.
200
157
  *
201
- * @throws Error in dev mode if called outside a scope
202
- * @example
203
- * ```tsx
204
- * onUpdate(() => {
205
- * console.log('Component updated');
206
- * });
207
- * ```
158
+ * @param hook - The hook function to register.
159
+ * @returns {void}
208
160
  */
209
161
  declare function onUpdate(hook: LifecycleHook): void;
210
162
  /**
211
163
  * Register a destroy lifecycle hook.
212
- * Runs before scope is disposed and resources are cleaned up.
213
- * Perfect for resetting external state, unsubscribing from events, etc.
214
164
  *
215
- * @throws Error in dev mode if called outside a scope
216
- * @example
217
- * ```tsx
218
- * onDestroy(() => {
219
- * unsubscribe();
220
- * clearTimeout(timerId);
221
- * });
222
- * ```
165
+ * @param hook - The hook function to register.
166
+ * @returns {void}
223
167
  */
224
168
  declare function onDestroy(hook: LifecycleHook): void;
225
169
 
226
170
  /**
227
- * Add event listener with automatic cleanup on scope destruction
171
+ * Modifiers supported by `bind:*` two-way bindings.
172
+ *
173
+ * - `trim` strip surrounding whitespace from string values
174
+ * - `number` coerce numeric strings to numbers (no-op on `NaN`)
175
+ * - `lazy` commit on `change` instead of `input`
176
+ */
177
+ interface BindModifiers {
178
+ trim?: boolean;
179
+ number?: boolean;
180
+ lazy?: boolean;
181
+ [key: string]: boolean | undefined;
182
+ }
183
+ /**
184
+ * Synchronizes a DOM element property with a model getter and setter.
185
+ *
186
+ * @param node The element to bind. `null` is tolerated (no-op).
187
+ * @param prop Bound property: `value` / `checked` / `files` / arbitrary.
188
+ * @param getter Reactive getter or static initial value.
189
+ * @param setter Receives the new value when the user edits the DOM.
190
+ * @param modifiers Optional `BindModifiers`.
191
+ */
192
+ declare function bindElement(node: Element | null, prop: 'value' | 'checked' | 'files' | string, getter: (() => unknown) | unknown, setter: (value: unknown) => void, modifiers?: BindModifiers): void;
193
+
194
+ /**
195
+ * Set up event delegation for specified event types.
196
+ *
197
+ * @param eventNames - Array of event names to delegate.
198
+ * @param document - Document to attach events to (defaults to window.document).
199
+ */
200
+ declare function delegateEvents(eventNames: string[], document?: Document): void;
201
+ /**
202
+ * Clear all delegated events from document.
228
203
  *
229
- * @param element - Element to attach listener to
230
- * @param event - Event name
231
- * @param handler - Event handler function
232
- * @param options - Event listener options
204
+ * @param document - Document to clear events from (defaults to window.document).
205
+ */
206
+ declare function clearDelegatedEvents(document?: Document): void;
207
+ /**
208
+ * Registers an event listener and scopes its cleanup when needed.
209
+ *
210
+ * @param element - The element to add the listener to.
211
+ * @param event - The event name.
212
+ * @param handler - The event handler.
213
+ * @param options - Optional event listener options.
214
+ * @returns {void}
233
215
  */
234
216
  declare function addEventListener(element: Element, event: string, handler: EventListener, options?: AddEventListenerOptions): void;
217
+
235
218
  /**
236
- * Bind an element to a setter function for two-way data binding
219
+ * Omits props from a target object using a proxy.
237
220
  *
238
- * @param node - The element to bind
239
- * @param key - The property key (unused, kept for API compatibility)
240
- * @param defaultValue - Default value (unused, kept for API compatibility)
241
- * @param setter - The setter function to call when the element's value changes
221
+ * @param target - The target object.
222
+ * @param keys - The keys to omit.
223
+ * @returns A proxy that omits specified keys.
242
224
  */
243
- declare function bindElement(node: Element, key: string, defaultValue: unknown, setter: (value: unknown) => void): void;
225
+ declare function omitProps<T extends object, K extends keyof T>(target: T, keys: K[]): Omit<T, K>;
226
+
244
227
  /**
245
228
  * Reactive node insertion with binding support
246
229
  *
247
230
  * @param parent Parent node
248
231
  * @param nodeFactory Node factory function or static node
249
232
  * @param before Reference node for insertion position
250
- *
251
233
  * @example
252
234
  * ```typescript
253
235
  * insert(container, () => message.value, null);
@@ -255,212 +237,203 @@ declare function bindElement(node: Element, key: string, defaultValue: unknown,
255
237
  * insert(container, "Hello World", null); // Direct string support
256
238
  * ```
257
239
  */
258
- declare function insert(parent: Node, nodeFactory: AnyNode, before?: Node): AnyNode[] | undefined;
240
+ declare function insert(parent: Node, nodeFactory: AnyNode, before?: Node): Node[] | undefined;
259
241
  /**
260
- * Map nodes from template by indexes
242
+ * Returns the first child of a node.
261
243
  *
262
- * @param template - Template node to traverse
263
- * @param indexes - Array of indexes to map
264
- * @returns Array of mapped nodes
244
+ * @param node - The node to get the child from.
245
+ * @returns The first child node or null.
265
246
  */
266
- declare function mapNodes(template: Node, indexes: number[]): Node[];
267
-
247
+ declare function child(node: Node | null): Node | null;
268
248
  /**
269
- * Set up event delegation for specified event types
270
- * @param {string[]} eventNames - Array of event names to delegate
271
- * @param {Document} document - Document to attach events to (defaults to window.document)
272
- */
273
- declare function delegateEvents(eventNames: string[], document?: Document): void;
274
-
275
- /**
276
- * Create a reactive proxy that excludes specified properties
249
+ * Returns the next sibling after advancing by `step`.
277
250
  *
278
- * @param target - The original reactive object
279
- * @param keys - List of property names to exclude
280
- * @returns A reactive proxy with specified properties excluded
251
+ * @param node - The starting node.
252
+ * @param step - Number of steps to advance.
253
+ * @returns The resulting sibling node or null.
281
254
  */
282
- declare function omitProps<T extends object, K extends keyof T>(target: T, keys: K[]): Omit<T, K>;
283
-
255
+ declare function next(node: Node | null, step?: number): Node | null;
284
256
  /**
285
- * DOM manipulation utilities
257
+ * Returns the child node at the requested index.
258
+ *
259
+ * @param node - The parent node.
260
+ * @param index - The child index.
261
+ * @returns The child node at index or null.
286
262
  */
263
+ declare function nthChild(node: Node | null, index: number): Node | null;
287
264
 
288
265
  /**
289
- * Remove node from its parent
266
+ * Returns a new hydration key.
290
267
  *
291
- * @param node - Node to remove
268
+ * @returns The new hydration key as a string.
292
269
  */
293
- declare function removeNode(node: AnyNode): void;
270
+ declare function getHydrationKey(): string;
294
271
  /**
295
- * Insert child node into parent
296
- * Handles both component nodes and DOM nodes
272
+ * Resets the client-side hydration key counter.
297
273
  *
298
- * @param parent - Parent node
299
- * @param child - Child node to insert
300
- * @param before - Reference node for insertion position
274
+ * @returns {void}
301
275
  */
302
- declare function insertNode(parent: Node, child: AnyNode, before?: AnyNode): void;
276
+ declare function resetHydrationKey(): void;
303
277
  /**
304
- * Replace child node with a new node
305
- * Handles both component nodes and DOM nodes
278
+ * Returns whether the runtime is currently in the hydration first-pass.
306
279
  *
307
- * @param parent - Parent node
308
- * @param newNode - New node to insert
309
- * @param oldNode - Old node to be replaced
280
+ * @returns True while hydrating, false otherwise.
310
281
  */
311
- declare function replaceNode(parent: Node, newNode: AnyNode, oldNode: AnyNode): void;
282
+ declare function isHydrating(): boolean;
283
+ /** Pop the next call-site anchor comment, or `null` if exhausted. */
284
+ declare function consumeTeleportAnchor(): Comment | null;
285
+ /** Pop the next teleport block from `target`, returning start/end markers and inner nodes. */
286
+ declare function consumeTeleportBlock(target: Element): {
287
+ start: Comment;
288
+ end: Comment;
289
+ nodes: Node[];
290
+ } | null;
312
291
  /**
313
- * Get the first DOM node from a node or component
292
+ * Begins hydration.
314
293
  *
315
- * @param node - Node or component
316
- * @returns The first DOM node or undefined
317
- */
318
- declare function getFirstDOMNode(node: AnyNode): Node | undefined;
319
-
320
- /**
321
- * Node normalization and comparison utilities
294
+ * @param root - The root element to hydrate.
322
295
  */
323
-
296
+ declare function beginHydration(root: Element): void;
324
297
  /**
325
- * Normalize node for reconciliation
326
- * Converts primitives to text nodes
298
+ * Ends hydration.
327
299
  *
328
- * @param node - Node to normalize
329
- * @returns Normalized DOM node
300
+ * @returns {void}
330
301
  */
331
- declare function normalizeNode(node: unknown): Node;
302
+ declare function endHydration(): void;
332
303
  /**
333
- * Check if two nodes are the same (for reconciliation)
334
- * Combines key check and type check
304
+ * Returns a factory function that, when called at component render time:
305
+ * - During hydration: increments the key, looks up the pre-built registry,
306
+ * and returns the ACTUAL SSR DOM node (no cloneNode).
307
+ * - During CSR: clones the parsed template as usual.
335
308
  *
336
- * @param a - First node
337
- * @param b - Second node
338
- * @returns true if nodes are considered the same
309
+ * @param html - The HTML template string.
310
+ * @returns A factory function that returns a DOM element.
339
311
  */
340
- declare function isSameNode(a: AnyNode, b: AnyNode): boolean;
312
+ declare function getRenderedElement(html: string): () => Element;
341
313
  /**
342
- * Shallow compare two objects or arrays
343
- * Performs strict equality check on top-level properties
314
+ * Patches class while considering hydration state.
344
315
  *
345
- * @param a - First value to compare
346
- * @param b - Second value to compare
347
- * @returns true if values are shallowly equal
348
- */
349
- declare function shallowCompare(a: unknown, b: unknown): boolean;
350
-
351
- /**
352
- * Get rendered element by hydration key or create from template
353
- * @param {string} temp - the template string
354
- * @returns {Function} a function that returns the element
316
+ * @param el - The element to patch.
317
+ * @param prev - Previous class value.
318
+ * @param next - Next class value.
319
+ * @param isSVG - Whether the element is an SVG element.
355
320
  */
356
- declare function getRenderedElement(temp: string): () => Node | null;
321
+ declare function patchClassHydrate(el: Element, prev: unknown, next: unknown, isSVG?: boolean): void;
357
322
  /**
358
- * Maps server-side rendered nodes during hydration
359
- * @param {HTMLElement} templateEl - The root template element
360
- * @param {number[]} idx - Array of indices to map
361
- * @returns {Node[]} Array of mapped nodes
323
+ * Patches attribute while considering hydration state.
324
+ *
325
+ * @param el - The element to patch.
326
+ * @param key - The attribute key.
327
+ * @param prev - Previous attribute value.
328
+ * @param next - Next attribute value.
362
329
  */
363
- declare function mapSSRNodes(templateEl: HTMLElement, idx: number[]): Node[];
330
+ declare function patchAttrHydrate(el: Element, key: string, prev: unknown, next: unknown): void;
364
331
  /**
365
- * Hydrate a server-rendered component
366
- * @param {ComponentFn} component - Component function to hydrate
367
- * @param {HTMLElement | string} container - Container element or selector
368
- * @returns {any} Component instance or undefined if hydration fails
332
+ * Patches style while considering hydration state.
333
+ *
334
+ * @param el - The element to patch.
335
+ * @param prev - Previous style value.
336
+ * @param next - Next style value.
369
337
  */
370
- declare function hydrate(component: ComponentFn, container: HTMLElement | string): any;
338
+ declare function patchStyleHydrate(el: HTMLElement, prev: unknown, next?: unknown): void;
371
339
 
372
340
  /**
373
- * Start hydration mode
374
- * Called when beginning client-side hydration of server-rendered content
375
- */
376
- declare function startHydration(): void;
377
- /**
378
- * End hydration mode
379
- * Called when hydration is complete
380
- */
381
- declare function endHydration(): void;
382
- /**
383
- * Check if hydration is currently active
384
- * @returns true if hydration is in progress
385
- */
386
- declare function isHydrating(): boolean;
387
- /**
388
- * Get the hydration key
389
- * @returns the hydration key string
341
+ * Applies a minimal class update to an element.
342
+ *
343
+ * Class values are normalized into a string first, then written through
344
+ * `className` or `setAttribute('class')` depending on the element type.
345
+ * Hydration stays silent and reuses the server-rendered result by default.
346
+ *
347
+ * @param el - The element to patch.
348
+ * @param prev - Previous class value.
349
+ * @param next - Next class value.
350
+ * @param isSVG - Whether the element is an SVG element.
351
+ * @returns {void}
390
352
  */
391
- declare function getHydrationKey(): string;
353
+ declare function patchClass(el: Element, prev: unknown, next: unknown, isSVG?: boolean): void;
392
354
  /**
393
- * Reset the hydration key counter
355
+ * Normalizes supported class inputs into a single string.
394
356
  */
395
- declare function resetHydrationKey(): void;
357
+ declare const normalizeClass: typeof normalizeClassName;
396
358
 
397
359
  /**
398
- * Patches the class attribute of an element
399
- * Supports silent hydration (skips DOM updates during hydration phase)
360
+ * Applies a minimal style update to an element.
361
+ *
362
+ * Supports both string-based and object-based styles, while staying silent
363
+ * during hydration so the server-rendered DOM can be reused.
400
364
  *
401
- * @param el - The element to patch classes on
402
- * @param prev - Previous class value for diffing
403
- * @param next - New class value to apply
404
- * @param isSVG - Whether the element is an SVG element
405
- * @public
365
+ * @param el - The element to patch.
366
+ * @param prev - Previous style value.
367
+ * @param next - Next style value.
368
+ * @returns {void}
406
369
  */
407
- declare function patchClass(el: Element, prev: unknown, next: unknown, isSVG?: boolean): void;
370
+ declare function patchStyle(el: HTMLElement, prev: unknown, next?: unknown): void;
408
371
  /**
409
- * Normalizes different class value formats into a single string
410
- * Re-exports normalizeClassName from shared as normalizeClass for backward compatibility
372
+ * Sets a single style property.
373
+ *
374
+ * Centralizes array-value expansion, CSS variable writes, vendor-prefix
375
+ * resolution, and `!important` handling in one place.
411
376
  *
412
- * @param value - The class value to normalize
413
- * @returns A normalized class string
414
- * @public
377
+ * @param style - Target style object.
378
+ * @param name - Style property name.
379
+ * @param val - Style value.
380
+ * @private
415
381
  */
416
- declare function normalizeClass(value: unknown): string;
382
+ declare function setStyle(style: CSSStyleDeclaration, name: string, val: string | string[]): void;
417
383
 
418
384
  /**
419
- * Patches the style of an element, optimized for different style formats
420
- * Supports silent hydration (skips DOM updates during hydration phase)
385
+ * Supported value types for the attribute patch layer.
421
386
  *
422
- * @param el - The element to patch styles on
423
- * @public
387
+ * In addition to primitive values, spread objects are also accepted, so the
388
+ * type keeps `Record<string, unknown>`.
424
389
  */
425
- declare function patchStyle(el: HTMLElement, prev: unknown, next: unknown): void;
390
+ type AttrValue = string | boolean | number | null | undefined | Record<string, unknown>;
426
391
  /**
427
- * Sets an individual style property with various optimizations
392
+ * Applies a minimal attribute update to an element.
428
393
  *
429
- * @param style - The style object to modify
430
- * @param name - The style property name
431
- * @param val - The style property value
432
- * @private
394
+ * This is the most general-purpose attribute updater in the template runtime.
395
+ * It is responsible for:
396
+ * - skipping internal reserved fields;
397
+ * - expanding spread attribute objects;
398
+ * - handling boolean attributes and SVG / xlink / xmlns namespaces;
399
+ * - applying basic dangerous-URL protection;
400
+ * - refusing raw HTML sinks such as `innerHTML` / `srcdoc`;
401
+ * - staying silent during hydration to avoid overwriting SSR DOM.
402
+ *
403
+ * @param el - The element to patch.
404
+ * @param key - The attribute key.
405
+ * @param prev - Previous attribute value.
406
+ * @param next - Next attribute value.
433
407
  */
434
- declare function setStyle(style: CSSStyleDeclaration, name: string, val: string | string[]): void;
435
-
436
- type AttrValue = string | boolean | number | null | undefined | Record<string, unknown>;
437
408
  declare function patchAttr(el: Element, key: string, prev: AttrValue, next: AttrValue): void;
438
409
 
439
410
  /**
440
- * Extended event options with delegation support
441
- * @public
411
+ * Extended listener options with optional event delegation support.
442
412
  */
443
413
  interface EventOptions extends AddEventListenerOptions {
444
414
  /**
445
- * CSS selector for event delegation
446
- * When provided, the event will only trigger if the target matches this selector
415
+ * Selector used for event delegation.
416
+ *
417
+ * When provided, the handler only runs if the event target matches the selector.
447
418
  */
448
419
  delegate?: string;
449
420
  }
450
421
  /**
451
- * Event handler cleanup function
452
- * @public
422
+ * Cleanup function signature for event listeners.
453
423
  */
454
424
  type EventCleanup = () => void;
455
425
  /**
456
- * Adds an event listener to an element with optional delegation
426
+ * Adds an event listener to an element with optional simple delegation.
427
+ *
428
+ * Without `delegate`, this is a thin wrapper around native `addEventListener()`.
429
+ * When `delegate` is provided, the runtime first checks the selector match before
430
+ * dispatching the real handler to the caller.
457
431
  *
458
- * @param el - The element to attach the event to
459
- * @param event - The event name (e.g., 'click', 'input')
460
- * @param handler - The event handler function
461
- * @param options - Additional event options including delegation
462
- * @returns A cleanup function to remove the event listener
463
- * @public
432
+ * @param el - The element to add the listener to.
433
+ * @param event - The name of the event to listen for.
434
+ * @param handler - The event handler function.
435
+ * @param options - Optional event listener options.
436
+ * @returns A cleanup function that removes the listener.
464
437
  */
465
438
  declare function addEvent(el: Element, event: string, handler: EventListener, options?: EventOptions): EventCleanup;
466
439
 
@@ -468,15 +441,15 @@ interface FragmentProps extends ComponentProps {
468
441
  children?: AnyNode | AnyNode[];
469
442
  }
470
443
  /**
471
- * Fragment component - renders multiple children without wrapper elements (Client-side only)
444
+ * Fragment component - renders multiple children without wrapper elements (Client-side only).
472
445
  *
473
446
  * **Client-side behavior:**
474
- * - Returns children directly for rendering
475
- * - Hydration system matches children using hydration keys
476
- * - The template system handles array children automatically
447
+ * - Returns children directly for rendering.
448
+ * - Hydration system matches children using hydration keys.
449
+ * - The template system handles array children automatically.
477
450
  *
478
- * @param props - Component props with children
479
- * @returns Children directly without wrapper
451
+ * @param props - Component props with children.
452
+ * @returns {AnyNode} Children directly without wrapper.
480
453
  *
481
454
  * @example
482
455
  * ```tsx
@@ -502,51 +475,66 @@ interface FragmentProps extends ComponentProps {
502
475
  declare function Fragment(props?: FragmentProps): AnyNode;
503
476
  declare namespace Fragment { }
504
477
  /**
505
- * Check if a node is a Fragment component
506
- * @param node - Node to check
507
- * @returns true if node is a Fragment
478
+ * Check if a node is a Fragment component.
479
+ *
480
+ * @param node - Node to check.
481
+ * @returns {boolean} True if node is a Fragment.
508
482
  */
509
483
  declare function isFragment(node: unknown): boolean;
510
484
 
511
485
  interface PortalProps {
486
+ /** Children to render at the target location. */
512
487
  children?: AnyNode | AnyNode[];
513
- target: string | HTMLElement;
514
- key?: string;
488
+ /**
489
+ * Mount target — CSS selector string, `Element`, or reactive getter.
490
+ * When the getter result changes, the Portal re-mounts at the new target.
491
+ */
492
+ target?: string | Element | (() => string | Element | null | undefined);
493
+ /**
494
+ * When truthy, children render inline at the call site instead of being teleported.
495
+ * May be a static value or a reactive getter.
496
+ */
497
+ disabled?: boolean | (() => boolean);
515
498
  }
516
499
  /**
517
- * Portal component - renders children into a different DOM node
500
+ * Portal teleports children into a different DOM node.
501
+ *
502
+ * - `disabled=true` renders children inline at the call site.
503
+ * - Otherwise children are teleported into `target`.
504
+ * - Both `target` and `disabled` may be reactive; changes trigger re-mount.
518
505
  *
519
- * @param props - Component props with children and target
520
- * @returns Comment node as placeholder in parent tree
506
+ * @returns A placeholder comment node that marks the call site.
521
507
  *
522
508
  * @example
523
509
  * ```tsx
524
- * <Portal target="#modal-root">
510
+ * <Portal target="#modal-root" disabled={isMobile}>
525
511
  * <div>Modal content</div>
526
512
  * </Portal>
513
+ * ```
527
514
  */
528
- declare function Portal(props: PortalProps): Comment | string;
515
+ declare function Portal(props: PortalProps): Comment;
529
516
  declare namespace Portal { }
530
517
  /**
531
- * Check if a node is a Portal component
532
- * @param node - Node to check
533
- * @returns true if node is a Portal
518
+ * Check if a node is a Portal component.
519
+ *
520
+ * @param node - Node to check.
521
+ * @returns True if node is a Portal.
534
522
  */
535
523
  declare function isPortal(node: unknown): boolean;
536
524
 
537
525
  interface SuspenseProps {
538
526
  /** The content to render. Can be a Promise for async loading. */
539
- children?: AnyNode | AnyNode[] | Promise<AnyNode | AnyNode[]>;
527
+ children?: Node | Node[] | Promise<Node | Node[]>;
540
528
  /** Fallback content to display while children is loading (Promise pending). */
541
- fallback?: AnyNode;
529
+ fallback?: Node;
542
530
  /** Optional key for reconciliation. */
543
531
  key?: string;
544
532
  }
545
533
  /**
546
- * Suspense component - handles async content with a fallback UI
534
+ * Suspense component - handles async content with a fallback UI.
547
535
  *
548
- * @param props - Component props with children, fallback, and optional key
549
- * @returns Placeholder node or fallback content
536
+ * @param props - Component props with children, fallback, and optional key.
537
+ * @returns {AnyNode} Placeholder node or fallback content.
550
538
  *
551
539
  * @example
552
540
  * ```tsx
@@ -555,12 +543,13 @@ interface SuspenseProps {
555
543
  * </Suspense>
556
544
  * ```
557
545
  */
558
- declare function Suspense(props: SuspenseProps): AnyNode;
546
+ declare function Suspense(props: SuspenseProps): Node;
559
547
  declare namespace Suspense { }
560
548
  /**
561
- * Check if a node is a Suspense component
562
- * @param node - Node to check
563
- * @returns true if node is a Suspense
549
+ * Check if a node is a Suspense component.
550
+ *
551
+ * @param node - Node to check.
552
+ * @returns {boolean} True if node is a Suspense.
564
553
  */
565
554
  declare function isSuspense(node: unknown): boolean;
566
555
 
@@ -579,38 +568,102 @@ interface ResourceOptions<T> {
579
568
  initialValue?: T;
580
569
  }
581
570
  /**
582
- * Create a resource for async data fetching
583
- * Inspired by SolidJS createResource
571
+ * Create a resource for async data fetching.
572
+ * Inspired by SolidJS createResource.
584
573
  *
585
- * @param fetcher - Function that returns a Promise with the data
586
- * @param options - Optional configuration
587
- * @returns Tuple of [resource, actions]
574
+ * @param fetcher - Function that returns a Promise with the data.
575
+ * @param options - Optional configuration.
576
+ * @returns {[Resource<T>, ResourceActions<T>]} Tuple of [resource, actions].
577
+ */
578
+ declare function createResource<T>(fetcher: () => Promise<T>, options?: ResourceOptions<T>): [Resource<T>, ResourceActions<T>];
579
+
580
+ interface AsyncComponentOptions {
581
+ /**
582
+ * Component to render while the async component is loading.
583
+ * Only shown after `delay` ms has elapsed (prevents flash of loading state).
584
+ */
585
+ loading?: ComponentFn;
586
+ /**
587
+ * Component to render when the async component fails to load.
588
+ * Receives `{ error, retry }` as props.
589
+ */
590
+ error?: ComponentFn<{
591
+ error: Error;
592
+ retry: () => void;
593
+ }>;
594
+ /**
595
+ * Delay in ms before showing the `loading` component (default: 200).
596
+ */
597
+ delay?: number;
598
+ /**
599
+ * Timeout in ms. If loading exceeds this, the error component is shown.
600
+ */
601
+ timeout?: number;
602
+ /**
603
+ * SSR rendering strategy (default: `'blocking'`).
604
+ *
605
+ * - `'blocking'` — Pre-calls the loader at definition time. If the module
606
+ * resolves before the component body runs (e.g. pre-loaded via
607
+ * `renderToStringAsync`), the real component is inlined in the HTML.
608
+ * - `'client-only'` — Renders `null` on the server; loads only in browser.
609
+ */
610
+ ssr?: 'blocking' | 'client-only';
611
+ /**
612
+ * Called when loading fails. Useful for logging or error tracking.
613
+ */
614
+ onError?: (error: Error, retry: () => void) => void;
615
+ }
616
+ type LoaderResult<P> = {
617
+ default: ComponentFn<P>;
618
+ } | ComponentFn<P>;
619
+ /**
620
+ * Define an async (lazy-loaded) component.
588
621
  *
589
- * @example
590
- * ```typescript
591
- * const [data, { refetch, mutate }] = createResource(
592
- * () => fetch('/api/user').then(r => r.json()),
593
- * { initialValue: null }
594
- * );
622
+ * Compatible with client, SSR, and SSG. Integrates with `<Suspense>` via
623
+ * `SuspenseContext` when rendered inside a Suspense boundary.
595
624
  *
596
- * // Access data
597
- * console.log(data());
598
- * console.log(data.loading.value);
599
- * console.log(data.state.value);
600
625
  *
601
- * // Refetch data
602
- * await refetch();
626
+ * @param loader - The async loader function.
627
+ * @param options - Configuration options.
628
+ * @returns {ComponentFn<P>} The async component wrapper function.
603
629
  *
604
- * // Update data directly
605
- * mutate({ name: 'John' });
630
+ * @example
631
+ * ```tsx
632
+ * // Simple
633
+ * const Chart = defineAsyncComponent(() => import('./Chart'));
634
+ *
635
+ * // With options
636
+ * const Chart = defineAsyncComponent(
637
+ * () => import('./Chart'),
638
+ * {
639
+ * loading: () => <Spinner />,
640
+ * error: ({ error, retry }) => (
641
+ * <div>
642
+ * <p>{error.message}</p>
643
+ * <button onClick={retry}>Retry</button>
644
+ * </div>
645
+ * ),
646
+ * delay: 200,
647
+ * timeout: 10_000,
648
+ * }
649
+ * );
650
+ *
651
+ * // Works standalone or inside Suspense
652
+ * function App() {
653
+ * return (
654
+ * <Suspense fallback={<div>Loading…</div>}>
655
+ * <Chart data={data} />
656
+ * </Suspense>
657
+ * );
658
+ * }
606
659
  * ```
607
660
  */
608
- declare function createResource<T>(fetcher: () => Promise<T>, options?: ResourceOptions<T>): [Resource<T>, ResourceActions<T>];
661
+ declare function defineAsyncComponent<P extends ComponentProps = ComponentProps>(loader: () => Promise<LoaderResult<P>>, options?: AsyncComponentOptions): ComponentFn<P>;
609
662
 
610
663
  interface ForProps<T> {
611
664
  each: T[] | Signal<T[]> | (() => T[]);
612
665
  children: (item: T, index: number) => AnyNode;
613
- keyFn?: (item: T) => unknown;
666
+ key?: (item: T) => unknown;
614
667
  fallback?: () => AnyNode;
615
668
  }
616
669
  /**
@@ -622,4 +675,4 @@ interface ForProps<T> {
622
675
  declare function For<T>(props: ForProps<T>): Node;
623
676
  declare namespace For { }
624
677
 
625
- export { Component, type ComponentFn, type ComponentProps, For, type ForProps, Fragment, type FragmentProps, type InjectionKey, Portal, type PortalProps, type Scope, Suspense, type SuspenseProps, addEvent, addEventListener, bindElement, createApp, createComponent, createResource, createScope, delegateEvents, disposeScope, endHydration, getActiveScope, getFirstDOMNode, getHydrationKey, getRenderedElement, hydrate, inject, insert, insertNode, isComponent, isFragment, isHydrating, isPortal, isSameNode, isSuspense, mapNodes, mapSSRNodes, normalizeClass, normalizeNode, omitProps, onCleanup, onDestroy, onMount, onUpdate, patchAttr, patchClass, patchStyle, provide, removeNode, replaceNode, resetHydrationKey, runWithScope, setActiveScope, setStyle, shallowCompare, startHydration, template };
678
+ export { type AsyncComponentOptions, Component, type ComponentFn, type ComponentProps, For, Fragment, Portal, Suspense, addEvent, addEventListener, beginHydration, bindElement, child, clearDelegatedEvents, consumeTeleportAnchor, consumeTeleportBlock, createApp, createComponent, createResource, defineAsyncComponent, delegateEvents, endHydration, getHydrationKey, getRenderedElement, hydrate, insert, isComponent, isFragment, isHydrating, isPortal, isSuspense, next, normalizeClass, nthChild, omitProps, onDestroy, onMount, onUpdate, patchAttr, patchAttrHydrate, patchClass, patchClassHydrate, patchStyle, patchStyleHydrate, resetHydrationKey, setStyle, template };