@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 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` of a message to be announced.
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 mean that whenever you call `this.$announcer.speak()` there will actually not be anything spoken out via the Text To Speech engine.
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
- // $announcer: Announcer
324
+ $announcer: Announcer
273
325
 
274
326
  /**
275
327
  * Triggers a forced update on state variables.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightningjs/blits",
3
- "version": "1.25.1",
3
+ "version": "1.26.0",
4
4
  "description": "Blits: The Lightning 3 App Development Framework",
5
5
  "bin": "bin/index.js",
6
6
  "exports": {
@@ -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
- clearTimeout(debounce)
39
- speechSynthesis.cancel()
40
- // assertive messages get spoken immediately
41
- if (politeness === 'assertive') {
42
- speechSynthesis.speak({ value: message })
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
- debounce = setTimeout(() => {
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 getUA = () => (window.navigator || {}).userAgent || ''
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 utterProps = {
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
- utterProps.voice = syn.getVoices()[0]
48
+ defaultUtteranceProps.voice = syn.getVoices()[0]
50
49
  initialized = true
51
50
  }
52
51
 
53
- const utterance = (scope, e) => {
54
- const utter = new SpeechSynthesisUtterance(e.value)
52
+ const speak = (options) => {
53
+ const utterance = new SpeechSynthesisUtterance(options.message)
55
54
 
56
- // utter props
57
- utter.lang = e.lang || utterProps.lang
58
- utter.pitch = e.pitch || utterProps.pitch
59
- utter.rate = e.rate || utterProps.rate
60
- utter.voice = e.voice || utterProps.voice
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
- // utter events
64
- utter.onstart = () => {
65
- if (!isAndroid()) {
66
- resumeInfinity(utter)
61
+ if (isAndroid === false) {
62
+ utterance.onstart = () => {
63
+ resumeInfinity(utterance)
67
64
  }
68
- scope.onstart()
69
- }
70
65
 
71
- utter.onresume = () => {
72
- if (!isAndroid()) {
73
- resumeInfinity(utter)
66
+ utterance.onresume = () => {
67
+ resumeInfinity(utterance)
74
68
  }
75
- scope.onresume()
76
69
  }
77
70
 
78
- utter.onpause = () => {
79
- scope.onpause()
80
- }
81
- utter.onend = () => {
82
- scope.onend()
83
- }
84
- utter.onerror = () => {
85
- clear()
86
- scope.onerror()
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(e) {
84
+ speak(options) {
93
85
  if (!initialized) {
94
86
  initialize()
95
87
  }
96
- this.cancel()
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
- getVoices() {
110
- return syn.getVoices()
111
- },
112
- onend() {
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 = c.config && c.config.parent.component
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 packageInfo from '../package.json' assert { type: 'json' }
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 ', packageInfo.version)
31
- Log.info('Renderer Version ', packageInfo.dependencies['@lightningjs/renderer'])
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) {
@@ -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
@@ -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`)