@lightningjs/blits 2.5.0 → 2.7.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 +17 -0
- package/index.d.ts +98 -3
- package/package.json +9 -1
- package/src/announcer/announcer.js +31 -7
- package/src/announcer/speechSynthesis.js +29 -2
- package/src/application.js +40 -20
- package/src/component/base/methods.js +6 -1
- package/src/component.js +6 -1
- package/src/components/RouterView.js +10 -2
- package/src/engines/L3/element.js +14 -2
- package/src/engines/L3/launch.js +9 -5
- package/src/focus/focus.js +7 -4
- package/src/launch.js +23 -0
- package/src/lib/reactivityguard/computedprops.js +6 -2
- package/src/platform.js +114 -0
- package/src/platforms/webOS/announcer.d.ts +39 -0
- package/src/platforms/webOS/announcer.js +77 -0
- package/src/plugins/storage/localCookie.js +15 -12
- package/src/router/router.js +4 -2
- package/src/testing/index.d.ts +60 -0
- package/src/testing/index.js +20 -0
- package/src/testing/nodePlatform.js +108 -0
- package/src/testing/renderComponent.js +396 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.7.0
|
|
4
|
+
|
|
5
|
+
_2 jul 2026_
|
|
6
|
+
|
|
7
|
+
- Added `keepAlive` option to `src`-object to prevent a texture from being cleaned up by the renderer
|
|
8
|
+
- Abstracted announcer (and speechSynthesis) to be platform provided (with speechSynthesis as the built-in default)
|
|
9
|
+
- Added dedicated findByData methods to testing harness to select specific nodes in the tree
|
|
10
|
+
- Improved test cases around frameTick cleanup
|
|
11
|
+
|
|
12
|
+
## 2.6.0
|
|
13
|
+
|
|
14
|
+
_25 jun 2026_
|
|
15
|
+
|
|
16
|
+
- Added platform layer for browser API abstraction (passed as a launch option)
|
|
17
|
+
- Improved reactivity guard vite plugin
|
|
18
|
+
- Added testing harness to support Blits component testing
|
|
19
|
+
|
|
3
20
|
## 2.5.0
|
|
4
21
|
|
|
5
22
|
_11 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,
|
|
@@ -1016,6 +1038,70 @@ declare module '@lightningjs/blits' {
|
|
|
1016
1038
|
type ReactivityModes = 'Proxy' | 'defineProperty'
|
|
1017
1039
|
type RenderModes = 'webgl' | 'canvas'
|
|
1018
1040
|
|
|
1041
|
+
interface Platform {
|
|
1042
|
+
/**
|
|
1043
|
+
* Target used for keyboard and pointer input listeners.
|
|
1044
|
+
*/
|
|
1045
|
+
input?: EventTarget | any,
|
|
1046
|
+
/**
|
|
1047
|
+
* Target used for viewport-level listeners such as resize, scroll, and hashchange.
|
|
1048
|
+
*/
|
|
1049
|
+
viewport?: EventTarget | any,
|
|
1050
|
+
/**
|
|
1051
|
+
* Dispatch an event through the platform's event system.
|
|
1052
|
+
*/
|
|
1053
|
+
dispatchEvent?: (event: any) => boolean | void,
|
|
1054
|
+
/**
|
|
1055
|
+
* Persistent key/value storage API.
|
|
1056
|
+
*/
|
|
1057
|
+
localStorage?: Storage | any,
|
|
1058
|
+
/**
|
|
1059
|
+
* Read the platform cookie string.
|
|
1060
|
+
*/
|
|
1061
|
+
getCookie?: () => string,
|
|
1062
|
+
/**
|
|
1063
|
+
* Write a cookie string.
|
|
1064
|
+
*/
|
|
1065
|
+
setCookie?: (value: string) => void,
|
|
1066
|
+
/**
|
|
1067
|
+
* Navigate back in the platform history, when available.
|
|
1068
|
+
*/
|
|
1069
|
+
historyBack?: () => void,
|
|
1070
|
+
/**
|
|
1071
|
+
* Current viewport/screen height.
|
|
1072
|
+
*/
|
|
1073
|
+
screenHeight?: number,
|
|
1074
|
+
/**
|
|
1075
|
+
* Available hardware concurrency.
|
|
1076
|
+
*/
|
|
1077
|
+
hardwareConcurrency?: number,
|
|
1078
|
+
/**
|
|
1079
|
+
* User agent string for platform-specific defaults.
|
|
1080
|
+
*/
|
|
1081
|
+
userAgent?: string,
|
|
1082
|
+
/**
|
|
1083
|
+
* KeyboardEvent constructor used for event creation and type checks.
|
|
1084
|
+
*/
|
|
1085
|
+
KeyboardEvent?: typeof KeyboardEvent | any,
|
|
1086
|
+
/**
|
|
1087
|
+
* Check whether an object should be treated as a keyboard event.
|
|
1088
|
+
*/
|
|
1089
|
+
isKeyboardEvent?: (event: any) => boolean,
|
|
1090
|
+
/**
|
|
1091
|
+
* Create a keyboard event for the current platform.
|
|
1092
|
+
*/
|
|
1093
|
+
createKeyboardEvent?: (type: string, init?: KeyboardEventInit | any) => any,
|
|
1094
|
+
/**
|
|
1095
|
+
* Platform-specific announcer driver used instead of the default Web Speech driver.
|
|
1096
|
+
*/
|
|
1097
|
+
announcer?: AnnouncerDriver,
|
|
1098
|
+
/**
|
|
1099
|
+
* Monotonic timestamp provider used for input throttling.
|
|
1100
|
+
*/
|
|
1101
|
+
now?: () => number,
|
|
1102
|
+
[key: string]: any,
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1019
1105
|
/**
|
|
1020
1106
|
* Settings
|
|
1021
1107
|
*
|
|
@@ -1331,11 +1417,20 @@ declare module '@lightningjs/blits' {
|
|
|
1331
1417
|
*/
|
|
1332
1418
|
maxFPS?: number,
|
|
1333
1419
|
/**
|
|
1334
|
-
* Custom platform layer to be used by the App
|
|
1420
|
+
* Custom platform layer to be used by the App.
|
|
1421
|
+
*
|
|
1422
|
+
* Allows replacing platform capabilities such as input targets, storage,
|
|
1423
|
+
* keyboard event constructors, and speech APIs.
|
|
1424
|
+
* A callback receives Blits' browser defaults and should return the platform overrides.
|
|
1425
|
+
*/
|
|
1426
|
+
platform?: (defaults: Platform) => Partial<Platform>,
|
|
1427
|
+
/**
|
|
1428
|
+
* Custom platform layer to be used by the renderer.
|
|
1335
1429
|
*
|
|
1336
|
-
*
|
|
1430
|
+
* For renderer-specific platform implementations. Passing a renderer platform
|
|
1431
|
+
* through `platform` is deprecated.
|
|
1337
1432
|
*/
|
|
1338
|
-
|
|
1433
|
+
rendererPlatform?: RendererMainSettings['platform']
|
|
1339
1434
|
|
|
1340
1435
|
}
|
|
1341
1436
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lightningjs/blits",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.0",
|
|
4
4
|
"description": "Blits: The Lightning 3 App Development Framework",
|
|
5
5
|
"bin": "bin/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -32,6 +32,14 @@
|
|
|
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
|
+
},
|
|
39
|
+
"./testing": {
|
|
40
|
+
"types": "./src/testing/index.d.ts",
|
|
41
|
+
"default": "./src/testing/index.js"
|
|
42
|
+
},
|
|
35
43
|
"./symbols": "./src/lib/symbols.js",
|
|
36
44
|
"./blitsFileConverter": "./src/lib/blitsfileconverter/blitsfileconverter.js",
|
|
37
45
|
"./components": "./src/components/FPScounter.js"
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import { Log } from '../lib/log.js'
|
|
19
|
-
import
|
|
19
|
+
import { platform } from '../platform.js'
|
|
20
20
|
|
|
21
21
|
let active = false
|
|
22
22
|
let count = 0
|
|
@@ -27,7 +27,21 @@ let debounce = null
|
|
|
27
27
|
|
|
28
28
|
// Global default utterance options
|
|
29
29
|
let globalDefaultOptions = {
|
|
30
|
-
enableUtteranceKeepAlive: !/android/i.test(
|
|
30
|
+
enableUtteranceKeepAlive: !/android/i.test(platform.userAgent || ''),
|
|
31
|
+
}
|
|
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
|
+
}
|
|
31
45
|
}
|
|
32
46
|
|
|
33
47
|
const noopAnnouncement = {
|
|
@@ -83,9 +97,9 @@ const addToQueue = (message, politeness, delay = false, options = {}) => {
|
|
|
83
97
|
// augment the promise with a stop function
|
|
84
98
|
done.stop = () => {
|
|
85
99
|
if (id === currentId) {
|
|
86
|
-
|
|
100
|
+
cancelDriver()
|
|
87
101
|
isProcessing = false
|
|
88
|
-
resolveFn('
|
|
102
|
+
resolveFn('interrupted')
|
|
89
103
|
}
|
|
90
104
|
}
|
|
91
105
|
|
|
@@ -132,7 +146,17 @@ const processQueue = async () => {
|
|
|
132
146
|
debounce = setTimeout(() => {
|
|
133
147
|
Log.debug(`Announcer - speaking: "${message}" (id: ${id})`)
|
|
134
148
|
|
|
135
|
-
|
|
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
|
|
136
160
|
.speak({
|
|
137
161
|
message,
|
|
138
162
|
id,
|
|
@@ -179,8 +203,8 @@ const stop = () => {
|
|
|
179
203
|
// Clear debounce timer if speech hasn't started yet
|
|
180
204
|
clearDebounceTimer()
|
|
181
205
|
|
|
182
|
-
// Always cancel
|
|
183
|
-
|
|
206
|
+
// Always cancel the platform announcer to ensure clean state
|
|
207
|
+
cancelDriver()
|
|
184
208
|
|
|
185
209
|
// Store resolve function before resetting state
|
|
186
210
|
const resolveFn = currentResolveFn
|
|
@@ -17,11 +17,27 @@
|
|
|
17
17
|
|
|
18
18
|
import { Log } from '../lib/log.js'
|
|
19
19
|
|
|
20
|
-
const syn = self.speechSynthesis
|
|
21
|
-
|
|
22
20
|
const utterances = new Map() // id -> { utterance, timer, ignoreResume }
|
|
23
21
|
|
|
24
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
|
+
}
|
|
25
41
|
|
|
26
42
|
const clear = (id) => {
|
|
27
43
|
const state = utterances.get(id)
|
|
@@ -36,6 +52,7 @@ const clear = (id) => {
|
|
|
36
52
|
}
|
|
37
53
|
|
|
38
54
|
const startKeepAlive = (id) => {
|
|
55
|
+
const syn = getSpeechSynthesis()
|
|
39
56
|
const state = utterances.get(id)
|
|
40
57
|
|
|
41
58
|
// utterance status: utterance was removed (cancelled or finished)
|
|
@@ -85,6 +102,7 @@ const defaultUtteranceProps = {
|
|
|
85
102
|
}
|
|
86
103
|
|
|
87
104
|
const initialize = () => {
|
|
105
|
+
const syn = getSpeechSynthesis()
|
|
88
106
|
// syn api check: syn might not have getVoices method
|
|
89
107
|
if (!syn || typeof syn.getVoices !== 'function') {
|
|
90
108
|
initialized = false
|
|
@@ -98,6 +116,7 @@ const initialize = () => {
|
|
|
98
116
|
|
|
99
117
|
const waitForSynthReady = (timeoutMs = 2000, checkIntervalMs = 100) => {
|
|
100
118
|
return new Promise((resolve) => {
|
|
119
|
+
const syn = getSpeechSynthesis()
|
|
101
120
|
if (!syn) {
|
|
102
121
|
Log.debug('SpeechSynthesis - syn unavailable')
|
|
103
122
|
resolve()
|
|
@@ -135,6 +154,7 @@ const waitForSynthReady = (timeoutMs = 2000, checkIntervalMs = 100) => {
|
|
|
135
154
|
}
|
|
136
155
|
|
|
137
156
|
const speak = async (options) => {
|
|
157
|
+
const syn = getSpeechSynthesis()
|
|
138
158
|
// options check: missing required options
|
|
139
159
|
if (!options || !options.message) {
|
|
140
160
|
return Promise.reject({ error: 'Missing message' })
|
|
@@ -154,6 +174,11 @@ const speak = async (options) => {
|
|
|
154
174
|
// Wait for engine to be ready
|
|
155
175
|
await waitForSynthReady()
|
|
156
176
|
|
|
177
|
+
const SpeechSynthesisUtterance = getSpeechSynthesisUtterance()
|
|
178
|
+
if (SpeechSynthesisUtterance === undefined) {
|
|
179
|
+
return Promise.reject({ error: 'unavailable' })
|
|
180
|
+
}
|
|
181
|
+
|
|
157
182
|
const utterance = new SpeechSynthesisUtterance(options.message)
|
|
158
183
|
utterance.lang = options.lang || defaultUtteranceProps.lang
|
|
159
184
|
utterance.pitch = options.pitch || defaultUtteranceProps.pitch
|
|
@@ -208,6 +233,7 @@ const speak = async (options) => {
|
|
|
208
233
|
|
|
209
234
|
export default {
|
|
210
235
|
speak(options) {
|
|
236
|
+
const syn = getSpeechSynthesis()
|
|
211
237
|
if (syn !== undefined) {
|
|
212
238
|
if (initialized === false) {
|
|
213
239
|
initialize()
|
|
@@ -219,6 +245,7 @@ export default {
|
|
|
219
245
|
}
|
|
220
246
|
},
|
|
221
247
|
cancel() {
|
|
248
|
+
const syn = getSpeechSynthesis()
|
|
222
249
|
if (syn !== undefined) {
|
|
223
250
|
// timers: clear all timers before cancelling
|
|
224
251
|
for (const id of utterances.keys()) {
|
package/src/application.js
CHANGED
|
@@ -23,6 +23,7 @@ import Settings from './settings.js'
|
|
|
23
23
|
import symbols from './lib/symbols.js'
|
|
24
24
|
import { DEFAULT_HOLD_TIMEOUT_MS, DEFAULT_KEYMAP } from './constants.js'
|
|
25
25
|
import { renderer } from './launch.js'
|
|
26
|
+
import { platform } from './platform.js'
|
|
26
27
|
|
|
27
28
|
/**
|
|
28
29
|
* Merged keyMap (default + custom settings).
|
|
@@ -31,6 +32,10 @@ import { renderer } from './launch.js'
|
|
|
31
32
|
*/
|
|
32
33
|
export let keyMap = {}
|
|
33
34
|
|
|
35
|
+
export const initKeyMap = () => {
|
|
36
|
+
keyMap = { ...DEFAULT_KEYMAP, ...Settings.get('keymap', {}) }
|
|
37
|
+
}
|
|
38
|
+
|
|
34
39
|
const Application = (config) => {
|
|
35
40
|
config.hooks = config.hooks || {}
|
|
36
41
|
|
|
@@ -39,22 +44,23 @@ const Application = (config) => {
|
|
|
39
44
|
let mouseMoveHandler
|
|
40
45
|
let mouseClickHandler
|
|
41
46
|
let updateCanvasRect
|
|
47
|
+
let inputCleanup
|
|
48
|
+
let mouseCleanup
|
|
42
49
|
let holdTimeout
|
|
43
50
|
let lastInputTime = 0
|
|
44
51
|
let lastInputKey = null
|
|
45
|
-
let mouseListenersAdded = false
|
|
46
52
|
|
|
47
53
|
config.hooks[symbols.destroy] = function () {
|
|
48
54
|
// Cancel pending key-hold timeout and reset hold state so focus is not left in hold mode after teardown
|
|
49
55
|
clearTimeout(holdTimeout)
|
|
50
56
|
Focus.hold = false
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
57
|
+
if (typeof inputCleanup === 'function') {
|
|
58
|
+
inputCleanup()
|
|
59
|
+
inputCleanup = undefined
|
|
60
|
+
}
|
|
61
|
+
if (typeof mouseCleanup === 'function') {
|
|
62
|
+
mouseCleanup()
|
|
63
|
+
mouseCleanup = undefined
|
|
58
64
|
Hover.clear()
|
|
59
65
|
}
|
|
60
66
|
}
|
|
@@ -68,15 +74,16 @@ const Application = (config) => {
|
|
|
68
74
|
this.$announcer.configure(announcerOptions)
|
|
69
75
|
}
|
|
70
76
|
// Initialize merged keyMap once during application init
|
|
71
|
-
|
|
77
|
+
initKeyMap()
|
|
72
78
|
|
|
73
79
|
/** @type {number} Input throttle time in milliseconds (0 = disabled) */
|
|
74
80
|
const throttleMs = Settings.get('inputThrottle', 0)
|
|
75
81
|
|
|
76
82
|
const mouseEnabled = Settings.get('enableMouse', false)
|
|
83
|
+
const { createKeyboardEvent, input, isKeyboardEvent, now = Date.now, viewport } = platform
|
|
77
84
|
|
|
78
85
|
keyDownHandler = async (e) => {
|
|
79
|
-
const currentTime =
|
|
86
|
+
const currentTime = now()
|
|
80
87
|
|
|
81
88
|
const key = keyMap[e.keyCode] || e.keyCode
|
|
82
89
|
const sameKey = lastInputKey === key
|
|
@@ -103,7 +110,7 @@ const Application = (config) => {
|
|
|
103
110
|
) {
|
|
104
111
|
e = await this[symbols.inputEvents].intercept.call(this, e)
|
|
105
112
|
// only pass on the key press to focused component when keyboard event is returned
|
|
106
|
-
if (
|
|
113
|
+
if (isKeyboardEvent && isKeyboardEvent(e) === false) return
|
|
107
114
|
}
|
|
108
115
|
|
|
109
116
|
Focus.input(key, e)
|
|
@@ -183,7 +190,9 @@ const Application = (config) => {
|
|
|
183
190
|
mouseClickHandler = () => {
|
|
184
191
|
if (currentComponent === undefined || currentComponent.eol === true) return
|
|
185
192
|
|
|
186
|
-
|
|
193
|
+
if (createKeyboardEvent === undefined) return
|
|
194
|
+
|
|
195
|
+
const e = createKeyboardEvent('keydown', {
|
|
187
196
|
key: 'Enter',
|
|
188
197
|
code: 'Enter',
|
|
189
198
|
keyCode: 13,
|
|
@@ -195,15 +204,26 @@ const Application = (config) => {
|
|
|
195
204
|
currentComponent.$input(e)
|
|
196
205
|
}
|
|
197
206
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
207
|
+
if (input !== undefined) {
|
|
208
|
+
input.addEventListener('keydown', keyDownHandler)
|
|
209
|
+
input.addEventListener('keyup', keyUpHandler)
|
|
210
|
+
inputCleanup = () => {
|
|
211
|
+
input.removeEventListener('keydown', keyDownHandler)
|
|
212
|
+
input.removeEventListener('keyup', keyUpHandler)
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (mouseEnabled === true && input !== undefined && viewport !== undefined) {
|
|
201
216
|
updateCanvasRect()
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
217
|
+
input.addEventListener('mousemove', mouseMoveHandler)
|
|
218
|
+
input.addEventListener('click', mouseClickHandler)
|
|
219
|
+
viewport.addEventListener('resize', updateCanvasRect)
|
|
220
|
+
viewport.addEventListener('scroll', updateCanvasRect)
|
|
221
|
+
mouseCleanup = () => {
|
|
222
|
+
input.removeEventListener('mousemove', mouseMoveHandler)
|
|
223
|
+
input.removeEventListener('click', mouseClickHandler)
|
|
224
|
+
viewport.removeEventListener('resize', updateCanvasRect)
|
|
225
|
+
viewport.removeEventListener('scroll', updateCanvasRect)
|
|
226
|
+
}
|
|
207
227
|
}
|
|
208
228
|
|
|
209
229
|
// next tick
|
|
@@ -23,6 +23,7 @@ import { Log } from '../../lib/log.js'
|
|
|
23
23
|
import { removeGlobalEffects } from '../../lib/reactivity/effect.js'
|
|
24
24
|
import { renderer } from '../../launch.js'
|
|
25
25
|
import { keyMap } from '../../application.js'
|
|
26
|
+
import { platform } from '../../platform.js'
|
|
26
27
|
|
|
27
28
|
export default {
|
|
28
29
|
$focus: {
|
|
@@ -48,7 +49,11 @@ export default {
|
|
|
48
49
|
* @returns {boolean} - Returns true if this component or a parent component handled the event, false otherwise
|
|
49
50
|
*/
|
|
50
51
|
value: function (event) {
|
|
51
|
-
if (
|
|
52
|
+
if (
|
|
53
|
+
event === null ||
|
|
54
|
+
event === undefined ||
|
|
55
|
+
(platform.isKeyboardEvent && platform.isKeyboardEvent(event) === false)
|
|
56
|
+
)
|
|
52
57
|
return false
|
|
53
58
|
|
|
54
59
|
const key = keyMap[event.keyCode] || event.keyCode
|
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)
|
|
@@ -23,6 +23,7 @@ import Router, {
|
|
|
23
23
|
} from '../router/router.js'
|
|
24
24
|
import symbols from '../lib/symbols.js'
|
|
25
25
|
import Focus from '../focus/focus.js'
|
|
26
|
+
import { platform } from '../platform.js'
|
|
26
27
|
|
|
27
28
|
/** @typedef {{ $input?: (event: any) => boolean, $focus?: (event: any) => void }} RouterViewParent */
|
|
28
29
|
|
|
@@ -159,11 +160,18 @@ export default () =>
|
|
|
159
160
|
Router.navigate.apply(this)
|
|
160
161
|
}
|
|
161
162
|
|
|
162
|
-
|
|
163
|
+
const viewport = platform.viewport
|
|
164
|
+
if (viewport !== undefined) {
|
|
165
|
+
this.hashchangeViewport = viewport
|
|
166
|
+
viewport.addEventListener('hashchange', this.hashchangeHandler)
|
|
167
|
+
}
|
|
163
168
|
},
|
|
164
169
|
destroy() {
|
|
165
170
|
unregisterRouterView(this)
|
|
166
|
-
|
|
171
|
+
if (this.hashchangeViewport !== undefined) {
|
|
172
|
+
this.hashchangeViewport.removeEventListener('hashchange', this.hashchangeHandler, false)
|
|
173
|
+
this.hashchangeViewport = undefined
|
|
174
|
+
}
|
|
167
175
|
},
|
|
168
176
|
focus() {
|
|
169
177
|
if (this.activeView && Focus.get() === this) {
|
|
@@ -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/engines/L3/launch.js
CHANGED
|
@@ -25,6 +25,7 @@ import { SCREEN_RESOLUTIONS, RENDER_QUALITIES } from '../../constants.js'
|
|
|
25
25
|
import colors from '../../lib/colors/colors.js'
|
|
26
26
|
import fontLoader from './fontLoader.js'
|
|
27
27
|
import shaderLoader from './shaderLoader.js'
|
|
28
|
+
import { platform } from '../../platform.js'
|
|
28
29
|
|
|
29
30
|
/** @type {RendererMain|{}} */
|
|
30
31
|
export let renderer = {}
|
|
@@ -73,6 +74,11 @@ const textureMemorySettings = (settings) => {
|
|
|
73
74
|
*
|
|
74
75
|
*/
|
|
75
76
|
export default (App, target, settings = {}) => {
|
|
77
|
+
const screenHeight = settings.platform ? settings.platform.screenHeight : platform.screenHeight
|
|
78
|
+
const hardwareConcurrency = settings.platform
|
|
79
|
+
? settings.platform.hardwareConcurrency
|
|
80
|
+
: platform.hardwareConcurrency
|
|
81
|
+
|
|
76
82
|
renderer = new RendererMain(
|
|
77
83
|
{
|
|
78
84
|
...{
|
|
@@ -84,12 +90,10 @@ export default (App, target, settings = {}) => {
|
|
|
84
90
|
deviceLogicalPixelRatio:
|
|
85
91
|
settings.pixelRatio ||
|
|
86
92
|
SCREEN_RESOLUTIONS[settings.screenResolution] ||
|
|
87
|
-
SCREEN_RESOLUTIONS[
|
|
93
|
+
SCREEN_RESOLUTIONS[screenHeight] ||
|
|
88
94
|
1,
|
|
89
95
|
numImageWorkers:
|
|
90
|
-
'webWorkersLimit' in settings
|
|
91
|
-
? settings.webWorkersLimit
|
|
92
|
-
: window.navigator.hardwareConcurrency || 2,
|
|
96
|
+
'webWorkersLimit' in settings ? settings.webWorkersLimit : hardwareConcurrency || 2,
|
|
93
97
|
clearColor: (settings.canvasColor && colors.normalize(settings.canvasColor)) || 0x00000000,
|
|
94
98
|
inspector: settings.inspector === true ? Inspector : undefined,
|
|
95
99
|
boundsMargin: settings.viewportMargin || 0,
|
|
@@ -100,7 +104,7 @@ export default (App, target, settings = {}) => {
|
|
|
100
104
|
textureMemory: textureMemorySettings(settings),
|
|
101
105
|
createImageBitmapSupport: 'auto',
|
|
102
106
|
targetFPS: 'maxFPS' in settings ? settings.maxFPS : 0,
|
|
103
|
-
platform:
|
|
107
|
+
platform: settings.rendererPlatform || null,
|
|
104
108
|
},
|
|
105
109
|
...(settings.advanced || {}),
|
|
106
110
|
},
|
package/src/focus/focus.js
CHANGED
|
@@ -21,7 +21,7 @@ import Settings from '../settings.js'
|
|
|
21
21
|
import { DEFAULT_HOLD_TIMEOUT_MS } from '../constants.js'
|
|
22
22
|
import { Log } from '../lib/log.js'
|
|
23
23
|
import { getAncestors } from './helpers.js'
|
|
24
|
-
import
|
|
24
|
+
import { platform } from '../platform.js'
|
|
25
25
|
|
|
26
26
|
let focusedComponent = null
|
|
27
27
|
let focusChain = []
|
|
@@ -136,8 +136,11 @@ const setFocus = (component, event) => {
|
|
|
136
136
|
focusedComponent = component
|
|
137
137
|
component[symbols.lifecycle].state = 'focus'
|
|
138
138
|
|
|
139
|
-
if (event
|
|
140
|
-
const internalEvent =
|
|
141
|
-
|
|
139
|
+
if (platform.isKeyboardEvent && platform.isKeyboardEvent(event) && platform.dispatchEvent) {
|
|
140
|
+
const internalEvent = platform.createKeyboardEvent
|
|
141
|
+
? platform.createKeyboardEvent('keydown', event)
|
|
142
|
+
: event
|
|
143
|
+
internalEvent[symbols.internalEvent] = true
|
|
144
|
+
platform.dispatchEvent(internalEvent)
|
|
142
145
|
}
|
|
143
146
|
}
|