@lightningjs/blits 1.25.1 → 1.26.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 +9 -0
- package/docs/plugins/text-to-speech-announcer.md +58 -2
- package/index.d.ts +53 -1
- package/package.json +1 -1
- package/src/announcer/announcer.js +76 -10
- package/src/announcer/speechSynthesis.js +31 -60
- package/src/component/base/utils.js +2 -1
- package/src/component.js +1 -1
- package/src/components/FPScounter.js +48 -48
- package/src/launch.js +4 -3
- package/src/lib/codegenerator/generator.js +3 -3
- package/src/lib/codegenerator/generator.test.js +36 -36
- package/src/lib/symbols.js +2 -0
- package/src/router/router.js +13 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v1.26.0
|
|
4
|
+
|
|
5
|
+
- Announcer updates: queue, cancel individual messages, promise based chaining
|
|
6
|
+
- Fixed naming collision for `config`-key on component instance
|
|
7
|
+
- Fixed logging correct renderer version
|
|
8
|
+
|
|
9
|
+
_08 apr 2025_
|
|
10
|
+
|
|
11
|
+
|
|
3
12
|
## v1.25.1
|
|
4
13
|
|
|
5
14
|
_01 apr 2025_
|
|
@@ -25,7 +25,7 @@ const routes = [
|
|
|
25
25
|
Furthermore, it's possible to speak out custom messages anywhere in your App logic, for example when a component receives focus, as a reaction to user input, or to announce error messages when a remote API call
|
|
26
26
|
fails.
|
|
27
27
|
|
|
28
|
-
Each component in Blits has a `this.$announcer.speak()` available, which accepts `string`
|
|
28
|
+
Each component in Blits has a `this.$announcer.speak()` available, which accepts a `string` with the message to be announced.
|
|
29
29
|
|
|
30
30
|
```js
|
|
31
31
|
Blits.Component('MyComponent', {
|
|
@@ -47,9 +47,65 @@ Blits.Component('MyComponent', {
|
|
|
47
47
|
})
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
+
### Execute logic when done
|
|
51
|
+
|
|
52
|
+
The `speak()`-function returns a promise that resolves when it's done speaking, or when it's interupted or errors out. The reason of the `speak()` method resolving is passed as an argument into the resolve function.
|
|
53
|
+
|
|
54
|
+
```js
|
|
55
|
+
this.$announcer.speak('Hello').then((reason) => console.log('Speaking hello finished', reason))
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Announcement queue
|
|
59
|
+
|
|
60
|
+
The `speak()`-method places the messages in an announcement queue and then starts processing that queue. That means that when the queue is empty, it will instantly announce the messages passed. If there are other messages in the queue, the messages will be spoken when all previous messages are announced.
|
|
61
|
+
|
|
62
|
+
Optionally you can set the politeness of a message to `assertive` and pass it a the second argument of the `speak()`-method.
|
|
63
|
+
|
|
64
|
+
```js
|
|
65
|
+
this.$announcer.speak('Very important message that should get priority', 'assertive')
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
This will place the message at the beginning of the queue, making in the first message to be announced after the current message (if any) has finished.
|
|
69
|
+
|
|
70
|
+
#### Clearing and interupting
|
|
71
|
+
|
|
72
|
+
Since each message added into the queue may take a bit of time to actually be announced, it's possible that the user has navigated elsewhere in the mean time, making the queued up messages not relevant.
|
|
73
|
+
|
|
74
|
+
The announcer plugin allows you to clear the entire queue by calling `this.$announcer.clear()`. Additionally if you want to interup the messages being currently spoken, the announcer plugin offers the `this.$announcer.stop()`-method.
|
|
75
|
+
|
|
76
|
+
#### Canceling individual messages
|
|
77
|
+
|
|
78
|
+
In some cases you may not want to clear the entire queue, but instead cancel out a single message.
|
|
79
|
+
|
|
80
|
+
Imagine an App with a row of tiles, it's possible that before the title of the role is being spoken out, the user already navigates through the tiles within the row. Traditionally you'd use the focus event to speak out info about each tile (i.e. adding tot the queue). You don't want all previously focused tiles to still be announced, but would still want the category of the row to be announced, making clearing the queue not required.
|
|
81
|
+
|
|
82
|
+
The `speak()`-method return a Promise that also contains a `cancel()` function. When called, it will cancel that specific message and remove it from the queue before it can be spoken out.
|
|
83
|
+
|
|
84
|
+
```js
|
|
85
|
+
Blits.Component('MyTile', {
|
|
86
|
+
//
|
|
87
|
+
props: ['title'],
|
|
88
|
+
state() {
|
|
89
|
+
return {
|
|
90
|
+
message: null
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
hooks: {
|
|
94
|
+
focus() {
|
|
95
|
+
// add message to the announcement queue
|
|
96
|
+
this.message = this.$announcer.speak(`This is tile ${this.title}`)
|
|
97
|
+
},
|
|
98
|
+
unfocus() {
|
|
99
|
+
// when unfocused cancel the message and remove it from the queue
|
|
100
|
+
this.message.cancel()
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
```
|
|
105
|
+
|
|
50
106
|
## Enabling / disabling the announcer
|
|
51
107
|
|
|
52
|
-
By default the announcer is disabled. This
|
|
108
|
+
By default the announcer is disabled. This means that whenever you call `this.$announcer.speak()` there will actually not be anything spoken out via the Text To Speech engine.
|
|
53
109
|
|
|
54
110
|
In order to enable utterances being spoken out, you can set the Blits launch setting `announcer` to `true` in the index.js.
|
|
55
111
|
|
package/index.d.ts
CHANGED
|
@@ -22,6 +22,58 @@ import {type ShaderEffect as RendererShaderEffect, type WebGlCoreShader, type Re
|
|
|
22
22
|
|
|
23
23
|
declare module '@lightningjs/blits' {
|
|
24
24
|
|
|
25
|
+
|
|
26
|
+
export interface AnnouncerUtterance extends Promise {
|
|
27
|
+
/**
|
|
28
|
+
* Removes a specific message from the announcement queue,
|
|
29
|
+
* to make sure it isn't spoke out.
|
|
30
|
+
*
|
|
31
|
+
* Does not interupt the message when it's already being announced.
|
|
32
|
+
*/
|
|
33
|
+
cancel()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface Announcer {
|
|
37
|
+
/**
|
|
38
|
+
* Instruct the Announcer to speak a message. Will add the message
|
|
39
|
+
* to the end of announcement queue by default
|
|
40
|
+
*
|
|
41
|
+
* When a message is added with politeness set to `assertive` the message
|
|
42
|
+
* will be added to the beginning of the queue
|
|
43
|
+
*
|
|
44
|
+
*/
|
|
45
|
+
speak(message: string | number, politeness?: 'off' | 'polite' | 'assertive'): AnnouncerUtterance;
|
|
46
|
+
/**
|
|
47
|
+
* Instruct the Announcer to add a pause of a certain duration (in ms). Will add this pause
|
|
48
|
+
* to the end of announcement queue
|
|
49
|
+
*
|
|
50
|
+
*/
|
|
51
|
+
pause(delay: number): AnnouncerUtterance;
|
|
52
|
+
/**
|
|
53
|
+
* Interupts and instantly stops any running text to speech utterance
|
|
54
|
+
*
|
|
55
|
+
*/
|
|
56
|
+
stop(): void;
|
|
57
|
+
/**
|
|
58
|
+
* Clears out the announcement queue of messages.
|
|
59
|
+
*/
|
|
60
|
+
clears(): void;
|
|
61
|
+
/**
|
|
62
|
+
* Enables the announcer.
|
|
63
|
+
*/
|
|
64
|
+
enable(): void;
|
|
65
|
+
/**
|
|
66
|
+
* Disables the announcer. Any messages passed in the announcer.speak() message
|
|
67
|
+
* will not be added to the queue
|
|
68
|
+
*/
|
|
69
|
+
disable(): void;
|
|
70
|
+
/**
|
|
71
|
+
* Toggles the announcer based on the passed toggle value (Boolean)
|
|
72
|
+
*/
|
|
73
|
+
toggle(toggle: Boolean): void;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
25
77
|
export interface Hooks {
|
|
26
78
|
/**
|
|
27
79
|
* Fires when the Component is being instantiated
|
|
@@ -269,7 +321,7 @@ declare module '@lightningjs/blits' {
|
|
|
269
321
|
/**
|
|
270
322
|
* Announcer methods for screen reader support
|
|
271
323
|
*/
|
|
272
|
-
|
|
324
|
+
$announcer: Announcer
|
|
273
325
|
|
|
274
326
|
/**
|
|
275
327
|
* Triggers a forced update on state variables.
|
package/package.json
CHANGED
|
@@ -17,9 +17,10 @@
|
|
|
17
17
|
|
|
18
18
|
import speechSynthesis from './speechSynthesis.js'
|
|
19
19
|
|
|
20
|
-
let debounce
|
|
21
|
-
|
|
22
20
|
let active = false
|
|
21
|
+
let count = 0
|
|
22
|
+
const queue = []
|
|
23
|
+
let isProcessing = false
|
|
23
24
|
|
|
24
25
|
const enable = () => {
|
|
25
26
|
active = true
|
|
@@ -35,16 +36,75 @@ const toggle = (v) => {
|
|
|
35
36
|
|
|
36
37
|
const speak = (message, politeness = 'off') => {
|
|
37
38
|
if (active === false) return
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
|
|
40
|
+
return addToQueue(message, politeness)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const pause = (delay) => {
|
|
44
|
+
if (active === false) return
|
|
45
|
+
|
|
46
|
+
return addToQueue(undefined, undefined, delay)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const addToQueue = (message, politeness, delay = false) => {
|
|
50
|
+
// keep track of the id so message can be canceled
|
|
51
|
+
const id = count++
|
|
52
|
+
|
|
53
|
+
// setup a promise to allow developer to chain functionality
|
|
54
|
+
// when specific utterances are done
|
|
55
|
+
let resolveFn
|
|
56
|
+
const done = new Promise((resolve) => {
|
|
57
|
+
resolveFn = resolve
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
// augment the promise with a cancel function
|
|
61
|
+
done.cancel = () => {
|
|
62
|
+
const index = queue.findIndex((item) => item.id === id)
|
|
63
|
+
if (index !== -1) queue.splice(index, 1)
|
|
64
|
+
resolveFn('canceled')
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// add message of pause
|
|
68
|
+
if (delay === false) {
|
|
69
|
+
politeness === 'assertive'
|
|
70
|
+
? queue.unshift({ message, resolveFn, id })
|
|
71
|
+
: queue.push({ message, resolveFn, id })
|
|
43
72
|
} else {
|
|
44
|
-
|
|
45
|
-
speechSynthesis.speak({ value: message })
|
|
46
|
-
}, 400)
|
|
73
|
+
queue.push({ delay, resolveFn, id })
|
|
47
74
|
}
|
|
75
|
+
|
|
76
|
+
setTimeout(() => {
|
|
77
|
+
processQueue()
|
|
78
|
+
}, 100)
|
|
79
|
+
|
|
80
|
+
return done
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const processQueue = async () => {
|
|
84
|
+
if (isProcessing === true || queue.length === 0) return
|
|
85
|
+
isProcessing = true
|
|
86
|
+
|
|
87
|
+
const { message, resolveFn, delay } = queue.shift()
|
|
88
|
+
|
|
89
|
+
if (delay !== false) {
|
|
90
|
+
setTimeout(() => {
|
|
91
|
+
isProcessing = false
|
|
92
|
+
resolveFn('finished')
|
|
93
|
+
processQueue()
|
|
94
|
+
}, delay)
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
speechSynthesis
|
|
98
|
+
.speak({ message })
|
|
99
|
+
.then(() => {
|
|
100
|
+
isProcessing = false
|
|
101
|
+
resolveFn('finished')
|
|
102
|
+
processQueue()
|
|
103
|
+
})
|
|
104
|
+
.catch((e) => {
|
|
105
|
+
isProcessing = false
|
|
106
|
+
resolveFn(e.error)
|
|
107
|
+
})
|
|
48
108
|
}
|
|
49
109
|
|
|
50
110
|
const polite = (message) => speak(message, 'polite')
|
|
@@ -55,6 +115,10 @@ const stop = () => {
|
|
|
55
115
|
speechSynthesis.cancel()
|
|
56
116
|
}
|
|
57
117
|
|
|
118
|
+
const clear = () => {
|
|
119
|
+
queue.length = 0
|
|
120
|
+
}
|
|
121
|
+
|
|
58
122
|
export default {
|
|
59
123
|
speak,
|
|
60
124
|
polite,
|
|
@@ -63,4 +127,6 @@ export default {
|
|
|
63
127
|
enable,
|
|
64
128
|
disable,
|
|
65
129
|
toggle,
|
|
130
|
+
clear,
|
|
131
|
+
pause,
|
|
66
132
|
}
|
|
@@ -17,8 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
const syn = window.speechSynthesis
|
|
19
19
|
|
|
20
|
-
const
|
|
21
|
-
const isAndroid = () => /android/i.test(getUA())
|
|
20
|
+
const isAndroid = /android/i.test((window.navigator || {}).userAgent || '')
|
|
22
21
|
|
|
23
22
|
let initialized = false
|
|
24
23
|
let infinityTimer = null
|
|
@@ -37,7 +36,7 @@ const resumeInfinity = (target) => {
|
|
|
37
36
|
}, 5000)
|
|
38
37
|
}
|
|
39
38
|
|
|
40
|
-
const
|
|
39
|
+
const defaultUtteranceProps = {
|
|
41
40
|
lang: 'en-US',
|
|
42
41
|
pitch: 1,
|
|
43
42
|
rate: 1,
|
|
@@ -46,82 +45,54 @@ const utterProps = {
|
|
|
46
45
|
}
|
|
47
46
|
|
|
48
47
|
const initialize = () => {
|
|
49
|
-
|
|
48
|
+
defaultUtteranceProps.voice = syn.getVoices()[0]
|
|
50
49
|
initialized = true
|
|
51
50
|
}
|
|
52
51
|
|
|
53
|
-
const
|
|
54
|
-
const
|
|
52
|
+
const speak = (options) => {
|
|
53
|
+
const utterance = new SpeechSynthesisUtterance(options.message)
|
|
55
54
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
utter.volume = e.volume || utterProps.volume
|
|
55
|
+
utterance.lang = options.lang || defaultUtteranceProps.lang
|
|
56
|
+
utterance.pitch = options.pitch || defaultUtteranceProps.pitch
|
|
57
|
+
utterance.rate = options.rate || defaultUtteranceProps.rate
|
|
58
|
+
utterance.voice = options.voice || defaultUtteranceProps.voice
|
|
59
|
+
utterance.volume = options.volume || defaultUtteranceProps.volume
|
|
62
60
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
resumeInfinity(utter)
|
|
61
|
+
if (isAndroid === false) {
|
|
62
|
+
utterance.onstart = () => {
|
|
63
|
+
resumeInfinity(utterance)
|
|
67
64
|
}
|
|
68
|
-
scope.onstart()
|
|
69
|
-
}
|
|
70
65
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
resumeInfinity(utter)
|
|
66
|
+
utterance.onresume = () => {
|
|
67
|
+
resumeInfinity(utterance)
|
|
74
68
|
}
|
|
75
|
-
scope.onresume()
|
|
76
69
|
}
|
|
77
70
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
syn.speak(utter)
|
|
71
|
+
return new Promise((resolve, reject) => {
|
|
72
|
+
utterance.onend = () => {
|
|
73
|
+
resolve()
|
|
74
|
+
}
|
|
75
|
+
utterance.onerror = (e) => {
|
|
76
|
+
reject(e)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
syn.speak(utterance)
|
|
80
|
+
})
|
|
89
81
|
}
|
|
90
82
|
|
|
91
83
|
export default {
|
|
92
|
-
speak(
|
|
84
|
+
speak(options) {
|
|
93
85
|
if (!initialized) {
|
|
94
86
|
initialize()
|
|
95
87
|
}
|
|
96
|
-
|
|
97
|
-
utterance(this, e)
|
|
98
|
-
},
|
|
99
|
-
resume() {
|
|
100
|
-
syn.resume()
|
|
101
|
-
},
|
|
102
|
-
pause() {
|
|
103
|
-
syn.pause()
|
|
88
|
+
return speak(options)
|
|
104
89
|
},
|
|
105
90
|
cancel() {
|
|
106
91
|
syn.cancel()
|
|
107
92
|
clear()
|
|
108
93
|
},
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
//event placeholder
|
|
114
|
-
},
|
|
115
|
-
onerror() {
|
|
116
|
-
//event placeholder
|
|
117
|
-
},
|
|
118
|
-
onstart() {
|
|
119
|
-
//event placeholder
|
|
120
|
-
},
|
|
121
|
-
onresume() {
|
|
122
|
-
//event placeholder
|
|
123
|
-
},
|
|
124
|
-
onpause() {
|
|
125
|
-
//eventplaceholder
|
|
126
|
-
},
|
|
94
|
+
// @todo
|
|
95
|
+
// getVoices() {
|
|
96
|
+
// return syn.getVoices()
|
|
97
|
+
// },
|
|
127
98
|
}
|
|
@@ -45,7 +45,8 @@ export default {
|
|
|
45
45
|
if (Object.getPrototypeOf(child) === Object.prototype) {
|
|
46
46
|
return Object.values(child).map((c) => {
|
|
47
47
|
// ugly hack .. but the point is to reference the right component
|
|
48
|
-
c.forComponent =
|
|
48
|
+
c.forComponent =
|
|
49
|
+
c[Symbol.for('config')] && c[Symbol.for('config')].parent.component
|
|
49
50
|
return c
|
|
50
51
|
})
|
|
51
52
|
}
|
package/src/component.js
CHANGED
|
@@ -272,7 +272,7 @@ const Component = (name = required('name'), config = required('config')) => {
|
|
|
272
272
|
|
|
273
273
|
// store the config on the factory, in order to access the config
|
|
274
274
|
// during the code generation step
|
|
275
|
-
factory.config = config
|
|
275
|
+
factory[Symbol.for('config')] = config
|
|
276
276
|
|
|
277
277
|
// To determine whether dynamic component is actual Blits component or not
|
|
278
278
|
factory[symbols.isComponent] = true
|
|
@@ -92,9 +92,9 @@ export default () =>
|
|
|
92
92
|
|
|
93
93
|
const skip3 = []
|
|
94
94
|
if (typeof cmp3 !== 'undefined') {
|
|
95
|
-
for (let key in cmp3.config.props) {
|
|
96
|
-
delete elementConfig3[cmp3.config.props[key]]
|
|
97
|
-
skip3.push(cmp3.config.props[key])
|
|
95
|
+
for (let key in cmp3[Symbol.for('config')].props) {
|
|
96
|
+
delete elementConfig3[cmp3[Symbol.for('config')].props[key]]
|
|
97
|
+
skip3.push(cmp3[Symbol.for('config')].props[key])
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
100
|
|
|
@@ -177,9 +177,9 @@ export default () =>
|
|
|
177
177
|
|
|
178
178
|
const skip6 = []
|
|
179
179
|
if (typeof cmp6 !== 'undefined') {
|
|
180
|
-
for (let key in cmp6.config.props) {
|
|
181
|
-
delete elementConfig6[cmp6.config.props[key]]
|
|
182
|
-
skip6.push(cmp6.config.props[key])
|
|
180
|
+
for (let key in cmp6[Symbol.for('config')].props) {
|
|
181
|
+
delete elementConfig6[cmp6[Symbol.for('config')].props[key]]
|
|
182
|
+
skip6.push(cmp6[Symbol.for('config')].props[key])
|
|
183
183
|
}
|
|
184
184
|
}
|
|
185
185
|
|
|
@@ -252,9 +252,9 @@ export default () =>
|
|
|
252
252
|
|
|
253
253
|
const skip8 = []
|
|
254
254
|
if (typeof cmp8 !== 'undefined') {
|
|
255
|
-
for (let key in cmp8.config.props) {
|
|
256
|
-
delete elementConfig8[cmp8.config.props[key]]
|
|
257
|
-
skip8.push(cmp8.config.props[key])
|
|
255
|
+
for (let key in cmp8[Symbol.for('config')].props) {
|
|
256
|
+
delete elementConfig8[cmp8[Symbol.for('config')].props[key]]
|
|
257
|
+
skip8.push(cmp8[Symbol.for('config')].props[key])
|
|
258
258
|
}
|
|
259
259
|
}
|
|
260
260
|
|
|
@@ -327,9 +327,9 @@ export default () =>
|
|
|
327
327
|
|
|
328
328
|
const skip10 = []
|
|
329
329
|
if (typeof cmp10 !== 'undefined') {
|
|
330
|
-
for (let key in cmp10.config.props) {
|
|
331
|
-
delete elementConfig10[cmp10.config.props[key]]
|
|
332
|
-
skip10.push(cmp10.config.props[key])
|
|
330
|
+
for (let key in cmp10[Symbol.for('config')].props) {
|
|
331
|
+
delete elementConfig10[cmp10[Symbol.for('config')].props[key]]
|
|
332
|
+
skip10.push(cmp10[Symbol.for('config')].props[key])
|
|
333
333
|
}
|
|
334
334
|
}
|
|
335
335
|
|
|
@@ -417,9 +417,9 @@ export default () =>
|
|
|
417
417
|
|
|
418
418
|
const skip13 = []
|
|
419
419
|
if (typeof cmp13 !== 'undefined') {
|
|
420
|
-
for (let key in cmp13.config.props) {
|
|
421
|
-
delete elementConfig13[cmp13.config.props[key]]
|
|
422
|
-
skip13.push(cmp13.config.props[key])
|
|
420
|
+
for (let key in cmp13[Symbol.for('config')].props) {
|
|
421
|
+
delete elementConfig13[cmp13[Symbol.for('config')].props[key]]
|
|
422
|
+
skip13.push(cmp13[Symbol.for('config')].props[key])
|
|
423
423
|
}
|
|
424
424
|
}
|
|
425
425
|
|
|
@@ -503,9 +503,9 @@ export default () =>
|
|
|
503
503
|
|
|
504
504
|
const skip16 = []
|
|
505
505
|
if (typeof cmp16 !== 'undefined') {
|
|
506
|
-
for (let key in cmp16.config.props) {
|
|
507
|
-
delete elementConfig16[cmp16.config.props[key]]
|
|
508
|
-
skip16.push(cmp16.config.props[key])
|
|
506
|
+
for (let key in cmp16[Symbol.for('config')].props) {
|
|
507
|
+
delete elementConfig16[cmp16[Symbol.for('config')].props[key]]
|
|
508
|
+
skip16.push(cmp16[Symbol.for('config')].props[key])
|
|
509
509
|
}
|
|
510
510
|
}
|
|
511
511
|
|
|
@@ -578,9 +578,9 @@ export default () =>
|
|
|
578
578
|
|
|
579
579
|
const skip18 = []
|
|
580
580
|
if (typeof cmp18 !== 'undefined') {
|
|
581
|
-
for (let key in cmp18.config.props) {
|
|
582
|
-
delete elementConfig18[cmp18.config.props[key]]
|
|
583
|
-
skip18.push(cmp18.config.props[key])
|
|
581
|
+
for (let key in cmp18[Symbol.for('config')].props) {
|
|
582
|
+
delete elementConfig18[cmp18[Symbol.for('config')].props[key]]
|
|
583
|
+
skip18.push(cmp18[Symbol.for('config')].props[key])
|
|
584
584
|
}
|
|
585
585
|
}
|
|
586
586
|
|
|
@@ -653,9 +653,9 @@ export default () =>
|
|
|
653
653
|
|
|
654
654
|
const skip20 = []
|
|
655
655
|
if (typeof cmp20 !== 'undefined') {
|
|
656
|
-
for (let key in cmp20.config.props) {
|
|
657
|
-
delete elementConfig20[cmp20.config.props[key]]
|
|
658
|
-
skip20.push(cmp20.config.props[key])
|
|
656
|
+
for (let key in cmp20[Symbol.for('config')].props) {
|
|
657
|
+
delete elementConfig20[cmp20[Symbol.for('config')].props[key]]
|
|
658
|
+
skip20.push(cmp20[Symbol.for('config')].props[key])
|
|
659
659
|
}
|
|
660
660
|
}
|
|
661
661
|
|
|
@@ -744,9 +744,9 @@ export default () =>
|
|
|
744
744
|
|
|
745
745
|
const skip23 = []
|
|
746
746
|
if (typeof cmp23 !== 'undefined') {
|
|
747
|
-
for (let key in cmp23.config.props) {
|
|
748
|
-
delete elementConfig23[cmp23.config.props[key]]
|
|
749
|
-
skip23.push(cmp23.config.props[key])
|
|
747
|
+
for (let key in cmp23[Symbol.for('config')].props) {
|
|
748
|
+
delete elementConfig23[cmp23[Symbol.for('config')].props[key]]
|
|
749
|
+
skip23.push(cmp23[Symbol.for('config')].props[key])
|
|
750
750
|
}
|
|
751
751
|
}
|
|
752
752
|
|
|
@@ -830,9 +830,9 @@ export default () =>
|
|
|
830
830
|
|
|
831
831
|
const skip26 = []
|
|
832
832
|
if (typeof cmp26 !== 'undefined') {
|
|
833
|
-
for (let key in cmp26.config.props) {
|
|
834
|
-
delete elementConfig26[cmp26.config.props[key]]
|
|
835
|
-
skip26.push(cmp26.config.props[key])
|
|
833
|
+
for (let key in cmp26[Symbol.for('config')].props) {
|
|
834
|
+
delete elementConfig26[cmp26[Symbol.for('config')].props[key]]
|
|
835
|
+
skip26.push(cmp26[Symbol.for('config')].props[key])
|
|
836
836
|
}
|
|
837
837
|
}
|
|
838
838
|
|
|
@@ -905,9 +905,9 @@ export default () =>
|
|
|
905
905
|
|
|
906
906
|
const skip28 = []
|
|
907
907
|
if (typeof cmp28 !== 'undefined') {
|
|
908
|
-
for (let key in cmp28.config.props) {
|
|
909
|
-
delete elementConfig28[cmp28.config.props[key]]
|
|
910
|
-
skip28.push(cmp28.config.props[key])
|
|
908
|
+
for (let key in cmp28[Symbol.for('config')].props) {
|
|
909
|
+
delete elementConfig28[cmp28[Symbol.for('config')].props[key]]
|
|
910
|
+
skip28.push(cmp28[Symbol.for('config')].props[key])
|
|
911
911
|
}
|
|
912
912
|
}
|
|
913
913
|
|
|
@@ -980,9 +980,9 @@ export default () =>
|
|
|
980
980
|
|
|
981
981
|
const skip30 = []
|
|
982
982
|
if (typeof cmp30 !== 'undefined') {
|
|
983
|
-
for (let key in cmp30.config.props) {
|
|
984
|
-
delete elementConfig30[cmp30.config.props[key]]
|
|
985
|
-
skip30.push(cmp30.config.props[key])
|
|
983
|
+
for (let key in cmp30[Symbol.for('config')].props) {
|
|
984
|
+
delete elementConfig30[cmp30[Symbol.for('config')].props[key]]
|
|
985
|
+
skip30.push(cmp30[Symbol.for('config')].props[key])
|
|
986
986
|
}
|
|
987
987
|
}
|
|
988
988
|
|
|
@@ -1070,9 +1070,9 @@ export default () =>
|
|
|
1070
1070
|
|
|
1071
1071
|
const skip33 = []
|
|
1072
1072
|
if (typeof cmp33 !== 'undefined') {
|
|
1073
|
-
for (let key in cmp33.config.props) {
|
|
1074
|
-
delete elementConfig33[cmp33.config.props[key]]
|
|
1075
|
-
skip33.push(cmp33.config.props[key])
|
|
1073
|
+
for (let key in cmp33[Symbol.for('config')].props) {
|
|
1074
|
+
delete elementConfig33[cmp33[Symbol.for('config')].props[key]]
|
|
1075
|
+
skip33.push(cmp33[Symbol.for('config')].props[key])
|
|
1076
1076
|
}
|
|
1077
1077
|
}
|
|
1078
1078
|
|
|
@@ -1155,9 +1155,9 @@ export default () =>
|
|
|
1155
1155
|
|
|
1156
1156
|
const skip36 = []
|
|
1157
1157
|
if (typeof cmp36 !== 'undefined') {
|
|
1158
|
-
for (let key in cmp36.config.props) {
|
|
1159
|
-
delete elementConfig36[cmp36.config.props[key]]
|
|
1160
|
-
skip36.push(cmp36.config.props[key])
|
|
1158
|
+
for (let key in cmp36[Symbol.for('config')].props) {
|
|
1159
|
+
delete elementConfig36[cmp36[Symbol.for('config')].props[key]]
|
|
1160
|
+
skip36.push(cmp36[Symbol.for('config')].props[key])
|
|
1161
1161
|
}
|
|
1162
1162
|
}
|
|
1163
1163
|
|
|
@@ -1230,9 +1230,9 @@ export default () =>
|
|
|
1230
1230
|
|
|
1231
1231
|
const skip38 = []
|
|
1232
1232
|
if (typeof cmp38 !== 'undefined') {
|
|
1233
|
-
for (let key in cmp38.config.props) {
|
|
1234
|
-
delete elementConfig38[cmp38.config.props[key]]
|
|
1235
|
-
skip38.push(cmp38.config.props[key])
|
|
1233
|
+
for (let key in cmp38[Symbol.for('config')].props) {
|
|
1234
|
+
delete elementConfig38[cmp38[Symbol.for('config')].props[key]]
|
|
1235
|
+
skip38.push(cmp38[Symbol.for('config')].props[key])
|
|
1236
1236
|
}
|
|
1237
1237
|
}
|
|
1238
1238
|
|
|
@@ -1305,9 +1305,9 @@ export default () =>
|
|
|
1305
1305
|
|
|
1306
1306
|
const skip40 = []
|
|
1307
1307
|
if (typeof cmp40 !== 'undefined') {
|
|
1308
|
-
for (let key in cmp40.config.props) {
|
|
1309
|
-
delete elementConfig40[cmp40.config.props[key]]
|
|
1310
|
-
skip40.push(cmp40.config.props[key])
|
|
1308
|
+
for (let key in cmp40[Symbol.for('config')].props) {
|
|
1309
|
+
delete elementConfig40[cmp40[Symbol.for('config')].props[key]]
|
|
1310
|
+
skip40.push(cmp40[Symbol.for('config')].props[key])
|
|
1311
1311
|
}
|
|
1312
1312
|
}
|
|
1313
1313
|
|
package/src/launch.js
CHANGED
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
import Settings from './settings.js'
|
|
19
19
|
import { initLog, Log } from './lib/log.js'
|
|
20
20
|
import engine from './engine.js'
|
|
21
|
-
import
|
|
21
|
+
import blitsPackageInfo from '../package.json' assert { type: 'json' }
|
|
22
|
+
import rendererPackageInfo from '../../renderer/package.json' assert { type: 'json' }
|
|
22
23
|
|
|
23
24
|
export let renderer = {}
|
|
24
25
|
export const stage = {}
|
|
@@ -27,8 +28,8 @@ export default (App, target, settings) => {
|
|
|
27
28
|
Settings.set(settings)
|
|
28
29
|
|
|
29
30
|
initLog()
|
|
30
|
-
Log.info('Blits Version ',
|
|
31
|
-
Log.info('Renderer Version ',
|
|
31
|
+
Log.info('Blits Version ', blitsPackageInfo.version)
|
|
32
|
+
Log.info('Renderer Version ', rendererPackageInfo.version)
|
|
32
33
|
|
|
33
34
|
stage.element = engine.Element
|
|
34
35
|
|
|
@@ -191,9 +191,9 @@ const generateElementCode = function (
|
|
|
191
191
|
renderCode.push(`
|
|
192
192
|
const skip${counter} = []
|
|
193
193
|
if(typeof cmp${counter} !== 'undefined') {
|
|
194
|
-
for(let key in cmp${counter}.config.props) {
|
|
195
|
-
delete elementConfig${counter}[cmp${counter}.config.props[key]]
|
|
196
|
-
skip${counter}.push(cmp${counter}.config.props[key])
|
|
194
|
+
for(let key in cmp${counter}[Symbol.for('config')].props) {
|
|
195
|
+
delete elementConfig${counter}[cmp${counter}[Symbol.for('config')].props[key]]
|
|
196
|
+
skip${counter}.push(cmp${counter}[Symbol.for('config')].props[key])
|
|
197
197
|
}
|
|
198
198
|
}
|
|
199
199
|
`)
|
|
@@ -1103,9 +1103,9 @@ test('Generate code for a template with custom components', (assert) => {
|
|
|
1103
1103
|
const skip1 = []
|
|
1104
1104
|
|
|
1105
1105
|
if (typeof cmp1 !== 'undefined') {
|
|
1106
|
-
for (let key in cmp1.config.props) {
|
|
1107
|
-
delete elementConfig1[cmp1.config.props[key]]
|
|
1108
|
-
skip1.push(cmp1.config.props[key])
|
|
1106
|
+
for (let key in cmp1[Symbol.for('config')].props) {
|
|
1107
|
+
delete elementConfig1[cmp1[Symbol.for('config')].props[key]]
|
|
1108
|
+
skip1.push(cmp1[Symbol.for('config')].props[key])
|
|
1109
1109
|
}
|
|
1110
1110
|
}
|
|
1111
1111
|
|
|
@@ -1149,9 +1149,9 @@ test('Generate code for a template with custom components', (assert) => {
|
|
|
1149
1149
|
const skip3 = []
|
|
1150
1150
|
|
|
1151
1151
|
if (typeof cmp3 !== 'undefined') {
|
|
1152
|
-
for (let key in cmp3.config.props) {
|
|
1153
|
-
delete elementConfig3[cmp3.config.props[key]]
|
|
1154
|
-
skip3.push(cmp3.config.props[key])
|
|
1152
|
+
for (let key in cmp3[Symbol.for('config')].props) {
|
|
1153
|
+
delete elementConfig3[cmp3[Symbol.for('config')].props[key]]
|
|
1154
|
+
skip3.push(cmp3[Symbol.for('config')].props[key])
|
|
1155
1155
|
}
|
|
1156
1156
|
}
|
|
1157
1157
|
|
|
@@ -1252,9 +1252,9 @@ test('Generate code for a template with an unregistered custom component', (asse
|
|
|
1252
1252
|
elms[1] = this.element({ parent: parent || 'root' }, inSlot === true ? slotComponent : component)
|
|
1253
1253
|
const skip1 = []
|
|
1254
1254
|
if (typeof cmp1 !== 'undefined') {
|
|
1255
|
-
for (let key in cmp1.config.props) {
|
|
1256
|
-
delete elementConfig1[cmp1.config.props[key]]
|
|
1257
|
-
skip1.push(cmp1.config.props[key])
|
|
1255
|
+
for (let key in cmp1[Symbol.for('config')].props) {
|
|
1256
|
+
delete elementConfig1[cmp1[Symbol.for('config')].props[key]]
|
|
1257
|
+
skip1.push(cmp1[Symbol.for('config')].props[key])
|
|
1258
1258
|
}
|
|
1259
1259
|
}
|
|
1260
1260
|
elms[1].populate(elementConfig1)
|
|
@@ -1292,9 +1292,9 @@ test('Generate code for a template with an unregistered custom component', (asse
|
|
|
1292
1292
|
elms[3] = this.element({ parent: parent || 'root' }, inSlot === true ? slotComponent : component)
|
|
1293
1293
|
const skip3 = []
|
|
1294
1294
|
if (typeof cmp3 !== 'undefined') {
|
|
1295
|
-
for (let key in cmp3.config.props) {
|
|
1296
|
-
delete elementConfig3[cmp3.config.props[key]]
|
|
1297
|
-
skip3.push(cmp3.config.props[key])
|
|
1295
|
+
for (let key in cmp3[Symbol.for('config')].props) {
|
|
1296
|
+
delete elementConfig3[cmp3[Symbol.for('config')].props[key]]
|
|
1297
|
+
skip3.push(cmp3[Symbol.for('config')].props[key])
|
|
1298
1298
|
}
|
|
1299
1299
|
}
|
|
1300
1300
|
elms[3].populate(elementConfig3)
|
|
@@ -1394,9 +1394,9 @@ test('Generate code for a template with custom components with arguments', (asse
|
|
|
1394
1394
|
elementConfig1['x'] = 10
|
|
1395
1395
|
const skip1 = []
|
|
1396
1396
|
if (typeof cmp1 !== 'undefined') {
|
|
1397
|
-
for (let key in cmp1.config.props) {
|
|
1398
|
-
delete elementConfig1[cmp1.config.props[key]]
|
|
1399
|
-
skip1.push(cmp1.config.props[key])
|
|
1397
|
+
for (let key in cmp1[Symbol.for('config')].props) {
|
|
1398
|
+
delete elementConfig1[cmp1[Symbol.for('config')].props[key]]
|
|
1399
|
+
skip1.push(cmp1[Symbol.for('config')].props[key])
|
|
1400
1400
|
}
|
|
1401
1401
|
}
|
|
1402
1402
|
elms[1].populate(elementConfig1)
|
|
@@ -1437,9 +1437,9 @@ test('Generate code for a template with custom components with arguments', (asse
|
|
|
1437
1437
|
elementConfig3['img'] = component.img
|
|
1438
1438
|
const skip3 = []
|
|
1439
1439
|
if (typeof cmp3 !== 'undefined') {
|
|
1440
|
-
for (let key in cmp3.config.props) {
|
|
1441
|
-
delete elementConfig3[cmp3.config.props[key]]
|
|
1442
|
-
skip3.push(cmp3.config.props[key])
|
|
1440
|
+
for (let key in cmp3[Symbol.for('config')].props) {
|
|
1441
|
+
delete elementConfig3[cmp3[Symbol.for('config')].props[key]]
|
|
1442
|
+
skip3.push(cmp3[Symbol.for('config')].props[key])
|
|
1443
1443
|
}
|
|
1444
1444
|
}
|
|
1445
1445
|
elms[3].populate(elementConfig3)
|
|
@@ -1561,9 +1561,9 @@ test('Generate code for a template with custom components with argument value as
|
|
|
1561
1561
|
|
|
1562
1562
|
const skip1 = []
|
|
1563
1563
|
if (typeof cmp1 !== 'undefined') {
|
|
1564
|
-
for (let key in cmp1.config.props) {
|
|
1565
|
-
delete elementConfig1[cmp1.config.props[key]]
|
|
1566
|
-
skip1.push(cmp1.config.props[key])
|
|
1564
|
+
for (let key in cmp1[Symbol.for('config')].props) {
|
|
1565
|
+
delete elementConfig1[cmp1[Symbol.for('config')].props[key]]
|
|
1566
|
+
skip1.push(cmp1[Symbol.for('config')].props[key])
|
|
1567
1567
|
}
|
|
1568
1568
|
}
|
|
1569
1569
|
|
|
@@ -1681,9 +1681,9 @@ test('Generate code for a template with custom components with reactive props',
|
|
|
1681
1681
|
elementConfig1['img'] = component.image
|
|
1682
1682
|
const skip1 = []
|
|
1683
1683
|
if(typeof cmp1 !== 'undefined') {
|
|
1684
|
-
for(let key in cmp1.config.props) {
|
|
1685
|
-
delete elementConfig1[cmp1.config.props[key]]
|
|
1686
|
-
skip1.push(cmp1.config.props[key])
|
|
1684
|
+
for(let key in cmp1[Symbol.for('config')].props) {
|
|
1685
|
+
delete elementConfig1[cmp1[Symbol.for('config')].props[key]]
|
|
1686
|
+
skip1.push(cmp1[Symbol.for('config')].props[key])
|
|
1687
1687
|
}
|
|
1688
1688
|
}
|
|
1689
1689
|
elms[1].populate(elementConfig1)
|
|
@@ -1730,9 +1730,9 @@ test('Generate code for a template with custom components with reactive props',
|
|
|
1730
1730
|
elementConfig3['img'] = component.image
|
|
1731
1731
|
const skip3 = []
|
|
1732
1732
|
if(typeof cmp3 !== 'undefined') {
|
|
1733
|
-
for(let key in cmp3.config.props) {
|
|
1734
|
-
delete elementConfig3[cmp3.config.props[key]]
|
|
1735
|
-
skip3.push(cmp3.config.props[key])
|
|
1733
|
+
for(let key in cmp3[Symbol.for('config')].props) {
|
|
1734
|
+
delete elementConfig3[cmp3[Symbol.for('config')].props[key]]
|
|
1735
|
+
skip3.push(cmp3[Symbol.for('config')].props[key])
|
|
1736
1736
|
}
|
|
1737
1737
|
}
|
|
1738
1738
|
elms[3].populate(elementConfig3)
|
|
@@ -1943,9 +1943,9 @@ test('Generate code for a template with slot content', (assert) => {
|
|
|
1943
1943
|
elementConfig1['h'] = 1080
|
|
1944
1944
|
const skip1 = []
|
|
1945
1945
|
if (typeof cmp1 !== 'undefined') {
|
|
1946
|
-
for (let key in cmp1.config.props) {
|
|
1947
|
-
delete elementConfig1[cmp1.config.props[key]]
|
|
1948
|
-
skip1.push(cmp1.config.props[key])
|
|
1946
|
+
for (let key in cmp1[Symbol.for('config')].props) {
|
|
1947
|
+
delete elementConfig1[cmp1[Symbol.for('config')].props[key]]
|
|
1948
|
+
skip1.push(cmp1[Symbol.for('config')].props[key])
|
|
1949
1949
|
}
|
|
1950
1950
|
}
|
|
1951
1951
|
elms[1].populate(elementConfig1)
|
|
@@ -2087,9 +2087,9 @@ test('Generate code for a template with slot content, using a named slot', (asse
|
|
|
2087
2087
|
elementConfig1['h'] = 1080
|
|
2088
2088
|
const skip1 = []
|
|
2089
2089
|
if (typeof cmp1 !== 'undefined') {
|
|
2090
|
-
for (let key in cmp1.config.props) {
|
|
2091
|
-
delete elementConfig1[cmp1.config.props[key]]
|
|
2092
|
-
skip1.push(cmp1.config.props[key])
|
|
2090
|
+
for (let key in cmp1[Symbol.for('config')].props) {
|
|
2091
|
+
delete elementConfig1[cmp1[Symbol.for('config')].props[key]]
|
|
2092
|
+
skip1.push(cmp1[Symbol.for('config')].props[key])
|
|
2093
2093
|
}
|
|
2094
2094
|
}
|
|
2095
2095
|
elms[1].populate(elementConfig1)
|
|
@@ -3173,9 +3173,9 @@ test('Generate code for a template with a simple for-loop on a Component with a
|
|
|
3173
3173
|
}
|
|
3174
3174
|
const skip1 = []
|
|
3175
3175
|
if (typeof cmp1 !== 'undefined') {
|
|
3176
|
-
for (let key in cmp1.config.props) {
|
|
3177
|
-
delete elementConfig1[cmp1.config.props[key]]
|
|
3178
|
-
skip1.push(cmp1.config.props[key])
|
|
3176
|
+
for (let key in cmp1[Symbol.for('config')].props) {
|
|
3177
|
+
delete elementConfig1[cmp1[Symbol.for('config')].props[key]]
|
|
3178
|
+
skip1.push(cmp1[Symbol.for('config')].props[key])
|
|
3179
3179
|
}
|
|
3180
3180
|
}
|
|
3181
3181
|
if (elms[1][scope.key].nodeId === undefined) {
|
package/src/lib/symbols.js
CHANGED
|
@@ -37,6 +37,8 @@ export default {
|
|
|
37
37
|
children: Symbol.for('children'),
|
|
38
38
|
// Symbol 'components' utilized within generated code
|
|
39
39
|
components: Symbol.for('components'),
|
|
40
|
+
// Symbol 'config' utilized within generated code
|
|
41
|
+
config: Symbol.for('config'),
|
|
40
42
|
// Symbol 'isSlot' utilized within generated code
|
|
41
43
|
isSlot: Symbol.for('isSlot'),
|
|
42
44
|
// Symbol 'props' utilized within generated code
|
package/src/router/router.js
CHANGED
|
@@ -125,6 +125,8 @@ export const matchHash = (path, routes = []) => {
|
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
export const navigate = async function () {
|
|
128
|
+
Announcer.stop()
|
|
129
|
+
Announcer.clear()
|
|
128
130
|
state.navigating = true
|
|
129
131
|
if (this.parent[symbols.routes]) {
|
|
130
132
|
let previousRoute = currentRoute ? Object.assign({}, currentRoute) : undefined
|
|
@@ -183,6 +185,16 @@ export const navigate = async function () {
|
|
|
183
185
|
let routeData
|
|
184
186
|
let { view, focus } = cacheMap.get(route.hash) || {}
|
|
185
187
|
|
|
188
|
+
// Announce route change if a message has been specified for this route
|
|
189
|
+
if (route.announce) {
|
|
190
|
+
if (typeof route.announce === 'string') {
|
|
191
|
+
route.announce = {
|
|
192
|
+
message: route.announce,
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
Announcer.speak(route.announce.message, route.announce.politeness)
|
|
196
|
+
}
|
|
197
|
+
|
|
186
198
|
if (!view) {
|
|
187
199
|
// create a holder element for the new view
|
|
188
200
|
holder = stage.element({ parent: this[symbols.children][0] })
|
|
@@ -210,6 +222,7 @@ export const navigate = async function () {
|
|
|
210
222
|
}
|
|
211
223
|
|
|
212
224
|
view = await route.component({ props }, holder, this)
|
|
225
|
+
|
|
213
226
|
if (view[Symbol.toStringTag] === 'Module') {
|
|
214
227
|
if (view.default && typeof view.default === 'function') {
|
|
215
228
|
view = view.default({ props }, holder, this)
|
|
@@ -294,16 +307,6 @@ export const navigate = async function () {
|
|
|
294
307
|
}
|
|
295
308
|
}
|
|
296
309
|
|
|
297
|
-
// Announce route change if a message has been specified for this route
|
|
298
|
-
if (route.announce) {
|
|
299
|
-
if (typeof route.announce === 'string') {
|
|
300
|
-
route.announce = {
|
|
301
|
-
message: route.announce,
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
Announcer.speak(route.announce.message, route.announce.politeness)
|
|
305
|
-
}
|
|
306
|
-
|
|
307
310
|
this.activeView = this[symbols.children][this[symbols.children].length - 1]
|
|
308
311
|
} else {
|
|
309
312
|
Log.error(`Route ${hash} not found`)
|