@lightningjs/blits 2.6.0 → 2.8.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 +16 -0
- package/index.d.ts +24 -6
- package/package.json +6 -2
- package/src/announcer/announcer.js +29 -6
- package/src/announcer/speechSynthesis.js +25 -8
- package/src/component.js +6 -1
- package/src/engines/L3/element.js +14 -2
- package/src/platform.js +4 -7
- package/src/platforms/webOS/announcer.d.ts +39 -0
- package/src/platforms/webOS/announcer.js +77 -0
- package/src/testing/index.d.ts +2 -0
- package/src/testing/nodePlatform.js +4 -12
- package/src/testing/renderComponent.js +51 -1
- package/vite/msdfGenerator.js +10 -2
- package/test-output.txt +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.8.0
|
|
4
|
+
|
|
5
|
+
_16 jul 2026_
|
|
6
|
+
|
|
7
|
+
- Bumped renderer to 3.1.3
|
|
8
|
+
- Disabled caching of msdf fonts during dev
|
|
9
|
+
|
|
10
|
+
## 2.7.0
|
|
11
|
+
|
|
12
|
+
_2 jul 2026_
|
|
13
|
+
|
|
14
|
+
- Added `keepAlive` option to `src`-object to prevent a texture from being cleaned up by the renderer
|
|
15
|
+
- Abstracted announcer (and speechSynthesis) to be platform provided (with speechSynthesis as the built-in default)
|
|
16
|
+
- Added dedicated findByData methods to testing harness to select specific nodes in the tree
|
|
17
|
+
- Improved test cases around frameTick cleanup
|
|
18
|
+
|
|
3
19
|
## 2.6.0
|
|
4
20
|
|
|
5
21
|
_25 jun 2026_
|
package/index.d.ts
CHANGED
|
@@ -66,6 +66,28 @@ declare module '@lightningjs/blits' {
|
|
|
66
66
|
enableUtteranceKeepAlive?: boolean
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
export interface AnnouncerDriverOptions extends AnnouncerUtteranceOptions {
|
|
70
|
+
/**
|
|
71
|
+
* Message to be spoken by the platform announcer driver.
|
|
72
|
+
*/
|
|
73
|
+
message: string | number,
|
|
74
|
+
/**
|
|
75
|
+
* Internal announcement id used by the Blits announcer queue.
|
|
76
|
+
*/
|
|
77
|
+
id: number,
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface AnnouncerDriver {
|
|
81
|
+
/**
|
|
82
|
+
* Speak one queued announcement.
|
|
83
|
+
*/
|
|
84
|
+
speak(options: AnnouncerDriverOptions): Promise<any>,
|
|
85
|
+
/**
|
|
86
|
+
* Stop the active platform announcement, when supported.
|
|
87
|
+
*/
|
|
88
|
+
cancel(): void,
|
|
89
|
+
}
|
|
90
|
+
|
|
69
91
|
export interface AnnouncerUtterance<T = any> extends Promise<T> {
|
|
70
92
|
/**
|
|
71
93
|
* Removes a specific message from the announcement queue,
|
|
@@ -1070,13 +1092,9 @@ declare module '@lightningjs/blits' {
|
|
|
1070
1092
|
*/
|
|
1071
1093
|
createKeyboardEvent?: (type: string, init?: KeyboardEventInit | any) => any,
|
|
1072
1094
|
/**
|
|
1073
|
-
*
|
|
1074
|
-
*/
|
|
1075
|
-
SpeechSynthesisUtterance?: typeof SpeechSynthesisUtterance | any,
|
|
1076
|
-
/**
|
|
1077
|
-
* Speech synthesis API implementation used by the announcer.
|
|
1095
|
+
* Platform-specific announcer driver used instead of the default Web Speech driver.
|
|
1078
1096
|
*/
|
|
1079
|
-
|
|
1097
|
+
announcer?: AnnouncerDriver,
|
|
1080
1098
|
/**
|
|
1081
1099
|
* Monotonic timestamp provider used for input throttling.
|
|
1082
1100
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lightningjs/blits",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.8.0",
|
|
4
4
|
"description": "Blits: The Lightning 3 App Development Framework",
|
|
5
5
|
"bin": "bin/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -32,6 +32,10 @@
|
|
|
32
32
|
"types": "./src/plugins/storage/storage.d.ts",
|
|
33
33
|
"default": "./src/plugins/storage/storage.js"
|
|
34
34
|
},
|
|
35
|
+
"./platforms/webOS/announcer": {
|
|
36
|
+
"types": "./src/platforms/webOS/announcer.d.ts",
|
|
37
|
+
"default": "./src/platforms/webOS/announcer.js"
|
|
38
|
+
},
|
|
35
39
|
"./testing": {
|
|
36
40
|
"types": "./src/testing/index.d.ts",
|
|
37
41
|
"default": "./src/testing/index.js"
|
|
@@ -83,7 +87,7 @@
|
|
|
83
87
|
"tape": "^5.5.0"
|
|
84
88
|
},
|
|
85
89
|
"dependencies": {
|
|
86
|
-
"@lightningjs/renderer": "^3.
|
|
90
|
+
"@lightningjs/renderer": "^3.1.3",
|
|
87
91
|
"magic-string": "^0.30.21"
|
|
88
92
|
},
|
|
89
93
|
"repository": {
|
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import { Log } from '../lib/log.js'
|
|
19
|
-
import speechSynthesis from './speechSynthesis.js'
|
|
20
19
|
import { platform } from '../platform.js'
|
|
21
20
|
|
|
22
21
|
let active = false
|
|
@@ -31,6 +30,20 @@ let globalDefaultOptions = {
|
|
|
31
30
|
enableUtteranceKeepAlive: !/android/i.test(platform.userAgent || ''),
|
|
32
31
|
}
|
|
33
32
|
|
|
33
|
+
const getDriver = () => {
|
|
34
|
+
const driver = platform.announcer
|
|
35
|
+
if (driver && typeof driver.speak === 'function') {
|
|
36
|
+
return driver
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const cancelDriver = () => {
|
|
41
|
+
const driver = getDriver()
|
|
42
|
+
if (driver && typeof driver.cancel === 'function') {
|
|
43
|
+
driver.cancel()
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
34
47
|
const noopAnnouncement = {
|
|
35
48
|
then() {},
|
|
36
49
|
done() {},
|
|
@@ -84,9 +97,9 @@ const addToQueue = (message, politeness, delay = false, options = {}) => {
|
|
|
84
97
|
// augment the promise with a stop function
|
|
85
98
|
done.stop = () => {
|
|
86
99
|
if (id === currentId) {
|
|
87
|
-
|
|
100
|
+
cancelDriver()
|
|
88
101
|
isProcessing = false
|
|
89
|
-
resolveFn('
|
|
102
|
+
resolveFn('interrupted')
|
|
90
103
|
}
|
|
91
104
|
}
|
|
92
105
|
|
|
@@ -133,7 +146,17 @@ const processQueue = async () => {
|
|
|
133
146
|
debounce = setTimeout(() => {
|
|
134
147
|
Log.debug(`Announcer - speaking: "${message}" (id: ${id})`)
|
|
135
148
|
|
|
136
|
-
|
|
149
|
+
const driver = getDriver()
|
|
150
|
+
if (driver === undefined) {
|
|
151
|
+
currentId = null
|
|
152
|
+
currentResolveFn = null
|
|
153
|
+
isProcessing = false
|
|
154
|
+
resolveFn('unavailable')
|
|
155
|
+
processQueue()
|
|
156
|
+
return
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
driver
|
|
137
160
|
.speak({
|
|
138
161
|
message,
|
|
139
162
|
id,
|
|
@@ -180,8 +203,8 @@ const stop = () => {
|
|
|
180
203
|
// Clear debounce timer if speech hasn't started yet
|
|
181
204
|
clearDebounceTimer()
|
|
182
205
|
|
|
183
|
-
// Always cancel
|
|
184
|
-
|
|
206
|
+
// Always cancel the platform announcer to ensure clean state
|
|
207
|
+
cancelDriver()
|
|
185
208
|
|
|
186
209
|
// Store resolve function before resetting state
|
|
187
210
|
const resolveFn = currentResolveFn
|
|
@@ -16,11 +16,28 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import { Log } from '../lib/log.js'
|
|
19
|
-
import { platform } from '../platform.js'
|
|
20
19
|
|
|
21
20
|
const utterances = new Map() // id -> { utterance, timer, ignoreResume }
|
|
22
21
|
|
|
23
22
|
let initialized = false
|
|
23
|
+
const globalScope = globalThis
|
|
24
|
+
|
|
25
|
+
const getSpeechSynthesis = () => {
|
|
26
|
+
const windowRef = globalScope.window
|
|
27
|
+
const selfRef = globalScope.self || windowRef || globalScope
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
globalScope.speechSynthesis ||
|
|
31
|
+
(selfRef && selfRef.speechSynthesis) ||
|
|
32
|
+
(windowRef && windowRef.speechSynthesis)
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const getSpeechSynthesisUtterance = () => {
|
|
37
|
+
const windowRef = globalScope.window
|
|
38
|
+
|
|
39
|
+
return globalScope.SpeechSynthesisUtterance || (windowRef && windowRef.SpeechSynthesisUtterance)
|
|
40
|
+
}
|
|
24
41
|
|
|
25
42
|
const clear = (id) => {
|
|
26
43
|
const state = utterances.get(id)
|
|
@@ -35,7 +52,7 @@ const clear = (id) => {
|
|
|
35
52
|
}
|
|
36
53
|
|
|
37
54
|
const startKeepAlive = (id) => {
|
|
38
|
-
const syn =
|
|
55
|
+
const syn = getSpeechSynthesis()
|
|
39
56
|
const state = utterances.get(id)
|
|
40
57
|
|
|
41
58
|
// utterance status: utterance was removed (cancelled or finished)
|
|
@@ -85,7 +102,7 @@ const defaultUtteranceProps = {
|
|
|
85
102
|
}
|
|
86
103
|
|
|
87
104
|
const initialize = () => {
|
|
88
|
-
const syn =
|
|
105
|
+
const syn = getSpeechSynthesis()
|
|
89
106
|
// syn api check: syn might not have getVoices method
|
|
90
107
|
if (!syn || typeof syn.getVoices !== 'function') {
|
|
91
108
|
initialized = false
|
|
@@ -99,7 +116,7 @@ const initialize = () => {
|
|
|
99
116
|
|
|
100
117
|
const waitForSynthReady = (timeoutMs = 2000, checkIntervalMs = 100) => {
|
|
101
118
|
return new Promise((resolve) => {
|
|
102
|
-
const syn =
|
|
119
|
+
const syn = getSpeechSynthesis()
|
|
103
120
|
if (!syn) {
|
|
104
121
|
Log.debug('SpeechSynthesis - syn unavailable')
|
|
105
122
|
resolve()
|
|
@@ -137,7 +154,7 @@ const waitForSynthReady = (timeoutMs = 2000, checkIntervalMs = 100) => {
|
|
|
137
154
|
}
|
|
138
155
|
|
|
139
156
|
const speak = async (options) => {
|
|
140
|
-
const syn =
|
|
157
|
+
const syn = getSpeechSynthesis()
|
|
141
158
|
// options check: missing required options
|
|
142
159
|
if (!options || !options.message) {
|
|
143
160
|
return Promise.reject({ error: 'Missing message' })
|
|
@@ -157,7 +174,7 @@ const speak = async (options) => {
|
|
|
157
174
|
// Wait for engine to be ready
|
|
158
175
|
await waitForSynthReady()
|
|
159
176
|
|
|
160
|
-
const SpeechSynthesisUtterance =
|
|
177
|
+
const SpeechSynthesisUtterance = getSpeechSynthesisUtterance()
|
|
161
178
|
if (SpeechSynthesisUtterance === undefined) {
|
|
162
179
|
return Promise.reject({ error: 'unavailable' })
|
|
163
180
|
}
|
|
@@ -216,7 +233,7 @@ const speak = async (options) => {
|
|
|
216
233
|
|
|
217
234
|
export default {
|
|
218
235
|
speak(options) {
|
|
219
|
-
const syn =
|
|
236
|
+
const syn = getSpeechSynthesis()
|
|
220
237
|
if (syn !== undefined) {
|
|
221
238
|
if (initialized === false) {
|
|
222
239
|
initialize()
|
|
@@ -228,7 +245,7 @@ export default {
|
|
|
228
245
|
}
|
|
229
246
|
},
|
|
230
247
|
cancel() {
|
|
231
|
-
const syn =
|
|
248
|
+
const syn = getSpeechSynthesis()
|
|
232
249
|
if (syn !== undefined) {
|
|
233
250
|
// timers: clear all timers before cancelling
|
|
234
251
|
for (const id of utterances.keys()) {
|
package/src/component.js
CHANGED
|
@@ -38,6 +38,11 @@ import { plugins } from './plugin.js'
|
|
|
38
38
|
|
|
39
39
|
// object to store global components
|
|
40
40
|
let globalComponents
|
|
41
|
+
let devMode = false
|
|
42
|
+
|
|
43
|
+
export const setDevMode = (enabled) => {
|
|
44
|
+
devMode = enabled === true
|
|
45
|
+
}
|
|
41
46
|
|
|
42
47
|
const required = (name) => {
|
|
43
48
|
throw new Error(`Parameter ${name} is required`)
|
|
@@ -493,7 +498,7 @@ const Component = (name = required('name'), config = required('config')) => {
|
|
|
493
498
|
// one time code generation (only if precompilation is turned off)
|
|
494
499
|
if (config.code === undefined) {
|
|
495
500
|
Log.debug(`Generating code for ${name} component`)
|
|
496
|
-
config.code = codegenerator.call(config, parser(config.template, name))
|
|
501
|
+
config.code = codegenerator.call(config, parser(config.template, name), devMode)
|
|
497
502
|
}
|
|
498
503
|
|
|
499
504
|
// create an instance of the component, using base as the prototype (which contains Base)
|
|
@@ -202,6 +202,15 @@ const colorMap = {
|
|
|
202
202
|
right: 'colorRight',
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
+
const setTextureOption = (target, key, value) => {
|
|
206
|
+
const opts =
|
|
207
|
+
target.props['textureOptions'] ||
|
|
208
|
+
(target.element.node && target.element.node.textureOptions) ||
|
|
209
|
+
{}
|
|
210
|
+
opts[key] = value
|
|
211
|
+
target.props['textureOptions'] = opts
|
|
212
|
+
}
|
|
213
|
+
|
|
205
214
|
/**
|
|
206
215
|
* Default text settings for text nodes (initialized on first use).
|
|
207
216
|
* @type {object|null}
|
|
@@ -275,6 +284,9 @@ const propsTransformer = {
|
|
|
275
284
|
if (typeof v === 'object' || (isObjectString(v) === true && (v = parseToObject(v)))) {
|
|
276
285
|
this.props['src'] = v.src
|
|
277
286
|
this.props['imageType'] = v.type
|
|
287
|
+
if ('keepAlive' in v) {
|
|
288
|
+
setTextureOption(this, 'preventCleanup', v.keepAlive === true)
|
|
289
|
+
}
|
|
278
290
|
} else {
|
|
279
291
|
this.props['src'] = v
|
|
280
292
|
}
|
|
@@ -318,7 +330,7 @@ const propsTransformer = {
|
|
|
318
330
|
const resizeMode = {}
|
|
319
331
|
|
|
320
332
|
if (v === 'cover' || v === 'contain') {
|
|
321
|
-
this
|
|
333
|
+
setTextureOption(this, 'resizeMode', { type: v })
|
|
322
334
|
return
|
|
323
335
|
}
|
|
324
336
|
|
|
@@ -333,7 +345,7 @@ const propsTransformer = {
|
|
|
333
345
|
resizeMode['clipX'] = 'x' in v.position === true ? v.position.x : null
|
|
334
346
|
resizeMode['clipY'] = 'y' in v.position === true ? v.position.y : null
|
|
335
347
|
}
|
|
336
|
-
this
|
|
348
|
+
setTextureOption(this, 'resizeMode', resizeMode)
|
|
337
349
|
}
|
|
338
350
|
},
|
|
339
351
|
set rtt(v) {
|
package/src/platform.js
CHANGED
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
* SPDX-License-Identifier: Apache-2.0
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
+
import speechSynthesis from './announcer/speechSynthesis.js'
|
|
19
|
+
|
|
18
20
|
const globalScope = globalThis
|
|
19
21
|
|
|
20
22
|
const createKeyboardEventFactory = (KeyboardEventConstructor) => {
|
|
@@ -73,10 +75,7 @@ const browserPlatform = () => {
|
|
|
73
75
|
? (event) => event instanceof KeyboardEventConstructor
|
|
74
76
|
: undefined,
|
|
75
77
|
createKeyboardEvent,
|
|
76
|
-
|
|
77
|
-
globalScope.SpeechSynthesisUtterance || (windowRef && windowRef.SpeechSynthesisUtterance),
|
|
78
|
-
speechSynthesis:
|
|
79
|
-
(selfRef && selfRef.speechSynthesis) || (windowRef && windowRef.speechSynthesis),
|
|
78
|
+
announcer: speechSynthesis,
|
|
80
79
|
now:
|
|
81
80
|
globalScope.performance && globalScope.performance.now !== undefined
|
|
82
81
|
? globalScope.performance.now.bind(globalScope.performance)
|
|
@@ -108,9 +107,7 @@ export const configurePlatform = (customPlatform) => {
|
|
|
108
107
|
platform.getCookie = platform.getCookie || basePlatform.getCookie
|
|
109
108
|
platform.setCookie = platform.setCookie || basePlatform.setCookie
|
|
110
109
|
platform.historyBack = platform.historyBack || basePlatform.historyBack
|
|
111
|
-
platform.
|
|
112
|
-
platform.SpeechSynthesisUtterance || basePlatform.SpeechSynthesisUtterance
|
|
113
|
-
platform.speechSynthesis = platform.speechSynthesis || basePlatform.speechSynthesis
|
|
110
|
+
platform.announcer = platform.announcer || basePlatform.announcer
|
|
114
111
|
platform.now = platform.now || basePlatform.now
|
|
115
112
|
|
|
116
113
|
return platform
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { AnnouncerDriver } from '@lightningjs/blits'
|
|
2
|
+
|
|
3
|
+
export interface WebOSRequestOptions {
|
|
4
|
+
method: 'speak'
|
|
5
|
+
parameters: {
|
|
6
|
+
text: string
|
|
7
|
+
clear: boolean
|
|
8
|
+
}
|
|
9
|
+
onFailure(error: any): void
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type WebOSRequest = (uri: string, options: WebOSRequestOptions) => any
|
|
13
|
+
|
|
14
|
+
export interface WebOSAnnouncerOptions {
|
|
15
|
+
/**
|
|
16
|
+
* webOS service request function. Defaults to `webOS.service.request` when available.
|
|
17
|
+
*/
|
|
18
|
+
request?: WebOSRequest
|
|
19
|
+
/**
|
|
20
|
+
* webOS TTS service URI.
|
|
21
|
+
*
|
|
22
|
+
* @default 'luna://com.webos.service.tts'
|
|
23
|
+
*/
|
|
24
|
+
uri?: string
|
|
25
|
+
/**
|
|
26
|
+
* Whether the platform TTS queue should be cleared before speaking.
|
|
27
|
+
*
|
|
28
|
+
* @default true
|
|
29
|
+
*/
|
|
30
|
+
clear?: boolean
|
|
31
|
+
/**
|
|
32
|
+
* Optional failure callback for the webOS TTS request.
|
|
33
|
+
*/
|
|
34
|
+
onFailure?: (error: any) => void
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
declare function createAnnouncer(options?: WebOSAnnouncerOptions): AnnouncerDriver
|
|
38
|
+
|
|
39
|
+
export default createAnnouncer
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 Comcast Cable Communications Management, LLC
|
|
3
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License.
|
|
5
|
+
* You may obtain a copy of the License at
|
|
6
|
+
*
|
|
7
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
*
|
|
9
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
* See the License for the specific language governing permissions and
|
|
13
|
+
* limitations under the License.
|
|
14
|
+
*
|
|
15
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const globalScope = globalThis
|
|
19
|
+
const defaultUri = 'luna://com.webos.service.tts'
|
|
20
|
+
|
|
21
|
+
const getDefaultRequest = () => {
|
|
22
|
+
const windowRef = globalScope.window
|
|
23
|
+
const webOS = globalScope.webOS || (windowRef && windowRef.webOS)
|
|
24
|
+
|
|
25
|
+
if (webOS && webOS.service && typeof webOS.service.request === 'function') {
|
|
26
|
+
return webOS.service.request.bind(webOS.service)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const createAnnouncer = (options = {}) => {
|
|
31
|
+
const request = options.request || getDefaultRequest()
|
|
32
|
+
const uri = options.uri || defaultUri
|
|
33
|
+
const clear = options.clear !== false
|
|
34
|
+
|
|
35
|
+
let currentRequest
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
speak(announcement) {
|
|
39
|
+
if (request === undefined) {
|
|
40
|
+
return Promise.reject({ error: 'unavailable' })
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!announcement || announcement.message === undefined || announcement.message === null) {
|
|
44
|
+
return Promise.reject({ error: 'Missing message' })
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
currentRequest = request(uri, {
|
|
49
|
+
method: 'speak',
|
|
50
|
+
parameters: {
|
|
51
|
+
text: String(announcement.message),
|
|
52
|
+
clear,
|
|
53
|
+
},
|
|
54
|
+
onFailure(error) {
|
|
55
|
+
currentRequest = undefined
|
|
56
|
+
if (options.onFailure) {
|
|
57
|
+
options.onFailure(error)
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
})
|
|
61
|
+
} catch (error) {
|
|
62
|
+
currentRequest = undefined
|
|
63
|
+
return Promise.reject(error)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return Promise.resolve()
|
|
67
|
+
},
|
|
68
|
+
cancel() {
|
|
69
|
+
if (currentRequest && typeof currentRequest.cancel === 'function') {
|
|
70
|
+
currentRequest.cancel()
|
|
71
|
+
}
|
|
72
|
+
currentRequest = undefined
|
|
73
|
+
},
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export default createAnnouncer
|
package/src/testing/index.d.ts
CHANGED
|
@@ -43,6 +43,8 @@ export interface RenderComponentFixture {
|
|
|
43
43
|
component: any
|
|
44
44
|
root: any
|
|
45
45
|
snapshot(): SnapshotNode | ComponentSnapshotNode
|
|
46
|
+
findByData(key: string, value: any): SnapshotNode | ComponentSnapshotNode | null
|
|
47
|
+
findAllByData(key: string, value: any): Array<SnapshotNode | ComponentSnapshotNode>
|
|
46
48
|
setProps(props: Record<string, any>): void
|
|
47
49
|
setState(state: Record<string, any>): void
|
|
48
50
|
focus(event?: KeyboardEvent): Promise<any>
|
|
@@ -95,19 +95,11 @@ const nodePlatform = () => {
|
|
|
95
95
|
KeyboardEvent: TestKeyboardEvent,
|
|
96
96
|
isKeyboardEvent: (event) => event instanceof TestKeyboardEvent,
|
|
97
97
|
createKeyboardEvent: (type, init = {}) => new TestKeyboardEvent(type, init),
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
102
|
-
},
|
|
103
|
-
speechSynthesis: {
|
|
104
|
-
cancel() {},
|
|
105
|
-
getVoices() {
|
|
106
|
-
return []
|
|
98
|
+
announcer: {
|
|
99
|
+
speak() {
|
|
100
|
+
return Promise.resolve()
|
|
107
101
|
},
|
|
108
|
-
|
|
109
|
-
resume() {},
|
|
110
|
-
speak() {},
|
|
102
|
+
cancel() {},
|
|
111
103
|
},
|
|
112
104
|
now: Date.now,
|
|
113
105
|
}
|
|
@@ -17,12 +17,14 @@
|
|
|
17
17
|
|
|
18
18
|
import Settings from '../settings.js'
|
|
19
19
|
import { renderer, stage } from '../launch.js'
|
|
20
|
+
import { setDevMode } from '../component.js'
|
|
20
21
|
import symbols from '../lib/symbols.js'
|
|
21
22
|
import { initLog } from '../lib/log.js'
|
|
22
23
|
import { getRaw } from '../lib/reactivity/reactive.js'
|
|
23
24
|
import Focus from '../focus/focus.js'
|
|
24
25
|
import { initKeyMap, keyMap } from '../application.js'
|
|
25
26
|
import { configurePlatform, platform } from '../platform.js'
|
|
27
|
+
import { isObjectString, parseToObject } from '../lib/utils.js'
|
|
26
28
|
import nodePlatform from './nodePlatform.js'
|
|
27
29
|
|
|
28
30
|
let nodeId = 0
|
|
@@ -32,6 +34,7 @@ const renderComponent = (Component, options = {}) => {
|
|
|
32
34
|
const originalPlatform = platform
|
|
33
35
|
|
|
34
36
|
Settings.set(options.settings || {})
|
|
37
|
+
setDevMode(true)
|
|
35
38
|
configurePlatform(() => nodePlatform())
|
|
36
39
|
initLog()
|
|
37
40
|
initKeyMap()
|
|
@@ -58,6 +61,27 @@ const renderComponent = (Component, options = {}) => {
|
|
|
58
61
|
return componentSnapshot(component, holderMap)
|
|
59
62
|
}
|
|
60
63
|
|
|
64
|
+
const findAllByData = (key, value) => {
|
|
65
|
+
const matches = []
|
|
66
|
+
visitSnapshot(snapshot(), (node) => {
|
|
67
|
+
if (node.attributes && node.attributes.data && node.attributes.data[key] === value) {
|
|
68
|
+
matches.push(node)
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
return matches
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const findByData = (key, value) => {
|
|
75
|
+
let match = null
|
|
76
|
+
visitSnapshot(snapshot(), (node) => {
|
|
77
|
+
if (node.attributes && node.attributes.data && node.attributes.data[key] === value) {
|
|
78
|
+
match = node
|
|
79
|
+
return true
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
return match
|
|
83
|
+
}
|
|
84
|
+
|
|
61
85
|
const setProps = (props = {}) => {
|
|
62
86
|
const keys = Object.keys(props)
|
|
63
87
|
for (let i = 0; i < keys.length; i++) {
|
|
@@ -107,6 +131,8 @@ const renderComponent = (Component, options = {}) => {
|
|
|
107
131
|
component,
|
|
108
132
|
root,
|
|
109
133
|
snapshot,
|
|
134
|
+
findByData,
|
|
135
|
+
findAllByData,
|
|
110
136
|
setProps,
|
|
111
137
|
setState,
|
|
112
138
|
focus,
|
|
@@ -289,10 +315,21 @@ const createElement = ({ parent } = {}) => {
|
|
|
289
315
|
}
|
|
290
316
|
},
|
|
291
317
|
set(key, value) {
|
|
318
|
+
if (key === 'inspector-data') {
|
|
319
|
+
this.setInspectorMetadata(value)
|
|
320
|
+
return
|
|
321
|
+
}
|
|
292
322
|
this.attributes[key] = value
|
|
293
323
|
this.node[key] = value
|
|
294
324
|
},
|
|
295
|
-
setInspectorMetadata() {
|
|
325
|
+
setInspectorMetadata(data) {
|
|
326
|
+
if (isObjectString(data) === true) {
|
|
327
|
+
data = parseToObject(data)
|
|
328
|
+
}
|
|
329
|
+
if (data === null || typeof data !== 'object') return
|
|
330
|
+
this.attributes.data = { ...(this.attributes.data || {}), ...data }
|
|
331
|
+
this.node.data = { ...(this.node.data || {}), ...data }
|
|
332
|
+
},
|
|
296
333
|
destroy() {
|
|
297
334
|
this.eol = true
|
|
298
335
|
this.children.length = 0
|
|
@@ -314,6 +351,19 @@ const appendToParent = (element, parent) => {
|
|
|
314
351
|
}
|
|
315
352
|
}
|
|
316
353
|
|
|
354
|
+
const visitSnapshot = (node, visit) => {
|
|
355
|
+
if (node === undefined || node === null) return false
|
|
356
|
+
if (visit(node) === true) return true
|
|
357
|
+
if (node.type === 'Component') {
|
|
358
|
+
return visitSnapshot(node.tree, visit)
|
|
359
|
+
}
|
|
360
|
+
const children = node.children || []
|
|
361
|
+
for (let i = 0; i < children.length; i++) {
|
|
362
|
+
if (visitSnapshot(children[i], visit) === true) return true
|
|
363
|
+
}
|
|
364
|
+
return false
|
|
365
|
+
}
|
|
366
|
+
|
|
317
367
|
const createKeyboardEvent = (key, init = {}) => {
|
|
318
368
|
const keyCode = init.keyCode || keyCodeFromKey(key)
|
|
319
369
|
return platform.createKeyboardEvent('keydown', {
|
package/vite/msdfGenerator.js
CHANGED
|
@@ -145,12 +145,20 @@ export default function () {
|
|
|
145
145
|
|
|
146
146
|
if (fs.existsSync(generatedFontFile)) {
|
|
147
147
|
const fileContent = fs.readFileSync(generatedFontFile)
|
|
148
|
+
const etag = `"${generateHash(generatedFontFile)}"`
|
|
148
149
|
|
|
149
150
|
// Check if headers have already been sent
|
|
150
151
|
if (!res.headersSent) {
|
|
151
152
|
res.setHeader('Content-Type', mimeType)
|
|
152
|
-
res.setHeader('Cache-Control', '
|
|
153
|
-
res.
|
|
153
|
+
res.setHeader('Cache-Control', 'no-cache')
|
|
154
|
+
res.setHeader('ETag', etag)
|
|
155
|
+
|
|
156
|
+
if (req.headers['if-none-match'] === etag) {
|
|
157
|
+
res.statusCode = 304
|
|
158
|
+
res.end()
|
|
159
|
+
} else {
|
|
160
|
+
res.end(fileContent)
|
|
161
|
+
}
|
|
154
162
|
} else {
|
|
155
163
|
// this should never happen except some edge cases
|
|
156
164
|
console.error(`ERROR: Headers already sent for ${req.url}`, res.getHeaders())
|
package/test-output.txt
DELETED
|
Binary file
|