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