@lightningjs/blits 2.8.3 → 2.8.5

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 CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.8.5
4
+
5
+ _29 jul 2026_
6
+
7
+ - Fixed EOL issue in transition progress callback
8
+
9
+
10
+ ## 2.8.4
11
+
12
+ _23 jul 2026_
13
+
14
+ - Fixed issues with navigating when using multiple RouterViews
15
+
16
+
3
17
  ## 2.8.3
4
18
 
5
19
  _23 jul 2026_
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightningjs/blits",
3
- "version": "2.8.3",
3
+ "version": "2.8.5",
4
4
  "description": "Blits: The Lightning 3 App Development Framework",
5
5
  "bin": "bin/index.js",
6
6
  "exports": {
@@ -16,6 +16,7 @@
16
16
  */
17
17
 
18
18
  import symbols from '../../lib/symbols.js'
19
+ import { Log } from '../../lib/log.js'
19
20
  import {
20
21
  back,
21
22
  currentRoute,
@@ -67,12 +68,37 @@ const resolveRouterView = (component, routerViewName) => {
67
68
  const registeredRouterView = getRegisteredRouterView(routerViewName)
68
69
  if (registeredRouterView !== null) return registeredRouterView
69
70
 
70
- return getRouterView(component, routerViewName)
71
+ const treeRouterView = getRouterView(component, routerViewName)
72
+ if (treeRouterView !== null) return treeRouterView
73
+
74
+ // Named view not found — fall back to default (unnamed) view
75
+ const defaultView = getRegisteredRouterView('')
76
+ if (defaultView !== null) {
77
+ Log.warn(
78
+ `RouterView "${routerViewName}" was not found. Navigation is falling back to the default RouterView.`
79
+ )
80
+ return defaultView
81
+ }
82
+
83
+ const treeDefaultView = getRouterView(component)
84
+ if (treeDefaultView !== null) {
85
+ Log.warn(
86
+ `RouterView "${routerViewName}" was not found. Navigation is falling back to the default RouterView.`
87
+ )
88
+ }
89
+ return treeDefaultView
71
90
  }
72
91
 
73
92
  const singleRouterView = getSingleRegisteredRouterView()
74
93
  if (singleRouterView !== null) return singleRouterView
75
94
 
95
+ // When multiple router views exist and no name was specified,
96
+ // prefer the default (unnamed) router view before falling back
97
+ // to tree-walking. This ensures hooks on the App's main router
98
+ // fire correctly when calling router.to() from a child component.
99
+ const defaultView = getRegisteredRouterView('')
100
+ if (defaultView !== null) return defaultView
101
+
76
102
  return getRouterView(component)
77
103
  }
78
104
 
@@ -174,7 +174,7 @@ export default () =>
174
174
  }
175
175
  },
176
176
  focus() {
177
- if (this.activeView && Focus.get() === this) {
177
+ if (this.active && this.activeView && Focus.get() === this) {
178
178
  this.activeView.$focus()
179
179
  }
180
180
  },
@@ -152,7 +152,11 @@ const layoutFn = function (config) {
152
152
  }
153
153
 
154
154
  // trigger layout on parent if parent is a layout
155
- if (this.config.parent && this.config.parent.props.__layout === true) {
155
+ if (
156
+ this.config.parent &&
157
+ this.config.parent.eol !== true &&
158
+ this.config.parent.props.__layout === true
159
+ ) {
156
160
  this.config.parent.triggerLayout(this.config.parent.props)
157
161
  }
158
162
  }
@@ -915,6 +919,7 @@ const Element = {
915
919
  if (transition.progress !== undefined && typeof transition.progress === 'function') {
916
920
  let prevProgress = 0
917
921
  f.on('tick', (_node, { progress }) => {
922
+ if (this.eol === true || !this.config) return
918
923
  transition.progress.call(this.component, this, prop, progress, prevProgress)
919
924
  prevProgress = progress
920
925
  })
@@ -81,14 +81,28 @@ const history = []
81
81
  const routerViews = new Set()
82
82
  let singleRouterView = null
83
83
 
84
- let overrideOptions = {}
85
- let navigationData = {}
86
- let navigatingBack = false
87
- let navigatingBackTo = undefined
88
- let previousFocus
89
- // Skips internal router navigation when set to true only for the next "navigate"
90
- // execution, needed for window.history management
91
- let preventHashChangeNavigation = false
84
+ // Per-RouterView navigation state keyed by router view name.
85
+ // When multiple router views exist, each view must have its own navigation
86
+ // state so that one view's navigation doesn't interfere with another's hooks.
87
+ const perViewState = new Map()
88
+
89
+ const getViewState = (name = '') => {
90
+ if (!perViewState.has(name)) {
91
+ perViewState.set(name, {
92
+ overrideOptions: {},
93
+ navigationData: {},
94
+ navigatingBack: false,
95
+ navigatingBackTo: undefined,
96
+ previousFocus: undefined,
97
+ preventHashChangeNavigation: false,
98
+ })
99
+ }
100
+ return perViewState.get(name)
101
+ }
102
+
103
+ const clearViewState = (name = '') => {
104
+ perViewState.delete(name)
105
+ }
92
106
 
93
107
  /**
94
108
  * Navigate to a route
@@ -103,8 +117,9 @@ let preventHashChangeNavigation = false
103
117
  * @returns {Promise<void>}
104
118
  */
105
119
  export const navigate = async function () {
120
+ const viewState = getViewState(this.name)
106
121
  // early return when in preventHashChange mode
107
- if (preventHashChangeNavigation !== false) return
122
+ if (viewState.preventHashChangeNavigation !== false) return
108
123
  // early return when no routes
109
124
  if (!this[symbols.parent][symbols.routes] || this[symbols.parent][symbols.routes].length === 0)
110
125
  return
@@ -118,7 +133,12 @@ export const navigate = async function () {
118
133
 
119
134
  const hash = getHash(location.hash, this.name)
120
135
  // try to find the route
121
- let route = matchHash(hash, this[symbols.parent][symbols.routes], overrideOptions, navigationData)
136
+ let route = matchHash(
137
+ hash,
138
+ this[symbols.parent][symbols.routes],
139
+ viewState.overrideOptions,
140
+ viewState.navigationData
141
+ )
122
142
 
123
143
  // early return when route not found
124
144
  if (route === false) {
@@ -151,10 +171,11 @@ export const navigate = async function () {
151
171
  this[symbols.parent],
152
172
  route,
153
173
  this.previousRoute,
154
- currentPath
174
+ currentPath,
175
+ viewState
155
176
  )
156
177
  if (beforeEachResult === false) {
157
- preventHashChangeNavigation = false
178
+ viewState.preventHashChangeNavigation = false
158
179
  state.navigating = false
159
180
  return
160
181
  }
@@ -167,10 +188,11 @@ export const navigate = async function () {
167
188
  this[symbols.parent],
168
189
  route,
169
190
  this.previousRoute,
170
- currentPath
191
+ currentPath,
192
+ viewState
171
193
  )
172
194
  if (beforeResult === false) {
173
- preventHashChangeNavigation = false
195
+ viewState.preventHashChangeNavigation = false
174
196
  state.navigating = false
175
197
  return
176
198
  }
@@ -184,7 +206,7 @@ export const navigate = async function () {
184
206
  this.previousRoute &&
185
207
  this.previousRoute.options &&
186
208
  this.previousRoute.options.inHistory === true &&
187
- navigatingBack === false
209
+ viewState.navigatingBack === false
188
210
  ) {
189
211
  this.history.push(this.previousRoute)
190
212
  }
@@ -202,10 +224,10 @@ export const navigate = async function () {
202
224
  let view
203
225
  let focus
204
226
  // when navigating back let's see if we're navigating back to a route that was kept alive
205
- if (navigatingBack === true && navigatingBackTo !== undefined) {
206
- view = navigatingBackTo.view
207
- focus = navigatingBackTo.focus
208
- navigatingBackTo = null
227
+ if (viewState.navigatingBack === true && viewState.navigatingBackTo !== undefined) {
228
+ view = viewState.navigatingBackTo.view
229
+ focus = viewState.navigatingBackTo.focus
230
+ viewState.navigatingBackTo = null
209
231
  }
210
232
  // merge props with potential route params, navigation data and route data to be injected into the component instance
211
233
  const props = {
@@ -284,7 +306,7 @@ export const navigate = async function () {
284
306
  }
285
307
 
286
308
  // keep reference to the previous focus for storing in cache
287
- previousFocus = Focus.get()
309
+ viewState.previousFocus = Focus.get()
288
310
 
289
311
  const children = this[symbols.children]
290
312
  this.activeView = children[children.length - 1]
@@ -313,15 +335,16 @@ export const navigate = async function () {
313
335
 
314
336
  // Resolve effective keepAlive: runtime override from $router.to() takes precedence
315
337
  // over the static route config option
316
- const hasOverrideOptions = overrideOptions && typeof overrideOptions === 'object'
338
+ const hasOverrideOptions =
339
+ viewState.overrideOptions && typeof viewState.overrideOptions === 'object'
317
340
  const keepAlive =
318
- hasOverrideOptions && overrideOptions.keepAlive !== undefined
319
- ? overrideOptions.keepAlive
341
+ hasOverrideOptions && viewState.overrideOptions.keepAlive !== undefined
342
+ ? viewState.overrideOptions.keepAlive
320
343
  : this.previousRoute.options && this.previousRoute.options.keepAlive
321
344
 
322
345
  // cache the page when it's as 'keepAlive' instead of destroying
323
346
  if (
324
- navigatingBack === false &&
347
+ viewState.navigatingBack === false &&
325
348
  keepAlive === true &&
326
349
  this.previousRoute &&
327
350
  this.previousRoute.options &&
@@ -330,7 +353,7 @@ export const navigate = async function () {
330
353
  const historyItem = this.history[this.history.length - 1]
331
354
  if (historyItem !== undefined) {
332
355
  historyItem.view = oldView
333
- historyItem.focus = previousFocus
356
+ historyItem.focus = viewState.previousFocus
334
357
  }
335
358
  }
336
359
 
@@ -339,12 +362,12 @@ export const navigate = async function () {
339
362
  * 2. Navigating back, and the previous route is configured with "keep alive" set to true.
340
363
  * 3. Navigating back, and the previous route is not configured with "keep alive" set to true.
341
364
  */
342
- if (this.previousRoute.options && (keepAlive !== true || navigatingBack === true)) {
365
+ if (this.previousRoute.options && (keepAlive !== true || viewState.navigatingBack === true)) {
343
366
  oldView.destroy()
344
367
  oldView = null
345
368
  }
346
369
 
347
- previousFocus = null
370
+ viewState.previousFocus = null
348
371
  }
349
372
  }
350
373
 
@@ -363,16 +386,14 @@ export const navigate = async function () {
363
386
  // execute after route Hook
364
387
  await executeAfterHook(route.hooks, 'after', this[symbols.parent], route, this.previousRoute)
365
388
 
366
- // Clear module-level variables after removeView has consumed them.
367
- // Placed here so it executes for all navigation flows, not only when
368
- // previousRoute exists and reuse is false.
369
- overrideOptions = {}
370
- navigationData = {}
389
+ // Clear per-view navigation state after navigation is complete.
390
+ viewState.overrideOptions = {}
391
+ viewState.navigationData = {}
371
392
 
372
393
  // reset navigating indicators
373
- navigatingBack = false
394
+ viewState.navigatingBack = false
374
395
  state.navigating = false
375
- preventHashChangeNavigation = false
396
+ viewState.preventHashChangeNavigation = false
376
397
  }
377
398
 
378
399
  const setOrAnimate = (element, transition, shouldAnimate = true) => {
@@ -402,7 +423,8 @@ const executeBeforeHook = async function (
402
423
  parent,
403
424
  route,
404
425
  previousRoute,
405
- currentPath
426
+ currentPath,
427
+ viewState
406
428
  ) {
407
429
  let result
408
430
  if (hooks && hooks[hookName]) {
@@ -417,10 +439,10 @@ const executeBeforeHook = async function (
417
439
  Log.error(`Error or Rejected Promise in "${hookName}" Hook`, error)
418
440
  this.currentRoute = previousRoute
419
441
  if (this.history.length > 0) {
420
- preventHashChangeNavigation = true
442
+ viewState.preventHashChangeNavigation = true
421
443
  platform.historyBack()
422
444
 
423
- navigatingBack = false
445
+ viewState.navigatingBack = false
424
446
  state.navigating = false
425
447
  }
426
448
  return false
@@ -435,9 +457,9 @@ const executeBeforeHook = async function (
435
457
  if (result === false) {
436
458
  this.currentRoute = previousRoute
437
459
  if (this.history.length > 0) {
438
- preventHashChangeNavigation = true
460
+ viewState.preventHashChangeNavigation = true
439
461
  platform.historyBack()
440
- navigatingBack = false
462
+ viewState.navigatingBack = false
441
463
  state.navigating = false
442
464
  }
443
465
  return false
@@ -493,20 +515,33 @@ const executeTransition = async (transition, element, animate) => {
493
515
  }
494
516
 
495
517
  export const to = (path, data = {}, options = {}, routerViewName = '') => {
496
- navigationData = data
497
- overrideOptions = options
518
+ const viewState = getViewState(routerViewName)
519
+ viewState.navigationData = data
520
+ viewState.overrideOptions = options
498
521
 
499
522
  setHash(path, routerViewName)
500
523
  }
501
524
 
502
525
  export const toRouterView = (routerView, path, data = {}, options = {}) => {
503
- navigationData = data
504
- overrideOptions = options
526
+ const viewState = getViewState(routerView.name)
527
+ viewState.navigationData = data
528
+ viewState.overrideOptions = options
505
529
 
506
530
  setHash(path, routerView.name)
507
531
  }
508
532
 
509
533
  export const registerRouterView = (routerView) => {
534
+ // RouterView names identify both the view and its navigation state, so duplicate
535
+ // names cause state collisions and ambiguous route resolution.
536
+ for (const existing of routerViews) {
537
+ if (existing !== routerView && existing.name === routerView.name) {
538
+ const name = routerView.name === '' ? 'the default name' : `"${routerView.name}"`
539
+ Log.warn(
540
+ `Multiple RouterViews use ${name}. Each RouterView must have a unique and stable "name" prop to ensure correct routing behavior.`
541
+ )
542
+ break
543
+ }
544
+ }
510
545
  routerViews.add(routerView)
511
546
  singleRouterView = routerViews.size === 1 ? routerView : null
512
547
  }
@@ -514,6 +549,7 @@ export const registerRouterView = (routerView) => {
514
549
  export const unregisterRouterView = (routerView) => {
515
550
  routerViews.delete(routerView)
516
551
  singleRouterView = routerViews.size === 1 ? routerViews.values().next().value : null
552
+ clearViewState(routerView.name)
517
553
  }
518
554
 
519
555
  export const getRegisteredRouterView = (name) => {
@@ -531,11 +567,12 @@ export const getSingleRegisteredRouterView = () => {
531
567
 
532
568
  export const back = function () {
533
569
  if (this.history === undefined) return
570
+ const viewState = getViewState(this.name)
534
571
  const route = this.history.pop()
535
572
  if (route && this.currentRoute !== route) {
536
573
  // set indicator that we are navigating back (to prevent adding page to history stack)
537
- navigatingBack = true
538
- navigatingBackTo = route
574
+ viewState.navigatingBack = true
575
+ viewState.navigatingBackTo = route
539
576
  toRouterView(this, route.hash, route.data, route.options)
540
577
  return true
541
578
  }
@@ -567,8 +604,8 @@ export const back = function () {
567
604
  const route = matchHash(
568
605
  getHash(path),
569
606
  this[symbols.parent][symbols.routes],
570
- overrideOptions,
571
- navigationData
607
+ viewState.overrideOptions,
608
+ viewState.navigationData
572
609
  )
573
610
 
574
611
  if (route && backtrack) {