eleva 1.0.1 → 1.1.0

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 (97) hide show
  1. package/README.md +21 -10
  2. package/dist/{eleva-plugins.cjs.js → eleva-plugins.cjs} +1002 -292
  3. package/dist/eleva-plugins.cjs.map +1 -0
  4. package/dist/eleva-plugins.d.cts +1352 -0
  5. package/dist/eleva-plugins.d.cts.map +1 -0
  6. package/dist/eleva-plugins.d.ts +1352 -0
  7. package/dist/eleva-plugins.d.ts.map +1 -0
  8. package/dist/{eleva-plugins.esm.js → eleva-plugins.js} +1002 -292
  9. package/dist/eleva-plugins.js.map +1 -0
  10. package/dist/eleva-plugins.umd.js +1001 -291
  11. package/dist/eleva-plugins.umd.js.map +1 -1
  12. package/dist/eleva-plugins.umd.min.js +1 -1
  13. package/dist/eleva-plugins.umd.min.js.map +1 -1
  14. package/dist/{eleva.cjs.js → eleva.cjs} +421 -191
  15. package/dist/eleva.cjs.map +1 -0
  16. package/dist/eleva.d.cts +1329 -0
  17. package/dist/eleva.d.cts.map +1 -0
  18. package/dist/eleva.d.ts +473 -226
  19. package/dist/eleva.d.ts.map +1 -0
  20. package/dist/{eleva.esm.js → eleva.js} +422 -192
  21. package/dist/eleva.js.map +1 -0
  22. package/dist/eleva.umd.js +420 -190
  23. package/dist/eleva.umd.js.map +1 -1
  24. package/dist/eleva.umd.min.js +1 -1
  25. package/dist/eleva.umd.min.js.map +1 -1
  26. package/dist/plugins/attr.cjs +279 -0
  27. package/dist/plugins/attr.cjs.map +1 -0
  28. package/dist/plugins/attr.d.cts +101 -0
  29. package/dist/plugins/attr.d.cts.map +1 -0
  30. package/dist/plugins/attr.d.ts +101 -0
  31. package/dist/plugins/attr.d.ts.map +1 -0
  32. package/dist/plugins/attr.js +276 -0
  33. package/dist/plugins/attr.js.map +1 -0
  34. package/dist/plugins/attr.umd.js +111 -22
  35. package/dist/plugins/attr.umd.js.map +1 -1
  36. package/dist/plugins/attr.umd.min.js +1 -1
  37. package/dist/plugins/attr.umd.min.js.map +1 -1
  38. package/dist/plugins/router.cjs +1873 -0
  39. package/dist/plugins/router.cjs.map +1 -0
  40. package/dist/plugins/router.d.cts +1296 -0
  41. package/dist/plugins/router.d.cts.map +1 -0
  42. package/dist/plugins/router.d.ts +1296 -0
  43. package/dist/plugins/router.d.ts.map +1 -0
  44. package/dist/plugins/router.js +1870 -0
  45. package/dist/plugins/router.js.map +1 -0
  46. package/dist/plugins/router.umd.js +482 -186
  47. package/dist/plugins/router.umd.js.map +1 -1
  48. package/dist/plugins/router.umd.min.js +1 -1
  49. package/dist/plugins/router.umd.min.js.map +1 -1
  50. package/dist/plugins/store.cjs +920 -0
  51. package/dist/plugins/store.cjs.map +1 -0
  52. package/dist/plugins/store.d.cts +266 -0
  53. package/dist/plugins/store.d.cts.map +1 -0
  54. package/dist/plugins/store.d.ts +266 -0
  55. package/dist/plugins/store.d.ts.map +1 -0
  56. package/dist/plugins/store.js +917 -0
  57. package/dist/plugins/store.js.map +1 -0
  58. package/dist/plugins/store.umd.js +410 -85
  59. package/dist/plugins/store.umd.js.map +1 -1
  60. package/dist/plugins/store.umd.min.js +1 -1
  61. package/dist/plugins/store.umd.min.js.map +1 -1
  62. package/package.json +112 -68
  63. package/src/core/Eleva.js +195 -115
  64. package/src/index.cjs +10 -0
  65. package/src/index.js +11 -0
  66. package/src/modules/Emitter.js +68 -20
  67. package/src/modules/Renderer.js +82 -20
  68. package/src/modules/Signal.js +43 -15
  69. package/src/modules/TemplateEngine.js +50 -9
  70. package/src/plugins/Attr.js +121 -19
  71. package/src/plugins/Router.js +526 -181
  72. package/src/plugins/Store.js +448 -69
  73. package/src/plugins/index.js +1 -0
  74. package/types/core/Eleva.d.ts +263 -169
  75. package/types/core/Eleva.d.ts.map +1 -1
  76. package/types/index.d.cts +3 -0
  77. package/types/index.d.cts.map +1 -0
  78. package/types/index.d.ts +5 -0
  79. package/types/index.d.ts.map +1 -1
  80. package/types/modules/Emitter.d.ts +73 -30
  81. package/types/modules/Emitter.d.ts.map +1 -1
  82. package/types/modules/Renderer.d.ts +48 -18
  83. package/types/modules/Renderer.d.ts.map +1 -1
  84. package/types/modules/Signal.d.ts +44 -16
  85. package/types/modules/Signal.d.ts.map +1 -1
  86. package/types/modules/TemplateEngine.d.ts +46 -11
  87. package/types/modules/TemplateEngine.d.ts.map +1 -1
  88. package/types/plugins/Attr.d.ts +83 -16
  89. package/types/plugins/Attr.d.ts.map +1 -1
  90. package/types/plugins/Router.d.ts +498 -207
  91. package/types/plugins/Router.d.ts.map +1 -1
  92. package/types/plugins/Store.d.ts +211 -37
  93. package/types/plugins/Store.d.ts.map +1 -1
  94. package/dist/eleva-plugins.cjs.js.map +0 -1
  95. package/dist/eleva-plugins.esm.js.map +0 -1
  96. package/dist/eleva.cjs.js.map +0 -1
  97. package/dist/eleva.esm.js.map +0 -1
package/dist/eleva.umd.js CHANGED
@@ -1,22 +1,38 @@
1
- /*! Eleva v1.0.1 | MIT License | https://elevajs.com */
1
+ /*! Eleva v1.1.0 | MIT License | https://elevajs.com */
2
2
  (function (global, factory) {
3
3
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
4
4
  typeof define === 'function' && define.amd ? define(factory) :
5
5
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Eleva = factory());
6
6
  })(this, (function () { 'use strict';
7
7
 
8
+ /**
9
+ * @module eleva/template-engine
10
+ * @fileoverview Expression evaluator for directive attributes and property bindings.
11
+ */ // ============================================================================
12
+ // TYPE DEFINITIONS
8
13
  // ============================================================================
9
- // TYPE DEFINITIONS - TypeScript-friendly JSDoc types for IDE support
10
- // ============================================================================
14
+ // -----------------------------------------------------------------------------
15
+ // Data Types
16
+ // -----------------------------------------------------------------------------
11
17
  /**
18
+ * Data context object for expression evaluation.
12
19
  * @typedef {Record<string, unknown>} ContextData
13
- * Data context for expression evaluation
20
+ * @description Contains variables and functions available during template evaluation.
14
21
  */ /**
22
+ * JavaScript expression string to be evaluated.
15
23
  * @typedef {string} Expression
16
- * A JavaScript expression to be evaluated in the data context
24
+ * @description A JavaScript expression evaluated against a ContextData object.
17
25
  */ /**
26
+ * Result of evaluating an expression.
18
27
  * @typedef {unknown} EvaluationResult
19
- * The result of evaluating an expression (string, number, boolean, object, function, etc.)
28
+ * @description Can be string, number, boolean, object, function, or any JavaScript value.
29
+ */ // -----------------------------------------------------------------------------
30
+ // Function Types
31
+ // -----------------------------------------------------------------------------
32
+ /**
33
+ * Compiled expression function cached for performance.
34
+ * @typedef {(data: ContextData) => EvaluationResult} CompiledExpressionFunction
35
+ * @description Pre-compiled function that evaluates an expression against context data.
20
36
  */ /**
21
37
  * @class 🔒 TemplateEngine
22
38
  * @classdesc A minimal expression evaluator for Eleva's directive attributes.
@@ -48,16 +64,29 @@
48
64
  /**
49
65
  * Evaluates an expression in the context of the provided data object.
50
66
  * Used for resolving `@event` handlers and `:prop` bindings.
67
+ * Non-string expressions are returned as-is.
68
+ *
69
+ * @security CRITICAL SECURITY WARNING
70
+ * This method is NOT sandboxed. It uses `new Function()` and `with` statement,
71
+ * allowing full access to the global scope. Potential attack vectors include:
72
+ * - Code injection via malicious expressions
73
+ * - XSS attacks if user input is used as expressions
74
+ * - Access to sensitive globals (window, document, fetch, etc.)
75
+ *
76
+ * ONLY use with developer-defined template strings.
77
+ * NEVER use with user-provided input or untrusted data.
51
78
  *
52
- * Note: This does not provide a true sandbox and evaluated expressions may access global scope.
53
- * The use of the `with` statement is necessary for expression evaluation but has security implications.
54
- * Only use with trusted templates. User input should never be directly interpolated.
79
+ * Mitigation strategies:
80
+ * - Always sanitize any user-generated content before rendering in templates
81
+ * - Use Content Security Policy (CSP) headers to restrict script execution
82
+ * - Keep expressions simple (property access, method calls) - avoid complex logic
55
83
  *
56
84
  * @public
57
85
  * @static
58
- * @param {Expression|unknown} expression - The expression to evaluate.
86
+ * @param {Expression | unknown} expression - The expression to evaluate.
59
87
  * @param {ContextData} data - The data context for evaluation.
60
88
  * @returns {EvaluationResult} The result of the evaluation, or empty string if evaluation fails.
89
+ * @note Evaluation failures return an empty string without throwing.
61
90
  *
62
91
  * @example
63
92
  * // Property access
@@ -110,28 +139,51 @@
110
139
  /**
111
140
  * Cache for compiled expression functions.
112
141
  * Stores compiled Function objects keyed by expression string for O(1) lookup.
142
+ * The cache persists for the application lifetime and is never cleared.
143
+ * This improves performance for repeated evaluations of the same expression.
144
+ *
145
+ * Memory consideration: For applications with highly dynamic expressions
146
+ * (e.g., user-generated), memory usage grows unbounded. This is typically
147
+ * not an issue for static templates where expressions are finite.
113
148
  *
114
149
  * @static
115
150
  * @private
116
- * @type {Map<string, Function>}
151
+ * @type {Map<string, CompiledExpressionFunction>}
117
152
  */ TemplateEngine._functionCache = new Map();
118
153
 
154
+ /**
155
+ * @module eleva/signal
156
+ * @fileoverview Reactive Signal primitive for fine-grained state management and change notification.
157
+ */ // ============================================================================
158
+ // TYPE DEFINITIONS
119
159
  // ============================================================================
120
- // TYPE DEFINITIONS - TypeScript-friendly JSDoc types for IDE support
121
- // ============================================================================
160
+ // -----------------------------------------------------------------------------
161
+ // Callback Types
162
+ // -----------------------------------------------------------------------------
122
163
  /**
123
- * @template T
164
+ * Callback function invoked when a signal's value changes.
165
+ * @template T The type of value held by the signal.
124
166
  * @callback SignalWatcher
125
- * @param {T} value - The new value of the signal
167
+ * @param {T} value
168
+ * The new value of the signal.
126
169
  * @returns {void}
127
170
  */ /**
171
+ * Function to unsubscribe a watcher from a signal.
128
172
  * @callback SignalUnsubscribe
129
- * @returns {boolean} True if the watcher was successfully removed
130
- */ /**
131
- * @template T
173
+ * @returns {boolean}
174
+ * True if the watcher was successfully removed, false if already removed.
175
+ * Safe to call multiple times (idempotent).
176
+ */ // -----------------------------------------------------------------------------
177
+ // Interface Types
178
+ // -----------------------------------------------------------------------------
179
+ /**
180
+ * Interface describing the public API of a Signal.
181
+ * @template T The type of value held by the signal.
132
182
  * @typedef {Object} SignalLike
133
- * @property {T} value - The current value
134
- * @property {function(SignalWatcher<T>): SignalUnsubscribe} watch - Subscribe to changes
183
+ * @property {T} value
184
+ * The current value of the signal.
185
+ * @property {function(SignalWatcher<T>): SignalUnsubscribe} watch
186
+ * Subscribe to value changes.
135
187
  */ /**
136
188
  * @class ⚡ Signal
137
189
  * @classdesc A reactive data holder that enables fine-grained reactivity in the Eleva framework.
@@ -141,7 +193,7 @@
141
193
  * Render batching is handled at the component level, not the signal level.
142
194
  * The class is generic, allowing type-safe handling of any value type T.
143
195
  *
144
- * @template T The type of value held by this signal
196
+ * @template T The type of value held by the signal.
145
197
  *
146
198
  * @example
147
199
  * // Basic usage
@@ -159,7 +211,6 @@
159
211
  *
160
212
  * @example
161
213
  * // With objects
162
- * /** @type {Signal<{x: number, y: number}>} *\/
163
214
  * const position = new Signal({ x: 0, y: 0 });
164
215
  * position.value = { x: 10, y: 20 }; // Triggers watchers
165
216
  *
@@ -177,6 +228,10 @@
177
228
  * Sets a new value for the signal and synchronously notifies all registered watchers if the value has changed.
178
229
  * Synchronous notification preserves stack traces and ensures immediate value consistency.
179
230
  *
231
+ * Uses strict equality (===) for comparison. For objects/arrays, watchers are only notified
232
+ * if the reference changes, not if properties are mutated. To trigger updates with objects,
233
+ * assign a new reference: `signal.value = { ...signal.value, updated: true }`.
234
+ *
180
235
  * @public
181
236
  * @param {T} newVal - The new value to set.
182
237
  * @returns {void}
@@ -193,6 +248,8 @@
193
248
  * @public
194
249
  * @param {SignalWatcher<T>} fn - The callback function to invoke on value change.
195
250
  * @returns {SignalUnsubscribe} A function to unsubscribe the watcher.
251
+ * Returns true if watcher was removed, false if it wasn't registered.
252
+ * Safe to call multiple times (idempotent after first call).
196
253
  *
197
254
  * @example
198
255
  * // Basic watching
@@ -201,6 +258,7 @@
201
258
  * @example
202
259
  * // Stop watching
203
260
  * unsubscribe(); // Returns true if watcher was removed
261
+ * unsubscribe(); // Returns false (already removed, safe to call again)
204
262
  *
205
263
  * @example
206
264
  * // Multiple watchers
@@ -216,6 +274,9 @@
216
274
  * This preserves stack traces for debugging and ensures immediate
217
275
  * value consistency. Render batching is handled at the component level.
218
276
  *
277
+ * @note If a watcher throws, subsequent watchers are NOT called.
278
+ * The error propagates to the caller (the setter).
279
+ *
219
280
  * @private
220
281
  * @returns {void}
221
282
  */ _notify() {
@@ -225,6 +286,7 @@
225
286
  * Creates a new Signal instance with the specified initial value.
226
287
  *
227
288
  * @public
289
+ * @constructor
228
290
  * @param {T} value - The initial value of the signal.
229
291
  *
230
292
  * @example
@@ -234,12 +296,9 @@
234
296
  * const active = new Signal(true); // Signal<boolean>
235
297
  *
236
298
  * @example
237
- * // Complex types (use JSDoc for type inference)
238
- * /** @type {Signal<string[]>} *\/
239
- * const items = new Signal([]);
240
- *
241
- * /** @type {Signal<{id: number, name: string} | null>} *\/
242
- * const user = new Signal(null);
299
+ * // Complex types
300
+ * const items = new Signal([]); // Signal holding an array
301
+ * const user = new Signal(null); // Signal holding nullable object
243
302
  */ constructor(value){
244
303
  /**
245
304
  * Internal storage for the signal's current value.
@@ -254,25 +313,56 @@
254
313
  }
255
314
  }
256
315
 
316
+ /**
317
+ * @module eleva/emitter
318
+ * @fileoverview Event emitter for publish-subscribe communication between components.
319
+ */ // ============================================================================
320
+ // TYPE DEFINITIONS
257
321
  // ============================================================================
258
- // TYPE DEFINITIONS - TypeScript-friendly JSDoc types for IDE support
259
- // ============================================================================
322
+ // -----------------------------------------------------------------------------
323
+ // Callback Types
324
+ // -----------------------------------------------------------------------------
260
325
  /**
261
- * @template T
326
+ * Callback function invoked when an event is emitted.
262
327
  * @callback EventHandler
263
- * @param {...T} args - Event arguments
264
- * @returns {void|Promise<void>}
328
+ * @param {...any} args
329
+ * Event arguments passed to the handler.
330
+ * @returns {void | Promise<void>}
265
331
  */ /**
332
+ * Function to unsubscribe an event handler.
266
333
  * @callback EventUnsubscribe
267
334
  * @returns {void}
268
- */ /**
269
- * @typedef {`${string}:${string}`} EventName
270
- * Event names follow the format 'namespace:action' (e.g., 'user:login', 'cart:update')
271
- */ /**
335
+ */ // -----------------------------------------------------------------------------
336
+ // Event Types
337
+ // -----------------------------------------------------------------------------
338
+ /**
339
+ * Event name string identifier.
340
+ * @typedef {string} EventName
341
+ * @description
342
+ * Recommended convention: 'namespace:action' (e.g., 'user:login').
343
+ * This pattern prevents naming collisions and improves code readability.
344
+ *
345
+ * Common namespaces:
346
+ * - `user:` - User-related events (login, logout, update)
347
+ * - `component:` - Component lifecycle events (mount, unmount)
348
+ * - `router:` - Navigation events (beforeEach, afterEach)
349
+ * - `store:` - State management events (change, error)
350
+ * @example
351
+ * 'user:login' // User logged in
352
+ * 'cart:update' // Shopping cart updated
353
+ * 'component:mount' // Component was mounted
354
+ */ // -----------------------------------------------------------------------------
355
+ // Interface Types
356
+ // -----------------------------------------------------------------------------
357
+ /**
358
+ * Interface describing the public API of an Emitter.
272
359
  * @typedef {Object} EmitterLike
273
- * @property {function(string, EventHandler<unknown>): EventUnsubscribe} on - Subscribe to an event
274
- * @property {function(string, EventHandler<unknown>=): void} off - Unsubscribe from an event
275
- * @property {function(string, ...unknown): void} emit - Emit an event
360
+ * @property {(event: string, handler: EventHandler) => EventUnsubscribe} on
361
+ * Subscribe to an event.
362
+ * @property {(event: string, handler?: EventHandler) => void} off
363
+ * Unsubscribe from an event.
364
+ * @property {(event: string, ...args: unknown[]) => void} emit
365
+ * Emit an event with arguments.
276
366
  */ /**
277
367
  * @class 📡 Emitter
278
368
  * @classdesc A robust event emitter that enables inter-component communication through a publish-subscribe pattern.
@@ -310,6 +400,7 @@
310
400
  * // Lifecycle events
311
401
  * emitter.on('component:mount', (component) => {});
312
402
  * emitter.on('component:unmount', (component) => {});
403
+ * // Note: These lifecycle names are conventions; Eleva core does not emit them by default.
313
404
  * // State events
314
405
  * emitter.on('state:change', (newState, oldState) => {});
315
406
  * // Navigation events
@@ -323,9 +414,10 @@
323
414
  * Event names should follow the format 'namespace:action' for consistency.
324
415
  *
325
416
  * @public
326
- * @template T
327
417
  * @param {string} event - The name of the event to listen for (e.g., 'user:login').
328
- * @param {EventHandler<T>} handler - The callback function to invoke when the event occurs.
418
+ * @param {EventHandler} handler - The callback function to invoke when the event occurs.
419
+ * Note: Handlers returning Promises are NOT awaited. For async operations,
420
+ * handle promise resolution within your handler.
329
421
  * @returns {EventUnsubscribe} A function to unsubscribe the event handler.
330
422
  *
331
423
  * @example
@@ -333,8 +425,8 @@
333
425
  * const unsubscribe = emitter.on('user:login', (user) => console.log(user));
334
426
  *
335
427
  * @example
336
- * // Typed handler
337
- * emitter.on('user:update', (/** @type {{id: number, name: string}} *\/ user) => {
428
+ * // Handler with typed parameter
429
+ * emitter.on('user:update', (user) => {
338
430
  * console.log(`User ${user.id}: ${user.name}`);
339
431
  * });
340
432
  *
@@ -349,13 +441,15 @@
349
441
  }
350
442
  /**
351
443
  * Removes an event handler for the specified event name.
352
- * If no handler is provided, all handlers for the event are removed.
353
444
  * Automatically cleans up empty event sets to prevent memory leaks.
354
445
  *
446
+ * Behavior varies based on whether handler is provided:
447
+ * - With handler: Removes only that specific handler function (O(1) Set deletion)
448
+ * - Without handler: Removes ALL handlers for the event (O(1) Map deletion)
449
+ *
355
450
  * @public
356
- * @template T
357
451
  * @param {string} event - The name of the event to remove handlers from.
358
- * @param {EventHandler<T>} [handler] - The specific handler function to remove.
452
+ * @param {EventHandler} [handler] - The specific handler to remove. If omitted, all handlers are removed.
359
453
  * @returns {void}
360
454
  *
361
455
  * @example
@@ -381,12 +475,19 @@
381
475
  * Emits an event with the specified data to all registered handlers.
382
476
  * Handlers are called synchronously in the order they were registered.
383
477
  * If no handlers are registered for the event, the emission is silently ignored.
478
+ * Handlers that return promises are not awaited.
479
+ *
480
+ * Error propagation behavior:
481
+ * - If a handler throws synchronously, the error propagates immediately
482
+ * - Remaining handlers in the iteration are NOT called after an error
483
+ * - For error-resilient emission, wrap your emit call in try/catch
484
+ * - Async handler rejections are not caught (fire-and-forget)
384
485
  *
385
486
  * @public
386
- * @template T
387
487
  * @param {string} event - The name of the event to emit.
388
- * @param {...T} args - Optional arguments to pass to the event handlers.
488
+ * @param {...any} args - Optional arguments to pass to the event handlers.
389
489
  * @returns {void}
490
+ * @throws {Error} If a handler throws synchronously, the error propagates to the caller.
390
491
  *
391
492
  * @example
392
493
  * // Emit an event with data
@@ -404,9 +505,10 @@
404
505
  if (handlers) for (const handler of handlers)handler(...args);
405
506
  }
406
507
  /**
407
- * Creates a new Emitter instance.
508
+ * Creates a new Emitter instance with an empty event registry.
408
509
  *
409
510
  * @public
511
+ * @constructor
410
512
  *
411
513
  * @example
412
514
  * const emitter = new Emitter();
@@ -414,22 +516,47 @@
414
516
  /**
415
517
  * Map of event names to their registered handler functions
416
518
  * @private
417
- * @type {Map<string, Set<EventHandler<unknown>>>}
519
+ * @type {Map<string, Set<EventHandler>>}
418
520
  */ this._events = new Map();
419
521
  }
420
522
  }
421
523
 
524
+ /**
525
+ * @module eleva/renderer
526
+ * @fileoverview High-performance DOM renderer with two-pointer diffing and keyed reconciliation.
527
+ */ // ============================================================================
528
+ // TYPE DEFINITIONS
422
529
  // ============================================================================
423
- // TYPE DEFINITIONS - TypeScript-friendly JSDoc types for IDE support
424
- // ============================================================================
530
+ // -----------------------------------------------------------------------------
531
+ // Data Types
532
+ // -----------------------------------------------------------------------------
425
533
  /**
534
+ * Map of key attribute values to their corresponding DOM nodes.
426
535
  * @typedef {Map<string, Node>} KeyMap
427
- * Map of key attribute values to their corresponding DOM nodes for O(1) lookup
428
- */ /**
536
+ * @description Enables O(1) lookup for keyed element reconciliation.
537
+ */ // -----------------------------------------------------------------------------
538
+ // Interface Types
539
+ // -----------------------------------------------------------------------------
540
+ /**
541
+ * Interface describing the public API of a Renderer.
429
542
  * @typedef {Object} RendererLike
430
- * @property {function(HTMLElement, string): void} patchDOM - Patches the DOM with new HTML
431
- */ /**
432
- * Properties that can diverge from attributes via user interaction.
543
+ * @property {function(HTMLElement, string): void} patchDOM
544
+ * Patches the DOM with new HTML content.
545
+ * @description
546
+ * Plugins may extend renderer behavior by wrapping private methods (e.g., `_patchNode`),
547
+ * but those hooks are not part of the public API.
548
+ */ // ============================================================================
549
+ // CONSTANTS
550
+ // ============================================================================
551
+ /**
552
+ * Properties that can diverge from attributes after user interaction.
553
+ * These are synchronized during DOM patching to ensure element state
554
+ * matches the rendered HTML attributes.
555
+ *
556
+ * - `value`: Text input, textarea, select element values
557
+ * - `checked`: Checkbox and radio button states
558
+ * - `selected`: Option element selection states
559
+ *
433
560
  * @private
434
561
  * @type {string[]}
435
562
  */ const SYNC_PROPS = [
@@ -490,6 +617,9 @@
490
617
  * @example
491
618
  * // Empty the container
492
619
  * renderer.patchDOM(container, '');
620
+ *
621
+ * @see _diff - Low-level diffing algorithm.
622
+ * @see _patchNode - Individual node patching.
493
623
  */ patchDOM(container, newHtml) {
494
624
  this._tempContainer.innerHTML = newHtml;
495
625
  this._diff(container, this._tempContainer);
@@ -499,16 +629,26 @@
499
629
  /**
500
630
  * Performs a diff between two DOM nodes and patches the old node to match the new node.
501
631
  * Uses a two-pointer algorithm with key-based reconciliation for optimal performance.
502
- *
503
- * Algorithm overview:
504
- * 1. Compare children from start using two pointers
505
- * 2. For mismatches, build a key map lazily for O(1) lookup
506
- * 3. Move or insert nodes as needed
507
- * 4. Clean up remaining nodes at the end
632
+ * This method modifies oldParent in-place - it is not a pure function.
633
+ *
634
+ * Algorithm details:
635
+ * 1. Early exit if both nodes have no children (O(1) leaf node optimization)
636
+ * 2. Convert NodeLists to arrays for indexed access
637
+ * 3. Initialize two-pointer indices (oldStart/oldEnd, newStart/newEnd)
638
+ * 4. While pointers haven't crossed:
639
+ * a. Skip null entries (from previous moves)
640
+ * b. If nodes match (same key+tag or same type+name): patch and advance
641
+ * c. On mismatch: lazily build key→node map for O(1) lookup
642
+ * d. If keyed match found: move existing node (preserves DOM identity)
643
+ * e. Otherwise: clone and insert new node
644
+ * 5. After loop: append remaining new nodes or remove remaining old nodes
645
+ *
646
+ * Complexity: O(n) for most cases, O(n²) worst case with no keys.
647
+ * Non-keyed elements are matched by position and tag name.
508
648
  *
509
649
  * @private
510
- * @param {HTMLElement} oldParent - The original DOM element to update.
511
- * @param {HTMLElement} newParent - The new DOM element with desired state.
650
+ * @param {Element} oldParent - The original DOM element to update (modified in-place).
651
+ * @param {Element} newParent - The new DOM element with desired state.
512
652
  * @returns {void}
513
653
  */ _diff(oldParent, newParent) {
514
654
  // Early exit for leaf nodes (no children)
@@ -567,7 +707,8 @@
567
707
  }
568
708
  /**
569
709
  * Patches a single node, updating its content and attributes to match the new node.
570
- * Handles text nodes by updating nodeValue, and element nodes by updating attributes
710
+ * Handles text nodes (nodeType 3 / Node.TEXT_NODE) by updating nodeValue,
711
+ * and element nodes (nodeType 1 / Node.ELEMENT_NODE) by updating attributes
571
712
  * and recursively diffing children.
572
713
  *
573
714
  * Skips nodes that are managed by Eleva component instances to prevent interference
@@ -592,12 +733,20 @@
592
733
  /**
593
734
  * Removes a node from its parent, with special handling for Eleva-managed elements.
594
735
  * Style elements with the `data-e-style` attribute are preserved to maintain
595
- * component-scoped styles across re-renders.
736
+ * component styles across re-renders. Without this protection, component styles
737
+ * would be removed during DOM diffing and lost until the next full re-render.
738
+ *
739
+ * @note Style tags persist for the component's entire lifecycle. If the template
740
+ * conditionally removes elements that the CSS rules target (e.g., `.foo` elements),
741
+ * the style rules remain but simply have no matching elements. This is expected
742
+ * behavior - styles are cleaned up when the component unmounts, not when individual
743
+ * elements are removed.
596
744
  *
597
745
  * @private
598
746
  * @param {HTMLElement} parent - The parent element containing the node.
599
747
  * @param {Node} node - The node to remove.
600
748
  * @returns {void}
749
+ * @see _injectStyles - Where data-e-style elements are created.
601
750
  */ _removeNode(parent, node) {
602
751
  // Preserve Eleva-managed style elements
603
752
  if (node.nodeName === "STYLE" && node.hasAttribute("data-e-style")) return;
@@ -608,12 +757,16 @@
608
757
  * Adds new attributes, updates changed values, and removes attributes no longer present.
609
758
  * Also syncs DOM properties that can diverge from attributes after user interaction.
610
759
  *
611
- * Event attributes (prefixed with `@`) are skipped as they are handled separately
612
- * by Eleva's event binding system.
760
+ * Processing order:
761
+ * 1. Iterate new attributes, skip @ prefixed (event) attributes
762
+ * 2. Update attribute if value changed
763
+ * 3. Sync corresponding DOM property if writable (handles boolean conversion)
764
+ * 4. Iterate old attributes in reverse, remove if not in new element
765
+ * 5. Sync SYNC_PROPS (value, checked, selected) from new to old element
613
766
  *
614
767
  * @private
615
- * @param {HTMLElement} oldEl - The original element to update.
616
- * @param {HTMLElement} newEl - The new element with target attributes.
768
+ * @param {Element} oldEl - The original element to update.
769
+ * @param {Element} newEl - The new element with target attributes.
617
770
  * @returns {void}
618
771
  */ _updateAttributes(oldEl, newEl) {
619
772
  // Add/update attributes from new element
@@ -674,10 +827,11 @@
674
827
  /**
675
828
  * Extracts the key attribute from a node if it exists.
676
829
  * Only element nodes (nodeType === 1) can have key attributes.
830
+ * Uses optional chaining for null-safe access.
677
831
  *
678
832
  * @private
679
- * @param {Node|null|undefined} node - The node to extract the key from.
680
- * @returns {string|null} The key attribute value, or null if not an element or no key.
833
+ * @param {Node | null | undefined} node - The node to extract the key from.
834
+ * @returns {string | null} The key attribute value, or null if not an element or no key.
681
835
  */ _getNodeKey(node) {
682
836
  return node?.nodeType === 1 ? node.getAttribute("key") : null;
683
837
  }
@@ -686,7 +840,7 @@
686
840
  * The map is built lazily only when needed (when a mismatch occurs during diffing).
687
841
  *
688
842
  * @private
689
- * @param {Array<ChildNode>} children - The array of child nodes to map.
843
+ * @param {ChildNode[]} children - The array of child nodes to map.
690
844
  * @param {number} start - The start index (inclusive) for mapping.
691
845
  * @param {number} end - The end index (inclusive) for mapping.
692
846
  * @returns {KeyMap} A Map of key strings to their corresponding DOM nodes.
@@ -700,8 +854,13 @@
700
854
  }
701
855
  /**
702
856
  * Creates a new Renderer instance.
857
+ * Initializes a reusable temporary container for HTML parsing.
858
+ *
859
+ * Performance: The temp container is reused across all patch operations,
860
+ * minimizing memory allocation overhead (O(1) memory per Renderer instance).
703
861
  *
704
862
  * @public
863
+ * @constructor
705
864
  *
706
865
  * @example
707
866
  * const renderer = new Renderer();
@@ -716,168 +875,195 @@
716
875
  }
717
876
 
718
877
  // ============================================================================
719
- // TYPE DEFINITIONS - TypeScript-friendly JSDoc types for IDE support
878
+ // TYPE DEFINITIONS
720
879
  // ============================================================================
721
880
  // -----------------------------------------------------------------------------
722
881
  // Configuration Types
723
882
  // -----------------------------------------------------------------------------
724
883
  /**
725
- * @typedef {Object} ElevaConfig
726
- * @property {boolean} [debug=false]
727
- * Enable debug mode for verbose logging
728
- * @property {string} [prefix='e']
729
- * Prefix for component style scoping
730
- * @property {boolean} [async=true]
731
- * Enable async component setup
884
+ * Configuration options for the Eleva instance (reserved for future use).
885
+ * @typedef {Record<string, unknown>} ElevaConfig
732
886
  */ // -----------------------------------------------------------------------------
733
887
  // Component Types
734
888
  // -----------------------------------------------------------------------------
735
889
  /**
890
+ * Component definition object.
736
891
  * @typedef {Object} ComponentDefinition
737
892
  * @property {SetupFunction} [setup]
738
- * Optional setup function that initializes the component's state and returns reactive data
739
- * @property {TemplateFunction|string} template
740
- * Required function or string that defines the component's HTML structure
741
- * @property {StyleFunction|string} [style]
742
- * Optional function or string that provides component-scoped CSS styles
893
+ * Optional setup function that initializes the component's state and returns reactive data.
894
+ * @property {TemplateFunction | string} template
895
+ * Required function or string that defines the component's HTML structure.
896
+ * @property {StyleFunction | string} [style]
897
+ * Optional function or string that provides CSS styles for the component.
898
+ * Styles are preserved across DOM diffs via data-e-style markers.
743
899
  * @property {ChildrenMap} [children]
744
- * Optional object defining nested child components
900
+ * Optional object defining nested child components.
745
901
  */ /**
902
+ * Setup function that initializes component state.
746
903
  * @callback SetupFunction
747
- * @param {ComponentContext} ctx - The component context with props, emitter, and signal factory
748
- * @returns {SetupResult|Promise<SetupResult>} Reactive data and lifecycle hooks
904
+ * @param {ComponentContext} ctx
905
+ * The component context with props, emitter, and signal factory.
906
+ * @returns {SetupResult | Promise<SetupResult>}
907
+ * Reactive data and lifecycle hooks.
749
908
  */ /**
909
+ * Data returned from setup function, may include lifecycle hooks.
750
910
  * @typedef {Record<string, unknown> & LifecycleHooks} SetupResult
751
- * Data returned from setup function, may include lifecycle hooks
752
911
  */ /**
912
+ * Template function that returns HTML markup.
753
913
  * @callback TemplateFunction
754
- * @param {ComponentContext} ctx - The component context
755
- * @returns {string|Promise<string>} HTML template string
914
+ * @param {ComponentContext & SetupResult} ctx
915
+ * The merged component context and setup data.
916
+ * @returns {string | Promise<string>}
917
+ * HTML template string.
756
918
  */ /**
919
+ * Style function that returns CSS styles.
757
920
  * @callback StyleFunction
758
- * @param {ComponentContext} ctx - The component context
759
- * @returns {string} CSS styles string
921
+ * @param {ComponentContext & SetupResult} ctx
922
+ * The merged component context and setup data.
923
+ * @returns {string}
924
+ * CSS styles string.
760
925
  */ /**
761
- * @typedef {Record<string, ComponentDefinition|string>} ChildrenMap
762
- * Map of CSS selectors to component definitions or registered component names
926
+ * Map of CSS selectors to component definitions or registered component names.
927
+ * @typedef {Record<string, ComponentDefinition | string>} ChildrenMap
763
928
  */ // -----------------------------------------------------------------------------
764
929
  // Context Types
765
930
  // -----------------------------------------------------------------------------
766
931
  /**
932
+ * Context passed to component setup function.
767
933
  * @typedef {Object} ComponentContext
768
934
  * @property {ComponentProps} props
769
- * Component properties passed during mounting
935
+ * Component properties passed during mounting.
770
936
  * @property {Emitter} emitter
771
- * Event emitter instance for component event handling
937
+ * Event emitter instance for component event handling.
772
938
  * @property {SignalFactory} signal
773
- * Factory function to create reactive Signal instances
939
+ * Factory function to create reactive Signal instances.
940
+ * @description
941
+ * Plugins may extend this context with additional properties (e.g., `ctx.router`, `ctx.store`).
942
+ * @see RouterContext - Router plugin injected context.
943
+ * @see StoreApi - Store plugin injected context.
774
944
  */ /**
945
+ * Properties passed to a component during mounting.
775
946
  * @typedef {Record<string, unknown>} ComponentProps
776
- * Properties passed to a component during mounting
777
947
  */ /**
778
- * @callback SignalFactory
779
- * @template T
780
- * @param {T} initialValue - The initial value for the signal
781
- * @returns {Signal<T>} A new Signal instance
948
+ * Factory function to create reactive Signal instances.
949
+ * @typedef {<T>(initialValue: T) => Signal<T>} SignalFactory
782
950
  */ // -----------------------------------------------------------------------------
783
951
  // Lifecycle Hook Types
784
952
  // -----------------------------------------------------------------------------
785
953
  /**
954
+ * Lifecycle hooks that can be returned from setup function.
786
955
  * @typedef {Object} LifecycleHooks
787
956
  * @property {LifecycleHook} [onBeforeMount]
788
- * Hook called before component mounting
957
+ * Called before component mounting.
789
958
  * @property {LifecycleHook} [onMount]
790
- * Hook called after component mounting
959
+ * Called after component mounting.
791
960
  * @property {LifecycleHook} [onBeforeUpdate]
792
- * Hook called before component update
961
+ * Called before component update.
793
962
  * @property {LifecycleHook} [onUpdate]
794
- * Hook called after component update
963
+ * Called after component update.
795
964
  * @property {UnmountHook} [onUnmount]
796
- * Hook called during component unmounting
965
+ * Called during component unmounting.
797
966
  */ /**
967
+ * Lifecycle hook function.
798
968
  * @callback LifecycleHook
799
- * @param {LifecycleHookContext} ctx - Context with container and component data
800
- * @returns {void|Promise<void>}
969
+ * @param {LifecycleHookContext} ctx
970
+ * Context with container and component data.
971
+ * @returns {void | Promise<void>}
801
972
  */ /**
973
+ * Unmount hook function with cleanup resources.
802
974
  * @callback UnmountHook
803
- * @param {UnmountHookContext} ctx - Context with cleanup resources
804
- * @returns {void|Promise<void>}
975
+ * @param {UnmountHookContext} ctx
976
+ * Context with cleanup resources.
977
+ * @returns {void | Promise<void>}
805
978
  */ /**
979
+ * Context passed to lifecycle hooks.
806
980
  * @typedef {Object} LifecycleHookContext
807
981
  * @property {HTMLElement} container
808
- * The DOM element where the component is mounted
982
+ * The DOM element where the component is mounted.
809
983
  * @property {ComponentContext & SetupResult} context
810
- * The component's reactive state and context data
984
+ * The component's reactive state and context data.
811
985
  */ /**
986
+ * Context passed to unmount hook with cleanup resources.
812
987
  * @typedef {Object} UnmountHookContext
813
988
  * @property {HTMLElement} container
814
- * The DOM element where the component is mounted
989
+ * The DOM element where the component is mounted.
815
990
  * @property {ComponentContext & SetupResult} context
816
- * The component's reactive state and context data
991
+ * The component's reactive state and context data.
817
992
  * @property {CleanupResources} cleanup
818
- * Object containing cleanup functions and instances
993
+ * Object containing cleanup functions and instances.
819
994
  */ /**
995
+ * Resources available for cleanup during unmount.
820
996
  * @typedef {Object} CleanupResources
821
- * @property {Array<UnsubscribeFunction>} watchers
822
- * Signal watcher cleanup functions
823
- * @property {Array<UnsubscribeFunction>} listeners
824
- * Event listener cleanup functions
825
- * @property {Array<MountResult>} children
826
- * Child component instances
997
+ * @property {UnsubscribeFunction[]} watchers
998
+ * Signal watcher cleanup functions.
999
+ * @property {UnsubscribeFunction[]} listeners
1000
+ * Event listener cleanup functions.
1001
+ * @property {MountResult[]} children
1002
+ * Child component instances.
827
1003
  */ // -----------------------------------------------------------------------------
828
1004
  // Mount Result Types
829
1005
  // -----------------------------------------------------------------------------
830
1006
  /**
1007
+ * Result of mounting a component.
831
1008
  * @typedef {Object} MountResult
832
1009
  * @property {HTMLElement} container
833
- * The DOM element where the component is mounted
1010
+ * The DOM element where the component is mounted.
834
1011
  * @property {ComponentContext & SetupResult} data
835
- * The component's reactive state and context data
1012
+ * The component's reactive state and context data.
836
1013
  * @property {UnmountFunction} unmount
837
- * Function to clean up and unmount the component
1014
+ * Function to clean up and unmount the component.
838
1015
  */ /**
1016
+ * Function to unmount a component and clean up resources.
839
1017
  * @callback UnmountFunction
840
1018
  * @returns {Promise<void>}
841
1019
  */ /**
1020
+ * Function to unsubscribe from events or watchers.
842
1021
  * @callback UnsubscribeFunction
843
- * @returns {void|boolean}
1022
+ * @returns {void | boolean}
844
1023
  */ // -----------------------------------------------------------------------------
845
1024
  // Plugin Types
846
1025
  // -----------------------------------------------------------------------------
847
1026
  /**
1027
+ * Plugin interface for extending Eleva.
848
1028
  * @typedef {Object} ElevaPlugin
849
- * @property {PluginInstallFunction} install
850
- * Function that installs the plugin into the Eleva instance
851
1029
  * @property {string} name
852
- * Unique identifier name for the plugin
1030
+ * Unique identifier name for the plugin.
1031
+ * @property {string} [version]
1032
+ * Optional version string for the plugin.
1033
+ * @property {PluginInstallFunction} install
1034
+ * Function that installs the plugin.
853
1035
  * @property {PluginUninstallFunction} [uninstall]
854
- * Optional function to uninstall the plugin
1036
+ * Optional function to uninstall the plugin.
855
1037
  */ /**
1038
+ * Plugin install function.
856
1039
  * @callback PluginInstallFunction
857
- * @param {Eleva} eleva - The Eleva instance
858
- * @param {PluginOptions} options - Plugin configuration options
859
- * @returns {void|Eleva|unknown} Optionally returns the Eleva instance or plugin result
1040
+ * @param {Eleva} eleva
1041
+ * The Eleva instance.
1042
+ * @param {PluginOptions} [options]
1043
+ * Plugin configuration options.
1044
+ * @returns {void | Eleva | unknown}
860
1045
  */ /**
1046
+ * Plugin uninstall function.
861
1047
  * @callback PluginUninstallFunction
862
- * @param {Eleva} eleva - The Eleva instance
863
- * @returns {void}
1048
+ * @param {Eleva} eleva
1049
+ * The Eleva instance.
1050
+ * @returns {void | Promise<void>}
864
1051
  */ /**
1052
+ * Configuration options passed to a plugin during installation.
865
1053
  * @typedef {Record<string, unknown>} PluginOptions
866
- * Configuration options passed to a plugin during installation
867
1054
  */ // -----------------------------------------------------------------------------
868
1055
  // Event Types
869
1056
  // -----------------------------------------------------------------------------
870
1057
  /**
871
- * @callback EventHandler
872
- * @param {Event} event - The DOM event object
873
- * @returns {void}
1058
+ * Handler function for DOM events (e.g., click, input, submit).
1059
+ * @typedef {(event: Event) => void} DOMEventHandler
874
1060
  */ /**
1061
+ * Common DOM event names (prefixed with @ in templates).
875
1062
  * @typedef {'click'|'submit'|'input'|'change'|'focus'|'blur'|'keydown'|'keyup'|'keypress'|'mouseenter'|'mouseleave'|'mouseover'|'mouseout'|'mousedown'|'mouseup'|'touchstart'|'touchend'|'touchmove'|'scroll'|'resize'|'load'|'error'|string} DOMEventName
876
- * Common DOM event names (prefixed with @ in templates)
877
1063
  */ /**
878
1064
  * @class 🧩 Eleva
879
1065
  * @classdesc A modern, signal-based component runtime framework that provides lifecycle hooks,
880
- * scoped styles, and plugin support. Eleva manages component registration, plugin integration,
1066
+ * component styles, and plugin support. Eleva manages component registration, plugin integration,
881
1067
  * event handling, and DOM rendering with a focus on performance and developer experience.
882
1068
  *
883
1069
  * @example
@@ -907,14 +1093,16 @@
907
1093
  * The plugin's install function will be called with the Eleva instance and provided options.
908
1094
  * After installation, the plugin will be available for use by components.
909
1095
  *
910
- * Note: Plugins that wrap core methods (e.g., mount) must be uninstalled in reverse order
1096
+ * @note Plugins that wrap core methods (e.g., mount) must be uninstalled in reverse order
911
1097
  * of installation (LIFO - Last In, First Out) to avoid conflicts.
912
1098
  *
913
1099
  * @public
914
1100
  * @param {ElevaPlugin} plugin - The plugin object which must have an `install` function.
915
- * @param {Object<string, unknown>} [options={}] - Optional configuration options for the plugin.
916
- * @returns {Eleva} The Eleva instance (for method chaining).
1101
+ * @param {PluginOptions} [options={}] - Optional configuration options for the plugin.
1102
+ * @returns {Eleva | unknown} The Eleva instance (for method chaining) or the result returned by the plugin.
917
1103
  * @throws {Error} If plugin does not have an install function.
1104
+ * @see component - Register components after installing plugins.
1105
+ * @see mount - Mount components to the DOM.
918
1106
  * @example
919
1107
  * app.use(myPlugin, { option1: "value1" });
920
1108
  *
@@ -942,6 +1130,7 @@
942
1130
  * @param {ComponentDefinition} definition - The component definition including setup, template, style, and children.
943
1131
  * @returns {Eleva} The Eleva instance (for method chaining).
944
1132
  * @throws {Error} If name is not a non-empty string or definition has no template.
1133
+ * @see mount - Mount this component to the DOM.
945
1134
  * @example
946
1135
  * app.component("myButton", {
947
1136
  * template: (ctx) => `<button>${ctx.props.text}</button>`,
@@ -960,21 +1149,25 @@
960
1149
  /**
961
1150
  * Mounts a registered component to a DOM element.
962
1151
  * This will initialize the component, set up its reactive state, and render it to the DOM.
1152
+ * If the container already has a mounted Eleva instance, it is returned as-is.
1153
+ * Unmount clears the container contents and removes the internal instance marker.
963
1154
  *
964
1155
  * @public
1156
+ * @async
965
1157
  * @param {HTMLElement} container - The DOM element where the component will be mounted.
966
- * @param {string|ComponentDefinition} compName - The name of the registered component or a direct component definition.
967
- * @param {Object<string, unknown>} [props={}] - Optional properties to pass to the component.
1158
+ * @param {string | ComponentDefinition} compName - The name of the registered component or a direct component definition.
1159
+ * @param {ComponentProps} [props={}] - Optional properties to pass to the component.
968
1160
  * @returns {Promise<MountResult>}
969
1161
  * A Promise that resolves to an object containing:
970
1162
  * - container: The mounted component's container element
971
1163
  * - data: The component's reactive state and context
972
1164
  * - unmount: Function to clean up and unmount the component
973
1165
  * @throws {Error} If container is not a DOM element or component is not registered.
1166
+ * @throws {Error} If setup function, template function, or style function throws.
974
1167
  * @example
975
1168
  * const instance = await app.mount(document.getElementById("app"), "myComponent", { text: "Click me" });
976
1169
  * // Later...
977
- * instance.unmount();
1170
+ * await instance.unmount();
978
1171
  */ async mount(container, compName, props = {}) {
979
1172
  if (!container?.nodeType) {
980
1173
  throw new Error("Eleva: container must be a DOM element");
@@ -987,13 +1180,13 @@
987
1180
  * Destructure the component definition to access core functionality.
988
1181
  * - setup: Optional function for component initialization and state management
989
1182
  * - template: Required function or string that returns the component's HTML structure
990
- * - style: Optional function or string for component-scoped CSS styles
1183
+ * - style: Optional function or string for component CSS styles (not auto-scoped)
991
1184
  * - children: Optional object defining nested child components
992
1185
  */ const { setup, template, style, children } = definition;
993
1186
  /** @type {ComponentContext} */ const context = {
994
1187
  props,
995
1188
  emitter: this.emitter,
996
- /** @type {(v: unknown) => Signal<unknown>} */ signal: (v)=>new this.signal(v)
1189
+ /** @type {SignalFactory} */ signal: (v)=>new this.signal(v)
997
1190
  };
998
1191
  /**
999
1192
  * Processes the mounting of the component.
@@ -1003,19 +1196,20 @@
1003
1196
  * 3. Rendering the component
1004
1197
  * 4. Managing component lifecycle
1005
1198
  *
1006
- * @param {Object<string, unknown>} data - Data returned from the component's setup function
1199
+ * @inner
1200
+ * @param {Record<string, unknown>} data - Data returned from the component's setup function.
1007
1201
  * @returns {Promise<MountResult>} An object containing:
1008
1202
  * - container: The mounted component's container element
1009
1203
  * - data: The component's reactive state and context
1010
1204
  * - unmount: Function to clean up and unmount the component
1011
1205
  */ const processMount = async (data)=>{
1012
- /** @type {ComponentContext} */ const mergedContext = {
1206
+ /** @type {ComponentContext & SetupResult} */ const mergedContext = {
1013
1207
  ...context,
1014
1208
  ...data
1015
1209
  };
1016
- /** @type {Array<() => void>} */ const watchers = [];
1017
- /** @type {Array<MountResult>} */ const childInstances = [];
1018
- /** @type {Array<() => void>} */ const listeners = [];
1210
+ /** @type {UnsubscribeFunction[]} */ const watchers = [];
1211
+ /** @type {MountResult[]} */ const childInstances = [];
1212
+ /** @type {UnsubscribeFunction[]} */ const listeners = [];
1019
1213
  /** @private {boolean} Local mounted state for this component instance */ let isMounted = false;
1020
1214
  // ========================================================================
1021
1215
  // Render Batching
@@ -1027,7 +1221,10 @@
1027
1221
  * changes in the same synchronous block will each call this function,
1028
1222
  * but only one render will be scheduled via queueMicrotask.
1029
1223
  * This separates concerns: signals handle state, components handle scheduling.
1224
+ *
1225
+ * @inner
1030
1226
  * @private
1227
+ * @returns {void}
1031
1228
  */ const scheduleRender = ()=>{
1032
1229
  if (renderScheduled) return;
1033
1230
  renderScheduled = true;
@@ -1042,6 +1239,10 @@
1042
1239
  * 2. Processing the template
1043
1240
  * 3. Updating the DOM
1044
1241
  * 4. Processing events, injecting styles, and mounting child components.
1242
+ *
1243
+ * @inner
1244
+ * @private
1245
+ * @returns {Promise<void>}
1045
1246
  */ const render = async ()=>{
1046
1247
  const html = typeof template === "function" ? await template(mergedContext) : template;
1047
1248
  // Execute before hooks
@@ -1057,6 +1258,18 @@
1057
1258
  });
1058
1259
  }
1059
1260
  this.renderer.patchDOM(container, html);
1261
+ // Unmount child components whose host elements were removed by patching.
1262
+ const childrenToUnmount = [];
1263
+ for(let i = childInstances.length - 1; i >= 0; i--){
1264
+ const child = childInstances[i];
1265
+ if (!container.contains(child.container)) {
1266
+ childInstances.splice(i, 1);
1267
+ childrenToUnmount.push(child);
1268
+ }
1269
+ }
1270
+ if (childrenToUnmount.length) {
1271
+ await Promise.allSettled(childrenToUnmount.map((child)=>child.unmount()));
1272
+ }
1060
1273
  this._processEvents(container, mergedContext, listeners);
1061
1274
  if (style) this._injectStyles(container, compId, style, mergedContext);
1062
1275
  if (children) await this._mountComponents(container, children, childInstances, mergedContext);
@@ -1079,6 +1292,10 @@
1079
1292
  * When a Signal's value changes, a batched render is scheduled.
1080
1293
  * Multiple changes within the same frame are collapsed into one render.
1081
1294
  * Stores unsubscribe functions to clean up watchers when component unmounts.
1295
+ *
1296
+ * @note Signal watchers are invoked synchronously when values change.
1297
+ * Render batching is handled at the component level via queueMicrotask,
1298
+ * not at the signal level. This preserves stack traces for debugging.
1082
1299
  */ for (const val of Object.values(data)){
1083
1300
  if (val instanceof Signal) watchers.push(val.watch(scheduleRender));
1084
1301
  }
@@ -1088,15 +1305,16 @@
1088
1305
  data: mergedContext,
1089
1306
  /**
1090
1307
  * Unmounts the component, cleaning up watchers and listeners, child components, and clearing the container.
1308
+ * Removes the internal instance marker from the container when complete.
1091
1309
  *
1092
- * @returns {void}
1310
+ * @returns {Promise<void>}
1093
1311
  */ unmount: async ()=>{
1094
- /** @type {UnmountHookContext} */ await mergedContext.onUnmount?.({
1312
+ await mergedContext.onUnmount?.({
1095
1313
  container,
1096
1314
  context: mergedContext,
1097
1315
  cleanup: {
1098
- watchers: watchers,
1099
- listeners: listeners,
1316
+ watchers,
1317
+ listeners,
1100
1318
  children: childInstances
1101
1319
  }
1102
1320
  });
@@ -1116,13 +1334,19 @@
1116
1334
  }
1117
1335
  /**
1118
1336
  * Processes DOM elements for event binding based on attributes starting with "@".
1119
- * This method handles the event delegation system and ensures proper cleanup of event listeners.
1337
+ * This method attaches event listeners directly to elements and ensures proper cleanup.
1338
+ * Bound `@event` attributes are removed after listeners are attached.
1339
+ *
1340
+ * Handler resolution order:
1341
+ * 1. Direct context property lookup (e.g., context["handleClick"])
1342
+ * 2. Template expression evaluation via TemplateEngine (e.g., "increment()")
1120
1343
  *
1121
1344
  * @private
1122
1345
  * @param {HTMLElement} container - The container element in which to search for event attributes.
1123
- * @param {ComponentContext} context - The current component context containing event handler definitions.
1124
- * @param {Array<() => void>} listeners - Array to collect cleanup functions for each event listener.
1346
+ * @param {ComponentContext & SetupResult} context - The merged component context and setup data.
1347
+ * @param {UnsubscribeFunction[]} listeners - Array to collect cleanup functions for each event listener.
1125
1348
  * @returns {void}
1349
+ * @see TemplateEngine.evaluate - Expression evaluation. fallback.
1126
1350
  */ _processEvents(container, context, listeners) {
1127
1351
  /** @type {NodeListOf<Element>} */ const elements = container.querySelectorAll("*");
1128
1352
  for (const el of elements){
@@ -1132,7 +1356,7 @@
1132
1356
  if (!attr.name.startsWith("@")) continue;
1133
1357
  /** @type {keyof HTMLElementEventMap} */ const event = attr.name.slice(1);
1134
1358
  /** @type {string} */ const handlerName = attr.value;
1135
- /** @type {(event: Event) => void} */ const handler = context[handlerName] || this.templateEngine.evaluate(handlerName, context);
1359
+ /** @type {DOMEventHandler} */ const handler = context[handlerName] || this.templateEngine.evaluate(handlerName, context);
1136
1360
  if (typeof handler === "function") {
1137
1361
  el.addEventListener(event, handler);
1138
1362
  el.removeAttribute(attr.name);
@@ -1142,18 +1366,22 @@
1142
1366
  }
1143
1367
  }
1144
1368
  /**
1145
- * Injects scoped styles into the component's container.
1146
- * The styles are automatically prefixed to prevent style leakage to other components.
1369
+ * Injects styles into the component's container.
1370
+ * Styles are placed in a `<style>` element with a `data-e-style` attribute for identification.
1371
+ *
1372
+ * @note Styles are not automatically scoped - use unique class names or CSS nesting for isolation.
1373
+ *
1374
+ * Optimization: Skips DOM update if style content hasn't changed.
1147
1375
  *
1148
1376
  * @private
1149
1377
  * @param {HTMLElement} container - The container element where styles should be injected.
1150
1378
  * @param {string} compId - The component ID used to identify the style element.
1151
- * @param {(function(ComponentContext): string)|string} styleDef - The component's style definition (function or string).
1152
- * @param {ComponentContext} context - The current component context for style interpolation.
1379
+ * @param {StyleFunction | string} styleDef - The component's style definition (function or string).
1380
+ * @param {ComponentContext & SetupResult} context - The merged component context and setup data.
1153
1381
  * @returns {void}
1154
1382
  */ _injectStyles(container, compId, styleDef, context) {
1155
1383
  /** @type {string} */ const newStyle = typeof styleDef === "function" ? styleDef(context) : styleDef;
1156
- /** @type {HTMLStyleElement|null} */ let styleEl = container.querySelector(`style[data-e-style="${compId}"]`);
1384
+ /** @type {HTMLStyleElement | null} */ let styleEl = container.querySelector(`style[data-e-style="${compId}"]`);
1157
1385
  if (styleEl && styleEl.textContent === newStyle) return;
1158
1386
  if (!styleEl) {
1159
1387
  styleEl = document.createElement("style");
@@ -1166,11 +1394,13 @@
1166
1394
  * Extracts and evaluates props from an element's attributes that start with `:`.
1167
1395
  * Prop values are evaluated as expressions against the component context,
1168
1396
  * allowing direct passing of objects, arrays, and other complex types.
1397
+ * Processed attributes are removed from the element after extraction.
1169
1398
  *
1170
1399
  * @private
1171
- * @param {HTMLElement} element - The DOM element to extract props from
1172
- * @param {ComponentContext} context - The component context for evaluating prop expressions
1173
- * @returns {Record<string, string>} An object containing the evaluated props
1400
+ * @param {HTMLElement} element - The DOM element to extract props from.
1401
+ * @param {ComponentContext & SetupResult} context - The merged component context and setup data.
1402
+ * @returns {ComponentProps} An object containing the evaluated props.
1403
+ * @see TemplateEngine.evaluate - Expression evaluation.
1174
1404
  * @example
1175
1405
  * // For an element with attributes:
1176
1406
  * // <div :name="user.name" :data="items">
@@ -1195,20 +1425,21 @@
1195
1425
  * This method handles mounting of explicitly defined children components.
1196
1426
  *
1197
1427
  * The mounting process follows these steps:
1198
- * 1. Cleans up any existing component instances
1428
+ * 1. Finds matching DOM nodes within the container
1199
1429
  * 2. Mounts explicitly defined children components
1200
1430
  *
1201
1431
  * @private
1202
- * @param {HTMLElement} container - The container element to mount components in
1203
- * @param {Object<string, ComponentDefinition>} children - Map of selectors to component definitions for explicit children
1204
- * @param {Array<MountResult>} childInstances - Array to store all mounted component instances
1205
- * @param {ComponentContext} context - The parent component context for evaluating prop expressions
1432
+ * @async
1433
+ * @param {HTMLElement} container - The container element to mount components in.
1434
+ * @param {ChildrenMap} children - Map of selectors to component definitions for explicit children.
1435
+ * @param {MountResult[]} childInstances - Array to store all mounted component instances.
1436
+ * @param {ComponentContext & SetupResult} context - The merged component context and setup data.
1206
1437
  * @returns {Promise<void>}
1207
1438
  *
1208
1439
  * @example
1209
1440
  * // Explicit children mounting:
1210
1441
  * const children = {
1211
- * 'UserProfile': UserProfileComponent,
1442
+ * 'user-profile': UserProfileComponent,
1212
1443
  * '#settings-panel': "settings-panel"
1213
1444
  * };
1214
1445
  */ async _mountComponents(container, children, childInstances, context) {
@@ -1216,7 +1447,7 @@
1216
1447
  if (!selector) continue;
1217
1448
  for (const el of container.querySelectorAll(selector)){
1218
1449
  if (!(el instanceof HTMLElement)) continue;
1219
- /** @type {Record<string, string>} */ const props = this._extractProps(el, context);
1450
+ /** @type {ComponentProps} */ const props = this._extractProps(el, context);
1220
1451
  /** @type {MountResult} */ const instance = await this.mount(el, component, props);
1221
1452
  if (instance && !childInstances.includes(instance)) {
1222
1453
  childInstances.push(instance);
@@ -1228,11 +1459,10 @@
1228
1459
  * Creates a new Eleva instance with the specified name and configuration.
1229
1460
  *
1230
1461
  * @public
1462
+ * @constructor
1231
1463
  * @param {string} name - The unique identifier name for this Eleva instance.
1232
- * @param {Record<string, unknown>} [config={}] - Optional configuration object for the instance.
1233
- * May include framework-wide settings and default behaviors.
1464
+ * @param {ElevaConfig} [config={}] - Optional configuration object for the instance.
1234
1465
  * @throws {Error} If the name is not provided or is not a string.
1235
- * @returns {Eleva} A new Eleva instance.
1236
1466
  *
1237
1467
  * @example
1238
1468
  * const app = new Eleva("myApp");
@@ -1246,12 +1476,12 @@
1246
1476
  if (!name || typeof name !== "string") {
1247
1477
  throw new Error("Eleva: name must be a non-empty string");
1248
1478
  }
1249
- /** @public {string} The unique identifier name for this Eleva instance */ this.name = name;
1250
- /** @public {Object<string, unknown>} Optional configuration object for the Eleva instance */ this.config = config;
1251
- /** @public {Emitter} Instance of the event emitter for handling component events */ this.emitter = new Emitter();
1252
- /** @public {typeof Signal} Static reference to the Signal class for creating reactive state */ this.signal = Signal;
1253
- /** @public {typeof TemplateEngine} Static reference to the TemplateEngine class for template parsing */ this.templateEngine = TemplateEngine;
1254
- /** @public {Renderer} Instance of the renderer for handling DOM updates and patching */ this.renderer = new Renderer();
1479
+ /** @public @readonly {string} The unique identifier name for this Eleva instance */ this.name = name;
1480
+ /** @public @readonly {Record<string, unknown>} Configuration object for the Eleva instance */ this.config = config;
1481
+ /** @public @readonly {Emitter} Event emitter for handling component events */ this.emitter = new Emitter();
1482
+ /** @public @readonly {typeof Signal} Signal class for creating reactive state */ this.signal = Signal;
1483
+ /** @public @readonly {typeof TemplateEngine} TemplateEngine class for template parsing */ this.templateEngine = TemplateEngine;
1484
+ /** @public @readonly {Renderer} Renderer for handling DOM updates and patching */ this.renderer = new Renderer();
1255
1485
  /** @private {Map<string, ComponentDefinition>} Registry of all component definitions by name */ this._components = new Map();
1256
1486
  /** @private {Map<string, ElevaPlugin>} Collection of installed plugin instances by name */ this._plugins = new Map();
1257
1487
  /** @private {number} Counter for generating unique component IDs */ this._componentCounter = 0;