@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.
@@ -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
+ }