@lightningjs/blits 2.2.0 → 2.4.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.
- package/CHANGELOG.md +14 -0
- package/index.d.ts +13 -2
- package/package.json +1 -1
- package/src/announcer/announcer.js +1 -1
- package/src/announcer/speechSynthesis.js +1 -1
- package/src/component/base/router.js +136 -22
- package/src/components/RouterView.js +27 -7
- package/src/focus/focus.js +1 -0
- package/src/lib/reactivityguard/computedprops.js +235 -153
- package/src/router/router.js +381 -503
- package/src/router/utils.js +245 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { default as fadeInFadeOutTransition } from './transitions/fadeInOut.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Get the current hash
|
|
5
|
+
* @returns {Hash}
|
|
6
|
+
*/
|
|
7
|
+
export const getHash = (hash, routerViewName = '') => {
|
|
8
|
+
if (!hash) hash = '/'
|
|
9
|
+
|
|
10
|
+
// split router views
|
|
11
|
+
const [main, ...named] = hash.split('|')
|
|
12
|
+
|
|
13
|
+
if (routerViewName !== '') {
|
|
14
|
+
const match = named.find((name) => name.startsWith(routerViewName + '='))
|
|
15
|
+
if (match !== undefined) {
|
|
16
|
+
hash = match.slice(routerViewName.length + 1)
|
|
17
|
+
} else {
|
|
18
|
+
hash = '/'
|
|
19
|
+
}
|
|
20
|
+
} else {
|
|
21
|
+
hash = main
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const [path = '', query = ''] = hash.replace(/^#/, '').split('?')
|
|
25
|
+
return {
|
|
26
|
+
path: path,
|
|
27
|
+
queryParams: new URLSearchParams(query),
|
|
28
|
+
hash: hash,
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const setHash = (path, routerViewName = '') => {
|
|
33
|
+
const cleanPath = (path || '').replace(/^#/, '')
|
|
34
|
+
const raw = (location.hash || '').replace(/^#/, '')
|
|
35
|
+
|
|
36
|
+
const parts = raw ? raw.split('|').filter(Boolean) : []
|
|
37
|
+
|
|
38
|
+
let newMain = '/'
|
|
39
|
+
const newNamed = {}
|
|
40
|
+
|
|
41
|
+
// Parse existing hash. The first segment is always the main route; only
|
|
42
|
+
// subsequent pipe-separated segments can be named router views.
|
|
43
|
+
for (let i = 0; i < parts.length; i++) {
|
|
44
|
+
const part = parts[i]
|
|
45
|
+
if (i === 0) {
|
|
46
|
+
newMain = part
|
|
47
|
+
continue
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const eqIndex = part.indexOf('=')
|
|
51
|
+
|
|
52
|
+
// named route: sidebar=/search
|
|
53
|
+
if (eqIndex > 0) {
|
|
54
|
+
const name = part.slice(0, eqIndex)
|
|
55
|
+
const value = part.slice(eqIndex + 1)
|
|
56
|
+
if (value) newNamed[name] = value
|
|
57
|
+
continue
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// unnamed/main route
|
|
61
|
+
if (part) {
|
|
62
|
+
newMain = part
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Update target router view
|
|
67
|
+
if (routerViewName === '') {
|
|
68
|
+
newMain = cleanPath || '/'
|
|
69
|
+
} else {
|
|
70
|
+
if (cleanPath) {
|
|
71
|
+
newNamed[routerViewName] = cleanPath
|
|
72
|
+
} else {
|
|
73
|
+
delete newNamed[routerViewName]
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Rebuild hash: main always first
|
|
78
|
+
let newHash = newMain || '/'
|
|
79
|
+
|
|
80
|
+
for (const [name, value] of Object.entries(newNamed)) {
|
|
81
|
+
newHash += `|${name}=${value}`
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
location.hash = newHash
|
|
85
|
+
}
|
|
86
|
+
export const normalizePath = (path) => {
|
|
87
|
+
return (
|
|
88
|
+
path
|
|
89
|
+
// remove leading and trailing slashes
|
|
90
|
+
.replace(/^\/+|\/+$/g, '')
|
|
91
|
+
.toLowerCase()
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Check if a value is an object
|
|
97
|
+
* @param {any} v
|
|
98
|
+
* @returns {boolean} True if v is an object
|
|
99
|
+
*/
|
|
100
|
+
export const isObject = (v) => typeof v === 'object' && v !== null
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Check if a value is a string
|
|
104
|
+
* @param {any} v
|
|
105
|
+
* @returns {boolean} True if v is a string
|
|
106
|
+
*/
|
|
107
|
+
export const isString = (v) => typeof v === 'string'
|
|
108
|
+
|
|
109
|
+
export const queryParamsToObject = (queryParams) => {
|
|
110
|
+
if (!queryParams) return {}
|
|
111
|
+
const object = {}
|
|
112
|
+
const queryParamsEntries = [...queryParams.entries()]
|
|
113
|
+
for (let i = 0; i < queryParamsEntries.length; i++) {
|
|
114
|
+
object[queryParamsEntries[i][0]] = queryParamsEntries[i][1]
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return object
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Default Route options
|
|
122
|
+
*
|
|
123
|
+
*/
|
|
124
|
+
const defaultOptions = {
|
|
125
|
+
inHistory: true,
|
|
126
|
+
keepAlive: false,
|
|
127
|
+
passFocus: true,
|
|
128
|
+
reuseComponent: false,
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export const makeRouteObject = (route, overrides, overrideOptions, navigationData) => {
|
|
132
|
+
const options = { ...defaultOptions, ...route.options }
|
|
133
|
+
|
|
134
|
+
// keepAlive applies to the route being left, so do not merge it into the destination.
|
|
135
|
+
if (isObject(overrideOptions)) {
|
|
136
|
+
const keys = Object.keys(overrideOptions)
|
|
137
|
+
for (let i = 0; i < keys.length; i++) {
|
|
138
|
+
const key = keys[i]
|
|
139
|
+
if (key !== 'keepAlive') {
|
|
140
|
+
options[key] = overrideOptions[key]
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const cleanRoute = {
|
|
146
|
+
hash: overrides.hash,
|
|
147
|
+
path: route.path,
|
|
148
|
+
component: route.component,
|
|
149
|
+
transition: 'transition' in route ? route.transition : fadeInFadeOutTransition,
|
|
150
|
+
options,
|
|
151
|
+
announce: route.announce || false,
|
|
152
|
+
hooks: route.hooks || {},
|
|
153
|
+
data: { ...route.data, ...navigationData, ...overrides.queryParams },
|
|
154
|
+
params: overrides.params || {},
|
|
155
|
+
meta: route.meta || {},
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return cleanRoute
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Match a path to a route
|
|
163
|
+
*
|
|
164
|
+
* @param {object} hashObject
|
|
165
|
+
* @param {Route[]} routes
|
|
166
|
+
* @returns {Route}
|
|
167
|
+
*/
|
|
168
|
+
export const matchHash = (
|
|
169
|
+
{ hash, path, queryParams },
|
|
170
|
+
routes = [],
|
|
171
|
+
overrideOptions = {},
|
|
172
|
+
navigationData = {}
|
|
173
|
+
) => {
|
|
174
|
+
// remove trailing slashes
|
|
175
|
+
const originalPath = path.replace(/^\/+|\/+$/g, '')
|
|
176
|
+
const originalNormalizedPath = normalizePath(path)
|
|
177
|
+
|
|
178
|
+
const override = {
|
|
179
|
+
hash: hash,
|
|
180
|
+
queryParams: queryParamsToObject(queryParams),
|
|
181
|
+
path: path,
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** @type {boolean|Route} */
|
|
185
|
+
let matchingRoute = false
|
|
186
|
+
let i = 0
|
|
187
|
+
while (!matchingRoute && i < routes.length) {
|
|
188
|
+
const route = routes[i]
|
|
189
|
+
|
|
190
|
+
const normalizedPath = normalizePath(route.path)
|
|
191
|
+
if (normalizePath(normalizedPath) === originalNormalizedPath) {
|
|
192
|
+
matchingRoute = makeRouteObject(route, override, overrideOptions, navigationData)
|
|
193
|
+
} else if (normalizedPath.indexOf(':') > -1) {
|
|
194
|
+
// match dynamic route parts
|
|
195
|
+
const dynamicRouteParts = [...normalizedPath.matchAll(/:([^\s/]+)/gi)]
|
|
196
|
+
|
|
197
|
+
// construct a regex for the route with dynamic parts
|
|
198
|
+
let dynamicRoutePartsRegex = normalizedPath
|
|
199
|
+
dynamicRouteParts.reverse().forEach((part) => {
|
|
200
|
+
dynamicRoutePartsRegex =
|
|
201
|
+
dynamicRoutePartsRegex.substring(0, part.index) +
|
|
202
|
+
'([^\\s/]+)' +
|
|
203
|
+
dynamicRoutePartsRegex.substring(part.index + part[0].length)
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
dynamicRoutePartsRegex = '^' + dynamicRoutePartsRegex
|
|
207
|
+
|
|
208
|
+
// test if the constructed regex matches the path
|
|
209
|
+
const match = originalPath.match(new RegExp(`${dynamicRoutePartsRegex}`, 'i'))
|
|
210
|
+
|
|
211
|
+
if (match) {
|
|
212
|
+
// map the route params to a params object
|
|
213
|
+
override.params = dynamicRouteParts.reverse().reduce((acc, part, index) => {
|
|
214
|
+
acc[part[1]] = match[index + 1]
|
|
215
|
+
return acc
|
|
216
|
+
}, {})
|
|
217
|
+
|
|
218
|
+
matchingRoute = makeRouteObject(route, override, overrideOptions, navigationData)
|
|
219
|
+
}
|
|
220
|
+
} else if (normalizedPath.endsWith('*')) {
|
|
221
|
+
const regex = new RegExp(normalizedPath.replace(/\/?\*/, '/?([^\\s]*)'), 'i')
|
|
222
|
+
const match = originalNormalizedPath.match(regex)
|
|
223
|
+
|
|
224
|
+
if (match) {
|
|
225
|
+
override.params = {}
|
|
226
|
+
if (match[1]) override.params.path = match[1]
|
|
227
|
+
matchingRoute = makeRouteObject(route, override, overrideOptions, navigationData)
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
i++
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// @ts-ignore - Remove me when we have a better way to handle this
|
|
234
|
+
return matchingRoute
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export const sameRouteObject = (route1, route2) => {
|
|
238
|
+
if (route1 === null) return false
|
|
239
|
+
if (route2 === null) return false
|
|
240
|
+
|
|
241
|
+
// this is too simple a check for now
|
|
242
|
+
if (route1.hash !== route2.hash) return false
|
|
243
|
+
|
|
244
|
+
return true
|
|
245
|
+
}
|