@lightningjs/blits 1.38.3 → 1.39.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/CHANGELOG.md +13 -0
- package/package.json +2 -2
- package/src/router/router.js +116 -82
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v1.39.1
|
|
4
|
+
|
|
5
|
+
_26 Sept 2025_
|
|
6
|
+
|
|
7
|
+
- Bumped Lightning renderer to 2.17.0
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## v1.39.0
|
|
11
|
+
|
|
12
|
+
_16 Sept 2025_
|
|
13
|
+
|
|
14
|
+
- Refactored router to be more performant and prevent runtime route changes to affect other route instances
|
|
15
|
+
|
|
3
16
|
## v1.38.3
|
|
4
17
|
|
|
5
18
|
_4 Sept 2025_
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lightningjs/blits",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.39.1",
|
|
4
4
|
"description": "Blits: The Lightning 3 App Development Framework",
|
|
5
5
|
"bin": "bin/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
57
|
"@lightningjs/msdf-generator": "^1.1.1",
|
|
58
|
-
"@lightningjs/renderer": "^2.
|
|
58
|
+
"@lightningjs/renderer": "^2.17.0"
|
|
59
59
|
},
|
|
60
60
|
"repository": {
|
|
61
61
|
"type": "git",
|
package/src/router/router.js
CHANGED
|
@@ -71,25 +71,25 @@ export const state = reactive(
|
|
|
71
71
|
* @property {BlitsComponent} focus - The focus of the route
|
|
72
72
|
*/
|
|
73
73
|
|
|
74
|
-
/** @type {Map<String, CacheMapEntry>} */
|
|
75
|
-
const cacheMap = new Map()
|
|
76
74
|
const history = []
|
|
77
75
|
|
|
78
76
|
let overrideOptions = {}
|
|
79
77
|
let navigationData = {}
|
|
80
78
|
let navigatingBack = false
|
|
79
|
+
let navigatingBackTo = undefined
|
|
81
80
|
let previousFocus
|
|
82
81
|
|
|
83
82
|
/**
|
|
84
83
|
* Get the current hash
|
|
85
84
|
* @returns {Hash}
|
|
86
85
|
*/
|
|
87
|
-
export const getHash = () => {
|
|
88
|
-
|
|
86
|
+
export const getHash = (hash) => {
|
|
87
|
+
if (!hash) hash = '/'
|
|
88
|
+
const hashParts = hash.replace(/^#/, '').split('?')
|
|
89
89
|
return {
|
|
90
90
|
path: hashParts[0],
|
|
91
91
|
queryParams: new URLSearchParams(hashParts[1]),
|
|
92
|
-
hash:
|
|
92
|
+
hash: hash,
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
95
|
|
|
@@ -116,33 +116,50 @@ const isObject = (v) => typeof v === 'object' && v !== null
|
|
|
116
116
|
*/
|
|
117
117
|
const isString = (v) => typeof v === 'string'
|
|
118
118
|
|
|
119
|
+
const queryParamsToObject = (queryParams) => {
|
|
120
|
+
if (!queryParams) return {}
|
|
121
|
+
const object = {}
|
|
122
|
+
const queryParamsEntries = [...queryParams.entries()]
|
|
123
|
+
for (let i = 0; i < queryParamsEntries.length; i++) {
|
|
124
|
+
object[queryParamsEntries[i][0]] = queryParamsEntries[i][1]
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return object
|
|
128
|
+
}
|
|
129
|
+
|
|
119
130
|
/**
|
|
120
131
|
* Match a path to a route
|
|
121
132
|
*
|
|
122
|
-
* @param {
|
|
133
|
+
* @param {object} hashObject
|
|
123
134
|
* @param {Route[]} routes
|
|
124
135
|
* @returns {Route}
|
|
125
136
|
*/
|
|
126
|
-
export const matchHash = (path, routes = []) => {
|
|
137
|
+
export const matchHash = ({ hash, path, queryParams }, routes = []) => {
|
|
127
138
|
// remove trailing slashes
|
|
128
139
|
const originalPath = path.replace(/^\/+|\/+$/g, '')
|
|
129
|
-
|
|
140
|
+
const originalNormalizedPath = normalizePath(path)
|
|
141
|
+
|
|
142
|
+
const override = {
|
|
143
|
+
hash: hash,
|
|
144
|
+
queryParams: queryParamsToObject(queryParams),
|
|
145
|
+
path: path,
|
|
146
|
+
}
|
|
130
147
|
|
|
131
148
|
/** @type {boolean|Route} */
|
|
132
149
|
let matchingRoute = false
|
|
133
150
|
let i = 0
|
|
134
151
|
while (!matchingRoute && i < routes.length) {
|
|
135
152
|
const route = routes[i]
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
matchingRoute = route
|
|
140
|
-
} else if (
|
|
153
|
+
|
|
154
|
+
const normalizedPath = normalizePath(route.path)
|
|
155
|
+
if (normalizePath(normalizedPath) === originalNormalizedPath) {
|
|
156
|
+
matchingRoute = makeRouteObject(route, override)
|
|
157
|
+
} else if (normalizedPath.indexOf(':') > -1) {
|
|
141
158
|
// match dynamic route parts
|
|
142
|
-
const dynamicRouteParts = [...
|
|
159
|
+
const dynamicRouteParts = [...normalizedPath.matchAll(/:([^\s/]+)/gi)]
|
|
143
160
|
|
|
144
161
|
// construct a regex for the route with dynamic parts
|
|
145
|
-
let dynamicRoutePartsRegex =
|
|
162
|
+
let dynamicRoutePartsRegex = normalizedPath
|
|
146
163
|
dynamicRouteParts.reverse().forEach((part) => {
|
|
147
164
|
dynamicRoutePartsRegex =
|
|
148
165
|
dynamicRoutePartsRegex.substring(0, part.index) +
|
|
@@ -157,31 +174,26 @@ export const matchHash = (path, routes = []) => {
|
|
|
157
174
|
|
|
158
175
|
if (match) {
|
|
159
176
|
// map the route params to a params object
|
|
160
|
-
|
|
177
|
+
override.params = dynamicRouteParts.reverse().reduce((acc, part, index) => {
|
|
161
178
|
acc[part[1]] = match[index + 1]
|
|
162
179
|
return acc
|
|
163
180
|
}, {})
|
|
164
|
-
|
|
181
|
+
|
|
182
|
+
matchingRoute = makeRouteObject(route, override)
|
|
165
183
|
}
|
|
166
|
-
} else if (
|
|
167
|
-
const regex = new RegExp(
|
|
168
|
-
const match =
|
|
184
|
+
} else if (normalizedPath.endsWith('*')) {
|
|
185
|
+
const regex = new RegExp(normalizedPath.replace(/\/?\*/, '/?([^\\s]*)'), 'i')
|
|
186
|
+
const match = originalNormalizedPath.match(regex)
|
|
169
187
|
|
|
170
188
|
if (match) {
|
|
171
|
-
|
|
172
|
-
|
|
189
|
+
override.params = {}
|
|
190
|
+
if (match[1]) override.params.path = match[1]
|
|
191
|
+
matchingRoute = makeRouteObject(route, override)
|
|
173
192
|
}
|
|
174
193
|
}
|
|
175
194
|
i++
|
|
176
195
|
}
|
|
177
196
|
|
|
178
|
-
if (matchingRoute) {
|
|
179
|
-
matchingRoute.options = { ...defaultOptions, ...matchingRoute.options, ...overrideOptions }
|
|
180
|
-
if (!matchingRoute.data) {
|
|
181
|
-
matchingRoute.data = {}
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
197
|
// @ts-ignore - Remove me when we have a better way to handle this
|
|
186
198
|
return matchingRoute
|
|
187
199
|
}
|
|
@@ -197,6 +209,22 @@ const defaultOptions = {
|
|
|
197
209
|
reuseComponent: false,
|
|
198
210
|
}
|
|
199
211
|
|
|
212
|
+
const makeRouteObject = (route, overrides) => {
|
|
213
|
+
const cleanRoute = {
|
|
214
|
+
hash: overrides.hash,
|
|
215
|
+
path: route.path,
|
|
216
|
+
component: route.component,
|
|
217
|
+
transition: 'transition' in route ? route.transition : fadeInFadeOutTransition,
|
|
218
|
+
options: { ...defaultOptions, ...route.options, ...overrideOptions },
|
|
219
|
+
announce: route.announce || false,
|
|
220
|
+
hooks: route.hooks || {},
|
|
221
|
+
data: { ...route.data, ...navigationData, ...overrides.queryParams },
|
|
222
|
+
params: overrides.params || {},
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return cleanRoute
|
|
226
|
+
}
|
|
227
|
+
|
|
200
228
|
/**
|
|
201
229
|
* Navigate to a route
|
|
202
230
|
*
|
|
@@ -216,28 +244,11 @@ export const navigate = async function () {
|
|
|
216
244
|
let reuse = false
|
|
217
245
|
if (this.parent[symbols.routes]) {
|
|
218
246
|
let previousRoute = currentRoute //? Object.assign({}, currentRoute) : undefined
|
|
219
|
-
|
|
220
|
-
let route = matchHash(path, this.parent[symbols.routes])
|
|
247
|
+
let route = matchHash(getHash(document.location.hash), this.parent[symbols.routes])
|
|
221
248
|
|
|
222
249
|
currentRoute = route
|
|
223
250
|
|
|
224
251
|
if (route) {
|
|
225
|
-
const queryParamsData = {}
|
|
226
|
-
const queryParamsEntries = [...queryParams.entries()]
|
|
227
|
-
for (let i = 0; i < queryParamsEntries.length; i++) {
|
|
228
|
-
queryParamsData[queryParamsEntries[i][0]] = queryParamsEntries[i][1]
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
route.data = {
|
|
232
|
-
...route.data,
|
|
233
|
-
...navigationData,
|
|
234
|
-
...queryParamsData,
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// Adding the location hash to the route if it exists.
|
|
238
|
-
if (hash !== null) {
|
|
239
|
-
route.hash = hash
|
|
240
|
-
}
|
|
241
252
|
let beforeEachResult
|
|
242
253
|
if (this.parent[symbols.routerHooks]) {
|
|
243
254
|
const hooks = this.parent[symbols.routerHooks]
|
|
@@ -250,30 +261,33 @@ export const navigate = async function () {
|
|
|
250
261
|
}
|
|
251
262
|
}
|
|
252
263
|
// If the resolved result is an object, assign it to the target route object
|
|
253
|
-
|
|
264
|
+
if (isObject(beforeEachResult) === true) {
|
|
265
|
+
route = beforeEachResult
|
|
266
|
+
}
|
|
254
267
|
|
|
255
268
|
let beforeHookOutput
|
|
256
|
-
if (route.hooks) {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
return
|
|
263
|
-
}
|
|
269
|
+
if (route.hooks.before) {
|
|
270
|
+
beforeHookOutput = await route.hooks.before.call(this.parent, route, previousRoute)
|
|
271
|
+
if (isString(beforeHookOutput)) {
|
|
272
|
+
currentRoute = previousRoute
|
|
273
|
+
to(beforeHookOutput)
|
|
274
|
+
return
|
|
264
275
|
}
|
|
265
276
|
}
|
|
266
|
-
|
|
277
|
+
// If the resolved result is an object, assign it to the target route object
|
|
278
|
+
if (isObject(beforeHookOutput) === true) {
|
|
279
|
+
route = beforeHookOutput
|
|
280
|
+
}
|
|
267
281
|
// add the previous route (technically still the current route at this point)
|
|
268
|
-
// into the history stack
|
|
269
|
-
if (
|
|
282
|
+
// into the history stack when inHistory is true and we're not navigating back
|
|
283
|
+
if (
|
|
284
|
+
previousRoute !== undefined &&
|
|
285
|
+
previousRoute.options.inHistory === true &&
|
|
286
|
+
navigatingBack === false
|
|
287
|
+
) {
|
|
270
288
|
history.push(previousRoute)
|
|
271
289
|
}
|
|
272
|
-
|
|
273
|
-
if (!('transition' in route)) {
|
|
274
|
-
/** @ts-ignore */
|
|
275
|
-
route.transition = fadeInFadeOutTransition
|
|
276
|
-
}
|
|
290
|
+
|
|
277
291
|
// a transition can be a function returning a dynamic transition object
|
|
278
292
|
// based on current and previous route
|
|
279
293
|
if (typeof route.transition === 'function') {
|
|
@@ -283,8 +297,14 @@ export const navigate = async function () {
|
|
|
283
297
|
/** @type {import('../engines/L3/element.js').BlitsElement} */
|
|
284
298
|
let holder
|
|
285
299
|
|
|
286
|
-
let
|
|
287
|
-
|
|
300
|
+
let view
|
|
301
|
+
let focus
|
|
302
|
+
// when navigating back let's see if we're navigating back to a route that was kept alive
|
|
303
|
+
if (navigatingBack === true && navigatingBackTo !== undefined) {
|
|
304
|
+
view = navigatingBackTo.view
|
|
305
|
+
focus = navigatingBackTo.focus
|
|
306
|
+
navigatingBackTo = null
|
|
307
|
+
}
|
|
288
308
|
// merge props with potential route params, navigation data and route data to be injected into the component instance
|
|
289
309
|
const props = {
|
|
290
310
|
...this[symbols.props],
|
|
@@ -292,11 +312,13 @@ export const navigate = async function () {
|
|
|
292
312
|
...route.data,
|
|
293
313
|
}
|
|
294
314
|
|
|
315
|
+
// see if the component of the previous route can be reused for the
|
|
316
|
+
// current route
|
|
295
317
|
if (
|
|
296
318
|
previousRoute &&
|
|
297
|
-
route.component === previousRoute.component &&
|
|
298
319
|
route.options.reuseComponent === true &&
|
|
299
|
-
route.options.keepAlive !== true
|
|
320
|
+
route.options.keepAlive !== true &&
|
|
321
|
+
route.component === previousRoute.component
|
|
300
322
|
) {
|
|
301
323
|
reuse = true
|
|
302
324
|
view = this[symbols.children][this[symbols.children].length - 1]
|
|
@@ -320,6 +342,7 @@ export const navigate = async function () {
|
|
|
320
342
|
state.path = route.path
|
|
321
343
|
state.params = route.params || {}
|
|
322
344
|
state.hash = route.hash
|
|
345
|
+
state.data = null
|
|
323
346
|
state.data = route.data || {}
|
|
324
347
|
|
|
325
348
|
if (!view) {
|
|
@@ -331,6 +354,7 @@ export const navigate = async function () {
|
|
|
331
354
|
|
|
332
355
|
view = await route.component({ props }, holder, this)
|
|
333
356
|
|
|
357
|
+
// is the component a dynamic module?
|
|
334
358
|
if (view[Symbol.toStringTag] === 'Module') {
|
|
335
359
|
if (view.default && typeof view.default === 'function') {
|
|
336
360
|
view = view.default({ props }, holder, this)
|
|
@@ -338,6 +362,7 @@ export const navigate = async function () {
|
|
|
338
362
|
Log.error("Dynamic import doesn't have a default export or default is not a function")
|
|
339
363
|
}
|
|
340
364
|
}
|
|
365
|
+
|
|
341
366
|
if (typeof view === 'function') {
|
|
342
367
|
// had to inline this because the tscompiler does not like LHS reassignments
|
|
343
368
|
// that also change the type of the variable in a variable union
|
|
@@ -366,6 +391,7 @@ export const navigate = async function () {
|
|
|
366
391
|
}
|
|
367
392
|
}
|
|
368
393
|
|
|
394
|
+
// store the new view as new child, only if we're not reusing the previous page component
|
|
369
395
|
if (reuse === false) {
|
|
370
396
|
this[symbols.children].push(view)
|
|
371
397
|
}
|
|
@@ -377,7 +403,7 @@ export const navigate = async function () {
|
|
|
377
403
|
this.activeView = children[children.length - 1]
|
|
378
404
|
|
|
379
405
|
// set focus to the view that we're routing to (unless explicitly disabling passing focus)
|
|
380
|
-
if (route.options
|
|
406
|
+
if (route.options.passFocus !== false) {
|
|
381
407
|
focus ? focus.$focus() : /** @type {BlitsComponent} */ (view).$focus()
|
|
382
408
|
}
|
|
383
409
|
|
|
@@ -394,8 +420,9 @@ export const navigate = async function () {
|
|
|
394
420
|
|
|
395
421
|
let shouldAnimate = false
|
|
396
422
|
|
|
397
|
-
// apply out out transition on previous view
|
|
398
|
-
|
|
423
|
+
// apply out out transition on previous view if available, unless
|
|
424
|
+
// we're reusing the prvious page component
|
|
425
|
+
if (previousRoute !== undefined && reuse === false) {
|
|
399
426
|
// only animate when there is a previous route
|
|
400
427
|
shouldAnimate = true
|
|
401
428
|
const oldView = this[symbols.children].splice(1, 1).pop()
|
|
@@ -417,10 +444,10 @@ export const navigate = async function () {
|
|
|
417
444
|
}
|
|
418
445
|
}
|
|
419
446
|
} else {
|
|
420
|
-
Log.error(`Route ${hash} not found`)
|
|
447
|
+
Log.error(`Route ${route.hash} not found`)
|
|
421
448
|
const routerHooks = this.parent[symbols.routerHooks]
|
|
422
449
|
if (routerHooks && typeof routerHooks.error === 'function') {
|
|
423
|
-
routerHooks.error.call(this.parent, `Route ${hash} not found`)
|
|
450
|
+
routerHooks.error.call(this.parent, `Route ${route.hash} not found`)
|
|
424
451
|
}
|
|
425
452
|
}
|
|
426
453
|
}
|
|
@@ -452,13 +479,19 @@ const removeView = async (route, view, transition, navigatingBack) => {
|
|
|
452
479
|
}
|
|
453
480
|
|
|
454
481
|
// cache the page when it's as 'keepAlive' instead of destroying
|
|
455
|
-
if (
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
482
|
+
if (
|
|
483
|
+
navigatingBack === false &&
|
|
484
|
+
route.options &&
|
|
485
|
+
route.options.keepAlive === true &&
|
|
486
|
+
route.options.inHistory === true
|
|
487
|
+
) {
|
|
488
|
+
const historyItem = history[history.length - 1]
|
|
489
|
+
if (historyItem !== undefined) {
|
|
490
|
+
historyItem.view = view
|
|
491
|
+
historyItem.focus = previousFocus
|
|
492
|
+
}
|
|
461
493
|
}
|
|
494
|
+
|
|
462
495
|
/* Destroy the view in the following cases:
|
|
463
496
|
* 1. Navigating forward, and the previous route is not configured with "keep alive" set to true.
|
|
464
497
|
* 2. Navigating back, and the previous route is configured with "keep alive" set to true.
|
|
@@ -470,6 +503,7 @@ const removeView = async (route, view, transition, navigatingBack) => {
|
|
|
470
503
|
}
|
|
471
504
|
|
|
472
505
|
previousFocus = null
|
|
506
|
+
route = null
|
|
473
507
|
}
|
|
474
508
|
|
|
475
509
|
const setOrAnimate = (node, transition, shouldAnimate = true) => {
|
|
@@ -505,7 +539,7 @@ export const back = function () {
|
|
|
505
539
|
if (route && currentRoute !== route) {
|
|
506
540
|
// set indicator that we are navigating back (to prevent adding page to history stack)
|
|
507
541
|
navigatingBack = true
|
|
508
|
-
|
|
542
|
+
navigatingBackTo = route
|
|
509
543
|
to(route.hash, route.data, route.options)
|
|
510
544
|
return true
|
|
511
545
|
}
|
|
@@ -534,10 +568,10 @@ export const back = function () {
|
|
|
534
568
|
}
|
|
535
569
|
// Construct new path to backtrack to
|
|
536
570
|
path = path.replace(hashEnd, '')
|
|
537
|
-
const route = matchHash(path, this.parent[symbols.routes])
|
|
571
|
+
const route = matchHash(getHash(path), this.parent[symbols.routes])
|
|
538
572
|
|
|
539
573
|
if (route && backtrack) {
|
|
540
|
-
to(route.path)
|
|
574
|
+
to(route.path, route.data, route.options)
|
|
541
575
|
return true
|
|
542
576
|
}
|
|
543
577
|
}
|