@inglorious/web 3.0.1 → 4.0.1
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 +29 -30
- package/package.json +5 -4
- package/src/mount.js +12 -12
- package/src/router/index.js +145 -120
- package/types/router.d.ts +56 -12
package/README.md
CHANGED
|
@@ -367,12 +367,12 @@ No additional configuration is needed.
|
|
|
367
367
|
|
|
368
368
|
### 1. Setup the Router
|
|
369
369
|
|
|
370
|
-
To enable the router, add it to your store's types and create a `router` entity.
|
|
370
|
+
To enable the router, add it to your store's types and create a `router` entity. Register route patterns using the router module helpers (`setRoutes`, `addRoute`) — routes are configured at module level and not stored on the router entity itself.
|
|
371
371
|
|
|
372
372
|
```javascript
|
|
373
373
|
// store.js
|
|
374
374
|
import { createStore, html } from "@inglorious/web"
|
|
375
|
-
import { router } from "@inglorious/web/router"
|
|
375
|
+
import { router, setRoutes } from "@inglorious/web/router"
|
|
376
376
|
|
|
377
377
|
const types = {
|
|
378
378
|
// 1. Add the router type to your store's types
|
|
@@ -395,14 +395,9 @@ const types = {
|
|
|
395
395
|
}
|
|
396
396
|
|
|
397
397
|
const entities = {
|
|
398
|
-
// 3. Create the router entity
|
|
398
|
+
// 3. Create the router entity (no `routes` here)
|
|
399
399
|
router: {
|
|
400
400
|
type: "router",
|
|
401
|
-
routes: {
|
|
402
|
-
"/": "homePage",
|
|
403
|
-
"/users/:id": "userPage",
|
|
404
|
-
"*": "notFoundPage", // Fallback for unmatched routes
|
|
405
|
-
},
|
|
406
401
|
},
|
|
407
402
|
userPage: {
|
|
408
403
|
type: "userPage",
|
|
@@ -411,6 +406,13 @@ const entities = {
|
|
|
411
406
|
}
|
|
412
407
|
|
|
413
408
|
export const store = createStore({ types, entities })
|
|
409
|
+
|
|
410
|
+
// Register routes at module level
|
|
411
|
+
setRoutes({
|
|
412
|
+
"/": "homePage",
|
|
413
|
+
"/users/:id": "userPage",
|
|
414
|
+
"*": "notFoundPage",
|
|
415
|
+
})
|
|
414
416
|
```
|
|
415
417
|
|
|
416
418
|
### 2. Render the Current Route
|
|
@@ -456,22 +458,23 @@ api.notify("navigate", {
|
|
|
456
458
|
|
|
457
459
|
### 4. Lazy Loading Routes
|
|
458
460
|
|
|
459
|
-
You can improve performance by lazy-loading routes.
|
|
461
|
+
You can improve performance by lazy-loading routes. Use a loader function that returns a dynamic import when registering the route via `setRoutes`.
|
|
460
462
|
|
|
461
463
|
**Note:** The imported module must use a named export for the entity type (not `export default`), so the router can register it with a unique name in the store.
|
|
462
464
|
|
|
463
465
|
```javascript
|
|
464
466
|
// store.js
|
|
465
467
|
const entities = {
|
|
466
|
-
router: {
|
|
467
|
-
type: "router",
|
|
468
|
-
routes: {
|
|
469
|
-
"/": "homePage",
|
|
470
|
-
// Lazy load: returns a Promise resolving to a module
|
|
471
|
-
"/admin": () => import("./pages/admin.js"),
|
|
472
|
-
},
|
|
473
|
-
},
|
|
468
|
+
router: { type: "router" },
|
|
474
469
|
}
|
|
470
|
+
|
|
471
|
+
export const store = createStore({ types, entities })
|
|
472
|
+
|
|
473
|
+
setRoutes({
|
|
474
|
+
"/": "homePage",
|
|
475
|
+
// Lazy load: returns a Promise resolving to a module
|
|
476
|
+
"/admin": () => import("./pages/admin.js"),
|
|
477
|
+
})
|
|
475
478
|
```
|
|
476
479
|
|
|
477
480
|
```javascript
|
|
@@ -538,22 +541,18 @@ const types = {
|
|
|
538
541
|
}
|
|
539
542
|
|
|
540
543
|
const entities = {
|
|
541
|
-
router: {
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
"/login": "loginPage",
|
|
545
|
-
"/admin": "adminPage",
|
|
546
|
-
},
|
|
547
|
-
},
|
|
548
|
-
adminPage: {
|
|
549
|
-
type: "adminPage",
|
|
550
|
-
},
|
|
551
|
-
loginPage: {
|
|
552
|
-
type: "loginPage",
|
|
553
|
-
},
|
|
544
|
+
router: { type: "router" },
|
|
545
|
+
adminPage: { type: "adminPage" },
|
|
546
|
+
loginPage: { type: "loginPage" },
|
|
554
547
|
}
|
|
555
548
|
|
|
556
549
|
export const store = createStore({ types, entities })
|
|
550
|
+
|
|
551
|
+
// Register routes via the router module API
|
|
552
|
+
setRoutes({
|
|
553
|
+
"/login": "loginPage",
|
|
554
|
+
"/admin": "adminPage",
|
|
555
|
+
})
|
|
557
556
|
```
|
|
558
557
|
|
|
559
558
|
#### How Type Composition Works
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inglorious/web",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.1",
|
|
4
4
|
"description": "A new web framework that leverages the power of the Inglorious Store combined with the performance and simplicity of lit-html.",
|
|
5
5
|
"author": "IceOnFire <antony.mistretta@gmail.com> (https://ingloriouscoderz.it)",
|
|
6
6
|
"license": "MIT",
|
|
@@ -53,8 +53,8 @@
|
|
|
53
53
|
},
|
|
54
54
|
"files": [
|
|
55
55
|
"src",
|
|
56
|
-
"
|
|
57
|
-
"
|
|
56
|
+
"types",
|
|
57
|
+
"!src/**/*.test.js"
|
|
58
58
|
],
|
|
59
59
|
"publishConfig": {
|
|
60
60
|
"access": "public"
|
|
@@ -63,8 +63,9 @@
|
|
|
63
63
|
"**/*.css"
|
|
64
64
|
],
|
|
65
65
|
"dependencies": {
|
|
66
|
+
"@lit-labs/ssr-client": "^1.1.8",
|
|
66
67
|
"lit-html": "^3.3.1",
|
|
67
|
-
"@inglorious/store": "
|
|
68
|
+
"@inglorious/store": "9.0.0",
|
|
68
69
|
"@inglorious/utils": "3.7.1"
|
|
69
70
|
},
|
|
70
71
|
"devDependencies": {
|
package/src/mount.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { hydrate } from "@lit-labs/ssr-client"
|
|
1
2
|
import { html, render } from "lit-html"
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -11,20 +12,19 @@ export function mount(store, renderFn, element) {
|
|
|
11
12
|
const api = { ...store._api }
|
|
12
13
|
api.render = createRender(api)
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
let shouldHydrate = element.hasChildNodes()
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
17
|
+
const unsubscribe = store.subscribe(() => {
|
|
18
|
+
const template = renderFn(api)
|
|
19
|
+
|
|
20
|
+
if (shouldHydrate) {
|
|
21
|
+
hydrate(template, element)
|
|
22
|
+
shouldHydrate = false
|
|
23
|
+
} else {
|
|
24
|
+
render(template, element)
|
|
25
|
+
}
|
|
26
|
+
})
|
|
25
27
|
|
|
26
|
-
// const unsubscribe = store.subscribe(scheduleRender)
|
|
27
|
-
const unsubscribe = store.subscribe(() => render(renderFn(api), element))
|
|
28
28
|
store.notify("init")
|
|
29
29
|
|
|
30
30
|
return unsubscribe
|
package/src/router/index.js
CHANGED
|
@@ -7,6 +7,18 @@
|
|
|
7
7
|
const SKIP_FULL_MATCH_GROUP = 1 // .match() result at index 0 is the full string
|
|
8
8
|
const REMOVE_COLON_PREFIX = 1
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Route configuration map. Keys are route patterns (e.g. `/users/:id`) and
|
|
12
|
+
* values are either a string type name or a loader function that returns a
|
|
13
|
+
* module exporting a type.
|
|
14
|
+
* @type {Record<string, string|function>}
|
|
15
|
+
*/
|
|
16
|
+
const routeConfig = {}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Guard to ensure global listeners are only attached once.
|
|
20
|
+
* @type {boolean}
|
|
21
|
+
*/
|
|
10
22
|
let areListenersInitialized = false
|
|
11
23
|
|
|
12
24
|
/**
|
|
@@ -25,7 +37,7 @@ export const router = {
|
|
|
25
37
|
// Handle initial route
|
|
26
38
|
const { pathname, search } = window.location
|
|
27
39
|
const initialPath = pathname + search
|
|
28
|
-
const route = findRoute(
|
|
40
|
+
const route = findRoute(routeConfig, initialPath)
|
|
29
41
|
const entityId = entity.id
|
|
30
42
|
|
|
31
43
|
if (route) {
|
|
@@ -40,19 +52,9 @@ export const router = {
|
|
|
40
52
|
areListenersInitialized = true
|
|
41
53
|
|
|
42
54
|
// Listen for browser back/forward
|
|
43
|
-
window.addEventListener("popstate", () =>
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const route = findRoute(routes, path)
|
|
47
|
-
|
|
48
|
-
if (route) {
|
|
49
|
-
api.notify(`#${entityId}:routeSync`, {
|
|
50
|
-
entityType: route.entityType,
|
|
51
|
-
path,
|
|
52
|
-
params: route.params,
|
|
53
|
-
})
|
|
54
|
-
}
|
|
55
|
-
})
|
|
55
|
+
window.addEventListener("popstate", () =>
|
|
56
|
+
api.notify(`#${entityId}:popstate`, payload),
|
|
57
|
+
)
|
|
56
58
|
|
|
57
59
|
// Intercept link clicks
|
|
58
60
|
document.addEventListener("click", (event) => {
|
|
@@ -79,20 +81,42 @@ export const router = {
|
|
|
79
81
|
event.preventDefault()
|
|
80
82
|
|
|
81
83
|
const path = link.pathname + link.search + link.hash
|
|
82
|
-
api.notify(
|
|
84
|
+
api.notify(`#${entityId}:navigate`, path)
|
|
83
85
|
})
|
|
84
86
|
},
|
|
85
87
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
88
|
+
/**
|
|
89
|
+
* Handles browser `popstate` events.
|
|
90
|
+
* Attempts to match the current location to a route and updates the router
|
|
91
|
+
* entity state. If the matched route is lazy (a loader function) it will
|
|
92
|
+
* attempt to load the module first.
|
|
93
|
+
* @param {RouterEntity} entity
|
|
94
|
+
* @param {Object} payload
|
|
95
|
+
* @param {Api} api
|
|
96
|
+
*/
|
|
97
|
+
async popstate(entity, payload, api) {
|
|
98
|
+
const path = window.location.pathname + window.location.search
|
|
99
|
+
const route = findRoute(routeConfig, path)
|
|
100
|
+
const entityId = entity.id
|
|
89
101
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
102
|
+
if (route) {
|
|
103
|
+
if (typeof route.entityType === "function") {
|
|
104
|
+
entity.isLoading = true
|
|
105
|
+
entity.error = null
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
const module = await route.entityType()
|
|
109
|
+
api.notify(`#${entityId}:routeLoadSuccess`, { module, route })
|
|
110
|
+
api.notify(`#${entityId}:popstate`, payload)
|
|
111
|
+
} catch (error) {
|
|
112
|
+
api.notify(`#${entityId}:routeLoadError`, { error, path })
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return
|
|
116
|
+
}
|
|
93
117
|
|
|
94
|
-
|
|
95
|
-
|
|
118
|
+
updateRouter(entity, route)
|
|
119
|
+
}
|
|
96
120
|
},
|
|
97
121
|
|
|
98
122
|
/**
|
|
@@ -122,9 +146,10 @@ export const router = {
|
|
|
122
146
|
}
|
|
123
147
|
|
|
124
148
|
// If "to" is already a final path (like "/users/1"), use it directly
|
|
125
|
-
// The router will match it against patterns in
|
|
149
|
+
// The router will match it against patterns in routeConfig
|
|
126
150
|
|
|
127
|
-
const route = findRoute(
|
|
151
|
+
const route = findRoute(routeConfig, path)
|
|
152
|
+
const entityId = entity.id
|
|
128
153
|
|
|
129
154
|
if (!route) {
|
|
130
155
|
console.warn(`No route matches path: ${path}`)
|
|
@@ -142,64 +167,57 @@ export const router = {
|
|
|
142
167
|
|
|
143
168
|
// Asynchronous navigation
|
|
144
169
|
if (typeof route.entityType === "function") {
|
|
145
|
-
entity.
|
|
170
|
+
entity.isLoading = true
|
|
146
171
|
entity.error = null
|
|
147
|
-
const entityId = entity.id
|
|
148
172
|
|
|
149
173
|
try {
|
|
150
174
|
const module = await route.entityType()
|
|
151
|
-
api.notify(`#${entityId}:
|
|
152
|
-
|
|
153
|
-
route,
|
|
154
|
-
path,
|
|
155
|
-
replace,
|
|
156
|
-
state,
|
|
157
|
-
})
|
|
175
|
+
api.notify(`#${entityId}:routeLoadSuccess`, { module, route })
|
|
176
|
+
api.notify(`#${entityId}:navigate`, payload)
|
|
158
177
|
} catch (error) {
|
|
159
|
-
api.notify(`#${entityId}:
|
|
178
|
+
api.notify(`#${entityId}:routeLoadError`, { error, path })
|
|
160
179
|
}
|
|
161
180
|
|
|
162
181
|
return
|
|
163
182
|
}
|
|
164
183
|
|
|
165
|
-
|
|
184
|
+
updateRouter(entity, route)
|
|
166
185
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
186
|
+
// Prepare history state
|
|
187
|
+
const historyState = {
|
|
188
|
+
...state,
|
|
189
|
+
route: entity.route,
|
|
190
|
+
params: entity.params,
|
|
191
|
+
query: entity.query,
|
|
192
|
+
path: entity.path,
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Navigate
|
|
196
|
+
const method = replace ? "replaceState" : "pushState"
|
|
197
|
+
history[method](historyState, "", path)
|
|
198
|
+
|
|
199
|
+
api.notify("routeChange", historyState)
|
|
178
200
|
},
|
|
179
201
|
|
|
180
202
|
/**
|
|
181
|
-
* Handles
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
* @param {
|
|
203
|
+
* Handles successful loading of a lazily-loaded route module.
|
|
204
|
+
* Registers the loaded type in the runtime type registry via `api.setType`
|
|
205
|
+
* and updates the `routeConfig` entry for the pattern.
|
|
206
|
+
* @param {RouterEntity} entity
|
|
207
|
+
* @param {{module: object, route: {pattern: string, entityType: string}}} payload
|
|
208
|
+
* @param {Api} api
|
|
185
209
|
*/
|
|
186
|
-
|
|
187
|
-
const { module, route
|
|
188
|
-
|
|
189
|
-
const [[typeName, type]] = Object.entries(module)
|
|
190
|
-
|
|
191
|
-
api.notify("morph", { name: typeName, type })
|
|
210
|
+
routeLoadSuccess(entity, payload, api) {
|
|
211
|
+
const { module, route } = payload
|
|
192
212
|
|
|
193
|
-
|
|
213
|
+
const [typeName, type] = Object.entries(module).find(
|
|
214
|
+
([, type]) => type?.render,
|
|
215
|
+
)
|
|
194
216
|
|
|
195
|
-
|
|
196
|
-
|
|
217
|
+
api.setType(typeName, type)
|
|
218
|
+
routeConfig[route.pattern] = typeName
|
|
197
219
|
|
|
198
|
-
|
|
199
|
-
entity,
|
|
200
|
-
{ entityType: typeName, path, params: route.params, replace, state },
|
|
201
|
-
api,
|
|
202
|
-
)
|
|
220
|
+
entity.isLoading = false
|
|
203
221
|
},
|
|
204
222
|
|
|
205
223
|
/**
|
|
@@ -207,22 +225,56 @@ export const router = {
|
|
|
207
225
|
* @param {RouterEntity} entity - The router entity.
|
|
208
226
|
* @param {{error: Error, path: string}} payload - The error payload.
|
|
209
227
|
*/
|
|
210
|
-
|
|
228
|
+
routeLoadError(entity, payload) {
|
|
211
229
|
const { error, path } = payload
|
|
212
230
|
console.error(`Failed to load route ${path}:`, error)
|
|
213
231
|
entity.path = path
|
|
214
|
-
entity.
|
|
232
|
+
entity.isLoading = false
|
|
215
233
|
entity.error = error
|
|
216
234
|
},
|
|
235
|
+
}
|
|
217
236
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
237
|
+
/**
|
|
238
|
+
* Retrieves the current route configuration.
|
|
239
|
+
* @returns {Record<string, string|function>} The current route configuration.
|
|
240
|
+
*/
|
|
241
|
+
export function getRoutes() {
|
|
242
|
+
return routeConfig
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Retrieves a single route configuration given its path.
|
|
247
|
+
* @param {string} path - The path of the route to retrieve.
|
|
248
|
+
* @returns {string|function|undefined} The route configuration or undefined if not found.
|
|
249
|
+
*/
|
|
250
|
+
export function getRoute(path) {
|
|
251
|
+
return routeConfig[path]
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Sets or updates routes in the route configuration.
|
|
256
|
+
* Can be used both during initialization and at any point to add or update routes dynamically.
|
|
257
|
+
* @param {Record<string, string|function>} routes - An object mapping route paths/patterns to entity type names or loader functions.
|
|
258
|
+
*/
|
|
259
|
+
export function setRoutes(routes) {
|
|
260
|
+
Object.assign(routeConfig, routes)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Adds a single route to the route configuration.
|
|
265
|
+
* @param {string} path - The route path or pattern (e.g., "/users/:userId").
|
|
266
|
+
* @param {string|function} route - The entity type name or a function that dynamically loads it.
|
|
267
|
+
*/
|
|
268
|
+
export function addRoute(path, route) {
|
|
269
|
+
routeConfig[path] = route
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Removes a route from the route configuration.
|
|
274
|
+
* @param {string} path - The route path or pattern to remove.
|
|
275
|
+
*/
|
|
276
|
+
export function removeRoute(path) {
|
|
277
|
+
delete routeConfig[path]
|
|
226
278
|
}
|
|
227
279
|
|
|
228
280
|
/**
|
|
@@ -244,25 +296,30 @@ function buildPath(pattern, params = {}) {
|
|
|
244
296
|
/**
|
|
245
297
|
* Finds a matching route configuration for a given URL path.
|
|
246
298
|
* It supports parameterized routes and a fallback "*" route.
|
|
247
|
-
* @param {Record<string, string>}
|
|
248
|
-
* @param {string}
|
|
299
|
+
* @param {Record<string, string>} routeConfig - The routes configuration map.
|
|
300
|
+
* @param {string} pathname - The URL path to match.
|
|
249
301
|
* @returns {{pattern: string, entityType: string, params: Record<string, string>, path: string}|null}
|
|
250
302
|
* The matched route object or null if no match is found.
|
|
251
303
|
*/
|
|
252
|
-
function findRoute(
|
|
253
|
-
const [
|
|
304
|
+
function findRoute(routeConfig, pathname) {
|
|
305
|
+
const [path, search] = pathname.split("?")
|
|
254
306
|
let fallbackRoute = null
|
|
255
307
|
|
|
256
|
-
for (const [pattern, entityType] of Object.entries(
|
|
308
|
+
for (const [pattern, entityType] of Object.entries(routeConfig)) {
|
|
257
309
|
if (pattern === "*") {
|
|
258
|
-
fallbackRoute = { pattern, entityType, params: {}, path
|
|
310
|
+
fallbackRoute = { pattern, entityType, params: {}, path }
|
|
259
311
|
continue
|
|
260
312
|
}
|
|
261
|
-
|
|
313
|
+
|
|
314
|
+
const params = matchRoute(pattern, path)
|
|
262
315
|
if (params !== null) {
|
|
263
|
-
|
|
316
|
+
const query = search
|
|
317
|
+
? Object.fromEntries(new URLSearchParams(search))
|
|
318
|
+
: {}
|
|
319
|
+
return { pattern, entityType, params, path, query }
|
|
264
320
|
}
|
|
265
321
|
}
|
|
322
|
+
|
|
266
323
|
return fallbackRoute
|
|
267
324
|
}
|
|
268
325
|
|
|
@@ -312,50 +369,18 @@ function patternToRegex(pattern) {
|
|
|
312
369
|
return new RegExp(`^${regexPattern}$`)
|
|
313
370
|
}
|
|
314
371
|
|
|
315
|
-
/**
|
|
316
|
-
* Performs the actual navigation by updating entity state and browser history.
|
|
317
|
-
* @param {RouterEntity} entity - The router entity.
|
|
318
|
-
* @param {object} options - Navigation options.
|
|
319
|
-
* @param {string} options.entityType - The type of the entity to render.
|
|
320
|
-
* @param {string} options.path - The full path.
|
|
321
|
-
* @param {object} options.params - The route parameters.
|
|
322
|
-
* @param {boolean} [options.replace] - Whether to replace the current history entry.
|
|
323
|
-
* @param {object} [options.state] - Additional state to save in history.
|
|
324
|
-
* @param {Api} api - The application API.
|
|
325
|
-
*/
|
|
326
|
-
function doNavigate(entity, { entityType, path, params, replace, state }, api) {
|
|
327
|
-
updateRouter(entity, { entityType, path, params })
|
|
328
|
-
|
|
329
|
-
// Prepare history state
|
|
330
|
-
const historyState = {
|
|
331
|
-
...state,
|
|
332
|
-
route: entity.route,
|
|
333
|
-
params: entity.params,
|
|
334
|
-
query: entity.query,
|
|
335
|
-
path: entity.path,
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
// Navigate
|
|
339
|
-
const method = replace ? "replaceState" : "pushState"
|
|
340
|
-
history[method](historyState, "", path)
|
|
341
|
-
|
|
342
|
-
api.notify("routeChange", historyState)
|
|
343
|
-
}
|
|
344
|
-
|
|
345
372
|
/**
|
|
346
373
|
* Updates the router entity's internal state.
|
|
347
374
|
* @param {RouterEntity} entity - The router entity.
|
|
348
375
|
* @param {object} options - The update options.
|
|
349
376
|
* @param {string} options.entityType - The matched entity type.
|
|
350
|
-
* @param {string} options.path - The full path.
|
|
351
|
-
* @param {object} options.params - The extracted parameters.
|
|
377
|
+
* @param {string} options.path - The full path (pathname only, no query).
|
|
378
|
+
* @param {object} options.params - The extracted route parameters.
|
|
379
|
+
* @param {object} [options.query] - The parsed query parameters.
|
|
352
380
|
*/
|
|
353
|
-
function updateRouter(entity, { entityType, path, params }) {
|
|
354
|
-
const [pathname, search] = path.split("?")
|
|
355
|
-
const query = search ? Object.fromEntries(new URLSearchParams(search)) : {}
|
|
356
|
-
|
|
357
|
-
entity.path = pathname
|
|
381
|
+
function updateRouter(entity, { entityType, path, params, query }) {
|
|
358
382
|
entity.route = entityType
|
|
383
|
+
entity.path = path
|
|
359
384
|
entity.params = params
|
|
360
385
|
entity.query = query
|
|
361
386
|
entity.hash = window.location.hash
|
package/types/router.d.ts
CHANGED
|
@@ -26,8 +26,6 @@ export type QueryParams = Record<string, string>
|
|
|
26
26
|
export interface RouterEntity {
|
|
27
27
|
/** A unique identifier for the router entity. */
|
|
28
28
|
id: string | number
|
|
29
|
-
/** The route configuration. */
|
|
30
|
-
routes: RoutesConfig
|
|
31
29
|
/** The current active path, without query string or hash. */
|
|
32
30
|
path?: string
|
|
33
31
|
/** The entity type of the current active route. */
|
|
@@ -38,6 +36,10 @@ export interface RouterEntity {
|
|
|
38
36
|
query?: QueryParams
|
|
39
37
|
/** The hash from the current URL. */
|
|
40
38
|
hash?: string
|
|
39
|
+
/** Whether a route is currently loading asynchronously. */
|
|
40
|
+
isLoading?: boolean
|
|
41
|
+
/** An error that occurred during route loading. */
|
|
42
|
+
error?: Error | null
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
/**
|
|
@@ -98,7 +100,7 @@ export interface RouterType {
|
|
|
98
100
|
entity: RouterEntity,
|
|
99
101
|
payload: string | number | NavigatePayload,
|
|
100
102
|
api: StoreApi,
|
|
101
|
-
): void
|
|
103
|
+
): void | Promise<void>
|
|
102
104
|
|
|
103
105
|
/**
|
|
104
106
|
* Synchronizes the router entity's state with data from a routing event,
|
|
@@ -112,19 +114,61 @@ export interface RouterType {
|
|
|
112
114
|
payload: RouteSyncPayload,
|
|
113
115
|
api: StoreApi,
|
|
114
116
|
): void
|
|
117
|
+
/**
|
|
118
|
+
* Handles browser `popstate` events. May perform async loading for lazy routes.
|
|
119
|
+
*/
|
|
120
|
+
popstate(
|
|
121
|
+
entity: RouterEntity,
|
|
122
|
+
payload: any,
|
|
123
|
+
api: StoreApi,
|
|
124
|
+
): void | Promise<void>
|
|
115
125
|
|
|
116
126
|
/**
|
|
117
|
-
* Handles successful async route loading.
|
|
118
|
-
*
|
|
119
|
-
* @param {Object} payload - The load success payload.
|
|
120
|
-
* @param {StoreApi} api - The store API.
|
|
127
|
+
* Handles successful async route loading for a pattern.
|
|
128
|
+
* Registers the loaded type and updates runtime route config.
|
|
121
129
|
*/
|
|
122
|
-
|
|
130
|
+
routeLoadSuccess(entity: RouterEntity, payload: any, api: StoreApi): void
|
|
123
131
|
|
|
124
132
|
/**
|
|
125
|
-
* Handles
|
|
126
|
-
* @param {RouterEntity} entity - The router entity.
|
|
127
|
-
* @param {Object} payload - The error payload.
|
|
133
|
+
* Handles errors that occurred while loading a lazy route.
|
|
128
134
|
*/
|
|
129
|
-
|
|
135
|
+
routeLoadError(entity: RouterEntity, payload: any): void
|
|
130
136
|
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Returns the current route configuration.
|
|
140
|
+
* @returns {Record<string, string|function>} The current route configuration.
|
|
141
|
+
*/
|
|
142
|
+
export function getRoutes(): Record<string, string | (() => Promise<any>)>
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Retrieves a single route configuration given its path.
|
|
146
|
+
* @param {string} path - The path of the route to retrieve.
|
|
147
|
+
* @returns {string|function|undefined} The route configuration or undefined if not found.
|
|
148
|
+
*/
|
|
149
|
+
export function getRoute(path: string): string | (() => Promise<any>)
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Sets or updates routes in the route configuration.
|
|
153
|
+
* Can be used both during initialization and at any point to add or update routes dynamically.
|
|
154
|
+
* @param routes An object mapping route paths/patterns to entity type names or loader functions.
|
|
155
|
+
*/
|
|
156
|
+
export function setRoutes(
|
|
157
|
+
routes: Record<string, string | (() => Promise<any>)>,
|
|
158
|
+
): void
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Adds a single route to the route configuration.
|
|
162
|
+
* @param path The route path or pattern (e.g., "/users/:userId").
|
|
163
|
+
* @param route The entity type name or a function that dynamically loads it.
|
|
164
|
+
*/
|
|
165
|
+
export function addRoute(
|
|
166
|
+
path: string,
|
|
167
|
+
route: string | (() => Promise<any>),
|
|
168
|
+
): void
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Removes a route from the route configuration.
|
|
172
|
+
* @param path The route path or pattern to remove.
|
|
173
|
+
*/
|
|
174
|
+
export function removeRoute(path: string): void
|