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