@lightningjs/blits 2.3.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 +7 -0
- package/index.d.ts +13 -2
- package/package.json +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 +92 -41
- package/src/router/utils.js +96 -9
package/CHANGELOG.md
CHANGED
package/index.d.ts
CHANGED
|
@@ -329,7 +329,14 @@ declare module '@lightningjs/blits' {
|
|
|
329
329
|
*
|
|
330
330
|
* @param {string}
|
|
331
331
|
*/
|
|
332
|
-
to(location: string, data?: RouteData, options?: RouteOptions):
|
|
332
|
+
to(location: string, data?: RouteData, options?: RouteOptions): boolean;
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Get a router scoped to a named RouterView
|
|
336
|
+
*
|
|
337
|
+
* @param {string}
|
|
338
|
+
*/
|
|
339
|
+
get(name: string): Router;
|
|
333
340
|
|
|
334
341
|
/**
|
|
335
342
|
* Navigate to the previous location
|
|
@@ -707,6 +714,10 @@ declare module '@lightningjs/blits' {
|
|
|
707
714
|
* Watchers for changes to state variables, props or computed properties
|
|
708
715
|
*/
|
|
709
716
|
watch?: W & ComponentContext<P, S, M, C>
|
|
717
|
+
/**
|
|
718
|
+
* Router Configuration
|
|
719
|
+
*/
|
|
720
|
+
router?: RouterConfig<P, S, M, C>
|
|
710
721
|
}
|
|
711
722
|
|
|
712
723
|
export interface RouterHooks {
|
|
@@ -759,7 +770,7 @@ declare module '@lightningjs/blits' {
|
|
|
759
770
|
|
|
760
771
|
export type ApplicationConfig<P extends Props, S, M, C, W> = Omit<
|
|
761
772
|
ComponentConfig<P, S, M, C, W>,
|
|
762
|
-
'hooks' | 'methods' | 'input' | 'computed' | 'watch' | 'state'
|
|
773
|
+
'hooks' | 'methods' | 'input' | 'computed' | 'watch' | 'state' | 'router' | 'routes'
|
|
763
774
|
> & {
|
|
764
775
|
hooks?: Hooks & ApplicationContext<P, S, M, C>
|
|
765
776
|
methods?: M & ApplicationContext<P, S, M, C>
|
package/package.json
CHANGED
|
@@ -16,31 +16,145 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import symbols from '../../lib/symbols.js'
|
|
19
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
back,
|
|
21
|
+
currentRoute,
|
|
22
|
+
getRegisteredRouterView,
|
|
23
|
+
getSingleRegisteredRouterView,
|
|
24
|
+
state,
|
|
25
|
+
toRouterView,
|
|
26
|
+
} from '../../router/router.js'
|
|
27
|
+
|
|
28
|
+
const routerCache = new WeakMap()
|
|
29
|
+
|
|
30
|
+
const isRouterView = (component, routerViewName = '') => {
|
|
31
|
+
if (component === undefined || component === null) return false
|
|
32
|
+
return (
|
|
33
|
+
Array.isArray(component.history) && (routerViewName === '' || component.name === routerViewName)
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const getRouterViewInChildren = (component, routerViewName = '') => {
|
|
38
|
+
if (component === undefined || component === null) return null
|
|
39
|
+
|
|
40
|
+
const children = component[symbols.children]
|
|
41
|
+
if (Array.isArray(children) === false) return null
|
|
42
|
+
|
|
43
|
+
for (let i = 0; i < children.length; i++) {
|
|
44
|
+
const child = children[i]
|
|
45
|
+
if (isRouterView(child, routerViewName)) return child
|
|
46
|
+
|
|
47
|
+
const nestedRouterView = getRouterViewInChildren(child, routerViewName)
|
|
48
|
+
if (nestedRouterView !== null) return nestedRouterView
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return null
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const getRouterView = (component, routerViewName = '') => {
|
|
55
|
+
let current = component
|
|
56
|
+
while (current !== undefined && current !== null) {
|
|
57
|
+
if (isRouterView(current, routerViewName)) {
|
|
58
|
+
return current
|
|
59
|
+
}
|
|
60
|
+
current = current[symbols.parent]
|
|
61
|
+
}
|
|
62
|
+
return getRouterViewInChildren(component, routerViewName)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const resolveRouterView = (component, routerViewName) => {
|
|
66
|
+
if (routerViewName !== undefined) {
|
|
67
|
+
const registeredRouterView = getRegisteredRouterView(routerViewName)
|
|
68
|
+
if (registeredRouterView !== null) return registeredRouterView
|
|
69
|
+
|
|
70
|
+
return getRouterView(component, routerViewName)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const singleRouterView = getSingleRegisteredRouterView()
|
|
74
|
+
if (singleRouterView !== null) return singleRouterView
|
|
75
|
+
|
|
76
|
+
return getRouterView(component)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const getRoutes = (component, routerViewName) => {
|
|
80
|
+
const routerView = resolveRouterView(component, routerViewName)
|
|
81
|
+
if (
|
|
82
|
+
routerView !== null &&
|
|
83
|
+
routerView[symbols.parent] !== undefined &&
|
|
84
|
+
routerView[symbols.parent][symbols.routes] !== undefined
|
|
85
|
+
) {
|
|
86
|
+
return routerView[symbols.parent][symbols.routes]
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (component[symbols.routes] !== undefined) return component[symbols.routes]
|
|
90
|
+
|
|
91
|
+
if (
|
|
92
|
+
component[symbols.parent] !== undefined &&
|
|
93
|
+
component[symbols.parent][symbols.routes] !== undefined
|
|
94
|
+
) {
|
|
95
|
+
return component[symbols.parent][symbols.routes]
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return undefined
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const getRouterFromCache = (component, routerViewName) => {
|
|
102
|
+
let routers = routerCache.get(component)
|
|
103
|
+
if (routers === undefined) {
|
|
104
|
+
routers = new Map()
|
|
105
|
+
routerCache.set(component, routers)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const cacheKey = routerViewName === undefined ? '' : routerViewName
|
|
109
|
+
const router = routers.get(cacheKey)
|
|
110
|
+
if (router !== undefined) return router
|
|
111
|
+
|
|
112
|
+
const newRouter = createRouter(component, routerViewName)
|
|
113
|
+
routers.set(cacheKey, newRouter)
|
|
114
|
+
return newRouter
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const createRouter = (component, routerViewName) => {
|
|
118
|
+
return {
|
|
119
|
+
to(path, data = {}, options = {}) {
|
|
120
|
+
const routerView = resolveRouterView(component, routerViewName)
|
|
121
|
+
if (routerView === null) return false
|
|
122
|
+
|
|
123
|
+
toRouterView(routerView, path, data, options)
|
|
124
|
+
return true
|
|
125
|
+
},
|
|
126
|
+
back() {
|
|
127
|
+
const routerView = resolveRouterView(component, routerViewName)
|
|
128
|
+
return routerView === null ? false : back.call(routerView)
|
|
129
|
+
},
|
|
130
|
+
get(name) {
|
|
131
|
+
return getRouterFromCache(component, name)
|
|
132
|
+
},
|
|
133
|
+
get backNavigation() {
|
|
134
|
+
return state.backNavigation !== false
|
|
135
|
+
},
|
|
136
|
+
set backNavigation(enabled) {
|
|
137
|
+
state.backNavigation = enabled !== false
|
|
138
|
+
},
|
|
139
|
+
get currentRoute() {
|
|
140
|
+
const routerView = resolveRouterView(component, routerViewName)
|
|
141
|
+
return routerView === null ? currentRoute : routerView.currentRoute
|
|
142
|
+
},
|
|
143
|
+
get routes() {
|
|
144
|
+
return getRoutes(component, routerViewName)
|
|
145
|
+
},
|
|
146
|
+
get navigating() {
|
|
147
|
+
return state.navigating
|
|
148
|
+
},
|
|
149
|
+
state,
|
|
150
|
+
}
|
|
151
|
+
}
|
|
20
152
|
|
|
21
153
|
export default {
|
|
22
154
|
$router: {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
get backNavigation() {
|
|
27
|
-
return state.backNavigation !== false
|
|
28
|
-
},
|
|
29
|
-
set backNavigation(enabled) {
|
|
30
|
-
state.backNavigation = enabled !== false
|
|
31
|
-
},
|
|
32
|
-
get currentRoute() {
|
|
33
|
-
return currentRoute
|
|
34
|
-
},
|
|
35
|
-
get routes() {
|
|
36
|
-
return this[symbols.routes]
|
|
37
|
-
},
|
|
38
|
-
get navigating() {
|
|
39
|
-
return state.navigating
|
|
40
|
-
},
|
|
41
|
-
state,
|
|
42
|
-
},
|
|
43
|
-
writable: false,
|
|
155
|
+
get() {
|
|
156
|
+
return getRouterFromCache(this)
|
|
157
|
+
},
|
|
44
158
|
enumerable: true,
|
|
45
159
|
configurable: false,
|
|
46
160
|
},
|
|
@@ -16,12 +16,14 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import Component from '../component.js'
|
|
19
|
-
import Router, {
|
|
19
|
+
import Router, {
|
|
20
|
+
registerRouterView,
|
|
21
|
+
state as routerState,
|
|
22
|
+
unregisterRouterView,
|
|
23
|
+
} from '../router/router.js'
|
|
20
24
|
import symbols from '../lib/symbols.js'
|
|
21
25
|
import Focus from '../focus/focus.js'
|
|
22
26
|
|
|
23
|
-
let hashchangeHandler = null
|
|
24
|
-
|
|
25
27
|
/** @typedef {{ $input?: (event: any) => boolean, $focus?: (event: any) => void }} RouterViewParent */
|
|
26
28
|
|
|
27
29
|
export default () =>
|
|
@@ -123,24 +125,42 @@ export default () =>
|
|
|
123
125
|
effects: [],
|
|
124
126
|
context: {},
|
|
125
127
|
},
|
|
128
|
+
props: {
|
|
129
|
+
name: '',
|
|
130
|
+
active: true,
|
|
131
|
+
},
|
|
126
132
|
state() {
|
|
127
133
|
return {
|
|
128
134
|
activeView: null,
|
|
135
|
+
history: [],
|
|
129
136
|
}
|
|
130
137
|
},
|
|
138
|
+
watch: {
|
|
139
|
+
active(v, old) {
|
|
140
|
+
if (v === true && old === false) {
|
|
141
|
+
Router.navigate.apply(this)
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
},
|
|
131
145
|
hooks: {
|
|
132
146
|
async ready() {
|
|
147
|
+
registerRouterView(this)
|
|
148
|
+
|
|
133
149
|
const parent = this[symbols.parent]
|
|
134
150
|
if (parent && parent[symbols.routerHooks] && parent[symbols.routerHooks].init) {
|
|
135
151
|
await parent[symbols.routerHooks].init.apply(parent)
|
|
136
152
|
}
|
|
153
|
+
this.hashchangeHandler = () => Router.navigate.apply(this)
|
|
154
|
+
|
|
155
|
+
if (this.active === true) {
|
|
156
|
+
Router.navigate.apply(this)
|
|
157
|
+
}
|
|
137
158
|
|
|
138
|
-
|
|
139
|
-
Router.navigate.apply(this)
|
|
140
|
-
window.addEventListener('hashchange', hashchangeHandler)
|
|
159
|
+
window.addEventListener('hashchange', this.hashchangeHandler)
|
|
141
160
|
},
|
|
142
161
|
destroy() {
|
|
143
|
-
|
|
162
|
+
unregisterRouterView(this)
|
|
163
|
+
window.removeEventListener('hashchange', this.hashchangeHandler, false)
|
|
144
164
|
},
|
|
145
165
|
focus() {
|
|
146
166
|
if (this.activeView && Focus.get() === this) {
|
package/src/focus/focus.js
CHANGED
|
@@ -100,176 +100,258 @@ const skipNonCode = (text, pos) => {
|
|
|
100
100
|
return p
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const
|
|
106
|
-
|
|
103
|
+
// Skip a non-method computed member (`key: value`) and return the position after it.
|
|
104
|
+
const skipComputedMember = (text, pos) => {
|
|
105
|
+
const keyMatch = /^(\w+)\s*:\s*/.exec(text.substring(pos))
|
|
106
|
+
if (!keyMatch) return -1
|
|
107
|
+
|
|
108
|
+
let p = pos + keyMatch[0].length
|
|
109
|
+
p = skipNonCode(text, p)
|
|
110
|
+
if (p >= text.length - 1) return p
|
|
111
|
+
|
|
112
|
+
if (text.startsWith('function', p)) {
|
|
113
|
+
const open = text.indexOf('{', p)
|
|
114
|
+
if (open === -1) return -1
|
|
115
|
+
const close = findMatchingBrace(text, open)
|
|
116
|
+
return close === -1 ? -1 : close
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
let depth = 0
|
|
120
|
+
let inString = false
|
|
121
|
+
let stringChar = ''
|
|
122
|
+
|
|
123
|
+
for (; p < text.length - 1; p++) {
|
|
124
|
+
const char = text[p]
|
|
125
|
+
|
|
126
|
+
if (!inString && (char === '"' || char === "'" || char === '`')) {
|
|
127
|
+
inString = true
|
|
128
|
+
stringChar = char
|
|
129
|
+
} else if (inString && char === stringChar && text[p - 1] !== '\\') {
|
|
130
|
+
inString = false
|
|
131
|
+
} else if (!inString && char === '/' && text[p + 1] === '/') {
|
|
132
|
+
const nextNewline = text.indexOf('\n', p + 2)
|
|
133
|
+
p = nextNewline === -1 ? text.length - 2 : nextNewline
|
|
134
|
+
} else if (!inString && char === '/' && text[p + 1] === '*') {
|
|
135
|
+
const end = text.indexOf('*/', p + 2)
|
|
136
|
+
p = end === -1 ? text.length - 2 : end + 1
|
|
137
|
+
} else if (!inString) {
|
|
138
|
+
if (char === '{' || char === '(' || char === '[') depth++
|
|
139
|
+
else if (char === '}' || char === ')' || char === ']') depth--
|
|
140
|
+
else if (char === ',' && depth === 0) return p
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return p
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Find the opening `{` of the component config object after Blits.Component( / Application(
|
|
148
|
+
const findConfigBraceStart = (text, openParenPos) => {
|
|
149
|
+
let p = openParenPos + 1
|
|
150
|
+
p = skipNonCode(text, p)
|
|
151
|
+
if (p >= text.length) return -1
|
|
152
|
+
|
|
153
|
+
if (text[p] === '{') return p
|
|
154
|
+
|
|
155
|
+
if (text[p] === "'" || text[p] === '"' || text[p] === '`') {
|
|
156
|
+
const quote = text[p]
|
|
157
|
+
p++
|
|
158
|
+
while (p < text.length) {
|
|
159
|
+
if (text[p] === quote && text[p - 1] !== '\\') {
|
|
160
|
+
p++
|
|
161
|
+
break
|
|
162
|
+
}
|
|
163
|
+
p++
|
|
164
|
+
}
|
|
165
|
+
p = skipNonCode(text, p)
|
|
166
|
+
if (p < text.length && text[p] === '{') return p
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return -1
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const processComputedBlock = (configObject) => {
|
|
107
173
|
const modifications = []
|
|
108
174
|
const commentText = ' /* auto-generated reactivity guard */ '
|
|
109
175
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
//
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
//
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
if (!functionNameMatch) break
|
|
145
|
-
|
|
146
|
-
const funcName = functionNameMatch[1]
|
|
147
|
-
const funcNamePos = currentPos
|
|
148
|
-
|
|
149
|
-
// Find opening brace for this function
|
|
150
|
-
const afterSignaturePos = funcNamePos + functionNameMatch[0].length
|
|
151
|
-
const openingBracePos = computedObj.indexOf('{', afterSignaturePos)
|
|
152
|
-
if (openingBracePos === -1) break
|
|
153
|
-
|
|
154
|
-
// Find closing brace for individual computed function
|
|
155
|
-
const closingBracePos = findComputedFunctionEnd(computedObj, openingBracePos)
|
|
156
|
-
|
|
157
|
-
if (closingBracePos === -1) {
|
|
158
|
-
break
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// Extract the entire function and its body
|
|
162
|
-
// This is better and more accurate than using regex to avoid nested braces
|
|
163
|
-
const functionStartPos = funcNamePos
|
|
164
|
-
const functionEndPos = closingBracePos
|
|
165
|
-
const fullFunction = computedObj.substring(functionStartPos, functionEndPos)
|
|
166
|
-
const functionBody = computedObj
|
|
167
|
-
.substring(openingBracePos + 1, closingBracePos - 1)
|
|
168
|
-
.trim()
|
|
169
|
-
|
|
170
|
-
computedProps.push({
|
|
171
|
-
name: funcName,
|
|
172
|
-
fullText: fullFunction,
|
|
173
|
-
body: functionBody,
|
|
174
|
-
startPos: functionStartPos,
|
|
175
|
-
endPos: functionEndPos,
|
|
176
|
-
})
|
|
177
|
-
|
|
178
|
-
// Move past this function
|
|
179
|
-
currentPos = closingBracePos
|
|
176
|
+
const computedKeywordMatch = /computed\s*:\s*{/.exec(configObject)
|
|
177
|
+
|
|
178
|
+
if (computedKeywordMatch) {
|
|
179
|
+
// Find start position of the computed block
|
|
180
|
+
const computedStart = computedKeywordMatch.index + computedKeywordMatch[0].length - 1 // Position of the opening brace
|
|
181
|
+
|
|
182
|
+
// Find the matching closing brace for computed object in config
|
|
183
|
+
const computedEnd = findComputedObjectEnd(configObject, computedStart)
|
|
184
|
+
|
|
185
|
+
if (computedEnd !== -1) {
|
|
186
|
+
const computedObj = configObject.substring(computedStart, computedEnd)
|
|
187
|
+
const computedBlockStart = computedKeywordMatch.index
|
|
188
|
+
const computedBlockEnd = computedEnd
|
|
189
|
+
|
|
190
|
+
// Process each computed property
|
|
191
|
+
const computedProps = []
|
|
192
|
+
let currentPos = 1 // Start after the opening brace
|
|
193
|
+
|
|
194
|
+
// Process computed properties content
|
|
195
|
+
// intentionally not using regexes to extract functions and their content
|
|
196
|
+
// using cursor-based parsing gives better accuracy
|
|
197
|
+
while (currentPos < computedObj.length - 1) {
|
|
198
|
+
// -1 to exclude the closing brace
|
|
199
|
+
// Skip whitespace/comments so we don't match commented-out computed functions
|
|
200
|
+
currentPos = skipNonCode(computedObj, currentPos)
|
|
201
|
+
if (currentPos >= computedObj.length - 1) break
|
|
202
|
+
|
|
203
|
+
// Find the next function name (computed properties are `name() { ... }`)
|
|
204
|
+
const functionNameMatch = /^(\w+)\s*\(\)\s*/.exec(computedObj.substring(currentPos))
|
|
205
|
+
if (!functionNameMatch) {
|
|
206
|
+
const nextPos = skipComputedMember(computedObj, currentPos)
|
|
207
|
+
if (nextPos === -1) break
|
|
208
|
+
currentPos = nextPos
|
|
209
|
+
continue
|
|
180
210
|
}
|
|
181
211
|
|
|
182
|
-
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
if (thisRefs.size > 0) {
|
|
196
|
-
// Skip if already modified
|
|
197
|
-
if (
|
|
198
|
-
prop.body.includes(commentText.trim()) ||
|
|
199
|
-
Array.from(thisRefs).some((ref) => {
|
|
200
|
-
if (prop.body.startsWith(ref)) return true
|
|
201
|
-
// stripped && guard: body starts with the first segment, e.g. 'this.media &&'
|
|
202
|
-
const firstSegEnd = ref.indexOf('.', 5)
|
|
203
|
-
return firstSegEnd !== -1 && prop.body.startsWith(ref.substring(0, firstSegEnd))
|
|
204
|
-
})
|
|
205
|
-
) {
|
|
206
|
-
continue
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// reactivity code
|
|
210
|
-
const refCode = Array.from(thisRefs)
|
|
211
|
-
.map((ref) => `${toSafeRef(ref)};`)
|
|
212
|
-
.join(' ')
|
|
213
|
-
|
|
214
|
-
// replacement with the same whitespace
|
|
215
|
-
const openBraceIndex = prop.fullText.indexOf('{')
|
|
216
|
-
|
|
217
|
-
if (openBraceIndex !== -1) {
|
|
218
|
-
// Extract indentation from the original function body
|
|
219
|
-
const indentMatch = prop.body.match(/^(\s+)/)
|
|
220
|
-
const indent = indentMatch ? indentMatch[1] + ' ' : ' ' // Default to 4 spaces if no indent found
|
|
221
|
-
|
|
222
|
-
const modifiedPropText =
|
|
223
|
-
prop.fullText.substring(0, openBraceIndex + 1) +
|
|
224
|
-
'\n' +
|
|
225
|
-
indent +
|
|
226
|
-
commentText +
|
|
227
|
-
'\n' +
|
|
228
|
-
indent +
|
|
229
|
-
refCode +
|
|
230
|
-
'\n' +
|
|
231
|
-
indent +
|
|
232
|
-
prop.body +
|
|
233
|
-
'\n' +
|
|
234
|
-
prop.fullText.substring(0, openBraceIndex).match(/^(\s*)/)[0] +
|
|
235
|
-
'}'
|
|
236
|
-
|
|
237
|
-
replacements.push({
|
|
238
|
-
start: prop.startPos,
|
|
239
|
-
end: prop.endPos,
|
|
240
|
-
text: modifiedPropText,
|
|
241
|
-
})
|
|
242
|
-
}
|
|
243
|
-
}
|
|
212
|
+
const funcName = functionNameMatch[1]
|
|
213
|
+
const funcNamePos = currentPos
|
|
214
|
+
|
|
215
|
+
// Find opening brace for this function
|
|
216
|
+
const afterSignaturePos = funcNamePos + functionNameMatch[0].length
|
|
217
|
+
const openingBracePos = computedObj.indexOf('{', afterSignaturePos)
|
|
218
|
+
if (openingBracePos === -1) break
|
|
219
|
+
|
|
220
|
+
// Find closing brace for individual computed function
|
|
221
|
+
const closingBracePos = findComputedFunctionEnd(computedObj, openingBracePos)
|
|
222
|
+
|
|
223
|
+
if (closingBracePos === -1) {
|
|
224
|
+
break
|
|
244
225
|
}
|
|
245
226
|
|
|
246
|
-
|
|
247
|
-
|
|
227
|
+
// Extract the entire function and its body
|
|
228
|
+
// This is better and more accurate than using regex to avoid nested braces
|
|
229
|
+
const functionStartPos = funcNamePos
|
|
230
|
+
const functionEndPos = closingBracePos
|
|
231
|
+
const fullFunction = computedObj.substring(functionStartPos, functionEndPos)
|
|
232
|
+
const functionBody = computedObj.substring(openingBracePos + 1, closingBracePos - 1).trim()
|
|
233
|
+
|
|
234
|
+
computedProps.push({
|
|
235
|
+
name: funcName,
|
|
236
|
+
fullText: fullFunction,
|
|
237
|
+
body: functionBody,
|
|
238
|
+
startPos: functionStartPos,
|
|
239
|
+
endPos: functionEndPos,
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
// Move past this function
|
|
243
|
+
currentPos = closingBracePos
|
|
244
|
+
}
|
|
248
245
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
246
|
+
// Store the original computed object for later replacement
|
|
247
|
+
const originalComputedObj = configObject.substring(computedBlockStart, computedBlockEnd)
|
|
248
|
+
const computedObjOffsetInOriginal = computedStart - computedBlockStart
|
|
249
|
+
const computedObjEndInOriginal = computedObjOffsetInOriginal + computedObj.length
|
|
250
|
+
|
|
251
|
+
let modifiedComputedObj = originalComputedObj
|
|
252
|
+
let hasChanges = false
|
|
253
|
+
|
|
254
|
+
const replacements = []
|
|
255
|
+
|
|
256
|
+
for (const prop of computedProps) {
|
|
257
|
+
const thisRefs = extractThisReferences(prop.body)
|
|
258
|
+
|
|
259
|
+
if (thisRefs.size > 0) {
|
|
260
|
+
// Skip if already modified
|
|
261
|
+
if (
|
|
262
|
+
prop.body.includes(commentText.trim()) ||
|
|
263
|
+
Array.from(thisRefs).some((ref) => {
|
|
264
|
+
if (prop.body.startsWith(ref)) return true
|
|
265
|
+
// stripped && guard: body starts with the first segment, e.g. 'this.media &&'
|
|
266
|
+
const firstSegEnd = ref.indexOf('.', 5)
|
|
267
|
+
return firstSegEnd !== -1 && prop.body.startsWith(ref.substring(0, firstSegEnd))
|
|
268
|
+
})
|
|
269
|
+
) {
|
|
270
|
+
continue
|
|
253
271
|
}
|
|
254
272
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
273
|
+
// reactivity code
|
|
274
|
+
const refCode = Array.from(thisRefs)
|
|
275
|
+
.map((ref) => `${toSafeRef(ref)};`)
|
|
276
|
+
.join(' ')
|
|
277
|
+
|
|
278
|
+
// replacement with the same whitespace
|
|
279
|
+
const openBraceIndex = prop.fullText.indexOf('{')
|
|
280
|
+
|
|
281
|
+
if (openBraceIndex !== -1) {
|
|
282
|
+
// Extract indentation from the original function body
|
|
283
|
+
const indentMatch = prop.body.match(/^(\s+)/)
|
|
284
|
+
const indent = indentMatch ? indentMatch[1] + ' ' : ' ' // Default to 4 spaces if no indent found
|
|
285
|
+
|
|
286
|
+
const modifiedPropText =
|
|
287
|
+
prop.fullText.substring(0, openBraceIndex + 1) +
|
|
288
|
+
'\n' +
|
|
289
|
+
indent +
|
|
290
|
+
commentText +
|
|
291
|
+
'\n' +
|
|
292
|
+
indent +
|
|
293
|
+
refCode +
|
|
294
|
+
'\n' +
|
|
295
|
+
indent +
|
|
296
|
+
prop.body +
|
|
297
|
+
'\n' +
|
|
298
|
+
prop.fullText.substring(0, openBraceIndex).match(/^(\s*)/)[0] +
|
|
299
|
+
'}'
|
|
300
|
+
|
|
301
|
+
replacements.push({
|
|
302
|
+
start: prop.startPos,
|
|
303
|
+
end: prop.endPos,
|
|
304
|
+
text: modifiedPropText,
|
|
305
|
+
})
|
|
306
|
+
}
|
|
260
307
|
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (replacements.length > 0) {
|
|
311
|
+
let modifiedComputedInner = computedObj
|
|
261
312
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
replacement: modifiedComputedObj,
|
|
267
|
-
})
|
|
313
|
+
replacements.sort((a, b) => b.start - a.start)
|
|
314
|
+
for (const r of replacements) {
|
|
315
|
+
modifiedComputedInner =
|
|
316
|
+
modifiedComputedInner.slice(0, r.start) + r.text + modifiedComputedInner.slice(r.end)
|
|
268
317
|
}
|
|
318
|
+
|
|
319
|
+
modifiedComputedObj =
|
|
320
|
+
originalComputedObj.slice(0, computedObjOffsetInOriginal) +
|
|
321
|
+
modifiedComputedInner +
|
|
322
|
+
originalComputedObj.slice(computedObjEndInOriginal)
|
|
323
|
+
hasChanges = true
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (hasChanges) {
|
|
327
|
+
modifications.push({
|
|
328
|
+
original: originalComputedObj,
|
|
329
|
+
replacement: modifiedComputedObj,
|
|
330
|
+
})
|
|
269
331
|
}
|
|
270
332
|
}
|
|
271
333
|
}
|
|
272
334
|
|
|
335
|
+
return modifications
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
export default (code) => {
|
|
339
|
+
const componentOpenRegex = /Blits\.(Component|Application)\s*\(/g
|
|
340
|
+
const modifications = []
|
|
341
|
+
|
|
342
|
+
let match
|
|
343
|
+
while ((match = componentOpenRegex.exec(code)) !== null) {
|
|
344
|
+
const openParenPos = match.index + match[0].length - 1
|
|
345
|
+
const configStart = findConfigBraceStart(code, openParenPos)
|
|
346
|
+
if (configStart === -1) continue
|
|
347
|
+
|
|
348
|
+
const configEnd = findMatchingBrace(code, configStart)
|
|
349
|
+
if (configEnd === -1) continue
|
|
350
|
+
|
|
351
|
+
const configObject = code.substring(configStart, configEnd + 1)
|
|
352
|
+
modifications.push(...processComputedBlock(configObject))
|
|
353
|
+
}
|
|
354
|
+
|
|
273
355
|
// Apply all modifications at once, from last to first to preserve positions
|
|
274
356
|
if (modifications.length > 0) {
|
|
275
357
|
let modifiedCode = code
|
package/src/router/router.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* SPDX-License-Identifier: Apache-2.0
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import { getHash, isObject, isString, matchHash } from './utils.js'
|
|
18
|
+
import { getHash, isObject, isString, matchHash, sameRouteObject, setHash } from './utils.js'
|
|
19
19
|
import { reactive } from '../lib/reactivity/reactive.js'
|
|
20
20
|
|
|
21
21
|
import symbols from '../lib/symbols.js'
|
|
@@ -52,7 +52,8 @@ import Settings from '../settings.js'
|
|
|
52
52
|
*/
|
|
53
53
|
|
|
54
54
|
/** @type {Route} */
|
|
55
|
-
export let currentRoute
|
|
55
|
+
export let currentRoute = {}
|
|
56
|
+
|
|
56
57
|
export const state = reactive(
|
|
57
58
|
{
|
|
58
59
|
path: '',
|
|
@@ -76,6 +77,8 @@ export const state = reactive(
|
|
|
76
77
|
*/
|
|
77
78
|
|
|
78
79
|
const history = []
|
|
80
|
+
const routerViews = new Set()
|
|
81
|
+
let singleRouterView = null
|
|
79
82
|
|
|
80
83
|
let overrideOptions = {}
|
|
81
84
|
let navigationData = {}
|
|
@@ -105,12 +108,14 @@ export const navigate = async function () {
|
|
|
105
108
|
if (!this[symbols.parent][symbols.routes] || this[symbols.parent][symbols.routes].length === 0)
|
|
106
109
|
return
|
|
107
110
|
|
|
111
|
+
if (this.history === undefined) this.history = []
|
|
112
|
+
|
|
108
113
|
state.navigating = true
|
|
109
114
|
|
|
110
115
|
Announcer.stop()
|
|
111
116
|
Announcer.clear()
|
|
112
117
|
|
|
113
|
-
const hash = getHash(location.hash)
|
|
118
|
+
const hash = getHash(location.hash, this.name)
|
|
114
119
|
// try to find the route
|
|
115
120
|
let route = matchHash(hash, this[symbols.parent][symbols.routes], overrideOptions, navigationData)
|
|
116
121
|
|
|
@@ -126,36 +131,46 @@ export const navigate = async function () {
|
|
|
126
131
|
return
|
|
127
132
|
}
|
|
128
133
|
|
|
134
|
+
// sameRouteObject is too simple check for now!
|
|
135
|
+
if (this.currentRoute !== undefined && sameRouteObject(route, this.currentRoute)) {
|
|
136
|
+
state.navigating = false
|
|
137
|
+
return
|
|
138
|
+
}
|
|
139
|
+
|
|
129
140
|
let reuse = false
|
|
130
|
-
|
|
131
|
-
currentRoute = route
|
|
132
|
-
const currentPath = currentRoute.path
|
|
141
|
+
this.previousRoute = this.currentRoute
|
|
142
|
+
this.currentRoute = route
|
|
143
|
+
const currentPath = this.currentRoute.path
|
|
133
144
|
|
|
134
145
|
// execute before each hook
|
|
135
|
-
const beforeEachResult = await executeBeforeHook(
|
|
146
|
+
const beforeEachResult = await executeBeforeHook.call(
|
|
147
|
+
this,
|
|
136
148
|
this[symbols.parent][symbols.routerHooks],
|
|
137
149
|
'beforeEach',
|
|
138
150
|
this[symbols.parent],
|
|
139
151
|
route,
|
|
140
|
-
previousRoute,
|
|
152
|
+
this.previousRoute,
|
|
141
153
|
currentPath
|
|
142
154
|
)
|
|
143
155
|
if (beforeEachResult === false) {
|
|
144
156
|
preventHashChangeNavigation = false
|
|
157
|
+
state.navigating = false
|
|
145
158
|
return
|
|
146
159
|
}
|
|
147
160
|
|
|
148
161
|
// execute before route hook
|
|
149
|
-
const beforeResult = await executeBeforeHook(
|
|
162
|
+
const beforeResult = await executeBeforeHook.call(
|
|
163
|
+
this,
|
|
150
164
|
route.hooks,
|
|
151
165
|
'before',
|
|
152
166
|
this[symbols.parent],
|
|
153
167
|
route,
|
|
154
|
-
previousRoute,
|
|
168
|
+
this.previousRoute,
|
|
155
169
|
currentPath
|
|
156
170
|
)
|
|
157
171
|
if (beforeResult === false) {
|
|
158
172
|
preventHashChangeNavigation = false
|
|
173
|
+
state.navigating = false
|
|
159
174
|
return
|
|
160
175
|
}
|
|
161
176
|
|
|
@@ -165,18 +180,18 @@ export const navigate = async function () {
|
|
|
165
180
|
// FIX: use truthy check instead of `!== undefined` because matchHash()
|
|
166
181
|
// can return `false`, which survives `!== undefined` but has no `.options`.
|
|
167
182
|
if (
|
|
168
|
-
previousRoute &&
|
|
169
|
-
previousRoute.options &&
|
|
170
|
-
previousRoute.options.inHistory === true &&
|
|
183
|
+
this.previousRoute &&
|
|
184
|
+
this.previousRoute.options &&
|
|
185
|
+
this.previousRoute.options.inHistory === true &&
|
|
171
186
|
navigatingBack === false
|
|
172
187
|
) {
|
|
173
|
-
history.push(previousRoute)
|
|
188
|
+
this.history.push(this.previousRoute)
|
|
174
189
|
}
|
|
175
190
|
|
|
176
191
|
// a transition can be a function returning a dynamic transition object
|
|
177
192
|
// based on current and previous route
|
|
178
193
|
if (typeof route.transition === 'function') {
|
|
179
|
-
route.transition = route.transition(previousRoute, route)
|
|
194
|
+
route.transition = route.transition(this.previousRoute, route)
|
|
180
195
|
}
|
|
181
196
|
|
|
182
197
|
/** @type {import('../engines/L3/element.js').BlitsElement} */
|
|
@@ -201,10 +216,10 @@ export const navigate = async function () {
|
|
|
201
216
|
// see if the component of the previous route can be reused for the
|
|
202
217
|
// current route
|
|
203
218
|
if (
|
|
204
|
-
previousRoute &&
|
|
219
|
+
this.previousRoute &&
|
|
205
220
|
route.options.reuseComponent === true &&
|
|
206
221
|
route.options.keepAlive !== true &&
|
|
207
|
-
route.component === previousRoute.component
|
|
222
|
+
route.component === this.previousRoute.component
|
|
208
223
|
) {
|
|
209
224
|
reuse = true
|
|
210
225
|
view = this[symbols.children][this[symbols.children].length - 1]
|
|
@@ -288,28 +303,30 @@ export const navigate = async function () {
|
|
|
288
303
|
// apply out out transition on previous view if available, unless
|
|
289
304
|
// we're reusing the prvious page component
|
|
290
305
|
// FIX: truthy guard — previousRoute can be `false` (see history-push comment above).
|
|
291
|
-
if (previousRoute && reuse === false) {
|
|
306
|
+
if (this.previousRoute && reuse === false) {
|
|
292
307
|
// only animate when there is a previous route
|
|
293
308
|
shouldAnimate = true
|
|
294
309
|
let oldView = this[symbols.children].splice(1, 1).pop()
|
|
295
310
|
if (oldView) {
|
|
296
|
-
executeTransition(previousRoute.transition.out, oldView[symbols.holder], true)
|
|
311
|
+
executeTransition(this.previousRoute.transition.out, oldView[symbols.holder], true)
|
|
297
312
|
|
|
298
313
|
// Resolve effective keepAlive: runtime override from $router.to() takes precedence
|
|
299
314
|
// over the static route config option
|
|
315
|
+
const hasOverrideOptions = overrideOptions && typeof overrideOptions === 'object'
|
|
300
316
|
const keepAlive =
|
|
301
|
-
overrideOptions.keepAlive !== undefined
|
|
317
|
+
hasOverrideOptions && overrideOptions.keepAlive !== undefined
|
|
302
318
|
? overrideOptions.keepAlive
|
|
303
|
-
: previousRoute.options && previousRoute.options.keepAlive
|
|
319
|
+
: this.previousRoute.options && this.previousRoute.options.keepAlive
|
|
304
320
|
|
|
305
321
|
// cache the page when it's as 'keepAlive' instead of destroying
|
|
306
322
|
if (
|
|
307
323
|
navigatingBack === false &&
|
|
308
324
|
keepAlive === true &&
|
|
309
|
-
previousRoute
|
|
310
|
-
previousRoute.options
|
|
325
|
+
this.previousRoute &&
|
|
326
|
+
this.previousRoute.options &&
|
|
327
|
+
this.previousRoute.options.inHistory === true
|
|
311
328
|
) {
|
|
312
|
-
const historyItem = history[history.length - 1]
|
|
329
|
+
const historyItem = this.history[this.history.length - 1]
|
|
313
330
|
if (historyItem !== undefined) {
|
|
314
331
|
historyItem.view = oldView
|
|
315
332
|
historyItem.focus = previousFocus
|
|
@@ -321,7 +338,7 @@ export const navigate = async function () {
|
|
|
321
338
|
* 2. Navigating back, and the previous route is configured with "keep alive" set to true.
|
|
322
339
|
* 3. Navigating back, and the previous route is not configured with "keep alive" set to true.
|
|
323
340
|
*/
|
|
324
|
-
if (previousRoute.options && (keepAlive !== true || navigatingBack === true)) {
|
|
341
|
+
if (this.previousRoute.options && (keepAlive !== true || navigatingBack === true)) {
|
|
325
342
|
oldView.destroy()
|
|
326
343
|
oldView = null
|
|
327
344
|
}
|
|
@@ -339,11 +356,11 @@ export const navigate = async function () {
|
|
|
339
356
|
'afterEach',
|
|
340
357
|
this[symbols.parent],
|
|
341
358
|
route,
|
|
342
|
-
previousRoute
|
|
359
|
+
this.previousRoute
|
|
343
360
|
)
|
|
344
361
|
|
|
345
362
|
// execute after route Hook
|
|
346
|
-
await executeAfterHook(route.hooks, 'after', this[symbols.parent], route, previousRoute)
|
|
363
|
+
await executeAfterHook(route.hooks, 'after', this[symbols.parent], route, this.previousRoute)
|
|
347
364
|
|
|
348
365
|
// Clear module-level variables after removeView has consumed them.
|
|
349
366
|
// Placed here so it executes for all navigation flows, not only when
|
|
@@ -397,9 +414,10 @@ const executeBeforeHook = async function (
|
|
|
397
414
|
}
|
|
398
415
|
} catch (error) {
|
|
399
416
|
Log.error(`Error or Rejected Promise in "${hookName}" Hook`, error)
|
|
400
|
-
if (history.length > 0) {
|
|
417
|
+
if (this.history.length > 0) {
|
|
401
418
|
preventHashChangeNavigation = true
|
|
402
419
|
currentRoute = previousRoute
|
|
420
|
+
/// hmmmm
|
|
403
421
|
window.history.back()
|
|
404
422
|
navigatingBack = false
|
|
405
423
|
state.navigating = false
|
|
@@ -413,12 +431,14 @@ const executeBeforeHook = async function (
|
|
|
413
431
|
return false
|
|
414
432
|
}
|
|
415
433
|
// If the resolved result is false, cancel navigation
|
|
416
|
-
if (result === false
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
434
|
+
if (result === false) {
|
|
435
|
+
if (this.history.length > 0) {
|
|
436
|
+
preventHashChangeNavigation = true
|
|
437
|
+
currentRoute = previousRoute
|
|
438
|
+
window.history.back()
|
|
439
|
+
navigatingBack = false
|
|
440
|
+
state.navigating = false
|
|
441
|
+
}
|
|
422
442
|
return false
|
|
423
443
|
}
|
|
424
444
|
}
|
|
@@ -475,20 +495,51 @@ export const to = (path, data = {}, options = {}) => {
|
|
|
475
495
|
navigationData = data
|
|
476
496
|
overrideOptions = options
|
|
477
497
|
|
|
478
|
-
|
|
498
|
+
setHash(path)
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
export const toRouterView = (routerView, path, data = {}, options = {}) => {
|
|
502
|
+
navigationData = data
|
|
503
|
+
overrideOptions = options
|
|
504
|
+
|
|
505
|
+
setHash(path, routerView.name)
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
export const registerRouterView = (routerView) => {
|
|
509
|
+
routerViews.add(routerView)
|
|
510
|
+
singleRouterView = routerViews.size === 1 ? routerView : null
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
export const unregisterRouterView = (routerView) => {
|
|
514
|
+
routerViews.delete(routerView)
|
|
515
|
+
singleRouterView = routerViews.size === 1 ? routerViews.values().next().value : null
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
export const getRegisteredRouterView = (name) => {
|
|
519
|
+
for (const routerView of routerViews) {
|
|
520
|
+
if (routerView.name === name) return routerView
|
|
521
|
+
}
|
|
522
|
+
return null
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
export const getRegisteredRouterViewsCount = () => routerViews.size
|
|
526
|
+
|
|
527
|
+
export const getSingleRegisteredRouterView = () => {
|
|
528
|
+
return singleRouterView
|
|
479
529
|
}
|
|
480
530
|
|
|
481
531
|
export const back = function () {
|
|
482
|
-
|
|
483
|
-
|
|
532
|
+
if (this.history === undefined) return
|
|
533
|
+
const route = this.history.pop()
|
|
534
|
+
if (route && this.currentRoute !== route) {
|
|
484
535
|
// set indicator that we are navigating back (to prevent adding page to history stack)
|
|
485
536
|
navigatingBack = true
|
|
486
537
|
navigatingBackTo = route
|
|
487
|
-
|
|
538
|
+
toRouterView(this, route.hash, route.data, route.options)
|
|
488
539
|
return true
|
|
489
540
|
}
|
|
490
541
|
|
|
491
|
-
const backtrack = (currentRoute && currentRoute.options.backtrack) || false
|
|
542
|
+
const backtrack = (this.currentRoute && this.currentRoute.options.backtrack) || false
|
|
492
543
|
|
|
493
544
|
// If we deeplink to a page without backtrack
|
|
494
545
|
// we we let the RouterView handle back
|
|
@@ -497,7 +548,7 @@ export const back = function () {
|
|
|
497
548
|
}
|
|
498
549
|
|
|
499
550
|
const hashEnd = /(\/:?[\w%\s-]+)$/
|
|
500
|
-
let path = currentRoute.path
|
|
551
|
+
let path = this.currentRoute.path
|
|
501
552
|
|
|
502
553
|
let level = path.split('/').length
|
|
503
554
|
|
|
@@ -520,7 +571,7 @@ export const back = function () {
|
|
|
520
571
|
)
|
|
521
572
|
|
|
522
573
|
if (route && backtrack) {
|
|
523
|
-
|
|
574
|
+
toRouterView(this, route.path, route.data, route.options)
|
|
524
575
|
return true
|
|
525
576
|
}
|
|
526
577
|
}
|
package/src/router/utils.js
CHANGED
|
@@ -4,16 +4,85 @@ import { default as fadeInFadeOutTransition } from './transitions/fadeInOut.js'
|
|
|
4
4
|
* Get the current hash
|
|
5
5
|
* @returns {Hash}
|
|
6
6
|
*/
|
|
7
|
-
export const getHash = (hash) => {
|
|
7
|
+
export const getHash = (hash, routerViewName = '') => {
|
|
8
8
|
if (!hash) hash = '/'
|
|
9
|
-
|
|
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('?')
|
|
10
25
|
return {
|
|
11
|
-
path:
|
|
12
|
-
queryParams: new URLSearchParams(
|
|
26
|
+
path: path,
|
|
27
|
+
queryParams: new URLSearchParams(query),
|
|
13
28
|
hash: hash,
|
|
14
29
|
}
|
|
15
30
|
}
|
|
16
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
|
+
}
|
|
17
86
|
export const normalizePath = (path) => {
|
|
18
87
|
return (
|
|
19
88
|
path
|
|
@@ -60,17 +129,25 @@ const defaultOptions = {
|
|
|
60
129
|
}
|
|
61
130
|
|
|
62
131
|
export const makeRouteObject = (route, overrides, overrideOptions, navigationData) => {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
//
|
|
66
|
-
|
|
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
|
+
}
|
|
67
144
|
|
|
68
145
|
const cleanRoute = {
|
|
69
146
|
hash: overrides.hash,
|
|
70
147
|
path: route.path,
|
|
71
148
|
component: route.component,
|
|
72
149
|
transition: 'transition' in route ? route.transition : fadeInFadeOutTransition,
|
|
73
|
-
options
|
|
150
|
+
options,
|
|
74
151
|
announce: route.announce || false,
|
|
75
152
|
hooks: route.hooks || {},
|
|
76
153
|
data: { ...route.data, ...navigationData, ...overrides.queryParams },
|
|
@@ -156,3 +233,13 @@ export const matchHash = (
|
|
|
156
233
|
// @ts-ignore - Remove me when we have a better way to handle this
|
|
157
234
|
return matchingRoute
|
|
158
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
|
+
}
|