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