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.
- package/README.md +21 -10
- package/dist/{eleva-plugins.cjs.js → eleva-plugins.cjs} +1002 -292
- package/dist/eleva-plugins.cjs.map +1 -0
- package/dist/eleva-plugins.d.cts +1352 -0
- package/dist/eleva-plugins.d.cts.map +1 -0
- package/dist/eleva-plugins.d.ts +1352 -0
- package/dist/eleva-plugins.d.ts.map +1 -0
- package/dist/{eleva-plugins.esm.js → eleva-plugins.js} +1002 -292
- package/dist/eleva-plugins.js.map +1 -0
- package/dist/eleva-plugins.umd.js +1001 -291
- package/dist/eleva-plugins.umd.js.map +1 -1
- package/dist/eleva-plugins.umd.min.js +1 -1
- package/dist/eleva-plugins.umd.min.js.map +1 -1
- package/dist/{eleva.cjs.js → eleva.cjs} +421 -191
- package/dist/eleva.cjs.map +1 -0
- package/dist/eleva.d.cts +1329 -0
- package/dist/eleva.d.cts.map +1 -0
- package/dist/eleva.d.ts +473 -226
- package/dist/eleva.d.ts.map +1 -0
- package/dist/{eleva.esm.js → eleva.js} +422 -192
- package/dist/eleva.js.map +1 -0
- package/dist/eleva.umd.js +420 -190
- package/dist/eleva.umd.js.map +1 -1
- package/dist/eleva.umd.min.js +1 -1
- package/dist/eleva.umd.min.js.map +1 -1
- package/dist/plugins/attr.cjs +279 -0
- package/dist/plugins/attr.cjs.map +1 -0
- package/dist/plugins/attr.d.cts +101 -0
- package/dist/plugins/attr.d.cts.map +1 -0
- package/dist/plugins/attr.d.ts +101 -0
- package/dist/plugins/attr.d.ts.map +1 -0
- package/dist/plugins/attr.js +276 -0
- package/dist/plugins/attr.js.map +1 -0
- package/dist/plugins/attr.umd.js +111 -22
- package/dist/plugins/attr.umd.js.map +1 -1
- package/dist/plugins/attr.umd.min.js +1 -1
- package/dist/plugins/attr.umd.min.js.map +1 -1
- package/dist/plugins/router.cjs +1873 -0
- package/dist/plugins/router.cjs.map +1 -0
- package/dist/plugins/router.d.cts +1296 -0
- package/dist/plugins/router.d.cts.map +1 -0
- package/dist/plugins/router.d.ts +1296 -0
- package/dist/plugins/router.d.ts.map +1 -0
- package/dist/plugins/router.js +1870 -0
- package/dist/plugins/router.js.map +1 -0
- package/dist/plugins/router.umd.js +482 -186
- package/dist/plugins/router.umd.js.map +1 -1
- package/dist/plugins/router.umd.min.js +1 -1
- package/dist/plugins/router.umd.min.js.map +1 -1
- package/dist/plugins/store.cjs +920 -0
- package/dist/plugins/store.cjs.map +1 -0
- package/dist/plugins/store.d.cts +266 -0
- package/dist/plugins/store.d.cts.map +1 -0
- package/dist/plugins/store.d.ts +266 -0
- package/dist/plugins/store.d.ts.map +1 -0
- package/dist/plugins/store.js +917 -0
- package/dist/plugins/store.js.map +1 -0
- package/dist/plugins/store.umd.js +410 -85
- package/dist/plugins/store.umd.js.map +1 -1
- package/dist/plugins/store.umd.min.js +1 -1
- package/dist/plugins/store.umd.min.js.map +1 -1
- package/package.json +112 -68
- package/src/core/Eleva.js +195 -115
- package/src/index.cjs +10 -0
- package/src/index.js +11 -0
- package/src/modules/Emitter.js +68 -20
- package/src/modules/Renderer.js +82 -20
- package/src/modules/Signal.js +43 -15
- package/src/modules/TemplateEngine.js +50 -9
- package/src/plugins/Attr.js +121 -19
- package/src/plugins/Router.js +526 -181
- package/src/plugins/Store.js +448 -69
- package/src/plugins/index.js +1 -0
- package/types/core/Eleva.d.ts +263 -169
- package/types/core/Eleva.d.ts.map +1 -1
- package/types/index.d.cts +3 -0
- package/types/index.d.cts.map +1 -0
- package/types/index.d.ts +5 -0
- package/types/index.d.ts.map +1 -1
- package/types/modules/Emitter.d.ts +73 -30
- package/types/modules/Emitter.d.ts.map +1 -1
- package/types/modules/Renderer.d.ts +48 -18
- package/types/modules/Renderer.d.ts.map +1 -1
- package/types/modules/Signal.d.ts +44 -16
- package/types/modules/Signal.d.ts.map +1 -1
- package/types/modules/TemplateEngine.d.ts +46 -11
- package/types/modules/TemplateEngine.d.ts.map +1 -1
- package/types/plugins/Attr.d.ts +83 -16
- package/types/plugins/Attr.d.ts.map +1 -1
- package/types/plugins/Router.d.ts +498 -207
- package/types/plugins/Router.d.ts.map +1 -1
- package/types/plugins/Store.d.ts +211 -37
- package/types/plugins/Store.d.ts.map +1 -1
- package/dist/eleva-plugins.cjs.js.map +0 -1
- package/dist/eleva-plugins.esm.js.map +0 -1
- package/dist/eleva.cjs.js.map +0 -1
- package/dist/eleva.esm.js.map +0 -1
package/src/index.cjs
ADDED
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;
|
package/src/modules/Emitter.js
CHANGED
|
@@ -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
|
|
9
|
+
// TYPE DEFINITIONS
|
|
5
10
|
// ============================================================================
|
|
6
11
|
|
|
12
|
+
// -----------------------------------------------------------------------------
|
|
13
|
+
// Callback Types
|
|
14
|
+
// -----------------------------------------------------------------------------
|
|
15
|
+
|
|
7
16
|
/**
|
|
8
|
-
*
|
|
17
|
+
* Callback function invoked when an event is emitted.
|
|
9
18
|
* @callback EventHandler
|
|
10
|
-
* @param {...
|
|
11
|
-
*
|
|
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
|
-
*
|
|
21
|
-
*
|
|
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 {
|
|
27
|
-
*
|
|
28
|
-
* @property {
|
|
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
|
|
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
|
|
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
|
-
* //
|
|
110
|
-
* emitter.on('user:update', (
|
|
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
|
|
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 {...
|
|
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
|
package/src/modules/Renderer.js
CHANGED
|
@@ -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
|
|
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
|
-
*
|
|
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
|
|
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
|
|
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
|
-
*
|
|
109
|
-
*
|
|
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 {
|
|
116
|
-
* @param {
|
|
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
|
|
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
|
|
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
|
-
*
|
|
234
|
-
*
|
|
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 {
|
|
238
|
-
* @param {
|
|
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 {
|
|
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.
|
package/src/modules/Signal.js
CHANGED
|
@@ -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
|
|
9
|
+
// TYPE DEFINITIONS
|
|
5
10
|
// ============================================================================
|
|
6
11
|
|
|
12
|
+
// -----------------------------------------------------------------------------
|
|
13
|
+
// Callback Types
|
|
14
|
+
// -----------------------------------------------------------------------------
|
|
15
|
+
|
|
7
16
|
/**
|
|
8
|
-
*
|
|
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
|
|
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}
|
|
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
|
-
*
|
|
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
|
|
23
|
-
*
|
|
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
|
|
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
|
|
74
|
-
*
|
|
75
|
-
* const
|
|
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
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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,
|
|
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
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
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
|