@estjs/template 0.0.15-beta.9 → 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,97 +1,22 @@
1
1
  import { Signal, Computed } from '@estjs/signals';
2
+ import { S as Scope } from './internal-Bz6h0aPa.js';
3
+ export { I as InjectionKey, i as inject, p as provide } from './internal-Bz6h0aPa.js';
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 {
8
- }
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
- /**
28
- * Scope represents an execution context in the component tree.
29
- * It manages provides, cleanup functions, and lifecycle hooks.
30
- */
31
- interface Scope {
32
- /** Unique identifier for debugging */
33
- readonly id: number;
34
- /** Parent scope in the hierarchy */
35
- parent: Scope | null;
36
- /** Child scopes (lazy initialized) */
37
- children: Set<Scope> | null;
38
- /** Provided values (lazy initialized) */
39
- provides: Map<InjectionKey<unknown> | string | number | symbol, unknown> | null;
40
- /** Cleanup functions (lazy initialized) */
41
- cleanup: Set<() => void> | null;
42
- /** Mount lifecycle hooks (lazy initialized) */
43
- onMount: Set<() => void | Promise<void>> | null;
44
- /** Update lifecycle hooks (lazy initialized) */
45
- onUpdate: Set<() => void | Promise<void>> | null;
46
- /** Destroy lifecycle hooks (lazy initialized) */
47
- onDestroy: Set<() => void | Promise<void>> | null;
48
- /** Whether the scope has been mounted */
49
- isMounted: boolean;
50
- /** Whether the scope has been destroyed */
51
- isDestroyed: boolean;
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
52
19
  }
53
- /**
54
- * Get the currently active scope.
55
- * @returns The active scope or null if none is active
56
- */
57
- declare function getActiveScope(): Scope | null;
58
- /**
59
- * Set the active scope (internal use).
60
- * @param scope - The scope to set as active
61
- */
62
- declare function setActiveScope(scope: Scope | null): void;
63
- /**
64
- * Create a new scope with optional parent.
65
- * If no parent is provided, uses the current active scope as parent.
66
- *
67
- * @param parent - Optional parent scope (defaults to active scope)
68
- * @returns A new scope instance
69
- */
70
- declare function createScope(parent?: Scope | null): Scope;
71
- /**
72
- * Run a function within a scope, ensuring proper cleanup.
73
- * The previous active scope is restored even if the function throws.
74
- *
75
- * @param scope - The scope to run within
76
- * @param fn - The function to execute
77
- * @returns The return value of the function
78
- */
79
- declare function runWithScope<T>(scope: Scope, fn: () => T): T;
80
- /**
81
- * Dispose a scope and all its children.
82
- * Children are disposed first (depth-first), then the scope itself.
83
- *
84
- * @param scope - The scope to dispose
85
- */
86
- declare function disposeScope(scope: Scope): void;
87
- /**
88
- * Register a cleanup function in the current scope.
89
- * The function will be called when the scope is disposed.
90
- *
91
- * @param fn - The cleanup function
92
- */
93
- declare function onCleanup(fn: () => void): void;
94
-
95
20
  declare enum COMPONENT_TYPE {
96
21
  NORMAL = "normal",
97
22
  FRAGMENT = "fragment",
@@ -104,53 +29,82 @@ type AnyNode = Node | Component<any> | Element | string | number | boolean | nul
104
29
  type ComponentProps = Record<string, unknown>;
105
30
  type ComponentFn<P = ComponentProps> = (props: P) => AnyNode;
106
31
 
107
- declare class Component<P extends ComponentProps = ComponentProps> {
108
- component: ComponentFn<P>;
32
+ declare class Component<P extends ComponentProps = {}> {
33
+ readonly component: ComponentFn<P>;
109
34
  props: P;
110
- protected renderedNodes: AnyNode[];
111
- protected scope: Scope | null;
112
- protected parentNode: Node | undefined;
35
+ readonly [COMPONENT_TYPE.NORMAL] = true;
36
+ scope: Scope | null;
37
+ state: COMPONENT_STATE;
113
38
  beforeNode: Node | undefined;
114
- private reactiveProps;
115
- readonly key: string | undefined;
116
- protected state: number;
117
- protected parentScope: Scope | null;
118
- private [COMPONENT_TYPE.NORMAL];
119
- get isConnected(): boolean;
120
- 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?;
121
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
+ */
122
52
  mount(parentNode: Node, beforeNode?: Node): AnyNode[];
123
- update(prevNode: Component<any>): Component<P>;
124
- forceUpdate(): Promise<void>;
125
53
  /**
126
- * Destroy component
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.
57
+ */
58
+ update(props: P): void;
59
+ /**
60
+ * Tear down and re-mount the component at its current insertion point.
61
+ * No-op if the component has never been mounted.
62
+ */
63
+ forceUpdate(): void;
64
+ /**
65
+ * Dispose the scope, remove all rendered nodes, and clear bookkeeping.
66
+ * Idempotent: subsequent calls are no-ops.
127
67
  */
128
68
  destroy(): void;
129
- 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;
130
84
  }
131
85
  /**
132
- * check if a node is a component
133
- * @param {unknown} node - the node to check
134
- * @returns {boolean} true if the node is a component, false otherwise
86
+ * Check if a value is a Component instance.
135
87
  */
136
88
  declare function isComponent(node: unknown): node is Component;
137
89
  /**
138
- * create a component
139
- * @param {Function} componentFn - the component function
140
- * @param {ComponentProps} props - the component props
141
- * @returns {Component} the component
90
+ * Wrap a component function in a Component instance, or pass an existing
91
+ * Component instance through unchanged.
142
92
  */
143
93
  declare function createComponent<P extends ComponentProps>(componentFn: ComponentFn<P>, props?: P): Component<P>;
144
94
 
145
95
  /**
146
- * Create a template factory function from HTML string
96
+ * Create a template factory function from HTML string.
147
97
  *
148
98
  * This function creates a reusable template factory that efficiently clones
149
- * 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.
100
+ *
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.
150
104
  *
151
- * @param html - The HTML string to create template from
152
- * @returns Factory function that returns a cloned node of the template
153
- * @throws {Error} When template content is empty or invalid
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.
154
108
  *
155
109
  * @example
156
110
  * ```typescript
@@ -161,14 +115,14 @@ declare function createComponent<P extends ComponentProps>(componentFn: Componen
161
115
  */
162
116
  declare function template(html: string): () => Node;
163
117
  /**
164
- * Create and mount an application with the specified component
118
+ * Create and mount an application with the specified component.
165
119
  *
166
120
  * This function initializes an application by mounting a root component
167
121
  * to a target DOM element. It handles target validation and cleanup.
168
122
  *
169
- * @param component - The root component function to mount
170
- * @param target - CSS selector string or DOM element to mount to
171
- * @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.
172
126
  *
173
127
  * @example
174
128
  * ```typescript
@@ -180,239 +134,306 @@ declare function template(html: string): () => Node;
180
134
  * const app = createApp(App, container);
181
135
  * ```
182
136
  */
183
- declare function createApp<P extends ComponentProps = {}>(component: ComponentFn<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;
184
145
 
185
146
  type LifecycleHook = () => void | Promise<void>;
186
147
  /**
187
148
  * Register a mount lifecycle hook.
188
- * Called after the component is mounted to the DOM.
149
+ * If the scope is already mounted, the hook is executed immediately.
189
150
  *
190
- * @param hook - The hook function to execute on mount
151
+ * @param hook - The hook function to register.
152
+ * @returns {void}
191
153
  */
192
154
  declare function onMount(hook: LifecycleHook): void;
155
+ /**
156
+ * Register an update lifecycle hook.
157
+ *
158
+ * @param hook - The hook function to register.
159
+ * @returns {void}
160
+ */
161
+ declare function onUpdate(hook: LifecycleHook): void;
193
162
  /**
194
163
  * Register a destroy lifecycle hook.
195
- * Called before the component is removed from the DOM.
196
164
  *
197
- * @param hook - The hook function to execute on destroy
165
+ * @param hook - The hook function to register.
166
+ * @returns {void}
198
167
  */
199
168
  declare function onDestroy(hook: LifecycleHook): void;
169
+
200
170
  /**
201
- * Register an update lifecycle hook.
202
- * Called after the component updates.
171
+ * Modifiers supported by `bind:*` two-way bindings.
203
172
  *
204
- * @param hook - The hook function to execute on update
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`
205
176
  */
206
- declare function onUpdate(hook: LifecycleHook): void;
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;
207
193
 
208
194
  /**
209
- * Add event listener with automatic cleanup on scope destruction
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.
210
203
  *
211
- * @param element - Element to attach listener to
212
- * @param event - Event name
213
- * @param handler - Event handler function
214
- * @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}
215
215
  */
216
216
  declare function addEventListener(element: Element, event: string, handler: EventListener, options?: AddEventListenerOptions): void;
217
+
217
218
  /**
218
- * Bind an element to a setter function for two-way data binding
219
+ * Omits props from a target object using a proxy.
219
220
  *
220
- * @param node - The element to bind
221
- * @param key - The property key (unused, kept for API compatibility)
222
- * @param defaultValue - Default value (unused, kept for API compatibility)
223
- * @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.
224
224
  */
225
- 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
+
226
227
  /**
227
228
  * Reactive node insertion with binding support
228
229
  *
229
- * @param parent - Parent node
230
- * @param nodeFactory - Node factory function or static node
231
- * @param before - Reference node for insertion position
232
- * @returns Array of rendered nodes
233
- *
230
+ * @param parent Parent node
231
+ * @param nodeFactory Node factory function or static node
232
+ * @param before Reference node for insertion position
234
233
  * @example
235
234
  * ```typescript
236
235
  * insert(container, () => message.value, null);
237
236
  * insert(container, staticElement, referenceNode);
238
- * insert(container, "Hello World", null);
237
+ * insert(container, "Hello World", null); // Direct string support
239
238
  * ```
240
239
  */
241
- declare function insert(parent: Node, nodeFactory: AnyNode, before?: Node): AnyNode[] | undefined;
240
+ declare function insert(parent: Node, nodeFactory: AnyNode, before?: Node): Node[] | undefined;
242
241
  /**
243
- * Map nodes from template by indexes
242
+ * Returns the first child of a node.
244
243
  *
245
- * @param template - Template node to traverse
246
- * @param indexes - Array of indexes to map
247
- * @returns Array of mapped nodes
244
+ * @param node - The node to get the child from.
245
+ * @returns The first child node or null.
248
246
  */
249
- declare function mapNodes(template: Node, indexes: number[]): Node[];
250
-
247
+ declare function child(node: Node | null): Node | null;
251
248
  /**
252
- * Set up event delegation for specified event types
253
- * @param {string[]} eventNames - Array of event names to delegate
254
- * @param {Document} document - Document to attach events to (defaults to window.document)
249
+ * Returns the next sibling after advancing by `step`.
250
+ *
251
+ * @param node - The starting node.
252
+ * @param step - Number of steps to advance.
253
+ * @returns The resulting sibling node or null.
255
254
  */
256
- declare function delegateEvents(eventNames: string[], document?: Document): void;
257
-
255
+ declare function next(node: Node | null, step?: number): Node | null;
258
256
  /**
259
- * Create a reactive proxy that excludes specified properties
257
+ * Returns the child node at the requested index.
260
258
  *
261
- * @param target - The original reactive object
262
- * @param keys - List of property names to exclude
263
- * @returns A reactive proxy with specified properties excluded
259
+ * @param node - The parent node.
260
+ * @param index - The child index.
261
+ * @returns The child node at index or null.
264
262
  */
265
- declare function omitProps<T extends object, K extends keyof T>(target: T, keys: K[]): Omit<T, K>;
263
+ declare function nthChild(node: Node | null, index: number): Node | null;
266
264
 
267
265
  /**
268
- * DOM manipulation utilities
266
+ * Returns a new hydration key.
267
+ *
268
+ * @returns The new hydration key as a string.
269
269
  */
270
-
270
+ declare function getHydrationKey(): string;
271
271
  /**
272
- * Remove node from its parent
272
+ * Resets the client-side hydration key counter.
273
273
  *
274
- * @param node - Node to remove
274
+ * @returns {void}
275
275
  */
276
- declare function removeNode(node: AnyNode): void;
276
+ declare function resetHydrationKey(): void;
277
277
  /**
278
- * Insert child node into parent
279
- * Handles both component nodes and DOM nodes
278
+ * Returns whether the runtime is currently in the hydration first-pass.
280
279
  *
281
- * @param parent - Parent node
282
- * @param child - Child node to insert
283
- * @param before - Reference node for insertion position
280
+ * @returns True while hydrating, false otherwise.
284
281
  */
285
- declare function insertNode(parent: Node, child: AnyNode, before?: 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;
286
291
  /**
287
- * Replace child node with a new node
288
- * Handles both component nodes and DOM nodes
292
+ * Begins hydration.
289
293
  *
290
- * @param parent - Parent node
291
- * @param newNode - New node to insert
292
- * @param oldNode - Old node to be replaced
294
+ * @param root - The root element to hydrate.
293
295
  */
294
- declare function replaceNode(parent: Node, newNode: AnyNode, oldNode: AnyNode): void;
296
+ declare function beginHydration(root: Element): void;
295
297
  /**
296
- * Get the first DOM node from a node or component
298
+ * Ends hydration.
297
299
  *
298
- * @param node - Node or component
299
- * @returns The first DOM node or undefined
300
+ * @returns {void}
300
301
  */
301
- declare function getFirstDOMNode(node: AnyNode): Node | undefined;
302
-
302
+ declare function endHydration(): void;
303
303
  /**
304
- * Node normalization and comparison utilities
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.
308
+ *
309
+ * @param html - The HTML template string.
310
+ * @returns A factory function that returns a DOM element.
305
311
  */
306
-
312
+ declare function getRenderedElement(html: string): () => Element;
307
313
  /**
308
- * Normalize node for reconciliation
309
- * Converts primitives to text nodes
314
+ * Patches class while considering hydration state.
310
315
  *
311
- * @param node - Node to normalize
312
- * @returns Normalized DOM node
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.
313
320
  */
314
- declare function normalizeNode(node: unknown): Node;
321
+ declare function patchClassHydrate(el: Element, prev: unknown, next: unknown, isSVG?: boolean): void;
315
322
  /**
316
- * Check if two nodes are the same (for reconciliation)
317
- * Combines key check and type check
323
+ * Patches attribute while considering hydration state.
318
324
  *
319
- * @param a - First node
320
- * @param b - Second node
321
- * @returns true if nodes are considered the same
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.
322
329
  */
323
- declare function isSameNode(a: AnyNode, b: AnyNode): boolean;
330
+ declare function patchAttrHydrate(el: Element, key: string, prev: unknown, next: unknown): void;
324
331
  /**
325
- * Shallow compare two objects
332
+ * Patches style while considering hydration state.
326
333
  *
327
- * @param a - First object
328
- * @param b - Second object
329
- * @returns true if objects are shallowly equal
334
+ * @param el - The element to patch.
335
+ * @param prev - Previous style value.
336
+ * @param next - Next style value.
330
337
  */
331
- declare function shallowCompare(a: any, b: any): boolean;
338
+ declare function patchStyleHydrate(el: HTMLElement, prev: unknown, next?: unknown): void;
332
339
 
333
340
  /**
334
- * Start hydration mode
335
- * Called when beginning client-side hydration of server-rendered content
336
- */
337
- declare function startHydration(): void;
338
- /**
339
- * End hydration mode
340
- * Called when hydration is complete
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}
341
352
  */
342
- declare function endHydration(): void;
353
+ declare function patchClass(el: Element, prev: unknown, next: unknown, isSVG?: boolean): void;
343
354
  /**
344
- * Check if hydration is currently active
345
- * @returns true if hydration is in progress
355
+ * Normalizes supported class inputs into a single string.
346
356
  */
347
- declare function isHydrating(): boolean;
357
+ declare const normalizeClass: typeof normalizeClassName;
348
358
 
349
359
  /**
350
- * Patches the class attribute of an element
351
- * 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.
352
364
  *
353
- * @param el - The element to patch classes on
354
- * @param prev - Previous class value for diffing
355
- * @param next - New class value to apply
356
- * @param isSVG - Whether the element is an SVG element
357
- * @public
365
+ * @param el - The element to patch.
366
+ * @param prev - Previous style value.
367
+ * @param next - Next style value.
368
+ * @returns {void}
358
369
  */
359
- declare function patchClass(el: Element, prev: unknown, next: unknown, isSVG?: boolean): void;
370
+ declare function patchStyle(el: HTMLElement, prev: unknown, next?: unknown): void;
360
371
  /**
361
- * Normalizes different class value formats into a single string
362
- * 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.
363
376
  *
364
- * @param value - The class value to normalize
365
- * @returns A normalized class string
366
- * @public
377
+ * @param style - Target style object.
378
+ * @param name - Style property name.
379
+ * @param val - Style value.
380
+ * @private
367
381
  */
368
- declare function normalizeClass(value: unknown): string;
382
+ declare function setStyle(style: CSSStyleDeclaration, name: string, val: string | string[]): void;
369
383
 
370
384
  /**
371
- * Patches the style of an element, optimized for different style formats
372
- * Supports silent hydration (skips DOM updates during hydration phase)
385
+ * Supported value types for the attribute patch layer.
373
386
  *
374
- * @param el - The element to patch styles on
375
- * @public
387
+ * In addition to primitive values, spread objects are also accepted, so the
388
+ * type keeps `Record<string, unknown>`.
376
389
  */
377
- declare function patchStyle(el: HTMLElement, prev: unknown, next: unknown): void;
390
+ type AttrValue = string | boolean | number | null | undefined | Record<string, unknown>;
378
391
  /**
379
- * Sets an individual style property with various optimizations
392
+ * Applies a minimal attribute update to an element.
380
393
  *
381
- * @param style - The style object to modify
382
- * @param name - The style property name
383
- * @param val - The style property value
384
- * @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.
385
407
  */
386
- declare function setStyle(style: CSSStyleDeclaration, name: string, val: string | string[]): void;
387
-
388
- type AttrValue = string | boolean | number | null | undefined | Record<string, unknown>;
389
408
  declare function patchAttr(el: Element, key: string, prev: AttrValue, next: AttrValue): void;
390
409
 
391
410
  /**
392
- * Extended event options with delegation support
393
- * @public
411
+ * Extended listener options with optional event delegation support.
394
412
  */
395
413
  interface EventOptions extends AddEventListenerOptions {
396
414
  /**
397
- * CSS selector for event delegation
398
- * 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.
399
418
  */
400
419
  delegate?: string;
401
420
  }
402
421
  /**
403
- * Event handler cleanup function
404
- * @public
422
+ * Cleanup function signature for event listeners.
405
423
  */
406
424
  type EventCleanup = () => void;
407
425
  /**
408
- * 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.
409
431
  *
410
- * @param el - The element to attach the event to
411
- * @param event - The event name (e.g., 'click', 'input')
412
- * @param handler - The event handler function
413
- * @param options - Additional event options including delegation
414
- * @returns A cleanup function to remove the event listener
415
- * @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.
416
437
  */
417
438
  declare function addEvent(el: Element, event: string, handler: EventListener, options?: EventOptions): EventCleanup;
418
439
 
@@ -420,72 +441,100 @@ interface FragmentProps extends ComponentProps {
420
441
  children?: AnyNode | AnyNode[];
421
442
  }
422
443
  /**
423
- * Fragment component - renders multiple children without wrapper elements
444
+ * Fragment component - renders multiple children without wrapper elements (Client-side only).
424
445
  *
425
- * @param props - Component props with children
426
- * @returns DocumentFragment containing all children
446
+ * **Client-side behavior:**
447
+ * - Returns children directly for rendering.
448
+ * - Hydration system matches children using hydration keys.
449
+ * - The template system handles array children automatically.
450
+ *
451
+ * @param props - Component props with children.
452
+ * @returns {AnyNode} Children directly without wrapper.
427
453
  *
428
454
  * @example
429
455
  * ```tsx
456
+ * // Basic usage
430
457
  * <Fragment>
431
458
  * <div>First</div>
432
459
  * <span>Second</span>
433
460
  * </Fragment>
461
+ *
462
+ * // Nested fragments
463
+ * <Fragment>
464
+ * <Fragment>
465
+ * <div>Nested 1</div>
466
+ * <div>Nested 2</div>
467
+ * </Fragment>
468
+ * <div>Third</div>
469
+ * </Fragment>
470
+ *
471
+ * // Empty fragment (renders nothing)
472
+ * <Fragment />
434
473
  * ```
435
474
  */
436
475
  declare function Fragment(props?: FragmentProps): AnyNode;
437
- declare namespace Fragment {
438
- var fragment: boolean;
439
- }
476
+ declare namespace Fragment { }
440
477
  /**
441
- * Check if a node is a Fragment component
442
- * @param node - Node to check
443
- * @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.
444
482
  */
445
483
  declare function isFragment(node: unknown): boolean;
446
484
 
447
485
  interface PortalProps {
486
+ /** Children to render at the target location. */
448
487
  children?: AnyNode | AnyNode[];
449
- target: string | HTMLElement;
450
- 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);
451
498
  }
452
499
  /**
453
- * 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.
454
505
  *
455
- * @param props - Component props with children and target
456
- * @returns Comment node as placeholder in parent tree
506
+ * @returns A placeholder comment node that marks the call site.
457
507
  *
458
508
  * @example
459
509
  * ```tsx
460
- * <Portal target="#modal-root">
510
+ * <Portal target="#modal-root" disabled={isMobile}>
461
511
  * <div>Modal content</div>
462
512
  * </Portal>
463
513
  * ```
464
514
  */
465
- declare function Portal(props: PortalProps): Comment | string;
466
- declare namespace Portal {
467
- var portal: boolean;
468
- }
515
+ declare function Portal(props: PortalProps): Comment;
516
+ declare namespace Portal { }
469
517
  /**
470
- * Check if a node is a Portal component
471
- * @param node - Node to check
472
- * @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.
473
522
  */
474
523
  declare function isPortal(node: unknown): boolean;
475
524
 
476
525
  interface SuspenseProps {
477
526
  /** The content to render. Can be a Promise for async loading. */
478
- children?: AnyNode | AnyNode[] | Promise<AnyNode | AnyNode[]>;
527
+ children?: Node | Node[] | Promise<Node | Node[]>;
479
528
  /** Fallback content to display while children is loading (Promise pending). */
480
- fallback?: AnyNode;
529
+ fallback?: Node;
481
530
  /** Optional key for reconciliation. */
482
531
  key?: string;
483
532
  }
484
533
  /**
485
- * Suspense component - handles async content with a fallback UI
534
+ * Suspense component - handles async content with a fallback UI.
486
535
  *
487
- * @param props - Component props with children, fallback, and optional key
488
- * @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.
489
538
  *
490
539
  * @example
491
540
  * ```tsx
@@ -494,14 +543,13 @@ interface SuspenseProps {
494
543
  * </Suspense>
495
544
  * ```
496
545
  */
497
- declare function Suspense(props: SuspenseProps): AnyNode;
498
- declare namespace Suspense {
499
- var suspense: boolean;
500
- }
546
+ declare function Suspense(props: SuspenseProps): Node;
547
+ declare namespace Suspense { }
501
548
  /**
502
- * Check if a node is a Suspense component
503
- * @param node - Node to check
504
- * @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.
505
553
  */
506
554
  declare function isSuspense(node: unknown): boolean;
507
555
 
@@ -520,32 +568,111 @@ interface ResourceOptions<T> {
520
568
  initialValue?: T;
521
569
  }
522
570
  /**
523
- * Create a resource for async data fetching
524
- * Inspired by SolidJS createResource
571
+ * Create a resource for async data fetching.
572
+ * Inspired by SolidJS createResource.
525
573
  *
526
- * @param fetcher - Function that returns a Promise with the data
527
- * @param options - Optional configuration
528
- * @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.
529
621
  *
530
- * @example
531
- * ```typescript
532
- * const [data, { refetch, mutate }] = createResource(
533
- * () => fetch('/api/user').then(r => r.json()),
534
- * { initialValue: null }
535
- * );
622
+ * Compatible with client, SSR, and SSG. Integrates with `<Suspense>` via
623
+ * `SuspenseContext` when rendered inside a Suspense boundary.
536
624
  *
537
- * // Access data
538
- * console.log(data());
539
- * console.log(data.loading.value);
540
- * console.log(data.state.value);
541
625
  *
542
- * // Refetch data
543
- * await refetch();
626
+ * @param loader - The async loader function.
627
+ * @param options - Configuration options.
628
+ * @returns {ComponentFn<P>} The async component wrapper function.
544
629
  *
545
- * // Update data directly
546
- * 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
+ * }
547
659
  * ```
548
660
  */
549
- 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>;
662
+
663
+ interface ForProps<T> {
664
+ each: T[] | Signal<T[]> | (() => T[]);
665
+ children: (item: T, index: number) => AnyNode;
666
+ key?: (item: T) => unknown;
667
+ fallback?: () => AnyNode;
668
+ }
669
+ /**
670
+ * Optimized For Component
671
+ * - Uses createDetachedScope to avoid parent.children Set overhead (SolidJS style)
672
+ * - Uses DocumentFragment batching for mass creation
673
+ * - Inlines scope switching for performance
674
+ */
675
+ declare function For<T>(props: ForProps<T>): Node;
676
+ declare namespace For { }
550
677
 
551
- export { Component, type ComponentFn, type ComponentProps, 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, inject, insert, insertNode, isComponent, isFragment, isHydrating, isPortal, isSameNode, isSuspense, mapNodes, normalizeClass, normalizeNode, omitProps, onCleanup, onDestroy, onMount, onUpdate, patchAttr, patchClass, patchStyle, provide, removeNode, replaceNode, 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 };