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/core/Eleva.js
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* @module eleva
|
|
5
|
+
* @fileoverview Core Eleva framework providing signal-based component lifecycle management,
|
|
6
|
+
* reactive rendering, and plugin architecture.
|
|
7
|
+
*/
|
|
8
|
+
|
|
3
9
|
import { TemplateEngine } from "../modules/TemplateEngine.js";
|
|
4
10
|
import { Signal } from "../modules/Signal.js";
|
|
5
11
|
import { Emitter } from "../modules/Emitter.js";
|
|
6
12
|
import { Renderer } from "../modules/Renderer.js";
|
|
7
13
|
|
|
8
14
|
// ============================================================================
|
|
9
|
-
// TYPE DEFINITIONS
|
|
15
|
+
// TYPE DEFINITIONS
|
|
10
16
|
// ============================================================================
|
|
11
17
|
|
|
12
18
|
// -----------------------------------------------------------------------------
|
|
@@ -14,13 +20,8 @@ import { Renderer } from "../modules/Renderer.js";
|
|
|
14
20
|
// -----------------------------------------------------------------------------
|
|
15
21
|
|
|
16
22
|
/**
|
|
17
|
-
*
|
|
18
|
-
* @
|
|
19
|
-
* Enable debug mode for verbose logging
|
|
20
|
-
* @property {string} [prefix='e']
|
|
21
|
-
* Prefix for component style scoping
|
|
22
|
-
* @property {boolean} [async=true]
|
|
23
|
-
* Enable async component setup
|
|
23
|
+
* Configuration options for the Eleva instance (reserved for future use).
|
|
24
|
+
* @typedef {Record<string, unknown>} ElevaConfig
|
|
24
25
|
*/
|
|
25
26
|
|
|
26
27
|
// -----------------------------------------------------------------------------
|
|
@@ -28,43 +29,54 @@ import { Renderer } from "../modules/Renderer.js";
|
|
|
28
29
|
// -----------------------------------------------------------------------------
|
|
29
30
|
|
|
30
31
|
/**
|
|
32
|
+
* Component definition object.
|
|
31
33
|
* @typedef {Object} ComponentDefinition
|
|
32
34
|
* @property {SetupFunction} [setup]
|
|
33
|
-
* Optional setup function that initializes the component's state and returns reactive data
|
|
34
|
-
* @property {TemplateFunction|string} template
|
|
35
|
-
* Required function or string that defines the component's HTML structure
|
|
36
|
-
* @property {StyleFunction|string} [style]
|
|
37
|
-
* Optional function or string that provides
|
|
35
|
+
* Optional setup function that initializes the component's state and returns reactive data.
|
|
36
|
+
* @property {TemplateFunction | string} template
|
|
37
|
+
* Required function or string that defines the component's HTML structure.
|
|
38
|
+
* @property {StyleFunction | string} [style]
|
|
39
|
+
* Optional function or string that provides CSS styles for the component.
|
|
40
|
+
* Styles are preserved across DOM diffs via data-e-style markers.
|
|
38
41
|
* @property {ChildrenMap} [children]
|
|
39
|
-
* Optional object defining nested child components
|
|
42
|
+
* Optional object defining nested child components.
|
|
40
43
|
*/
|
|
41
44
|
|
|
42
45
|
/**
|
|
46
|
+
* Setup function that initializes component state.
|
|
43
47
|
* @callback SetupFunction
|
|
44
|
-
* @param {ComponentContext} ctx
|
|
45
|
-
*
|
|
48
|
+
* @param {ComponentContext} ctx
|
|
49
|
+
* The component context with props, emitter, and signal factory.
|
|
50
|
+
* @returns {SetupResult | Promise<SetupResult>}
|
|
51
|
+
* Reactive data and lifecycle hooks.
|
|
46
52
|
*/
|
|
47
53
|
|
|
48
54
|
/**
|
|
55
|
+
* Data returned from setup function, may include lifecycle hooks.
|
|
49
56
|
* @typedef {Record<string, unknown> & LifecycleHooks} SetupResult
|
|
50
|
-
* Data returned from setup function, may include lifecycle hooks
|
|
51
57
|
*/
|
|
52
58
|
|
|
53
59
|
/**
|
|
60
|
+
* Template function that returns HTML markup.
|
|
54
61
|
* @callback TemplateFunction
|
|
55
|
-
* @param {ComponentContext} ctx
|
|
56
|
-
*
|
|
62
|
+
* @param {ComponentContext & SetupResult} ctx
|
|
63
|
+
* The merged component context and setup data.
|
|
64
|
+
* @returns {string | Promise<string>}
|
|
65
|
+
* HTML template string.
|
|
57
66
|
*/
|
|
58
67
|
|
|
59
68
|
/**
|
|
69
|
+
* Style function that returns CSS styles.
|
|
60
70
|
* @callback StyleFunction
|
|
61
|
-
* @param {ComponentContext} ctx
|
|
62
|
-
*
|
|
71
|
+
* @param {ComponentContext & SetupResult} ctx
|
|
72
|
+
* The merged component context and setup data.
|
|
73
|
+
* @returns {string}
|
|
74
|
+
* CSS styles string.
|
|
63
75
|
*/
|
|
64
76
|
|
|
65
77
|
/**
|
|
66
|
-
*
|
|
67
|
-
*
|
|
78
|
+
* Map of CSS selectors to component definitions or registered component names.
|
|
79
|
+
* @typedef {Record<string, ComponentDefinition | string>} ChildrenMap
|
|
68
80
|
*/
|
|
69
81
|
|
|
70
82
|
// -----------------------------------------------------------------------------
|
|
@@ -72,25 +84,28 @@ import { Renderer } from "../modules/Renderer.js";
|
|
|
72
84
|
// -----------------------------------------------------------------------------
|
|
73
85
|
|
|
74
86
|
/**
|
|
87
|
+
* Context passed to component setup function.
|
|
75
88
|
* @typedef {Object} ComponentContext
|
|
76
89
|
* @property {ComponentProps} props
|
|
77
|
-
* Component properties passed during mounting
|
|
90
|
+
* Component properties passed during mounting.
|
|
78
91
|
* @property {Emitter} emitter
|
|
79
|
-
* Event emitter instance for component event handling
|
|
92
|
+
* Event emitter instance for component event handling.
|
|
80
93
|
* @property {SignalFactory} signal
|
|
81
|
-
* Factory function to create reactive Signal instances
|
|
94
|
+
* Factory function to create reactive Signal instances.
|
|
95
|
+
* @description
|
|
96
|
+
* Plugins may extend this context with additional properties (e.g., `ctx.router`, `ctx.store`).
|
|
97
|
+
* @see RouterContext - Router plugin injected context.
|
|
98
|
+
* @see StoreApi - Store plugin injected context.
|
|
82
99
|
*/
|
|
83
100
|
|
|
84
101
|
/**
|
|
102
|
+
* Properties passed to a component during mounting.
|
|
85
103
|
* @typedef {Record<string, unknown>} ComponentProps
|
|
86
|
-
* Properties passed to a component during mounting
|
|
87
104
|
*/
|
|
88
105
|
|
|
89
106
|
/**
|
|
90
|
-
*
|
|
91
|
-
* @
|
|
92
|
-
* @param {T} initialValue - The initial value for the signal
|
|
93
|
-
* @returns {Signal<T>} A new Signal instance
|
|
107
|
+
* Factory function to create reactive Signal instances.
|
|
108
|
+
* @typedef {<T>(initialValue: T) => Signal<T>} SignalFactory
|
|
94
109
|
*/
|
|
95
110
|
|
|
96
111
|
// -----------------------------------------------------------------------------
|
|
@@ -98,57 +113,65 @@ import { Renderer } from "../modules/Renderer.js";
|
|
|
98
113
|
// -----------------------------------------------------------------------------
|
|
99
114
|
|
|
100
115
|
/**
|
|
116
|
+
* Lifecycle hooks that can be returned from setup function.
|
|
101
117
|
* @typedef {Object} LifecycleHooks
|
|
102
118
|
* @property {LifecycleHook} [onBeforeMount]
|
|
103
|
-
*
|
|
119
|
+
* Called before component mounting.
|
|
104
120
|
* @property {LifecycleHook} [onMount]
|
|
105
|
-
*
|
|
121
|
+
* Called after component mounting.
|
|
106
122
|
* @property {LifecycleHook} [onBeforeUpdate]
|
|
107
|
-
*
|
|
123
|
+
* Called before component update.
|
|
108
124
|
* @property {LifecycleHook} [onUpdate]
|
|
109
|
-
*
|
|
125
|
+
* Called after component update.
|
|
110
126
|
* @property {UnmountHook} [onUnmount]
|
|
111
|
-
*
|
|
127
|
+
* Called during component unmounting.
|
|
112
128
|
*/
|
|
113
129
|
|
|
114
130
|
/**
|
|
131
|
+
* Lifecycle hook function.
|
|
115
132
|
* @callback LifecycleHook
|
|
116
|
-
* @param {LifecycleHookContext} ctx
|
|
117
|
-
*
|
|
133
|
+
* @param {LifecycleHookContext} ctx
|
|
134
|
+
* Context with container and component data.
|
|
135
|
+
* @returns {void | Promise<void>}
|
|
118
136
|
*/
|
|
119
137
|
|
|
120
138
|
/**
|
|
139
|
+
* Unmount hook function with cleanup resources.
|
|
121
140
|
* @callback UnmountHook
|
|
122
|
-
* @param {UnmountHookContext} ctx
|
|
123
|
-
*
|
|
141
|
+
* @param {UnmountHookContext} ctx
|
|
142
|
+
* Context with cleanup resources.
|
|
143
|
+
* @returns {void | Promise<void>}
|
|
124
144
|
*/
|
|
125
145
|
|
|
126
146
|
/**
|
|
147
|
+
* Context passed to lifecycle hooks.
|
|
127
148
|
* @typedef {Object} LifecycleHookContext
|
|
128
149
|
* @property {HTMLElement} container
|
|
129
|
-
* The DOM element where the component is mounted
|
|
150
|
+
* The DOM element where the component is mounted.
|
|
130
151
|
* @property {ComponentContext & SetupResult} context
|
|
131
|
-
* The component's reactive state and context data
|
|
152
|
+
* The component's reactive state and context data.
|
|
132
153
|
*/
|
|
133
154
|
|
|
134
155
|
/**
|
|
156
|
+
* Context passed to unmount hook with cleanup resources.
|
|
135
157
|
* @typedef {Object} UnmountHookContext
|
|
136
158
|
* @property {HTMLElement} container
|
|
137
|
-
* The DOM element where the component is mounted
|
|
159
|
+
* The DOM element where the component is mounted.
|
|
138
160
|
* @property {ComponentContext & SetupResult} context
|
|
139
|
-
* The component's reactive state and context data
|
|
161
|
+
* The component's reactive state and context data.
|
|
140
162
|
* @property {CleanupResources} cleanup
|
|
141
|
-
* Object containing cleanup functions and instances
|
|
163
|
+
* Object containing cleanup functions and instances.
|
|
142
164
|
*/
|
|
143
165
|
|
|
144
166
|
/**
|
|
167
|
+
* Resources available for cleanup during unmount.
|
|
145
168
|
* @typedef {Object} CleanupResources
|
|
146
|
-
* @property {
|
|
147
|
-
* Signal watcher cleanup functions
|
|
148
|
-
* @property {
|
|
149
|
-
* Event listener cleanup functions
|
|
150
|
-
* @property {
|
|
151
|
-
* Child component instances
|
|
169
|
+
* @property {UnsubscribeFunction[]} watchers
|
|
170
|
+
* Signal watcher cleanup functions.
|
|
171
|
+
* @property {UnsubscribeFunction[]} listeners
|
|
172
|
+
* Event listener cleanup functions.
|
|
173
|
+
* @property {MountResult[]} children
|
|
174
|
+
* Child component instances.
|
|
152
175
|
*/
|
|
153
176
|
|
|
154
177
|
// -----------------------------------------------------------------------------
|
|
@@ -156,23 +179,26 @@ import { Renderer } from "../modules/Renderer.js";
|
|
|
156
179
|
// -----------------------------------------------------------------------------
|
|
157
180
|
|
|
158
181
|
/**
|
|
182
|
+
* Result of mounting a component.
|
|
159
183
|
* @typedef {Object} MountResult
|
|
160
184
|
* @property {HTMLElement} container
|
|
161
|
-
* The DOM element where the component is mounted
|
|
185
|
+
* The DOM element where the component is mounted.
|
|
162
186
|
* @property {ComponentContext & SetupResult} data
|
|
163
|
-
* The component's reactive state and context data
|
|
187
|
+
* The component's reactive state and context data.
|
|
164
188
|
* @property {UnmountFunction} unmount
|
|
165
|
-
* Function to clean up and unmount the component
|
|
189
|
+
* Function to clean up and unmount the component.
|
|
166
190
|
*/
|
|
167
191
|
|
|
168
192
|
/**
|
|
193
|
+
* Function to unmount a component and clean up resources.
|
|
169
194
|
* @callback UnmountFunction
|
|
170
195
|
* @returns {Promise<void>}
|
|
171
196
|
*/
|
|
172
197
|
|
|
173
198
|
/**
|
|
199
|
+
* Function to unsubscribe from events or watchers.
|
|
174
200
|
* @callback UnsubscribeFunction
|
|
175
|
-
* @returns {void|boolean}
|
|
201
|
+
* @returns {void | boolean}
|
|
176
202
|
*/
|
|
177
203
|
|
|
178
204
|
// -----------------------------------------------------------------------------
|
|
@@ -180,31 +206,39 @@ import { Renderer } from "../modules/Renderer.js";
|
|
|
180
206
|
// -----------------------------------------------------------------------------
|
|
181
207
|
|
|
182
208
|
/**
|
|
209
|
+
* Plugin interface for extending Eleva.
|
|
183
210
|
* @typedef {Object} ElevaPlugin
|
|
184
|
-
* @property {PluginInstallFunction} install
|
|
185
|
-
* Function that installs the plugin into the Eleva instance
|
|
186
211
|
* @property {string} name
|
|
187
|
-
* Unique identifier name for the plugin
|
|
212
|
+
* Unique identifier name for the plugin.
|
|
213
|
+
* @property {string} [version]
|
|
214
|
+
* Optional version string for the plugin.
|
|
215
|
+
* @property {PluginInstallFunction} install
|
|
216
|
+
* Function that installs the plugin.
|
|
188
217
|
* @property {PluginUninstallFunction} [uninstall]
|
|
189
|
-
* Optional function to uninstall the plugin
|
|
218
|
+
* Optional function to uninstall the plugin.
|
|
190
219
|
*/
|
|
191
220
|
|
|
192
221
|
/**
|
|
222
|
+
* Plugin install function.
|
|
193
223
|
* @callback PluginInstallFunction
|
|
194
|
-
* @param {Eleva} eleva
|
|
195
|
-
*
|
|
196
|
-
* @
|
|
224
|
+
* @param {Eleva} eleva
|
|
225
|
+
* The Eleva instance.
|
|
226
|
+
* @param {PluginOptions} [options]
|
|
227
|
+
* Plugin configuration options.
|
|
228
|
+
* @returns {void | Eleva | unknown}
|
|
197
229
|
*/
|
|
198
230
|
|
|
199
231
|
/**
|
|
232
|
+
* Plugin uninstall function.
|
|
200
233
|
* @callback PluginUninstallFunction
|
|
201
|
-
* @param {Eleva} eleva
|
|
202
|
-
*
|
|
234
|
+
* @param {Eleva} eleva
|
|
235
|
+
* The Eleva instance.
|
|
236
|
+
* @returns {void | Promise<void>}
|
|
203
237
|
*/
|
|
204
238
|
|
|
205
239
|
/**
|
|
240
|
+
* Configuration options passed to a plugin during installation.
|
|
206
241
|
* @typedef {Record<string, unknown>} PluginOptions
|
|
207
|
-
* Configuration options passed to a plugin during installation
|
|
208
242
|
*/
|
|
209
243
|
|
|
210
244
|
// -----------------------------------------------------------------------------
|
|
@@ -212,20 +246,19 @@ import { Renderer } from "../modules/Renderer.js";
|
|
|
212
246
|
// -----------------------------------------------------------------------------
|
|
213
247
|
|
|
214
248
|
/**
|
|
215
|
-
*
|
|
216
|
-
* @
|
|
217
|
-
* @returns {void}
|
|
249
|
+
* Handler function for DOM events (e.g., click, input, submit).
|
|
250
|
+
* @typedef {(event: Event) => void} DOMEventHandler
|
|
218
251
|
*/
|
|
219
252
|
|
|
220
253
|
/**
|
|
254
|
+
* Common DOM event names (prefixed with @ in templates).
|
|
221
255
|
* @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
|
|
222
|
-
* Common DOM event names (prefixed with @ in templates)
|
|
223
256
|
*/
|
|
224
257
|
|
|
225
258
|
/**
|
|
226
259
|
* @class 🧩 Eleva
|
|
227
260
|
* @classdesc A modern, signal-based component runtime framework that provides lifecycle hooks,
|
|
228
|
-
*
|
|
261
|
+
* component styles, and plugin support. Eleva manages component registration, plugin integration,
|
|
229
262
|
* event handling, and DOM rendering with a focus on performance and developer experience.
|
|
230
263
|
*
|
|
231
264
|
* @example
|
|
@@ -255,11 +288,10 @@ export class Eleva {
|
|
|
255
288
|
* Creates a new Eleva instance with the specified name and configuration.
|
|
256
289
|
*
|
|
257
290
|
* @public
|
|
291
|
+
* @constructor
|
|
258
292
|
* @param {string} name - The unique identifier name for this Eleva instance.
|
|
259
|
-
* @param {
|
|
260
|
-
* May include framework-wide settings and default behaviors.
|
|
293
|
+
* @param {ElevaConfig} [config={}] - Optional configuration object for the instance.
|
|
261
294
|
* @throws {Error} If the name is not provided or is not a string.
|
|
262
|
-
* @returns {Eleva} A new Eleva instance.
|
|
263
295
|
*
|
|
264
296
|
* @example
|
|
265
297
|
* const app = new Eleva("myApp");
|
|
@@ -274,17 +306,17 @@ export class Eleva {
|
|
|
274
306
|
if (!name || typeof name !== "string") {
|
|
275
307
|
throw new Error("Eleva: name must be a non-empty string");
|
|
276
308
|
}
|
|
277
|
-
/** @public {string} The unique identifier name for this Eleva instance */
|
|
309
|
+
/** @public @readonly {string} The unique identifier name for this Eleva instance */
|
|
278
310
|
this.name = name;
|
|
279
|
-
/** @public {
|
|
311
|
+
/** @public @readonly {Record<string, unknown>} Configuration object for the Eleva instance */
|
|
280
312
|
this.config = config;
|
|
281
|
-
/** @public {Emitter}
|
|
313
|
+
/** @public @readonly {Emitter} Event emitter for handling component events */
|
|
282
314
|
this.emitter = new Emitter();
|
|
283
|
-
/** @public {typeof Signal}
|
|
315
|
+
/** @public @readonly {typeof Signal} Signal class for creating reactive state */
|
|
284
316
|
this.signal = Signal;
|
|
285
|
-
/** @public {typeof TemplateEngine}
|
|
317
|
+
/** @public @readonly {typeof TemplateEngine} TemplateEngine class for template parsing */
|
|
286
318
|
this.templateEngine = TemplateEngine;
|
|
287
|
-
/** @public {Renderer}
|
|
319
|
+
/** @public @readonly {Renderer} Renderer for handling DOM updates and patching */
|
|
288
320
|
this.renderer = new Renderer();
|
|
289
321
|
|
|
290
322
|
/** @private {Map<string, ComponentDefinition>} Registry of all component definitions by name */
|
|
@@ -300,14 +332,16 @@ export class Eleva {
|
|
|
300
332
|
* The plugin's install function will be called with the Eleva instance and provided options.
|
|
301
333
|
* After installation, the plugin will be available for use by components.
|
|
302
334
|
*
|
|
303
|
-
*
|
|
335
|
+
* @note Plugins that wrap core methods (e.g., mount) must be uninstalled in reverse order
|
|
304
336
|
* of installation (LIFO - Last In, First Out) to avoid conflicts.
|
|
305
337
|
*
|
|
306
338
|
* @public
|
|
307
339
|
* @param {ElevaPlugin} plugin - The plugin object which must have an `install` function.
|
|
308
|
-
* @param {
|
|
309
|
-
* @returns {Eleva} The Eleva instance (for method chaining).
|
|
340
|
+
* @param {PluginOptions} [options={}] - Optional configuration options for the plugin.
|
|
341
|
+
* @returns {Eleva | unknown} The Eleva instance (for method chaining) or the result returned by the plugin.
|
|
310
342
|
* @throws {Error} If plugin does not have an install function.
|
|
343
|
+
* @see component - Register components after installing plugins.
|
|
344
|
+
* @see mount - Mount components to the DOM.
|
|
311
345
|
* @example
|
|
312
346
|
* app.use(myPlugin, { option1: "value1" });
|
|
313
347
|
*
|
|
@@ -338,6 +372,7 @@ export class Eleva {
|
|
|
338
372
|
* @param {ComponentDefinition} definition - The component definition including setup, template, style, and children.
|
|
339
373
|
* @returns {Eleva} The Eleva instance (for method chaining).
|
|
340
374
|
* @throws {Error} If name is not a non-empty string or definition has no template.
|
|
375
|
+
* @see mount - Mount this component to the DOM.
|
|
341
376
|
* @example
|
|
342
377
|
* app.component("myButton", {
|
|
343
378
|
* template: (ctx) => `<button>${ctx.props.text}</button>`,
|
|
@@ -359,21 +394,25 @@ export class Eleva {
|
|
|
359
394
|
/**
|
|
360
395
|
* Mounts a registered component to a DOM element.
|
|
361
396
|
* This will initialize the component, set up its reactive state, and render it to the DOM.
|
|
397
|
+
* If the container already has a mounted Eleva instance, it is returned as-is.
|
|
398
|
+
* Unmount clears the container contents and removes the internal instance marker.
|
|
362
399
|
*
|
|
363
400
|
* @public
|
|
401
|
+
* @async
|
|
364
402
|
* @param {HTMLElement} container - The DOM element where the component will be mounted.
|
|
365
|
-
* @param {string|ComponentDefinition} compName - The name of the registered component or a direct component definition.
|
|
366
|
-
* @param {
|
|
403
|
+
* @param {string | ComponentDefinition} compName - The name of the registered component or a direct component definition.
|
|
404
|
+
* @param {ComponentProps} [props={}] - Optional properties to pass to the component.
|
|
367
405
|
* @returns {Promise<MountResult>}
|
|
368
406
|
* A Promise that resolves to an object containing:
|
|
369
407
|
* - container: The mounted component's container element
|
|
370
408
|
* - data: The component's reactive state and context
|
|
371
409
|
* - unmount: Function to clean up and unmount the component
|
|
372
410
|
* @throws {Error} If container is not a DOM element or component is not registered.
|
|
411
|
+
* @throws {Error} If setup function, template function, or style function throws.
|
|
373
412
|
* @example
|
|
374
413
|
* const instance = await app.mount(document.getElementById("app"), "myComponent", { text: "Click me" });
|
|
375
414
|
* // Later...
|
|
376
|
-
* instance.unmount();
|
|
415
|
+
* await instance.unmount();
|
|
377
416
|
*/
|
|
378
417
|
async mount(container, compName, props = {}) {
|
|
379
418
|
if (!container?.nodeType) {
|
|
@@ -394,7 +433,7 @@ export class Eleva {
|
|
|
394
433
|
* Destructure the component definition to access core functionality.
|
|
395
434
|
* - setup: Optional function for component initialization and state management
|
|
396
435
|
* - template: Required function or string that returns the component's HTML structure
|
|
397
|
-
* - style: Optional function or string for component
|
|
436
|
+
* - style: Optional function or string for component CSS styles (not auto-scoped)
|
|
398
437
|
* - children: Optional object defining nested child components
|
|
399
438
|
*/
|
|
400
439
|
const { setup, template, style, children } = definition;
|
|
@@ -403,7 +442,7 @@ export class Eleva {
|
|
|
403
442
|
const context = {
|
|
404
443
|
props,
|
|
405
444
|
emitter: this.emitter,
|
|
406
|
-
/** @type {
|
|
445
|
+
/** @type {SignalFactory} */
|
|
407
446
|
signal: (v) => new this.signal(v),
|
|
408
447
|
};
|
|
409
448
|
|
|
@@ -415,20 +454,21 @@ export class Eleva {
|
|
|
415
454
|
* 3. Rendering the component
|
|
416
455
|
* 4. Managing component lifecycle
|
|
417
456
|
*
|
|
418
|
-
* @
|
|
457
|
+
* @inner
|
|
458
|
+
* @param {Record<string, unknown>} data - Data returned from the component's setup function.
|
|
419
459
|
* @returns {Promise<MountResult>} An object containing:
|
|
420
460
|
* - container: The mounted component's container element
|
|
421
461
|
* - data: The component's reactive state and context
|
|
422
462
|
* - unmount: Function to clean up and unmount the component
|
|
423
463
|
*/
|
|
424
464
|
const processMount = async (data) => {
|
|
425
|
-
/** @type {ComponentContext} */
|
|
465
|
+
/** @type {ComponentContext & SetupResult} */
|
|
426
466
|
const mergedContext = { ...context, ...data };
|
|
427
|
-
/** @type {
|
|
467
|
+
/** @type {UnsubscribeFunction[]} */
|
|
428
468
|
const watchers = [];
|
|
429
|
-
/** @type {
|
|
469
|
+
/** @type {MountResult[]} */
|
|
430
470
|
const childInstances = [];
|
|
431
|
-
/** @type {
|
|
471
|
+
/** @type {UnsubscribeFunction[]} */
|
|
432
472
|
const listeners = [];
|
|
433
473
|
/** @private {boolean} Local mounted state for this component instance */
|
|
434
474
|
let isMounted = false;
|
|
@@ -446,7 +486,10 @@ export class Eleva {
|
|
|
446
486
|
* changes in the same synchronous block will each call this function,
|
|
447
487
|
* but only one render will be scheduled via queueMicrotask.
|
|
448
488
|
* This separates concerns: signals handle state, components handle scheduling.
|
|
489
|
+
*
|
|
490
|
+
* @inner
|
|
449
491
|
* @private
|
|
492
|
+
* @returns {void}
|
|
450
493
|
*/
|
|
451
494
|
const scheduleRender = () => {
|
|
452
495
|
if (renderScheduled) return;
|
|
@@ -463,6 +506,10 @@ export class Eleva {
|
|
|
463
506
|
* 2. Processing the template
|
|
464
507
|
* 3. Updating the DOM
|
|
465
508
|
* 4. Processing events, injecting styles, and mounting child components.
|
|
509
|
+
*
|
|
510
|
+
* @inner
|
|
511
|
+
* @private
|
|
512
|
+
* @returns {Promise<void>}
|
|
466
513
|
*/
|
|
467
514
|
const render = async () => {
|
|
468
515
|
const html =
|
|
@@ -484,6 +531,22 @@ export class Eleva {
|
|
|
484
531
|
}
|
|
485
532
|
|
|
486
533
|
this.renderer.patchDOM(container, html);
|
|
534
|
+
|
|
535
|
+
// Unmount child components whose host elements were removed by patching.
|
|
536
|
+
const childrenToUnmount = [];
|
|
537
|
+
for (let i = childInstances.length - 1; i >= 0; i--) {
|
|
538
|
+
const child = childInstances[i];
|
|
539
|
+
if (!container.contains(child.container)) {
|
|
540
|
+
childInstances.splice(i, 1);
|
|
541
|
+
childrenToUnmount.push(child);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
if (childrenToUnmount.length) {
|
|
545
|
+
await Promise.allSettled(
|
|
546
|
+
childrenToUnmount.map((child) => child.unmount())
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
|
|
487
550
|
this._processEvents(container, mergedContext, listeners);
|
|
488
551
|
if (style) this._injectStyles(container, compId, style, mergedContext);
|
|
489
552
|
if (children)
|
|
@@ -514,6 +577,10 @@ export class Eleva {
|
|
|
514
577
|
* When a Signal's value changes, a batched render is scheduled.
|
|
515
578
|
* Multiple changes within the same frame are collapsed into one render.
|
|
516
579
|
* Stores unsubscribe functions to clean up watchers when component unmounts.
|
|
580
|
+
*
|
|
581
|
+
* @note Signal watchers are invoked synchronously when values change.
|
|
582
|
+
* Render batching is handled at the component level via queueMicrotask,
|
|
583
|
+
* not at the signal level. This preserves stack traces for debugging.
|
|
517
584
|
*/
|
|
518
585
|
for (const val of Object.values(data)) {
|
|
519
586
|
if (val instanceof Signal) watchers.push(val.watch(scheduleRender));
|
|
@@ -526,17 +593,17 @@ export class Eleva {
|
|
|
526
593
|
data: mergedContext,
|
|
527
594
|
/**
|
|
528
595
|
* Unmounts the component, cleaning up watchers and listeners, child components, and clearing the container.
|
|
596
|
+
* Removes the internal instance marker from the container when complete.
|
|
529
597
|
*
|
|
530
|
-
* @returns {void}
|
|
598
|
+
* @returns {Promise<void>}
|
|
531
599
|
*/
|
|
532
600
|
unmount: async () => {
|
|
533
|
-
/** @type {UnmountHookContext} */
|
|
534
601
|
await mergedContext.onUnmount?.({
|
|
535
602
|
container,
|
|
536
603
|
context: mergedContext,
|
|
537
604
|
cleanup: {
|
|
538
|
-
watchers
|
|
539
|
-
listeners
|
|
605
|
+
watchers,
|
|
606
|
+
listeners,
|
|
540
607
|
children: childInstances,
|
|
541
608
|
},
|
|
542
609
|
});
|
|
@@ -559,13 +626,19 @@ export class Eleva {
|
|
|
559
626
|
|
|
560
627
|
/**
|
|
561
628
|
* Processes DOM elements for event binding based on attributes starting with "@".
|
|
562
|
-
* This method
|
|
629
|
+
* This method attaches event listeners directly to elements and ensures proper cleanup.
|
|
630
|
+
* Bound `@event` attributes are removed after listeners are attached.
|
|
631
|
+
*
|
|
632
|
+
* Handler resolution order:
|
|
633
|
+
* 1. Direct context property lookup (e.g., context["handleClick"])
|
|
634
|
+
* 2. Template expression evaluation via TemplateEngine (e.g., "increment()")
|
|
563
635
|
*
|
|
564
636
|
* @private
|
|
565
637
|
* @param {HTMLElement} container - The container element in which to search for event attributes.
|
|
566
|
-
* @param {ComponentContext} context - The
|
|
567
|
-
* @param {
|
|
638
|
+
* @param {ComponentContext & SetupResult} context - The merged component context and setup data.
|
|
639
|
+
* @param {UnsubscribeFunction[]} listeners - Array to collect cleanup functions for each event listener.
|
|
568
640
|
* @returns {void}
|
|
641
|
+
* @see TemplateEngine.evaluate - Expression evaluation. fallback.
|
|
569
642
|
*/
|
|
570
643
|
_processEvents(container, context, listeners) {
|
|
571
644
|
/** @type {NodeListOf<Element>} */
|
|
@@ -583,7 +656,7 @@ export class Eleva {
|
|
|
583
656
|
const event = attr.name.slice(1);
|
|
584
657
|
/** @type {string} */
|
|
585
658
|
const handlerName = attr.value;
|
|
586
|
-
/** @type {
|
|
659
|
+
/** @type {DOMEventHandler} */
|
|
587
660
|
const handler =
|
|
588
661
|
context[handlerName] ||
|
|
589
662
|
this.templateEngine.evaluate(handlerName, context);
|
|
@@ -597,14 +670,18 @@ export class Eleva {
|
|
|
597
670
|
}
|
|
598
671
|
|
|
599
672
|
/**
|
|
600
|
-
* Injects
|
|
601
|
-
*
|
|
673
|
+
* Injects styles into the component's container.
|
|
674
|
+
* Styles are placed in a `<style>` element with a `data-e-style` attribute for identification.
|
|
675
|
+
*
|
|
676
|
+
* @note Styles are not automatically scoped - use unique class names or CSS nesting for isolation.
|
|
677
|
+
*
|
|
678
|
+
* Optimization: Skips DOM update if style content hasn't changed.
|
|
602
679
|
*
|
|
603
680
|
* @private
|
|
604
681
|
* @param {HTMLElement} container - The container element where styles should be injected.
|
|
605
682
|
* @param {string} compId - The component ID used to identify the style element.
|
|
606
|
-
* @param {
|
|
607
|
-
* @param {ComponentContext} context - The
|
|
683
|
+
* @param {StyleFunction | string} styleDef - The component's style definition (function or string).
|
|
684
|
+
* @param {ComponentContext & SetupResult} context - The merged component context and setup data.
|
|
608
685
|
* @returns {void}
|
|
609
686
|
*/
|
|
610
687
|
_injectStyles(container, compId, styleDef, context) {
|
|
@@ -612,7 +689,7 @@ export class Eleva {
|
|
|
612
689
|
const newStyle =
|
|
613
690
|
typeof styleDef === "function" ? styleDef(context) : styleDef;
|
|
614
691
|
|
|
615
|
-
/** @type {HTMLStyleElement|null} */
|
|
692
|
+
/** @type {HTMLStyleElement | null} */
|
|
616
693
|
let styleEl = container.querySelector(`style[data-e-style="${compId}"]`);
|
|
617
694
|
|
|
618
695
|
if (styleEl && styleEl.textContent === newStyle) return;
|
|
@@ -629,11 +706,13 @@ export class Eleva {
|
|
|
629
706
|
* Extracts and evaluates props from an element's attributes that start with `:`.
|
|
630
707
|
* Prop values are evaluated as expressions against the component context,
|
|
631
708
|
* allowing direct passing of objects, arrays, and other complex types.
|
|
709
|
+
* Processed attributes are removed from the element after extraction.
|
|
632
710
|
*
|
|
633
711
|
* @private
|
|
634
|
-
* @param {HTMLElement} element - The DOM element to extract props from
|
|
635
|
-
* @param {ComponentContext} context - The component context
|
|
636
|
-
* @returns {
|
|
712
|
+
* @param {HTMLElement} element - The DOM element to extract props from.
|
|
713
|
+
* @param {ComponentContext & SetupResult} context - The merged component context and setup data.
|
|
714
|
+
* @returns {ComponentProps} An object containing the evaluated props.
|
|
715
|
+
* @see TemplateEngine.evaluate - Expression evaluation.
|
|
637
716
|
* @example
|
|
638
717
|
* // For an element with attributes:
|
|
639
718
|
* // <div :name="user.name" :data="items">
|
|
@@ -662,20 +741,21 @@ export class Eleva {
|
|
|
662
741
|
* This method handles mounting of explicitly defined children components.
|
|
663
742
|
*
|
|
664
743
|
* The mounting process follows these steps:
|
|
665
|
-
* 1.
|
|
744
|
+
* 1. Finds matching DOM nodes within the container
|
|
666
745
|
* 2. Mounts explicitly defined children components
|
|
667
746
|
*
|
|
668
747
|
* @private
|
|
669
|
-
* @
|
|
670
|
-
* @param {
|
|
671
|
-
* @param {
|
|
672
|
-
* @param {
|
|
748
|
+
* @async
|
|
749
|
+
* @param {HTMLElement} container - The container element to mount components in.
|
|
750
|
+
* @param {ChildrenMap} children - Map of selectors to component definitions for explicit children.
|
|
751
|
+
* @param {MountResult[]} childInstances - Array to store all mounted component instances.
|
|
752
|
+
* @param {ComponentContext & SetupResult} context - The merged component context and setup data.
|
|
673
753
|
* @returns {Promise<void>}
|
|
674
754
|
*
|
|
675
755
|
* @example
|
|
676
756
|
* // Explicit children mounting:
|
|
677
757
|
* const children = {
|
|
678
|
-
* '
|
|
758
|
+
* 'user-profile': UserProfileComponent,
|
|
679
759
|
* '#settings-panel': "settings-panel"
|
|
680
760
|
* };
|
|
681
761
|
*/
|
|
@@ -684,7 +764,7 @@ export class Eleva {
|
|
|
684
764
|
if (!selector) continue;
|
|
685
765
|
for (const el of container.querySelectorAll(selector)) {
|
|
686
766
|
if (!(el instanceof HTMLElement)) continue;
|
|
687
|
-
/** @type {
|
|
767
|
+
/** @type {ComponentProps} */
|
|
688
768
|
const props = this._extractProps(el, context);
|
|
689
769
|
/** @type {MountResult} */
|
|
690
770
|
const instance = await this.mount(el, component, props);
|