eleva 1.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/README.md +21 -10
  2. package/dist/{eleva-plugins.cjs.js → eleva-plugins.cjs} +1002 -292
  3. package/dist/eleva-plugins.cjs.map +1 -0
  4. package/dist/eleva-plugins.d.cts +1352 -0
  5. package/dist/eleva-plugins.d.cts.map +1 -0
  6. package/dist/eleva-plugins.d.ts +1352 -0
  7. package/dist/eleva-plugins.d.ts.map +1 -0
  8. package/dist/{eleva-plugins.esm.js → eleva-plugins.js} +1002 -292
  9. package/dist/eleva-plugins.js.map +1 -0
  10. package/dist/eleva-plugins.umd.js +1001 -291
  11. package/dist/eleva-plugins.umd.js.map +1 -1
  12. package/dist/eleva-plugins.umd.min.js +1 -1
  13. package/dist/eleva-plugins.umd.min.js.map +1 -1
  14. package/dist/{eleva.cjs.js → eleva.cjs} +421 -191
  15. package/dist/eleva.cjs.map +1 -0
  16. package/dist/eleva.d.cts +1329 -0
  17. package/dist/eleva.d.cts.map +1 -0
  18. package/dist/eleva.d.ts +473 -226
  19. package/dist/eleva.d.ts.map +1 -0
  20. package/dist/{eleva.esm.js → eleva.js} +422 -192
  21. package/dist/eleva.js.map +1 -0
  22. package/dist/eleva.umd.js +420 -190
  23. package/dist/eleva.umd.js.map +1 -1
  24. package/dist/eleva.umd.min.js +1 -1
  25. package/dist/eleva.umd.min.js.map +1 -1
  26. package/dist/plugins/attr.cjs +279 -0
  27. package/dist/plugins/attr.cjs.map +1 -0
  28. package/dist/plugins/attr.d.cts +101 -0
  29. package/dist/plugins/attr.d.cts.map +1 -0
  30. package/dist/plugins/attr.d.ts +101 -0
  31. package/dist/plugins/attr.d.ts.map +1 -0
  32. package/dist/plugins/attr.js +276 -0
  33. package/dist/plugins/attr.js.map +1 -0
  34. package/dist/plugins/attr.umd.js +111 -22
  35. package/dist/plugins/attr.umd.js.map +1 -1
  36. package/dist/plugins/attr.umd.min.js +1 -1
  37. package/dist/plugins/attr.umd.min.js.map +1 -1
  38. package/dist/plugins/router.cjs +1873 -0
  39. package/dist/plugins/router.cjs.map +1 -0
  40. package/dist/plugins/router.d.cts +1296 -0
  41. package/dist/plugins/router.d.cts.map +1 -0
  42. package/dist/plugins/router.d.ts +1296 -0
  43. package/dist/plugins/router.d.ts.map +1 -0
  44. package/dist/plugins/router.js +1870 -0
  45. package/dist/plugins/router.js.map +1 -0
  46. package/dist/plugins/router.umd.js +482 -186
  47. package/dist/plugins/router.umd.js.map +1 -1
  48. package/dist/plugins/router.umd.min.js +1 -1
  49. package/dist/plugins/router.umd.min.js.map +1 -1
  50. package/dist/plugins/store.cjs +920 -0
  51. package/dist/plugins/store.cjs.map +1 -0
  52. package/dist/plugins/store.d.cts +266 -0
  53. package/dist/plugins/store.d.cts.map +1 -0
  54. package/dist/plugins/store.d.ts +266 -0
  55. package/dist/plugins/store.d.ts.map +1 -0
  56. package/dist/plugins/store.js +917 -0
  57. package/dist/plugins/store.js.map +1 -0
  58. package/dist/plugins/store.umd.js +410 -85
  59. package/dist/plugins/store.umd.js.map +1 -1
  60. package/dist/plugins/store.umd.min.js +1 -1
  61. package/dist/plugins/store.umd.min.js.map +1 -1
  62. package/package.json +112 -68
  63. package/src/core/Eleva.js +195 -115
  64. package/src/index.cjs +10 -0
  65. package/src/index.js +11 -0
  66. package/src/modules/Emitter.js +68 -20
  67. package/src/modules/Renderer.js +82 -20
  68. package/src/modules/Signal.js +43 -15
  69. package/src/modules/TemplateEngine.js +50 -9
  70. package/src/plugins/Attr.js +121 -19
  71. package/src/plugins/Router.js +526 -181
  72. package/src/plugins/Store.js +448 -69
  73. package/src/plugins/index.js +1 -0
  74. package/types/core/Eleva.d.ts +263 -169
  75. package/types/core/Eleva.d.ts.map +1 -1
  76. package/types/index.d.cts +3 -0
  77. package/types/index.d.cts.map +1 -0
  78. package/types/index.d.ts +5 -0
  79. package/types/index.d.ts.map +1 -1
  80. package/types/modules/Emitter.d.ts +73 -30
  81. package/types/modules/Emitter.d.ts.map +1 -1
  82. package/types/modules/Renderer.d.ts +48 -18
  83. package/types/modules/Renderer.d.ts.map +1 -1
  84. package/types/modules/Signal.d.ts +44 -16
  85. package/types/modules/Signal.d.ts.map +1 -1
  86. package/types/modules/TemplateEngine.d.ts +46 -11
  87. package/types/modules/TemplateEngine.d.ts.map +1 -1
  88. package/types/plugins/Attr.d.ts +83 -16
  89. package/types/plugins/Attr.d.ts.map +1 -1
  90. package/types/plugins/Router.d.ts +498 -207
  91. package/types/plugins/Router.d.ts.map +1 -1
  92. package/types/plugins/Store.d.ts +211 -37
  93. package/types/plugins/Store.d.ts.map +1 -1
  94. package/dist/eleva-plugins.cjs.js.map +0 -1
  95. package/dist/eleva-plugins.esm.js.map +0 -1
  96. package/dist/eleva.cjs.js.map +0 -1
  97. package/dist/eleva.esm.js.map +0 -1
package/src/index.cjs ADDED
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * @module eleva
5
+ * @fileoverview CommonJS-only entry point for the Eleva framework.
6
+ */
7
+
8
+ import { Eleva } from "./core/Eleva.js";
9
+
10
+ export default Eleva;
package/src/index.js CHANGED
@@ -1,5 +1,16 @@
1
1
  "use strict";
2
2
 
3
+ /**
4
+ * @module eleva
5
+ * @fileoverview Main entry point for the Eleva framework.
6
+ */
7
+
3
8
  import { Eleva } from "./core/Eleva.js";
4
9
 
10
+ export * from "./core/Eleva.js";
11
+ export { Emitter } from "./modules/Emitter.js";
12
+ export { Signal } from "./modules/Signal.js";
13
+ export { TemplateEngine } from "./modules/TemplateEngine.js";
14
+ export { Renderer } from "./modules/Renderer.js";
15
+
5
16
  export default Eleva;
@@ -1,31 +1,67 @@
1
1
  "use strict";
2
2
 
3
+ /**
4
+ * @module eleva/emitter
5
+ * @fileoverview Event emitter for publish-subscribe communication between components.
6
+ */
7
+
3
8
  // ============================================================================
4
- // TYPE DEFINITIONS - TypeScript-friendly JSDoc types for IDE support
9
+ // TYPE DEFINITIONS
5
10
  // ============================================================================
6
11
 
12
+ // -----------------------------------------------------------------------------
13
+ // Callback Types
14
+ // -----------------------------------------------------------------------------
15
+
7
16
  /**
8
- * @template T
17
+ * Callback function invoked when an event is emitted.
9
18
  * @callback EventHandler
10
- * @param {...T} args - Event arguments
11
- * @returns {void|Promise<void>}
19
+ * @param {...any} args
20
+ * Event arguments passed to the handler.
21
+ * @returns {void | Promise<void>}
12
22
  */
13
23
 
14
24
  /**
25
+ * Function to unsubscribe an event handler.
15
26
  * @callback EventUnsubscribe
16
27
  * @returns {void}
17
28
  */
18
29
 
30
+ // -----------------------------------------------------------------------------
31
+ // Event Types
32
+ // -----------------------------------------------------------------------------
33
+
19
34
  /**
20
- * @typedef {`${string}:${string}`} EventName
21
- * Event names follow the format 'namespace:action' (e.g., 'user:login', 'cart:update')
35
+ * Event name string identifier.
36
+ * @typedef {string} EventName
37
+ * @description
38
+ * Recommended convention: 'namespace:action' (e.g., 'user:login').
39
+ * This pattern prevents naming collisions and improves code readability.
40
+ *
41
+ * Common namespaces:
42
+ * - `user:` - User-related events (login, logout, update)
43
+ * - `component:` - Component lifecycle events (mount, unmount)
44
+ * - `router:` - Navigation events (beforeEach, afterEach)
45
+ * - `store:` - State management events (change, error)
46
+ * @example
47
+ * 'user:login' // User logged in
48
+ * 'cart:update' // Shopping cart updated
49
+ * 'component:mount' // Component was mounted
22
50
  */
23
51
 
52
+ // -----------------------------------------------------------------------------
53
+ // Interface Types
54
+ // -----------------------------------------------------------------------------
55
+
24
56
  /**
57
+ * Interface describing the public API of an Emitter.
25
58
  * @typedef {Object} EmitterLike
26
- * @property {function(string, EventHandler<unknown>): EventUnsubscribe} on - Subscribe to an event
27
- * @property {function(string, EventHandler<unknown>=): void} off - Unsubscribe from an event
28
- * @property {function(string, ...unknown): void} emit - Emit an event
59
+ * @property {(event: string, handler: EventHandler) => EventUnsubscribe} on
60
+ * Subscribe to an event.
61
+ * @property {(event: string, handler?: EventHandler) => void} off
62
+ * Unsubscribe from an event.
63
+ * @property {(event: string, ...args: unknown[]) => void} emit
64
+ * Emit an event with arguments.
29
65
  */
30
66
 
31
67
  /**
@@ -65,6 +101,7 @@
65
101
  * // Lifecycle events
66
102
  * emitter.on('component:mount', (component) => {});
67
103
  * emitter.on('component:unmount', (component) => {});
104
+ * // Note: These lifecycle names are conventions; Eleva core does not emit them by default.
68
105
  * // State events
69
106
  * emitter.on('state:change', (newState, oldState) => {});
70
107
  * // Navigation events
@@ -74,9 +111,10 @@
74
111
  */
75
112
  export class Emitter {
76
113
  /**
77
- * Creates a new Emitter instance.
114
+ * Creates a new Emitter instance with an empty event registry.
78
115
  *
79
116
  * @public
117
+ * @constructor
80
118
  *
81
119
  * @example
82
120
  * const emitter = new Emitter();
@@ -85,7 +123,7 @@ export class Emitter {
85
123
  /**
86
124
  * Map of event names to their registered handler functions
87
125
  * @private
88
- * @type {Map<string, Set<EventHandler<unknown>>>}
126
+ * @type {Map<string, Set<EventHandler>>}
89
127
  */
90
128
  this._events = new Map();
91
129
  }
@@ -96,9 +134,10 @@ export class Emitter {
96
134
  * Event names should follow the format 'namespace:action' for consistency.
97
135
  *
98
136
  * @public
99
- * @template T
100
137
  * @param {string} event - The name of the event to listen for (e.g., 'user:login').
101
- * @param {EventHandler<T>} handler - The callback function to invoke when the event occurs.
138
+ * @param {EventHandler} handler - The callback function to invoke when the event occurs.
139
+ * Note: Handlers returning Promises are NOT awaited. For async operations,
140
+ * handle promise resolution within your handler.
102
141
  * @returns {EventUnsubscribe} A function to unsubscribe the event handler.
103
142
  *
104
143
  * @example
@@ -106,8 +145,8 @@ export class Emitter {
106
145
  * const unsubscribe = emitter.on('user:login', (user) => console.log(user));
107
146
  *
108
147
  * @example
109
- * // Typed handler
110
- * emitter.on('user:update', (/** @type {{id: number, name: string}} *\/ user) => {
148
+ * // Handler with typed parameter
149
+ * emitter.on('user:update', (user) => {
111
150
  * console.log(`User ${user.id}: ${user.name}`);
112
151
  * });
113
152
  *
@@ -124,13 +163,15 @@ export class Emitter {
124
163
 
125
164
  /**
126
165
  * Removes an event handler for the specified event name.
127
- * If no handler is provided, all handlers for the event are removed.
128
166
  * Automatically cleans up empty event sets to prevent memory leaks.
129
167
  *
168
+ * Behavior varies based on whether handler is provided:
169
+ * - With handler: Removes only that specific handler function (O(1) Set deletion)
170
+ * - Without handler: Removes ALL handlers for the event (O(1) Map deletion)
171
+ *
130
172
  * @public
131
- * @template T
132
173
  * @param {string} event - The name of the event to remove handlers from.
133
- * @param {EventHandler<T>} [handler] - The specific handler function to remove.
174
+ * @param {EventHandler} [handler] - The specific handler to remove. If omitted, all handlers are removed.
134
175
  * @returns {void}
135
176
  *
136
177
  * @example
@@ -158,12 +199,19 @@ export class Emitter {
158
199
  * Emits an event with the specified data to all registered handlers.
159
200
  * Handlers are called synchronously in the order they were registered.
160
201
  * If no handlers are registered for the event, the emission is silently ignored.
202
+ * Handlers that return promises are not awaited.
203
+ *
204
+ * Error propagation behavior:
205
+ * - If a handler throws synchronously, the error propagates immediately
206
+ * - Remaining handlers in the iteration are NOT called after an error
207
+ * - For error-resilient emission, wrap your emit call in try/catch
208
+ * - Async handler rejections are not caught (fire-and-forget)
161
209
  *
162
210
  * @public
163
- * @template T
164
211
  * @param {string} event - The name of the event to emit.
165
- * @param {...T} args - Optional arguments to pass to the event handlers.
212
+ * @param {...any} args - Optional arguments to pass to the event handlers.
166
213
  * @returns {void}
214
+ * @throws {Error} If a handler throws synchronously, the error propagates to the caller.
167
215
  *
168
216
  * @example
169
217
  * // Emit an event with data
@@ -1,21 +1,51 @@
1
1
  "use strict";
2
2
 
3
+ /**
4
+ * @module eleva/renderer
5
+ * @fileoverview High-performance DOM renderer with two-pointer diffing and keyed reconciliation.
6
+ */
7
+
3
8
  // ============================================================================
4
- // TYPE DEFINITIONS - TypeScript-friendly JSDoc types for IDE support
9
+ // TYPE DEFINITIONS
5
10
  // ============================================================================
6
11
 
12
+ // -----------------------------------------------------------------------------
13
+ // Data Types
14
+ // -----------------------------------------------------------------------------
15
+
7
16
  /**
17
+ * Map of key attribute values to their corresponding DOM nodes.
8
18
  * @typedef {Map<string, Node>} KeyMap
9
- * Map of key attribute values to their corresponding DOM nodes for O(1) lookup
19
+ * @description Enables O(1) lookup for keyed element reconciliation.
10
20
  */
11
21
 
22
+ // -----------------------------------------------------------------------------
23
+ // Interface Types
24
+ // -----------------------------------------------------------------------------
25
+
12
26
  /**
27
+ * Interface describing the public API of a Renderer.
13
28
  * @typedef {Object} RendererLike
14
- * @property {function(HTMLElement, string): void} patchDOM - Patches the DOM with new HTML
29
+ * @property {function(HTMLElement, string): void} patchDOM
30
+ * Patches the DOM with new HTML content.
31
+ * @description
32
+ * Plugins may extend renderer behavior by wrapping private methods (e.g., `_patchNode`),
33
+ * but those hooks are not part of the public API.
15
34
  */
16
35
 
36
+ // ============================================================================
37
+ // CONSTANTS
38
+ // ============================================================================
39
+
17
40
  /**
18
- * Properties that can diverge from attributes via user interaction.
41
+ * Properties that can diverge from attributes after user interaction.
42
+ * These are synchronized during DOM patching to ensure element state
43
+ * matches the rendered HTML attributes.
44
+ *
45
+ * - `value`: Text input, textarea, select element values
46
+ * - `checked`: Checkbox and radio button states
47
+ * - `selected`: Option element selection states
48
+ *
19
49
  * @private
20
50
  * @type {string[]}
21
51
  */
@@ -55,8 +85,13 @@ const SYNC_PROPS = ["value", "checked", "selected"];
55
85
  export class Renderer {
56
86
  /**
57
87
  * Creates a new Renderer instance.
88
+ * Initializes a reusable temporary container for HTML parsing.
89
+ *
90
+ * Performance: The temp container is reused across all patch operations,
91
+ * minimizing memory allocation overhead (O(1) memory per Renderer instance).
58
92
  *
59
93
  * @public
94
+ * @constructor
60
95
  *
61
96
  * @example
62
97
  * const renderer = new Renderer();
@@ -93,6 +128,9 @@ export class Renderer {
93
128
  * @example
94
129
  * // Empty the container
95
130
  * renderer.patchDOM(container, '');
131
+ *
132
+ * @see _diff - Low-level diffing algorithm.
133
+ * @see _patchNode - Individual node patching.
96
134
  */
97
135
  patchDOM(container, newHtml) {
98
136
  this._tempContainer.innerHTML = newHtml;
@@ -104,16 +142,26 @@ export class Renderer {
104
142
  /**
105
143
  * Performs a diff between two DOM nodes and patches the old node to match the new node.
106
144
  * Uses a two-pointer algorithm with key-based reconciliation for optimal performance.
145
+ * This method modifies oldParent in-place - it is not a pure function.
146
+ *
147
+ * Algorithm details:
148
+ * 1. Early exit if both nodes have no children (O(1) leaf node optimization)
149
+ * 2. Convert NodeLists to arrays for indexed access
150
+ * 3. Initialize two-pointer indices (oldStart/oldEnd, newStart/newEnd)
151
+ * 4. While pointers haven't crossed:
152
+ * a. Skip null entries (from previous moves)
153
+ * b. If nodes match (same key+tag or same type+name): patch and advance
154
+ * c. On mismatch: lazily build key→node map for O(1) lookup
155
+ * d. If keyed match found: move existing node (preserves DOM identity)
156
+ * e. Otherwise: clone and insert new node
157
+ * 5. After loop: append remaining new nodes or remove remaining old nodes
107
158
  *
108
- * Algorithm overview:
109
- * 1. Compare children from start using two pointers
110
- * 2. For mismatches, build a key map lazily for O(1) lookup
111
- * 3. Move or insert nodes as needed
112
- * 4. Clean up remaining nodes at the end
159
+ * Complexity: O(n) for most cases, O(n²) worst case with no keys.
160
+ * Non-keyed elements are matched by position and tag name.
113
161
  *
114
162
  * @private
115
- * @param {HTMLElement} oldParent - The original DOM element to update.
116
- * @param {HTMLElement} newParent - The new DOM element with desired state.
163
+ * @param {Element} oldParent - The original DOM element to update (modified in-place).
164
+ * @param {Element} newParent - The new DOM element with desired state.
117
165
  * @returns {void}
118
166
  */
119
167
  _diff(oldParent, newParent) {
@@ -184,7 +232,8 @@ export class Renderer {
184
232
 
185
233
  /**
186
234
  * Patches a single node, updating its content and attributes to match the new node.
187
- * Handles text nodes by updating nodeValue, and element nodes by updating attributes
235
+ * Handles text nodes (nodeType 3 / Node.TEXT_NODE) by updating nodeValue,
236
+ * and element nodes (nodeType 1 / Node.ELEMENT_NODE) by updating attributes
188
237
  * and recursively diffing children.
189
238
  *
190
239
  * Skips nodes that are managed by Eleva component instances to prevent interference
@@ -212,12 +261,20 @@ export class Renderer {
212
261
  /**
213
262
  * Removes a node from its parent, with special handling for Eleva-managed elements.
214
263
  * Style elements with the `data-e-style` attribute are preserved to maintain
215
- * component-scoped styles across re-renders.
264
+ * component styles across re-renders. Without this protection, component styles
265
+ * would be removed during DOM diffing and lost until the next full re-render.
266
+ *
267
+ * @note Style tags persist for the component's entire lifecycle. If the template
268
+ * conditionally removes elements that the CSS rules target (e.g., `.foo` elements),
269
+ * the style rules remain but simply have no matching elements. This is expected
270
+ * behavior - styles are cleaned up when the component unmounts, not when individual
271
+ * elements are removed.
216
272
  *
217
273
  * @private
218
274
  * @param {HTMLElement} parent - The parent element containing the node.
219
275
  * @param {Node} node - The node to remove.
220
276
  * @returns {void}
277
+ * @see _injectStyles - Where data-e-style elements are created.
221
278
  */
222
279
  _removeNode(parent, node) {
223
280
  // Preserve Eleva-managed style elements
@@ -230,12 +287,16 @@ export class Renderer {
230
287
  * Adds new attributes, updates changed values, and removes attributes no longer present.
231
288
  * Also syncs DOM properties that can diverge from attributes after user interaction.
232
289
  *
233
- * Event attributes (prefixed with `@`) are skipped as they are handled separately
234
- * by Eleva's event binding system.
290
+ * Processing order:
291
+ * 1. Iterate new attributes, skip @ prefixed (event) attributes
292
+ * 2. Update attribute if value changed
293
+ * 3. Sync corresponding DOM property if writable (handles boolean conversion)
294
+ * 4. Iterate old attributes in reverse, remove if not in new element
295
+ * 5. Sync SYNC_PROPS (value, checked, selected) from new to old element
235
296
  *
236
297
  * @private
237
- * @param {HTMLElement} oldEl - The original element to update.
238
- * @param {HTMLElement} newEl - The new element with target attributes.
298
+ * @param {Element} oldEl - The original element to update.
299
+ * @param {Element} newEl - The new element with target attributes.
239
300
  * @returns {void}
240
301
  */
241
302
  _updateAttributes(oldEl, newEl) {
@@ -315,10 +376,11 @@ export class Renderer {
315
376
  /**
316
377
  * Extracts the key attribute from a node if it exists.
317
378
  * Only element nodes (nodeType === 1) can have key attributes.
379
+ * Uses optional chaining for null-safe access.
318
380
  *
319
381
  * @private
320
- * @param {Node|null|undefined} node - The node to extract the key from.
321
- * @returns {string|null} The key attribute value, or null if not an element or no key.
382
+ * @param {Node | null | undefined} node - The node to extract the key from.
383
+ * @returns {string | null} The key attribute value, or null if not an element or no key.
322
384
  */
323
385
  _getNodeKey(node) {
324
386
  return node?.nodeType === 1 ? node.getAttribute("key") : null;
@@ -329,7 +391,7 @@ export class Renderer {
329
391
  * The map is built lazily only when needed (when a mismatch occurs during diffing).
330
392
  *
331
393
  * @private
332
- * @param {Array<ChildNode>} children - The array of child nodes to map.
394
+ * @param {ChildNode[]} children - The array of child nodes to map.
333
395
  * @param {number} start - The start index (inclusive) for mapping.
334
396
  * @param {number} end - The end index (inclusive) for mapping.
335
397
  * @returns {KeyMap} A Map of key strings to their corresponding DOM nodes.
@@ -1,26 +1,47 @@
1
1
  "use strict";
2
2
 
3
+ /**
4
+ * @module eleva/signal
5
+ * @fileoverview Reactive Signal primitive for fine-grained state management and change notification.
6
+ */
7
+
3
8
  // ============================================================================
4
- // TYPE DEFINITIONS - TypeScript-friendly JSDoc types for IDE support
9
+ // TYPE DEFINITIONS
5
10
  // ============================================================================
6
11
 
12
+ // -----------------------------------------------------------------------------
13
+ // Callback Types
14
+ // -----------------------------------------------------------------------------
15
+
7
16
  /**
8
- * @template T
17
+ * Callback function invoked when a signal's value changes.
18
+ * @template T The type of value held by the signal.
9
19
  * @callback SignalWatcher
10
- * @param {T} value - The new value of the signal
20
+ * @param {T} value
21
+ * The new value of the signal.
11
22
  * @returns {void}
12
23
  */
13
24
 
14
25
  /**
26
+ * Function to unsubscribe a watcher from a signal.
15
27
  * @callback SignalUnsubscribe
16
- * @returns {boolean} True if the watcher was successfully removed
28
+ * @returns {boolean}
29
+ * True if the watcher was successfully removed, false if already removed.
30
+ * Safe to call multiple times (idempotent).
17
31
  */
18
32
 
33
+ // -----------------------------------------------------------------------------
34
+ // Interface Types
35
+ // -----------------------------------------------------------------------------
36
+
19
37
  /**
20
- * @template T
38
+ * Interface describing the public API of a Signal.
39
+ * @template T The type of value held by the signal.
21
40
  * @typedef {Object} SignalLike
22
- * @property {T} value - The current value
23
- * @property {function(SignalWatcher<T>): SignalUnsubscribe} watch - Subscribe to changes
41
+ * @property {T} value
42
+ * The current value of the signal.
43
+ * @property {function(SignalWatcher<T>): SignalUnsubscribe} watch
44
+ * Subscribe to value changes.
24
45
  */
25
46
 
26
47
  /**
@@ -32,7 +53,7 @@
32
53
  * Render batching is handled at the component level, not the signal level.
33
54
  * The class is generic, allowing type-safe handling of any value type T.
34
55
  *
35
- * @template T The type of value held by this signal
56
+ * @template T The type of value held by the signal.
36
57
  *
37
58
  * @example
38
59
  * // Basic usage
@@ -50,7 +71,6 @@
50
71
  *
51
72
  * @example
52
73
  * // With objects
53
- * /** @type {Signal<{x: number, y: number}>} *\/
54
74
  * const position = new Signal({ x: 0, y: 0 });
55
75
  * position.value = { x: 10, y: 20 }; // Triggers watchers
56
76
  *
@@ -61,6 +81,7 @@ export class Signal {
61
81
  * Creates a new Signal instance with the specified initial value.
62
82
  *
63
83
  * @public
84
+ * @constructor
64
85
  * @param {T} value - The initial value of the signal.
65
86
  *
66
87
  * @example
@@ -70,12 +91,9 @@ export class Signal {
70
91
  * const active = new Signal(true); // Signal<boolean>
71
92
  *
72
93
  * @example
73
- * // Complex types (use JSDoc for type inference)
74
- * /** @type {Signal<string[]>} *\/
75
- * const items = new Signal([]);
76
- *
77
- * /** @type {Signal<{id: number, name: string} | null>} *\/
78
- * const user = new Signal(null);
94
+ * // Complex types
95
+ * const items = new Signal([]); // Signal holding an array
96
+ * const user = new Signal(null); // Signal holding nullable object
79
97
  */
80
98
  constructor(value) {
81
99
  /**
@@ -106,6 +124,10 @@ export class Signal {
106
124
  * Sets a new value for the signal and synchronously notifies all registered watchers if the value has changed.
107
125
  * Synchronous notification preserves stack traces and ensures immediate value consistency.
108
126
  *
127
+ * Uses strict equality (===) for comparison. For objects/arrays, watchers are only notified
128
+ * if the reference changes, not if properties are mutated. To trigger updates with objects,
129
+ * assign a new reference: `signal.value = { ...signal.value, updated: true }`.
130
+ *
109
131
  * @public
110
132
  * @param {T} newVal - The new value to set.
111
133
  * @returns {void}
@@ -124,6 +146,8 @@ export class Signal {
124
146
  * @public
125
147
  * @param {SignalWatcher<T>} fn - The callback function to invoke on value change.
126
148
  * @returns {SignalUnsubscribe} A function to unsubscribe the watcher.
149
+ * Returns true if watcher was removed, false if it wasn't registered.
150
+ * Safe to call multiple times (idempotent after first call).
127
151
  *
128
152
  * @example
129
153
  * // Basic watching
@@ -132,6 +156,7 @@ export class Signal {
132
156
  * @example
133
157
  * // Stop watching
134
158
  * unsubscribe(); // Returns true if watcher was removed
159
+ * unsubscribe(); // Returns false (already removed, safe to call again)
135
160
  *
136
161
  * @example
137
162
  * // Multiple watchers
@@ -149,6 +174,9 @@ export class Signal {
149
174
  * This preserves stack traces for debugging and ensures immediate
150
175
  * value consistency. Render batching is handled at the component level.
151
176
  *
177
+ * @note If a watcher throws, subsequent watchers are NOT called.
178
+ * The error propagates to the caller (the setter).
179
+ *
152
180
  * @private
153
181
  * @returns {void}
154
182
  */
@@ -1,22 +1,44 @@
1
1
  "use strict";
2
2
 
3
+ /**
4
+ * @module eleva/template-engine
5
+ * @fileoverview Expression evaluator for directive attributes and property bindings.
6
+ */
7
+
3
8
  // ============================================================================
4
- // TYPE DEFINITIONS - TypeScript-friendly JSDoc types for IDE support
9
+ // TYPE DEFINITIONS
5
10
  // ============================================================================
6
11
 
12
+ // -----------------------------------------------------------------------------
13
+ // Data Types
14
+ // -----------------------------------------------------------------------------
15
+
7
16
  /**
17
+ * Data context object for expression evaluation.
8
18
  * @typedef {Record<string, unknown>} ContextData
9
- * Data context for expression evaluation
19
+ * @description Contains variables and functions available during template evaluation.
10
20
  */
11
21
 
12
22
  /**
23
+ * JavaScript expression string to be evaluated.
13
24
  * @typedef {string} Expression
14
- * A JavaScript expression to be evaluated in the data context
25
+ * @description A JavaScript expression evaluated against a ContextData object.
15
26
  */
16
27
 
17
28
  /**
29
+ * Result of evaluating an expression.
18
30
  * @typedef {unknown} EvaluationResult
19
- * The result of evaluating an expression (string, number, boolean, object, function, etc.)
31
+ * @description Can be string, number, boolean, object, function, or any JavaScript value.
32
+ */
33
+
34
+ // -----------------------------------------------------------------------------
35
+ // Function Types
36
+ // -----------------------------------------------------------------------------
37
+
38
+ /**
39
+ * Compiled expression function cached for performance.
40
+ * @typedef {(data: ContextData) => EvaluationResult} CompiledExpressionFunction
41
+ * @description Pre-compiled function that evaluates an expression against context data.
20
42
  */
21
43
 
22
44
  /**
@@ -51,26 +73,45 @@ export class TemplateEngine {
51
73
  /**
52
74
  * Cache for compiled expression functions.
53
75
  * Stores compiled Function objects keyed by expression string for O(1) lookup.
76
+ * The cache persists for the application lifetime and is never cleared.
77
+ * This improves performance for repeated evaluations of the same expression.
78
+ *
79
+ * Memory consideration: For applications with highly dynamic expressions
80
+ * (e.g., user-generated), memory usage grows unbounded. This is typically
81
+ * not an issue for static templates where expressions are finite.
54
82
  *
55
83
  * @static
56
84
  * @private
57
- * @type {Map<string, Function>}
85
+ * @type {Map<string, CompiledExpressionFunction>}
58
86
  */
59
87
  static _functionCache = new Map();
60
88
 
61
89
  /**
62
90
  * Evaluates an expression in the context of the provided data object.
63
91
  * Used for resolving `@event` handlers and `:prop` bindings.
92
+ * Non-string expressions are returned as-is.
93
+ *
94
+ * @security CRITICAL SECURITY WARNING
95
+ * This method is NOT sandboxed. It uses `new Function()` and `with` statement,
96
+ * allowing full access to the global scope. Potential attack vectors include:
97
+ * - Code injection via malicious expressions
98
+ * - XSS attacks if user input is used as expressions
99
+ * - Access to sensitive globals (window, document, fetch, etc.)
100
+ *
101
+ * ONLY use with developer-defined template strings.
102
+ * NEVER use with user-provided input or untrusted data.
64
103
  *
65
- * Note: This does not provide a true sandbox and evaluated expressions may access global scope.
66
- * The use of the `with` statement is necessary for expression evaluation but has security implications.
67
- * Only use with trusted templates. User input should never be directly interpolated.
104
+ * Mitigation strategies:
105
+ * - Always sanitize any user-generated content before rendering in templates
106
+ * - Use Content Security Policy (CSP) headers to restrict script execution
107
+ * - Keep expressions simple (property access, method calls) - avoid complex logic
68
108
  *
69
109
  * @public
70
110
  * @static
71
- * @param {Expression|unknown} expression - The expression to evaluate.
111
+ * @param {Expression | unknown} expression - The expression to evaluate.
72
112
  * @param {ContextData} data - The data context for evaluation.
73
113
  * @returns {EvaluationResult} The result of the evaluation, or empty string if evaluation fails.
114
+ * @note Evaluation failures return an empty string without throwing.
74
115
  *
75
116
  * @example
76
117
  * // Property access