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
|
@@ -1,5 +1,36 @@
|
|
|
1
|
-
/*! Eleva Plugins v1.0
|
|
1
|
+
/*! Eleva Plugins v1.1.0 | MIT License | https://elevajs.com */
|
|
2
2
|
/**
|
|
3
|
+
* @module eleva/plugins/attr
|
|
4
|
+
* @fileoverview Attribute plugin providing ARIA, data, boolean, and dynamic attribute handling.
|
|
5
|
+
*/ // ============================================================================
|
|
6
|
+
// TYPE DEFINITIONS
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// -----------------------------------------------------------------------------
|
|
9
|
+
// External Type Imports
|
|
10
|
+
// -----------------------------------------------------------------------------
|
|
11
|
+
/**
|
|
12
|
+
* Type imports from the Eleva core library.
|
|
13
|
+
* @typedef {import('eleva').Eleva} Eleva
|
|
14
|
+
*/ // -----------------------------------------------------------------------------
|
|
15
|
+
// Attr Type Definitions
|
|
16
|
+
// -----------------------------------------------------------------------------
|
|
17
|
+
/**
|
|
18
|
+
* Configuration options for the AttrPlugin.
|
|
19
|
+
* @typedef {Object} AttrPluginOptions
|
|
20
|
+
* @property {boolean} [enableAria=true]
|
|
21
|
+
* Enable ARIA attribute handling.
|
|
22
|
+
* @property {boolean} [enableData=true]
|
|
23
|
+
* Enable data attribute handling.
|
|
24
|
+
* @property {boolean} [enableBoolean=true]
|
|
25
|
+
* Enable boolean attribute handling.
|
|
26
|
+
* @property {boolean} [enableDynamic=true]
|
|
27
|
+
* Enable dynamic property detection.
|
|
28
|
+
* @description Configuration options passed to AttrPlugin.install().
|
|
29
|
+
*/ /**
|
|
30
|
+
* Function signature for attribute update operations.
|
|
31
|
+
* @typedef {(oldEl: HTMLElement, newEl: HTMLElement) => void} AttributeUpdateFunction
|
|
32
|
+
* @description Updates attributes on oldEl to match newEl's attributes.
|
|
33
|
+
*/ /**
|
|
3
34
|
* A regular expression to match hyphenated lowercase letters.
|
|
4
35
|
* @private
|
|
5
36
|
* @type {RegExp}
|
|
@@ -40,33 +71,76 @@
|
|
|
40
71
|
/**
|
|
41
72
|
* Plugin version
|
|
42
73
|
* @type {string}
|
|
43
|
-
*/ version: "1.
|
|
74
|
+
*/ version: "1.1.0",
|
|
44
75
|
/**
|
|
45
76
|
* Plugin description
|
|
46
77
|
* @type {string}
|
|
47
78
|
*/ description: "Advanced attribute handling for Eleva components",
|
|
48
79
|
/**
|
|
49
|
-
* Installs the plugin into the Eleva instance
|
|
80
|
+
* Installs the plugin into the Eleva instance.
|
|
81
|
+
*
|
|
82
|
+
* @public
|
|
83
|
+
* Method wrapping behavior:
|
|
84
|
+
* - Stores original `_patchNode` in `renderer._originalPatchNode`
|
|
85
|
+
* - Overrides `renderer._patchNode` to use enhanced attribute handling
|
|
86
|
+
* - Adds `renderer.updateAttributes` and `eleva.updateElementAttributes` helpers
|
|
87
|
+
* - Call `uninstall()` to restore original behavior
|
|
88
|
+
*
|
|
89
|
+
* @param {Eleva} eleva - The Eleva instance to enhance.
|
|
90
|
+
* @param {AttrPluginOptions} options - Plugin configuration options.
|
|
91
|
+
* @param {boolean} [options.enableAria=true] - Enable ARIA attribute handling.
|
|
92
|
+
* Maps aria-* attributes to DOM properties (e.g., aria-expanded → ariaExpanded).
|
|
93
|
+
* @param {boolean} [options.enableData=true] - Enable data attribute handling.
|
|
94
|
+
* Syncs data-* attributes with element.dataset for consistent access.
|
|
95
|
+
* @param {boolean} [options.enableBoolean=true] - Enable boolean attribute handling.
|
|
96
|
+
* Treats empty strings and attribute names as true, "false" string as false.
|
|
97
|
+
* @param {boolean} [options.enableDynamic=true] - Enable dynamic property detection.
|
|
98
|
+
* Searches element prototype chain for property matches (useful for custom elements).
|
|
99
|
+
* @returns {void}
|
|
100
|
+
* @example
|
|
101
|
+
* // Basic installation with defaults
|
|
102
|
+
* app.use(AttrPlugin);
|
|
50
103
|
*
|
|
51
|
-
* @
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
104
|
+
* @example
|
|
105
|
+
* // Custom configuration
|
|
106
|
+
* app.use(AttrPlugin, {
|
|
107
|
+
* enableAria: true,
|
|
108
|
+
* enableData: true,
|
|
109
|
+
* enableBoolean: true,
|
|
110
|
+
* enableDynamic: false // Disable for performance
|
|
111
|
+
* });
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* // Using ARIA attributes in templates
|
|
115
|
+
* template: (ctx) => `
|
|
116
|
+
* <div role="dialog" aria-modal="true" aria-labelledby="title">
|
|
117
|
+
* <h2 id="title">Modal Title</h2>
|
|
118
|
+
* <button aria-expanded="${ctx.isOpen.value}">Toggle</button>
|
|
119
|
+
* </div>
|
|
120
|
+
* `
|
|
121
|
+
* @see uninstall - Remove the plugin and restore original behavior.
|
|
57
122
|
*/ install (eleva, options = {}) {
|
|
58
123
|
const { enableAria = true, enableData = true, enableBoolean = true, enableDynamic = true } = options;
|
|
59
124
|
/**
|
|
60
125
|
* Updates the attributes of an element to match a new element's attributes.
|
|
61
|
-
* This method provides sophisticated attribute processing including:
|
|
62
|
-
* - ARIA attribute handling with proper property mapping
|
|
63
|
-
* - Data attribute management
|
|
64
|
-
* - Boolean attribute processing
|
|
65
|
-
* - Dynamic property detection and mapping
|
|
66
|
-
* - Attribute cleanup and removal
|
|
67
126
|
*
|
|
68
|
-
*
|
|
69
|
-
* @
|
|
127
|
+
* Processing order:
|
|
128
|
+
* 1. Skip event attributes (@click, @input) - handled by Eleva's event system
|
|
129
|
+
* 2. Skip unchanged attributes - optimization
|
|
130
|
+
* 3. ARIA attributes (aria-*): Map to DOM properties (aria-expanded → ariaExpanded)
|
|
131
|
+
* 4. Data attributes (data-*): Update both dataset and attribute
|
|
132
|
+
* 5. Boolean attributes: Handle empty string as true, "false" as false
|
|
133
|
+
* 6. Other attributes: Map to properties with dynamic detection for custom elements
|
|
134
|
+
* 7. Remove old attributes not present in new element
|
|
135
|
+
*
|
|
136
|
+
* Dynamic property detection (when enableDynamic=true):
|
|
137
|
+
* - Checks if property exists directly on element
|
|
138
|
+
* - Searches element's prototype chain for case-insensitive matches
|
|
139
|
+
* - Enables compatibility with custom elements and Web Components
|
|
140
|
+
*
|
|
141
|
+
* @inner
|
|
142
|
+
* @param {HTMLElement} oldEl - The original element to update (modified in-place).
|
|
143
|
+
* @param {HTMLElement} newEl - The reference element with desired attributes.
|
|
70
144
|
* @returns {void}
|
|
71
145
|
*/ const updateAttributes = (oldEl, newEl)=>{
|
|
72
146
|
const oldAttrs = oldEl.attributes;
|
|
@@ -137,8 +211,14 @@
|
|
|
137
211
|
// Store the original _patchNode method
|
|
138
212
|
const originalPatchNode = eleva.renderer._patchNode;
|
|
139
213
|
eleva.renderer._originalPatchNode = originalPatchNode;
|
|
140
|
-
|
|
141
|
-
|
|
214
|
+
/**
|
|
215
|
+
* Overridden _patchNode method that uses enhanced attribute handling.
|
|
216
|
+
* Delegates to `updateAttributes` instead of the basic `_updateAttributes`.
|
|
217
|
+
*
|
|
218
|
+
* @param {Node} oldNode - The original DOM node to update.
|
|
219
|
+
* @param {Node} newNode - The new DOM node with desired state.
|
|
220
|
+
* @returns {void}
|
|
221
|
+
*/ eleva.renderer._patchNode = function(oldNode, newNode) {
|
|
142
222
|
if (oldNode?._eleva_instance) return;
|
|
143
223
|
if (oldNode.nodeType === Node.TEXT_NODE) {
|
|
144
224
|
if (oldNode.nodeValue !== newNode.nodeValue) {
|
|
@@ -162,12 +242,21 @@
|
|
|
162
242
|
options
|
|
163
243
|
});
|
|
164
244
|
// Add utility methods for manual attribute updates
|
|
165
|
-
eleva.updateElementAttributes = updateAttributes;
|
|
245
|
+
/** @type {AttributeUpdateFunction} */ eleva.updateElementAttributes = updateAttributes;
|
|
166
246
|
},
|
|
167
247
|
/**
|
|
168
|
-
* Uninstalls the plugin from the Eleva instance
|
|
248
|
+
* Uninstalls the plugin from the Eleva instance.
|
|
169
249
|
*
|
|
170
|
-
* @
|
|
250
|
+
* @public
|
|
251
|
+
* @param {Eleva} eleva - The Eleva instance.
|
|
252
|
+
* @returns {void}
|
|
253
|
+
* @description
|
|
254
|
+
* Restores the original renderer patching behavior and removes
|
|
255
|
+
* `eleva.updateElementAttributes`.
|
|
256
|
+
* @example
|
|
257
|
+
* // Uninstall the plugin
|
|
258
|
+
* AttrPlugin.uninstall(app);
|
|
259
|
+
* @see install - Install the plugin.
|
|
171
260
|
*/ uninstall (eleva) {
|
|
172
261
|
// Restore original _patchNode method if it exists
|
|
173
262
|
if (eleva.renderer && eleva.renderer._originalPatchNode) {
|
|
@@ -184,60 +273,176 @@
|
|
|
184
273
|
};
|
|
185
274
|
|
|
186
275
|
/**
|
|
276
|
+
* @module eleva/plugins/router
|
|
277
|
+
* @fileoverview Client-side router plugin with hash, history, and query modes,
|
|
278
|
+
* navigation guards, and lifecycle hooks.
|
|
279
|
+
*/ // ============================================================================
|
|
280
|
+
// TYPE DEFINITIONS
|
|
281
|
+
// ============================================================================
|
|
282
|
+
// -----------------------------------------------------------------------------
|
|
283
|
+
// External Type Imports
|
|
284
|
+
// -----------------------------------------------------------------------------
|
|
285
|
+
/**
|
|
286
|
+
* Type imports from the Eleva core library.
|
|
187
287
|
* @typedef {import('eleva').Eleva} Eleva
|
|
188
|
-
* @typedef {import('eleva').Signal} Signal
|
|
189
288
|
* @typedef {import('eleva').ComponentDefinition} ComponentDefinition
|
|
190
289
|
* @typedef {import('eleva').Emitter} Emitter
|
|
191
290
|
* @typedef {import('eleva').MountResult} MountResult
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
291
|
+
* @typedef {import('eleva').UnsubscribeFunction} UnsubscribeFunction
|
|
292
|
+
*/ /**
|
|
293
|
+
* Generic type import.
|
|
294
|
+
* @template T
|
|
295
|
+
* @typedef {import('eleva').Signal<T>} Signal
|
|
296
|
+
*/ // -----------------------------------------------------------------------------
|
|
297
|
+
// Router Events
|
|
298
|
+
// -----------------------------------------------------------------------------
|
|
299
|
+
/**
|
|
300
|
+
* Fired when the router initialization completes successfully.
|
|
301
|
+
* @event router:ready
|
|
302
|
+
* @type {Router}
|
|
303
|
+
*/ /**
|
|
304
|
+
* Fired when an error occurs during navigation or route handling.
|
|
305
|
+
* @event router:error
|
|
306
|
+
* @type {Error}
|
|
307
|
+
*/ /**
|
|
308
|
+
* Fired when no matching route is found for the requested path.
|
|
309
|
+
* @event router:notFound
|
|
310
|
+
* @type {{to: RouteLocation, from: RouteLocation | null, path: string}}
|
|
311
|
+
*/ /**
|
|
312
|
+
* Fired before guards run, allowing plugins to block or redirect navigation.
|
|
313
|
+
* @event router:beforeEach
|
|
314
|
+
* @type {NavigationContext}
|
|
315
|
+
*/ /**
|
|
316
|
+
* Fired before component resolution, allowing plugins to modify the resolve context.
|
|
317
|
+
* @event router:beforeResolve
|
|
318
|
+
* @type {ResolveContext}
|
|
319
|
+
*/ /**
|
|
320
|
+
* Fired after components are resolved successfully.
|
|
321
|
+
* @event router:afterResolve
|
|
322
|
+
* @type {ResolveContext}
|
|
323
|
+
*/ /**
|
|
324
|
+
* Fired after leaving the previous route.
|
|
325
|
+
* @event router:afterLeave
|
|
326
|
+
* @type {{to: RouteLocation, from: RouteLocation}}
|
|
327
|
+
*/ /**
|
|
328
|
+
* Fired before DOM rendering begins.
|
|
329
|
+
* @event router:beforeRender
|
|
330
|
+
* @type {RenderContext}
|
|
331
|
+
*/ /**
|
|
332
|
+
* Fired after DOM rendering completes.
|
|
333
|
+
* @event router:afterRender
|
|
334
|
+
* @type {RenderContext}
|
|
335
|
+
*/ /**
|
|
336
|
+
* Fired after render for scroll behavior handling.
|
|
337
|
+
* @event router:scroll
|
|
338
|
+
* @type {ScrollContext}
|
|
339
|
+
*/ /**
|
|
340
|
+
* Fired after entering the new route.
|
|
341
|
+
* @event router:afterEnter
|
|
342
|
+
* @type {{to: RouteLocation, from: RouteLocation | null}}
|
|
343
|
+
*/ /**
|
|
344
|
+
* Fired after navigation completes successfully.
|
|
345
|
+
* @event router:afterEach
|
|
346
|
+
* @type {{to: RouteLocation, from: RouteLocation | null}}
|
|
347
|
+
*/ /**
|
|
348
|
+
* Fired when a route is dynamically added.
|
|
349
|
+
* @event router:routeAdded
|
|
350
|
+
* @type {RouteDefinition}
|
|
351
|
+
*/ /**
|
|
352
|
+
* Fired when a route is dynamically removed.
|
|
353
|
+
* @event router:routeRemoved
|
|
354
|
+
* @type {RouteDefinition}
|
|
355
|
+
*/ // -----------------------------------------------------------------------------
|
|
356
|
+
// Router Data Types
|
|
357
|
+
// -----------------------------------------------------------------------------
|
|
195
358
|
/**
|
|
196
|
-
* @typedef {'hash' | 'history' | 'query'} RouterMode
|
|
197
359
|
* The routing mode determines how the router manages URL state.
|
|
198
360
|
* - `hash`: Uses URL hash (e.g., `/#/path`) - works without server config
|
|
199
361
|
* - `history`: Uses HTML5 History API (e.g., `/path`) - requires server config
|
|
200
362
|
* - `query`: Uses query parameters (e.g., `?view=/path`) - useful for embedded apps
|
|
363
|
+
* @typedef {'hash' | 'history' | 'query'} RouterMode
|
|
364
|
+
*/ /**
|
|
365
|
+
* Route parameters extracted from the URL path.
|
|
366
|
+
* @typedef {Record<string, string>} RouteParams
|
|
367
|
+
* @description Key-value pairs extracted from dynamic route segments (e.g., `/users/:id` → `{ id: '123' }`).
|
|
368
|
+
*/ /**
|
|
369
|
+
* Query parameters from the URL query string.
|
|
370
|
+
* @typedef {Record<string, string>} QueryParams
|
|
371
|
+
* @description Key-value pairs from the URL query string (e.g., `?page=1&sort=name`).
|
|
201
372
|
*/ /**
|
|
373
|
+
* Navigation input parameters supporting multiple value types.
|
|
374
|
+
* @typedef {Record<string, string | number | boolean>} NavigationParams
|
|
375
|
+
* @description Parameters passed to navigation functions, automatically converted to strings in URLs.
|
|
376
|
+
*/ /**
|
|
377
|
+
* Function signature for programmatic navigation.
|
|
378
|
+
* @typedef {(location: string | NavigationTarget, params?: NavigationParams) => Promise<boolean>} NavigateFunction
|
|
379
|
+
* @description Returns true if navigation succeeded, false if blocked by a guard.
|
|
380
|
+
*/ /**
|
|
381
|
+
* Router configuration options.
|
|
202
382
|
* @typedef {Object} RouterOptions
|
|
203
|
-
* @property {RouterMode} [mode='hash']
|
|
204
|
-
*
|
|
205
|
-
* @property {string} [
|
|
206
|
-
*
|
|
207
|
-
* @property {
|
|
208
|
-
*
|
|
209
|
-
* @property {
|
|
383
|
+
* @property {RouterMode} [mode='hash']
|
|
384
|
+
* The routing mode to use.
|
|
385
|
+
* @property {string} [queryParam='view']
|
|
386
|
+
* Query parameter name for 'query' mode.
|
|
387
|
+
* @property {string} [viewSelector='view']
|
|
388
|
+
* Base selector for the view element.
|
|
389
|
+
* @property {string} mount
|
|
390
|
+
* CSS selector for the mount point element.
|
|
391
|
+
* @property {RouteDefinition[]} routes
|
|
392
|
+
* Array of route definitions.
|
|
393
|
+
* @property {RouteComponent} [globalLayout]
|
|
394
|
+
* Default layout for all routes.
|
|
395
|
+
* @property {NavigationGuard} [onBeforeEach]
|
|
396
|
+
* Global navigation guard.
|
|
397
|
+
* @property {boolean} [autoStart=true]
|
|
398
|
+
* Whether to start the router automatically.
|
|
210
399
|
* @description Configuration options for the Router plugin.
|
|
211
400
|
*/ /**
|
|
401
|
+
* Object describing a navigation target for `router.navigate()`.
|
|
212
402
|
* @typedef {Object} NavigationTarget
|
|
213
|
-
* @property {string} path
|
|
214
|
-
*
|
|
215
|
-
* @property {
|
|
216
|
-
*
|
|
217
|
-
* @property {
|
|
403
|
+
* @property {string} path
|
|
404
|
+
* The target path (can include params like '/users/:id').
|
|
405
|
+
* @property {NavigationParams} [params]
|
|
406
|
+
* Route parameters to inject.
|
|
407
|
+
* @property {NavigationParams} [query]
|
|
408
|
+
* Query parameters to append.
|
|
409
|
+
* @property {boolean} [replace=false]
|
|
410
|
+
* Whether to replace current history entry.
|
|
411
|
+
* @property {unknown} [state]
|
|
412
|
+
* History state to pass.
|
|
218
413
|
* @description Object describing a navigation target for `router.navigate()`.
|
|
219
414
|
*/ /**
|
|
415
|
+
* Saved scroll position.
|
|
220
416
|
* @typedef {Object} ScrollPosition
|
|
221
|
-
* @property {number} x
|
|
222
|
-
*
|
|
417
|
+
* @property {number} x
|
|
418
|
+
* Horizontal scroll position.
|
|
419
|
+
* @property {number} y
|
|
420
|
+
* Vertical scroll position.
|
|
223
421
|
* @description Represents a saved scroll position.
|
|
224
422
|
*/ /**
|
|
423
|
+
* Internal representation of a parsed route path segment.
|
|
225
424
|
* @typedef {Object} RouteSegment
|
|
226
|
-
* @property {'static' | 'param'} type
|
|
227
|
-
*
|
|
228
|
-
* @property {string} [
|
|
425
|
+
* @property {'static' | 'param'} type
|
|
426
|
+
* The segment type.
|
|
427
|
+
* @property {string} [value]
|
|
428
|
+
* The segment value (static segments).
|
|
429
|
+
* @property {string} [name]
|
|
430
|
+
* The parameter name (param segments).
|
|
229
431
|
* @description Internal representation of a parsed route path segment.
|
|
230
432
|
* @private
|
|
231
433
|
*/ /**
|
|
434
|
+
* Result of matching a path against route definitions.
|
|
232
435
|
* @typedef {Object} RouteMatch
|
|
233
|
-
* @property {RouteDefinition} route
|
|
234
|
-
*
|
|
436
|
+
* @property {RouteDefinition} route
|
|
437
|
+
* The matched route definition.
|
|
438
|
+
* @property {RouteParams} params
|
|
439
|
+
* The extracted route parameters.
|
|
235
440
|
* @description Result of matching a path against route definitions.
|
|
236
441
|
* @private
|
|
237
442
|
*/ /**
|
|
238
|
-
*
|
|
239
|
-
* @
|
|
240
|
-
* Common properties include:
|
|
443
|
+
* Arbitrary metadata attached to routes for use in guards and components.
|
|
444
|
+
* @typedef {Record<string, unknown>} RouteMeta
|
|
445
|
+
* @description Common properties include:
|
|
241
446
|
* - `requiresAuth: boolean` - Whether the route requires authentication
|
|
242
447
|
* - `title: string` - Page title for the route
|
|
243
448
|
* - `roles: string[]` - Required user roles
|
|
@@ -248,76 +453,119 @@
|
|
|
248
453
|
* meta: { requiresAuth: true, roles: ['admin'], title: 'Admin Dashboard' }
|
|
249
454
|
* }
|
|
250
455
|
*/ /**
|
|
456
|
+
* Interface for the router's error handling system.
|
|
251
457
|
* @typedef {Object} RouterErrorHandler
|
|
252
|
-
* @property {(error: Error, context: string, details?: Record<string,
|
|
253
|
-
*
|
|
254
|
-
* @property {(message: string,
|
|
458
|
+
* @property {(error: Error, context: string, details?: Record<string, unknown>) => void} handle
|
|
459
|
+
* Throws a formatted error.
|
|
460
|
+
* @property {(message: string, details?: Record<string, unknown>) => void} warn
|
|
461
|
+
* Logs a warning.
|
|
462
|
+
* @property {(message: string, error: Error, details?: Record<string, unknown>) => void} log
|
|
463
|
+
* Logs an error without throwing.
|
|
255
464
|
* @description Interface for the router's error handling system.
|
|
256
|
-
*/ //
|
|
257
|
-
// Event Callback
|
|
258
|
-
//
|
|
465
|
+
*/ // -----------------------------------------------------------------------------
|
|
466
|
+
// Event Callback Types
|
|
467
|
+
// -----------------------------------------------------------------------------
|
|
259
468
|
/**
|
|
469
|
+
* Callback for `router:beforeEach` event.
|
|
260
470
|
* @callback NavigationContextCallback
|
|
261
|
-
* @param {NavigationContext} context
|
|
471
|
+
* @param {NavigationContext} context
|
|
472
|
+
* The navigation context (can be modified to block/redirect).
|
|
262
473
|
* @returns {void | Promise<void>}
|
|
263
|
-
* @description
|
|
474
|
+
* @description Modify context to control navigation flow.
|
|
264
475
|
*/ /**
|
|
476
|
+
* Callback for `router:beforeResolve` and `router:afterResolve` events.
|
|
265
477
|
* @callback ResolveContextCallback
|
|
266
|
-
* @param {ResolveContext} context
|
|
478
|
+
* @param {ResolveContext} context
|
|
479
|
+
* The resolve context (can be modified to block/redirect).
|
|
267
480
|
* @returns {void | Promise<void>}
|
|
268
481
|
* @description Callback for `router:beforeResolve` and `router:afterResolve` events.
|
|
269
482
|
*/ /**
|
|
483
|
+
* Callback for `router:beforeRender` and `router:afterRender` events.
|
|
270
484
|
* @callback RenderContextCallback
|
|
271
|
-
* @param {RenderContext} context
|
|
485
|
+
* @param {RenderContext} context
|
|
486
|
+
* The render context.
|
|
272
487
|
* @returns {void | Promise<void>}
|
|
273
488
|
* @description Callback for `router:beforeRender` and `router:afterRender` events.
|
|
274
489
|
*/ /**
|
|
490
|
+
* Callback for `router:scroll` event.
|
|
275
491
|
* @callback ScrollContextCallback
|
|
276
|
-
* @param {ScrollContext} context
|
|
492
|
+
* @param {ScrollContext} context
|
|
493
|
+
* The scroll context with saved position info.
|
|
277
494
|
* @returns {void | Promise<void>}
|
|
278
|
-
* @description
|
|
495
|
+
* @description Use to implement custom scroll behavior.
|
|
279
496
|
*/ /**
|
|
497
|
+
* Callback for `router:afterEnter`, `router:afterLeave`, `router:afterEach` events.
|
|
280
498
|
* @callback RouteChangeCallback
|
|
281
|
-
* @param {RouteLocation} to
|
|
282
|
-
*
|
|
499
|
+
* @param {RouteLocation} to
|
|
500
|
+
* The target route location.
|
|
501
|
+
* @param {RouteLocation | null} from
|
|
502
|
+
* The source route location.
|
|
283
503
|
* @returns {void | Promise<void>}
|
|
284
504
|
* @description Callback for `router:afterEnter`, `router:afterLeave`, `router:afterEach` events.
|
|
285
505
|
*/ /**
|
|
506
|
+
* Router context injected into component setup as `ctx.router`.
|
|
507
|
+
* @typedef {Object} RouterContext
|
|
508
|
+
* @property {NavigateFunction} navigate
|
|
509
|
+
* Programmatic navigation function.
|
|
510
|
+
* @property {Signal<RouteLocation | null>} current
|
|
511
|
+
* Reactive signal for current route.
|
|
512
|
+
* @property {Signal<RouteLocation | null>} previous
|
|
513
|
+
* Reactive signal for previous route.
|
|
514
|
+
* @property {RouteParams} params
|
|
515
|
+
* Current route params (getter).
|
|
516
|
+
* @property {QueryParams} query
|
|
517
|
+
* Current route query (getter).
|
|
518
|
+
* @property {string} path
|
|
519
|
+
* Current route path (getter).
|
|
520
|
+
* @property {string} fullUrl
|
|
521
|
+
* Current routed URL string (getter).
|
|
522
|
+
* @property {RouteMeta} meta
|
|
523
|
+
* Current route meta (getter).
|
|
524
|
+
* @description Injected into component setup as `ctx.router`.
|
|
525
|
+
*/ /**
|
|
526
|
+
* Callback for `router:error` event.
|
|
286
527
|
* @callback RouterErrorCallback
|
|
287
|
-
* @param {Error} error
|
|
288
|
-
*
|
|
289
|
-
* @param {RouteLocation
|
|
528
|
+
* @param {Error} error
|
|
529
|
+
* The error that occurred.
|
|
530
|
+
* @param {RouteLocation} [to]
|
|
531
|
+
* The target route (if available).
|
|
532
|
+
* @param {RouteLocation | null} [from]
|
|
533
|
+
* The source route (if available).
|
|
290
534
|
* @returns {void | Promise<void>}
|
|
291
|
-
* @description Callback for `router:
|
|
535
|
+
* @description Callback for `router:error` event.
|
|
292
536
|
*/ /**
|
|
537
|
+
* Callback for `router:ready` event.
|
|
293
538
|
* @callback RouterReadyCallback
|
|
294
|
-
* @param {Router} router
|
|
539
|
+
* @param {Router} router
|
|
540
|
+
* The router instance.
|
|
295
541
|
* @returns {void | Promise<void>}
|
|
296
542
|
* @description Callback for `router:ready` event.
|
|
297
543
|
*/ /**
|
|
544
|
+
* Callback for `router:routeAdded` event.
|
|
298
545
|
* @callback RouteAddedCallback
|
|
299
|
-
* @param {RouteDefinition} route
|
|
546
|
+
* @param {RouteDefinition} route
|
|
547
|
+
* The added route definition.
|
|
300
548
|
* @returns {void | Promise<void>}
|
|
301
549
|
* @description Callback for `router:routeAdded` event.
|
|
302
550
|
*/ /**
|
|
551
|
+
* Callback for `router:routeRemoved` event.
|
|
303
552
|
* @callback RouteRemovedCallback
|
|
304
|
-
* @param {RouteDefinition} route
|
|
553
|
+
* @param {RouteDefinition} route
|
|
554
|
+
* The removed route definition.
|
|
305
555
|
* @returns {void | Promise<void>}
|
|
306
556
|
* @description Callback for `router:routeRemoved` event.
|
|
307
|
-
*/ //
|
|
308
|
-
//
|
|
309
|
-
//
|
|
557
|
+
*/ // ============================================================================
|
|
558
|
+
// CORE IMPLEMENTATION
|
|
559
|
+
// ============================================================================
|
|
310
560
|
/**
|
|
311
561
|
* Simple error handler for the core router.
|
|
312
|
-
* Can be overridden by error handling plugins.
|
|
313
|
-
* Provides consistent error formatting and logging for router operations.
|
|
314
562
|
* @private
|
|
315
563
|
*/ const CoreErrorHandler = {
|
|
316
564
|
/**
|
|
317
565
|
* Handles router errors with basic formatting.
|
|
318
566
|
* @param {Error} error - The error to handle.
|
|
319
567
|
* @param {string} context - The context where the error occurred.
|
|
320
|
-
* @param {
|
|
568
|
+
* @param {Record<string, unknown>} details - Additional error details.
|
|
321
569
|
* @throws {Error} The formatted error.
|
|
322
570
|
*/ handle (error, context, details = {}) {
|
|
323
571
|
const message = `[ElevaRouter] ${context}: ${error.message}`;
|
|
@@ -336,7 +584,7 @@
|
|
|
336
584
|
/**
|
|
337
585
|
* Logs a warning without throwing an error.
|
|
338
586
|
* @param {string} message - The warning message.
|
|
339
|
-
* @param {
|
|
587
|
+
* @param {Record<string, unknown>} details - Additional warning details.
|
|
340
588
|
*/ warn (message, details = {}) {
|
|
341
589
|
console.warn(`[ElevaRouter] ${message}`, details);
|
|
342
590
|
},
|
|
@@ -344,7 +592,7 @@
|
|
|
344
592
|
* Logs an error without throwing.
|
|
345
593
|
* @param {string} message - The error message.
|
|
346
594
|
* @param {Error} error - The original error.
|
|
347
|
-
* @param {
|
|
595
|
+
* @param {Record<string, unknown>} details - Additional error details.
|
|
348
596
|
*/ log (message, error, details = {}) {
|
|
349
597
|
console.error(`[ElevaRouter] ${message}`, {
|
|
350
598
|
error,
|
|
@@ -353,26 +601,37 @@
|
|
|
353
601
|
}
|
|
354
602
|
};
|
|
355
603
|
/**
|
|
604
|
+
* Represents the current or target location in the router.
|
|
356
605
|
* @typedef {Object} RouteLocation
|
|
357
|
-
* @property {string} path
|
|
358
|
-
*
|
|
359
|
-
* @property {
|
|
360
|
-
*
|
|
361
|
-
* @property {
|
|
362
|
-
*
|
|
363
|
-
* @property {
|
|
606
|
+
* @property {string} path
|
|
607
|
+
* The path of the route (e.g., '/users/123').
|
|
608
|
+
* @property {QueryParams} query
|
|
609
|
+
* Query parameters as key-value pairs.
|
|
610
|
+
* @property {string} fullUrl
|
|
611
|
+
* The routed URL string (path plus query).
|
|
612
|
+
* @property {RouteParams} params
|
|
613
|
+
* Dynamic route parameters.
|
|
614
|
+
* @property {RouteMeta} meta
|
|
615
|
+
* Metadata associated with the matched route.
|
|
616
|
+
* @property {string} [name]
|
|
617
|
+
* The optional name of the matched route.
|
|
618
|
+
* @property {RouteDefinition} matched
|
|
619
|
+
* The raw route definition that was matched.
|
|
364
620
|
* @description Represents the current or target location in the router.
|
|
365
621
|
*/ /**
|
|
366
|
-
*
|
|
367
|
-
* The return value of a navigation guard.
|
|
622
|
+
* Return value of a navigation guard.
|
|
368
623
|
* - `true` or `undefined/void`: Allow navigation
|
|
369
624
|
* - `false`: Abort navigation
|
|
370
625
|
* - `string`: Redirect to path
|
|
371
626
|
* - `NavigationTarget`: Redirect with options
|
|
627
|
+
* @typedef {boolean | string | NavigationTarget | void} NavigationGuardResult
|
|
372
628
|
*/ /**
|
|
629
|
+
* Navigation guard function that controls navigation flow.
|
|
373
630
|
* @callback NavigationGuard
|
|
374
|
-
* @param {RouteLocation} to
|
|
375
|
-
*
|
|
631
|
+
* @param {RouteLocation} to
|
|
632
|
+
* The target route location.
|
|
633
|
+
* @param {RouteLocation | null} from
|
|
634
|
+
* The source route location (null on initial).
|
|
376
635
|
* @returns {NavigationGuardResult | Promise<NavigationGuardResult>}
|
|
377
636
|
* @description A function that controls navigation flow. Runs before navigation is confirmed.
|
|
378
637
|
* @example
|
|
@@ -384,9 +643,12 @@
|
|
|
384
643
|
* // Allow navigation (implicit return undefined)
|
|
385
644
|
* };
|
|
386
645
|
*/ /**
|
|
646
|
+
* Navigation hook for side effects. Does not affect navigation flow.
|
|
387
647
|
* @callback NavigationHook
|
|
388
|
-
* @param {RouteLocation} to
|
|
389
|
-
*
|
|
648
|
+
* @param {RouteLocation} to
|
|
649
|
+
* The target route location.
|
|
650
|
+
* @param {RouteLocation | null} from
|
|
651
|
+
* The source route location.
|
|
390
652
|
* @returns {void | Promise<void>}
|
|
391
653
|
* @description A lifecycle hook for side effects. Does not affect navigation flow.
|
|
392
654
|
* @example
|
|
@@ -395,11 +657,16 @@
|
|
|
395
657
|
* analytics.trackPageView(to.path);
|
|
396
658
|
* };
|
|
397
659
|
*/ /**
|
|
660
|
+
* Interface for router plugins.
|
|
398
661
|
* @typedef {Object} RouterPlugin
|
|
399
|
-
* @property {string} name
|
|
400
|
-
*
|
|
401
|
-
* @property {
|
|
402
|
-
*
|
|
662
|
+
* @property {string} name
|
|
663
|
+
* Unique plugin identifier.
|
|
664
|
+
* @property {string} [version]
|
|
665
|
+
* Plugin version (recommended to match router version).
|
|
666
|
+
* @property {(router: Router, options?: Record<string, unknown>) => void} install
|
|
667
|
+
* Installation function.
|
|
668
|
+
* @property {(router: Router) => void | Promise<void>} [destroy]
|
|
669
|
+
* Cleanup function called on router.destroy().
|
|
403
670
|
* @description Interface for router plugins. Plugins can extend router functionality.
|
|
404
671
|
* @example
|
|
405
672
|
* const AnalyticsPlugin = {
|
|
@@ -412,57 +679,93 @@
|
|
|
412
679
|
* }
|
|
413
680
|
* };
|
|
414
681
|
*/ /**
|
|
682
|
+
* Context object for navigation events that plugins can modify.
|
|
415
683
|
* @typedef {Object} NavigationContext
|
|
416
|
-
* @property {RouteLocation} to
|
|
417
|
-
*
|
|
418
|
-
* @property {
|
|
419
|
-
*
|
|
420
|
-
* @
|
|
684
|
+
* @property {RouteLocation} to
|
|
685
|
+
* The target route location.
|
|
686
|
+
* @property {RouteLocation | null} from
|
|
687
|
+
* The source route location.
|
|
688
|
+
* @property {boolean} cancelled
|
|
689
|
+
* Whether navigation has been cancelled.
|
|
690
|
+
* @property {string | NavigationTarget | null} redirectTo
|
|
691
|
+
* Redirect target if set.
|
|
692
|
+
* @description Passed to navigation events. Plugins can modify to control navigation flow.
|
|
421
693
|
*/ /**
|
|
694
|
+
* Context object for component resolution events.
|
|
422
695
|
* @typedef {Object} ResolveContext
|
|
423
|
-
* @property {RouteLocation} to
|
|
424
|
-
*
|
|
425
|
-
* @property {
|
|
426
|
-
*
|
|
427
|
-
* @property {
|
|
428
|
-
*
|
|
429
|
-
* @property {
|
|
430
|
-
*
|
|
696
|
+
* @property {RouteLocation} to
|
|
697
|
+
* The target route location.
|
|
698
|
+
* @property {RouteLocation | null} from
|
|
699
|
+
* The source route location.
|
|
700
|
+
* @property {RouteDefinition} route
|
|
701
|
+
* The matched route definition.
|
|
702
|
+
* @property {ComponentDefinition | null} layoutComponent
|
|
703
|
+
* The resolved layout component (available in afterResolve).
|
|
704
|
+
* @property {ComponentDefinition | null} pageComponent
|
|
705
|
+
* The resolved page component (available in afterResolve).
|
|
706
|
+
* @property {boolean} cancelled
|
|
707
|
+
* Whether navigation has been cancelled.
|
|
708
|
+
* @property {string | NavigationTarget | null} redirectTo
|
|
709
|
+
* Redirect target if set.
|
|
710
|
+
* @description Passed to component resolution events.
|
|
431
711
|
*/ /**
|
|
712
|
+
* Context object for render events.
|
|
432
713
|
* @typedef {Object} RenderContext
|
|
433
|
-
* @property {RouteLocation} to
|
|
434
|
-
*
|
|
435
|
-
* @property {
|
|
436
|
-
*
|
|
437
|
-
* @
|
|
714
|
+
* @property {RouteLocation} to
|
|
715
|
+
* The target route location.
|
|
716
|
+
* @property {RouteLocation | null} from
|
|
717
|
+
* The source route location.
|
|
718
|
+
* @property {ComponentDefinition | null} layoutComponent
|
|
719
|
+
* The layout component being rendered.
|
|
720
|
+
* @property {ComponentDefinition} pageComponent
|
|
721
|
+
* The page component being rendered.
|
|
722
|
+
* @description Passed to render events.
|
|
438
723
|
*/ /**
|
|
724
|
+
* Context object for scroll events.
|
|
439
725
|
* @typedef {Object} ScrollContext
|
|
440
|
-
* @property {RouteLocation} to
|
|
441
|
-
*
|
|
442
|
-
* @property {
|
|
443
|
-
*
|
|
726
|
+
* @property {RouteLocation} to
|
|
727
|
+
* The target route location.
|
|
728
|
+
* @property {RouteLocation | null} from
|
|
729
|
+
* The source route location.
|
|
730
|
+
* @property {{x: number, y: number} | null} savedPosition
|
|
731
|
+
* Saved position (back/forward nav).
|
|
732
|
+
* @description Passed to scroll events for plugins to handle scroll behavior.
|
|
444
733
|
*/ /**
|
|
445
|
-
* @typedef {string | ComponentDefinition | (() => Promise<{default: ComponentDefinition}>)} RouteComponent
|
|
446
734
|
* A component that can be rendered for a route.
|
|
447
735
|
* - `string`: Name of a registered component
|
|
448
736
|
* - `ComponentDefinition`: Inline component definition
|
|
449
|
-
* - `() =>
|
|
737
|
+
* - `() => ComponentDefinition`: Factory function returning a component
|
|
738
|
+
* - `() => Promise<ComponentDefinition>`: Async factory function
|
|
739
|
+
* - `() => Promise<{default: ComponentDefinition}>`: Lazy-loaded module (e.g., `() => import('./Page.js')`)
|
|
740
|
+
* @typedef {string | ComponentDefinition | (() => ComponentDefinition | Promise<ComponentDefinition | {default: ComponentDefinition}>)} RouteComponent
|
|
450
741
|
*/ /**
|
|
742
|
+
* Defines a route in the application.
|
|
451
743
|
* @typedef {Object} RouteDefinition
|
|
452
|
-
* @property {string} path
|
|
453
|
-
*
|
|
454
|
-
*
|
|
455
|
-
*
|
|
456
|
-
*
|
|
457
|
-
* @property {RouteComponent}
|
|
458
|
-
*
|
|
459
|
-
* @property {
|
|
460
|
-
*
|
|
461
|
-
* @property {
|
|
462
|
-
*
|
|
463
|
-
* @property {
|
|
464
|
-
*
|
|
744
|
+
* @property {string} path
|
|
745
|
+
* URL path pattern. Supports:
|
|
746
|
+
* - Static: '/about'
|
|
747
|
+
* - Dynamic params: '/users/:id'
|
|
748
|
+
* - Wildcard: '*' (catch-all, conventionally last)
|
|
749
|
+
* @property {RouteComponent} component
|
|
750
|
+
* The component to render for this route.
|
|
751
|
+
* @property {RouteComponent} [layout]
|
|
752
|
+
* Optional layout component to wrap the route component.
|
|
753
|
+
* @property {string} [name]
|
|
754
|
+
* Optional route name for programmatic navigation.
|
|
755
|
+
* @property {RouteMeta} [meta]
|
|
756
|
+
* Optional metadata (auth flags, titles, etc.).
|
|
757
|
+
* @property {NavigationGuard} [beforeEnter]
|
|
758
|
+
* Route-specific guard before entering.
|
|
759
|
+
* @property {NavigationHook} [afterEnter]
|
|
760
|
+
* Hook after entering and component is mounted.
|
|
761
|
+
* @property {NavigationGuard} [beforeLeave]
|
|
762
|
+
* Guard before leaving this route.
|
|
763
|
+
* @property {NavigationHook} [afterLeave]
|
|
764
|
+
* Hook after leaving and component is unmounted.
|
|
765
|
+
* @property {RouteSegment[]} [segments]
|
|
766
|
+
* Internal: parsed path segments (added by router).
|
|
465
767
|
* @description Defines a route in the application.
|
|
768
|
+
* @note Nested routes are not supported. Use shared layouts with flat routes instead.
|
|
466
769
|
* @example
|
|
467
770
|
* // Static route
|
|
468
771
|
* { path: '/about', component: AboutPage }
|
|
@@ -478,10 +781,10 @@
|
|
|
478
781
|
* beforeEnter: (to, from) => isLoggedIn() || '/login'
|
|
479
782
|
* }
|
|
480
783
|
*
|
|
481
|
-
* // Catch-all 404 route (
|
|
784
|
+
* // Catch-all 404 route (conventionally last)
|
|
482
785
|
* { path: '*', component: NotFoundPage }
|
|
483
786
|
*/ /**
|
|
484
|
-
* @class Router
|
|
787
|
+
* @class 🛤️ Router
|
|
485
788
|
* @classdesc A powerful, reactive, and flexible Router Plugin for Eleva.
|
|
486
789
|
* This class manages all routing logic, including state, navigation, and rendering.
|
|
487
790
|
*
|
|
@@ -507,15 +810,15 @@
|
|
|
507
810
|
* | `router:scroll` | {@link ScrollContextCallback} | No | For scroll behavior |
|
|
508
811
|
* | `router:afterEnter` | {@link RouteChangeCallback} | No | After entering route |
|
|
509
812
|
* | `router:afterEach` | {@link RouteChangeCallback} | No | Navigation complete |
|
|
510
|
-
* | `router:
|
|
813
|
+
* | `router:error` | {@link RouterErrorCallback} | No | Navigation error |
|
|
511
814
|
* | `router:routeAdded` | {@link RouteAddedCallback} | No | Dynamic route added |
|
|
512
815
|
* | `router:routeRemoved` | {@link RouteRemovedCallback} | No | Dynamic route removed |
|
|
513
816
|
*
|
|
514
817
|
* ## Reactive Signals
|
|
515
818
|
* - `currentRoute: Signal<RouteLocation | null>` - Current route info
|
|
516
819
|
* - `previousRoute: Signal<RouteLocation | null>` - Previous route info
|
|
517
|
-
* - `currentParams: Signal<
|
|
518
|
-
* - `currentQuery: Signal<
|
|
820
|
+
* - `currentParams: Signal<RouteParams>` - Current route params
|
|
821
|
+
* - `currentQuery: Signal<QueryParams>` - Current query params
|
|
519
822
|
* - `currentLayout: Signal<MountResult | null>` - Mounted layout instance
|
|
520
823
|
* - `currentView: Signal<MountResult | null>` - Mounted view instance
|
|
521
824
|
* - `isReady: Signal<boolean>` - Router readiness state
|
|
@@ -590,7 +893,7 @@
|
|
|
590
893
|
* Parses a route path string into an array of static and parameter segments.
|
|
591
894
|
* @private
|
|
592
895
|
* @param {string} path - The path pattern to parse.
|
|
593
|
-
* @returns {
|
|
896
|
+
* @returns {{type: 'static' | 'param', value?: string, name?: string}[]} An array of segment objects.
|
|
594
897
|
* @throws {Error} If the route path is not a valid string.
|
|
595
898
|
*/ _parsePathIntoSegments(path) {
|
|
596
899
|
if (!path || typeof path !== "string") {
|
|
@@ -634,6 +937,11 @@
|
|
|
634
937
|
/**
|
|
635
938
|
* Starts the router, initializes event listeners, and performs the initial navigation.
|
|
636
939
|
* @returns {Promise<Router>} The router instance for method chaining.
|
|
940
|
+
* @listens window:hashchange In hash mode, triggers route changes.
|
|
941
|
+
* @listens window:popstate In history/query mode, triggers route changes.
|
|
942
|
+
* @emits router:ready When initialization completes successfully.
|
|
943
|
+
* @see destroy - Stop the router and clean up listeners.
|
|
944
|
+
* @see navigate - Programmatically navigate to a route.
|
|
637
945
|
*
|
|
638
946
|
* @example
|
|
639
947
|
* // Basic usage
|
|
@@ -678,8 +986,11 @@
|
|
|
678
986
|
return this;
|
|
679
987
|
}
|
|
680
988
|
/**
|
|
681
|
-
* Stops the router and cleans up
|
|
989
|
+
* Stops the router and cleans up event listeners.
|
|
990
|
+
* Unmounts the current layout instance if present.
|
|
991
|
+
* @async
|
|
682
992
|
* @returns {Promise<void>}
|
|
993
|
+
* @see start - Restart the router after destroying.
|
|
683
994
|
*/ async destroy() {
|
|
684
995
|
if (!this.isStarted) return;
|
|
685
996
|
// Clean up plugins
|
|
@@ -703,6 +1014,7 @@
|
|
|
703
1014
|
/**
|
|
704
1015
|
* Alias for destroy(). Stops the router and cleans up all resources.
|
|
705
1016
|
* Provided for semantic consistency (start/stop pattern).
|
|
1017
|
+
* @async
|
|
706
1018
|
* @returns {Promise<void>}
|
|
707
1019
|
*
|
|
708
1020
|
* @example
|
|
@@ -714,9 +1026,13 @@
|
|
|
714
1026
|
}
|
|
715
1027
|
/**
|
|
716
1028
|
* Programmatically navigates to a new route.
|
|
1029
|
+
* @async
|
|
717
1030
|
* @param {string | NavigationTarget} location - The target location as a path string or navigation target object.
|
|
718
|
-
* @param {
|
|
1031
|
+
* @param {NavigationParams} [params] - Route parameters (only used when location is a string).
|
|
719
1032
|
* @returns {Promise<boolean>} True if navigation succeeded, false if blocked by guards or failed.
|
|
1033
|
+
* @emits router:error When navigation fails due to an exception.
|
|
1034
|
+
* @see start - Initialize the router before navigating.
|
|
1035
|
+
* @see currentRoute - Access the current route after navigation.
|
|
720
1036
|
*
|
|
721
1037
|
* @example
|
|
722
1038
|
* // Basic navigation
|
|
@@ -779,7 +1095,7 @@
|
|
|
779
1095
|
return navigationSuccessful;
|
|
780
1096
|
} catch (error) {
|
|
781
1097
|
this.errorHandler.log("Navigation failed", error);
|
|
782
|
-
await this.emitter.emit("router:
|
|
1098
|
+
await this.emitter.emit("router:error", error);
|
|
783
1099
|
return false;
|
|
784
1100
|
}
|
|
785
1101
|
}
|
|
@@ -799,7 +1115,7 @@
|
|
|
799
1115
|
* @param {string} path - The target path with query string.
|
|
800
1116
|
* @param {object} params - The target params.
|
|
801
1117
|
* @param {object} query - The target query.
|
|
802
|
-
* @returns {boolean}
|
|
1118
|
+
* @returns {boolean} True if the routes are the same.
|
|
803
1119
|
*/ _isSameRoute(path, params, query) {
|
|
804
1120
|
const current = this.currentRoute.value;
|
|
805
1121
|
if (!current) return false;
|
|
@@ -809,7 +1125,16 @@
|
|
|
809
1125
|
}
|
|
810
1126
|
/**
|
|
811
1127
|
* Injects dynamic parameters into a path string.
|
|
1128
|
+
* Replaces `:param` placeholders with URL-encoded values from the params object.
|
|
1129
|
+
*
|
|
812
1130
|
* @private
|
|
1131
|
+
* @param {string} path - The path pattern containing `:param` placeholders.
|
|
1132
|
+
* @param {RouteParams} params - Key-value pairs to inject into the path.
|
|
1133
|
+
* @returns {string} The path with all parameters replaced.
|
|
1134
|
+
*
|
|
1135
|
+
* @example
|
|
1136
|
+
* this._buildPath('/users/:id/posts/:postId', { id: '123', postId: '456' });
|
|
1137
|
+
* // Returns: '/users/123/posts/456'
|
|
813
1138
|
*/ _buildPath(path, params) {
|
|
814
1139
|
let result = path;
|
|
815
1140
|
for (const [key, value] of Object.entries(params)){
|
|
@@ -821,8 +1146,12 @@
|
|
|
821
1146
|
}
|
|
822
1147
|
/**
|
|
823
1148
|
* The handler for browser-initiated route changes (e.g., back/forward buttons).
|
|
1149
|
+
*
|
|
824
1150
|
* @private
|
|
1151
|
+
* @async
|
|
825
1152
|
* @param {boolean} [isPopState=true] - Whether this is a popstate event (back/forward navigation).
|
|
1153
|
+
* @returns {Promise<void>}
|
|
1154
|
+
* @emits router:error When route change handling fails.
|
|
826
1155
|
*/ async _handleRouteChange(isPopState = true) {
|
|
827
1156
|
if (this._isNavigating) return;
|
|
828
1157
|
try {
|
|
@@ -841,26 +1170,30 @@
|
|
|
841
1170
|
this.errorHandler.log("Route change handling failed", error, {
|
|
842
1171
|
currentUrl: typeof window !== "undefined" ? window.location.href : ""
|
|
843
1172
|
});
|
|
844
|
-
await this.emitter.emit("router:
|
|
1173
|
+
await this.emitter.emit("router:error", error);
|
|
845
1174
|
}
|
|
846
1175
|
}
|
|
847
1176
|
/**
|
|
848
1177
|
* Manages the core navigation lifecycle. Runs guards before committing changes.
|
|
849
|
-
* Emits lifecycle events that plugins can hook into:
|
|
850
|
-
* - router:beforeEach - Before guards run (can block/redirect via context)
|
|
851
|
-
* - router:beforeResolve - Before component resolution (can block/redirect)
|
|
852
|
-
* - router:afterResolve - After components are resolved
|
|
853
|
-
* - router:beforeRender - Before DOM rendering
|
|
854
|
-
* - router:afterRender - After DOM rendering
|
|
855
|
-
* - router:scroll - After render, for scroll behavior
|
|
856
|
-
* - router:afterEnter - After entering a route
|
|
857
|
-
* - router:afterLeave - After leaving a route
|
|
858
|
-
* - router:afterEach - After navigation completes
|
|
859
1178
|
*
|
|
860
1179
|
* @private
|
|
1180
|
+
* @async
|
|
861
1181
|
* @param {string} fullPath - The full path (e.g., '/users/123?foo=bar') to navigate to.
|
|
862
1182
|
* @param {boolean} [isPopState=false] - Whether this navigation was triggered by popstate (back/forward).
|
|
863
|
-
* @returns {Promise<boolean>}
|
|
1183
|
+
* @returns {Promise<boolean>} `true` if navigation succeeded, `false` if aborted.
|
|
1184
|
+
* @emits router:notFound When no matching route is found.
|
|
1185
|
+
* @emits router:beforeResolve Before component resolution (can block/redirect).
|
|
1186
|
+
* @emits router:afterResolve After components are resolved.
|
|
1187
|
+
* @emits router:afterLeave After leaving the previous route.
|
|
1188
|
+
* @emits router:beforeRender Before DOM rendering.
|
|
1189
|
+
* @emits router:afterRender After DOM rendering completes.
|
|
1190
|
+
* @emits router:scroll After render, for scroll behavior handling.
|
|
1191
|
+
* @emits router:afterEnter After entering the new route.
|
|
1192
|
+
* @emits router:afterEach After navigation completes successfully.
|
|
1193
|
+
* @emits router:error When an error occurs during navigation.
|
|
1194
|
+
* @see _runGuards - Guard execution.
|
|
1195
|
+
* @see _resolveComponents - Component resolution.
|
|
1196
|
+
* @see _render - DOM rendering.
|
|
864
1197
|
*/ async _proceedWithNavigation(fullPath, isPopState = false) {
|
|
865
1198
|
const from = this.currentRoute.value;
|
|
866
1199
|
const [path, queryString] = (fullPath || "/").split("?");
|
|
@@ -880,7 +1213,7 @@
|
|
|
880
1213
|
}
|
|
881
1214
|
};
|
|
882
1215
|
} else {
|
|
883
|
-
await this.emitter.emit("router:
|
|
1216
|
+
await this.emitter.emit("router:error", new Error(`Route not found: ${toLocation.path}`), toLocation, from);
|
|
884
1217
|
return false;
|
|
885
1218
|
}
|
|
886
1219
|
}
|
|
@@ -967,7 +1300,7 @@
|
|
|
967
1300
|
};
|
|
968
1301
|
await this.emitter.emit("router:beforeRender", renderContext);
|
|
969
1302
|
// 9. Render the new components.
|
|
970
|
-
await this._render(layoutComponent, pageComponent
|
|
1303
|
+
await this._render(layoutComponent, pageComponent);
|
|
971
1304
|
// 10. Emit afterRender event - plugins can trigger animations
|
|
972
1305
|
await this.emitter.emit("router:afterRender", renderContext);
|
|
973
1306
|
// 11. Emit scroll event - plugins can handle scroll restoration
|
|
@@ -989,7 +1322,7 @@
|
|
|
989
1322
|
to,
|
|
990
1323
|
from
|
|
991
1324
|
});
|
|
992
|
-
await this.emitter.emit("router:
|
|
1325
|
+
await this.emitter.emit("router:error", error, to, from);
|
|
993
1326
|
return false;
|
|
994
1327
|
}
|
|
995
1328
|
}
|
|
@@ -1005,7 +1338,8 @@
|
|
|
1005
1338
|
* @param {RouteLocation} to - The target route location.
|
|
1006
1339
|
* @param {RouteLocation | null} from - The current route location (null on initial navigation).
|
|
1007
1340
|
* @param {RouteDefinition} route - The matched route definition.
|
|
1008
|
-
* @returns {Promise<boolean>}
|
|
1341
|
+
* @returns {Promise<boolean>} `false` if navigation should be aborted.
|
|
1342
|
+
* @emits router:beforeEach Before guards run (can block/redirect via context).
|
|
1009
1343
|
*/ async _runGuards(to, from, route) {
|
|
1010
1344
|
// Create navigation context that plugins can modify to block navigation
|
|
1011
1345
|
/** @type {NavigationContext} */ const navContext = {
|
|
@@ -1066,7 +1400,8 @@
|
|
|
1066
1400
|
/**
|
|
1067
1401
|
* Resolves a function component definition to a component object.
|
|
1068
1402
|
* @private
|
|
1069
|
-
* @
|
|
1403
|
+
* @async
|
|
1404
|
+
* @param {() => ComponentDefinition | Promise<ComponentDefinition | { default: ComponentDefinition }>} def - The function to resolve.
|
|
1070
1405
|
* @returns {Promise<ComponentDefinition>} The resolved component.
|
|
1071
1406
|
* @throws {Error} If the function fails to load the component.
|
|
1072
1407
|
*/ async _resolveFunctionComponent(def) {
|
|
@@ -1085,7 +1420,7 @@
|
|
|
1085
1420
|
/**
|
|
1086
1421
|
* Validates a component definition object.
|
|
1087
1422
|
* @private
|
|
1088
|
-
* @param {
|
|
1423
|
+
* @param {unknown} def - The component definition to validate.
|
|
1089
1424
|
* @returns {ComponentDefinition} The validated component.
|
|
1090
1425
|
* @throws {Error} If the component definition is invalid.
|
|
1091
1426
|
*/ _validateComponentDefinition(def) {
|
|
@@ -1104,7 +1439,7 @@
|
|
|
1104
1439
|
/**
|
|
1105
1440
|
* Resolves a component definition to a component object.
|
|
1106
1441
|
* @private
|
|
1107
|
-
* @param {
|
|
1442
|
+
* @param {unknown} def - The component definition to resolve.
|
|
1108
1443
|
* @returns {Promise<ComponentDefinition | null>} The resolved component or null.
|
|
1109
1444
|
*/ async _resolveComponent(def) {
|
|
1110
1445
|
if (def === null || def === undefined) {
|
|
@@ -1126,8 +1461,10 @@
|
|
|
1126
1461
|
/**
|
|
1127
1462
|
* Asynchronously resolves the layout and page components for a route.
|
|
1128
1463
|
* @private
|
|
1464
|
+
* @async
|
|
1129
1465
|
* @param {RouteDefinition} route - The route to resolve components for.
|
|
1130
1466
|
* @returns {Promise<{layoutComponent: ComponentDefinition | null, pageComponent: ComponentDefinition}>}
|
|
1467
|
+
* @throws {Error} If page component cannot be resolved.
|
|
1131
1468
|
*/ async _resolveComponents(route) {
|
|
1132
1469
|
const effectiveLayout = route.layout || this.options.globalLayout;
|
|
1133
1470
|
try {
|
|
@@ -1153,9 +1490,24 @@
|
|
|
1153
1490
|
}
|
|
1154
1491
|
/**
|
|
1155
1492
|
* Renders the components for the current route into the DOM.
|
|
1493
|
+
*
|
|
1494
|
+
* Rendering algorithm:
|
|
1495
|
+
* 1. Find the mount element using options.mount selector
|
|
1496
|
+
* 2. If layoutComponent exists:
|
|
1497
|
+
* a. Mount layout to mount element
|
|
1498
|
+
* b. Find view element within layout (using viewSelector)
|
|
1499
|
+
* c. Mount page component to view element
|
|
1500
|
+
* 3. If no layoutComponent:
|
|
1501
|
+
* a. Mount page component directly to mount element
|
|
1502
|
+
* b. Set currentLayout to null
|
|
1503
|
+
*
|
|
1156
1504
|
* @private
|
|
1505
|
+
* @async
|
|
1157
1506
|
* @param {ComponentDefinition | null} layoutComponent - The pre-loaded layout component.
|
|
1158
1507
|
* @param {ComponentDefinition} pageComponent - The pre-loaded page component.
|
|
1508
|
+
* @returns {Promise<void>}
|
|
1509
|
+
* @throws {Error} If mount element is not found in the DOM.
|
|
1510
|
+
* @throws {Error} If component mounting fails (propagated from eleva.mount).
|
|
1159
1511
|
*/ async _render(layoutComponent, pageComponent) {
|
|
1160
1512
|
const mountEl = document.querySelector(this.options.mount);
|
|
1161
1513
|
if (!mountEl) {
|
|
@@ -1179,8 +1531,8 @@
|
|
|
1179
1531
|
* Creates a getter function for router context properties.
|
|
1180
1532
|
* @private
|
|
1181
1533
|
* @param {string} property - The property name to access.
|
|
1182
|
-
* @param {
|
|
1183
|
-
* @returns {
|
|
1534
|
+
* @param {unknown} defaultValue - The default value if property is undefined.
|
|
1535
|
+
* @returns {() => unknown} A getter function.
|
|
1184
1536
|
*/ _createRouteGetter(property, defaultValue) {
|
|
1185
1537
|
return ()=>this.currentRoute.value?.[property] ?? defaultValue;
|
|
1186
1538
|
}
|
|
@@ -1195,7 +1547,7 @@
|
|
|
1195
1547
|
return {
|
|
1196
1548
|
...component,
|
|
1197
1549
|
async setup (ctx) {
|
|
1198
|
-
ctx.router = {
|
|
1550
|
+
/** @type {RouterContext} */ ctx.router = {
|
|
1199
1551
|
navigate: self.navigate.bind(self),
|
|
1200
1552
|
current: self.currentRoute,
|
|
1201
1553
|
previous: self.previousRoute,
|
|
@@ -1222,9 +1574,13 @@
|
|
|
1222
1574
|
}
|
|
1223
1575
|
/**
|
|
1224
1576
|
* Recursively wraps all child components to ensure they have access to router context.
|
|
1577
|
+
* String component references are returned as-is (context injected during mount).
|
|
1578
|
+
* Objects are wrapped with router context and their children are recursively wrapped.
|
|
1579
|
+
*
|
|
1225
1580
|
* @private
|
|
1226
1581
|
* @param {ComponentDefinition | string} component - The component to wrap (can be a definition object or a registered component name).
|
|
1227
1582
|
* @returns {ComponentDefinition | string} The wrapped component definition or the original string reference.
|
|
1583
|
+
* @see _wrapComponent - Single component wrapping.
|
|
1228
1584
|
*/ _wrapComponentWithChildren(component) {
|
|
1229
1585
|
// If the component is a string (registered component name), return as-is
|
|
1230
1586
|
// The router context will be injected when the component is resolved during mounting
|
|
@@ -1281,7 +1637,15 @@
|
|
|
1281
1637
|
}
|
|
1282
1638
|
/**
|
|
1283
1639
|
* Parses a query string into a key-value object.
|
|
1640
|
+
* Uses URLSearchParams for robust parsing of encoded values.
|
|
1641
|
+
*
|
|
1284
1642
|
* @private
|
|
1643
|
+
* @param {string} queryString - The query string to parse (without leading '?').
|
|
1644
|
+
* @returns {QueryParams} Key-value pairs from the query string.
|
|
1645
|
+
*
|
|
1646
|
+
* @example
|
|
1647
|
+
* this._parseQuery('foo=bar&baz=qux');
|
|
1648
|
+
* // Returns: { foo: 'bar', baz: 'qux' }
|
|
1285
1649
|
*/ _parseQuery(queryString) {
|
|
1286
1650
|
const query = {};
|
|
1287
1651
|
if (queryString) {
|
|
@@ -1333,10 +1697,12 @@
|
|
|
1333
1697
|
/**
|
|
1334
1698
|
* Adds a new route dynamically at runtime.
|
|
1335
1699
|
* The route will be processed and available for navigation immediately.
|
|
1700
|
+
* Routes are inserted before the wildcard (*) route if one exists.
|
|
1336
1701
|
*
|
|
1337
1702
|
* @param {RouteDefinition} route - The route definition to add.
|
|
1338
1703
|
* @param {RouteDefinition} [parentRoute] - Optional parent route to add as a child (not yet implemented).
|
|
1339
|
-
* @returns {() => void} A function to remove the added route.
|
|
1704
|
+
* @returns {() => void} A function to remove the added route (returns no-op if route was invalid).
|
|
1705
|
+
* @emits router:routeAdded When a route is successfully added.
|
|
1340
1706
|
*
|
|
1341
1707
|
* @example
|
|
1342
1708
|
* // Add a route dynamically
|
|
@@ -1384,6 +1750,7 @@
|
|
|
1384
1750
|
*
|
|
1385
1751
|
* @param {string} path - The path of the route to remove.
|
|
1386
1752
|
* @returns {boolean} True if the route was removed, false if not found.
|
|
1753
|
+
* @emits router:routeRemoved When a route is successfully removed.
|
|
1387
1754
|
*
|
|
1388
1755
|
* @example
|
|
1389
1756
|
* router.removeRoute('/dynamic');
|
|
@@ -1471,6 +1838,7 @@
|
|
|
1471
1838
|
* Registers a global hook that runs after a new route component has been mounted.
|
|
1472
1839
|
* @param {NavigationHook} hook - The hook function to register.
|
|
1473
1840
|
* @returns {() => void} A function to unregister the hook.
|
|
1841
|
+
* @listens router:afterEnter
|
|
1474
1842
|
*/ onAfterEnter(hook) {
|
|
1475
1843
|
return this.emitter.on("router:afterEnter", hook);
|
|
1476
1844
|
}
|
|
@@ -1478,6 +1846,7 @@
|
|
|
1478
1846
|
* Registers a global hook that runs after a route component has been unmounted.
|
|
1479
1847
|
* @param {NavigationHook} hook - The hook function to register.
|
|
1480
1848
|
* @returns {() => void} A function to unregister the hook.
|
|
1849
|
+
* @listens router:afterLeave
|
|
1481
1850
|
*/ onAfterLeave(hook) {
|
|
1482
1851
|
return this.emitter.on("router:afterLeave", hook);
|
|
1483
1852
|
}
|
|
@@ -1485,6 +1854,7 @@
|
|
|
1485
1854
|
* Registers a global hook that runs after a navigation has been confirmed and all hooks have completed.
|
|
1486
1855
|
* @param {NavigationHook} hook - The hook function to register.
|
|
1487
1856
|
* @returns {() => void} A function to unregister the hook.
|
|
1857
|
+
* @listens router:afterEach
|
|
1488
1858
|
*/ onAfterEach(hook) {
|
|
1489
1859
|
return this.emitter.on("router:afterEach", hook);
|
|
1490
1860
|
}
|
|
@@ -1492,12 +1862,18 @@
|
|
|
1492
1862
|
* Registers a global error handler for navigation errors.
|
|
1493
1863
|
* @param {(error: Error, to?: RouteLocation, from?: RouteLocation) => void} handler - The error handler function.
|
|
1494
1864
|
* @returns {() => void} A function to unregister the handler.
|
|
1865
|
+
* @listens router:error
|
|
1495
1866
|
*/ onError(handler) {
|
|
1496
|
-
return this.emitter.on("router:
|
|
1867
|
+
return this.emitter.on("router:error", handler);
|
|
1497
1868
|
}
|
|
1498
1869
|
/**
|
|
1499
1870
|
* Registers a plugin with the router.
|
|
1500
|
-
*
|
|
1871
|
+
* Logs a warning if the plugin is already registered.
|
|
1872
|
+
*
|
|
1873
|
+
* @param {RouterPlugin} plugin - The plugin to register (must have install method).
|
|
1874
|
+
* @param {Record<string, unknown>} [options={}] - Options to pass to plugin.install().
|
|
1875
|
+
* @returns {void}
|
|
1876
|
+
* @throws {Error} If plugin does not have an install method.
|
|
1501
1877
|
*/ use(plugin, options = {}) {
|
|
1502
1878
|
if (typeof plugin.install !== "function") {
|
|
1503
1879
|
this.errorHandler.handle(new Error("Plugin must have an install method"), "Plugin registration failed", {
|
|
@@ -1546,7 +1922,9 @@
|
|
|
1546
1922
|
}
|
|
1547
1923
|
/**
|
|
1548
1924
|
* Sets a custom error handler. Used by error handling plugins.
|
|
1549
|
-
*
|
|
1925
|
+
* Logs a warning if the provided handler is invalid (missing required methods).
|
|
1926
|
+
* @param {RouterErrorHandler} errorHandler - The error handler object with handle, warn, and log methods.
|
|
1927
|
+
* @returns {void}
|
|
1550
1928
|
*/ setErrorHandler(errorHandler) {
|
|
1551
1929
|
if (errorHandler && typeof errorHandler.handle === "function" && typeof errorHandler.warn === "function" && typeof errorHandler.log === "function") {
|
|
1552
1930
|
this.errorHandler = errorHandler;
|
|
@@ -1558,6 +1936,7 @@
|
|
|
1558
1936
|
* Creates an instance of the Router.
|
|
1559
1937
|
* @param {Eleva} eleva - The Eleva framework instance.
|
|
1560
1938
|
* @param {RouterOptions} options - The configuration options for the router.
|
|
1939
|
+
* @throws {Error} If the routing mode is invalid.
|
|
1561
1940
|
*/ constructor(eleva, options = {}){
|
|
1562
1941
|
/** @type {Eleva} The Eleva framework instance. */ this.eleva = eleva;
|
|
1563
1942
|
/** @type {RouterOptions} The merged router options. */ this.options = {
|
|
@@ -1567,40 +1946,30 @@
|
|
|
1567
1946
|
...options
|
|
1568
1947
|
};
|
|
1569
1948
|
/** @private @type {RouteDefinition[]} The processed list of route definitions. */ this.routes = this._processRoutes(options.routes || []);
|
|
1570
|
-
/** @private @type {
|
|
1949
|
+
/** @private @type {Emitter} The shared Eleva event emitter for global hooks. */ this.emitter = this.eleva.emitter;
|
|
1571
1950
|
/** @private @type {boolean} A flag indicating if the router has been started. */ this.isStarted = false;
|
|
1572
1951
|
/** @private @type {boolean} A flag to prevent navigation loops from history events. */ this._isNavigating = false;
|
|
1573
1952
|
/** @private @type {number} Counter for tracking navigation operations to prevent race conditions. */ this._navigationId = 0;
|
|
1574
|
-
/** @private @type {
|
|
1953
|
+
/** @private @type {UnsubscribeFunction[]} A collection of cleanup functions for event listeners. */ this.eventListeners = [];
|
|
1575
1954
|
/** @type {Signal<RouteLocation | null>} A reactive signal holding the current route's information. */ this.currentRoute = new this.eleva.signal(null);
|
|
1576
1955
|
/** @type {Signal<RouteLocation | null>} A reactive signal holding the previous route's information. */ this.previousRoute = new this.eleva.signal(null);
|
|
1577
|
-
/** @type {Signal<
|
|
1578
|
-
/** @type {Signal<
|
|
1579
|
-
/** @type {Signal<
|
|
1580
|
-
/** @type {Signal<
|
|
1956
|
+
/** @type {Signal<RouteParams>} A reactive signal holding the current route's parameters. */ this.currentParams = new this.eleva.signal({});
|
|
1957
|
+
/** @type {Signal<QueryParams>} A reactive signal holding the current route's query parameters. */ this.currentQuery = new this.eleva.signal({});
|
|
1958
|
+
/** @type {Signal<MountResult | null>} A reactive signal for the currently mounted layout instance. */ this.currentLayout = new this.eleva.signal(null);
|
|
1959
|
+
/** @type {Signal<MountResult | null>} A reactive signal for the currently mounted view (page) instance. */ this.currentView = new this.eleva.signal(null);
|
|
1581
1960
|
/** @type {Signal<boolean>} A reactive signal indicating if the router is ready (started and initial navigation complete). */ this.isReady = new this.eleva.signal(false);
|
|
1582
1961
|
/** @private @type {Map<string, RouterPlugin>} Map of registered plugins by name. */ this.plugins = new Map();
|
|
1583
|
-
/** @private @type {
|
|
1962
|
+
/** @private @type {NavigationGuard[]} Array of global before-each navigation guards. */ this._beforeEachGuards = [];
|
|
1584
1963
|
// If onBeforeEach was provided in options, add it to the guards array
|
|
1585
1964
|
if (options.onBeforeEach) {
|
|
1586
1965
|
this._beforeEachGuards.push(options.onBeforeEach);
|
|
1587
1966
|
}
|
|
1588
|
-
/** @type {
|
|
1967
|
+
/** @type {RouterErrorHandler} The error handler instance. Can be overridden by plugins. */ this.errorHandler = CoreErrorHandler;
|
|
1589
1968
|
/** @private @type {Map<string, {x: number, y: number}>} Saved scroll positions by route path. */ this._scrollPositions = new Map();
|
|
1590
1969
|
this._validateOptions();
|
|
1591
1970
|
}
|
|
1592
1971
|
}
|
|
1593
1972
|
/**
|
|
1594
|
-
* @typedef {Object} RouterOptions
|
|
1595
|
-
* @property {string} mount - A CSS selector for the main element where the app is mounted.
|
|
1596
|
-
* @property {RouteDefinition[]} routes - An array of route definitions.
|
|
1597
|
-
* @property {'hash' | 'query' | 'history'} [mode='hash'] - The routing mode.
|
|
1598
|
-
* @property {string} [queryParam='page'] - The query parameter to use in 'query' mode.
|
|
1599
|
-
* @property {string} [viewSelector='view'] - The selector for the view element within a layout.
|
|
1600
|
-
* @property {boolean} [autoStart=true] - Whether to start the router automatically.
|
|
1601
|
-
* @property {NavigationGuard} [onBeforeEach] - A global guard executed before every navigation.
|
|
1602
|
-
* @property {string | ComponentDefinition | (() => Promise<{default: ComponentDefinition}>)} [globalLayout] - A global layout for all routes. Can be overridden by a route's specific layout.
|
|
1603
|
-
*/ /**
|
|
1604
1973
|
* @class 🚀 RouterPlugin
|
|
1605
1974
|
* @classdesc A powerful, reactive, and flexible Router Plugin for Eleva applications.
|
|
1606
1975
|
* This plugin provides comprehensive client-side routing functionality including:
|
|
@@ -1639,7 +2008,7 @@
|
|
|
1639
2008
|
/**
|
|
1640
2009
|
* Plugin version
|
|
1641
2010
|
* @type {string}
|
|
1642
|
-
*/ version: "1.0
|
|
2011
|
+
*/ version: "1.1.0",
|
|
1643
2012
|
/**
|
|
1644
2013
|
* Plugin description
|
|
1645
2014
|
* @type {string}
|
|
@@ -1647,16 +2016,25 @@
|
|
|
1647
2016
|
/**
|
|
1648
2017
|
* Installs the RouterPlugin into an Eleva instance.
|
|
1649
2018
|
*
|
|
1650
|
-
* @
|
|
1651
|
-
* @param {
|
|
1652
|
-
* @param {
|
|
1653
|
-
* @param {
|
|
1654
|
-
* @param {
|
|
1655
|
-
* @param {
|
|
1656
|
-
* @param {string} [options.
|
|
1657
|
-
* @param {
|
|
1658
|
-
* @param {
|
|
1659
|
-
* @param {
|
|
2019
|
+
* @public
|
|
2020
|
+
* @param {Eleva} eleva - The Eleva instance.
|
|
2021
|
+
* @param {RouterOptions} options - Router configuration options.
|
|
2022
|
+
* @param {string} options.mount - A CSS selector for the main element where the app is mounted.
|
|
2023
|
+
* @param {RouteDefinition[]} options.routes - An array of route definitions.
|
|
2024
|
+
* @param {'hash' | 'query' | 'history'} [options.mode='hash'] - The routing mode.
|
|
2025
|
+
* @param {string} [options.queryParam='view'] - The query parameter to use in 'query' mode.
|
|
2026
|
+
* @param {string} [options.viewSelector='view'] - Base selector for the view element (matched as #id, .class, [data-*], or raw selector).
|
|
2027
|
+
* @param {boolean} [options.autoStart=true] - Whether to start the router automatically.
|
|
2028
|
+
* @param {NavigationGuard} [options.onBeforeEach] - A global guard executed before every navigation.
|
|
2029
|
+
* @param {RouteComponent} [options.globalLayout] - A global layout for all routes.
|
|
2030
|
+
* @returns {Router} The created router instance.
|
|
2031
|
+
* @throws {Error} If 'mount' option is not provided.
|
|
2032
|
+
* @throws {Error} If 'routes' option is not an array.
|
|
2033
|
+
* @throws {Error} If component registration fails during route processing.
|
|
2034
|
+
* @description
|
|
2035
|
+
* Registers route/layout components, sets `eleva.router`, and adds helpers
|
|
2036
|
+
* (`eleva.navigate`, `eleva.getCurrentRoute`, `eleva.getRouteParams`, `eleva.getRouteQuery`).
|
|
2037
|
+
* When `autoStart` is enabled, startup is scheduled via microtask.
|
|
1660
2038
|
*
|
|
1661
2039
|
* @example
|
|
1662
2040
|
* // main.js
|
|
@@ -1686,9 +2064,10 @@
|
|
|
1686
2064
|
* Registers a component definition with the Eleva instance.
|
|
1687
2065
|
* This method handles both inline component objects and pre-registered component names.
|
|
1688
2066
|
*
|
|
1689
|
-
* @
|
|
1690
|
-
* @param {
|
|
1691
|
-
* @
|
|
2067
|
+
* @inner
|
|
2068
|
+
* @param {unknown} def - The component definition to register.
|
|
2069
|
+
* @param {string} type - The type of component for naming (e.g., "Route", "Layout").
|
|
2070
|
+
* @returns {string | null} The registered component name or null if no definition provided.
|
|
1692
2071
|
*/ const register = (def, type)=>{
|
|
1693
2072
|
if (!def) return null;
|
|
1694
2073
|
if (typeof def === "object" && def !== null && !def.name) {
|
|
@@ -1712,7 +2091,7 @@
|
|
|
1712
2091
|
}
|
|
1713
2092
|
});
|
|
1714
2093
|
const router = new Router(eleva, options);
|
|
1715
|
-
eleva.router = router;
|
|
2094
|
+
/** @type {Router} */ eleva.router = router;
|
|
1716
2095
|
if (options.autoStart !== false) {
|
|
1717
2096
|
queueMicrotask(()=>router.start());
|
|
1718
2097
|
}
|
|
@@ -1727,16 +2106,22 @@
|
|
|
1727
2106
|
options
|
|
1728
2107
|
});
|
|
1729
2108
|
// Add utility methods for manual router access
|
|
1730
|
-
eleva.navigate = router.navigate.bind(router);
|
|
1731
|
-
eleva.getCurrentRoute = ()=>router.currentRoute.value;
|
|
1732
|
-
eleva.getRouteParams = ()=>router.currentParams.value;
|
|
1733
|
-
eleva.getRouteQuery = ()=>router.currentQuery.value;
|
|
2109
|
+
/** @type {NavigateFunction} */ eleva.navigate = router.navigate.bind(router);
|
|
2110
|
+
/** @type {() => RouteLocation | null} */ eleva.getCurrentRoute = ()=>router.currentRoute.value;
|
|
2111
|
+
/** @type {() => RouteParams} */ eleva.getRouteParams = ()=>router.currentParams.value;
|
|
2112
|
+
/** @type {() => QueryParams} */ eleva.getRouteQuery = ()=>router.currentQuery.value;
|
|
1734
2113
|
return router;
|
|
1735
2114
|
},
|
|
1736
2115
|
/**
|
|
1737
|
-
* Uninstalls the plugin from the Eleva instance
|
|
2116
|
+
* Uninstalls the plugin from the Eleva instance.
|
|
1738
2117
|
*
|
|
1739
|
-
* @
|
|
2118
|
+
* @public
|
|
2119
|
+
* @async
|
|
2120
|
+
* @param {Eleva} eleva - The Eleva instance.
|
|
2121
|
+
* @returns {Promise<void>}
|
|
2122
|
+
* @description
|
|
2123
|
+
* Destroys the router instance, removes `eleva.router`, and deletes helper methods
|
|
2124
|
+
* (`eleva.navigate`, `eleva.getCurrentRoute`, `eleva.getRouteParams`, `eleva.getRouteQuery`).
|
|
1740
2125
|
*/ async uninstall (eleva) {
|
|
1741
2126
|
if (eleva.router) {
|
|
1742
2127
|
await eleva.router.destroy();
|
|
@@ -1755,6 +2140,194 @@
|
|
|
1755
2140
|
};
|
|
1756
2141
|
|
|
1757
2142
|
/**
|
|
2143
|
+
* @module eleva/plugins/store
|
|
2144
|
+
* @fileoverview Reactive state management plugin with namespaced modules,
|
|
2145
|
+
* persistence, and subscription system.
|
|
2146
|
+
*/ // ============================================================================
|
|
2147
|
+
// TYPE DEFINITIONS
|
|
2148
|
+
// ============================================================================
|
|
2149
|
+
// -----------------------------------------------------------------------------
|
|
2150
|
+
// External Type Imports
|
|
2151
|
+
// -----------------------------------------------------------------------------
|
|
2152
|
+
/**
|
|
2153
|
+
* Type imports from the Eleva core library.
|
|
2154
|
+
* @typedef {import('eleva').Eleva} Eleva
|
|
2155
|
+
* @typedef {import('eleva').ComponentDefinition} ComponentDefinition
|
|
2156
|
+
* @typedef {import('eleva').ComponentContext} ComponentContext
|
|
2157
|
+
* @typedef {import('eleva').SetupResult} SetupResult
|
|
2158
|
+
* @typedef {import('eleva').ComponentProps} ComponentProps
|
|
2159
|
+
* @typedef {import('eleva').ChildrenMap} ChildrenMap
|
|
2160
|
+
* @typedef {import('eleva').MountResult} MountResult
|
|
2161
|
+
*/ /**
|
|
2162
|
+
* Generic type import.
|
|
2163
|
+
* @template T
|
|
2164
|
+
* @typedef {import('eleva').Signal<T>} Signal
|
|
2165
|
+
*/ // -----------------------------------------------------------------------------
|
|
2166
|
+
// Store Type Definitions
|
|
2167
|
+
// -----------------------------------------------------------------------------
|
|
2168
|
+
/**
|
|
2169
|
+
* Mutation record emitted to subscribers.
|
|
2170
|
+
* @typedef {Object} StoreMutation
|
|
2171
|
+
* @property {string} type
|
|
2172
|
+
* The action name that was dispatched.
|
|
2173
|
+
* @property {unknown} payload
|
|
2174
|
+
* The payload passed to the action.
|
|
2175
|
+
* @property {number} timestamp
|
|
2176
|
+
* Unix timestamp of when the mutation occurred.
|
|
2177
|
+
* @description Record passed to subscribers when state changes via dispatch.
|
|
2178
|
+
* @example
|
|
2179
|
+
* store.subscribe((mutation, state) => {
|
|
2180
|
+
* console.log(`Action: ${mutation.type}`);
|
|
2181
|
+
* console.log(`Payload: ${mutation.payload}`);
|
|
2182
|
+
* console.log(`Time: ${new Date(mutation.timestamp)}`);
|
|
2183
|
+
* });
|
|
2184
|
+
*/ /**
|
|
2185
|
+
* Store configuration options.
|
|
2186
|
+
* @typedef {Object} StoreOptions
|
|
2187
|
+
* @property {Record<string, unknown>} [state]
|
|
2188
|
+
* Initial state object.
|
|
2189
|
+
* @property {Record<string, ActionFunction>} [actions]
|
|
2190
|
+
* Action functions for state mutations.
|
|
2191
|
+
* @property {Record<string, StoreModule>} [namespaces]
|
|
2192
|
+
* Namespaced modules for organizing store.
|
|
2193
|
+
* @property {StorePersistenceOptions} [persistence]
|
|
2194
|
+
* Persistence configuration.
|
|
2195
|
+
* @property {boolean} [devTools]
|
|
2196
|
+
* Enable development tools integration.
|
|
2197
|
+
* @property {StoreErrorHandler} [onError]
|
|
2198
|
+
* Error handler function.
|
|
2199
|
+
* @description Configuration options passed to StorePlugin.install().
|
|
2200
|
+
* @example
|
|
2201
|
+
* app.use(StorePlugin, {
|
|
2202
|
+
* state: { count: 0, user: null },
|
|
2203
|
+
* actions: {
|
|
2204
|
+
* increment: (state) => state.count.value++,
|
|
2205
|
+
* setUser: (state, user) => state.user.value = user
|
|
2206
|
+
* },
|
|
2207
|
+
* persistence: { enabled: true, key: 'my-app' }
|
|
2208
|
+
* });
|
|
2209
|
+
*/ /**
|
|
2210
|
+
* Namespaced store module definition.
|
|
2211
|
+
* @typedef {Object} StoreModule
|
|
2212
|
+
* @property {Record<string, unknown>} state
|
|
2213
|
+
* Module state.
|
|
2214
|
+
* @property {Record<string, ActionFunction>} [actions]
|
|
2215
|
+
* Module actions.
|
|
2216
|
+
* @description Defines a namespaced module for organizing related state and actions.
|
|
2217
|
+
* @example
|
|
2218
|
+
* // Define a module
|
|
2219
|
+
* const authModule = {
|
|
2220
|
+
* state: { user: null, token: null },
|
|
2221
|
+
* actions: {
|
|
2222
|
+
* login: (state, { user, token }) => {
|
|
2223
|
+
* state.auth.user.value = user;
|
|
2224
|
+
* state.auth.token.value = token;
|
|
2225
|
+
* }
|
|
2226
|
+
* }
|
|
2227
|
+
* };
|
|
2228
|
+
*
|
|
2229
|
+
* // Register dynamically
|
|
2230
|
+
* store.registerModule('auth', authModule);
|
|
2231
|
+
*/ /**
|
|
2232
|
+
* Store persistence configuration.
|
|
2233
|
+
* @typedef {Object} StorePersistenceOptions
|
|
2234
|
+
* @property {boolean} [enabled]
|
|
2235
|
+
* Enable state persistence.
|
|
2236
|
+
* @property {string} [key]
|
|
2237
|
+
* Storage key (default: "eleva-store").
|
|
2238
|
+
* @property {'localStorage' | 'sessionStorage'} [storage]
|
|
2239
|
+
* Storage type.
|
|
2240
|
+
* @property {string[]} [include]
|
|
2241
|
+
* Dot-path prefixes to persist (e.g., "auth.user").
|
|
2242
|
+
* @property {string[]} [exclude]
|
|
2243
|
+
* Dot-path prefixes to exclude.
|
|
2244
|
+
* @description Configuration for persisting store state to localStorage or sessionStorage.
|
|
2245
|
+
* @example
|
|
2246
|
+
* // Persist only specific state paths
|
|
2247
|
+
* persistence: {
|
|
2248
|
+
* enabled: true,
|
|
2249
|
+
* key: 'my-app-store',
|
|
2250
|
+
* storage: 'localStorage',
|
|
2251
|
+
* include: ['user', 'settings.theme']
|
|
2252
|
+
* }
|
|
2253
|
+
*
|
|
2254
|
+
* @example
|
|
2255
|
+
* // Exclude sensitive data
|
|
2256
|
+
* persistence: {
|
|
2257
|
+
* enabled: true,
|
|
2258
|
+
* exclude: ['auth.token', 'temp']
|
|
2259
|
+
* }
|
|
2260
|
+
*/ /**
|
|
2261
|
+
* Store error handler callback.
|
|
2262
|
+
* @typedef {(error: Error, context: string) => void} StoreErrorHandler
|
|
2263
|
+
* @description Custom error handler for store operations.
|
|
2264
|
+
* @example
|
|
2265
|
+
* app.use(StorePlugin, {
|
|
2266
|
+
* onError: (error, context) => {
|
|
2267
|
+
* console.error(`Store error in ${context}:`, error);
|
|
2268
|
+
* // Send to error tracking service
|
|
2269
|
+
* errorTracker.capture(error, { context });
|
|
2270
|
+
* }
|
|
2271
|
+
* });
|
|
2272
|
+
*/ /**
|
|
2273
|
+
* Reactive state tree containing signals and nested namespaces.
|
|
2274
|
+
* @typedef {Record<string, Signal<unknown> | Record<string, unknown>>} StoreState
|
|
2275
|
+
* @description Represents the store's reactive state structure with support for nested modules.
|
|
2276
|
+
*/ /**
|
|
2277
|
+
* Action function signature for store actions.
|
|
2278
|
+
* @typedef {(state: StoreState, payload?: unknown) => unknown} ActionFunction
|
|
2279
|
+
* @description Function that receives state and optional payload, returns action result.
|
|
2280
|
+
*/ /**
|
|
2281
|
+
* Dispatch function signature for triggering actions.
|
|
2282
|
+
* @typedef {(actionName: string, payload?: unknown) => Promise<unknown>} DispatchFunction
|
|
2283
|
+
* @description Dispatches an action by name with optional payload, returns action result.
|
|
2284
|
+
*/ /**
|
|
2285
|
+
* Subscribe callback signature for mutation listeners.
|
|
2286
|
+
* @typedef {(mutation: StoreMutation, state: StoreState) => void} SubscribeCallback
|
|
2287
|
+
* @description Called after each successful action dispatch with mutation details and current state.
|
|
2288
|
+
*/ /**
|
|
2289
|
+
* Store API exposed to components via ctx.store.
|
|
2290
|
+
* @typedef {Object} StoreApi
|
|
2291
|
+
* @property {StoreState} state
|
|
2292
|
+
* Reactive state signals (supports nested modules).
|
|
2293
|
+
* @property {DispatchFunction} dispatch
|
|
2294
|
+
* Dispatch an action by name with optional payload.
|
|
2295
|
+
* @property {(callback: SubscribeCallback) => () => void} subscribe
|
|
2296
|
+
* Subscribe to state mutations. Returns unsubscribe function.
|
|
2297
|
+
* @property {() => Record<string, unknown>} getState
|
|
2298
|
+
* Get a snapshot of current state values.
|
|
2299
|
+
* @property {(namespace: string, module: StoreModule) => void} registerModule
|
|
2300
|
+
* Register a namespaced module dynamically.
|
|
2301
|
+
* @property {(namespace: string) => void} unregisterModule
|
|
2302
|
+
* Unregister a namespaced module.
|
|
2303
|
+
* @property {(key: string, initialValue: unknown) => Signal<unknown>} createState
|
|
2304
|
+
* Create a new state signal dynamically.
|
|
2305
|
+
* @property {(name: string, actionFn: ActionFunction) => void} createAction
|
|
2306
|
+
* Register a new action dynamically.
|
|
2307
|
+
* @property {new <T>(value: T) => Signal<T>} signal
|
|
2308
|
+
* Signal class constructor for manual state creation.
|
|
2309
|
+
* @description The store API injected into component setup as `ctx.store`.
|
|
2310
|
+
* @example
|
|
2311
|
+
* app.component('Counter', {
|
|
2312
|
+
* setup({ store }) {
|
|
2313
|
+
* // Access reactive state
|
|
2314
|
+
* const count = store.state.count;
|
|
2315
|
+
*
|
|
2316
|
+
* // Dispatch actions
|
|
2317
|
+
* const increment = () => store.dispatch('increment');
|
|
2318
|
+
*
|
|
2319
|
+
* // Subscribe to changes
|
|
2320
|
+
* const unsub = store.subscribe((mutation) => {
|
|
2321
|
+
* console.log('State changed:', mutation.type);
|
|
2322
|
+
* });
|
|
2323
|
+
*
|
|
2324
|
+
* return { count, increment, onUnmount: () => unsub() };
|
|
2325
|
+
* },
|
|
2326
|
+
* template: (ctx) => `<button @click="increment">${ctx.count.value}</button>`
|
|
2327
|
+
* });
|
|
2328
|
+
* @see StoreMutation - Mutation record structure.
|
|
2329
|
+
* @see StoreModule - Module definition for namespaces.
|
|
2330
|
+
*/ /**
|
|
1758
2331
|
* @class 🏪 StorePlugin
|
|
1759
2332
|
* @classdesc A powerful reactive state management plugin for Eleva that enables sharing
|
|
1760
2333
|
* reactive data across the entire application. The Store plugin provides a centralized,
|
|
@@ -1781,7 +2354,7 @@
|
|
|
1781
2354
|
* },
|
|
1782
2355
|
* actions: {
|
|
1783
2356
|
* increment: (state) => state.counter.value++,
|
|
1784
|
-
* addTodo: (state, todo) => state.todos.value.
|
|
2357
|
+
* addTodo: (state, todo) => state.todos.value = [...state.todos.value, todo],
|
|
1785
2358
|
* setUser: (state, user) => state.user.value = user
|
|
1786
2359
|
* },
|
|
1787
2360
|
* persistence: {
|
|
@@ -1804,7 +2377,7 @@
|
|
|
1804
2377
|
* <div>
|
|
1805
2378
|
* <p>Hello ${ctx.user.value.name}!</p>
|
|
1806
2379
|
* <p>Count: ${ctx.count.value}</p>
|
|
1807
|
-
* <button
|
|
2380
|
+
* <button @click="increment">+</button>
|
|
1808
2381
|
* </div>
|
|
1809
2382
|
* `
|
|
1810
2383
|
* });
|
|
@@ -1816,27 +2389,34 @@
|
|
|
1816
2389
|
/**
|
|
1817
2390
|
* Plugin version
|
|
1818
2391
|
* @type {string}
|
|
1819
|
-
*/ version: "1.
|
|
2392
|
+
*/ version: "1.1.0",
|
|
1820
2393
|
/**
|
|
1821
2394
|
* Plugin description
|
|
1822
2395
|
* @type {string}
|
|
1823
2396
|
*/ description: "Reactive state management for sharing data across the entire Eleva application",
|
|
1824
2397
|
/**
|
|
1825
|
-
* Installs the plugin into the Eleva instance
|
|
2398
|
+
* Installs the plugin into the Eleva instance.
|
|
1826
2399
|
*
|
|
1827
|
-
* @
|
|
1828
|
-
* @param {
|
|
1829
|
-
* @param {
|
|
1830
|
-
* @param {
|
|
1831
|
-
* @param {
|
|
1832
|
-
* @param {
|
|
1833
|
-
* @param {
|
|
1834
|
-
* @param {
|
|
1835
|
-
* @param {
|
|
1836
|
-
* @param {
|
|
1837
|
-
* @param {
|
|
1838
|
-
* @param {
|
|
1839
|
-
* @param {
|
|
2400
|
+
* @public
|
|
2401
|
+
* @param {Eleva} eleva - The Eleva instance.
|
|
2402
|
+
* @param {StoreOptions} options - Plugin configuration options.
|
|
2403
|
+
* @param {Record<string, unknown>} [options.state={}] - Initial state object.
|
|
2404
|
+
* @param {Record<string, ActionFunction>} [options.actions={}] - Action functions for state mutations.
|
|
2405
|
+
* @param {Record<string, StoreModule>} [options.namespaces={}] - Namespaced modules for organizing store.
|
|
2406
|
+
* @param {StorePersistenceOptions} [options.persistence] - Persistence configuration.
|
|
2407
|
+
* @param {boolean} [options.persistence.enabled=false] - Enable state persistence.
|
|
2408
|
+
* @param {string} [options.persistence.key="eleva-store"] - Storage key.
|
|
2409
|
+
* @param {'localStorage' | 'sessionStorage'} [options.persistence.storage="localStorage"] - Storage type.
|
|
2410
|
+
* @param {string[]} [options.persistence.include] - Dot-path prefixes to persist (e.g., "auth.user")
|
|
2411
|
+
* @param {string[]} [options.persistence.exclude] - Dot-path prefixes to exclude (applies when include is empty).
|
|
2412
|
+
* @param {boolean} [options.devTools=false] - Enable development tools integration.
|
|
2413
|
+
* @param {(error: Error, context: string) => void} [options.onError=null] - Error handler function.
|
|
2414
|
+
* @returns {void}
|
|
2415
|
+
* @description
|
|
2416
|
+
* Installs the store and injects `store` into component setup context by wrapping
|
|
2417
|
+
* `eleva.mount` and `eleva._mountComponents`. Also exposes `eleva.store` and
|
|
2418
|
+
* helper methods (`eleva.dispatch`, `eleva.getState`, `eleva.subscribe`, `eleva.createAction`).
|
|
2419
|
+
* Uninstall restores the originals.
|
|
1840
2420
|
*
|
|
1841
2421
|
* @example
|
|
1842
2422
|
* // Basic installation
|
|
@@ -1856,12 +2436,12 @@
|
|
|
1856
2436
|
* state: { user: null, token: null },
|
|
1857
2437
|
* actions: {
|
|
1858
2438
|
* login: (state, { user, token }) => {
|
|
1859
|
-
* state.user.value = user;
|
|
1860
|
-
* state.token.value = token;
|
|
2439
|
+
* state.auth.user.value = user;
|
|
2440
|
+
* state.auth.token.value = token;
|
|
1861
2441
|
* },
|
|
1862
2442
|
* logout: (state) => {
|
|
1863
|
-
* state.user.value = null;
|
|
1864
|
-
* state.token.value = null;
|
|
2443
|
+
* state.auth.user.value = null;
|
|
2444
|
+
* state.auth.token.value = null;
|
|
1865
2445
|
* }
|
|
1866
2446
|
* }
|
|
1867
2447
|
* }
|
|
@@ -1874,12 +2454,18 @@
|
|
|
1874
2454
|
*/ install (eleva, options = {}) {
|
|
1875
2455
|
const { state = {}, actions = {}, namespaces = {}, persistence = {}, devTools = false, onError = null } = options;
|
|
1876
2456
|
/**
|
|
1877
|
-
* Store
|
|
2457
|
+
* @class Store
|
|
2458
|
+
* @classdesc Store instance that manages all state and provides the API.
|
|
1878
2459
|
* @private
|
|
1879
2460
|
*/ class Store {
|
|
1880
2461
|
/**
|
|
1881
|
-
* Initializes the root state and actions
|
|
2462
|
+
* Initializes the root state and actions.
|
|
2463
|
+
* Creates reactive signals for each state property and copies actions.
|
|
2464
|
+
*
|
|
1882
2465
|
* @private
|
|
2466
|
+
* @param {Record<string, unknown>} initialState - The initial state key-value pairs.
|
|
2467
|
+
* @param {Record<string, ActionFunction>} initialActions - The action functions to register.
|
|
2468
|
+
* @returns {void}
|
|
1883
2469
|
*/ _initializeState(initialState, initialActions) {
|
|
1884
2470
|
// Create reactive signals for each state property
|
|
1885
2471
|
Object.entries(initialState).forEach(([key, value])=>{
|
|
@@ -1891,8 +2477,12 @@
|
|
|
1891
2477
|
};
|
|
1892
2478
|
}
|
|
1893
2479
|
/**
|
|
1894
|
-
* Initializes namespaced modules
|
|
2480
|
+
* Initializes namespaced modules.
|
|
2481
|
+
* Creates namespace objects and populates them with state signals and actions.
|
|
2482
|
+
*
|
|
1895
2483
|
* @private
|
|
2484
|
+
* @param {Record<string, StoreModule>} namespaces - Map of namespace names to module definitions.
|
|
2485
|
+
* @returns {void}
|
|
1896
2486
|
*/ _initializeNamespaces(namespaces) {
|
|
1897
2487
|
Object.entries(namespaces).forEach(([namespace, module])=>{
|
|
1898
2488
|
const { state: moduleState = {}, actions: moduleActions = {} } = module;
|
|
@@ -1914,8 +2504,12 @@
|
|
|
1914
2504
|
});
|
|
1915
2505
|
}
|
|
1916
2506
|
/**
|
|
1917
|
-
* Loads persisted state from storage
|
|
2507
|
+
* Loads persisted state from storage.
|
|
2508
|
+
* Reads from localStorage/sessionStorage and applies values to state signals.
|
|
2509
|
+
* Does nothing if persistence is disabled or running in SSR environment.
|
|
2510
|
+
*
|
|
1918
2511
|
* @private
|
|
2512
|
+
* @returns {void}
|
|
1919
2513
|
*/ _loadPersistedState() {
|
|
1920
2514
|
if (!this.persistence.enabled || typeof window === "undefined") {
|
|
1921
2515
|
return;
|
|
@@ -1936,8 +2530,14 @@
|
|
|
1936
2530
|
}
|
|
1937
2531
|
}
|
|
1938
2532
|
/**
|
|
1939
|
-
* Applies persisted data to the current state
|
|
2533
|
+
* Applies persisted data to the current state.
|
|
2534
|
+
* Recursively updates signal values for paths that should be persisted.
|
|
2535
|
+
*
|
|
1940
2536
|
* @private
|
|
2537
|
+
* @param {Record<string, unknown>} data - The persisted data object to apply.
|
|
2538
|
+
* @param {Record<string, unknown>} [currentState=this.state] - The current state object to update.
|
|
2539
|
+
* @param {string} [path=""] - The current dot-notation path (for include/exclude filtering).
|
|
2540
|
+
* @returns {void}
|
|
1941
2541
|
*/ _applyPersistedData(data, currentState = this.state, path = "") {
|
|
1942
2542
|
Object.entries(data).forEach(([key, value])=>{
|
|
1943
2543
|
const fullPath = path ? `${path}.${key}` : key;
|
|
@@ -1953,8 +2553,12 @@
|
|
|
1953
2553
|
});
|
|
1954
2554
|
}
|
|
1955
2555
|
/**
|
|
1956
|
-
* Determines if a state path should be persisted
|
|
2556
|
+
* Determines if a state path should be persisted.
|
|
2557
|
+
* Checks against include/exclude filters configured in persistence options.
|
|
2558
|
+
*
|
|
1957
2559
|
* @private
|
|
2560
|
+
* @param {string} path - The dot-notation path to check (e.g., "auth.user").
|
|
2561
|
+
* @returns {boolean} True if the path should be persisted, false otherwise.
|
|
1958
2562
|
*/ _shouldPersist(path) {
|
|
1959
2563
|
const { include, exclude } = this.persistence;
|
|
1960
2564
|
if (include && include.length > 0) {
|
|
@@ -1966,8 +2570,12 @@
|
|
|
1966
2570
|
return true;
|
|
1967
2571
|
}
|
|
1968
2572
|
/**
|
|
1969
|
-
* Saves current state to storage
|
|
2573
|
+
* Saves current state to storage.
|
|
2574
|
+
* Extracts persistable data and writes to localStorage/sessionStorage.
|
|
2575
|
+
* Does nothing if persistence is disabled or running in SSR environment.
|
|
2576
|
+
*
|
|
1970
2577
|
* @private
|
|
2578
|
+
* @returns {void}
|
|
1971
2579
|
*/ _saveState() {
|
|
1972
2580
|
if (!this.persistence.enabled || typeof window === "undefined") {
|
|
1973
2581
|
return;
|
|
@@ -1985,8 +2593,13 @@
|
|
|
1985
2593
|
}
|
|
1986
2594
|
}
|
|
1987
2595
|
/**
|
|
1988
|
-
* Extracts data that should be persisted
|
|
2596
|
+
* Extracts data that should be persisted.
|
|
2597
|
+
* Recursively extracts signal values for paths that pass persistence filters.
|
|
2598
|
+
*
|
|
1989
2599
|
* @private
|
|
2600
|
+
* @param {Record<string, unknown>} [currentState=this.state] - The state object to extract from.
|
|
2601
|
+
* @param {string} [path=""] - The current dot-notation path (for include/exclude filtering).
|
|
2602
|
+
* @returns {Record<string, unknown>} The extracted data object with raw values (not signals).
|
|
1990
2603
|
*/ _extractPersistedData(currentState = this.state, path = "") {
|
|
1991
2604
|
const result = {};
|
|
1992
2605
|
Object.entries(currentState).forEach(([key, value])=>{
|
|
@@ -2007,8 +2620,12 @@
|
|
|
2007
2620
|
return result;
|
|
2008
2621
|
}
|
|
2009
2622
|
/**
|
|
2010
|
-
* Sets up development tools integration
|
|
2623
|
+
* Sets up development tools integration.
|
|
2624
|
+
* Registers the store with Eleva DevTools if available and enabled.
|
|
2625
|
+
* Does nothing if devTools is disabled, running in SSR, or DevTools not installed.
|
|
2626
|
+
*
|
|
2011
2627
|
* @private
|
|
2628
|
+
* @returns {void}
|
|
2012
2629
|
*/ _setupDevTools() {
|
|
2013
2630
|
if (!this.devTools || typeof window === "undefined" || !window.__ELEVA_DEVTOOLS__) {
|
|
2014
2631
|
return;
|
|
@@ -2016,10 +2633,26 @@
|
|
|
2016
2633
|
window.__ELEVA_DEVTOOLS__.registerStore(this);
|
|
2017
2634
|
}
|
|
2018
2635
|
/**
|
|
2019
|
-
* Dispatches an action to mutate the state
|
|
2020
|
-
*
|
|
2021
|
-
*
|
|
2022
|
-
*
|
|
2636
|
+
* Dispatches an action to mutate the state.
|
|
2637
|
+
*
|
|
2638
|
+
* Execution flow:
|
|
2639
|
+
* 1. Retrieves the action function (supports namespaced actions like "auth.login")
|
|
2640
|
+
* 2. Records mutation for devtools/history (keeps last 100 mutations)
|
|
2641
|
+
* 3. Executes action with await (actions can be sync or async)
|
|
2642
|
+
* 4. Saves state if persistence is enabled
|
|
2643
|
+
* 5. Notifies all subscribers with (mutation, state)
|
|
2644
|
+
* 6. Notifies devtools if enabled
|
|
2645
|
+
*
|
|
2646
|
+
* @note Always returns a Promise regardless of whether the action is sync or async.
|
|
2647
|
+
* Subscriber callbacks that throw are caught and passed to onError handler.
|
|
2648
|
+
*
|
|
2649
|
+
* @async
|
|
2650
|
+
* @param {string} actionName - The name of the action to dispatch (supports dot notation for namespaces).
|
|
2651
|
+
* @param {unknown} payload - The payload to pass to the action.
|
|
2652
|
+
* @returns {Promise<unknown>} The result of the action (undefined if action returns nothing).
|
|
2653
|
+
* @throws {Error} If action is not found or action function throws.
|
|
2654
|
+
* @see subscribe - Listen for mutations triggered by dispatch.
|
|
2655
|
+
* @see getState - Get current state values.
|
|
2023
2656
|
*/ async dispatch(actionName, payload) {
|
|
2024
2657
|
try {
|
|
2025
2658
|
const action = this._getAction(actionName);
|
|
@@ -2067,8 +2700,12 @@
|
|
|
2067
2700
|
}
|
|
2068
2701
|
}
|
|
2069
2702
|
/**
|
|
2070
|
-
* Gets an action by name (supports namespaced actions)
|
|
2703
|
+
* Gets an action by name (supports namespaced actions).
|
|
2704
|
+
* Traverses the actions object using dot-notation path segments.
|
|
2705
|
+
*
|
|
2071
2706
|
* @private
|
|
2707
|
+
* @param {string} actionName - The action name, supports dot notation for namespaces (e.g., "auth.login").
|
|
2708
|
+
* @returns {ActionFunction | null} The action function if found and is a function, null otherwise.
|
|
2072
2709
|
*/ _getAction(actionName) {
|
|
2073
2710
|
const parts = actionName.split(".");
|
|
2074
2711
|
let current = this.actions;
|
|
@@ -2081,9 +2718,18 @@
|
|
|
2081
2718
|
return typeof current === "function" ? current : null;
|
|
2082
2719
|
}
|
|
2083
2720
|
/**
|
|
2084
|
-
* Subscribes to store mutations
|
|
2085
|
-
*
|
|
2086
|
-
*
|
|
2721
|
+
* Subscribes to store mutations.
|
|
2722
|
+
* Callback is invoked after every successful action dispatch.
|
|
2723
|
+
*
|
|
2724
|
+
* @param {SubscribeCallback} callback
|
|
2725
|
+
* Called after each mutation with:
|
|
2726
|
+
* - mutation.type: The action name that was dispatched
|
|
2727
|
+
* - mutation.payload: The payload passed to the action
|
|
2728
|
+
* - mutation.timestamp: When the mutation occurred (Date.now())
|
|
2729
|
+
* - state: The current state object (contains Signals)
|
|
2730
|
+
* @returns {() => void} Unsubscribe function. Call to stop receiving notifications.
|
|
2731
|
+
* @throws {Error} If callback is not a function.
|
|
2732
|
+
* @see dispatch - Triggers mutations that notify subscribers.
|
|
2087
2733
|
*/ subscribe(callback) {
|
|
2088
2734
|
if (typeof callback !== "function") {
|
|
2089
2735
|
throw new Error("Subscribe callback must be a function");
|
|
@@ -2095,20 +2741,32 @@
|
|
|
2095
2741
|
};
|
|
2096
2742
|
}
|
|
2097
2743
|
/**
|
|
2098
|
-
* Gets
|
|
2099
|
-
*
|
|
2744
|
+
* Gets current state values (not signals).
|
|
2745
|
+
*
|
|
2746
|
+
* @note When persistence include/exclude filters are configured,
|
|
2747
|
+
* this returns only the filtered subset of state.
|
|
2748
|
+
* @returns {Record<string, unknown>} The current state values (filtered by persistence config if set).
|
|
2749
|
+
* @see replaceState - Set state values.
|
|
2750
|
+
* @see subscribe - Listen for state changes.
|
|
2100
2751
|
*/ getState() {
|
|
2101
2752
|
return this._extractPersistedData();
|
|
2102
2753
|
}
|
|
2103
2754
|
/**
|
|
2104
|
-
* Replaces
|
|
2105
|
-
*
|
|
2755
|
+
* Replaces state values (useful for testing or state hydration).
|
|
2756
|
+
*
|
|
2757
|
+
* @note When persistence include/exclude filters are configured,
|
|
2758
|
+
* this only updates the filtered subset of state.
|
|
2759
|
+
* @param {Record<string, unknown>} newState - The new state object.
|
|
2760
|
+
* @returns {void}
|
|
2761
|
+
* @see getState - Get current state values.
|
|
2106
2762
|
*/ replaceState(newState) {
|
|
2107
2763
|
this._applyPersistedData(newState);
|
|
2108
2764
|
this._saveState();
|
|
2109
2765
|
}
|
|
2110
2766
|
/**
|
|
2111
|
-
* Clears persisted state from storage
|
|
2767
|
+
* Clears persisted state from storage.
|
|
2768
|
+
* Does nothing if persistence is disabled or running in SSR.
|
|
2769
|
+
* @returns {void}
|
|
2112
2770
|
*/ clearPersistedState() {
|
|
2113
2771
|
if (!this.persistence.enabled || typeof window === "undefined") {
|
|
2114
2772
|
return;
|
|
@@ -2123,11 +2781,14 @@
|
|
|
2123
2781
|
}
|
|
2124
2782
|
}
|
|
2125
2783
|
/**
|
|
2126
|
-
* Registers a new namespaced module at runtime
|
|
2127
|
-
*
|
|
2128
|
-
*
|
|
2129
|
-
* @param {
|
|
2130
|
-
* @param {
|
|
2784
|
+
* Registers a new namespaced module at runtime.
|
|
2785
|
+
* Logs a warning if the namespace already exists.
|
|
2786
|
+
* Module state is nested under `state[namespace]` and actions under `actions[namespace]`.
|
|
2787
|
+
* @param {string} namespace - The namespace for the module.
|
|
2788
|
+
* @param {StoreModule} module - The module definition.
|
|
2789
|
+
* @param {Record<string, unknown>} module.state - The module's initial state.
|
|
2790
|
+
* @param {Record<string, ActionFunction>} module.actions - The module's actions.
|
|
2791
|
+
* @returns {void}
|
|
2131
2792
|
*/ registerModule(namespace, module) {
|
|
2132
2793
|
if (this.state[namespace] || this.actions[namespace]) {
|
|
2133
2794
|
console.warn(`[StorePlugin] Module "${namespace}" already exists`);
|
|
@@ -2143,8 +2804,11 @@
|
|
|
2143
2804
|
this._saveState();
|
|
2144
2805
|
}
|
|
2145
2806
|
/**
|
|
2146
|
-
* Unregisters a namespaced module
|
|
2147
|
-
*
|
|
2807
|
+
* Unregisters a namespaced module.
|
|
2808
|
+
* Logs a warning if the namespace doesn't exist.
|
|
2809
|
+
* Removes both state and actions under the namespace.
|
|
2810
|
+
* @param {string} namespace - The namespace to unregister.
|
|
2811
|
+
* @returns {void}
|
|
2148
2812
|
*/ unregisterModule(namespace) {
|
|
2149
2813
|
if (!this.state[namespace] && !this.actions[namespace]) {
|
|
2150
2814
|
console.warn(`[StorePlugin] Module "${namespace}" does not exist`);
|
|
@@ -2155,10 +2819,11 @@
|
|
|
2155
2819
|
this._saveState();
|
|
2156
2820
|
}
|
|
2157
2821
|
/**
|
|
2158
|
-
* Creates a new reactive state property at runtime
|
|
2159
|
-
*
|
|
2160
|
-
* @param {
|
|
2161
|
-
* @
|
|
2822
|
+
* Creates a new reactive state property at runtime.
|
|
2823
|
+
*
|
|
2824
|
+
* @param {string} key - The state key.
|
|
2825
|
+
* @param {*} initialValue - The initial value.
|
|
2826
|
+
* @returns {Signal} The created signal, or existing signal if key exists.
|
|
2162
2827
|
*/ createState(key, initialValue) {
|
|
2163
2828
|
if (this.state[key]) {
|
|
2164
2829
|
return this.state[key]; // Return existing state
|
|
@@ -2168,21 +2833,50 @@
|
|
|
2168
2833
|
return this.state[key];
|
|
2169
2834
|
}
|
|
2170
2835
|
/**
|
|
2171
|
-
* Creates a new action at runtime
|
|
2172
|
-
*
|
|
2173
|
-
*
|
|
2836
|
+
* Creates a new action at runtime.
|
|
2837
|
+
* Overwrites existing action if name already exists.
|
|
2838
|
+
* Supports dot-notation for namespaced actions (e.g., "auth.login").
|
|
2839
|
+
* @param {string} name - The action name (supports dot notation for namespaces).
|
|
2840
|
+
* @param {ActionFunction} actionFn - The action function (receives state and payload).
|
|
2841
|
+
* @returns {void}
|
|
2842
|
+
* @throws {Error} If actionFn is not a function.
|
|
2843
|
+
* @example
|
|
2844
|
+
* // Root-level action
|
|
2845
|
+
* store.createAction("increment", (state) => state.count.value++);
|
|
2846
|
+
*
|
|
2847
|
+
* // Namespaced action
|
|
2848
|
+
* store.createAction("auth.login", async (state, credentials) => {
|
|
2849
|
+
* // ... login logic
|
|
2850
|
+
* });
|
|
2174
2851
|
*/ createAction(name, actionFn) {
|
|
2175
2852
|
if (typeof actionFn !== "function") {
|
|
2176
2853
|
throw new Error("Action must be a function");
|
|
2177
2854
|
}
|
|
2178
|
-
|
|
2855
|
+
// Fast path: no dot means simple action (avoids array allocation)
|
|
2856
|
+
if (name.indexOf(".") === -1) {
|
|
2857
|
+
this.actions[name] = actionFn;
|
|
2858
|
+
return;
|
|
2859
|
+
}
|
|
2860
|
+
// Namespaced action, traverse/create nested structure
|
|
2861
|
+
const parts = name.split(".");
|
|
2862
|
+
const lastIndex = parts.length - 1;
|
|
2863
|
+
let current = this.actions;
|
|
2864
|
+
for(let i = 0; i < lastIndex; i++){
|
|
2865
|
+
current = current[parts[i]] || (current[parts[i]] = {});
|
|
2866
|
+
}
|
|
2867
|
+
current[parts[lastIndex]] = actionFn;
|
|
2179
2868
|
}
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2869
|
+
/**
|
|
2870
|
+
* Creates a new Store instance.
|
|
2871
|
+
* Initializes state signals, actions, persistence, and devtools integration.
|
|
2872
|
+
*
|
|
2873
|
+
* @constructor
|
|
2874
|
+
*/ constructor(){
|
|
2875
|
+
/** @type {Record<string, Signal | Record<string, unknown>>} */ this.state = {};
|
|
2876
|
+
/** @type {Record<string, ActionFunction | Record<string, ActionFunction>>} */ this.actions = {};
|
|
2877
|
+
/** @type {Set<SubscribeCallback>} */ this.subscribers = new Set();
|
|
2878
|
+
/** @type {StoreMutation[]} */ this.mutations = [];
|
|
2879
|
+
/** @type {{enabled: boolean, key: string, storage: string, include: string[]|null, exclude: string[]|null}} */ this.persistence = {
|
|
2186
2880
|
enabled: false,
|
|
2187
2881
|
key: "eleva-store",
|
|
2188
2882
|
storage: "localStorage",
|
|
@@ -2190,8 +2884,8 @@
|
|
|
2190
2884
|
exclude: null,
|
|
2191
2885
|
...persistence
|
|
2192
2886
|
};
|
|
2193
|
-
this.devTools = devTools;
|
|
2194
|
-
this.onError = onError;
|
|
2887
|
+
/** @type {boolean} */ this.devTools = devTools;
|
|
2888
|
+
/** @type {((error: Error, context: string) => void)|null} */ this.onError = onError;
|
|
2195
2889
|
this._initializeState(state, actions);
|
|
2196
2890
|
this._initializeNamespaces(namespaces);
|
|
2197
2891
|
this._loadPersistedState();
|
|
@@ -2203,7 +2897,13 @@
|
|
|
2203
2897
|
// Store the original mount method to override it
|
|
2204
2898
|
const originalMount = eleva.mount;
|
|
2205
2899
|
/**
|
|
2206
|
-
*
|
|
2900
|
+
* Overridden mount method that injects store context into components.
|
|
2901
|
+
* Wraps the original mount to add `ctx.store` to the component's setup context.
|
|
2902
|
+
*
|
|
2903
|
+
* @param {HTMLElement} container - The DOM element where the component will be mounted.
|
|
2904
|
+
* @param {string | ComponentDefinition} compName - Component name or definition.
|
|
2905
|
+
* @param {ComponentProps} [props={}] - Optional properties to pass to the component.
|
|
2906
|
+
* @returns {Promise<MountResult>} The mount result.
|
|
2207
2907
|
*/ eleva.mount = async (container, compName, props = {})=>{
|
|
2208
2908
|
// Get the component definition
|
|
2209
2909
|
const componentDef = typeof compName === "string" ? eleva._components.get(compName) || compName : compName;
|
|
@@ -2214,8 +2914,7 @@
|
|
|
2214
2914
|
const wrappedComponent = {
|
|
2215
2915
|
...componentDef,
|
|
2216
2916
|
async setup (ctx) {
|
|
2217
|
-
|
|
2218
|
-
ctx.store = {
|
|
2917
|
+
/** @type {StoreApi} */ ctx.store = {
|
|
2219
2918
|
// Core store functionality
|
|
2220
2919
|
state: store.state,
|
|
2221
2920
|
dispatch: store.dispatch.bind(store),
|
|
@@ -2241,7 +2940,16 @@
|
|
|
2241
2940
|
};
|
|
2242
2941
|
// Override _mountComponents to ensure child components also get store context
|
|
2243
2942
|
const originalMountComponents = eleva._mountComponents;
|
|
2244
|
-
|
|
2943
|
+
/**
|
|
2944
|
+
* Overridden _mountComponents method that injects store context into child components.
|
|
2945
|
+
* Wraps each child component's setup function to add `ctx.store` before mounting.
|
|
2946
|
+
*
|
|
2947
|
+
* @param {HTMLElement} container - The parent container element.
|
|
2948
|
+
* @param {ChildrenMap} children - Map of selectors to component definitions.
|
|
2949
|
+
* @param {MountResult[]} childInstances - Array to store mounted instances.
|
|
2950
|
+
* @param {ComponentContext & SetupResult} context - Parent component context.
|
|
2951
|
+
* @returns {Promise<void>}
|
|
2952
|
+
*/ eleva._mountComponents = async (container, children, childInstances, context)=>{
|
|
2245
2953
|
// Create wrapped children with store injection
|
|
2246
2954
|
const wrappedChildren = {};
|
|
2247
2955
|
for (const [selector, childComponent] of Object.entries(children)){
|
|
@@ -2250,8 +2958,7 @@
|
|
|
2250
2958
|
wrappedChildren[selector] = {
|
|
2251
2959
|
...componentDef,
|
|
2252
2960
|
async setup (ctx) {
|
|
2253
|
-
|
|
2254
|
-
ctx.store = {
|
|
2961
|
+
/** @type {StoreApi} */ ctx.store = {
|
|
2255
2962
|
// Core store functionality
|
|
2256
2963
|
state: store.state,
|
|
2257
2964
|
dispatch: store.dispatch.bind(store),
|
|
@@ -2277,23 +2984,23 @@
|
|
|
2277
2984
|
}
|
|
2278
2985
|
}
|
|
2279
2986
|
// Call original _mountComponents with wrapped children
|
|
2280
|
-
return await originalMountComponents.call(eleva, container, wrappedChildren, childInstances);
|
|
2987
|
+
return await originalMountComponents.call(eleva, container, wrappedChildren, childInstances, context);
|
|
2281
2988
|
};
|
|
2282
2989
|
// Expose store instance and utilities on the Eleva instance
|
|
2283
|
-
eleva.store = store;
|
|
2990
|
+
/** @type {StoreApi} */ eleva.store = store;
|
|
2284
2991
|
/**
|
|
2285
|
-
* Expose utility methods on the Eleva instance
|
|
2286
|
-
*
|
|
2287
|
-
*/ eleva.createAction = (name, actionFn)=>{
|
|
2288
|
-
store.
|
|
2992
|
+
* Expose utility methods on the Eleva instance.
|
|
2993
|
+
* These are top-level helpers (e.g., `eleva.dispatch`) in addition to `eleva.store`.
|
|
2994
|
+
*/ /** @type {(name: string, actionFn: ActionFunction) => void} */ eleva.createAction = (name, actionFn)=>{
|
|
2995
|
+
store.createAction(name, actionFn);
|
|
2289
2996
|
};
|
|
2290
|
-
eleva.dispatch = (actionName, payload)=>{
|
|
2997
|
+
/** @type {DispatchFunction} */ eleva.dispatch = (actionName, payload)=>{
|
|
2291
2998
|
return store.dispatch(actionName, payload);
|
|
2292
2999
|
};
|
|
2293
|
-
eleva.getState = ()=>{
|
|
3000
|
+
/** @type {() => Record<string, unknown>} */ eleva.getState = ()=>{
|
|
2294
3001
|
return store.getState();
|
|
2295
3002
|
};
|
|
2296
|
-
eleva.subscribe = (callback)=>{
|
|
3003
|
+
/** @type {(callback: SubscribeCallback) => () => void} */ eleva.subscribe = (callback)=>{
|
|
2297
3004
|
return store.subscribe(callback);
|
|
2298
3005
|
};
|
|
2299
3006
|
// Store original methods for cleanup
|
|
@@ -2301,14 +3008,17 @@
|
|
|
2301
3008
|
eleva._originalMountComponents = originalMountComponents;
|
|
2302
3009
|
},
|
|
2303
3010
|
/**
|
|
2304
|
-
* Uninstalls the plugin from the Eleva instance
|
|
2305
|
-
*
|
|
2306
|
-
* @param {Object} eleva - The Eleva instance
|
|
3011
|
+
* Uninstalls the plugin from the Eleva instance.
|
|
2307
3012
|
*
|
|
3013
|
+
* @public
|
|
3014
|
+
* @param {Eleva} eleva - The Eleva instance.
|
|
3015
|
+
* @returns {void}
|
|
2308
3016
|
* @description
|
|
2309
3017
|
* Restores the original Eleva methods and removes all plugin-specific
|
|
2310
3018
|
* functionality. This method should be called when the plugin is no
|
|
2311
3019
|
* longer needed.
|
|
3020
|
+
* Also removes `eleva.store` and top-level helpers (`eleva.dispatch`,
|
|
3021
|
+
* `eleva.getState`, `eleva.subscribe`, `eleva.createAction`).
|
|
2312
3022
|
*
|
|
2313
3023
|
* @example
|
|
2314
3024
|
* // Uninstall the plugin
|
|
@@ -2344,4 +3054,4 @@
|
|
|
2344
3054
|
};
|
|
2345
3055
|
|
|
2346
3056
|
export { AttrPlugin as Attr, RouterPlugin as Router, StorePlugin as Store };
|
|
2347
|
-
//# sourceMappingURL=eleva-plugins.
|
|
3057
|
+
//# sourceMappingURL=eleva-plugins.js.map
|