@lightningjs/blits 1.13.0 → 1.14.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 +22 -1
- package/docs/built-in/layout.md +32 -0
- package/docs/components/user_input.md +10 -0
- package/docs/components/utility_methods.md +36 -0
- package/docs/router/basics.md +11 -2
- package/index.d.ts +30 -4
- package/package.json +1 -1
- package/src/application.js +8 -1
- package/src/component/base/events.js +8 -0
- package/src/component/base/utils.js +9 -0
- package/src/engines/L3/element.js +24 -4
- package/src/lib/eventListeners.js +13 -0
- package/src/lib/eventListeners.test.js +47 -0
- package/src/lib/reactivity/reactive.js +11 -2
- package/src/router/router.js +17 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v1.14.0
|
|
4
|
+
|
|
5
|
+
_6 jan 2025_
|
|
6
|
+
|
|
7
|
+
- Added ability to deregister listeners (`this.$unlisten()`)
|
|
8
|
+
- Added autosize to images without `w` and `h` attributes
|
|
9
|
+
- Fixed cleanup of transitions and end-callbacks when Elements are destroyed
|
|
10
|
+
- Added support for reactively updating an entire object (instead of having to update each object key individually)
|
|
11
|
+
- Added `this.$size()` method to set the dimensions of a Component
|
|
12
|
+
- Added `intercept` input method to handle key presses before they reach the currently focused Component
|
|
13
|
+
- Added support for query-parameters in routes (in addition to regular query params)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
## v1.13.1
|
|
17
|
+
|
|
18
|
+
_9 dec 2024_
|
|
19
|
+
|
|
20
|
+
- Fixed setting of correct color when turning off rtt on an element
|
|
21
|
+
- Added check for txManager in reactivity
|
|
22
|
+
- Fixed reference to correct `this`-scope in router before hook
|
|
23
|
+
|
|
3
24
|
## v1.13.0
|
|
4
25
|
|
|
5
26
|
_5 dec 2024_
|
|
@@ -48,7 +69,7 @@ _8 nov 2024_
|
|
|
48
69
|
- Fixed issue with watching nested state variables and global state
|
|
49
70
|
- Upgraded to renderer 2.6.2
|
|
50
71
|
- Fixed issue with white background for Elements with falsy src attribute
|
|
51
|
-
- Fixed issue with calling focus on component that already is
|
|
72
|
+
- Fixed issue with calling focus on component that already is focused
|
|
52
73
|
|
|
53
74
|
|
|
54
75
|
## v1.9.2
|
package/docs/built-in/layout.md
CHANGED
|
@@ -55,6 +55,38 @@ The Layout component also uses this event, to execute its calculation and positi
|
|
|
55
55
|
|
|
56
56
|
When the children of the Layout-component have _reactive_ dimensions (i.e. `<Element :w="$mywidth" :h="$myheight" />), the Layout component ensures that all child elements are properly re-positioned whenever a dimension changes.
|
|
57
57
|
|
|
58
|
+
### Components inside a Layout
|
|
59
|
+
|
|
60
|
+
It is also possible to place Components inside of a Layout, but there is a small _gotcha_ there. By default a Component does not have any dimensions - it has a width and height of `0`, regardless of the contents of the Component. Normally, when using absolute positioning, this isn't a problem. But in the context of a Layout, each child needs to have dimensions.
|
|
61
|
+
|
|
62
|
+
If the Component has fixed dimensions, you can just add a `w` and a `h` attribute to the Component tag (i.e. `<MyButton w="100" h="40" />`). This is the most performant way to supply dimensions to a Component and should be used whenever possible.
|
|
63
|
+
|
|
64
|
+
If the Component has dynamic dimensions that are not known upfront, you can dynamically update the dimensions from inside the Component by calling the `this.$size()`-method. This method accepts an object as its argument with a `w` property for setting the _width_ and a `h` property for setting the _height_.
|
|
65
|
+
|
|
66
|
+
```js
|
|
67
|
+
export default Blits.Component('MyButton', {
|
|
68
|
+
template: ``,
|
|
69
|
+
props: ['type'],
|
|
70
|
+
hooks: {
|
|
71
|
+
ready() {
|
|
72
|
+
if(this.type === 'large') {
|
|
73
|
+
this.$size({
|
|
74
|
+
w: 200,
|
|
75
|
+
h: 80
|
|
76
|
+
})
|
|
77
|
+
} else {
|
|
78
|
+
this.$size({
|
|
79
|
+
w: 100,
|
|
80
|
+
h: 40
|
|
81
|
+
})
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
```
|
|
88
|
+
At this moment Blits does not have support for automatically growing the dimensions of a Component based on it's contents, because of the performance impact that this functionality has.
|
|
89
|
+
|
|
58
90
|
### Nesting Layouts
|
|
59
91
|
|
|
60
92
|
It's also possible to nest layouts. Simply place a new `<Layout>`-tag, with it's own children in between the children of another Layout-component. The Layout-component itself will grow automatically with the dimensions of it's children. In other words, it's not required to specify a width (`w`) or height (`h`) on the `<Layout>`-tag itself.
|
|
@@ -72,6 +72,16 @@ When a component handles a key press by having a corresponding function specifie
|
|
|
72
72
|
}
|
|
73
73
|
```
|
|
74
74
|
|
|
75
|
+
## Intercepting key input
|
|
76
|
+
|
|
77
|
+
In addition to the Event handling chain explained above. Blits offers the option to _intercept_ key presses at the root level of the Application, before they reach the currently focused Component. This can be useful in certain situation where you want to globally disable all key presses, or when implementing an override key press handler.
|
|
78
|
+
|
|
79
|
+
The `intercept()` input-method can only be implemented in the `Blits.Application`-component. When present, the method acts as a _catch-all_ method, and will be executed for _all_ key presses. It receives the `KeyboardEvent` as its argument, allowing you to execute logic based on the key being pressed.
|
|
80
|
+
|
|
81
|
+
Only when the `intercept()` input-method returns the `KeyboardEvent` (possibly modified), the keypress will continue to be handled (by the currently focused Component).
|
|
82
|
+
|
|
83
|
+
The `intercept`-method can also be an asynchronous method.
|
|
84
|
+
|
|
75
85
|
## Key-up handling
|
|
76
86
|
|
|
77
87
|
The functions specified in the `input` configuration are invoked when a key is _pressed down_ (i.e. the `keydown` event listener). But sometimes you may also want to execute some logic when a key is _released_ (i.e. the `keyup` event listener).
|
|
@@ -177,6 +177,42 @@ export default Blits.Component('MyComponent', {
|
|
|
177
177
|
})
|
|
178
178
|
```
|
|
179
179
|
|
|
180
|
+
### $unlisten
|
|
181
|
+
|
|
182
|
+
The `$unlisten` utility method (which is available on each Blits Component) is designed to remove event listeners that were previously registered using `$listen()`. This method helps in managing event listeners manually when needed.
|
|
183
|
+
|
|
184
|
+
The first argument of the `$unlisten()` method is the event `name` to stop listening for. This ensures that all listeners for the specified event are removed from the component.
|
|
185
|
+
|
|
186
|
+
Generally, you might not need to call `$unlisten()` manually, as Blits automatically deregisters listeners when a Component is destroyed. However, it can be useful in scenarios where you need to stop listening for events before the Component is destroyed.
|
|
187
|
+
|
|
188
|
+
```js
|
|
189
|
+
export default Blits.Component('MyComponent', {
|
|
190
|
+
hooks: {
|
|
191
|
+
init() {
|
|
192
|
+
this.activateListener = () => {
|
|
193
|
+
console.log('activated')
|
|
194
|
+
this.text = 'We are active!'
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
this.changeBackgroundListener = (data) => {
|
|
198
|
+
setTimeout(() => {
|
|
199
|
+
this.backgroundImage = data.img
|
|
200
|
+
}, data.delay)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Register listeners
|
|
204
|
+
this.$listen('activate', this.activateListener)
|
|
205
|
+
this.$listen('changeBackground', this.changeBackgroundListener)
|
|
206
|
+
},
|
|
207
|
+
unfocus() {
|
|
208
|
+
// Remove listeners when Component is unfocused
|
|
209
|
+
this.$unlisten('activate')
|
|
210
|
+
this.$unlisten('changeBackground')
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
})
|
|
214
|
+
```
|
|
215
|
+
|
|
180
216
|
## Timeouts
|
|
181
217
|
|
|
182
218
|
Setting a timeout is a typical way in JS to execute functionality with a delay. But timeouts can also be a common cause of
|
package/docs/router/basics.md
CHANGED
|
@@ -30,10 +30,19 @@ Each route in this array, is an Object literal that includes the following key p
|
|
|
30
30
|
|
|
31
31
|
Besides static routes such as `/account` and `/settings/wifi/advanced`, the Blits router also supports dynamic routes where URI parts can contain params.
|
|
32
32
|
|
|
33
|
-
To define a param in a route path prefix a string with a colon (`:`), followed by the name of the param, e.g. `/movies/:genre/:id`. This route path will match a URI hash such as `#/movies/sci-fi/65281918`. The router will take the params and inject them as a `prop` into the Page that's being navigated to.
|
|
33
|
+
To define a param in a route path, prefix a string with a colon (`:`), followed by the name of the param, e.g. `/movies/:genre/:id`. This route path will match a URI hash such as `#/movies/sci-fi/65281918`. The router will take the params and inject them as a `prop` into the Page that's being navigated to.
|
|
34
34
|
|
|
35
|
-
To access these props in the Page component, they must first be defined in the
|
|
35
|
+
To access these props in the Page component, they must first be defined in the Page component as _props_ first (i.e. `props: ['genre', 'id']`). Once defined, the values (`sci-fi` and `65281918`) in this case, will be available on the `this` scope as `this.genre` and `this.id` (or as `$genre` and `$id` when used in the template), as with any regular Component.
|
|
36
36
|
|
|
37
|
+
#### Using query parameters in routes
|
|
38
|
+
|
|
39
|
+
According to the browser specifications, query parameters are not part of a URL hash. This means that query parameters should be placed _before_ the URL hash (i.e. `http://localhost:5173?id=100&name=john#/my/page/hash`) and implies that "real" query parameters are _not_ part of the route paths.
|
|
40
|
+
|
|
41
|
+
In this case `id` and `name` won't automatically be made available as props inside a Blits component. They can however be retrieved using `document.location.search` in combination with `new URLSearchParams`, and can be used in an App's `index.js` to set dynamic launch settings for example.
|
|
42
|
+
|
|
43
|
+
Sometimes you may actually want pass custom data into a Blits component, without it being part of the dynamic route path or `data`-object during navigation. For these cases Blits allows you to use query parameters as part of a route (i.e. `#/series/simpsons/5/10?id=100&name=john`).
|
|
44
|
+
|
|
45
|
+
This URL hash will match the route `/series/:show/:season/:episode` and it will pass `id` and `name` as additional props into the Page component. Note: similar to dynamic path params, route query params should also be be defined in the Page component as _props_ first (i.e. `props: ['id', 'name']`) in order to be accessed on the `this`-scope.
|
|
37
46
|
|
|
38
47
|
## Router view
|
|
39
48
|
|
package/index.d.ts
CHANGED
|
@@ -81,14 +81,26 @@ declare module '@lightningjs/blits' {
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
export interface Input {
|
|
84
|
-
[key: string]: (event: KeyboardEvent) => void | undefined,
|
|
84
|
+
[key: string]: (event: KeyboardEvent) => void | undefined | unknown,
|
|
85
85
|
/**
|
|
86
86
|
* Catch all input function
|
|
87
87
|
*
|
|
88
88
|
* Will be invoked when there is no dedicated function for a certain key
|
|
89
89
|
*/
|
|
90
90
|
// @ts-ignore
|
|
91
|
-
any?: (event: KeyboardEvent) => void
|
|
91
|
+
any?: (event: KeyboardEvent) => void,
|
|
92
|
+
/**
|
|
93
|
+
* Intercept key presses on the root Application component before being handled
|
|
94
|
+
* by the currently focused component.
|
|
95
|
+
*
|
|
96
|
+
* Only when a KeyboardEvent (the original one, or a modified one) is returned from the
|
|
97
|
+
* intercept function, the Input event is passed on to the Component with focus.
|
|
98
|
+
*
|
|
99
|
+
* The intercept function can be asynchronous.
|
|
100
|
+
*
|
|
101
|
+
* Note: the intercept input handler is only available on the Root App component (i.e. Blits.Application)
|
|
102
|
+
*/
|
|
103
|
+
intercept?: (event: KeyboardEvent) => KeyboardEvent | Promise<KeyboardEvent | any> | any
|
|
92
104
|
}
|
|
93
105
|
|
|
94
106
|
export interface Log {
|
|
@@ -153,7 +165,9 @@ declare module '@lightningjs/blits' {
|
|
|
153
165
|
|
|
154
166
|
export type ComponentBase = {
|
|
155
167
|
/**
|
|
156
|
-
*
|
|
168
|
+
* Indicates whether the component currently has focus
|
|
169
|
+
*
|
|
170
|
+
* @returns Boolean
|
|
157
171
|
*/
|
|
158
172
|
hasFocus: boolean,
|
|
159
173
|
|
|
@@ -244,11 +258,23 @@ declare module '@lightningjs/blits' {
|
|
|
244
258
|
* Deprecated: use `this.$trigger()` instead
|
|
245
259
|
*/
|
|
246
260
|
trigger: (key: string) => void
|
|
247
|
-
|
|
248
261
|
/**
|
|
249
262
|
* Router instance
|
|
250
263
|
*/
|
|
251
264
|
$router: Router
|
|
265
|
+
/**
|
|
266
|
+
* Dynamically set the size of a component holder node
|
|
267
|
+
*/
|
|
268
|
+
$size: (dimensions: {
|
|
269
|
+
/**
|
|
270
|
+
* Component width
|
|
271
|
+
*/
|
|
272
|
+
w: number,
|
|
273
|
+
/**
|
|
274
|
+
* Component height
|
|
275
|
+
*/
|
|
276
|
+
h: number
|
|
277
|
+
}) => void
|
|
252
278
|
}
|
|
253
279
|
|
|
254
280
|
/**
|
package/package.json
CHANGED
package/src/application.js
CHANGED
|
@@ -56,8 +56,15 @@ const Application = (config) => {
|
|
|
56
56
|
config.hooks[symbols.init] = function () {
|
|
57
57
|
const keyMap = { ...defaultKeyMap, ...Settings.get('keymap', {}) }
|
|
58
58
|
|
|
59
|
-
keyDownHandler = (e) => {
|
|
59
|
+
keyDownHandler = async (e) => {
|
|
60
60
|
const key = keyMap[e.key] || keyMap[e.keyCode] || e.key || e.keyCode
|
|
61
|
+
// intercept key press if specified in main Application component
|
|
62
|
+
if (this[symbols.inputEvents].intercept !== undefined) {
|
|
63
|
+
e = await this[symbols.inputEvents].intercept.call(this, e)
|
|
64
|
+
// only pass on the key press to focused component when keyboard event is returned
|
|
65
|
+
if (e instanceof KeyboardEvent === false) return
|
|
66
|
+
}
|
|
67
|
+
|
|
61
68
|
Focus.input(key, e)
|
|
62
69
|
clearTimeout(holdTimeout)
|
|
63
70
|
holdTimeout = setTimeout(() => {
|
|
@@ -20,6 +20,15 @@ import symbols from '../../lib/symbols.js'
|
|
|
20
20
|
import { renderer } from '../../launch.js'
|
|
21
21
|
|
|
22
22
|
export default {
|
|
23
|
+
$size: {
|
|
24
|
+
value: function (dimensions = { w: 0, h: 0 }) {
|
|
25
|
+
this[symbols.holder].set('w', dimensions.w || 0)
|
|
26
|
+
this[symbols.holder].set('h', dimensions.h || 0)
|
|
27
|
+
},
|
|
28
|
+
writable: false,
|
|
29
|
+
enumerable: true,
|
|
30
|
+
configurable: false,
|
|
31
|
+
},
|
|
23
32
|
[symbols.renderer]: {
|
|
24
33
|
value: () => renderer,
|
|
25
34
|
writable: false,
|
|
@@ -217,6 +217,10 @@ const propsTransformer = {
|
|
|
217
217
|
if (this.raw['color'] === undefined) {
|
|
218
218
|
this.props['color'] = this.props['src'] ? 0xffffffff : 0x00000000
|
|
219
219
|
}
|
|
220
|
+
// apply auto sizing when no width or height specified
|
|
221
|
+
if (!('w' in this.raw) && !('w' in this.raw) && !('h' in this.raw) && !('height' in this.raw)) {
|
|
222
|
+
this.props['autosize'] = true
|
|
223
|
+
}
|
|
220
224
|
},
|
|
221
225
|
set texture(v) {
|
|
222
226
|
this.props['texture'] = v
|
|
@@ -251,8 +255,8 @@ const propsTransformer = {
|
|
|
251
255
|
},
|
|
252
256
|
set rtt(v) {
|
|
253
257
|
this.props['rtt'] = v
|
|
254
|
-
if (
|
|
255
|
-
this.props['color'] = 0xffffffff
|
|
258
|
+
if (this.raw['color'] === undefined) {
|
|
259
|
+
this.props['color'] = v === true ? 0xffffffff : 0x00000000
|
|
256
260
|
}
|
|
257
261
|
},
|
|
258
262
|
set mount(v) {
|
|
@@ -536,12 +540,18 @@ const Element = {
|
|
|
536
540
|
}
|
|
537
541
|
|
|
538
542
|
f.once('stopped', () => {
|
|
539
|
-
|
|
540
|
-
|
|
543
|
+
if (
|
|
544
|
+
this.scheduledTransitions[prop] !== undefined &&
|
|
545
|
+
this.scheduledTransitions[prop].canceled === true
|
|
546
|
+
) {
|
|
547
|
+
return
|
|
548
|
+
}
|
|
541
549
|
// fire transition end callback when animation ends (if specified)
|
|
542
550
|
if (transition.end && typeof transition.end === 'function') {
|
|
543
551
|
transition.end.call(this.component, this, prop, this.node[prop])
|
|
544
552
|
}
|
|
553
|
+
// remove the prop from scheduled transitions
|
|
554
|
+
delete this.scheduledTransitions[prop]
|
|
545
555
|
})
|
|
546
556
|
|
|
547
557
|
// start animation
|
|
@@ -550,6 +560,16 @@ const Element = {
|
|
|
550
560
|
destroy() {
|
|
551
561
|
Log.debug('Deleting Node', this.nodeId)
|
|
552
562
|
this.node.destroy()
|
|
563
|
+
|
|
564
|
+
// Clearing transition end callback functions
|
|
565
|
+
const transitionProps = Object.keys(this.scheduledTransitions)
|
|
566
|
+
for (let i = 0; i < transitionProps.length; i++) {
|
|
567
|
+
const transition = this.scheduledTransitions[transitionProps[i]]
|
|
568
|
+
if (transition !== undefined) {
|
|
569
|
+
transition.canceled = true
|
|
570
|
+
if (transition.f !== undefined) transition.f.stop()
|
|
571
|
+
}
|
|
572
|
+
}
|
|
553
573
|
},
|
|
554
574
|
get nodeId() {
|
|
555
575
|
return this.node && this.node.id
|
|
@@ -36,6 +36,19 @@ export default {
|
|
|
36
36
|
callbackCache.delete(event) // Invalidate the callbackCache when a new callback is added
|
|
37
37
|
},
|
|
38
38
|
|
|
39
|
+
deregisterListener(component, event) {
|
|
40
|
+
let componentsMap = eventsMap.get(event)
|
|
41
|
+
if (componentsMap === undefined) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (componentsMap.contains(component)) {
|
|
46
|
+
componentsMap.delete(component);
|
|
47
|
+
eventsMap.set(event, componentsMap)
|
|
48
|
+
callbackCache.delete(event)
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
|
|
39
52
|
executeListeners(event, params) {
|
|
40
53
|
const componentsMap = eventsMap.get(event)
|
|
41
54
|
if (componentsMap === undefined || componentsMap.size === 0) {
|
|
@@ -130,6 +130,53 @@ test('executeListeners - stop propagation', (t) => {
|
|
|
130
130
|
t.end()
|
|
131
131
|
})
|
|
132
132
|
|
|
133
|
+
test('deregisterListener', (t) => {
|
|
134
|
+
resetModule()
|
|
135
|
+
|
|
136
|
+
const component = 'testComponent'
|
|
137
|
+
const event = 'testEvent'
|
|
138
|
+
let callCount = 0
|
|
139
|
+
const cb = () => {
|
|
140
|
+
callCount++
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
eventListener.registerListener(component, event, cb)
|
|
144
|
+
eventListener.deregisterListener(component, event)
|
|
145
|
+
|
|
146
|
+
const result = eventListener.executeListeners(event)
|
|
147
|
+
t.equal(result, true, 'Should return true when no listeners are registered after deregistration')
|
|
148
|
+
t.equal(callCount, 0, 'Should not call deregistered callback')
|
|
149
|
+
|
|
150
|
+
t.end()
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
test('deregisterListener - only deregisters specified component listener', (t) => {
|
|
154
|
+
resetModule()
|
|
155
|
+
|
|
156
|
+
const componentA = 'testComponent'
|
|
157
|
+
const componentB = 'testComponentB'
|
|
158
|
+
const event = 'testEvent'
|
|
159
|
+
let callCount1 = 0
|
|
160
|
+
let callCount2 = 0
|
|
161
|
+
|
|
162
|
+
eventListener.registerListener(componentA, event, () => {
|
|
163
|
+
callCount1++
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
eventListener.registerListener(componentB, event, () => {
|
|
167
|
+
callCount2++
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
eventListener.deregisterListener(componentA, event)
|
|
171
|
+
|
|
172
|
+
const result = eventListener.executeListeners(event)
|
|
173
|
+
t.equal(result, true, 'Should return true when listeners are still registered after deregistering one')
|
|
174
|
+
t.equal(callCount1, 0, 'Should not call deregistered callback')
|
|
175
|
+
t.equal(callCount2, 1, 'Should call remaining registered callback')
|
|
176
|
+
|
|
177
|
+
t.end()
|
|
178
|
+
})
|
|
179
|
+
|
|
133
180
|
test('removeListeners', (t) => {
|
|
134
181
|
resetModule()
|
|
135
182
|
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* SPDX-License-Identifier: Apache-2.0
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import { ImageTexture } from '@lightningjs/renderer'
|
|
18
|
+
import { ImageTexture, Texture } from '@lightningjs/renderer'
|
|
19
19
|
import { track, trigger, pauseTracking, resumeTracking } from './effect.js'
|
|
20
20
|
import symbols from '../symbols.js'
|
|
21
21
|
import deepEqualArray from '../../helpers/deepEqualArray.js'
|
|
@@ -35,6 +35,7 @@ const reactiveProxy = (original, _parent = null, _key, global) => {
|
|
|
35
35
|
if (typeof original === 'object') {
|
|
36
36
|
if (original[symbols.id] !== undefined) return original
|
|
37
37
|
if (original.constructor.name === ImageTexture.name) return original
|
|
38
|
+
if (original.txManager !== undefined) return original
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
// if original object is already a proxy, don't create a new one but return the existing one instead
|
|
@@ -106,7 +107,15 @@ const reactiveProxy = (original, _parent = null, _key, global) => {
|
|
|
106
107
|
: oldRawValue === rawValue
|
|
107
108
|
|
|
108
109
|
if (isEqual === false) {
|
|
109
|
-
if (Array.isArray(value) === true)
|
|
110
|
+
if (Array.isArray(value) === true) {
|
|
111
|
+
value = getRaw(value).slice(0)
|
|
112
|
+
} else if (
|
|
113
|
+
typeof value === 'object' &&
|
|
114
|
+
target[key] !== null &&
|
|
115
|
+
Array.isArray(target) === false
|
|
116
|
+
) {
|
|
117
|
+
value = Object.assign(receiver[key], value)
|
|
118
|
+
}
|
|
110
119
|
result = Reflect.set(target, key, value, receiver)
|
|
111
120
|
}
|
|
112
121
|
|
package/src/router/router.js
CHANGED
|
@@ -35,7 +35,11 @@ let navigatingBack = false
|
|
|
35
35
|
let previousFocus
|
|
36
36
|
|
|
37
37
|
export const getHash = () => {
|
|
38
|
-
|
|
38
|
+
const hashParts = (document.location.hash || '/').replace(/^#/, '').split('?')
|
|
39
|
+
return {
|
|
40
|
+
hash: hashParts[0],
|
|
41
|
+
queryParams: new URLSearchParams(hashParts[1]),
|
|
42
|
+
}
|
|
39
43
|
}
|
|
40
44
|
|
|
41
45
|
const normalizePath = (path) => {
|
|
@@ -113,13 +117,13 @@ export const navigate = async function () {
|
|
|
113
117
|
navigating = true
|
|
114
118
|
if (this.parent[symbols.routes]) {
|
|
115
119
|
const previousRoute = currentRoute
|
|
116
|
-
const hash = getHash()
|
|
120
|
+
const { hash, queryParams } = getHash()
|
|
117
121
|
let route = matchHash(hash, this.parent[symbols.routes])
|
|
118
122
|
let beforeHookOutput
|
|
119
123
|
if (route) {
|
|
120
124
|
if (route.hooks) {
|
|
121
125
|
if (route.hooks.before) {
|
|
122
|
-
beforeHookOutput = await route.hooks.before(route, previousRoute)
|
|
126
|
+
beforeHookOutput = await route.hooks.before.call(this.parent, route, previousRoute)
|
|
123
127
|
if (isString(beforeHookOutput)) {
|
|
124
128
|
currentRoute = previousRoute
|
|
125
129
|
to(beforeHookOutput)
|
|
@@ -153,7 +157,16 @@ export const navigate = async function () {
|
|
|
153
157
|
holder.set('w', '100%')
|
|
154
158
|
holder.set('h', '100%')
|
|
155
159
|
// merge props with potential route params, navigation data and route data to be injected into the component instance
|
|
156
|
-
const props = {
|
|
160
|
+
const props = {
|
|
161
|
+
...this[symbols.props],
|
|
162
|
+
...route.params,
|
|
163
|
+
...navigationData,
|
|
164
|
+
...route.data,
|
|
165
|
+
...queryParams.entries().reduce((obj, item) => {
|
|
166
|
+
obj[item[0]] = item[1]
|
|
167
|
+
return obj
|
|
168
|
+
}, {}),
|
|
169
|
+
}
|
|
157
170
|
|
|
158
171
|
view = await route.component({ props }, holder, this)
|
|
159
172
|
if (view[Symbol.toStringTag] === 'Module') {
|