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,4 +1,4 @@
|
|
|
1
|
-
/*! Eleva Plugins v1.0
|
|
1
|
+
/*! Eleva Plugins v1.1.0 | MIT License | https://elevajs.com */
|
|
2
2
|
(function (global, factory) {
|
|
3
3
|
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
|
|
4
4
|
typeof define === 'function' && define.amd ? define(['exports'], factory) :
|
|
@@ -6,6 +6,37 @@
|
|
|
6
6
|
})(this, (function (exports) { 'use strict';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
+
* @module eleva/plugins/attr
|
|
10
|
+
* @fileoverview Attribute plugin providing ARIA, data, boolean, and dynamic attribute handling.
|
|
11
|
+
*/ // ============================================================================
|
|
12
|
+
// TYPE DEFINITIONS
|
|
13
|
+
// ============================================================================
|
|
14
|
+
// -----------------------------------------------------------------------------
|
|
15
|
+
// External Type Imports
|
|
16
|
+
// -----------------------------------------------------------------------------
|
|
17
|
+
/**
|
|
18
|
+
* Type imports from the Eleva core library.
|
|
19
|
+
* @typedef {import('eleva').Eleva} Eleva
|
|
20
|
+
*/ // -----------------------------------------------------------------------------
|
|
21
|
+
// Attr Type Definitions
|
|
22
|
+
// -----------------------------------------------------------------------------
|
|
23
|
+
/**
|
|
24
|
+
* Configuration options for the AttrPlugin.
|
|
25
|
+
* @typedef {Object} AttrPluginOptions
|
|
26
|
+
* @property {boolean} [enableAria=true]
|
|
27
|
+
* Enable ARIA attribute handling.
|
|
28
|
+
* @property {boolean} [enableData=true]
|
|
29
|
+
* Enable data attribute handling.
|
|
30
|
+
* @property {boolean} [enableBoolean=true]
|
|
31
|
+
* Enable boolean attribute handling.
|
|
32
|
+
* @property {boolean} [enableDynamic=true]
|
|
33
|
+
* Enable dynamic property detection.
|
|
34
|
+
* @description Configuration options passed to AttrPlugin.install().
|
|
35
|
+
*/ /**
|
|
36
|
+
* Function signature for attribute update operations.
|
|
37
|
+
* @typedef {(oldEl: HTMLElement, newEl: HTMLElement) => void} AttributeUpdateFunction
|
|
38
|
+
* @description Updates attributes on oldEl to match newEl's attributes.
|
|
39
|
+
*/ /**
|
|
9
40
|
* A regular expression to match hyphenated lowercase letters.
|
|
10
41
|
* @private
|
|
11
42
|
* @type {RegExp}
|
|
@@ -46,33 +77,76 @@
|
|
|
46
77
|
/**
|
|
47
78
|
* Plugin version
|
|
48
79
|
* @type {string}
|
|
49
|
-
*/ version: "1.
|
|
80
|
+
*/ version: "1.1.0",
|
|
50
81
|
/**
|
|
51
82
|
* Plugin description
|
|
52
83
|
* @type {string}
|
|
53
84
|
*/ description: "Advanced attribute handling for Eleva components",
|
|
54
85
|
/**
|
|
55
|
-
* Installs the plugin into the Eleva instance
|
|
86
|
+
* Installs the plugin into the Eleva instance.
|
|
87
|
+
*
|
|
88
|
+
* @public
|
|
89
|
+
* Method wrapping behavior:
|
|
90
|
+
* - Stores original `_patchNode` in `renderer._originalPatchNode`
|
|
91
|
+
* - Overrides `renderer._patchNode` to use enhanced attribute handling
|
|
92
|
+
* - Adds `renderer.updateAttributes` and `eleva.updateElementAttributes` helpers
|
|
93
|
+
* - Call `uninstall()` to restore original behavior
|
|
94
|
+
*
|
|
95
|
+
* @param {Eleva} eleva - The Eleva instance to enhance.
|
|
96
|
+
* @param {AttrPluginOptions} options - Plugin configuration options.
|
|
97
|
+
* @param {boolean} [options.enableAria=true] - Enable ARIA attribute handling.
|
|
98
|
+
* Maps aria-* attributes to DOM properties (e.g., aria-expanded → ariaExpanded).
|
|
99
|
+
* @param {boolean} [options.enableData=true] - Enable data attribute handling.
|
|
100
|
+
* Syncs data-* attributes with element.dataset for consistent access.
|
|
101
|
+
* @param {boolean} [options.enableBoolean=true] - Enable boolean attribute handling.
|
|
102
|
+
* Treats empty strings and attribute names as true, "false" string as false.
|
|
103
|
+
* @param {boolean} [options.enableDynamic=true] - Enable dynamic property detection.
|
|
104
|
+
* Searches element prototype chain for property matches (useful for custom elements).
|
|
105
|
+
* @returns {void}
|
|
106
|
+
* @example
|
|
107
|
+
* // Basic installation with defaults
|
|
108
|
+
* app.use(AttrPlugin);
|
|
56
109
|
*
|
|
57
|
-
* @
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
110
|
+
* @example
|
|
111
|
+
* // Custom configuration
|
|
112
|
+
* app.use(AttrPlugin, {
|
|
113
|
+
* enableAria: true,
|
|
114
|
+
* enableData: true,
|
|
115
|
+
* enableBoolean: true,
|
|
116
|
+
* enableDynamic: false // Disable for performance
|
|
117
|
+
* });
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* // Using ARIA attributes in templates
|
|
121
|
+
* template: (ctx) => `
|
|
122
|
+
* <div role="dialog" aria-modal="true" aria-labelledby="title">
|
|
123
|
+
* <h2 id="title">Modal Title</h2>
|
|
124
|
+
* <button aria-expanded="${ctx.isOpen.value}">Toggle</button>
|
|
125
|
+
* </div>
|
|
126
|
+
* `
|
|
127
|
+
* @see uninstall - Remove the plugin and restore original behavior.
|
|
63
128
|
*/ install (eleva, options = {}) {
|
|
64
129
|
const { enableAria = true, enableData = true, enableBoolean = true, enableDynamic = true } = options;
|
|
65
130
|
/**
|
|
66
131
|
* Updates the attributes of an element to match a new element's attributes.
|
|
67
|
-
* This method provides sophisticated attribute processing including:
|
|
68
|
-
* - ARIA attribute handling with proper property mapping
|
|
69
|
-
* - Data attribute management
|
|
70
|
-
* - Boolean attribute processing
|
|
71
|
-
* - Dynamic property detection and mapping
|
|
72
|
-
* - Attribute cleanup and removal
|
|
73
132
|
*
|
|
74
|
-
*
|
|
75
|
-
* @
|
|
133
|
+
* Processing order:
|
|
134
|
+
* 1. Skip event attributes (@click, @input) - handled by Eleva's event system
|
|
135
|
+
* 2. Skip unchanged attributes - optimization
|
|
136
|
+
* 3. ARIA attributes (aria-*): Map to DOM properties (aria-expanded → ariaExpanded)
|
|
137
|
+
* 4. Data attributes (data-*): Update both dataset and attribute
|
|
138
|
+
* 5. Boolean attributes: Handle empty string as true, "false" as false
|
|
139
|
+
* 6. Other attributes: Map to properties with dynamic detection for custom elements
|
|
140
|
+
* 7. Remove old attributes not present in new element
|
|
141
|
+
*
|
|
142
|
+
* Dynamic property detection (when enableDynamic=true):
|
|
143
|
+
* - Checks if property exists directly on element
|
|
144
|
+
* - Searches element's prototype chain for case-insensitive matches
|
|
145
|
+
* - Enables compatibility with custom elements and Web Components
|
|
146
|
+
*
|
|
147
|
+
* @inner
|
|
148
|
+
* @param {HTMLElement} oldEl - The original element to update (modified in-place).
|
|
149
|
+
* @param {HTMLElement} newEl - The reference element with desired attributes.
|
|
76
150
|
* @returns {void}
|
|
77
151
|
*/ const updateAttributes = (oldEl, newEl)=>{
|
|
78
152
|
const oldAttrs = oldEl.attributes;
|
|
@@ -143,8 +217,14 @@
|
|
|
143
217
|
// Store the original _patchNode method
|
|
144
218
|
const originalPatchNode = eleva.renderer._patchNode;
|
|
145
219
|
eleva.renderer._originalPatchNode = originalPatchNode;
|
|
146
|
-
|
|
147
|
-
|
|
220
|
+
/**
|
|
221
|
+
* Overridden _patchNode method that uses enhanced attribute handling.
|
|
222
|
+
* Delegates to `updateAttributes` instead of the basic `_updateAttributes`.
|
|
223
|
+
*
|
|
224
|
+
* @param {Node} oldNode - The original DOM node to update.
|
|
225
|
+
* @param {Node} newNode - The new DOM node with desired state.
|
|
226
|
+
* @returns {void}
|
|
227
|
+
*/ eleva.renderer._patchNode = function(oldNode, newNode) {
|
|
148
228
|
if (oldNode?._eleva_instance) return;
|
|
149
229
|
if (oldNode.nodeType === Node.TEXT_NODE) {
|
|
150
230
|
if (oldNode.nodeValue !== newNode.nodeValue) {
|
|
@@ -168,12 +248,21 @@
|
|
|
168
248
|
options
|
|
169
249
|
});
|
|
170
250
|
// Add utility methods for manual attribute updates
|
|
171
|
-
eleva.updateElementAttributes = updateAttributes;
|
|
251
|
+
/** @type {AttributeUpdateFunction} */ eleva.updateElementAttributes = updateAttributes;
|
|
172
252
|
},
|
|
173
253
|
/**
|
|
174
|
-
* Uninstalls the plugin from the Eleva instance
|
|
254
|
+
* Uninstalls the plugin from the Eleva instance.
|
|
175
255
|
*
|
|
176
|
-
* @
|
|
256
|
+
* @public
|
|
257
|
+
* @param {Eleva} eleva - The Eleva instance.
|
|
258
|
+
* @returns {void}
|
|
259
|
+
* @description
|
|
260
|
+
* Restores the original renderer patching behavior and removes
|
|
261
|
+
* `eleva.updateElementAttributes`.
|
|
262
|
+
* @example
|
|
263
|
+
* // Uninstall the plugin
|
|
264
|
+
* AttrPlugin.uninstall(app);
|
|
265
|
+
* @see install - Install the plugin.
|
|
177
266
|
*/ uninstall (eleva) {
|
|
178
267
|
// Restore original _patchNode method if it exists
|
|
179
268
|
if (eleva.renderer && eleva.renderer._originalPatchNode) {
|
|
@@ -190,60 +279,176 @@
|
|
|
190
279
|
};
|
|
191
280
|
|
|
192
281
|
/**
|
|
282
|
+
* @module eleva/plugins/router
|
|
283
|
+
* @fileoverview Client-side router plugin with hash, history, and query modes,
|
|
284
|
+
* navigation guards, and lifecycle hooks.
|
|
285
|
+
*/ // ============================================================================
|
|
286
|
+
// TYPE DEFINITIONS
|
|
287
|
+
// ============================================================================
|
|
288
|
+
// -----------------------------------------------------------------------------
|
|
289
|
+
// External Type Imports
|
|
290
|
+
// -----------------------------------------------------------------------------
|
|
291
|
+
/**
|
|
292
|
+
* Type imports from the Eleva core library.
|
|
193
293
|
* @typedef {import('eleva').Eleva} Eleva
|
|
194
|
-
* @typedef {import('eleva').Signal} Signal
|
|
195
294
|
* @typedef {import('eleva').ComponentDefinition} ComponentDefinition
|
|
196
295
|
* @typedef {import('eleva').Emitter} Emitter
|
|
197
296
|
* @typedef {import('eleva').MountResult} MountResult
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
297
|
+
* @typedef {import('eleva').UnsubscribeFunction} UnsubscribeFunction
|
|
298
|
+
*/ /**
|
|
299
|
+
* Generic type import.
|
|
300
|
+
* @template T
|
|
301
|
+
* @typedef {import('eleva').Signal<T>} Signal
|
|
302
|
+
*/ // -----------------------------------------------------------------------------
|
|
303
|
+
// Router Events
|
|
304
|
+
// -----------------------------------------------------------------------------
|
|
305
|
+
/**
|
|
306
|
+
* Fired when the router initialization completes successfully.
|
|
307
|
+
* @event router:ready
|
|
308
|
+
* @type {Router}
|
|
309
|
+
*/ /**
|
|
310
|
+
* Fired when an error occurs during navigation or route handling.
|
|
311
|
+
* @event router:error
|
|
312
|
+
* @type {Error}
|
|
313
|
+
*/ /**
|
|
314
|
+
* Fired when no matching route is found for the requested path.
|
|
315
|
+
* @event router:notFound
|
|
316
|
+
* @type {{to: RouteLocation, from: RouteLocation | null, path: string}}
|
|
317
|
+
*/ /**
|
|
318
|
+
* Fired before guards run, allowing plugins to block or redirect navigation.
|
|
319
|
+
* @event router:beforeEach
|
|
320
|
+
* @type {NavigationContext}
|
|
321
|
+
*/ /**
|
|
322
|
+
* Fired before component resolution, allowing plugins to modify the resolve context.
|
|
323
|
+
* @event router:beforeResolve
|
|
324
|
+
* @type {ResolveContext}
|
|
325
|
+
*/ /**
|
|
326
|
+
* Fired after components are resolved successfully.
|
|
327
|
+
* @event router:afterResolve
|
|
328
|
+
* @type {ResolveContext}
|
|
329
|
+
*/ /**
|
|
330
|
+
* Fired after leaving the previous route.
|
|
331
|
+
* @event router:afterLeave
|
|
332
|
+
* @type {{to: RouteLocation, from: RouteLocation}}
|
|
333
|
+
*/ /**
|
|
334
|
+
* Fired before DOM rendering begins.
|
|
335
|
+
* @event router:beforeRender
|
|
336
|
+
* @type {RenderContext}
|
|
337
|
+
*/ /**
|
|
338
|
+
* Fired after DOM rendering completes.
|
|
339
|
+
* @event router:afterRender
|
|
340
|
+
* @type {RenderContext}
|
|
341
|
+
*/ /**
|
|
342
|
+
* Fired after render for scroll behavior handling.
|
|
343
|
+
* @event router:scroll
|
|
344
|
+
* @type {ScrollContext}
|
|
345
|
+
*/ /**
|
|
346
|
+
* Fired after entering the new route.
|
|
347
|
+
* @event router:afterEnter
|
|
348
|
+
* @type {{to: RouteLocation, from: RouteLocation | null}}
|
|
349
|
+
*/ /**
|
|
350
|
+
* Fired after navigation completes successfully.
|
|
351
|
+
* @event router:afterEach
|
|
352
|
+
* @type {{to: RouteLocation, from: RouteLocation | null}}
|
|
353
|
+
*/ /**
|
|
354
|
+
* Fired when a route is dynamically added.
|
|
355
|
+
* @event router:routeAdded
|
|
356
|
+
* @type {RouteDefinition}
|
|
357
|
+
*/ /**
|
|
358
|
+
* Fired when a route is dynamically removed.
|
|
359
|
+
* @event router:routeRemoved
|
|
360
|
+
* @type {RouteDefinition}
|
|
361
|
+
*/ // -----------------------------------------------------------------------------
|
|
362
|
+
// Router Data Types
|
|
363
|
+
// -----------------------------------------------------------------------------
|
|
201
364
|
/**
|
|
202
|
-
* @typedef {'hash' | 'history' | 'query'} RouterMode
|
|
203
365
|
* The routing mode determines how the router manages URL state.
|
|
204
366
|
* - `hash`: Uses URL hash (e.g., `/#/path`) - works without server config
|
|
205
367
|
* - `history`: Uses HTML5 History API (e.g., `/path`) - requires server config
|
|
206
368
|
* - `query`: Uses query parameters (e.g., `?view=/path`) - useful for embedded apps
|
|
369
|
+
* @typedef {'hash' | 'history' | 'query'} RouterMode
|
|
370
|
+
*/ /**
|
|
371
|
+
* Route parameters extracted from the URL path.
|
|
372
|
+
* @typedef {Record<string, string>} RouteParams
|
|
373
|
+
* @description Key-value pairs extracted from dynamic route segments (e.g., `/users/:id` → `{ id: '123' }`).
|
|
374
|
+
*/ /**
|
|
375
|
+
* Query parameters from the URL query string.
|
|
376
|
+
* @typedef {Record<string, string>} QueryParams
|
|
377
|
+
* @description Key-value pairs from the URL query string (e.g., `?page=1&sort=name`).
|
|
207
378
|
*/ /**
|
|
379
|
+
* Navigation input parameters supporting multiple value types.
|
|
380
|
+
* @typedef {Record<string, string | number | boolean>} NavigationParams
|
|
381
|
+
* @description Parameters passed to navigation functions, automatically converted to strings in URLs.
|
|
382
|
+
*/ /**
|
|
383
|
+
* Function signature for programmatic navigation.
|
|
384
|
+
* @typedef {(location: string | NavigationTarget, params?: NavigationParams) => Promise<boolean>} NavigateFunction
|
|
385
|
+
* @description Returns true if navigation succeeded, false if blocked by a guard.
|
|
386
|
+
*/ /**
|
|
387
|
+
* Router configuration options.
|
|
208
388
|
* @typedef {Object} RouterOptions
|
|
209
|
-
* @property {RouterMode} [mode='hash']
|
|
210
|
-
*
|
|
211
|
-
* @property {string} [
|
|
212
|
-
*
|
|
213
|
-
* @property {
|
|
214
|
-
*
|
|
215
|
-
* @property {
|
|
389
|
+
* @property {RouterMode} [mode='hash']
|
|
390
|
+
* The routing mode to use.
|
|
391
|
+
* @property {string} [queryParam='view']
|
|
392
|
+
* Query parameter name for 'query' mode.
|
|
393
|
+
* @property {string} [viewSelector='view']
|
|
394
|
+
* Base selector for the view element.
|
|
395
|
+
* @property {string} mount
|
|
396
|
+
* CSS selector for the mount point element.
|
|
397
|
+
* @property {RouteDefinition[]} routes
|
|
398
|
+
* Array of route definitions.
|
|
399
|
+
* @property {RouteComponent} [globalLayout]
|
|
400
|
+
* Default layout for all routes.
|
|
401
|
+
* @property {NavigationGuard} [onBeforeEach]
|
|
402
|
+
* Global navigation guard.
|
|
403
|
+
* @property {boolean} [autoStart=true]
|
|
404
|
+
* Whether to start the router automatically.
|
|
216
405
|
* @description Configuration options for the Router plugin.
|
|
217
406
|
*/ /**
|
|
407
|
+
* Object describing a navigation target for `router.navigate()`.
|
|
218
408
|
* @typedef {Object} NavigationTarget
|
|
219
|
-
* @property {string} path
|
|
220
|
-
*
|
|
221
|
-
* @property {
|
|
222
|
-
*
|
|
223
|
-
* @property {
|
|
409
|
+
* @property {string} path
|
|
410
|
+
* The target path (can include params like '/users/:id').
|
|
411
|
+
* @property {NavigationParams} [params]
|
|
412
|
+
* Route parameters to inject.
|
|
413
|
+
* @property {NavigationParams} [query]
|
|
414
|
+
* Query parameters to append.
|
|
415
|
+
* @property {boolean} [replace=false]
|
|
416
|
+
* Whether to replace current history entry.
|
|
417
|
+
* @property {unknown} [state]
|
|
418
|
+
* History state to pass.
|
|
224
419
|
* @description Object describing a navigation target for `router.navigate()`.
|
|
225
420
|
*/ /**
|
|
421
|
+
* Saved scroll position.
|
|
226
422
|
* @typedef {Object} ScrollPosition
|
|
227
|
-
* @property {number} x
|
|
228
|
-
*
|
|
423
|
+
* @property {number} x
|
|
424
|
+
* Horizontal scroll position.
|
|
425
|
+
* @property {number} y
|
|
426
|
+
* Vertical scroll position.
|
|
229
427
|
* @description Represents a saved scroll position.
|
|
230
428
|
*/ /**
|
|
429
|
+
* Internal representation of a parsed route path segment.
|
|
231
430
|
* @typedef {Object} RouteSegment
|
|
232
|
-
* @property {'static' | 'param'} type
|
|
233
|
-
*
|
|
234
|
-
* @property {string} [
|
|
431
|
+
* @property {'static' | 'param'} type
|
|
432
|
+
* The segment type.
|
|
433
|
+
* @property {string} [value]
|
|
434
|
+
* The segment value (static segments).
|
|
435
|
+
* @property {string} [name]
|
|
436
|
+
* The parameter name (param segments).
|
|
235
437
|
* @description Internal representation of a parsed route path segment.
|
|
236
438
|
* @private
|
|
237
439
|
*/ /**
|
|
440
|
+
* Result of matching a path against route definitions.
|
|
238
441
|
* @typedef {Object} RouteMatch
|
|
239
|
-
* @property {RouteDefinition} route
|
|
240
|
-
*
|
|
442
|
+
* @property {RouteDefinition} route
|
|
443
|
+
* The matched route definition.
|
|
444
|
+
* @property {RouteParams} params
|
|
445
|
+
* The extracted route parameters.
|
|
241
446
|
* @description Result of matching a path against route definitions.
|
|
242
447
|
* @private
|
|
243
448
|
*/ /**
|
|
244
|
-
*
|
|
245
|
-
* @
|
|
246
|
-
* Common properties include:
|
|
449
|
+
* Arbitrary metadata attached to routes for use in guards and components.
|
|
450
|
+
* @typedef {Record<string, unknown>} RouteMeta
|
|
451
|
+
* @description Common properties include:
|
|
247
452
|
* - `requiresAuth: boolean` - Whether the route requires authentication
|
|
248
453
|
* - `title: string` - Page title for the route
|
|
249
454
|
* - `roles: string[]` - Required user roles
|
|
@@ -254,76 +459,119 @@
|
|
|
254
459
|
* meta: { requiresAuth: true, roles: ['admin'], title: 'Admin Dashboard' }
|
|
255
460
|
* }
|
|
256
461
|
*/ /**
|
|
462
|
+
* Interface for the router's error handling system.
|
|
257
463
|
* @typedef {Object} RouterErrorHandler
|
|
258
|
-
* @property {(error: Error, context: string, details?: Record<string,
|
|
259
|
-
*
|
|
260
|
-
* @property {(message: string,
|
|
464
|
+
* @property {(error: Error, context: string, details?: Record<string, unknown>) => void} handle
|
|
465
|
+
* Throws a formatted error.
|
|
466
|
+
* @property {(message: string, details?: Record<string, unknown>) => void} warn
|
|
467
|
+
* Logs a warning.
|
|
468
|
+
* @property {(message: string, error: Error, details?: Record<string, unknown>) => void} log
|
|
469
|
+
* Logs an error without throwing.
|
|
261
470
|
* @description Interface for the router's error handling system.
|
|
262
|
-
*/ //
|
|
263
|
-
// Event Callback
|
|
264
|
-
//
|
|
471
|
+
*/ // -----------------------------------------------------------------------------
|
|
472
|
+
// Event Callback Types
|
|
473
|
+
// -----------------------------------------------------------------------------
|
|
265
474
|
/**
|
|
475
|
+
* Callback for `router:beforeEach` event.
|
|
266
476
|
* @callback NavigationContextCallback
|
|
267
|
-
* @param {NavigationContext} context
|
|
477
|
+
* @param {NavigationContext} context
|
|
478
|
+
* The navigation context (can be modified to block/redirect).
|
|
268
479
|
* @returns {void | Promise<void>}
|
|
269
|
-
* @description
|
|
480
|
+
* @description Modify context to control navigation flow.
|
|
270
481
|
*/ /**
|
|
482
|
+
* Callback for `router:beforeResolve` and `router:afterResolve` events.
|
|
271
483
|
* @callback ResolveContextCallback
|
|
272
|
-
* @param {ResolveContext} context
|
|
484
|
+
* @param {ResolveContext} context
|
|
485
|
+
* The resolve context (can be modified to block/redirect).
|
|
273
486
|
* @returns {void | Promise<void>}
|
|
274
487
|
* @description Callback for `router:beforeResolve` and `router:afterResolve` events.
|
|
275
488
|
*/ /**
|
|
489
|
+
* Callback for `router:beforeRender` and `router:afterRender` events.
|
|
276
490
|
* @callback RenderContextCallback
|
|
277
|
-
* @param {RenderContext} context
|
|
491
|
+
* @param {RenderContext} context
|
|
492
|
+
* The render context.
|
|
278
493
|
* @returns {void | Promise<void>}
|
|
279
494
|
* @description Callback for `router:beforeRender` and `router:afterRender` events.
|
|
280
495
|
*/ /**
|
|
496
|
+
* Callback for `router:scroll` event.
|
|
281
497
|
* @callback ScrollContextCallback
|
|
282
|
-
* @param {ScrollContext} context
|
|
498
|
+
* @param {ScrollContext} context
|
|
499
|
+
* The scroll context with saved position info.
|
|
283
500
|
* @returns {void | Promise<void>}
|
|
284
|
-
* @description
|
|
501
|
+
* @description Use to implement custom scroll behavior.
|
|
285
502
|
*/ /**
|
|
503
|
+
* Callback for `router:afterEnter`, `router:afterLeave`, `router:afterEach` events.
|
|
286
504
|
* @callback RouteChangeCallback
|
|
287
|
-
* @param {RouteLocation} to
|
|
288
|
-
*
|
|
505
|
+
* @param {RouteLocation} to
|
|
506
|
+
* The target route location.
|
|
507
|
+
* @param {RouteLocation | null} from
|
|
508
|
+
* The source route location.
|
|
289
509
|
* @returns {void | Promise<void>}
|
|
290
510
|
* @description Callback for `router:afterEnter`, `router:afterLeave`, `router:afterEach` events.
|
|
291
511
|
*/ /**
|
|
512
|
+
* Router context injected into component setup as `ctx.router`.
|
|
513
|
+
* @typedef {Object} RouterContext
|
|
514
|
+
* @property {NavigateFunction} navigate
|
|
515
|
+
* Programmatic navigation function.
|
|
516
|
+
* @property {Signal<RouteLocation | null>} current
|
|
517
|
+
* Reactive signal for current route.
|
|
518
|
+
* @property {Signal<RouteLocation | null>} previous
|
|
519
|
+
* Reactive signal for previous route.
|
|
520
|
+
* @property {RouteParams} params
|
|
521
|
+
* Current route params (getter).
|
|
522
|
+
* @property {QueryParams} query
|
|
523
|
+
* Current route query (getter).
|
|
524
|
+
* @property {string} path
|
|
525
|
+
* Current route path (getter).
|
|
526
|
+
* @property {string} fullUrl
|
|
527
|
+
* Current routed URL string (getter).
|
|
528
|
+
* @property {RouteMeta} meta
|
|
529
|
+
* Current route meta (getter).
|
|
530
|
+
* @description Injected into component setup as `ctx.router`.
|
|
531
|
+
*/ /**
|
|
532
|
+
* Callback for `router:error` event.
|
|
292
533
|
* @callback RouterErrorCallback
|
|
293
|
-
* @param {Error} error
|
|
294
|
-
*
|
|
295
|
-
* @param {RouteLocation
|
|
534
|
+
* @param {Error} error
|
|
535
|
+
* The error that occurred.
|
|
536
|
+
* @param {RouteLocation} [to]
|
|
537
|
+
* The target route (if available).
|
|
538
|
+
* @param {RouteLocation | null} [from]
|
|
539
|
+
* The source route (if available).
|
|
296
540
|
* @returns {void | Promise<void>}
|
|
297
|
-
* @description Callback for `router:
|
|
541
|
+
* @description Callback for `router:error` event.
|
|
298
542
|
*/ /**
|
|
543
|
+
* Callback for `router:ready` event.
|
|
299
544
|
* @callback RouterReadyCallback
|
|
300
|
-
* @param {Router} router
|
|
545
|
+
* @param {Router} router
|
|
546
|
+
* The router instance.
|
|
301
547
|
* @returns {void | Promise<void>}
|
|
302
548
|
* @description Callback for `router:ready` event.
|
|
303
549
|
*/ /**
|
|
550
|
+
* Callback for `router:routeAdded` event.
|
|
304
551
|
* @callback RouteAddedCallback
|
|
305
|
-
* @param {RouteDefinition} route
|
|
552
|
+
* @param {RouteDefinition} route
|
|
553
|
+
* The added route definition.
|
|
306
554
|
* @returns {void | Promise<void>}
|
|
307
555
|
* @description Callback for `router:routeAdded` event.
|
|
308
556
|
*/ /**
|
|
557
|
+
* Callback for `router:routeRemoved` event.
|
|
309
558
|
* @callback RouteRemovedCallback
|
|
310
|
-
* @param {RouteDefinition} route
|
|
559
|
+
* @param {RouteDefinition} route
|
|
560
|
+
* The removed route definition.
|
|
311
561
|
* @returns {void | Promise<void>}
|
|
312
562
|
* @description Callback for `router:routeRemoved` event.
|
|
313
|
-
*/ //
|
|
314
|
-
//
|
|
315
|
-
//
|
|
563
|
+
*/ // ============================================================================
|
|
564
|
+
// CORE IMPLEMENTATION
|
|
565
|
+
// ============================================================================
|
|
316
566
|
/**
|
|
317
567
|
* Simple error handler for the core router.
|
|
318
|
-
* Can be overridden by error handling plugins.
|
|
319
|
-
* Provides consistent error formatting and logging for router operations.
|
|
320
568
|
* @private
|
|
321
569
|
*/ const CoreErrorHandler = {
|
|
322
570
|
/**
|
|
323
571
|
* Handles router errors with basic formatting.
|
|
324
572
|
* @param {Error} error - The error to handle.
|
|
325
573
|
* @param {string} context - The context where the error occurred.
|
|
326
|
-
* @param {
|
|
574
|
+
* @param {Record<string, unknown>} details - Additional error details.
|
|
327
575
|
* @throws {Error} The formatted error.
|
|
328
576
|
*/ handle (error, context, details = {}) {
|
|
329
577
|
const message = `[ElevaRouter] ${context}: ${error.message}`;
|
|
@@ -342,7 +590,7 @@
|
|
|
342
590
|
/**
|
|
343
591
|
* Logs a warning without throwing an error.
|
|
344
592
|
* @param {string} message - The warning message.
|
|
345
|
-
* @param {
|
|
593
|
+
* @param {Record<string, unknown>} details - Additional warning details.
|
|
346
594
|
*/ warn (message, details = {}) {
|
|
347
595
|
console.warn(`[ElevaRouter] ${message}`, details);
|
|
348
596
|
},
|
|
@@ -350,7 +598,7 @@
|
|
|
350
598
|
* Logs an error without throwing.
|
|
351
599
|
* @param {string} message - The error message.
|
|
352
600
|
* @param {Error} error - The original error.
|
|
353
|
-
* @param {
|
|
601
|
+
* @param {Record<string, unknown>} details - Additional error details.
|
|
354
602
|
*/ log (message, error, details = {}) {
|
|
355
603
|
console.error(`[ElevaRouter] ${message}`, {
|
|
356
604
|
error,
|
|
@@ -359,26 +607,37 @@
|
|
|
359
607
|
}
|
|
360
608
|
};
|
|
361
609
|
/**
|
|
610
|
+
* Represents the current or target location in the router.
|
|
362
611
|
* @typedef {Object} RouteLocation
|
|
363
|
-
* @property {string} path
|
|
364
|
-
*
|
|
365
|
-
* @property {
|
|
366
|
-
*
|
|
367
|
-
* @property {
|
|
368
|
-
*
|
|
369
|
-
* @property {
|
|
612
|
+
* @property {string} path
|
|
613
|
+
* The path of the route (e.g., '/users/123').
|
|
614
|
+
* @property {QueryParams} query
|
|
615
|
+
* Query parameters as key-value pairs.
|
|
616
|
+
* @property {string} fullUrl
|
|
617
|
+
* The routed URL string (path plus query).
|
|
618
|
+
* @property {RouteParams} params
|
|
619
|
+
* Dynamic route parameters.
|
|
620
|
+
* @property {RouteMeta} meta
|
|
621
|
+
* Metadata associated with the matched route.
|
|
622
|
+
* @property {string} [name]
|
|
623
|
+
* The optional name of the matched route.
|
|
624
|
+
* @property {RouteDefinition} matched
|
|
625
|
+
* The raw route definition that was matched.
|
|
370
626
|
* @description Represents the current or target location in the router.
|
|
371
627
|
*/ /**
|
|
372
|
-
*
|
|
373
|
-
* The return value of a navigation guard.
|
|
628
|
+
* Return value of a navigation guard.
|
|
374
629
|
* - `true` or `undefined/void`: Allow navigation
|
|
375
630
|
* - `false`: Abort navigation
|
|
376
631
|
* - `string`: Redirect to path
|
|
377
632
|
* - `NavigationTarget`: Redirect with options
|
|
633
|
+
* @typedef {boolean | string | NavigationTarget | void} NavigationGuardResult
|
|
378
634
|
*/ /**
|
|
635
|
+
* Navigation guard function that controls navigation flow.
|
|
379
636
|
* @callback NavigationGuard
|
|
380
|
-
* @param {RouteLocation} to
|
|
381
|
-
*
|
|
637
|
+
* @param {RouteLocation} to
|
|
638
|
+
* The target route location.
|
|
639
|
+
* @param {RouteLocation | null} from
|
|
640
|
+
* The source route location (null on initial).
|
|
382
641
|
* @returns {NavigationGuardResult | Promise<NavigationGuardResult>}
|
|
383
642
|
* @description A function that controls navigation flow. Runs before navigation is confirmed.
|
|
384
643
|
* @example
|
|
@@ -390,9 +649,12 @@
|
|
|
390
649
|
* // Allow navigation (implicit return undefined)
|
|
391
650
|
* };
|
|
392
651
|
*/ /**
|
|
652
|
+
* Navigation hook for side effects. Does not affect navigation flow.
|
|
393
653
|
* @callback NavigationHook
|
|
394
|
-
* @param {RouteLocation} to
|
|
395
|
-
*
|
|
654
|
+
* @param {RouteLocation} to
|
|
655
|
+
* The target route location.
|
|
656
|
+
* @param {RouteLocation | null} from
|
|
657
|
+
* The source route location.
|
|
396
658
|
* @returns {void | Promise<void>}
|
|
397
659
|
* @description A lifecycle hook for side effects. Does not affect navigation flow.
|
|
398
660
|
* @example
|
|
@@ -401,11 +663,16 @@
|
|
|
401
663
|
* analytics.trackPageView(to.path);
|
|
402
664
|
* };
|
|
403
665
|
*/ /**
|
|
666
|
+
* Interface for router plugins.
|
|
404
667
|
* @typedef {Object} RouterPlugin
|
|
405
|
-
* @property {string} name
|
|
406
|
-
*
|
|
407
|
-
* @property {
|
|
408
|
-
*
|
|
668
|
+
* @property {string} name
|
|
669
|
+
* Unique plugin identifier.
|
|
670
|
+
* @property {string} [version]
|
|
671
|
+
* Plugin version (recommended to match router version).
|
|
672
|
+
* @property {(router: Router, options?: Record<string, unknown>) => void} install
|
|
673
|
+
* Installation function.
|
|
674
|
+
* @property {(router: Router) => void | Promise<void>} [destroy]
|
|
675
|
+
* Cleanup function called on router.destroy().
|
|
409
676
|
* @description Interface for router plugins. Plugins can extend router functionality.
|
|
410
677
|
* @example
|
|
411
678
|
* const AnalyticsPlugin = {
|
|
@@ -418,57 +685,93 @@
|
|
|
418
685
|
* }
|
|
419
686
|
* };
|
|
420
687
|
*/ /**
|
|
688
|
+
* Context object for navigation events that plugins can modify.
|
|
421
689
|
* @typedef {Object} NavigationContext
|
|
422
|
-
* @property {RouteLocation} to
|
|
423
|
-
*
|
|
424
|
-
* @property {
|
|
425
|
-
*
|
|
426
|
-
* @
|
|
690
|
+
* @property {RouteLocation} to
|
|
691
|
+
* The target route location.
|
|
692
|
+
* @property {RouteLocation | null} from
|
|
693
|
+
* The source route location.
|
|
694
|
+
* @property {boolean} cancelled
|
|
695
|
+
* Whether navigation has been cancelled.
|
|
696
|
+
* @property {string | NavigationTarget | null} redirectTo
|
|
697
|
+
* Redirect target if set.
|
|
698
|
+
* @description Passed to navigation events. Plugins can modify to control navigation flow.
|
|
427
699
|
*/ /**
|
|
700
|
+
* Context object for component resolution events.
|
|
428
701
|
* @typedef {Object} ResolveContext
|
|
429
|
-
* @property {RouteLocation} to
|
|
430
|
-
*
|
|
431
|
-
* @property {
|
|
432
|
-
*
|
|
433
|
-
* @property {
|
|
434
|
-
*
|
|
435
|
-
* @property {
|
|
436
|
-
*
|
|
702
|
+
* @property {RouteLocation} to
|
|
703
|
+
* The target route location.
|
|
704
|
+
* @property {RouteLocation | null} from
|
|
705
|
+
* The source route location.
|
|
706
|
+
* @property {RouteDefinition} route
|
|
707
|
+
* The matched route definition.
|
|
708
|
+
* @property {ComponentDefinition | null} layoutComponent
|
|
709
|
+
* The resolved layout component (available in afterResolve).
|
|
710
|
+
* @property {ComponentDefinition | null} pageComponent
|
|
711
|
+
* The resolved page component (available in afterResolve).
|
|
712
|
+
* @property {boolean} cancelled
|
|
713
|
+
* Whether navigation has been cancelled.
|
|
714
|
+
* @property {string | NavigationTarget | null} redirectTo
|
|
715
|
+
* Redirect target if set.
|
|
716
|
+
* @description Passed to component resolution events.
|
|
437
717
|
*/ /**
|
|
718
|
+
* Context object for render events.
|
|
438
719
|
* @typedef {Object} RenderContext
|
|
439
|
-
* @property {RouteLocation} to
|
|
440
|
-
*
|
|
441
|
-
* @property {
|
|
442
|
-
*
|
|
443
|
-
* @
|
|
720
|
+
* @property {RouteLocation} to
|
|
721
|
+
* The target route location.
|
|
722
|
+
* @property {RouteLocation | null} from
|
|
723
|
+
* The source route location.
|
|
724
|
+
* @property {ComponentDefinition | null} layoutComponent
|
|
725
|
+
* The layout component being rendered.
|
|
726
|
+
* @property {ComponentDefinition} pageComponent
|
|
727
|
+
* The page component being rendered.
|
|
728
|
+
* @description Passed to render events.
|
|
444
729
|
*/ /**
|
|
730
|
+
* Context object for scroll events.
|
|
445
731
|
* @typedef {Object} ScrollContext
|
|
446
|
-
* @property {RouteLocation} to
|
|
447
|
-
*
|
|
448
|
-
* @property {
|
|
449
|
-
*
|
|
732
|
+
* @property {RouteLocation} to
|
|
733
|
+
* The target route location.
|
|
734
|
+
* @property {RouteLocation | null} from
|
|
735
|
+
* The source route location.
|
|
736
|
+
* @property {{x: number, y: number} | null} savedPosition
|
|
737
|
+
* Saved position (back/forward nav).
|
|
738
|
+
* @description Passed to scroll events for plugins to handle scroll behavior.
|
|
450
739
|
*/ /**
|
|
451
|
-
* @typedef {string | ComponentDefinition | (() => Promise<{default: ComponentDefinition}>)} RouteComponent
|
|
452
740
|
* A component that can be rendered for a route.
|
|
453
741
|
* - `string`: Name of a registered component
|
|
454
742
|
* - `ComponentDefinition`: Inline component definition
|
|
455
|
-
* - `() =>
|
|
743
|
+
* - `() => ComponentDefinition`: Factory function returning a component
|
|
744
|
+
* - `() => Promise<ComponentDefinition>`: Async factory function
|
|
745
|
+
* - `() => Promise<{default: ComponentDefinition}>`: Lazy-loaded module (e.g., `() => import('./Page.js')`)
|
|
746
|
+
* @typedef {string | ComponentDefinition | (() => ComponentDefinition | Promise<ComponentDefinition | {default: ComponentDefinition}>)} RouteComponent
|
|
456
747
|
*/ /**
|
|
748
|
+
* Defines a route in the application.
|
|
457
749
|
* @typedef {Object} RouteDefinition
|
|
458
|
-
* @property {string} path
|
|
459
|
-
*
|
|
460
|
-
*
|
|
461
|
-
*
|
|
462
|
-
*
|
|
463
|
-
* @property {RouteComponent}
|
|
464
|
-
*
|
|
465
|
-
* @property {
|
|
466
|
-
*
|
|
467
|
-
* @property {
|
|
468
|
-
*
|
|
469
|
-
* @property {
|
|
470
|
-
*
|
|
750
|
+
* @property {string} path
|
|
751
|
+
* URL path pattern. Supports:
|
|
752
|
+
* - Static: '/about'
|
|
753
|
+
* - Dynamic params: '/users/:id'
|
|
754
|
+
* - Wildcard: '*' (catch-all, conventionally last)
|
|
755
|
+
* @property {RouteComponent} component
|
|
756
|
+
* The component to render for this route.
|
|
757
|
+
* @property {RouteComponent} [layout]
|
|
758
|
+
* Optional layout component to wrap the route component.
|
|
759
|
+
* @property {string} [name]
|
|
760
|
+
* Optional route name for programmatic navigation.
|
|
761
|
+
* @property {RouteMeta} [meta]
|
|
762
|
+
* Optional metadata (auth flags, titles, etc.).
|
|
763
|
+
* @property {NavigationGuard} [beforeEnter]
|
|
764
|
+
* Route-specific guard before entering.
|
|
765
|
+
* @property {NavigationHook} [afterEnter]
|
|
766
|
+
* Hook after entering and component is mounted.
|
|
767
|
+
* @property {NavigationGuard} [beforeLeave]
|
|
768
|
+
* Guard before leaving this route.
|
|
769
|
+
* @property {NavigationHook} [afterLeave]
|
|
770
|
+
* Hook after leaving and component is unmounted.
|
|
771
|
+
* @property {RouteSegment[]} [segments]
|
|
772
|
+
* Internal: parsed path segments (added by router).
|
|
471
773
|
* @description Defines a route in the application.
|
|
774
|
+
* @note Nested routes are not supported. Use shared layouts with flat routes instead.
|
|
472
775
|
* @example
|
|
473
776
|
* // Static route
|
|
474
777
|
* { path: '/about', component: AboutPage }
|
|
@@ -484,10 +787,10 @@
|
|
|
484
787
|
* beforeEnter: (to, from) => isLoggedIn() || '/login'
|
|
485
788
|
* }
|
|
486
789
|
*
|
|
487
|
-
* // Catch-all 404 route (
|
|
790
|
+
* // Catch-all 404 route (conventionally last)
|
|
488
791
|
* { path: '*', component: NotFoundPage }
|
|
489
792
|
*/ /**
|
|
490
|
-
* @class Router
|
|
793
|
+
* @class 🛤️ Router
|
|
491
794
|
* @classdesc A powerful, reactive, and flexible Router Plugin for Eleva.
|
|
492
795
|
* This class manages all routing logic, including state, navigation, and rendering.
|
|
493
796
|
*
|
|
@@ -513,15 +816,15 @@
|
|
|
513
816
|
* | `router:scroll` | {@link ScrollContextCallback} | No | For scroll behavior |
|
|
514
817
|
* | `router:afterEnter` | {@link RouteChangeCallback} | No | After entering route |
|
|
515
818
|
* | `router:afterEach` | {@link RouteChangeCallback} | No | Navigation complete |
|
|
516
|
-
* | `router:
|
|
819
|
+
* | `router:error` | {@link RouterErrorCallback} | No | Navigation error |
|
|
517
820
|
* | `router:routeAdded` | {@link RouteAddedCallback} | No | Dynamic route added |
|
|
518
821
|
* | `router:routeRemoved` | {@link RouteRemovedCallback} | No | Dynamic route removed |
|
|
519
822
|
*
|
|
520
823
|
* ## Reactive Signals
|
|
521
824
|
* - `currentRoute: Signal<RouteLocation | null>` - Current route info
|
|
522
825
|
* - `previousRoute: Signal<RouteLocation | null>` - Previous route info
|
|
523
|
-
* - `currentParams: Signal<
|
|
524
|
-
* - `currentQuery: Signal<
|
|
826
|
+
* - `currentParams: Signal<RouteParams>` - Current route params
|
|
827
|
+
* - `currentQuery: Signal<QueryParams>` - Current query params
|
|
525
828
|
* - `currentLayout: Signal<MountResult | null>` - Mounted layout instance
|
|
526
829
|
* - `currentView: Signal<MountResult | null>` - Mounted view instance
|
|
527
830
|
* - `isReady: Signal<boolean>` - Router readiness state
|
|
@@ -596,7 +899,7 @@
|
|
|
596
899
|
* Parses a route path string into an array of static and parameter segments.
|
|
597
900
|
* @private
|
|
598
901
|
* @param {string} path - The path pattern to parse.
|
|
599
|
-
* @returns {
|
|
902
|
+
* @returns {{type: 'static' | 'param', value?: string, name?: string}[]} An array of segment objects.
|
|
600
903
|
* @throws {Error} If the route path is not a valid string.
|
|
601
904
|
*/ _parsePathIntoSegments(path) {
|
|
602
905
|
if (!path || typeof path !== "string") {
|
|
@@ -640,6 +943,11 @@
|
|
|
640
943
|
/**
|
|
641
944
|
* Starts the router, initializes event listeners, and performs the initial navigation.
|
|
642
945
|
* @returns {Promise<Router>} The router instance for method chaining.
|
|
946
|
+
* @listens window:hashchange In hash mode, triggers route changes.
|
|
947
|
+
* @listens window:popstate In history/query mode, triggers route changes.
|
|
948
|
+
* @emits router:ready When initialization completes successfully.
|
|
949
|
+
* @see destroy - Stop the router and clean up listeners.
|
|
950
|
+
* @see navigate - Programmatically navigate to a route.
|
|
643
951
|
*
|
|
644
952
|
* @example
|
|
645
953
|
* // Basic usage
|
|
@@ -684,8 +992,11 @@
|
|
|
684
992
|
return this;
|
|
685
993
|
}
|
|
686
994
|
/**
|
|
687
|
-
* Stops the router and cleans up
|
|
995
|
+
* Stops the router and cleans up event listeners.
|
|
996
|
+
* Unmounts the current layout instance if present.
|
|
997
|
+
* @async
|
|
688
998
|
* @returns {Promise<void>}
|
|
999
|
+
* @see start - Restart the router after destroying.
|
|
689
1000
|
*/ async destroy() {
|
|
690
1001
|
if (!this.isStarted) return;
|
|
691
1002
|
// Clean up plugins
|
|
@@ -709,6 +1020,7 @@
|
|
|
709
1020
|
/**
|
|
710
1021
|
* Alias for destroy(). Stops the router and cleans up all resources.
|
|
711
1022
|
* Provided for semantic consistency (start/stop pattern).
|
|
1023
|
+
* @async
|
|
712
1024
|
* @returns {Promise<void>}
|
|
713
1025
|
*
|
|
714
1026
|
* @example
|
|
@@ -720,9 +1032,13 @@
|
|
|
720
1032
|
}
|
|
721
1033
|
/**
|
|
722
1034
|
* Programmatically navigates to a new route.
|
|
1035
|
+
* @async
|
|
723
1036
|
* @param {string | NavigationTarget} location - The target location as a path string or navigation target object.
|
|
724
|
-
* @param {
|
|
1037
|
+
* @param {NavigationParams} [params] - Route parameters (only used when location is a string).
|
|
725
1038
|
* @returns {Promise<boolean>} True if navigation succeeded, false if blocked by guards or failed.
|
|
1039
|
+
* @emits router:error When navigation fails due to an exception.
|
|
1040
|
+
* @see start - Initialize the router before navigating.
|
|
1041
|
+
* @see currentRoute - Access the current route after navigation.
|
|
726
1042
|
*
|
|
727
1043
|
* @example
|
|
728
1044
|
* // Basic navigation
|
|
@@ -785,7 +1101,7 @@
|
|
|
785
1101
|
return navigationSuccessful;
|
|
786
1102
|
} catch (error) {
|
|
787
1103
|
this.errorHandler.log("Navigation failed", error);
|
|
788
|
-
await this.emitter.emit("router:
|
|
1104
|
+
await this.emitter.emit("router:error", error);
|
|
789
1105
|
return false;
|
|
790
1106
|
}
|
|
791
1107
|
}
|
|
@@ -805,7 +1121,7 @@
|
|
|
805
1121
|
* @param {string} path - The target path with query string.
|
|
806
1122
|
* @param {object} params - The target params.
|
|
807
1123
|
* @param {object} query - The target query.
|
|
808
|
-
* @returns {boolean}
|
|
1124
|
+
* @returns {boolean} True if the routes are the same.
|
|
809
1125
|
*/ _isSameRoute(path, params, query) {
|
|
810
1126
|
const current = this.currentRoute.value;
|
|
811
1127
|
if (!current) return false;
|
|
@@ -815,7 +1131,16 @@
|
|
|
815
1131
|
}
|
|
816
1132
|
/**
|
|
817
1133
|
* Injects dynamic parameters into a path string.
|
|
1134
|
+
* Replaces `:param` placeholders with URL-encoded values from the params object.
|
|
1135
|
+
*
|
|
818
1136
|
* @private
|
|
1137
|
+
* @param {string} path - The path pattern containing `:param` placeholders.
|
|
1138
|
+
* @param {RouteParams} params - Key-value pairs to inject into the path.
|
|
1139
|
+
* @returns {string} The path with all parameters replaced.
|
|
1140
|
+
*
|
|
1141
|
+
* @example
|
|
1142
|
+
* this._buildPath('/users/:id/posts/:postId', { id: '123', postId: '456' });
|
|
1143
|
+
* // Returns: '/users/123/posts/456'
|
|
819
1144
|
*/ _buildPath(path, params) {
|
|
820
1145
|
let result = path;
|
|
821
1146
|
for (const [key, value] of Object.entries(params)){
|
|
@@ -827,8 +1152,12 @@
|
|
|
827
1152
|
}
|
|
828
1153
|
/**
|
|
829
1154
|
* The handler for browser-initiated route changes (e.g., back/forward buttons).
|
|
1155
|
+
*
|
|
830
1156
|
* @private
|
|
1157
|
+
* @async
|
|
831
1158
|
* @param {boolean} [isPopState=true] - Whether this is a popstate event (back/forward navigation).
|
|
1159
|
+
* @returns {Promise<void>}
|
|
1160
|
+
* @emits router:error When route change handling fails.
|
|
832
1161
|
*/ async _handleRouteChange(isPopState = true) {
|
|
833
1162
|
if (this._isNavigating) return;
|
|
834
1163
|
try {
|
|
@@ -847,26 +1176,30 @@
|
|
|
847
1176
|
this.errorHandler.log("Route change handling failed", error, {
|
|
848
1177
|
currentUrl: typeof window !== "undefined" ? window.location.href : ""
|
|
849
1178
|
});
|
|
850
|
-
await this.emitter.emit("router:
|
|
1179
|
+
await this.emitter.emit("router:error", error);
|
|
851
1180
|
}
|
|
852
1181
|
}
|
|
853
1182
|
/**
|
|
854
1183
|
* Manages the core navigation lifecycle. Runs guards before committing changes.
|
|
855
|
-
* Emits lifecycle events that plugins can hook into:
|
|
856
|
-
* - router:beforeEach - Before guards run (can block/redirect via context)
|
|
857
|
-
* - router:beforeResolve - Before component resolution (can block/redirect)
|
|
858
|
-
* - router:afterResolve - After components are resolved
|
|
859
|
-
* - router:beforeRender - Before DOM rendering
|
|
860
|
-
* - router:afterRender - After DOM rendering
|
|
861
|
-
* - router:scroll - After render, for scroll behavior
|
|
862
|
-
* - router:afterEnter - After entering a route
|
|
863
|
-
* - router:afterLeave - After leaving a route
|
|
864
|
-
* - router:afterEach - After navigation completes
|
|
865
1184
|
*
|
|
866
1185
|
* @private
|
|
1186
|
+
* @async
|
|
867
1187
|
* @param {string} fullPath - The full path (e.g., '/users/123?foo=bar') to navigate to.
|
|
868
1188
|
* @param {boolean} [isPopState=false] - Whether this navigation was triggered by popstate (back/forward).
|
|
869
|
-
* @returns {Promise<boolean>}
|
|
1189
|
+
* @returns {Promise<boolean>} `true` if navigation succeeded, `false` if aborted.
|
|
1190
|
+
* @emits router:notFound When no matching route is found.
|
|
1191
|
+
* @emits router:beforeResolve Before component resolution (can block/redirect).
|
|
1192
|
+
* @emits router:afterResolve After components are resolved.
|
|
1193
|
+
* @emits router:afterLeave After leaving the previous route.
|
|
1194
|
+
* @emits router:beforeRender Before DOM rendering.
|
|
1195
|
+
* @emits router:afterRender After DOM rendering completes.
|
|
1196
|
+
* @emits router:scroll After render, for scroll behavior handling.
|
|
1197
|
+
* @emits router:afterEnter After entering the new route.
|
|
1198
|
+
* @emits router:afterEach After navigation completes successfully.
|
|
1199
|
+
* @emits router:error When an error occurs during navigation.
|
|
1200
|
+
* @see _runGuards - Guard execution.
|
|
1201
|
+
* @see _resolveComponents - Component resolution.
|
|
1202
|
+
* @see _render - DOM rendering.
|
|
870
1203
|
*/ async _proceedWithNavigation(fullPath, isPopState = false) {
|
|
871
1204
|
const from = this.currentRoute.value;
|
|
872
1205
|
const [path, queryString] = (fullPath || "/").split("?");
|
|
@@ -886,7 +1219,7 @@
|
|
|
886
1219
|
}
|
|
887
1220
|
};
|
|
888
1221
|
} else {
|
|
889
|
-
await this.emitter.emit("router:
|
|
1222
|
+
await this.emitter.emit("router:error", new Error(`Route not found: ${toLocation.path}`), toLocation, from);
|
|
890
1223
|
return false;
|
|
891
1224
|
}
|
|
892
1225
|
}
|
|
@@ -973,7 +1306,7 @@
|
|
|
973
1306
|
};
|
|
974
1307
|
await this.emitter.emit("router:beforeRender", renderContext);
|
|
975
1308
|
// 9. Render the new components.
|
|
976
|
-
await this._render(layoutComponent, pageComponent
|
|
1309
|
+
await this._render(layoutComponent, pageComponent);
|
|
977
1310
|
// 10. Emit afterRender event - plugins can trigger animations
|
|
978
1311
|
await this.emitter.emit("router:afterRender", renderContext);
|
|
979
1312
|
// 11. Emit scroll event - plugins can handle scroll restoration
|
|
@@ -995,7 +1328,7 @@
|
|
|
995
1328
|
to,
|
|
996
1329
|
from
|
|
997
1330
|
});
|
|
998
|
-
await this.emitter.emit("router:
|
|
1331
|
+
await this.emitter.emit("router:error", error, to, from);
|
|
999
1332
|
return false;
|
|
1000
1333
|
}
|
|
1001
1334
|
}
|
|
@@ -1011,7 +1344,8 @@
|
|
|
1011
1344
|
* @param {RouteLocation} to - The target route location.
|
|
1012
1345
|
* @param {RouteLocation | null} from - The current route location (null on initial navigation).
|
|
1013
1346
|
* @param {RouteDefinition} route - The matched route definition.
|
|
1014
|
-
* @returns {Promise<boolean>}
|
|
1347
|
+
* @returns {Promise<boolean>} `false` if navigation should be aborted.
|
|
1348
|
+
* @emits router:beforeEach Before guards run (can block/redirect via context).
|
|
1015
1349
|
*/ async _runGuards(to, from, route) {
|
|
1016
1350
|
// Create navigation context that plugins can modify to block navigation
|
|
1017
1351
|
/** @type {NavigationContext} */ const navContext = {
|
|
@@ -1072,7 +1406,8 @@
|
|
|
1072
1406
|
/**
|
|
1073
1407
|
* Resolves a function component definition to a component object.
|
|
1074
1408
|
* @private
|
|
1075
|
-
* @
|
|
1409
|
+
* @async
|
|
1410
|
+
* @param {() => ComponentDefinition | Promise<ComponentDefinition | { default: ComponentDefinition }>} def - The function to resolve.
|
|
1076
1411
|
* @returns {Promise<ComponentDefinition>} The resolved component.
|
|
1077
1412
|
* @throws {Error} If the function fails to load the component.
|
|
1078
1413
|
*/ async _resolveFunctionComponent(def) {
|
|
@@ -1091,7 +1426,7 @@
|
|
|
1091
1426
|
/**
|
|
1092
1427
|
* Validates a component definition object.
|
|
1093
1428
|
* @private
|
|
1094
|
-
* @param {
|
|
1429
|
+
* @param {unknown} def - The component definition to validate.
|
|
1095
1430
|
* @returns {ComponentDefinition} The validated component.
|
|
1096
1431
|
* @throws {Error} If the component definition is invalid.
|
|
1097
1432
|
*/ _validateComponentDefinition(def) {
|
|
@@ -1110,7 +1445,7 @@
|
|
|
1110
1445
|
/**
|
|
1111
1446
|
* Resolves a component definition to a component object.
|
|
1112
1447
|
* @private
|
|
1113
|
-
* @param {
|
|
1448
|
+
* @param {unknown} def - The component definition to resolve.
|
|
1114
1449
|
* @returns {Promise<ComponentDefinition | null>} The resolved component or null.
|
|
1115
1450
|
*/ async _resolveComponent(def) {
|
|
1116
1451
|
if (def === null || def === undefined) {
|
|
@@ -1132,8 +1467,10 @@
|
|
|
1132
1467
|
/**
|
|
1133
1468
|
* Asynchronously resolves the layout and page components for a route.
|
|
1134
1469
|
* @private
|
|
1470
|
+
* @async
|
|
1135
1471
|
* @param {RouteDefinition} route - The route to resolve components for.
|
|
1136
1472
|
* @returns {Promise<{layoutComponent: ComponentDefinition | null, pageComponent: ComponentDefinition}>}
|
|
1473
|
+
* @throws {Error} If page component cannot be resolved.
|
|
1137
1474
|
*/ async _resolveComponents(route) {
|
|
1138
1475
|
const effectiveLayout = route.layout || this.options.globalLayout;
|
|
1139
1476
|
try {
|
|
@@ -1159,9 +1496,24 @@
|
|
|
1159
1496
|
}
|
|
1160
1497
|
/**
|
|
1161
1498
|
* Renders the components for the current route into the DOM.
|
|
1499
|
+
*
|
|
1500
|
+
* Rendering algorithm:
|
|
1501
|
+
* 1. Find the mount element using options.mount selector
|
|
1502
|
+
* 2. If layoutComponent exists:
|
|
1503
|
+
* a. Mount layout to mount element
|
|
1504
|
+
* b. Find view element within layout (using viewSelector)
|
|
1505
|
+
* c. Mount page component to view element
|
|
1506
|
+
* 3. If no layoutComponent:
|
|
1507
|
+
* a. Mount page component directly to mount element
|
|
1508
|
+
* b. Set currentLayout to null
|
|
1509
|
+
*
|
|
1162
1510
|
* @private
|
|
1511
|
+
* @async
|
|
1163
1512
|
* @param {ComponentDefinition | null} layoutComponent - The pre-loaded layout component.
|
|
1164
1513
|
* @param {ComponentDefinition} pageComponent - The pre-loaded page component.
|
|
1514
|
+
* @returns {Promise<void>}
|
|
1515
|
+
* @throws {Error} If mount element is not found in the DOM.
|
|
1516
|
+
* @throws {Error} If component mounting fails (propagated from eleva.mount).
|
|
1165
1517
|
*/ async _render(layoutComponent, pageComponent) {
|
|
1166
1518
|
const mountEl = document.querySelector(this.options.mount);
|
|
1167
1519
|
if (!mountEl) {
|
|
@@ -1185,8 +1537,8 @@
|
|
|
1185
1537
|
* Creates a getter function for router context properties.
|
|
1186
1538
|
* @private
|
|
1187
1539
|
* @param {string} property - The property name to access.
|
|
1188
|
-
* @param {
|
|
1189
|
-
* @returns {
|
|
1540
|
+
* @param {unknown} defaultValue - The default value if property is undefined.
|
|
1541
|
+
* @returns {() => unknown} A getter function.
|
|
1190
1542
|
*/ _createRouteGetter(property, defaultValue) {
|
|
1191
1543
|
return ()=>this.currentRoute.value?.[property] ?? defaultValue;
|
|
1192
1544
|
}
|
|
@@ -1201,7 +1553,7 @@
|
|
|
1201
1553
|
return {
|
|
1202
1554
|
...component,
|
|
1203
1555
|
async setup (ctx) {
|
|
1204
|
-
ctx.router = {
|
|
1556
|
+
/** @type {RouterContext} */ ctx.router = {
|
|
1205
1557
|
navigate: self.navigate.bind(self),
|
|
1206
1558
|
current: self.currentRoute,
|
|
1207
1559
|
previous: self.previousRoute,
|
|
@@ -1228,9 +1580,13 @@
|
|
|
1228
1580
|
}
|
|
1229
1581
|
/**
|
|
1230
1582
|
* Recursively wraps all child components to ensure they have access to router context.
|
|
1583
|
+
* String component references are returned as-is (context injected during mount).
|
|
1584
|
+
* Objects are wrapped with router context and their children are recursively wrapped.
|
|
1585
|
+
*
|
|
1231
1586
|
* @private
|
|
1232
1587
|
* @param {ComponentDefinition | string} component - The component to wrap (can be a definition object or a registered component name).
|
|
1233
1588
|
* @returns {ComponentDefinition | string} The wrapped component definition or the original string reference.
|
|
1589
|
+
* @see _wrapComponent - Single component wrapping.
|
|
1234
1590
|
*/ _wrapComponentWithChildren(component) {
|
|
1235
1591
|
// If the component is a string (registered component name), return as-is
|
|
1236
1592
|
// The router context will be injected when the component is resolved during mounting
|
|
@@ -1287,7 +1643,15 @@
|
|
|
1287
1643
|
}
|
|
1288
1644
|
/**
|
|
1289
1645
|
* Parses a query string into a key-value object.
|
|
1646
|
+
* Uses URLSearchParams for robust parsing of encoded values.
|
|
1647
|
+
*
|
|
1290
1648
|
* @private
|
|
1649
|
+
* @param {string} queryString - The query string to parse (without leading '?').
|
|
1650
|
+
* @returns {QueryParams} Key-value pairs from the query string.
|
|
1651
|
+
*
|
|
1652
|
+
* @example
|
|
1653
|
+
* this._parseQuery('foo=bar&baz=qux');
|
|
1654
|
+
* // Returns: { foo: 'bar', baz: 'qux' }
|
|
1291
1655
|
*/ _parseQuery(queryString) {
|
|
1292
1656
|
const query = {};
|
|
1293
1657
|
if (queryString) {
|
|
@@ -1339,10 +1703,12 @@
|
|
|
1339
1703
|
/**
|
|
1340
1704
|
* Adds a new route dynamically at runtime.
|
|
1341
1705
|
* The route will be processed and available for navigation immediately.
|
|
1706
|
+
* Routes are inserted before the wildcard (*) route if one exists.
|
|
1342
1707
|
*
|
|
1343
1708
|
* @param {RouteDefinition} route - The route definition to add.
|
|
1344
1709
|
* @param {RouteDefinition} [parentRoute] - Optional parent route to add as a child (not yet implemented).
|
|
1345
|
-
* @returns {() => void} A function to remove the added route.
|
|
1710
|
+
* @returns {() => void} A function to remove the added route (returns no-op if route was invalid).
|
|
1711
|
+
* @emits router:routeAdded When a route is successfully added.
|
|
1346
1712
|
*
|
|
1347
1713
|
* @example
|
|
1348
1714
|
* // Add a route dynamically
|
|
@@ -1390,6 +1756,7 @@
|
|
|
1390
1756
|
*
|
|
1391
1757
|
* @param {string} path - The path of the route to remove.
|
|
1392
1758
|
* @returns {boolean} True if the route was removed, false if not found.
|
|
1759
|
+
* @emits router:routeRemoved When a route is successfully removed.
|
|
1393
1760
|
*
|
|
1394
1761
|
* @example
|
|
1395
1762
|
* router.removeRoute('/dynamic');
|
|
@@ -1477,6 +1844,7 @@
|
|
|
1477
1844
|
* Registers a global hook that runs after a new route component has been mounted.
|
|
1478
1845
|
* @param {NavigationHook} hook - The hook function to register.
|
|
1479
1846
|
* @returns {() => void} A function to unregister the hook.
|
|
1847
|
+
* @listens router:afterEnter
|
|
1480
1848
|
*/ onAfterEnter(hook) {
|
|
1481
1849
|
return this.emitter.on("router:afterEnter", hook);
|
|
1482
1850
|
}
|
|
@@ -1484,6 +1852,7 @@
|
|
|
1484
1852
|
* Registers a global hook that runs after a route component has been unmounted.
|
|
1485
1853
|
* @param {NavigationHook} hook - The hook function to register.
|
|
1486
1854
|
* @returns {() => void} A function to unregister the hook.
|
|
1855
|
+
* @listens router:afterLeave
|
|
1487
1856
|
*/ onAfterLeave(hook) {
|
|
1488
1857
|
return this.emitter.on("router:afterLeave", hook);
|
|
1489
1858
|
}
|
|
@@ -1491,6 +1860,7 @@
|
|
|
1491
1860
|
* Registers a global hook that runs after a navigation has been confirmed and all hooks have completed.
|
|
1492
1861
|
* @param {NavigationHook} hook - The hook function to register.
|
|
1493
1862
|
* @returns {() => void} A function to unregister the hook.
|
|
1863
|
+
* @listens router:afterEach
|
|
1494
1864
|
*/ onAfterEach(hook) {
|
|
1495
1865
|
return this.emitter.on("router:afterEach", hook);
|
|
1496
1866
|
}
|
|
@@ -1498,12 +1868,18 @@
|
|
|
1498
1868
|
* Registers a global error handler for navigation errors.
|
|
1499
1869
|
* @param {(error: Error, to?: RouteLocation, from?: RouteLocation) => void} handler - The error handler function.
|
|
1500
1870
|
* @returns {() => void} A function to unregister the handler.
|
|
1871
|
+
* @listens router:error
|
|
1501
1872
|
*/ onError(handler) {
|
|
1502
|
-
return this.emitter.on("router:
|
|
1873
|
+
return this.emitter.on("router:error", handler);
|
|
1503
1874
|
}
|
|
1504
1875
|
/**
|
|
1505
1876
|
* Registers a plugin with the router.
|
|
1506
|
-
*
|
|
1877
|
+
* Logs a warning if the plugin is already registered.
|
|
1878
|
+
*
|
|
1879
|
+
* @param {RouterPlugin} plugin - The plugin to register (must have install method).
|
|
1880
|
+
* @param {Record<string, unknown>} [options={}] - Options to pass to plugin.install().
|
|
1881
|
+
* @returns {void}
|
|
1882
|
+
* @throws {Error} If plugin does not have an install method.
|
|
1507
1883
|
*/ use(plugin, options = {}) {
|
|
1508
1884
|
if (typeof plugin.install !== "function") {
|
|
1509
1885
|
this.errorHandler.handle(new Error("Plugin must have an install method"), "Plugin registration failed", {
|
|
@@ -1552,7 +1928,9 @@
|
|
|
1552
1928
|
}
|
|
1553
1929
|
/**
|
|
1554
1930
|
* Sets a custom error handler. Used by error handling plugins.
|
|
1555
|
-
*
|
|
1931
|
+
* Logs a warning if the provided handler is invalid (missing required methods).
|
|
1932
|
+
* @param {RouterErrorHandler} errorHandler - The error handler object with handle, warn, and log methods.
|
|
1933
|
+
* @returns {void}
|
|
1556
1934
|
*/ setErrorHandler(errorHandler) {
|
|
1557
1935
|
if (errorHandler && typeof errorHandler.handle === "function" && typeof errorHandler.warn === "function" && typeof errorHandler.log === "function") {
|
|
1558
1936
|
this.errorHandler = errorHandler;
|
|
@@ -1564,6 +1942,7 @@
|
|
|
1564
1942
|
* Creates an instance of the Router.
|
|
1565
1943
|
* @param {Eleva} eleva - The Eleva framework instance.
|
|
1566
1944
|
* @param {RouterOptions} options - The configuration options for the router.
|
|
1945
|
+
* @throws {Error} If the routing mode is invalid.
|
|
1567
1946
|
*/ constructor(eleva, options = {}){
|
|
1568
1947
|
/** @type {Eleva} The Eleva framework instance. */ this.eleva = eleva;
|
|
1569
1948
|
/** @type {RouterOptions} The merged router options. */ this.options = {
|
|
@@ -1573,40 +1952,30 @@
|
|
|
1573
1952
|
...options
|
|
1574
1953
|
};
|
|
1575
1954
|
/** @private @type {RouteDefinition[]} The processed list of route definitions. */ this.routes = this._processRoutes(options.routes || []);
|
|
1576
|
-
/** @private @type {
|
|
1955
|
+
/** @private @type {Emitter} The shared Eleva event emitter for global hooks. */ this.emitter = this.eleva.emitter;
|
|
1577
1956
|
/** @private @type {boolean} A flag indicating if the router has been started. */ this.isStarted = false;
|
|
1578
1957
|
/** @private @type {boolean} A flag to prevent navigation loops from history events. */ this._isNavigating = false;
|
|
1579
1958
|
/** @private @type {number} Counter for tracking navigation operations to prevent race conditions. */ this._navigationId = 0;
|
|
1580
|
-
/** @private @type {
|
|
1959
|
+
/** @private @type {UnsubscribeFunction[]} A collection of cleanup functions for event listeners. */ this.eventListeners = [];
|
|
1581
1960
|
/** @type {Signal<RouteLocation | null>} A reactive signal holding the current route's information. */ this.currentRoute = new this.eleva.signal(null);
|
|
1582
1961
|
/** @type {Signal<RouteLocation | null>} A reactive signal holding the previous route's information. */ this.previousRoute = new this.eleva.signal(null);
|
|
1583
|
-
/** @type {Signal<
|
|
1584
|
-
/** @type {Signal<
|
|
1585
|
-
/** @type {Signal<
|
|
1586
|
-
/** @type {Signal<
|
|
1962
|
+
/** @type {Signal<RouteParams>} A reactive signal holding the current route's parameters. */ this.currentParams = new this.eleva.signal({});
|
|
1963
|
+
/** @type {Signal<QueryParams>} A reactive signal holding the current route's query parameters. */ this.currentQuery = new this.eleva.signal({});
|
|
1964
|
+
/** @type {Signal<MountResult | null>} A reactive signal for the currently mounted layout instance. */ this.currentLayout = new this.eleva.signal(null);
|
|
1965
|
+
/** @type {Signal<MountResult | null>} A reactive signal for the currently mounted view (page) instance. */ this.currentView = new this.eleva.signal(null);
|
|
1587
1966
|
/** @type {Signal<boolean>} A reactive signal indicating if the router is ready (started and initial navigation complete). */ this.isReady = new this.eleva.signal(false);
|
|
1588
1967
|
/** @private @type {Map<string, RouterPlugin>} Map of registered plugins by name. */ this.plugins = new Map();
|
|
1589
|
-
/** @private @type {
|
|
1968
|
+
/** @private @type {NavigationGuard[]} Array of global before-each navigation guards. */ this._beforeEachGuards = [];
|
|
1590
1969
|
// If onBeforeEach was provided in options, add it to the guards array
|
|
1591
1970
|
if (options.onBeforeEach) {
|
|
1592
1971
|
this._beforeEachGuards.push(options.onBeforeEach);
|
|
1593
1972
|
}
|
|
1594
|
-
/** @type {
|
|
1973
|
+
/** @type {RouterErrorHandler} The error handler instance. Can be overridden by plugins. */ this.errorHandler = CoreErrorHandler;
|
|
1595
1974
|
/** @private @type {Map<string, {x: number, y: number}>} Saved scroll positions by route path. */ this._scrollPositions = new Map();
|
|
1596
1975
|
this._validateOptions();
|
|
1597
1976
|
}
|
|
1598
1977
|
}
|
|
1599
1978
|
/**
|
|
1600
|
-
* @typedef {Object} RouterOptions
|
|
1601
|
-
* @property {string} mount - A CSS selector for the main element where the app is mounted.
|
|
1602
|
-
* @property {RouteDefinition[]} routes - An array of route definitions.
|
|
1603
|
-
* @property {'hash' | 'query' | 'history'} [mode='hash'] - The routing mode.
|
|
1604
|
-
* @property {string} [queryParam='page'] - The query parameter to use in 'query' mode.
|
|
1605
|
-
* @property {string} [viewSelector='view'] - The selector for the view element within a layout.
|
|
1606
|
-
* @property {boolean} [autoStart=true] - Whether to start the router automatically.
|
|
1607
|
-
* @property {NavigationGuard} [onBeforeEach] - A global guard executed before every navigation.
|
|
1608
|
-
* @property {string | ComponentDefinition | (() => Promise<{default: ComponentDefinition}>)} [globalLayout] - A global layout for all routes. Can be overridden by a route's specific layout.
|
|
1609
|
-
*/ /**
|
|
1610
1979
|
* @class 🚀 RouterPlugin
|
|
1611
1980
|
* @classdesc A powerful, reactive, and flexible Router Plugin for Eleva applications.
|
|
1612
1981
|
* This plugin provides comprehensive client-side routing functionality including:
|
|
@@ -1645,7 +2014,7 @@
|
|
|
1645
2014
|
/**
|
|
1646
2015
|
* Plugin version
|
|
1647
2016
|
* @type {string}
|
|
1648
|
-
*/ version: "1.0
|
|
2017
|
+
*/ version: "1.1.0",
|
|
1649
2018
|
/**
|
|
1650
2019
|
* Plugin description
|
|
1651
2020
|
* @type {string}
|
|
@@ -1653,16 +2022,25 @@
|
|
|
1653
2022
|
/**
|
|
1654
2023
|
* Installs the RouterPlugin into an Eleva instance.
|
|
1655
2024
|
*
|
|
1656
|
-
* @
|
|
1657
|
-
* @param {
|
|
1658
|
-
* @param {
|
|
1659
|
-
* @param {
|
|
1660
|
-
* @param {
|
|
1661
|
-
* @param {
|
|
1662
|
-
* @param {string} [options.
|
|
1663
|
-
* @param {
|
|
1664
|
-
* @param {
|
|
1665
|
-
* @param {
|
|
2025
|
+
* @public
|
|
2026
|
+
* @param {Eleva} eleva - The Eleva instance.
|
|
2027
|
+
* @param {RouterOptions} options - Router configuration options.
|
|
2028
|
+
* @param {string} options.mount - A CSS selector for the main element where the app is mounted.
|
|
2029
|
+
* @param {RouteDefinition[]} options.routes - An array of route definitions.
|
|
2030
|
+
* @param {'hash' | 'query' | 'history'} [options.mode='hash'] - The routing mode.
|
|
2031
|
+
* @param {string} [options.queryParam='view'] - The query parameter to use in 'query' mode.
|
|
2032
|
+
* @param {string} [options.viewSelector='view'] - Base selector for the view element (matched as #id, .class, [data-*], or raw selector).
|
|
2033
|
+
* @param {boolean} [options.autoStart=true] - Whether to start the router automatically.
|
|
2034
|
+
* @param {NavigationGuard} [options.onBeforeEach] - A global guard executed before every navigation.
|
|
2035
|
+
* @param {RouteComponent} [options.globalLayout] - A global layout for all routes.
|
|
2036
|
+
* @returns {Router} The created router instance.
|
|
2037
|
+
* @throws {Error} If 'mount' option is not provided.
|
|
2038
|
+
* @throws {Error} If 'routes' option is not an array.
|
|
2039
|
+
* @throws {Error} If component registration fails during route processing.
|
|
2040
|
+
* @description
|
|
2041
|
+
* Registers route/layout components, sets `eleva.router`, and adds helpers
|
|
2042
|
+
* (`eleva.navigate`, `eleva.getCurrentRoute`, `eleva.getRouteParams`, `eleva.getRouteQuery`).
|
|
2043
|
+
* When `autoStart` is enabled, startup is scheduled via microtask.
|
|
1666
2044
|
*
|
|
1667
2045
|
* @example
|
|
1668
2046
|
* // main.js
|
|
@@ -1692,9 +2070,10 @@
|
|
|
1692
2070
|
* Registers a component definition with the Eleva instance.
|
|
1693
2071
|
* This method handles both inline component objects and pre-registered component names.
|
|
1694
2072
|
*
|
|
1695
|
-
* @
|
|
1696
|
-
* @param {
|
|
1697
|
-
* @
|
|
2073
|
+
* @inner
|
|
2074
|
+
* @param {unknown} def - The component definition to register.
|
|
2075
|
+
* @param {string} type - The type of component for naming (e.g., "Route", "Layout").
|
|
2076
|
+
* @returns {string | null} The registered component name or null if no definition provided.
|
|
1698
2077
|
*/ const register = (def, type)=>{
|
|
1699
2078
|
if (!def) return null;
|
|
1700
2079
|
if (typeof def === "object" && def !== null && !def.name) {
|
|
@@ -1718,7 +2097,7 @@
|
|
|
1718
2097
|
}
|
|
1719
2098
|
});
|
|
1720
2099
|
const router = new Router(eleva, options);
|
|
1721
|
-
eleva.router = router;
|
|
2100
|
+
/** @type {Router} */ eleva.router = router;
|
|
1722
2101
|
if (options.autoStart !== false) {
|
|
1723
2102
|
queueMicrotask(()=>router.start());
|
|
1724
2103
|
}
|
|
@@ -1733,16 +2112,22 @@
|
|
|
1733
2112
|
options
|
|
1734
2113
|
});
|
|
1735
2114
|
// Add utility methods for manual router access
|
|
1736
|
-
eleva.navigate = router.navigate.bind(router);
|
|
1737
|
-
eleva.getCurrentRoute = ()=>router.currentRoute.value;
|
|
1738
|
-
eleva.getRouteParams = ()=>router.currentParams.value;
|
|
1739
|
-
eleva.getRouteQuery = ()=>router.currentQuery.value;
|
|
2115
|
+
/** @type {NavigateFunction} */ eleva.navigate = router.navigate.bind(router);
|
|
2116
|
+
/** @type {() => RouteLocation | null} */ eleva.getCurrentRoute = ()=>router.currentRoute.value;
|
|
2117
|
+
/** @type {() => RouteParams} */ eleva.getRouteParams = ()=>router.currentParams.value;
|
|
2118
|
+
/** @type {() => QueryParams} */ eleva.getRouteQuery = ()=>router.currentQuery.value;
|
|
1740
2119
|
return router;
|
|
1741
2120
|
},
|
|
1742
2121
|
/**
|
|
1743
|
-
* Uninstalls the plugin from the Eleva instance
|
|
2122
|
+
* Uninstalls the plugin from the Eleva instance.
|
|
1744
2123
|
*
|
|
1745
|
-
* @
|
|
2124
|
+
* @public
|
|
2125
|
+
* @async
|
|
2126
|
+
* @param {Eleva} eleva - The Eleva instance.
|
|
2127
|
+
* @returns {Promise<void>}
|
|
2128
|
+
* @description
|
|
2129
|
+
* Destroys the router instance, removes `eleva.router`, and deletes helper methods
|
|
2130
|
+
* (`eleva.navigate`, `eleva.getCurrentRoute`, `eleva.getRouteParams`, `eleva.getRouteQuery`).
|
|
1746
2131
|
*/ async uninstall (eleva) {
|
|
1747
2132
|
if (eleva.router) {
|
|
1748
2133
|
await eleva.router.destroy();
|
|
@@ -1761,6 +2146,194 @@
|
|
|
1761
2146
|
};
|
|
1762
2147
|
|
|
1763
2148
|
/**
|
|
2149
|
+
* @module eleva/plugins/store
|
|
2150
|
+
* @fileoverview Reactive state management plugin with namespaced modules,
|
|
2151
|
+
* persistence, and subscription system.
|
|
2152
|
+
*/ // ============================================================================
|
|
2153
|
+
// TYPE DEFINITIONS
|
|
2154
|
+
// ============================================================================
|
|
2155
|
+
// -----------------------------------------------------------------------------
|
|
2156
|
+
// External Type Imports
|
|
2157
|
+
// -----------------------------------------------------------------------------
|
|
2158
|
+
/**
|
|
2159
|
+
* Type imports from the Eleva core library.
|
|
2160
|
+
* @typedef {import('eleva').Eleva} Eleva
|
|
2161
|
+
* @typedef {import('eleva').ComponentDefinition} ComponentDefinition
|
|
2162
|
+
* @typedef {import('eleva').ComponentContext} ComponentContext
|
|
2163
|
+
* @typedef {import('eleva').SetupResult} SetupResult
|
|
2164
|
+
* @typedef {import('eleva').ComponentProps} ComponentProps
|
|
2165
|
+
* @typedef {import('eleva').ChildrenMap} ChildrenMap
|
|
2166
|
+
* @typedef {import('eleva').MountResult} MountResult
|
|
2167
|
+
*/ /**
|
|
2168
|
+
* Generic type import.
|
|
2169
|
+
* @template T
|
|
2170
|
+
* @typedef {import('eleva').Signal<T>} Signal
|
|
2171
|
+
*/ // -----------------------------------------------------------------------------
|
|
2172
|
+
// Store Type Definitions
|
|
2173
|
+
// -----------------------------------------------------------------------------
|
|
2174
|
+
/**
|
|
2175
|
+
* Mutation record emitted to subscribers.
|
|
2176
|
+
* @typedef {Object} StoreMutation
|
|
2177
|
+
* @property {string} type
|
|
2178
|
+
* The action name that was dispatched.
|
|
2179
|
+
* @property {unknown} payload
|
|
2180
|
+
* The payload passed to the action.
|
|
2181
|
+
* @property {number} timestamp
|
|
2182
|
+
* Unix timestamp of when the mutation occurred.
|
|
2183
|
+
* @description Record passed to subscribers when state changes via dispatch.
|
|
2184
|
+
* @example
|
|
2185
|
+
* store.subscribe((mutation, state) => {
|
|
2186
|
+
* console.log(`Action: ${mutation.type}`);
|
|
2187
|
+
* console.log(`Payload: ${mutation.payload}`);
|
|
2188
|
+
* console.log(`Time: ${new Date(mutation.timestamp)}`);
|
|
2189
|
+
* });
|
|
2190
|
+
*/ /**
|
|
2191
|
+
* Store configuration options.
|
|
2192
|
+
* @typedef {Object} StoreOptions
|
|
2193
|
+
* @property {Record<string, unknown>} [state]
|
|
2194
|
+
* Initial state object.
|
|
2195
|
+
* @property {Record<string, ActionFunction>} [actions]
|
|
2196
|
+
* Action functions for state mutations.
|
|
2197
|
+
* @property {Record<string, StoreModule>} [namespaces]
|
|
2198
|
+
* Namespaced modules for organizing store.
|
|
2199
|
+
* @property {StorePersistenceOptions} [persistence]
|
|
2200
|
+
* Persistence configuration.
|
|
2201
|
+
* @property {boolean} [devTools]
|
|
2202
|
+
* Enable development tools integration.
|
|
2203
|
+
* @property {StoreErrorHandler} [onError]
|
|
2204
|
+
* Error handler function.
|
|
2205
|
+
* @description Configuration options passed to StorePlugin.install().
|
|
2206
|
+
* @example
|
|
2207
|
+
* app.use(StorePlugin, {
|
|
2208
|
+
* state: { count: 0, user: null },
|
|
2209
|
+
* actions: {
|
|
2210
|
+
* increment: (state) => state.count.value++,
|
|
2211
|
+
* setUser: (state, user) => state.user.value = user
|
|
2212
|
+
* },
|
|
2213
|
+
* persistence: { enabled: true, key: 'my-app' }
|
|
2214
|
+
* });
|
|
2215
|
+
*/ /**
|
|
2216
|
+
* Namespaced store module definition.
|
|
2217
|
+
* @typedef {Object} StoreModule
|
|
2218
|
+
* @property {Record<string, unknown>} state
|
|
2219
|
+
* Module state.
|
|
2220
|
+
* @property {Record<string, ActionFunction>} [actions]
|
|
2221
|
+
* Module actions.
|
|
2222
|
+
* @description Defines a namespaced module for organizing related state and actions.
|
|
2223
|
+
* @example
|
|
2224
|
+
* // Define a module
|
|
2225
|
+
* const authModule = {
|
|
2226
|
+
* state: { user: null, token: null },
|
|
2227
|
+
* actions: {
|
|
2228
|
+
* login: (state, { user, token }) => {
|
|
2229
|
+
* state.auth.user.value = user;
|
|
2230
|
+
* state.auth.token.value = token;
|
|
2231
|
+
* }
|
|
2232
|
+
* }
|
|
2233
|
+
* };
|
|
2234
|
+
*
|
|
2235
|
+
* // Register dynamically
|
|
2236
|
+
* store.registerModule('auth', authModule);
|
|
2237
|
+
*/ /**
|
|
2238
|
+
* Store persistence configuration.
|
|
2239
|
+
* @typedef {Object} StorePersistenceOptions
|
|
2240
|
+
* @property {boolean} [enabled]
|
|
2241
|
+
* Enable state persistence.
|
|
2242
|
+
* @property {string} [key]
|
|
2243
|
+
* Storage key (default: "eleva-store").
|
|
2244
|
+
* @property {'localStorage' | 'sessionStorage'} [storage]
|
|
2245
|
+
* Storage type.
|
|
2246
|
+
* @property {string[]} [include]
|
|
2247
|
+
* Dot-path prefixes to persist (e.g., "auth.user").
|
|
2248
|
+
* @property {string[]} [exclude]
|
|
2249
|
+
* Dot-path prefixes to exclude.
|
|
2250
|
+
* @description Configuration for persisting store state to localStorage or sessionStorage.
|
|
2251
|
+
* @example
|
|
2252
|
+
* // Persist only specific state paths
|
|
2253
|
+
* persistence: {
|
|
2254
|
+
* enabled: true,
|
|
2255
|
+
* key: 'my-app-store',
|
|
2256
|
+
* storage: 'localStorage',
|
|
2257
|
+
* include: ['user', 'settings.theme']
|
|
2258
|
+
* }
|
|
2259
|
+
*
|
|
2260
|
+
* @example
|
|
2261
|
+
* // Exclude sensitive data
|
|
2262
|
+
* persistence: {
|
|
2263
|
+
* enabled: true,
|
|
2264
|
+
* exclude: ['auth.token', 'temp']
|
|
2265
|
+
* }
|
|
2266
|
+
*/ /**
|
|
2267
|
+
* Store error handler callback.
|
|
2268
|
+
* @typedef {(error: Error, context: string) => void} StoreErrorHandler
|
|
2269
|
+
* @description Custom error handler for store operations.
|
|
2270
|
+
* @example
|
|
2271
|
+
* app.use(StorePlugin, {
|
|
2272
|
+
* onError: (error, context) => {
|
|
2273
|
+
* console.error(`Store error in ${context}:`, error);
|
|
2274
|
+
* // Send to error tracking service
|
|
2275
|
+
* errorTracker.capture(error, { context });
|
|
2276
|
+
* }
|
|
2277
|
+
* });
|
|
2278
|
+
*/ /**
|
|
2279
|
+
* Reactive state tree containing signals and nested namespaces.
|
|
2280
|
+
* @typedef {Record<string, Signal<unknown> | Record<string, unknown>>} StoreState
|
|
2281
|
+
* @description Represents the store's reactive state structure with support for nested modules.
|
|
2282
|
+
*/ /**
|
|
2283
|
+
* Action function signature for store actions.
|
|
2284
|
+
* @typedef {(state: StoreState, payload?: unknown) => unknown} ActionFunction
|
|
2285
|
+
* @description Function that receives state and optional payload, returns action result.
|
|
2286
|
+
*/ /**
|
|
2287
|
+
* Dispatch function signature for triggering actions.
|
|
2288
|
+
* @typedef {(actionName: string, payload?: unknown) => Promise<unknown>} DispatchFunction
|
|
2289
|
+
* @description Dispatches an action by name with optional payload, returns action result.
|
|
2290
|
+
*/ /**
|
|
2291
|
+
* Subscribe callback signature for mutation listeners.
|
|
2292
|
+
* @typedef {(mutation: StoreMutation, state: StoreState) => void} SubscribeCallback
|
|
2293
|
+
* @description Called after each successful action dispatch with mutation details and current state.
|
|
2294
|
+
*/ /**
|
|
2295
|
+
* Store API exposed to components via ctx.store.
|
|
2296
|
+
* @typedef {Object} StoreApi
|
|
2297
|
+
* @property {StoreState} state
|
|
2298
|
+
* Reactive state signals (supports nested modules).
|
|
2299
|
+
* @property {DispatchFunction} dispatch
|
|
2300
|
+
* Dispatch an action by name with optional payload.
|
|
2301
|
+
* @property {(callback: SubscribeCallback) => () => void} subscribe
|
|
2302
|
+
* Subscribe to state mutations. Returns unsubscribe function.
|
|
2303
|
+
* @property {() => Record<string, unknown>} getState
|
|
2304
|
+
* Get a snapshot of current state values.
|
|
2305
|
+
* @property {(namespace: string, module: StoreModule) => void} registerModule
|
|
2306
|
+
* Register a namespaced module dynamically.
|
|
2307
|
+
* @property {(namespace: string) => void} unregisterModule
|
|
2308
|
+
* Unregister a namespaced module.
|
|
2309
|
+
* @property {(key: string, initialValue: unknown) => Signal<unknown>} createState
|
|
2310
|
+
* Create a new state signal dynamically.
|
|
2311
|
+
* @property {(name: string, actionFn: ActionFunction) => void} createAction
|
|
2312
|
+
* Register a new action dynamically.
|
|
2313
|
+
* @property {new <T>(value: T) => Signal<T>} signal
|
|
2314
|
+
* Signal class constructor for manual state creation.
|
|
2315
|
+
* @description The store API injected into component setup as `ctx.store`.
|
|
2316
|
+
* @example
|
|
2317
|
+
* app.component('Counter', {
|
|
2318
|
+
* setup({ store }) {
|
|
2319
|
+
* // Access reactive state
|
|
2320
|
+
* const count = store.state.count;
|
|
2321
|
+
*
|
|
2322
|
+
* // Dispatch actions
|
|
2323
|
+
* const increment = () => store.dispatch('increment');
|
|
2324
|
+
*
|
|
2325
|
+
* // Subscribe to changes
|
|
2326
|
+
* const unsub = store.subscribe((mutation) => {
|
|
2327
|
+
* console.log('State changed:', mutation.type);
|
|
2328
|
+
* });
|
|
2329
|
+
*
|
|
2330
|
+
* return { count, increment, onUnmount: () => unsub() };
|
|
2331
|
+
* },
|
|
2332
|
+
* template: (ctx) => `<button @click="increment">${ctx.count.value}</button>`
|
|
2333
|
+
* });
|
|
2334
|
+
* @see StoreMutation - Mutation record structure.
|
|
2335
|
+
* @see StoreModule - Module definition for namespaces.
|
|
2336
|
+
*/ /**
|
|
1764
2337
|
* @class 🏪 StorePlugin
|
|
1765
2338
|
* @classdesc A powerful reactive state management plugin for Eleva that enables sharing
|
|
1766
2339
|
* reactive data across the entire application. The Store plugin provides a centralized,
|
|
@@ -1787,7 +2360,7 @@
|
|
|
1787
2360
|
* },
|
|
1788
2361
|
* actions: {
|
|
1789
2362
|
* increment: (state) => state.counter.value++,
|
|
1790
|
-
* addTodo: (state, todo) => state.todos.value.
|
|
2363
|
+
* addTodo: (state, todo) => state.todos.value = [...state.todos.value, todo],
|
|
1791
2364
|
* setUser: (state, user) => state.user.value = user
|
|
1792
2365
|
* },
|
|
1793
2366
|
* persistence: {
|
|
@@ -1810,7 +2383,7 @@
|
|
|
1810
2383
|
* <div>
|
|
1811
2384
|
* <p>Hello ${ctx.user.value.name}!</p>
|
|
1812
2385
|
* <p>Count: ${ctx.count.value}</p>
|
|
1813
|
-
* <button
|
|
2386
|
+
* <button @click="increment">+</button>
|
|
1814
2387
|
* </div>
|
|
1815
2388
|
* `
|
|
1816
2389
|
* });
|
|
@@ -1822,27 +2395,34 @@
|
|
|
1822
2395
|
/**
|
|
1823
2396
|
* Plugin version
|
|
1824
2397
|
* @type {string}
|
|
1825
|
-
*/ version: "1.
|
|
2398
|
+
*/ version: "1.1.0",
|
|
1826
2399
|
/**
|
|
1827
2400
|
* Plugin description
|
|
1828
2401
|
* @type {string}
|
|
1829
2402
|
*/ description: "Reactive state management for sharing data across the entire Eleva application",
|
|
1830
2403
|
/**
|
|
1831
|
-
* Installs the plugin into the Eleva instance
|
|
2404
|
+
* Installs the plugin into the Eleva instance.
|
|
1832
2405
|
*
|
|
1833
|
-
* @
|
|
1834
|
-
* @param {
|
|
1835
|
-
* @param {
|
|
1836
|
-
* @param {
|
|
1837
|
-
* @param {
|
|
1838
|
-
* @param {
|
|
1839
|
-
* @param {
|
|
1840
|
-
* @param {
|
|
1841
|
-
* @param {
|
|
1842
|
-
* @param {
|
|
1843
|
-
* @param {
|
|
1844
|
-
* @param {
|
|
1845
|
-
* @param {
|
|
2406
|
+
* @public
|
|
2407
|
+
* @param {Eleva} eleva - The Eleva instance.
|
|
2408
|
+
* @param {StoreOptions} options - Plugin configuration options.
|
|
2409
|
+
* @param {Record<string, unknown>} [options.state={}] - Initial state object.
|
|
2410
|
+
* @param {Record<string, ActionFunction>} [options.actions={}] - Action functions for state mutations.
|
|
2411
|
+
* @param {Record<string, StoreModule>} [options.namespaces={}] - Namespaced modules for organizing store.
|
|
2412
|
+
* @param {StorePersistenceOptions} [options.persistence] - Persistence configuration.
|
|
2413
|
+
* @param {boolean} [options.persistence.enabled=false] - Enable state persistence.
|
|
2414
|
+
* @param {string} [options.persistence.key="eleva-store"] - Storage key.
|
|
2415
|
+
* @param {'localStorage' | 'sessionStorage'} [options.persistence.storage="localStorage"] - Storage type.
|
|
2416
|
+
* @param {string[]} [options.persistence.include] - Dot-path prefixes to persist (e.g., "auth.user")
|
|
2417
|
+
* @param {string[]} [options.persistence.exclude] - Dot-path prefixes to exclude (applies when include is empty).
|
|
2418
|
+
* @param {boolean} [options.devTools=false] - Enable development tools integration.
|
|
2419
|
+
* @param {(error: Error, context: string) => void} [options.onError=null] - Error handler function.
|
|
2420
|
+
* @returns {void}
|
|
2421
|
+
* @description
|
|
2422
|
+
* Installs the store and injects `store` into component setup context by wrapping
|
|
2423
|
+
* `eleva.mount` and `eleva._mountComponents`. Also exposes `eleva.store` and
|
|
2424
|
+
* helper methods (`eleva.dispatch`, `eleva.getState`, `eleva.subscribe`, `eleva.createAction`).
|
|
2425
|
+
* Uninstall restores the originals.
|
|
1846
2426
|
*
|
|
1847
2427
|
* @example
|
|
1848
2428
|
* // Basic installation
|
|
@@ -1862,12 +2442,12 @@
|
|
|
1862
2442
|
* state: { user: null, token: null },
|
|
1863
2443
|
* actions: {
|
|
1864
2444
|
* login: (state, { user, token }) => {
|
|
1865
|
-
* state.user.value = user;
|
|
1866
|
-
* state.token.value = token;
|
|
2445
|
+
* state.auth.user.value = user;
|
|
2446
|
+
* state.auth.token.value = token;
|
|
1867
2447
|
* },
|
|
1868
2448
|
* logout: (state) => {
|
|
1869
|
-
* state.user.value = null;
|
|
1870
|
-
* state.token.value = null;
|
|
2449
|
+
* state.auth.user.value = null;
|
|
2450
|
+
* state.auth.token.value = null;
|
|
1871
2451
|
* }
|
|
1872
2452
|
* }
|
|
1873
2453
|
* }
|
|
@@ -1880,12 +2460,18 @@
|
|
|
1880
2460
|
*/ install (eleva, options = {}) {
|
|
1881
2461
|
const { state = {}, actions = {}, namespaces = {}, persistence = {}, devTools = false, onError = null } = options;
|
|
1882
2462
|
/**
|
|
1883
|
-
* Store
|
|
2463
|
+
* @class Store
|
|
2464
|
+
* @classdesc Store instance that manages all state and provides the API.
|
|
1884
2465
|
* @private
|
|
1885
2466
|
*/ class Store {
|
|
1886
2467
|
/**
|
|
1887
|
-
* Initializes the root state and actions
|
|
2468
|
+
* Initializes the root state and actions.
|
|
2469
|
+
* Creates reactive signals for each state property and copies actions.
|
|
2470
|
+
*
|
|
1888
2471
|
* @private
|
|
2472
|
+
* @param {Record<string, unknown>} initialState - The initial state key-value pairs.
|
|
2473
|
+
* @param {Record<string, ActionFunction>} initialActions - The action functions to register.
|
|
2474
|
+
* @returns {void}
|
|
1889
2475
|
*/ _initializeState(initialState, initialActions) {
|
|
1890
2476
|
// Create reactive signals for each state property
|
|
1891
2477
|
Object.entries(initialState).forEach(([key, value])=>{
|
|
@@ -1897,8 +2483,12 @@
|
|
|
1897
2483
|
};
|
|
1898
2484
|
}
|
|
1899
2485
|
/**
|
|
1900
|
-
* Initializes namespaced modules
|
|
2486
|
+
* Initializes namespaced modules.
|
|
2487
|
+
* Creates namespace objects and populates them with state signals and actions.
|
|
2488
|
+
*
|
|
1901
2489
|
* @private
|
|
2490
|
+
* @param {Record<string, StoreModule>} namespaces - Map of namespace names to module definitions.
|
|
2491
|
+
* @returns {void}
|
|
1902
2492
|
*/ _initializeNamespaces(namespaces) {
|
|
1903
2493
|
Object.entries(namespaces).forEach(([namespace, module])=>{
|
|
1904
2494
|
const { state: moduleState = {}, actions: moduleActions = {} } = module;
|
|
@@ -1920,8 +2510,12 @@
|
|
|
1920
2510
|
});
|
|
1921
2511
|
}
|
|
1922
2512
|
/**
|
|
1923
|
-
* Loads persisted state from storage
|
|
2513
|
+
* Loads persisted state from storage.
|
|
2514
|
+
* Reads from localStorage/sessionStorage and applies values to state signals.
|
|
2515
|
+
* Does nothing if persistence is disabled or running in SSR environment.
|
|
2516
|
+
*
|
|
1924
2517
|
* @private
|
|
2518
|
+
* @returns {void}
|
|
1925
2519
|
*/ _loadPersistedState() {
|
|
1926
2520
|
if (!this.persistence.enabled || typeof window === "undefined") {
|
|
1927
2521
|
return;
|
|
@@ -1942,8 +2536,14 @@
|
|
|
1942
2536
|
}
|
|
1943
2537
|
}
|
|
1944
2538
|
/**
|
|
1945
|
-
* Applies persisted data to the current state
|
|
2539
|
+
* Applies persisted data to the current state.
|
|
2540
|
+
* Recursively updates signal values for paths that should be persisted.
|
|
2541
|
+
*
|
|
1946
2542
|
* @private
|
|
2543
|
+
* @param {Record<string, unknown>} data - The persisted data object to apply.
|
|
2544
|
+
* @param {Record<string, unknown>} [currentState=this.state] - The current state object to update.
|
|
2545
|
+
* @param {string} [path=""] - The current dot-notation path (for include/exclude filtering).
|
|
2546
|
+
* @returns {void}
|
|
1947
2547
|
*/ _applyPersistedData(data, currentState = this.state, path = "") {
|
|
1948
2548
|
Object.entries(data).forEach(([key, value])=>{
|
|
1949
2549
|
const fullPath = path ? `${path}.${key}` : key;
|
|
@@ -1959,8 +2559,12 @@
|
|
|
1959
2559
|
});
|
|
1960
2560
|
}
|
|
1961
2561
|
/**
|
|
1962
|
-
* Determines if a state path should be persisted
|
|
2562
|
+
* Determines if a state path should be persisted.
|
|
2563
|
+
* Checks against include/exclude filters configured in persistence options.
|
|
2564
|
+
*
|
|
1963
2565
|
* @private
|
|
2566
|
+
* @param {string} path - The dot-notation path to check (e.g., "auth.user").
|
|
2567
|
+
* @returns {boolean} True if the path should be persisted, false otherwise.
|
|
1964
2568
|
*/ _shouldPersist(path) {
|
|
1965
2569
|
const { include, exclude } = this.persistence;
|
|
1966
2570
|
if (include && include.length > 0) {
|
|
@@ -1972,8 +2576,12 @@
|
|
|
1972
2576
|
return true;
|
|
1973
2577
|
}
|
|
1974
2578
|
/**
|
|
1975
|
-
* Saves current state to storage
|
|
2579
|
+
* Saves current state to storage.
|
|
2580
|
+
* Extracts persistable data and writes to localStorage/sessionStorage.
|
|
2581
|
+
* Does nothing if persistence is disabled or running in SSR environment.
|
|
2582
|
+
*
|
|
1976
2583
|
* @private
|
|
2584
|
+
* @returns {void}
|
|
1977
2585
|
*/ _saveState() {
|
|
1978
2586
|
if (!this.persistence.enabled || typeof window === "undefined") {
|
|
1979
2587
|
return;
|
|
@@ -1991,8 +2599,13 @@
|
|
|
1991
2599
|
}
|
|
1992
2600
|
}
|
|
1993
2601
|
/**
|
|
1994
|
-
* Extracts data that should be persisted
|
|
2602
|
+
* Extracts data that should be persisted.
|
|
2603
|
+
* Recursively extracts signal values for paths that pass persistence filters.
|
|
2604
|
+
*
|
|
1995
2605
|
* @private
|
|
2606
|
+
* @param {Record<string, unknown>} [currentState=this.state] - The state object to extract from.
|
|
2607
|
+
* @param {string} [path=""] - The current dot-notation path (for include/exclude filtering).
|
|
2608
|
+
* @returns {Record<string, unknown>} The extracted data object with raw values (not signals).
|
|
1996
2609
|
*/ _extractPersistedData(currentState = this.state, path = "") {
|
|
1997
2610
|
const result = {};
|
|
1998
2611
|
Object.entries(currentState).forEach(([key, value])=>{
|
|
@@ -2013,8 +2626,12 @@
|
|
|
2013
2626
|
return result;
|
|
2014
2627
|
}
|
|
2015
2628
|
/**
|
|
2016
|
-
* Sets up development tools integration
|
|
2629
|
+
* Sets up development tools integration.
|
|
2630
|
+
* Registers the store with Eleva DevTools if available and enabled.
|
|
2631
|
+
* Does nothing if devTools is disabled, running in SSR, or DevTools not installed.
|
|
2632
|
+
*
|
|
2017
2633
|
* @private
|
|
2634
|
+
* @returns {void}
|
|
2018
2635
|
*/ _setupDevTools() {
|
|
2019
2636
|
if (!this.devTools || typeof window === "undefined" || !window.__ELEVA_DEVTOOLS__) {
|
|
2020
2637
|
return;
|
|
@@ -2022,10 +2639,26 @@
|
|
|
2022
2639
|
window.__ELEVA_DEVTOOLS__.registerStore(this);
|
|
2023
2640
|
}
|
|
2024
2641
|
/**
|
|
2025
|
-
* Dispatches an action to mutate the state
|
|
2026
|
-
*
|
|
2027
|
-
*
|
|
2028
|
-
*
|
|
2642
|
+
* Dispatches an action to mutate the state.
|
|
2643
|
+
*
|
|
2644
|
+
* Execution flow:
|
|
2645
|
+
* 1. Retrieves the action function (supports namespaced actions like "auth.login")
|
|
2646
|
+
* 2. Records mutation for devtools/history (keeps last 100 mutations)
|
|
2647
|
+
* 3. Executes action with await (actions can be sync or async)
|
|
2648
|
+
* 4. Saves state if persistence is enabled
|
|
2649
|
+
* 5. Notifies all subscribers with (mutation, state)
|
|
2650
|
+
* 6. Notifies devtools if enabled
|
|
2651
|
+
*
|
|
2652
|
+
* @note Always returns a Promise regardless of whether the action is sync or async.
|
|
2653
|
+
* Subscriber callbacks that throw are caught and passed to onError handler.
|
|
2654
|
+
*
|
|
2655
|
+
* @async
|
|
2656
|
+
* @param {string} actionName - The name of the action to dispatch (supports dot notation for namespaces).
|
|
2657
|
+
* @param {unknown} payload - The payload to pass to the action.
|
|
2658
|
+
* @returns {Promise<unknown>} The result of the action (undefined if action returns nothing).
|
|
2659
|
+
* @throws {Error} If action is not found or action function throws.
|
|
2660
|
+
* @see subscribe - Listen for mutations triggered by dispatch.
|
|
2661
|
+
* @see getState - Get current state values.
|
|
2029
2662
|
*/ async dispatch(actionName, payload) {
|
|
2030
2663
|
try {
|
|
2031
2664
|
const action = this._getAction(actionName);
|
|
@@ -2073,8 +2706,12 @@
|
|
|
2073
2706
|
}
|
|
2074
2707
|
}
|
|
2075
2708
|
/**
|
|
2076
|
-
* Gets an action by name (supports namespaced actions)
|
|
2709
|
+
* Gets an action by name (supports namespaced actions).
|
|
2710
|
+
* Traverses the actions object using dot-notation path segments.
|
|
2711
|
+
*
|
|
2077
2712
|
* @private
|
|
2713
|
+
* @param {string} actionName - The action name, supports dot notation for namespaces (e.g., "auth.login").
|
|
2714
|
+
* @returns {ActionFunction | null} The action function if found and is a function, null otherwise.
|
|
2078
2715
|
*/ _getAction(actionName) {
|
|
2079
2716
|
const parts = actionName.split(".");
|
|
2080
2717
|
let current = this.actions;
|
|
@@ -2087,9 +2724,18 @@
|
|
|
2087
2724
|
return typeof current === "function" ? current : null;
|
|
2088
2725
|
}
|
|
2089
2726
|
/**
|
|
2090
|
-
* Subscribes to store mutations
|
|
2091
|
-
*
|
|
2092
|
-
*
|
|
2727
|
+
* Subscribes to store mutations.
|
|
2728
|
+
* Callback is invoked after every successful action dispatch.
|
|
2729
|
+
*
|
|
2730
|
+
* @param {SubscribeCallback} callback
|
|
2731
|
+
* Called after each mutation with:
|
|
2732
|
+
* - mutation.type: The action name that was dispatched
|
|
2733
|
+
* - mutation.payload: The payload passed to the action
|
|
2734
|
+
* - mutation.timestamp: When the mutation occurred (Date.now())
|
|
2735
|
+
* - state: The current state object (contains Signals)
|
|
2736
|
+
* @returns {() => void} Unsubscribe function. Call to stop receiving notifications.
|
|
2737
|
+
* @throws {Error} If callback is not a function.
|
|
2738
|
+
* @see dispatch - Triggers mutations that notify subscribers.
|
|
2093
2739
|
*/ subscribe(callback) {
|
|
2094
2740
|
if (typeof callback !== "function") {
|
|
2095
2741
|
throw new Error("Subscribe callback must be a function");
|
|
@@ -2101,20 +2747,32 @@
|
|
|
2101
2747
|
};
|
|
2102
2748
|
}
|
|
2103
2749
|
/**
|
|
2104
|
-
* Gets
|
|
2105
|
-
*
|
|
2750
|
+
* Gets current state values (not signals).
|
|
2751
|
+
*
|
|
2752
|
+
* @note When persistence include/exclude filters are configured,
|
|
2753
|
+
* this returns only the filtered subset of state.
|
|
2754
|
+
* @returns {Record<string, unknown>} The current state values (filtered by persistence config if set).
|
|
2755
|
+
* @see replaceState - Set state values.
|
|
2756
|
+
* @see subscribe - Listen for state changes.
|
|
2106
2757
|
*/ getState() {
|
|
2107
2758
|
return this._extractPersistedData();
|
|
2108
2759
|
}
|
|
2109
2760
|
/**
|
|
2110
|
-
* Replaces
|
|
2111
|
-
*
|
|
2761
|
+
* Replaces state values (useful for testing or state hydration).
|
|
2762
|
+
*
|
|
2763
|
+
* @note When persistence include/exclude filters are configured,
|
|
2764
|
+
* this only updates the filtered subset of state.
|
|
2765
|
+
* @param {Record<string, unknown>} newState - The new state object.
|
|
2766
|
+
* @returns {void}
|
|
2767
|
+
* @see getState - Get current state values.
|
|
2112
2768
|
*/ replaceState(newState) {
|
|
2113
2769
|
this._applyPersistedData(newState);
|
|
2114
2770
|
this._saveState();
|
|
2115
2771
|
}
|
|
2116
2772
|
/**
|
|
2117
|
-
* Clears persisted state from storage
|
|
2773
|
+
* Clears persisted state from storage.
|
|
2774
|
+
* Does nothing if persistence is disabled or running in SSR.
|
|
2775
|
+
* @returns {void}
|
|
2118
2776
|
*/ clearPersistedState() {
|
|
2119
2777
|
if (!this.persistence.enabled || typeof window === "undefined") {
|
|
2120
2778
|
return;
|
|
@@ -2129,11 +2787,14 @@
|
|
|
2129
2787
|
}
|
|
2130
2788
|
}
|
|
2131
2789
|
/**
|
|
2132
|
-
* Registers a new namespaced module at runtime
|
|
2133
|
-
*
|
|
2134
|
-
*
|
|
2135
|
-
* @param {
|
|
2136
|
-
* @param {
|
|
2790
|
+
* Registers a new namespaced module at runtime.
|
|
2791
|
+
* Logs a warning if the namespace already exists.
|
|
2792
|
+
* Module state is nested under `state[namespace]` and actions under `actions[namespace]`.
|
|
2793
|
+
* @param {string} namespace - The namespace for the module.
|
|
2794
|
+
* @param {StoreModule} module - The module definition.
|
|
2795
|
+
* @param {Record<string, unknown>} module.state - The module's initial state.
|
|
2796
|
+
* @param {Record<string, ActionFunction>} module.actions - The module's actions.
|
|
2797
|
+
* @returns {void}
|
|
2137
2798
|
*/ registerModule(namespace, module) {
|
|
2138
2799
|
if (this.state[namespace] || this.actions[namespace]) {
|
|
2139
2800
|
console.warn(`[StorePlugin] Module "${namespace}" already exists`);
|
|
@@ -2149,8 +2810,11 @@
|
|
|
2149
2810
|
this._saveState();
|
|
2150
2811
|
}
|
|
2151
2812
|
/**
|
|
2152
|
-
* Unregisters a namespaced module
|
|
2153
|
-
*
|
|
2813
|
+
* Unregisters a namespaced module.
|
|
2814
|
+
* Logs a warning if the namespace doesn't exist.
|
|
2815
|
+
* Removes both state and actions under the namespace.
|
|
2816
|
+
* @param {string} namespace - The namespace to unregister.
|
|
2817
|
+
* @returns {void}
|
|
2154
2818
|
*/ unregisterModule(namespace) {
|
|
2155
2819
|
if (!this.state[namespace] && !this.actions[namespace]) {
|
|
2156
2820
|
console.warn(`[StorePlugin] Module "${namespace}" does not exist`);
|
|
@@ -2161,10 +2825,11 @@
|
|
|
2161
2825
|
this._saveState();
|
|
2162
2826
|
}
|
|
2163
2827
|
/**
|
|
2164
|
-
* Creates a new reactive state property at runtime
|
|
2165
|
-
*
|
|
2166
|
-
* @param {
|
|
2167
|
-
* @
|
|
2828
|
+
* Creates a new reactive state property at runtime.
|
|
2829
|
+
*
|
|
2830
|
+
* @param {string} key - The state key.
|
|
2831
|
+
* @param {*} initialValue - The initial value.
|
|
2832
|
+
* @returns {Signal} The created signal, or existing signal if key exists.
|
|
2168
2833
|
*/ createState(key, initialValue) {
|
|
2169
2834
|
if (this.state[key]) {
|
|
2170
2835
|
return this.state[key]; // Return existing state
|
|
@@ -2174,21 +2839,50 @@
|
|
|
2174
2839
|
return this.state[key];
|
|
2175
2840
|
}
|
|
2176
2841
|
/**
|
|
2177
|
-
* Creates a new action at runtime
|
|
2178
|
-
*
|
|
2179
|
-
*
|
|
2842
|
+
* Creates a new action at runtime.
|
|
2843
|
+
* Overwrites existing action if name already exists.
|
|
2844
|
+
* Supports dot-notation for namespaced actions (e.g., "auth.login").
|
|
2845
|
+
* @param {string} name - The action name (supports dot notation for namespaces).
|
|
2846
|
+
* @param {ActionFunction} actionFn - The action function (receives state and payload).
|
|
2847
|
+
* @returns {void}
|
|
2848
|
+
* @throws {Error} If actionFn is not a function.
|
|
2849
|
+
* @example
|
|
2850
|
+
* // Root-level action
|
|
2851
|
+
* store.createAction("increment", (state) => state.count.value++);
|
|
2852
|
+
*
|
|
2853
|
+
* // Namespaced action
|
|
2854
|
+
* store.createAction("auth.login", async (state, credentials) => {
|
|
2855
|
+
* // ... login logic
|
|
2856
|
+
* });
|
|
2180
2857
|
*/ createAction(name, actionFn) {
|
|
2181
2858
|
if (typeof actionFn !== "function") {
|
|
2182
2859
|
throw new Error("Action must be a function");
|
|
2183
2860
|
}
|
|
2184
|
-
|
|
2861
|
+
// Fast path: no dot means simple action (avoids array allocation)
|
|
2862
|
+
if (name.indexOf(".") === -1) {
|
|
2863
|
+
this.actions[name] = actionFn;
|
|
2864
|
+
return;
|
|
2865
|
+
}
|
|
2866
|
+
// Namespaced action, traverse/create nested structure
|
|
2867
|
+
const parts = name.split(".");
|
|
2868
|
+
const lastIndex = parts.length - 1;
|
|
2869
|
+
let current = this.actions;
|
|
2870
|
+
for(let i = 0; i < lastIndex; i++){
|
|
2871
|
+
current = current[parts[i]] || (current[parts[i]] = {});
|
|
2872
|
+
}
|
|
2873
|
+
current[parts[lastIndex]] = actionFn;
|
|
2185
2874
|
}
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2875
|
+
/**
|
|
2876
|
+
* Creates a new Store instance.
|
|
2877
|
+
* Initializes state signals, actions, persistence, and devtools integration.
|
|
2878
|
+
*
|
|
2879
|
+
* @constructor
|
|
2880
|
+
*/ constructor(){
|
|
2881
|
+
/** @type {Record<string, Signal | Record<string, unknown>>} */ this.state = {};
|
|
2882
|
+
/** @type {Record<string, ActionFunction | Record<string, ActionFunction>>} */ this.actions = {};
|
|
2883
|
+
/** @type {Set<SubscribeCallback>} */ this.subscribers = new Set();
|
|
2884
|
+
/** @type {StoreMutation[]} */ this.mutations = [];
|
|
2885
|
+
/** @type {{enabled: boolean, key: string, storage: string, include: string[]|null, exclude: string[]|null}} */ this.persistence = {
|
|
2192
2886
|
enabled: false,
|
|
2193
2887
|
key: "eleva-store",
|
|
2194
2888
|
storage: "localStorage",
|
|
@@ -2196,8 +2890,8 @@
|
|
|
2196
2890
|
exclude: null,
|
|
2197
2891
|
...persistence
|
|
2198
2892
|
};
|
|
2199
|
-
this.devTools = devTools;
|
|
2200
|
-
this.onError = onError;
|
|
2893
|
+
/** @type {boolean} */ this.devTools = devTools;
|
|
2894
|
+
/** @type {((error: Error, context: string) => void)|null} */ this.onError = onError;
|
|
2201
2895
|
this._initializeState(state, actions);
|
|
2202
2896
|
this._initializeNamespaces(namespaces);
|
|
2203
2897
|
this._loadPersistedState();
|
|
@@ -2209,7 +2903,13 @@
|
|
|
2209
2903
|
// Store the original mount method to override it
|
|
2210
2904
|
const originalMount = eleva.mount;
|
|
2211
2905
|
/**
|
|
2212
|
-
*
|
|
2906
|
+
* Overridden mount method that injects store context into components.
|
|
2907
|
+
* Wraps the original mount to add `ctx.store` to the component's setup context.
|
|
2908
|
+
*
|
|
2909
|
+
* @param {HTMLElement} container - The DOM element where the component will be mounted.
|
|
2910
|
+
* @param {string | ComponentDefinition} compName - Component name or definition.
|
|
2911
|
+
* @param {ComponentProps} [props={}] - Optional properties to pass to the component.
|
|
2912
|
+
* @returns {Promise<MountResult>} The mount result.
|
|
2213
2913
|
*/ eleva.mount = async (container, compName, props = {})=>{
|
|
2214
2914
|
// Get the component definition
|
|
2215
2915
|
const componentDef = typeof compName === "string" ? eleva._components.get(compName) || compName : compName;
|
|
@@ -2220,8 +2920,7 @@
|
|
|
2220
2920
|
const wrappedComponent = {
|
|
2221
2921
|
...componentDef,
|
|
2222
2922
|
async setup (ctx) {
|
|
2223
|
-
|
|
2224
|
-
ctx.store = {
|
|
2923
|
+
/** @type {StoreApi} */ ctx.store = {
|
|
2225
2924
|
// Core store functionality
|
|
2226
2925
|
state: store.state,
|
|
2227
2926
|
dispatch: store.dispatch.bind(store),
|
|
@@ -2247,7 +2946,16 @@
|
|
|
2247
2946
|
};
|
|
2248
2947
|
// Override _mountComponents to ensure child components also get store context
|
|
2249
2948
|
const originalMountComponents = eleva._mountComponents;
|
|
2250
|
-
|
|
2949
|
+
/**
|
|
2950
|
+
* Overridden _mountComponents method that injects store context into child components.
|
|
2951
|
+
* Wraps each child component's setup function to add `ctx.store` before mounting.
|
|
2952
|
+
*
|
|
2953
|
+
* @param {HTMLElement} container - The parent container element.
|
|
2954
|
+
* @param {ChildrenMap} children - Map of selectors to component definitions.
|
|
2955
|
+
* @param {MountResult[]} childInstances - Array to store mounted instances.
|
|
2956
|
+
* @param {ComponentContext & SetupResult} context - Parent component context.
|
|
2957
|
+
* @returns {Promise<void>}
|
|
2958
|
+
*/ eleva._mountComponents = async (container, children, childInstances, context)=>{
|
|
2251
2959
|
// Create wrapped children with store injection
|
|
2252
2960
|
const wrappedChildren = {};
|
|
2253
2961
|
for (const [selector, childComponent] of Object.entries(children)){
|
|
@@ -2256,8 +2964,7 @@
|
|
|
2256
2964
|
wrappedChildren[selector] = {
|
|
2257
2965
|
...componentDef,
|
|
2258
2966
|
async setup (ctx) {
|
|
2259
|
-
|
|
2260
|
-
ctx.store = {
|
|
2967
|
+
/** @type {StoreApi} */ ctx.store = {
|
|
2261
2968
|
// Core store functionality
|
|
2262
2969
|
state: store.state,
|
|
2263
2970
|
dispatch: store.dispatch.bind(store),
|
|
@@ -2283,23 +2990,23 @@
|
|
|
2283
2990
|
}
|
|
2284
2991
|
}
|
|
2285
2992
|
// Call original _mountComponents with wrapped children
|
|
2286
|
-
return await originalMountComponents.call(eleva, container, wrappedChildren, childInstances);
|
|
2993
|
+
return await originalMountComponents.call(eleva, container, wrappedChildren, childInstances, context);
|
|
2287
2994
|
};
|
|
2288
2995
|
// Expose store instance and utilities on the Eleva instance
|
|
2289
|
-
eleva.store = store;
|
|
2996
|
+
/** @type {StoreApi} */ eleva.store = store;
|
|
2290
2997
|
/**
|
|
2291
|
-
* Expose utility methods on the Eleva instance
|
|
2292
|
-
*
|
|
2293
|
-
*/ eleva.createAction = (name, actionFn)=>{
|
|
2294
|
-
store.
|
|
2998
|
+
* Expose utility methods on the Eleva instance.
|
|
2999
|
+
* These are top-level helpers (e.g., `eleva.dispatch`) in addition to `eleva.store`.
|
|
3000
|
+
*/ /** @type {(name: string, actionFn: ActionFunction) => void} */ eleva.createAction = (name, actionFn)=>{
|
|
3001
|
+
store.createAction(name, actionFn);
|
|
2295
3002
|
};
|
|
2296
|
-
eleva.dispatch = (actionName, payload)=>{
|
|
3003
|
+
/** @type {DispatchFunction} */ eleva.dispatch = (actionName, payload)=>{
|
|
2297
3004
|
return store.dispatch(actionName, payload);
|
|
2298
3005
|
};
|
|
2299
|
-
eleva.getState = ()=>{
|
|
3006
|
+
/** @type {() => Record<string, unknown>} */ eleva.getState = ()=>{
|
|
2300
3007
|
return store.getState();
|
|
2301
3008
|
};
|
|
2302
|
-
eleva.subscribe = (callback)=>{
|
|
3009
|
+
/** @type {(callback: SubscribeCallback) => () => void} */ eleva.subscribe = (callback)=>{
|
|
2303
3010
|
return store.subscribe(callback);
|
|
2304
3011
|
};
|
|
2305
3012
|
// Store original methods for cleanup
|
|
@@ -2307,14 +3014,17 @@
|
|
|
2307
3014
|
eleva._originalMountComponents = originalMountComponents;
|
|
2308
3015
|
},
|
|
2309
3016
|
/**
|
|
2310
|
-
* Uninstalls the plugin from the Eleva instance
|
|
2311
|
-
*
|
|
2312
|
-
* @param {Object} eleva - The Eleva instance
|
|
3017
|
+
* Uninstalls the plugin from the Eleva instance.
|
|
2313
3018
|
*
|
|
3019
|
+
* @public
|
|
3020
|
+
* @param {Eleva} eleva - The Eleva instance.
|
|
3021
|
+
* @returns {void}
|
|
2314
3022
|
* @description
|
|
2315
3023
|
* Restores the original Eleva methods and removes all plugin-specific
|
|
2316
3024
|
* functionality. This method should be called when the plugin is no
|
|
2317
3025
|
* longer needed.
|
|
3026
|
+
* Also removes `eleva.store` and top-level helpers (`eleva.dispatch`,
|
|
3027
|
+
* `eleva.getState`, `eleva.subscribe`, `eleva.createAction`).
|
|
2318
3028
|
*
|
|
2319
3029
|
* @example
|
|
2320
3030
|
* // Uninstall the plugin
|