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.
Files changed (97) hide show
  1. package/README.md +21 -10
  2. package/dist/{eleva-plugins.cjs.js → eleva-plugins.cjs} +1002 -292
  3. package/dist/eleva-plugins.cjs.map +1 -0
  4. package/dist/eleva-plugins.d.cts +1352 -0
  5. package/dist/eleva-plugins.d.cts.map +1 -0
  6. package/dist/eleva-plugins.d.ts +1352 -0
  7. package/dist/eleva-plugins.d.ts.map +1 -0
  8. package/dist/{eleva-plugins.esm.js → eleva-plugins.js} +1002 -292
  9. package/dist/eleva-plugins.js.map +1 -0
  10. package/dist/eleva-plugins.umd.js +1001 -291
  11. package/dist/eleva-plugins.umd.js.map +1 -1
  12. package/dist/eleva-plugins.umd.min.js +1 -1
  13. package/dist/eleva-plugins.umd.min.js.map +1 -1
  14. package/dist/{eleva.cjs.js → eleva.cjs} +421 -191
  15. package/dist/eleva.cjs.map +1 -0
  16. package/dist/eleva.d.cts +1329 -0
  17. package/dist/eleva.d.cts.map +1 -0
  18. package/dist/eleva.d.ts +473 -226
  19. package/dist/eleva.d.ts.map +1 -0
  20. package/dist/{eleva.esm.js → eleva.js} +422 -192
  21. package/dist/eleva.js.map +1 -0
  22. package/dist/eleva.umd.js +420 -190
  23. package/dist/eleva.umd.js.map +1 -1
  24. package/dist/eleva.umd.min.js +1 -1
  25. package/dist/eleva.umd.min.js.map +1 -1
  26. package/dist/plugins/attr.cjs +279 -0
  27. package/dist/plugins/attr.cjs.map +1 -0
  28. package/dist/plugins/attr.d.cts +101 -0
  29. package/dist/plugins/attr.d.cts.map +1 -0
  30. package/dist/plugins/attr.d.ts +101 -0
  31. package/dist/plugins/attr.d.ts.map +1 -0
  32. package/dist/plugins/attr.js +276 -0
  33. package/dist/plugins/attr.js.map +1 -0
  34. package/dist/plugins/attr.umd.js +111 -22
  35. package/dist/plugins/attr.umd.js.map +1 -1
  36. package/dist/plugins/attr.umd.min.js +1 -1
  37. package/dist/plugins/attr.umd.min.js.map +1 -1
  38. package/dist/plugins/router.cjs +1873 -0
  39. package/dist/plugins/router.cjs.map +1 -0
  40. package/dist/plugins/router.d.cts +1296 -0
  41. package/dist/plugins/router.d.cts.map +1 -0
  42. package/dist/plugins/router.d.ts +1296 -0
  43. package/dist/plugins/router.d.ts.map +1 -0
  44. package/dist/plugins/router.js +1870 -0
  45. package/dist/plugins/router.js.map +1 -0
  46. package/dist/plugins/router.umd.js +482 -186
  47. package/dist/plugins/router.umd.js.map +1 -1
  48. package/dist/plugins/router.umd.min.js +1 -1
  49. package/dist/plugins/router.umd.min.js.map +1 -1
  50. package/dist/plugins/store.cjs +920 -0
  51. package/dist/plugins/store.cjs.map +1 -0
  52. package/dist/plugins/store.d.cts +266 -0
  53. package/dist/plugins/store.d.cts.map +1 -0
  54. package/dist/plugins/store.d.ts +266 -0
  55. package/dist/plugins/store.d.ts.map +1 -0
  56. package/dist/plugins/store.js +917 -0
  57. package/dist/plugins/store.js.map +1 -0
  58. package/dist/plugins/store.umd.js +410 -85
  59. package/dist/plugins/store.umd.js.map +1 -1
  60. package/dist/plugins/store.umd.min.js +1 -1
  61. package/dist/plugins/store.umd.min.js.map +1 -1
  62. package/package.json +112 -68
  63. package/src/core/Eleva.js +195 -115
  64. package/src/index.cjs +10 -0
  65. package/src/index.js +11 -0
  66. package/src/modules/Emitter.js +68 -20
  67. package/src/modules/Renderer.js +82 -20
  68. package/src/modules/Signal.js +43 -15
  69. package/src/modules/TemplateEngine.js +50 -9
  70. package/src/plugins/Attr.js +121 -19
  71. package/src/plugins/Router.js +526 -181
  72. package/src/plugins/Store.js +448 -69
  73. package/src/plugins/index.js +1 -0
  74. package/types/core/Eleva.d.ts +263 -169
  75. package/types/core/Eleva.d.ts.map +1 -1
  76. package/types/index.d.cts +3 -0
  77. package/types/index.d.cts.map +1 -0
  78. package/types/index.d.ts +5 -0
  79. package/types/index.d.ts.map +1 -1
  80. package/types/modules/Emitter.d.ts +73 -30
  81. package/types/modules/Emitter.d.ts.map +1 -1
  82. package/types/modules/Renderer.d.ts +48 -18
  83. package/types/modules/Renderer.d.ts.map +1 -1
  84. package/types/modules/Signal.d.ts +44 -16
  85. package/types/modules/Signal.d.ts.map +1 -1
  86. package/types/modules/TemplateEngine.d.ts +46 -11
  87. package/types/modules/TemplateEngine.d.ts.map +1 -1
  88. package/types/plugins/Attr.d.ts +83 -16
  89. package/types/plugins/Attr.d.ts.map +1 -1
  90. package/types/plugins/Router.d.ts +498 -207
  91. package/types/plugins/Router.d.ts.map +1 -1
  92. package/types/plugins/Store.d.ts +211 -37
  93. package/types/plugins/Store.d.ts.map +1 -1
  94. package/dist/eleva-plugins.cjs.js.map +0 -1
  95. package/dist/eleva-plugins.esm.js.map +0 -1
  96. package/dist/eleva.cjs.js.map +0 -1
  97. package/dist/eleva.esm.js.map +0 -1
@@ -1,4 +1,4 @@
1
- /*! Eleva Router Plugin v1.0.1 | MIT License | https://elevajs.com */
1
+ /*! Eleva Router Plugin v1.1.0 | MIT License | https://elevajs.com */
2
2
  (function (global, factory) {
3
3
  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
4
4
  typeof define === 'function' && define.amd ? define(['exports'], factory) :
@@ -6,60 +6,176 @@
6
6
  })(this, (function (exports) { 'use strict';
7
7
 
8
8
  /**
9
+ * @module eleva/plugins/router
10
+ * @fileoverview Client-side router plugin with hash, history, and query modes,
11
+ * navigation guards, and lifecycle hooks.
12
+ */ // ============================================================================
13
+ // TYPE DEFINITIONS
14
+ // ============================================================================
15
+ // -----------------------------------------------------------------------------
16
+ // External Type Imports
17
+ // -----------------------------------------------------------------------------
18
+ /**
19
+ * Type imports from the Eleva core library.
9
20
  * @typedef {import('eleva').Eleva} Eleva
10
- * @typedef {import('eleva').Signal} Signal
11
21
  * @typedef {import('eleva').ComponentDefinition} ComponentDefinition
12
22
  * @typedef {import('eleva').Emitter} Emitter
13
23
  * @typedef {import('eleva').MountResult} MountResult
14
- */ // ============================================
15
- // Core Type Definitions
16
- // ============================================
24
+ * @typedef {import('eleva').UnsubscribeFunction} UnsubscribeFunction
25
+ */ /**
26
+ * Generic type import.
27
+ * @template T
28
+ * @typedef {import('eleva').Signal<T>} Signal
29
+ */ // -----------------------------------------------------------------------------
30
+ // Router Events
31
+ // -----------------------------------------------------------------------------
32
+ /**
33
+ * Fired when the router initialization completes successfully.
34
+ * @event router:ready
35
+ * @type {Router}
36
+ */ /**
37
+ * Fired when an error occurs during navigation or route handling.
38
+ * @event router:error
39
+ * @type {Error}
40
+ */ /**
41
+ * Fired when no matching route is found for the requested path.
42
+ * @event router:notFound
43
+ * @type {{to: RouteLocation, from: RouteLocation | null, path: string}}
44
+ */ /**
45
+ * Fired before guards run, allowing plugins to block or redirect navigation.
46
+ * @event router:beforeEach
47
+ * @type {NavigationContext}
48
+ */ /**
49
+ * Fired before component resolution, allowing plugins to modify the resolve context.
50
+ * @event router:beforeResolve
51
+ * @type {ResolveContext}
52
+ */ /**
53
+ * Fired after components are resolved successfully.
54
+ * @event router:afterResolve
55
+ * @type {ResolveContext}
56
+ */ /**
57
+ * Fired after leaving the previous route.
58
+ * @event router:afterLeave
59
+ * @type {{to: RouteLocation, from: RouteLocation}}
60
+ */ /**
61
+ * Fired before DOM rendering begins.
62
+ * @event router:beforeRender
63
+ * @type {RenderContext}
64
+ */ /**
65
+ * Fired after DOM rendering completes.
66
+ * @event router:afterRender
67
+ * @type {RenderContext}
68
+ */ /**
69
+ * Fired after render for scroll behavior handling.
70
+ * @event router:scroll
71
+ * @type {ScrollContext}
72
+ */ /**
73
+ * Fired after entering the new route.
74
+ * @event router:afterEnter
75
+ * @type {{to: RouteLocation, from: RouteLocation | null}}
76
+ */ /**
77
+ * Fired after navigation completes successfully.
78
+ * @event router:afterEach
79
+ * @type {{to: RouteLocation, from: RouteLocation | null}}
80
+ */ /**
81
+ * Fired when a route is dynamically added.
82
+ * @event router:routeAdded
83
+ * @type {RouteDefinition}
84
+ */ /**
85
+ * Fired when a route is dynamically removed.
86
+ * @event router:routeRemoved
87
+ * @type {RouteDefinition}
88
+ */ // -----------------------------------------------------------------------------
89
+ // Router Data Types
90
+ // -----------------------------------------------------------------------------
17
91
  /**
18
- * @typedef {'hash' | 'history' | 'query'} RouterMode
19
92
  * The routing mode determines how the router manages URL state.
20
93
  * - `hash`: Uses URL hash (e.g., `/#/path`) - works without server config
21
94
  * - `history`: Uses HTML5 History API (e.g., `/path`) - requires server config
22
95
  * - `query`: Uses query parameters (e.g., `?view=/path`) - useful for embedded apps
96
+ * @typedef {'hash' | 'history' | 'query'} RouterMode
97
+ */ /**
98
+ * Route parameters extracted from the URL path.
99
+ * @typedef {Record<string, string>} RouteParams
100
+ * @description Key-value pairs extracted from dynamic route segments (e.g., `/users/:id` → `{ id: '123' }`).
101
+ */ /**
102
+ * Query parameters from the URL query string.
103
+ * @typedef {Record<string, string>} QueryParams
104
+ * @description Key-value pairs from the URL query string (e.g., `?page=1&sort=name`).
105
+ */ /**
106
+ * Navigation input parameters supporting multiple value types.
107
+ * @typedef {Record<string, string | number | boolean>} NavigationParams
108
+ * @description Parameters passed to navigation functions, automatically converted to strings in URLs.
23
109
  */ /**
110
+ * Function signature for programmatic navigation.
111
+ * @typedef {(location: string | NavigationTarget, params?: NavigationParams) => Promise<boolean>} NavigateFunction
112
+ * @description Returns true if navigation succeeded, false if blocked by a guard.
113
+ */ /**
114
+ * Router configuration options.
24
115
  * @typedef {Object} RouterOptions
25
- * @property {RouterMode} [mode='hash'] - The routing mode to use.
26
- * @property {string} [queryParam='view'] - Query parameter name for 'query' mode.
27
- * @property {string} [viewSelector='root'] - Selector for the view container element.
28
- * @property {string} mount - CSS selector for the mount point element.
29
- * @property {RouteDefinition[]} routes - Array of route definitions.
30
- * @property {string | ComponentDefinition} [globalLayout] - Default layout for all routes.
31
- * @property {NavigationGuard} [onBeforeEach] - Global navigation guard.
116
+ * @property {RouterMode} [mode='hash']
117
+ * The routing mode to use.
118
+ * @property {string} [queryParam='view']
119
+ * Query parameter name for 'query' mode.
120
+ * @property {string} [viewSelector='view']
121
+ * Base selector for the view element.
122
+ * @property {string} mount
123
+ * CSS selector for the mount point element.
124
+ * @property {RouteDefinition[]} routes
125
+ * Array of route definitions.
126
+ * @property {RouteComponent} [globalLayout]
127
+ * Default layout for all routes.
128
+ * @property {NavigationGuard} [onBeforeEach]
129
+ * Global navigation guard.
130
+ * @property {boolean} [autoStart=true]
131
+ * Whether to start the router automatically.
32
132
  * @description Configuration options for the Router plugin.
33
133
  */ /**
134
+ * Object describing a navigation target for `router.navigate()`.
34
135
  * @typedef {Object} NavigationTarget
35
- * @property {string} path - The target path (can include params like '/users/:id').
36
- * @property {Record<string, string>} [params] - Route parameters to inject into the path.
37
- * @property {Record<string, string>} [query] - Query parameters to append.
38
- * @property {boolean} [replace=false] - Whether to replace current history entry.
39
- * @property {Record<string, any>} [state] - State object to pass to history.
136
+ * @property {string} path
137
+ * The target path (can include params like '/users/:id').
138
+ * @property {NavigationParams} [params]
139
+ * Route parameters to inject.
140
+ * @property {NavigationParams} [query]
141
+ * Query parameters to append.
142
+ * @property {boolean} [replace=false]
143
+ * Whether to replace current history entry.
144
+ * @property {unknown} [state]
145
+ * History state to pass.
40
146
  * @description Object describing a navigation target for `router.navigate()`.
41
147
  */ /**
148
+ * Saved scroll position.
42
149
  * @typedef {Object} ScrollPosition
43
- * @property {number} x - Horizontal scroll position.
44
- * @property {number} y - Vertical scroll position.
150
+ * @property {number} x
151
+ * Horizontal scroll position.
152
+ * @property {number} y
153
+ * Vertical scroll position.
45
154
  * @description Represents a saved scroll position.
46
155
  */ /**
156
+ * Internal representation of a parsed route path segment.
47
157
  * @typedef {Object} RouteSegment
48
- * @property {'static' | 'param'} type - The segment type.
49
- * @property {string} value - The segment value (for static) or empty string (for param).
50
- * @property {string} [name] - The parameter name (for param segments).
158
+ * @property {'static' | 'param'} type
159
+ * The segment type.
160
+ * @property {string} [value]
161
+ * The segment value (static segments).
162
+ * @property {string} [name]
163
+ * The parameter name (param segments).
51
164
  * @description Internal representation of a parsed route path segment.
52
165
  * @private
53
166
  */ /**
167
+ * Result of matching a path against route definitions.
54
168
  * @typedef {Object} RouteMatch
55
- * @property {RouteDefinition} route - The matched route definition.
56
- * @property {Record<string, string>} params - The extracted route parameters.
169
+ * @property {RouteDefinition} route
170
+ * The matched route definition.
171
+ * @property {RouteParams} params
172
+ * The extracted route parameters.
57
173
  * @description Result of matching a path against route definitions.
58
174
  * @private
59
175
  */ /**
60
- * @typedef {Record<string, any>} RouteMeta
61
- * @description Arbitrary metadata attached to routes for use in guards and components.
62
- * Common properties include:
176
+ * Arbitrary metadata attached to routes for use in guards and components.
177
+ * @typedef {Record<string, unknown>} RouteMeta
178
+ * @description Common properties include:
63
179
  * - `requiresAuth: boolean` - Whether the route requires authentication
64
180
  * - `title: string` - Page title for the route
65
181
  * - `roles: string[]` - Required user roles
@@ -70,76 +186,119 @@
70
186
  * meta: { requiresAuth: true, roles: ['admin'], title: 'Admin Dashboard' }
71
187
  * }
72
188
  */ /**
189
+ * Interface for the router's error handling system.
73
190
  * @typedef {Object} RouterErrorHandler
74
- * @property {(error: Error, context: string, details?: Record<string, any>) => void} handle - Throws a formatted error.
75
- * @property {(message: string, details?: Record<string, any>) => void} warn - Logs a warning.
76
- * @property {(message: string, error: Error, details?: Record<string, any>) => void} log - Logs an error without throwing.
191
+ * @property {(error: Error, context: string, details?: Record<string, unknown>) => void} handle
192
+ * Throws a formatted error.
193
+ * @property {(message: string, details?: Record<string, unknown>) => void} warn
194
+ * Logs a warning.
195
+ * @property {(message: string, error: Error, details?: Record<string, unknown>) => void} log
196
+ * Logs an error without throwing.
77
197
  * @description Interface for the router's error handling system.
78
- */ // ============================================
79
- // Event Callback Type Definitions
80
- // ============================================
198
+ */ // -----------------------------------------------------------------------------
199
+ // Event Callback Types
200
+ // -----------------------------------------------------------------------------
81
201
  /**
202
+ * Callback for `router:beforeEach` event.
82
203
  * @callback NavigationContextCallback
83
- * @param {NavigationContext} context - The navigation context (can be modified to block/redirect).
204
+ * @param {NavigationContext} context
205
+ * The navigation context (can be modified to block/redirect).
84
206
  * @returns {void | Promise<void>}
85
- * @description Callback for `router:beforeEach` event. Modify context to control navigation.
207
+ * @description Modify context to control navigation flow.
86
208
  */ /**
209
+ * Callback for `router:beforeResolve` and `router:afterResolve` events.
87
210
  * @callback ResolveContextCallback
88
- * @param {ResolveContext} context - The resolve context (can be modified to block/redirect).
211
+ * @param {ResolveContext} context
212
+ * The resolve context (can be modified to block/redirect).
89
213
  * @returns {void | Promise<void>}
90
214
  * @description Callback for `router:beforeResolve` and `router:afterResolve` events.
91
215
  */ /**
216
+ * Callback for `router:beforeRender` and `router:afterRender` events.
92
217
  * @callback RenderContextCallback
93
- * @param {RenderContext} context - The render context.
218
+ * @param {RenderContext} context
219
+ * The render context.
94
220
  * @returns {void | Promise<void>}
95
221
  * @description Callback for `router:beforeRender` and `router:afterRender` events.
96
222
  */ /**
223
+ * Callback for `router:scroll` event.
97
224
  * @callback ScrollContextCallback
98
- * @param {ScrollContext} context - The scroll context with saved position info.
225
+ * @param {ScrollContext} context
226
+ * The scroll context with saved position info.
99
227
  * @returns {void | Promise<void>}
100
- * @description Callback for `router:scroll` event. Use to implement scroll behavior.
228
+ * @description Use to implement custom scroll behavior.
101
229
  */ /**
230
+ * Callback for `router:afterEnter`, `router:afterLeave`, `router:afterEach` events.
102
231
  * @callback RouteChangeCallback
103
- * @param {RouteLocation} to - The target route location.
104
- * @param {RouteLocation | null} from - The source route location.
232
+ * @param {RouteLocation} to
233
+ * The target route location.
234
+ * @param {RouteLocation | null} from
235
+ * The source route location.
105
236
  * @returns {void | Promise<void>}
106
237
  * @description Callback for `router:afterEnter`, `router:afterLeave`, `router:afterEach` events.
107
238
  */ /**
239
+ * Router context injected into component setup as `ctx.router`.
240
+ * @typedef {Object} RouterContext
241
+ * @property {NavigateFunction} navigate
242
+ * Programmatic navigation function.
243
+ * @property {Signal<RouteLocation | null>} current
244
+ * Reactive signal for current route.
245
+ * @property {Signal<RouteLocation | null>} previous
246
+ * Reactive signal for previous route.
247
+ * @property {RouteParams} params
248
+ * Current route params (getter).
249
+ * @property {QueryParams} query
250
+ * Current route query (getter).
251
+ * @property {string} path
252
+ * Current route path (getter).
253
+ * @property {string} fullUrl
254
+ * Current routed URL string (getter).
255
+ * @property {RouteMeta} meta
256
+ * Current route meta (getter).
257
+ * @description Injected into component setup as `ctx.router`.
258
+ */ /**
259
+ * Callback for `router:error` event.
108
260
  * @callback RouterErrorCallback
109
- * @param {Error} error - The error that occurred.
110
- * @param {RouteLocation} [to] - The target route (if available).
111
- * @param {RouteLocation | null} [from] - The source route (if available).
261
+ * @param {Error} error
262
+ * The error that occurred.
263
+ * @param {RouteLocation} [to]
264
+ * The target route (if available).
265
+ * @param {RouteLocation | null} [from]
266
+ * The source route (if available).
112
267
  * @returns {void | Promise<void>}
113
- * @description Callback for `router:onError` event.
268
+ * @description Callback for `router:error` event.
114
269
  */ /**
270
+ * Callback for `router:ready` event.
115
271
  * @callback RouterReadyCallback
116
- * @param {Router} router - The router instance.
272
+ * @param {Router} router
273
+ * The router instance.
117
274
  * @returns {void | Promise<void>}
118
275
  * @description Callback for `router:ready` event.
119
276
  */ /**
277
+ * Callback for `router:routeAdded` event.
120
278
  * @callback RouteAddedCallback
121
- * @param {RouteDefinition} route - The added route definition.
279
+ * @param {RouteDefinition} route
280
+ * The added route definition.
122
281
  * @returns {void | Promise<void>}
123
282
  * @description Callback for `router:routeAdded` event.
124
283
  */ /**
284
+ * Callback for `router:routeRemoved` event.
125
285
  * @callback RouteRemovedCallback
126
- * @param {RouteDefinition} route - The removed route definition.
286
+ * @param {RouteDefinition} route
287
+ * The removed route definition.
127
288
  * @returns {void | Promise<void>}
128
289
  * @description Callback for `router:routeRemoved` event.
129
- */ // ============================================
130
- // Core Type Definitions (continued)
131
- // ============================================
290
+ */ // ============================================================================
291
+ // CORE IMPLEMENTATION
292
+ // ============================================================================
132
293
  /**
133
294
  * Simple error handler for the core router.
134
- * Can be overridden by error handling plugins.
135
- * Provides consistent error formatting and logging for router operations.
136
295
  * @private
137
296
  */ const CoreErrorHandler = {
138
297
  /**
139
298
  * Handles router errors with basic formatting.
140
299
  * @param {Error} error - The error to handle.
141
300
  * @param {string} context - The context where the error occurred.
142
- * @param {Object} details - Additional error details.
301
+ * @param {Record<string, unknown>} details - Additional error details.
143
302
  * @throws {Error} The formatted error.
144
303
  */ handle (error, context, details = {}) {
145
304
  const message = `[ElevaRouter] ${context}: ${error.message}`;
@@ -158,7 +317,7 @@
158
317
  /**
159
318
  * Logs a warning without throwing an error.
160
319
  * @param {string} message - The warning message.
161
- * @param {Object} details - Additional warning details.
320
+ * @param {Record<string, unknown>} details - Additional warning details.
162
321
  */ warn (message, details = {}) {
163
322
  console.warn(`[ElevaRouter] ${message}`, details);
164
323
  },
@@ -166,7 +325,7 @@
166
325
  * Logs an error without throwing.
167
326
  * @param {string} message - The error message.
168
327
  * @param {Error} error - The original error.
169
- * @param {Object} details - Additional error details.
328
+ * @param {Record<string, unknown>} details - Additional error details.
170
329
  */ log (message, error, details = {}) {
171
330
  console.error(`[ElevaRouter] ${message}`, {
172
331
  error,
@@ -175,26 +334,37 @@
175
334
  }
176
335
  };
177
336
  /**
337
+ * Represents the current or target location in the router.
178
338
  * @typedef {Object} RouteLocation
179
- * @property {string} path - The path of the route (e.g., '/users/123').
180
- * @property {Record<string, string>} query - Query parameters as key-value pairs.
181
- * @property {string} fullUrl - The complete URL including hash, path, and query string.
182
- * @property {Record<string, string>} params - Dynamic route parameters (e.g., `{ id: '123' }`).
183
- * @property {RouteMeta} meta - Metadata associated with the matched route.
184
- * @property {string} [name] - The optional name of the matched route.
185
- * @property {RouteDefinition} matched - The raw route definition object that was matched.
339
+ * @property {string} path
340
+ * The path of the route (e.g., '/users/123').
341
+ * @property {QueryParams} query
342
+ * Query parameters as key-value pairs.
343
+ * @property {string} fullUrl
344
+ * The routed URL string (path plus query).
345
+ * @property {RouteParams} params
346
+ * Dynamic route parameters.
347
+ * @property {RouteMeta} meta
348
+ * Metadata associated with the matched route.
349
+ * @property {string} [name]
350
+ * The optional name of the matched route.
351
+ * @property {RouteDefinition} matched
352
+ * The raw route definition that was matched.
186
353
  * @description Represents the current or target location in the router.
187
354
  */ /**
188
- * @typedef {boolean | string | NavigationTarget | void} NavigationGuardResult
189
- * The return value of a navigation guard.
355
+ * Return value of a navigation guard.
190
356
  * - `true` or `undefined/void`: Allow navigation
191
357
  * - `false`: Abort navigation
192
358
  * - `string`: Redirect to path
193
359
  * - `NavigationTarget`: Redirect with options
360
+ * @typedef {boolean | string | NavigationTarget | void} NavigationGuardResult
194
361
  */ /**
362
+ * Navigation guard function that controls navigation flow.
195
363
  * @callback NavigationGuard
196
- * @param {RouteLocation} to - The target route location.
197
- * @param {RouteLocation | null} from - The source route location (null on initial navigation).
364
+ * @param {RouteLocation} to
365
+ * The target route location.
366
+ * @param {RouteLocation | null} from
367
+ * The source route location (null on initial).
198
368
  * @returns {NavigationGuardResult | Promise<NavigationGuardResult>}
199
369
  * @description A function that controls navigation flow. Runs before navigation is confirmed.
200
370
  * @example
@@ -206,9 +376,12 @@
206
376
  * // Allow navigation (implicit return undefined)
207
377
  * };
208
378
  */ /**
379
+ * Navigation hook for side effects. Does not affect navigation flow.
209
380
  * @callback NavigationHook
210
- * @param {RouteLocation} to - The target route location.
211
- * @param {RouteLocation | null} from - The source route location.
381
+ * @param {RouteLocation} to
382
+ * The target route location.
383
+ * @param {RouteLocation | null} from
384
+ * The source route location.
212
385
  * @returns {void | Promise<void>}
213
386
  * @description A lifecycle hook for side effects. Does not affect navigation flow.
214
387
  * @example
@@ -217,11 +390,16 @@
217
390
  * analytics.trackPageView(to.path);
218
391
  * };
219
392
  */ /**
393
+ * Interface for router plugins.
220
394
  * @typedef {Object} RouterPlugin
221
- * @property {string} name - Unique plugin identifier.
222
- * @property {string} [version] - Plugin version (recommended to match router version).
223
- * @property {(router: Router, options?: Record<string, any>) => void} install - Installation function.
224
- * @property {(router: Router) => void | Promise<void>} [destroy] - Cleanup function called on router.destroy().
395
+ * @property {string} name
396
+ * Unique plugin identifier.
397
+ * @property {string} [version]
398
+ * Plugin version (recommended to match router version).
399
+ * @property {(router: Router, options?: Record<string, unknown>) => void} install
400
+ * Installation function.
401
+ * @property {(router: Router) => void | Promise<void>} [destroy]
402
+ * Cleanup function called on router.destroy().
225
403
  * @description Interface for router plugins. Plugins can extend router functionality.
226
404
  * @example
227
405
  * const AnalyticsPlugin = {
@@ -234,57 +412,93 @@
234
412
  * }
235
413
  * };
236
414
  */ /**
415
+ * Context object for navigation events that plugins can modify.
237
416
  * @typedef {Object} NavigationContext
238
- * @property {RouteLocation} to - The target route location.
239
- * @property {RouteLocation | null} from - The source route location.
240
- * @property {boolean} cancelled - Whether navigation has been cancelled.
241
- * @property {string | {path: string} | null} redirectTo - Redirect target if navigation should redirect.
242
- * @description A context object passed to navigation events that plugins can modify to control navigation flow.
417
+ * @property {RouteLocation} to
418
+ * The target route location.
419
+ * @property {RouteLocation | null} from
420
+ * The source route location.
421
+ * @property {boolean} cancelled
422
+ * Whether navigation has been cancelled.
423
+ * @property {string | NavigationTarget | null} redirectTo
424
+ * Redirect target if set.
425
+ * @description Passed to navigation events. Plugins can modify to control navigation flow.
243
426
  */ /**
427
+ * Context object for component resolution events.
244
428
  * @typedef {Object} ResolveContext
245
- * @property {RouteLocation} to - The target route location.
246
- * @property {RouteLocation | null} from - The source route location.
247
- * @property {RouteDefinition} route - The matched route definition.
248
- * @property {ComponentDefinition | null} layoutComponent - The resolved layout component (available in afterResolve).
249
- * @property {ComponentDefinition | null} pageComponent - The resolved page component (available in afterResolve).
250
- * @property {boolean} cancelled - Whether navigation has been cancelled.
251
- * @property {string | {path: string} | null} redirectTo - Redirect target if navigation should redirect.
252
- * @description A context object passed to component resolution events.
429
+ * @property {RouteLocation} to
430
+ * The target route location.
431
+ * @property {RouteLocation | null} from
432
+ * The source route location.
433
+ * @property {RouteDefinition} route
434
+ * The matched route definition.
435
+ * @property {ComponentDefinition | null} layoutComponent
436
+ * The resolved layout component (available in afterResolve).
437
+ * @property {ComponentDefinition | null} pageComponent
438
+ * The resolved page component (available in afterResolve).
439
+ * @property {boolean} cancelled
440
+ * Whether navigation has been cancelled.
441
+ * @property {string | NavigationTarget | null} redirectTo
442
+ * Redirect target if set.
443
+ * @description Passed to component resolution events.
253
444
  */ /**
445
+ * Context object for render events.
254
446
  * @typedef {Object} RenderContext
255
- * @property {RouteLocation} to - The target route location.
256
- * @property {RouteLocation | null} from - The source route location.
257
- * @property {ComponentDefinition | null} layoutComponent - The layout component being rendered.
258
- * @property {ComponentDefinition} pageComponent - The page component being rendered.
259
- * @description A context object passed to render events.
447
+ * @property {RouteLocation} to
448
+ * The target route location.
449
+ * @property {RouteLocation | null} from
450
+ * The source route location.
451
+ * @property {ComponentDefinition | null} layoutComponent
452
+ * The layout component being rendered.
453
+ * @property {ComponentDefinition} pageComponent
454
+ * The page component being rendered.
455
+ * @description Passed to render events.
260
456
  */ /**
457
+ * Context object for scroll events.
261
458
  * @typedef {Object} ScrollContext
262
- * @property {RouteLocation} to - The target route location.
263
- * @property {RouteLocation | null} from - The source route location.
264
- * @property {{x: number, y: number} | null} savedPosition - The saved scroll position (if navigating via back/forward).
265
- * @description A context object passed to scroll events for plugins to handle scroll behavior.
459
+ * @property {RouteLocation} to
460
+ * The target route location.
461
+ * @property {RouteLocation | null} from
462
+ * The source route location.
463
+ * @property {{x: number, y: number} | null} savedPosition
464
+ * Saved position (back/forward nav).
465
+ * @description Passed to scroll events for plugins to handle scroll behavior.
266
466
  */ /**
267
- * @typedef {string | ComponentDefinition | (() => Promise<{default: ComponentDefinition}>)} RouteComponent
268
467
  * A component that can be rendered for a route.
269
468
  * - `string`: Name of a registered component
270
469
  * - `ComponentDefinition`: Inline component definition
271
- * - `() => Promise<{default: ComponentDefinition}>`: Lazy-loaded component (e.g., `() => import('./Page.js')`)
470
+ * - `() => ComponentDefinition`: Factory function returning a component
471
+ * - `() => Promise<ComponentDefinition>`: Async factory function
472
+ * - `() => Promise<{default: ComponentDefinition}>`: Lazy-loaded module (e.g., `() => import('./Page.js')`)
473
+ * @typedef {string | ComponentDefinition | (() => ComponentDefinition | Promise<ComponentDefinition | {default: ComponentDefinition}>)} RouteComponent
272
474
  */ /**
475
+ * Defines a route in the application.
273
476
  * @typedef {Object} RouteDefinition
274
- * @property {string} path - URL path pattern. Supports:
275
- * - Static: `'/about'`
276
- * - Dynamic params: `'/users/:id'`
277
- * - Wildcard: `'*'` (catch-all, must be last)
278
- * @property {RouteComponent} component - The component to render for this route.
279
- * @property {RouteComponent} [layout] - Optional layout component to wrap the route component.
280
- * @property {string} [name] - Optional route name for programmatic navigation.
281
- * @property {RouteMeta} [meta] - Optional metadata (auth flags, titles, etc.).
282
- * @property {NavigationGuard} [beforeEnter] - Route-specific guard before entering.
283
- * @property {NavigationHook} [afterEnter] - Hook after entering and component is mounted.
284
- * @property {NavigationGuard} [beforeLeave] - Guard before leaving this route.
285
- * @property {NavigationHook} [afterLeave] - Hook after leaving and component is unmounted.
286
- * @property {RouteSegment[]} [segments] - Internal: parsed path segments (added by router).
477
+ * @property {string} path
478
+ * URL path pattern. Supports:
479
+ * - Static: '/about'
480
+ * - Dynamic params: '/users/:id'
481
+ * - Wildcard: '*' (catch-all, conventionally last)
482
+ * @property {RouteComponent} component
483
+ * The component to render for this route.
484
+ * @property {RouteComponent} [layout]
485
+ * Optional layout component to wrap the route component.
486
+ * @property {string} [name]
487
+ * Optional route name for programmatic navigation.
488
+ * @property {RouteMeta} [meta]
489
+ * Optional metadata (auth flags, titles, etc.).
490
+ * @property {NavigationGuard} [beforeEnter]
491
+ * Route-specific guard before entering.
492
+ * @property {NavigationHook} [afterEnter]
493
+ * Hook after entering and component is mounted.
494
+ * @property {NavigationGuard} [beforeLeave]
495
+ * Guard before leaving this route.
496
+ * @property {NavigationHook} [afterLeave]
497
+ * Hook after leaving and component is unmounted.
498
+ * @property {RouteSegment[]} [segments]
499
+ * Internal: parsed path segments (added by router).
287
500
  * @description Defines a route in the application.
501
+ * @note Nested routes are not supported. Use shared layouts with flat routes instead.
288
502
  * @example
289
503
  * // Static route
290
504
  * { path: '/about', component: AboutPage }
@@ -300,10 +514,10 @@
300
514
  * beforeEnter: (to, from) => isLoggedIn() || '/login'
301
515
  * }
302
516
  *
303
- * // Catch-all 404 route (must be last)
517
+ * // Catch-all 404 route (conventionally last)
304
518
  * { path: '*', component: NotFoundPage }
305
519
  */ /**
306
- * @class Router
520
+ * @class 🛤️ Router
307
521
  * @classdesc A powerful, reactive, and flexible Router Plugin for Eleva.
308
522
  * This class manages all routing logic, including state, navigation, and rendering.
309
523
  *
@@ -329,15 +543,15 @@
329
543
  * | `router:scroll` | {@link ScrollContextCallback} | No | For scroll behavior |
330
544
  * | `router:afterEnter` | {@link RouteChangeCallback} | No | After entering route |
331
545
  * | `router:afterEach` | {@link RouteChangeCallback} | No | Navigation complete |
332
- * | `router:onError` | {@link RouterErrorCallback} | No | Navigation error |
546
+ * | `router:error` | {@link RouterErrorCallback} | No | Navigation error |
333
547
  * | `router:routeAdded` | {@link RouteAddedCallback} | No | Dynamic route added |
334
548
  * | `router:routeRemoved` | {@link RouteRemovedCallback} | No | Dynamic route removed |
335
549
  *
336
550
  * ## Reactive Signals
337
551
  * - `currentRoute: Signal<RouteLocation | null>` - Current route info
338
552
  * - `previousRoute: Signal<RouteLocation | null>` - Previous route info
339
- * - `currentParams: Signal<Record<string, string>>` - Current route params
340
- * - `currentQuery: Signal<Record<string, string>>` - Current query params
553
+ * - `currentParams: Signal<RouteParams>` - Current route params
554
+ * - `currentQuery: Signal<QueryParams>` - Current query params
341
555
  * - `currentLayout: Signal<MountResult | null>` - Mounted layout instance
342
556
  * - `currentView: Signal<MountResult | null>` - Mounted view instance
343
557
  * - `isReady: Signal<boolean>` - Router readiness state
@@ -412,7 +626,7 @@
412
626
  * Parses a route path string into an array of static and parameter segments.
413
627
  * @private
414
628
  * @param {string} path - The path pattern to parse.
415
- * @returns {Array<{type: 'static' | 'param', value?: string, name?: string}>} An array of segment objects.
629
+ * @returns {{type: 'static' | 'param', value?: string, name?: string}[]} An array of segment objects.
416
630
  * @throws {Error} If the route path is not a valid string.
417
631
  */ _parsePathIntoSegments(path) {
418
632
  if (!path || typeof path !== "string") {
@@ -456,6 +670,11 @@
456
670
  /**
457
671
  * Starts the router, initializes event listeners, and performs the initial navigation.
458
672
  * @returns {Promise<Router>} The router instance for method chaining.
673
+ * @listens window:hashchange In hash mode, triggers route changes.
674
+ * @listens window:popstate In history/query mode, triggers route changes.
675
+ * @emits router:ready When initialization completes successfully.
676
+ * @see destroy - Stop the router and clean up listeners.
677
+ * @see navigate - Programmatically navigate to a route.
459
678
  *
460
679
  * @example
461
680
  * // Basic usage
@@ -500,8 +719,11 @@
500
719
  return this;
501
720
  }
502
721
  /**
503
- * Stops the router and cleans up all event listeners and mounted components.
722
+ * Stops the router and cleans up event listeners.
723
+ * Unmounts the current layout instance if present.
724
+ * @async
504
725
  * @returns {Promise<void>}
726
+ * @see start - Restart the router after destroying.
505
727
  */ async destroy() {
506
728
  if (!this.isStarted) return;
507
729
  // Clean up plugins
@@ -525,6 +747,7 @@
525
747
  /**
526
748
  * Alias for destroy(). Stops the router and cleans up all resources.
527
749
  * Provided for semantic consistency (start/stop pattern).
750
+ * @async
528
751
  * @returns {Promise<void>}
529
752
  *
530
753
  * @example
@@ -536,9 +759,13 @@
536
759
  }
537
760
  /**
538
761
  * Programmatically navigates to a new route.
762
+ * @async
539
763
  * @param {string | NavigationTarget} location - The target location as a path string or navigation target object.
540
- * @param {Record<string, string>} [params] - Route parameters (only used when location is a string).
764
+ * @param {NavigationParams} [params] - Route parameters (only used when location is a string).
541
765
  * @returns {Promise<boolean>} True if navigation succeeded, false if blocked by guards or failed.
766
+ * @emits router:error When navigation fails due to an exception.
767
+ * @see start - Initialize the router before navigating.
768
+ * @see currentRoute - Access the current route after navigation.
542
769
  *
543
770
  * @example
544
771
  * // Basic navigation
@@ -601,7 +828,7 @@
601
828
  return navigationSuccessful;
602
829
  } catch (error) {
603
830
  this.errorHandler.log("Navigation failed", error);
604
- await this.emitter.emit("router:onError", error);
831
+ await this.emitter.emit("router:error", error);
605
832
  return false;
606
833
  }
607
834
  }
@@ -621,7 +848,7 @@
621
848
  * @param {string} path - The target path with query string.
622
849
  * @param {object} params - The target params.
623
850
  * @param {object} query - The target query.
624
- * @returns {boolean} - True if the routes are the same.
851
+ * @returns {boolean} True if the routes are the same.
625
852
  */ _isSameRoute(path, params, query) {
626
853
  const current = this.currentRoute.value;
627
854
  if (!current) return false;
@@ -631,7 +858,16 @@
631
858
  }
632
859
  /**
633
860
  * Injects dynamic parameters into a path string.
861
+ * Replaces `:param` placeholders with URL-encoded values from the params object.
862
+ *
634
863
  * @private
864
+ * @param {string} path - The path pattern containing `:param` placeholders.
865
+ * @param {RouteParams} params - Key-value pairs to inject into the path.
866
+ * @returns {string} The path with all parameters replaced.
867
+ *
868
+ * @example
869
+ * this._buildPath('/users/:id/posts/:postId', { id: '123', postId: '456' });
870
+ * // Returns: '/users/123/posts/456'
635
871
  */ _buildPath(path, params) {
636
872
  let result = path;
637
873
  for (const [key, value] of Object.entries(params)){
@@ -643,8 +879,12 @@
643
879
  }
644
880
  /**
645
881
  * The handler for browser-initiated route changes (e.g., back/forward buttons).
882
+ *
646
883
  * @private
884
+ * @async
647
885
  * @param {boolean} [isPopState=true] - Whether this is a popstate event (back/forward navigation).
886
+ * @returns {Promise<void>}
887
+ * @emits router:error When route change handling fails.
648
888
  */ async _handleRouteChange(isPopState = true) {
649
889
  if (this._isNavigating) return;
650
890
  try {
@@ -663,26 +903,30 @@
663
903
  this.errorHandler.log("Route change handling failed", error, {
664
904
  currentUrl: typeof window !== "undefined" ? window.location.href : ""
665
905
  });
666
- await this.emitter.emit("router:onError", error);
906
+ await this.emitter.emit("router:error", error);
667
907
  }
668
908
  }
669
909
  /**
670
910
  * Manages the core navigation lifecycle. Runs guards before committing changes.
671
- * Emits lifecycle events that plugins can hook into:
672
- * - router:beforeEach - Before guards run (can block/redirect via context)
673
- * - router:beforeResolve - Before component resolution (can block/redirect)
674
- * - router:afterResolve - After components are resolved
675
- * - router:beforeRender - Before DOM rendering
676
- * - router:afterRender - After DOM rendering
677
- * - router:scroll - After render, for scroll behavior
678
- * - router:afterEnter - After entering a route
679
- * - router:afterLeave - After leaving a route
680
- * - router:afterEach - After navigation completes
681
911
  *
682
912
  * @private
913
+ * @async
683
914
  * @param {string} fullPath - The full path (e.g., '/users/123?foo=bar') to navigate to.
684
915
  * @param {boolean} [isPopState=false] - Whether this navigation was triggered by popstate (back/forward).
685
- * @returns {Promise<boolean>} - `true` if navigation succeeded, `false` if aborted.
916
+ * @returns {Promise<boolean>} `true` if navigation succeeded, `false` if aborted.
917
+ * @emits router:notFound When no matching route is found.
918
+ * @emits router:beforeResolve Before component resolution (can block/redirect).
919
+ * @emits router:afterResolve After components are resolved.
920
+ * @emits router:afterLeave After leaving the previous route.
921
+ * @emits router:beforeRender Before DOM rendering.
922
+ * @emits router:afterRender After DOM rendering completes.
923
+ * @emits router:scroll After render, for scroll behavior handling.
924
+ * @emits router:afterEnter After entering the new route.
925
+ * @emits router:afterEach After navigation completes successfully.
926
+ * @emits router:error When an error occurs during navigation.
927
+ * @see _runGuards - Guard execution.
928
+ * @see _resolveComponents - Component resolution.
929
+ * @see _render - DOM rendering.
686
930
  */ async _proceedWithNavigation(fullPath, isPopState = false) {
687
931
  const from = this.currentRoute.value;
688
932
  const [path, queryString] = (fullPath || "/").split("?");
@@ -702,7 +946,7 @@
702
946
  }
703
947
  };
704
948
  } else {
705
- await this.emitter.emit("router:onError", new Error(`Route not found: ${toLocation.path}`), toLocation, from);
949
+ await this.emitter.emit("router:error", new Error(`Route not found: ${toLocation.path}`), toLocation, from);
706
950
  return false;
707
951
  }
708
952
  }
@@ -789,7 +1033,7 @@
789
1033
  };
790
1034
  await this.emitter.emit("router:beforeRender", renderContext);
791
1035
  // 9. Render the new components.
792
- await this._render(layoutComponent, pageComponent, to);
1036
+ await this._render(layoutComponent, pageComponent);
793
1037
  // 10. Emit afterRender event - plugins can trigger animations
794
1038
  await this.emitter.emit("router:afterRender", renderContext);
795
1039
  // 11. Emit scroll event - plugins can handle scroll restoration
@@ -811,7 +1055,7 @@
811
1055
  to,
812
1056
  from
813
1057
  });
814
- await this.emitter.emit("router:onError", error, to, from);
1058
+ await this.emitter.emit("router:error", error, to, from);
815
1059
  return false;
816
1060
  }
817
1061
  }
@@ -827,7 +1071,8 @@
827
1071
  * @param {RouteLocation} to - The target route location.
828
1072
  * @param {RouteLocation | null} from - The current route location (null on initial navigation).
829
1073
  * @param {RouteDefinition} route - The matched route definition.
830
- * @returns {Promise<boolean>} - `false` if navigation should be aborted.
1074
+ * @returns {Promise<boolean>} `false` if navigation should be aborted.
1075
+ * @emits router:beforeEach Before guards run (can block/redirect via context).
831
1076
  */ async _runGuards(to, from, route) {
832
1077
  // Create navigation context that plugins can modify to block navigation
833
1078
  /** @type {NavigationContext} */ const navContext = {
@@ -888,7 +1133,8 @@
888
1133
  /**
889
1134
  * Resolves a function component definition to a component object.
890
1135
  * @private
891
- * @param {Function} def - The function to resolve.
1136
+ * @async
1137
+ * @param {() => ComponentDefinition | Promise<ComponentDefinition | { default: ComponentDefinition }>} def - The function to resolve.
892
1138
  * @returns {Promise<ComponentDefinition>} The resolved component.
893
1139
  * @throws {Error} If the function fails to load the component.
894
1140
  */ async _resolveFunctionComponent(def) {
@@ -907,7 +1153,7 @@
907
1153
  /**
908
1154
  * Validates a component definition object.
909
1155
  * @private
910
- * @param {any} def - The component definition to validate.
1156
+ * @param {unknown} def - The component definition to validate.
911
1157
  * @returns {ComponentDefinition} The validated component.
912
1158
  * @throws {Error} If the component definition is invalid.
913
1159
  */ _validateComponentDefinition(def) {
@@ -926,7 +1172,7 @@
926
1172
  /**
927
1173
  * Resolves a component definition to a component object.
928
1174
  * @private
929
- * @param {any} def - The component definition to resolve.
1175
+ * @param {unknown} def - The component definition to resolve.
930
1176
  * @returns {Promise<ComponentDefinition | null>} The resolved component or null.
931
1177
  */ async _resolveComponent(def) {
932
1178
  if (def === null || def === undefined) {
@@ -948,8 +1194,10 @@
948
1194
  /**
949
1195
  * Asynchronously resolves the layout and page components for a route.
950
1196
  * @private
1197
+ * @async
951
1198
  * @param {RouteDefinition} route - The route to resolve components for.
952
1199
  * @returns {Promise<{layoutComponent: ComponentDefinition | null, pageComponent: ComponentDefinition}>}
1200
+ * @throws {Error} If page component cannot be resolved.
953
1201
  */ async _resolveComponents(route) {
954
1202
  const effectiveLayout = route.layout || this.options.globalLayout;
955
1203
  try {
@@ -975,9 +1223,24 @@
975
1223
  }
976
1224
  /**
977
1225
  * Renders the components for the current route into the DOM.
1226
+ *
1227
+ * Rendering algorithm:
1228
+ * 1. Find the mount element using options.mount selector
1229
+ * 2. If layoutComponent exists:
1230
+ * a. Mount layout to mount element
1231
+ * b. Find view element within layout (using viewSelector)
1232
+ * c. Mount page component to view element
1233
+ * 3. If no layoutComponent:
1234
+ * a. Mount page component directly to mount element
1235
+ * b. Set currentLayout to null
1236
+ *
978
1237
  * @private
1238
+ * @async
979
1239
  * @param {ComponentDefinition | null} layoutComponent - The pre-loaded layout component.
980
1240
  * @param {ComponentDefinition} pageComponent - The pre-loaded page component.
1241
+ * @returns {Promise<void>}
1242
+ * @throws {Error} If mount element is not found in the DOM.
1243
+ * @throws {Error} If component mounting fails (propagated from eleva.mount).
981
1244
  */ async _render(layoutComponent, pageComponent) {
982
1245
  const mountEl = document.querySelector(this.options.mount);
983
1246
  if (!mountEl) {
@@ -1001,8 +1264,8 @@
1001
1264
  * Creates a getter function for router context properties.
1002
1265
  * @private
1003
1266
  * @param {string} property - The property name to access.
1004
- * @param {any} defaultValue - The default value if property is undefined.
1005
- * @returns {Function} A getter function.
1267
+ * @param {unknown} defaultValue - The default value if property is undefined.
1268
+ * @returns {() => unknown} A getter function.
1006
1269
  */ _createRouteGetter(property, defaultValue) {
1007
1270
  return ()=>this.currentRoute.value?.[property] ?? defaultValue;
1008
1271
  }
@@ -1017,7 +1280,7 @@
1017
1280
  return {
1018
1281
  ...component,
1019
1282
  async setup (ctx) {
1020
- ctx.router = {
1283
+ /** @type {RouterContext} */ ctx.router = {
1021
1284
  navigate: self.navigate.bind(self),
1022
1285
  current: self.currentRoute,
1023
1286
  previous: self.previousRoute,
@@ -1044,9 +1307,13 @@
1044
1307
  }
1045
1308
  /**
1046
1309
  * Recursively wraps all child components to ensure they have access to router context.
1310
+ * String component references are returned as-is (context injected during mount).
1311
+ * Objects are wrapped with router context and their children are recursively wrapped.
1312
+ *
1047
1313
  * @private
1048
1314
  * @param {ComponentDefinition | string} component - The component to wrap (can be a definition object or a registered component name).
1049
1315
  * @returns {ComponentDefinition | string} The wrapped component definition or the original string reference.
1316
+ * @see _wrapComponent - Single component wrapping.
1050
1317
  */ _wrapComponentWithChildren(component) {
1051
1318
  // If the component is a string (registered component name), return as-is
1052
1319
  // The router context will be injected when the component is resolved during mounting
@@ -1103,7 +1370,15 @@
1103
1370
  }
1104
1371
  /**
1105
1372
  * Parses a query string into a key-value object.
1373
+ * Uses URLSearchParams for robust parsing of encoded values.
1374
+ *
1106
1375
  * @private
1376
+ * @param {string} queryString - The query string to parse (without leading '?').
1377
+ * @returns {QueryParams} Key-value pairs from the query string.
1378
+ *
1379
+ * @example
1380
+ * this._parseQuery('foo=bar&baz=qux');
1381
+ * // Returns: { foo: 'bar', baz: 'qux' }
1107
1382
  */ _parseQuery(queryString) {
1108
1383
  const query = {};
1109
1384
  if (queryString) {
@@ -1155,10 +1430,12 @@
1155
1430
  /**
1156
1431
  * Adds a new route dynamically at runtime.
1157
1432
  * The route will be processed and available for navigation immediately.
1433
+ * Routes are inserted before the wildcard (*) route if one exists.
1158
1434
  *
1159
1435
  * @param {RouteDefinition} route - The route definition to add.
1160
1436
  * @param {RouteDefinition} [parentRoute] - Optional parent route to add as a child (not yet implemented).
1161
- * @returns {() => void} A function to remove the added route.
1437
+ * @returns {() => void} A function to remove the added route (returns no-op if route was invalid).
1438
+ * @emits router:routeAdded When a route is successfully added.
1162
1439
  *
1163
1440
  * @example
1164
1441
  * // Add a route dynamically
@@ -1206,6 +1483,7 @@
1206
1483
  *
1207
1484
  * @param {string} path - The path of the route to remove.
1208
1485
  * @returns {boolean} True if the route was removed, false if not found.
1486
+ * @emits router:routeRemoved When a route is successfully removed.
1209
1487
  *
1210
1488
  * @example
1211
1489
  * router.removeRoute('/dynamic');
@@ -1293,6 +1571,7 @@
1293
1571
  * Registers a global hook that runs after a new route component has been mounted.
1294
1572
  * @param {NavigationHook} hook - The hook function to register.
1295
1573
  * @returns {() => void} A function to unregister the hook.
1574
+ * @listens router:afterEnter
1296
1575
  */ onAfterEnter(hook) {
1297
1576
  return this.emitter.on("router:afterEnter", hook);
1298
1577
  }
@@ -1300,6 +1579,7 @@
1300
1579
  * Registers a global hook that runs after a route component has been unmounted.
1301
1580
  * @param {NavigationHook} hook - The hook function to register.
1302
1581
  * @returns {() => void} A function to unregister the hook.
1582
+ * @listens router:afterLeave
1303
1583
  */ onAfterLeave(hook) {
1304
1584
  return this.emitter.on("router:afterLeave", hook);
1305
1585
  }
@@ -1307,6 +1587,7 @@
1307
1587
  * Registers a global hook that runs after a navigation has been confirmed and all hooks have completed.
1308
1588
  * @param {NavigationHook} hook - The hook function to register.
1309
1589
  * @returns {() => void} A function to unregister the hook.
1590
+ * @listens router:afterEach
1310
1591
  */ onAfterEach(hook) {
1311
1592
  return this.emitter.on("router:afterEach", hook);
1312
1593
  }
@@ -1314,12 +1595,18 @@
1314
1595
  * Registers a global error handler for navigation errors.
1315
1596
  * @param {(error: Error, to?: RouteLocation, from?: RouteLocation) => void} handler - The error handler function.
1316
1597
  * @returns {() => void} A function to unregister the handler.
1598
+ * @listens router:error
1317
1599
  */ onError(handler) {
1318
- return this.emitter.on("router:onError", handler);
1600
+ return this.emitter.on("router:error", handler);
1319
1601
  }
1320
1602
  /**
1321
1603
  * Registers a plugin with the router.
1322
- * @param {RouterPlugin} plugin - The plugin to register.
1604
+ * Logs a warning if the plugin is already registered.
1605
+ *
1606
+ * @param {RouterPlugin} plugin - The plugin to register (must have install method).
1607
+ * @param {Record<string, unknown>} [options={}] - Options to pass to plugin.install().
1608
+ * @returns {void}
1609
+ * @throws {Error} If plugin does not have an install method.
1323
1610
  */ use(plugin, options = {}) {
1324
1611
  if (typeof plugin.install !== "function") {
1325
1612
  this.errorHandler.handle(new Error("Plugin must have an install method"), "Plugin registration failed", {
@@ -1368,7 +1655,9 @@
1368
1655
  }
1369
1656
  /**
1370
1657
  * Sets a custom error handler. Used by error handling plugins.
1371
- * @param {Object} errorHandler - The error handler object with handle, warn, and log methods.
1658
+ * Logs a warning if the provided handler is invalid (missing required methods).
1659
+ * @param {RouterErrorHandler} errorHandler - The error handler object with handle, warn, and log methods.
1660
+ * @returns {void}
1372
1661
  */ setErrorHandler(errorHandler) {
1373
1662
  if (errorHandler && typeof errorHandler.handle === "function" && typeof errorHandler.warn === "function" && typeof errorHandler.log === "function") {
1374
1663
  this.errorHandler = errorHandler;
@@ -1380,6 +1669,7 @@
1380
1669
  * Creates an instance of the Router.
1381
1670
  * @param {Eleva} eleva - The Eleva framework instance.
1382
1671
  * @param {RouterOptions} options - The configuration options for the router.
1672
+ * @throws {Error} If the routing mode is invalid.
1383
1673
  */ constructor(eleva, options = {}){
1384
1674
  /** @type {Eleva} The Eleva framework instance. */ this.eleva = eleva;
1385
1675
  /** @type {RouterOptions} The merged router options. */ this.options = {
@@ -1389,40 +1679,30 @@
1389
1679
  ...options
1390
1680
  };
1391
1681
  /** @private @type {RouteDefinition[]} The processed list of route definitions. */ this.routes = this._processRoutes(options.routes || []);
1392
- /** @private @type {import('eleva').Emitter} The shared Eleva event emitter for global hooks. */ this.emitter = this.eleva.emitter;
1682
+ /** @private @type {Emitter} The shared Eleva event emitter for global hooks. */ this.emitter = this.eleva.emitter;
1393
1683
  /** @private @type {boolean} A flag indicating if the router has been started. */ this.isStarted = false;
1394
1684
  /** @private @type {boolean} A flag to prevent navigation loops from history events. */ this._isNavigating = false;
1395
1685
  /** @private @type {number} Counter for tracking navigation operations to prevent race conditions. */ this._navigationId = 0;
1396
- /** @private @type {Array<() => void>} A collection of cleanup functions for event listeners. */ this.eventListeners = [];
1686
+ /** @private @type {UnsubscribeFunction[]} A collection of cleanup functions for event listeners. */ this.eventListeners = [];
1397
1687
  /** @type {Signal<RouteLocation | null>} A reactive signal holding the current route's information. */ this.currentRoute = new this.eleva.signal(null);
1398
1688
  /** @type {Signal<RouteLocation | null>} A reactive signal holding the previous route's information. */ this.previousRoute = new this.eleva.signal(null);
1399
- /** @type {Signal<Object<string, string>>} A reactive signal holding the current route's parameters. */ this.currentParams = new this.eleva.signal({});
1400
- /** @type {Signal<Object<string, string>>} A reactive signal holding the current route's query parameters. */ this.currentQuery = new this.eleva.signal({});
1401
- /** @type {Signal<import('eleva').MountResult | null>} A reactive signal for the currently mounted layout instance. */ this.currentLayout = new this.eleva.signal(null);
1402
- /** @type {Signal<import('eleva').MountResult | null>} A reactive signal for the currently mounted view (page) instance. */ this.currentView = new this.eleva.signal(null);
1689
+ /** @type {Signal<RouteParams>} A reactive signal holding the current route's parameters. */ this.currentParams = new this.eleva.signal({});
1690
+ /** @type {Signal<QueryParams>} A reactive signal holding the current route's query parameters. */ this.currentQuery = new this.eleva.signal({});
1691
+ /** @type {Signal<MountResult | null>} A reactive signal for the currently mounted layout instance. */ this.currentLayout = new this.eleva.signal(null);
1692
+ /** @type {Signal<MountResult | null>} A reactive signal for the currently mounted view (page) instance. */ this.currentView = new this.eleva.signal(null);
1403
1693
  /** @type {Signal<boolean>} A reactive signal indicating if the router is ready (started and initial navigation complete). */ this.isReady = new this.eleva.signal(false);
1404
1694
  /** @private @type {Map<string, RouterPlugin>} Map of registered plugins by name. */ this.plugins = new Map();
1405
- /** @private @type {Array<NavigationGuard>} Array of global before-each navigation guards. */ this._beforeEachGuards = [];
1695
+ /** @private @type {NavigationGuard[]} Array of global before-each navigation guards. */ this._beforeEachGuards = [];
1406
1696
  // If onBeforeEach was provided in options, add it to the guards array
1407
1697
  if (options.onBeforeEach) {
1408
1698
  this._beforeEachGuards.push(options.onBeforeEach);
1409
1699
  }
1410
- /** @type {Object} The error handler instance. Can be overridden by plugins. */ this.errorHandler = CoreErrorHandler;
1700
+ /** @type {RouterErrorHandler} The error handler instance. Can be overridden by plugins. */ this.errorHandler = CoreErrorHandler;
1411
1701
  /** @private @type {Map<string, {x: number, y: number}>} Saved scroll positions by route path. */ this._scrollPositions = new Map();
1412
1702
  this._validateOptions();
1413
1703
  }
1414
1704
  }
1415
1705
  /**
1416
- * @typedef {Object} RouterOptions
1417
- * @property {string} mount - A CSS selector for the main element where the app is mounted.
1418
- * @property {RouteDefinition[]} routes - An array of route definitions.
1419
- * @property {'hash' | 'query' | 'history'} [mode='hash'] - The routing mode.
1420
- * @property {string} [queryParam='page'] - The query parameter to use in 'query' mode.
1421
- * @property {string} [viewSelector='view'] - The selector for the view element within a layout.
1422
- * @property {boolean} [autoStart=true] - Whether to start the router automatically.
1423
- * @property {NavigationGuard} [onBeforeEach] - A global guard executed before every navigation.
1424
- * @property {string | ComponentDefinition | (() => Promise<{default: ComponentDefinition}>)} [globalLayout] - A global layout for all routes. Can be overridden by a route's specific layout.
1425
- */ /**
1426
1706
  * @class 🚀 RouterPlugin
1427
1707
  * @classdesc A powerful, reactive, and flexible Router Plugin for Eleva applications.
1428
1708
  * This plugin provides comprehensive client-side routing functionality including:
@@ -1461,7 +1741,7 @@
1461
1741
  /**
1462
1742
  * Plugin version
1463
1743
  * @type {string}
1464
- */ version: "1.0.1",
1744
+ */ version: "1.1.0",
1465
1745
  /**
1466
1746
  * Plugin description
1467
1747
  * @type {string}
@@ -1469,16 +1749,25 @@
1469
1749
  /**
1470
1750
  * Installs the RouterPlugin into an Eleva instance.
1471
1751
  *
1472
- * @param {Eleva} eleva - The Eleva instance
1473
- * @param {RouterOptions} options - Router configuration options
1474
- * @param {string} options.mount - A CSS selector for the main element where the app is mounted
1475
- * @param {RouteDefinition[]} options.routes - An array of route definitions
1476
- * @param {'hash' | 'query' | 'history'} [options.mode='hash'] - The routing mode
1477
- * @param {string} [options.queryParam='page'] - The query parameter to use in 'query' mode
1478
- * @param {string} [options.viewSelector='view'] - The selector for the view element within a layout
1479
- * @param {boolean} [options.autoStart=true] - Whether to start the router automatically
1480
- * @param {NavigationGuard} [options.onBeforeEach] - A global guard executed before every navigation
1481
- * @param {string | ComponentDefinition | (() => Promise<{default: ComponentDefinition}>)} [options.globalLayout] - A global layout for all routes
1752
+ * @public
1753
+ * @param {Eleva} eleva - The Eleva instance.
1754
+ * @param {RouterOptions} options - Router configuration options.
1755
+ * @param {string} options.mount - A CSS selector for the main element where the app is mounted.
1756
+ * @param {RouteDefinition[]} options.routes - An array of route definitions.
1757
+ * @param {'hash' | 'query' | 'history'} [options.mode='hash'] - The routing mode.
1758
+ * @param {string} [options.queryParam='view'] - The query parameter to use in 'query' mode.
1759
+ * @param {string} [options.viewSelector='view'] - Base selector for the view element (matched as #id, .class, [data-*], or raw selector).
1760
+ * @param {boolean} [options.autoStart=true] - Whether to start the router automatically.
1761
+ * @param {NavigationGuard} [options.onBeforeEach] - A global guard executed before every navigation.
1762
+ * @param {RouteComponent} [options.globalLayout] - A global layout for all routes.
1763
+ * @returns {Router} The created router instance.
1764
+ * @throws {Error} If 'mount' option is not provided.
1765
+ * @throws {Error} If 'routes' option is not an array.
1766
+ * @throws {Error} If component registration fails during route processing.
1767
+ * @description
1768
+ * Registers route/layout components, sets `eleva.router`, and adds helpers
1769
+ * (`eleva.navigate`, `eleva.getCurrentRoute`, `eleva.getRouteParams`, `eleva.getRouteQuery`).
1770
+ * When `autoStart` is enabled, startup is scheduled via microtask.
1482
1771
  *
1483
1772
  * @example
1484
1773
  * // main.js
@@ -1508,9 +1797,10 @@
1508
1797
  * Registers a component definition with the Eleva instance.
1509
1798
  * This method handles both inline component objects and pre-registered component names.
1510
1799
  *
1511
- * @param {any} def - The component definition to register
1512
- * @param {string} type - The type of component for naming (e.g., "Route", "Layout")
1513
- * @returns {string | null} The registered component name or null if no definition provided
1800
+ * @inner
1801
+ * @param {unknown} def - The component definition to register.
1802
+ * @param {string} type - The type of component for naming (e.g., "Route", "Layout").
1803
+ * @returns {string | null} The registered component name or null if no definition provided.
1514
1804
  */ const register = (def, type)=>{
1515
1805
  if (!def) return null;
1516
1806
  if (typeof def === "object" && def !== null && !def.name) {
@@ -1534,7 +1824,7 @@
1534
1824
  }
1535
1825
  });
1536
1826
  const router = new Router(eleva, options);
1537
- eleva.router = router;
1827
+ /** @type {Router} */ eleva.router = router;
1538
1828
  if (options.autoStart !== false) {
1539
1829
  queueMicrotask(()=>router.start());
1540
1830
  }
@@ -1549,16 +1839,22 @@
1549
1839
  options
1550
1840
  });
1551
1841
  // Add utility methods for manual router access
1552
- eleva.navigate = router.navigate.bind(router);
1553
- eleva.getCurrentRoute = ()=>router.currentRoute.value;
1554
- eleva.getRouteParams = ()=>router.currentParams.value;
1555
- eleva.getRouteQuery = ()=>router.currentQuery.value;
1842
+ /** @type {NavigateFunction} */ eleva.navigate = router.navigate.bind(router);
1843
+ /** @type {() => RouteLocation | null} */ eleva.getCurrentRoute = ()=>router.currentRoute.value;
1844
+ /** @type {() => RouteParams} */ eleva.getRouteParams = ()=>router.currentParams.value;
1845
+ /** @type {() => QueryParams} */ eleva.getRouteQuery = ()=>router.currentQuery.value;
1556
1846
  return router;
1557
1847
  },
1558
1848
  /**
1559
- * Uninstalls the plugin from the Eleva instance
1849
+ * Uninstalls the plugin from the Eleva instance.
1560
1850
  *
1561
- * @param {Eleva} eleva - The Eleva instance
1851
+ * @public
1852
+ * @async
1853
+ * @param {Eleva} eleva - The Eleva instance.
1854
+ * @returns {Promise<void>}
1855
+ * @description
1856
+ * Destroys the router instance, removes `eleva.router`, and deletes helper methods
1857
+ * (`eleva.navigate`, `eleva.getCurrentRoute`, `eleva.getRouteParams`, `eleva.getRouteQuery`).
1562
1858
  */ async uninstall (eleva) {
1563
1859
  if (eleva.router) {
1564
1860
  await eleva.router.destroy();