Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@lightningjs/blits

Package Overview
Dependencies
Maintainers
5
Versions
188
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@lightningjs/blits - npm Package Compare versions

Comparing version
2.2.0
to
2.3.0
+158
src/router/utils.js
import { default as fadeInFadeOutTransition } from './transitions/fadeInOut.js'
/**
* Get the current hash
* @returns {Hash}
*/
export const getHash = (hash) => {
if (!hash) hash = '/'
const hashParts = hash.replace(/^#/, '').split('?')
return {
path: hashParts[0],
queryParams: new URLSearchParams(hashParts[1]),
hash: hash,
}
}
export const normalizePath = (path) => {
return (
path
// remove leading and trailing slashes
.replace(/^\/+|\/+$/g, '')
.toLowerCase()
)
}
/**
* Check if a value is an object
* @param {any} v
* @returns {boolean} True if v is an object
*/
export const isObject = (v) => typeof v === 'object' && v !== null
/**
* Check if a value is a string
* @param {any} v
* @returns {boolean} True if v is a string
*/
export const isString = (v) => typeof v === 'string'
export const queryParamsToObject = (queryParams) => {
if (!queryParams) return {}
const object = {}
const queryParamsEntries = [...queryParams.entries()]
for (let i = 0; i < queryParamsEntries.length; i++) {
object[queryParamsEntries[i][0]] = queryParamsEntries[i][1]
}
return object
}
/**
* Default Route options
*
*/
const defaultOptions = {
inHistory: true,
keepAlive: false,
passFocus: true,
reuseComponent: false,
}
export const makeRouteObject = (route, overrides, overrideOptions, navigationData) => {
// FIX: exclude keepAlive from the destination route options. Unlike other
// overrides, keepAlive applies to the route being LEFT, not the destination.
// It is consumed by removeView() instead.
const { keepAlive: _keepAlive, ...destOverrides } = overrideOptions // eslint-disable-line no-unused-vars
const cleanRoute = {
hash: overrides.hash,
path: route.path,
component: route.component,
transition: 'transition' in route ? route.transition : fadeInFadeOutTransition,
options: { ...defaultOptions, ...route.options, ...destOverrides },
announce: route.announce || false,
hooks: route.hooks || {},
data: { ...route.data, ...navigationData, ...overrides.queryParams },
params: overrides.params || {},
meta: route.meta || {},
}
return cleanRoute
}
/**
* Match a path to a route
*
* @param {object} hashObject
* @param {Route[]} routes
* @returns {Route}
*/
export const matchHash = (
{ hash, path, queryParams },
routes = [],
overrideOptions = {},
navigationData = {}
) => {
// remove trailing slashes
const originalPath = path.replace(/^\/+|\/+$/g, '')
const originalNormalizedPath = normalizePath(path)
const override = {
hash: hash,
queryParams: queryParamsToObject(queryParams),
path: path,
}
/** @type {boolean|Route} */
let matchingRoute = false
let i = 0
while (!matchingRoute && i < routes.length) {
const route = routes[i]
const normalizedPath = normalizePath(route.path)
if (normalizePath(normalizedPath) === originalNormalizedPath) {
matchingRoute = makeRouteObject(route, override, overrideOptions, navigationData)
} else if (normalizedPath.indexOf(':') > -1) {
// match dynamic route parts
const dynamicRouteParts = [...normalizedPath.matchAll(/:([^\s/]+)/gi)]
// construct a regex for the route with dynamic parts
let dynamicRoutePartsRegex = normalizedPath
dynamicRouteParts.reverse().forEach((part) => {
dynamicRoutePartsRegex =
dynamicRoutePartsRegex.substring(0, part.index) +
'([^\\s/]+)' +
dynamicRoutePartsRegex.substring(part.index + part[0].length)
})
dynamicRoutePartsRegex = '^' + dynamicRoutePartsRegex
// test if the constructed regex matches the path
const match = originalPath.match(new RegExp(`${dynamicRoutePartsRegex}`, 'i'))
if (match) {
// map the route params to a params object
override.params = dynamicRouteParts.reverse().reduce((acc, part, index) => {
acc[part[1]] = match[index + 1]
return acc
}, {})
matchingRoute = makeRouteObject(route, override, overrideOptions, navigationData)
}
} else if (normalizedPath.endsWith('*')) {
const regex = new RegExp(normalizedPath.replace(/\/?\*/, '/?([^\\s]*)'), 'i')
const match = originalNormalizedPath.match(regex)
if (match) {
override.params = {}
if (match[1]) override.params.path = match[1]
matchingRoute = makeRouteObject(route, override, overrideOptions, navigationData)
}
}
i++
}
// @ts-ignore - Remove me when we have a better way to handle this
return matchingRoute
}
+7
-0
# Changelog
## v2.3.0
_20 may 2026_
- Refactored router functionality
- Replaced `window` reference with `self` reference in announcer
## v2.2.0

@@ -4,0 +11,0 @@

+1
-1
{
"name": "@lightningjs/blits",
"version": "2.2.0",
"version": "2.3.0",
"description": "Blits: The Lightning 3 App Development Framework",

@@ -5,0 +5,0 @@ "bin": "bin/index.js",

@@ -30,3 +30,3 @@ /*

let globalDefaultOptions = {
enableUtteranceKeepAlive: !/android/i.test((window.navigator || {}).userAgent || ''),
enableUtteranceKeepAlive: !/android/i.test((self.navigator || {}).userAgent || ''),
}

@@ -33,0 +33,0 @@

@@ -20,3 +20,3 @@ /*

const syn = window.speechSynthesis
const syn = self.speechSynthesis

@@ -23,0 +23,0 @@ const utterances = new Map() // id -> { utterance, timer, ignoreResume }

@@ -18,3 +18,3 @@ /*

import { default as fadeInFadeOutTransition } from './transitions/fadeInOut.js'
import { getHash, isObject, isString, matchHash } from './utils.js'
import { reactive } from '../lib/reactivity/reactive.js'

@@ -89,155 +89,4 @@

let preventHashChangeNavigation = false
/**
* Get the current hash
* @returns {Hash}
*/
export const getHash = (hash) => {
if (!hash) hash = '/'
const hashParts = hash.replace(/^#/, '').split('?')
return {
path: hashParts[0],
queryParams: new URLSearchParams(hashParts[1]),
hash: hash,
}
}
const normalizePath = (path) => {
return (
path
// remove leading and trailing slashes
.replace(/^\/+|\/+$/g, '')
.toLowerCase()
)
}
/**
* Check if a value is an object
* @param {any} v
* @returns {boolean} True if v is an object
*/
const isObject = (v) => typeof v === 'object' && v !== null
/**
* Check if a value is a function
* @param {any} v
* @returns {boolean} True if v is a string
*/
const isString = (v) => typeof v === 'string'
const queryParamsToObject = (queryParams) => {
if (!queryParams) return {}
const object = {}
const queryParamsEntries = [...queryParams.entries()]
for (let i = 0; i < queryParamsEntries.length; i++) {
object[queryParamsEntries[i][0]] = queryParamsEntries[i][1]
}
return object
}
/**
* Match a path to a route
*
* @param {object} hashObject
* @param {Route[]} routes
* @returns {Route}
*/
export const matchHash = ({ hash, path, queryParams }, routes = []) => {
// remove trailing slashes
const originalPath = path.replace(/^\/+|\/+$/g, '')
const originalNormalizedPath = normalizePath(path)
const override = {
hash: hash,
queryParams: queryParamsToObject(queryParams),
path: path,
}
/** @type {boolean|Route} */
let matchingRoute = false
let i = 0
while (!matchingRoute && i < routes.length) {
const route = routes[i]
const normalizedPath = normalizePath(route.path)
if (normalizePath(normalizedPath) === originalNormalizedPath) {
matchingRoute = makeRouteObject(route, override)
} else if (normalizedPath.indexOf(':') > -1) {
// match dynamic route parts
const dynamicRouteParts = [...normalizedPath.matchAll(/:([^\s/]+)/gi)]
// construct a regex for the route with dynamic parts
let dynamicRoutePartsRegex = normalizedPath
dynamicRouteParts.reverse().forEach((part) => {
dynamicRoutePartsRegex =
dynamicRoutePartsRegex.substring(0, part.index) +
'([^\\s/]+)' +
dynamicRoutePartsRegex.substring(part.index + part[0].length)
})
dynamicRoutePartsRegex = '^' + dynamicRoutePartsRegex
// test if the constructed regex matches the path
const match = originalPath.match(new RegExp(`${dynamicRoutePartsRegex}`, 'i'))
if (match) {
// map the route params to a params object
override.params = dynamicRouteParts.reverse().reduce((acc, part, index) => {
acc[part[1]] = match[index + 1]
return acc
}, {})
matchingRoute = makeRouteObject(route, override)
}
} else if (normalizedPath.endsWith('*')) {
const regex = new RegExp(normalizedPath.replace(/\/?\*/, '/?([^\\s]*)'), 'i')
const match = originalNormalizedPath.match(regex)
if (match) {
override.params = {}
if (match[1]) override.params.path = match[1]
matchingRoute = makeRouteObject(route, override)
}
}
i++
}
// @ts-ignore - Remove me when we have a better way to handle this
return matchingRoute
}
/**
* Default Route options
*
*/
const defaultOptions = {
inHistory: true,
keepAlive: false,
passFocus: true,
reuseComponent: false,
}
const makeRouteObject = (route, overrides) => {
// FIX: exclude keepAlive from the destination route options. Unlike other
// overrides, keepAlive applies to the route being LEFT, not the destination.
// It is consumed by removeView() instead.
const { keepAlive: _keepAlive, ...destOverrides } = overrideOptions // eslint-disable-line no-unused-vars
const cleanRoute = {
hash: overrides.hash,
path: route.path,
component: route.component,
transition: 'transition' in route ? route.transition : fadeInFadeOutTransition,
options: { ...defaultOptions, ...route.options, ...destOverrides },
announce: route.announce || false,
hooks: route.hooks || {},
data: { ...route.data, ...navigationData, ...overrides.queryParams },
params: overrides.params || {},
meta: route.meta || {},
}
return cleanRoute
}
/**
* Navigate to a route

@@ -255,312 +104,248 @@ *

export const navigate = async function () {
// early return when in preventHashChange mode
if (preventHashChangeNavigation !== false) return
// early return when no routes
if (!this[symbols.parent][symbols.routes] || this[symbols.parent][symbols.routes].length === 0)
return
state.navigating = true
Announcer.stop()
Announcer.clear()
state.navigating = true
let reuse = false
if (preventHashChangeNavigation === false && this[symbols.parent][symbols.routes]) {
let previousRoute = currentRoute //? Object.assign({}, currentRoute) : undefined
let route = matchHash(getHash(location.hash), this[symbols.parent][symbols.routes])
currentRoute = route
const hash = getHash(location.hash)
// try to find the route
let route = matchHash(hash, this[symbols.parent][symbols.routes], overrideOptions, navigationData)
if (route) {
const currentPath = currentRoute.path
let beforeEachResult
if (this[symbols.parent][symbols.routerHooks]) {
const hooks = this[symbols.parent][symbols.routerHooks]
if (hooks.beforeEach) {
try {
beforeEachResult = await hooks.beforeEach.call(
this[symbols.parent],
route,
previousRoute
)
if (isString(beforeEachResult)) {
currentRoute = previousRoute
to(beforeEachResult)
return
}
} catch (error) {
Log.error('Error or Rejected Promise in "BeforeEach" Hook', error)
// early return when route not found
if (route === false) {
state.navigating = false
if (history.length > 0) {
preventHashChangeNavigation = true
currentRoute = previousRoute
window.history.back()
Log.error(`Route ${hash.hash} not found`)
const routerHooks = this[symbols.parent][symbols.routerHooks]
if (routerHooks && typeof routerHooks.error === 'function') {
routerHooks.error.call(this[symbols.parent], `Route ${hash.hash} not found`)
}
return
}
navigatingBack = false
state.navigating = false
return
}
}
// If the resolved result is an object, redirect if the path in the object was changed
if (isObject(beforeEachResult) === true && beforeEachResult.path !== currentPath) {
currentRoute = previousRoute
to(beforeEachResult.path, beforeEachResult.data, beforeEachResult.options)
return
}
// If the resolved result is false, cancel navigation
if (beforeEachResult === false && history.length > 0) {
preventHashChangeNavigation = true
currentRoute = previousRoute
window.history.back()
let reuse = false
let previousRoute = currentRoute
currentRoute = route
const currentPath = currentRoute.path
navigatingBack = false
state.navigating = false
return
}
}
}
// execute before each hook
const beforeEachResult = await executeBeforeHook(
this[symbols.parent][symbols.routerHooks],
'beforeEach',
this[symbols.parent],
route,
previousRoute,
currentPath
)
if (beforeEachResult === false) {
preventHashChangeNavigation = false
return
}
let beforeHookOutput
if (route.hooks.before) {
try {
beforeHookOutput = await route.hooks.before.call(
this[symbols.parent],
route,
previousRoute
)
if (isString(beforeHookOutput)) {
currentRoute = previousRoute
to(beforeHookOutput)
return
}
} catch (error) {
Log.error('Error or Rejected Promise in "Before" Hook', error)
// execute before route hook
const beforeResult = await executeBeforeHook(
route.hooks,
'before',
this[symbols.parent],
route,
previousRoute,
currentPath
)
if (beforeResult === false) {
preventHashChangeNavigation = false
return
}
if (history.length > 0) {
preventHashChangeNavigation = true
currentRoute = previousRoute
window.history.back()
// add the previous route (technically still the current route at this point)
// into the history stack when inHistory is true and we're not navigating back
//
// FIX: use truthy check instead of `!== undefined` because matchHash()
// can return `false`, which survives `!== undefined` but has no `.options`.
if (
previousRoute &&
previousRoute.options &&
previousRoute.options.inHistory === true &&
navigatingBack === false
) {
history.push(previousRoute)
}
navigatingBack = false
state.navigating = false
return
}
}
// If the resolved result is an object, redirect if the path in the object was changed
if (isObject(beforeHookOutput) === true && beforeHookOutput.path !== currentPath) {
currentRoute = previousRoute
to(beforeHookOutput.path, beforeHookOutput.data, beforeHookOutput.options)
return
}
// If the resolved result is false, cancel navigation
if (beforeHookOutput === false && history.length > 0) {
preventHashChangeNavigation = true
currentRoute = previousRoute
window.history.back()
// a transition can be a function returning a dynamic transition object
// based on current and previous route
if (typeof route.transition === 'function') {
route.transition = route.transition(previousRoute, route)
}
navigatingBack = false
state.navigating = false
return
}
}
/** @type {import('../engines/L3/element.js').BlitsElement} */
let holder
// add the previous route (technically still the current route at this point)
// into the history stack when inHistory is true and we're not navigating back
//
// FIX: use truthy check instead of `!== undefined` because matchHash()
// can return `false`, which survives `!== undefined` but has no `.options`.
if (
previousRoute &&
previousRoute.options &&
previousRoute.options.inHistory === true &&
navigatingBack === false
) {
history.push(previousRoute)
}
/** @type {RouteViewWithOptionalDefault|undefined|null} */
let view
let focus
// when navigating back let's see if we're navigating back to a route that was kept alive
if (navigatingBack === true && navigatingBackTo !== undefined) {
view = navigatingBackTo.view
focus = navigatingBackTo.focus
navigatingBackTo = null
}
// merge props with potential route params, navigation data and route data to be injected into the component instance
const props = {
...this[symbols.props],
...route.params,
...route.data,
}
// a transition can be a function returning a dynamic transition object
// based on current and previous route
if (typeof route.transition === 'function') {
route.transition = route.transition(previousRoute, route)
}
// see if the component of the previous route can be reused for the
// current route
if (
previousRoute &&
route.options.reuseComponent === true &&
route.options.keepAlive !== true &&
route.component === previousRoute.component
) {
reuse = true
view = this[symbols.children][this[symbols.children].length - 1]
for (const prop in props) {
view[symbols.props][prop] = props[prop]
}
}
/** @type {import('../engines/L3/element.js').BlitsElement} */
let holder
// Announce route change if a message has been specified for this route
if (route.announce) {
if (typeof route.announce === 'string') {
Announcer.speak(route.announce)
} else {
Announcer.speak(route.announce.message, route.announce.politeness)
}
}
/** @type {RouteViewWithOptionalDefault|undefined|null} */
let view
let focus
// when navigating back let's see if we're navigating back to a route that was kept alive
if (navigatingBack === true && navigatingBackTo !== undefined) {
view = navigatingBackTo.view
focus = navigatingBackTo.focus
navigatingBackTo = null
}
// merge props with potential route params, navigation data and route data to be injected into the component instance
const props = {
...this[symbols.props],
...route.params,
...route.data,
}
// Update router state after announcements and final route resolution,
// right before initializing or restoring the view
state.path = route.path
state.params = Object.keys(route.params).length === 0 ? null : route.params
state.hash = route.hash
state.data = null
state.data = route.data || {}
// see if the component of the previous route can be reused for the
// current route
if (
previousRoute &&
route.options.reuseComponent === true &&
route.options.keepAlive !== true &&
route.component === previousRoute.component
) {
reuse = true
view = this[symbols.children][this[symbols.children].length - 1]
for (const prop in props) {
view[symbols.props][prop] = props[prop]
}
}
// routing to a new page (instead of routing back to a keepAlive page)
if (view === undefined) {
// create a holder element for the new view
holder = stage.element({ parent: this[symbols.children][0] })
holder.populate({})
holder.set('w', '100%')
holder.set('h', '100%')
// Announce route change if a message has been specified for this route
if (route.announce) {
if (typeof route.announce === 'string') {
route.announce = {
message: route.announce,
}
}
Announcer.speak(route.announce.message, route.announce.politeness)
}
view = await loadPage.call(this, route, holder, props)
} else {
holder = view[symbols.holder]
// Update router state after announcements and final route resolution,
// right before initializing or restoring the view
state.path = route.path
state.params = Object.keys(route.params).length === 0 ? null : route.params
state.hash = route.hash
state.data = null
state.data = route.data || {}
if (!view) {
// create a holder element for the new view
holder = stage.element({ parent: this[symbols.children][0] })
holder.populate({})
holder.set('w', '100%')
holder.set('h', '100%')
view = await route.component({ props }, holder, this)
// is the component a dynamic module?
if (view[Symbol.toStringTag] === 'Module') {
if (view.default && typeof view.default === 'function') {
view = view.default({ props }, holder, this)
} else {
Log.error("Dynamic import doesn't have a default export or default is not a function")
}
}
if (typeof view === 'function') {
// had to inline this because the tscompiler does not like LHS reassignments
// that also change the type of the variable in a variable union
view = /** @type {BlitsComponentFactory} */ (view)({ props }, holder, this)
}
} else {
holder = view[symbols.holder]
// Check, whether cached view holder's alpha prop is exists in transition or not
let hasAlphaProp = false
if (route.transition.before) {
if (Array.isArray(route.transition.before)) {
for (let i = 0; i < route.transition.before.length; i++) {
if (route.transition.before[i].prop === 'alpha') {
hasAlphaProp = true
break
}
}
} else if (route.transition.before.prop === 'alpha') {
// Check, whether cached view holder's alpha prop is exists in transition or not
let hasAlphaProp = false
if (route.transition.before) {
if (Array.isArray(route.transition.before)) {
for (let i = 0; i < route.transition.before.length; i++) {
if (route.transition.before[i].prop === 'alpha') {
hasAlphaProp = true
break
}
}
// set holder alpha when alpha prop is not exists in route transition
if (hasAlphaProp === false) {
holder.set('alpha', 1)
}
} else if (route.transition.before.prop === 'alpha') {
hasAlphaProp = true
}
}
// set holder alpha when alpha prop is not exists in route transition
if (hasAlphaProp === false) {
holder.set('alpha', 1)
}
}
// store the new view as new child, only if we're not reusing the previous page component
if (reuse === false) {
this[symbols.children].push(view)
}
// store the new view as new child, only if we're not reusing the previous page component
if (reuse === false) {
this[symbols.children].push(view)
}
// keep reference to the previous focus for storing in cache
previousFocus = Focus.get()
// keep reference to the previous focus for storing in cache
previousFocus = Focus.get()
const children = this[symbols.children]
this.activeView = children[children.length - 1]
const children = this[symbols.children]
this.activeView = children[children.length - 1]
// set focus to the view that we're routing to (unless explicitly disabling passing focus)
if (route.options.passFocus !== false) {
focus ? focus.$focus() : /** @type {BlitsComponent} */ (view).$focus()
}
// set focus to the view that we're routing to (unless explicitly disabling passing focus)
if (route.options.passFocus !== false) {
focus ? focus.$focus() : /** @type {BlitsComponent} */ (view).$focus()
}
// apply before settings to holder element
if (route.transition.before) {
if (Array.isArray(route.transition.before)) {
for (let i = 0; i < route.transition.before.length; i++) {
holder.set(route.transition.before[i].prop, route.transition.before[i].value)
}
} else {
holder.set(route.transition.before.prop, route.transition.before.value)
}
}
// apply starting state of transition
if (route.transition.before) {
await executeTransition(route.transition.before, holder, false)
}
let shouldAnimate = false
let shouldAnimate = false
// apply out out transition on previous view if available, unless
// we're reusing the prvious page component
// FIX: truthy guard — previousRoute can be `false` (see history-push comment above).
if (previousRoute && reuse === false) {
// only animate when there is a previous route
shouldAnimate = true
const oldView = this[symbols.children].splice(1, 1).pop()
if (oldView) {
await removeView(previousRoute, oldView, route.transition.out, navigatingBack)
}
}
// apply out out transition on previous view if available, unless
// we're reusing the prvious page component
// FIX: truthy guard — previousRoute can be `false` (see history-push comment above).
if (previousRoute && reuse === false) {
// only animate when there is a previous route
shouldAnimate = true
let oldView = this[symbols.children].splice(1, 1).pop()
if (oldView) {
executeTransition(previousRoute.transition.out, oldView[symbols.holder], true)
// apply in transition
if (route.transition.in) {
if (Array.isArray(route.transition.in)) {
for (let i = 0; i < route.transition.in.length; i++) {
i === route.transition.in.length - 1
? await setOrAnimate(holder, route.transition.in[i], shouldAnimate)
: setOrAnimate(holder, route.transition.in[i], shouldAnimate)
}
} else {
await setOrAnimate(holder, route.transition.in, shouldAnimate)
// Resolve effective keepAlive: runtime override from $router.to() takes precedence
// over the static route config option
const keepAlive =
overrideOptions.keepAlive !== undefined
? overrideOptions.keepAlive
: previousRoute.options && previousRoute.options.keepAlive
// cache the page when it's as 'keepAlive' instead of destroying
if (
navigatingBack === false &&
keepAlive === true &&
previousRoute.options &&
previousRoute.options.inHistory === true
) {
const historyItem = history[history.length - 1]
if (historyItem !== undefined) {
historyItem.view = oldView
historyItem.focus = previousFocus
}
}
if (this[symbols.parent][symbols.routerHooks]) {
const hooks = this[symbols.parent][symbols.routerHooks]
if (hooks.afterEach) {
try {
await hooks.afterEach.call(
this[symbols.parent],
route, // to
previousRoute // from
)
} catch (error) {
Log.error('Error in "AfterEach" Hook', error)
}
}
/* Destroy the view in the following cases:
* 1. Navigating forward, and the previous route is not configured with "keep alive" set to true.
* 2. Navigating back, and the previous route is configured with "keep alive" set to true.
* 3. Navigating back, and the previous route is not configured with "keep alive" set to true.
*/
if (previousRoute.options && (keepAlive !== true || navigatingBack === true)) {
oldView.destroy()
oldView = null
}
if (route.hooks.after) {
try {
await route.hooks.after.call(
this[symbols.parent],
route, // to
previousRoute // from
)
} catch (error) {
Log.error('Error or Rejected Promise in "After" Hook', error)
}
}
} else {
Log.error(`Route ${route.hash} not found`)
const routerHooks = this[symbols.parent][symbols.routerHooks]
if (routerHooks && typeof routerHooks.error === 'function') {
routerHooks.error.call(this[symbols.parent], `Route ${route.hash} not found`)
}
previousFocus = null
}
}
// apply in transition
if (route.transition.in) await executeTransition(route.transition.in, holder, shouldAnimate)
// execute after each Hook
await executeAfterHook(
this[symbols.parent][symbols.routerHooks],
'afterEach',
this[symbols.parent],
route,
previousRoute
)
// execute after route Hook
await executeAfterHook(route.hooks, 'after', this[symbols.parent], route, previousRoute)
// Clear module-level variables after removeView has consumed them.

@@ -578,79 +363,116 @@ // Placed here so it executes for all navigation flows, not only when

/**
* Remove the currently active view
*
* @param {Route} route
* @param {BlitsComponent} view
* @param {Object} transition
*/
const removeView = async (route, view, transition, navigatingBack) => {
// apply out transition
if (transition) {
if (Array.isArray(transition)) {
for (let i = 0; i < transition.length; i++) {
i === transition.length - 1
? await setOrAnimate(view[symbols.holder], transition[i])
: setOrAnimate(view[symbols.holder], transition[i])
const setOrAnimate = (element, transition, shouldAnimate = true) => {
if (shouldAnimate === true) {
return new Promise((resolve) => {
// resolve the promise in the transition end-callback
// ("extending" end callback when one is already specified)
let existingEndCallback = transition.end
transition.end = (...args) => {
existingEndCallback && existingEndCallback(args)
// null the callback to enable memory cleanup
existingEndCallback = null
resolve()
}
} else {
await setOrAnimate(view[symbols.holder], transition)
if (element !== undefined) element.set(transition.prop, { transition })
else resolve()
})
} else {
element !== undefined && element.set(transition.prop, transition.value)
return true
}
}
const executeBeforeHook = async function (
hooks,
hookName,
parent,
route,
previousRoute,
currentPath
) {
let result
if (hooks && hooks[hookName]) {
try {
result = await hooks[hookName].call(parent, route, previousRoute)
if (isString(result)) {
currentRoute = previousRoute
to(result)
return false
}
} catch (error) {
Log.error(`Error or Rejected Promise in "${hookName}" Hook`, error)
if (history.length > 0) {
preventHashChangeNavigation = true
currentRoute = previousRoute
window.history.back()
navigatingBack = false
state.navigating = false
return false
}
}
// If the resolved result is an object, redirect if the path in the object was changed
if (isObject(result) === true && result.path !== currentPath) {
currentRoute = previousRoute
to(result.path, result.data, result.options)
return false
}
// If the resolved result is false, cancel navigation
if (result === false && history.length > 0) {
preventHashChangeNavigation = true
currentRoute = previousRoute
window.history.back()
navigatingBack = false
state.navigating = false
return false
}
}
}
// Resolve effective keepAlive: runtime override from $router.to() takes precedence
// over the static route config option
const keepAlive =
overrideOptions.keepAlive !== undefined
? overrideOptions.keepAlive
: route.options && route.options.keepAlive
const loadPage = async function (route, holder, props) {
let view = await route.component({ props }, holder, this)
// cache the page when it's as 'keepAlive' instead of destroying
if (
navigatingBack === false &&
route.options &&
keepAlive === true &&
route.options.inHistory === true
) {
const historyItem = history[history.length - 1]
if (historyItem !== undefined) {
historyItem.view = view
historyItem.focus = previousFocus
// is the component a dynamic module?
if (view[Symbol.toStringTag] === 'Module') {
if (view.default && typeof view.default === 'function') {
view = view.default({ props }, holder, this)
} else {
Log.error("Dynamic import doesn't have a default export or default is not a function")
}
}
/* Destroy the view in the following cases:
* 1. Navigating forward, and the previous route is not configured with "keep alive" set to true.
* 2. Navigating back, and the previous route is configured with "keep alive" set to true.
* 3. Navigating back, and the previous route is not configured with "keep alive" set to true.
*/
if (route.options && (keepAlive !== true || navigatingBack === true)) {
view.destroy()
view = null
if (typeof view === 'function') {
// had to inline this because the tscompiler does not like LHS reassignments
// that also change the type of the variable in a variable union
view = /** @type {BlitsComponentFactory} */ (view)({ props }, holder, this)
}
previousFocus = null
route = null
return view
}
const setOrAnimate = (node, transition, shouldAnimate = true) => {
return new Promise((resolve) => {
if (shouldAnimate === true) {
// resolve the promise in the transition end-callback
// ("extending" end callback when one is already specified)
let existingEndCallback = transition.end
transition.end = (...args) => {
existingEndCallback && existingEndCallback(args)
// null the callback to enable memory cleanup
existingEndCallback = null
resolve()
}
if (node !== undefined) node.set(transition.prop, { transition })
else resolve()
} else {
node !== undefined && node.set(transition.prop, transition.value)
resolve()
const executeAfterHook = async function (hooks, hookName, parent, route, previousRoute) {
if (hooks && hooks[hookName]) {
try {
await hooks[hookName].call(
parent,
route, // to
previousRoute // from
)
} catch (error) {
Log.error(`Error or Rejected Promise in "${hookName}" Hook`, error)
}
})
}
}
const executeTransition = async (transition, element, animate) => {
if (Array.isArray(transition)) {
for (let i = 0; i < transition.length; i++) {
i === transition.length - 1
? await setOrAnimate(element, transition[i], animate)
: setOrAnimate(element, transition[i], animate)
}
} else {
await setOrAnimate(element, transition, animate)
}
}
export const to = (path, data = {}, options = {}) => {

@@ -697,3 +519,8 @@ navigationData = data

path = path.replace(hashEnd, '')
const route = matchHash(getHash(path), this[symbols.parent][symbols.routes])
const route = matchHash(
getHash(path),
this[symbols.parent][symbols.routes],
overrideOptions,
navigationData
)

@@ -700,0 +527,0 @@ if (route && backtrack) {