@lightningjs/blits 1.42.1 → 1.43.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 +14 -0
- package/eslint.config.cjs +52 -48
- package/index.d.ts +106 -3
- package/package.json +3 -3
- package/scripts/lint.js +50 -0
- package/scripts/prepush.js +7 -4
- package/src/announcer/announcer.js +22 -9
- package/src/application.js +5 -0
- package/src/component/base/events.js +14 -0
- package/src/component.js +25 -12
- package/src/components/Sprite.js +50 -16
- package/src/launch.js +1 -1
- package/src/router/router.js +55 -8
- package/vite/index.js +9 -1
- package/vite/inject-dev-config.js +14 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v1.43.0
|
|
4
|
+
|
|
5
|
+
_12 Nov 2025_
|
|
6
|
+
|
|
7
|
+
- Exposed different renderer events as hooks (`idle`, `fpsUpdate` and `frameTick`)
|
|
8
|
+
- Added `$clearListeners` utility helper
|
|
9
|
+
- Added `$clearListeners` and `$unlisten` to type definitions
|
|
10
|
+
- Fixed issue with __DEV__ flag being set in vite config
|
|
11
|
+
- Fixed `lint` and `lint:fix` scripts to work cross platform (added Windows support)
|
|
12
|
+
- Added support for utterance options (`rate`, `pitch`, `lang`, `voice` and `volume`) to Announcer plugin
|
|
13
|
+
- Added reactivity to `src`-attribute of the Sprite
|
|
14
|
+
- Updates deprecated `assert` keyword for new `with` keyword
|
|
15
|
+
|
|
16
|
+
|
|
3
17
|
## v1.42.1
|
|
4
18
|
|
|
5
19
|
_28 Oct 2025_
|
package/eslint.config.cjs
CHANGED
|
@@ -15,71 +15,75 @@
|
|
|
15
15
|
* SPDX-License-Identifier: Apache-2.0
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
const {
|
|
19
|
-
defineConfig,
|
|
20
|
-
globalIgnores,
|
|
21
|
-
} = require("eslint/config");
|
|
18
|
+
const { defineConfig, globalIgnores } = require('eslint/config')
|
|
22
19
|
|
|
23
|
-
const globals = require(
|
|
24
|
-
const prettier = require(
|
|
25
|
-
const babelParser = require(
|
|
26
|
-
const js = require(
|
|
20
|
+
const globals = require('globals')
|
|
21
|
+
const prettier = require('eslint-plugin-prettier')
|
|
22
|
+
const babelParser = require('@babel/eslint-parser')
|
|
23
|
+
const js = require('@eslint/js')
|
|
27
24
|
|
|
28
|
-
const {
|
|
29
|
-
FlatCompat,
|
|
30
|
-
} = require("@eslint/eslintrc");
|
|
25
|
+
const { FlatCompat } = require('@eslint/eslintrc')
|
|
31
26
|
|
|
32
27
|
const compat = new FlatCompat({
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
})
|
|
28
|
+
baseDirectory: __dirname,
|
|
29
|
+
recommendedConfig: js.configs.recommended,
|
|
30
|
+
allConfig: js.configs.all,
|
|
31
|
+
})
|
|
37
32
|
|
|
38
|
-
module.exports = defineConfig([
|
|
33
|
+
module.exports = defineConfig([
|
|
34
|
+
{
|
|
39
35
|
languageOptions: {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
36
|
+
globals: {
|
|
37
|
+
...globals.browser,
|
|
38
|
+
...globals.node,
|
|
39
|
+
globalThis: false,
|
|
40
|
+
},
|
|
45
41
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
42
|
+
parser: babelParser,
|
|
43
|
+
ecmaVersion: 2018,
|
|
44
|
+
sourceType: 'module',
|
|
49
45
|
|
|
50
|
-
|
|
51
|
-
|
|
46
|
+
parserOptions: {
|
|
47
|
+
requireConfigFile: false,
|
|
52
48
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
},
|
|
49
|
+
babelOptions: {
|
|
50
|
+
plugins: ['@babel/plugin-syntax-import-assertions'],
|
|
56
51
|
},
|
|
52
|
+
},
|
|
57
53
|
},
|
|
58
54
|
|
|
59
55
|
plugins: {
|
|
60
|
-
|
|
56
|
+
prettier,
|
|
61
57
|
},
|
|
62
58
|
|
|
63
|
-
extends: compat.extends(
|
|
59
|
+
extends: compat.extends('eslint:recommended', 'plugin:prettier/recommended', 'prettier'),
|
|
64
60
|
|
|
65
61
|
rules: {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
62
|
+
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
|
63
|
+
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
|
64
|
+
quotes: [2, 'single', 'avoid-escape'],
|
|
65
|
+
semi: [2, 'never'],
|
|
66
|
+
'no-extra-boolean-cast': 'off',
|
|
71
67
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
68
|
+
'no-unused-vars': [
|
|
69
|
+
1,
|
|
70
|
+
{
|
|
71
|
+
argsIgnorePattern: 'res|next|^err',
|
|
72
|
+
},
|
|
73
|
+
],
|
|
75
74
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
75
|
+
'prettier/prettier': [
|
|
76
|
+
'error',
|
|
77
|
+
{
|
|
78
|
+
trailingComma: 'es5',
|
|
79
|
+
singleQuote: true,
|
|
80
|
+
tabWidth: 2,
|
|
81
|
+
semi: false,
|
|
82
|
+
printWidth: 100,
|
|
83
|
+
endOfLine: 'auto',
|
|
84
|
+
},
|
|
85
|
+
],
|
|
84
86
|
},
|
|
85
|
-
},
|
|
87
|
+
},
|
|
88
|
+
globalIgnores(['**/node_modules']),
|
|
89
|
+
])
|
package/index.d.ts
CHANGED
|
@@ -23,7 +23,40 @@ import {type ShaderEffect as RendererShaderEffect, type WebGlCoreShader, type Re
|
|
|
23
23
|
declare module '@lightningjs/blits' {
|
|
24
24
|
|
|
25
25
|
|
|
26
|
-
export interface
|
|
26
|
+
export interface AnnouncerUtteranceOptions {
|
|
27
|
+
/**
|
|
28
|
+
* Language code (BCP 47 format, e.g., 'en-US', 'fr-FR')
|
|
29
|
+
*
|
|
30
|
+
* @default 'en-US'
|
|
31
|
+
*/
|
|
32
|
+
lang?: string,
|
|
33
|
+
/**
|
|
34
|
+
* Voice pitch (0 to 2, where 1 is normal)
|
|
35
|
+
*
|
|
36
|
+
* @default 1
|
|
37
|
+
*/
|
|
38
|
+
pitch?: number,
|
|
39
|
+
/**
|
|
40
|
+
* Speech rate (0.1 to 10, where 1 is normal)
|
|
41
|
+
*
|
|
42
|
+
* @default 1
|
|
43
|
+
*/
|
|
44
|
+
rate?: number,
|
|
45
|
+
/**
|
|
46
|
+
* Voice to use (obtained from `speechSynthesis.getVoices()`)
|
|
47
|
+
*
|
|
48
|
+
* @default null
|
|
49
|
+
*/
|
|
50
|
+
voice?: SpeechSynthesisVoice | null,
|
|
51
|
+
/**
|
|
52
|
+
* Volume level (0 to 1, where 1 is full volume)
|
|
53
|
+
*
|
|
54
|
+
* @default 1
|
|
55
|
+
*/
|
|
56
|
+
volume?: number,
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface AnnouncerUtterance<T = any> extends Promise<T> {
|
|
27
60
|
/**
|
|
28
61
|
* Removes a specific message from the announcement queue,
|
|
29
62
|
* to make sure it isn't spoke out.
|
|
@@ -46,8 +79,34 @@ declare module '@lightningjs/blits' {
|
|
|
46
79
|
* When a message is added with politeness set to `assertive` the message
|
|
47
80
|
* will be added to the beginning of the queue
|
|
48
81
|
*
|
|
82
|
+
* @param message - The message to be spoken
|
|
83
|
+
* @param politeness - Politeness level ('off', 'polite', or 'assertive')
|
|
84
|
+
* @param options - Optional utterance options (rate, pitch, lang, voice, volume)
|
|
85
|
+
*/
|
|
86
|
+
speak(message: string | number, politeness?: 'off' | 'polite' | 'assertive', options?: AnnouncerUtteranceOptions): AnnouncerUtterance;
|
|
87
|
+
/**
|
|
88
|
+
* Instruct the Announcer to speak a message with 'polite' politeness level.
|
|
89
|
+
* Will add the message to the end of announcement queue.
|
|
90
|
+
*
|
|
91
|
+
* @param message - The message to be spoken
|
|
92
|
+
* @param options - Optional utterance options (rate, pitch, lang, voice, volume)
|
|
93
|
+
*/
|
|
94
|
+
polite(message: string | number, options?: AnnouncerUtteranceOptions): AnnouncerUtterance;
|
|
95
|
+
/**
|
|
96
|
+
* Instruct the Announcer to speak a message with 'assertive' politeness level.
|
|
97
|
+
* Will add the message to the beginning of announcement queue.
|
|
98
|
+
*
|
|
99
|
+
* @param message - The message to be spoken
|
|
100
|
+
* @param options - Optional utterance options (rate, pitch, lang, voice, volume)
|
|
49
101
|
*/
|
|
50
|
-
|
|
102
|
+
assertive(message: string | number, options?: AnnouncerUtteranceOptions): AnnouncerUtterance;
|
|
103
|
+
/**
|
|
104
|
+
* Configure global default utterance options that will be applied to all
|
|
105
|
+
* subsequent announcements unless overridden by per-call options.
|
|
106
|
+
*
|
|
107
|
+
* @param options - Default utterance options (rate, pitch, lang, voice, volume)
|
|
108
|
+
*/
|
|
109
|
+
configure(options?: AnnouncerUtteranceOptions): void;
|
|
51
110
|
/**
|
|
52
111
|
* Instruct the Announcer to add a pause of a certain duration (in ms). Will add this pause
|
|
53
112
|
* to the end of announcement queue
|
|
@@ -135,6 +194,18 @@ declare module '@lightningjs/blits' {
|
|
|
135
194
|
* This event can fire multiple times during the component's lifecycle
|
|
136
195
|
*/
|
|
137
196
|
exit?: () => void;
|
|
197
|
+
/**
|
|
198
|
+
* Fires when the renderer is done rendering and enters an idle state
|
|
199
|
+
*
|
|
200
|
+
* Note: This event can fire multiple times
|
|
201
|
+
*/
|
|
202
|
+
idle?: () => void;
|
|
203
|
+
/**
|
|
204
|
+
* Fires at a predefined interval and reports the current FPS value
|
|
205
|
+
*
|
|
206
|
+
* Note: This event fire multiple times
|
|
207
|
+
*/
|
|
208
|
+
fpsUpdate?: () => void;
|
|
138
209
|
}
|
|
139
210
|
|
|
140
211
|
export interface Input {
|
|
@@ -305,6 +376,14 @@ declare module '@lightningjs/blits' {
|
|
|
305
376
|
$listen: {
|
|
306
377
|
(event: string, callback: (args: any) => void, priority?: number): void;
|
|
307
378
|
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Remove an event listener previously registered with $listen
|
|
382
|
+
*/
|
|
383
|
+
$unlisten: {
|
|
384
|
+
(event: string): void;
|
|
385
|
+
}
|
|
386
|
+
|
|
308
387
|
/**
|
|
309
388
|
* Emit events that other components can listen to
|
|
310
389
|
* @param name - name of the event to be emitted
|
|
@@ -316,6 +395,13 @@ declare module '@lightningjs/blits' {
|
|
|
316
395
|
*/
|
|
317
396
|
$emit(name: string, data?: any, byReference?: boolean): void;
|
|
318
397
|
|
|
398
|
+
/**
|
|
399
|
+
* Remove all listeners for this component from all events
|
|
400
|
+
*/
|
|
401
|
+
$clearListeners: {
|
|
402
|
+
(): void;
|
|
403
|
+
}
|
|
404
|
+
|
|
319
405
|
/**
|
|
320
406
|
* Set a timeout that is automatically cleaned upon component destroy
|
|
321
407
|
*/
|
|
@@ -1078,7 +1164,24 @@ declare module '@lightningjs/blits' {
|
|
|
1078
1164
|
*
|
|
1079
1165
|
* @default false
|
|
1080
1166
|
*/
|
|
1081
|
-
announcer?: boolean
|
|
1167
|
+
announcer?: boolean,
|
|
1168
|
+
/**
|
|
1169
|
+
* Global default utterance options for the announcer.
|
|
1170
|
+
*
|
|
1171
|
+
* These options will be applied to all announcements unless overridden
|
|
1172
|
+
* by per-call options passed to `speak()`, `polite()`, or `assertive()`.
|
|
1173
|
+
*
|
|
1174
|
+
* @example
|
|
1175
|
+
* ```js
|
|
1176
|
+
* announcerOptions: {
|
|
1177
|
+
* rate: 1.0,
|
|
1178
|
+
* pitch: 1.0,
|
|
1179
|
+
* lang: 'en-US',
|
|
1180
|
+
* volume: 1.0
|
|
1181
|
+
* }
|
|
1182
|
+
* ```
|
|
1183
|
+
*/
|
|
1184
|
+
announcerOptions?: AnnouncerUtteranceOptions,
|
|
1082
1185
|
/**
|
|
1083
1186
|
* Maximum FPS at which the App will be rendered
|
|
1084
1187
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lightningjs/blits",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.43.0",
|
|
4
4
|
"description": "Blits: The Lightning 3 App Development Framework",
|
|
5
5
|
"bin": "bin/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
"test": "c8 node scripts/runTests.js",
|
|
17
17
|
"test:ci": "c8 npm run test:run",
|
|
18
18
|
"test:run": "node -r global-jsdom/register ./node_modules/.bin/tape '{,!(node_modules|packages)/**/}*.test.js' | tap-diff",
|
|
19
|
-
"lint": "
|
|
20
|
-
"lint:fix": "
|
|
19
|
+
"lint": "node scripts/lint.js",
|
|
20
|
+
"lint:fix": "node scripts/lint.js fix",
|
|
21
21
|
"prepublishOnly": "node scripts/prepublishOnly.js",
|
|
22
22
|
"postpublish": "node scripts/postpublish.js",
|
|
23
23
|
"prepare": "husky",
|
package/scripts/lint.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import fg from 'fast-glob'
|
|
2
|
+
import { resolve } from 'path'
|
|
3
|
+
import { fileURLToPath } from 'url'
|
|
4
|
+
import { ESLint } from 'eslint'
|
|
5
|
+
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
7
|
+
const __dirname = resolve(__filename, '../..')
|
|
8
|
+
|
|
9
|
+
// Get "fix" flag from command line arguments
|
|
10
|
+
const isFix = process.argv[2] === 'fix'
|
|
11
|
+
|
|
12
|
+
console.log('Starting the linting process...')
|
|
13
|
+
console.log(`Fix mode is ${isFix ? 'enabled' : 'disabled'}`)
|
|
14
|
+
|
|
15
|
+
const lintFiles = await fg(['**/*.js', '!node_modules/**'], {
|
|
16
|
+
cwd: __dirname,
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
console.log(`Found ${lintFiles.length} JavaScript file(s) to lint.`)
|
|
20
|
+
|
|
21
|
+
if (lintFiles.length === 0) {
|
|
22
|
+
console.log('No JavaScript files found to lint.')
|
|
23
|
+
process.exit(0)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const lint = new ESLint({
|
|
27
|
+
fix: isFix,
|
|
28
|
+
cwd: __dirname,
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
const results = await lint.lintFiles(lintFiles)
|
|
32
|
+
|
|
33
|
+
// Apply fixes to files if "fix" option is enabled
|
|
34
|
+
if (isFix) {
|
|
35
|
+
await ESLint.outputFixes(results)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const formatter = await lint.loadFormatter('stylish')
|
|
39
|
+
const resultText = formatter.format(results)
|
|
40
|
+
|
|
41
|
+
if (resultText) {
|
|
42
|
+
console.log(resultText)
|
|
43
|
+
} else {
|
|
44
|
+
console.log('Linting complete: All files passed without issues.')
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Determine if there were any errors (not warnings)
|
|
48
|
+
const hasErrors = results.some((result) => result.errorCount > 0)
|
|
49
|
+
|
|
50
|
+
process.exit(hasErrors ? 1 : 0)
|
package/scripts/prepush.js
CHANGED
|
@@ -43,9 +43,13 @@ try {
|
|
|
43
43
|
})
|
|
44
44
|
} else {
|
|
45
45
|
// Only check files in the commits that are being pushed right now
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
// Added --diff-filter=AM to exclude deleted files from linting.
|
|
47
|
+
filesToLint = execSync(
|
|
48
|
+
`git diff --diff-filter=AM --name-only ${remoteSha}..${localSha} | grep '\\.js$'`,
|
|
49
|
+
{
|
|
50
|
+
encoding: 'utf8',
|
|
51
|
+
}
|
|
52
|
+
)
|
|
49
53
|
}
|
|
50
54
|
} catch (error) {
|
|
51
55
|
// grep will exit with code 1 if it doesn't find anything
|
|
@@ -60,7 +64,6 @@ if (!filesToLint.trim()) {
|
|
|
60
64
|
|
|
61
65
|
const fileList = filesToLint.trim().split('\n')
|
|
62
66
|
console.log(`Running ESLint on ${fileList.length} changed JavaScript files:\n`)
|
|
63
|
-
console.log(filesToLint)
|
|
64
67
|
|
|
65
68
|
try {
|
|
66
69
|
// Run ESLint on the changed files
|
|
@@ -25,6 +25,9 @@ let isProcessing = false
|
|
|
25
25
|
let currentId = null
|
|
26
26
|
let debounce = null
|
|
27
27
|
|
|
28
|
+
// Global default utterance options
|
|
29
|
+
let globalDefaultOptions = {}
|
|
30
|
+
|
|
28
31
|
const noopAnnouncement = {
|
|
29
32
|
then() {},
|
|
30
33
|
done() {},
|
|
@@ -45,10 +48,10 @@ const toggle = (v) => {
|
|
|
45
48
|
active = v ? true : false
|
|
46
49
|
}
|
|
47
50
|
|
|
48
|
-
const speak = (message, politeness = 'off') => {
|
|
51
|
+
const speak = (message, politeness = 'off', options = {}) => {
|
|
49
52
|
if (active === false) return noopAnnouncement
|
|
50
53
|
|
|
51
|
-
return addToQueue(message, politeness)
|
|
54
|
+
return addToQueue(message, politeness, false, options)
|
|
52
55
|
}
|
|
53
56
|
|
|
54
57
|
const pause = (delay) => {
|
|
@@ -57,7 +60,7 @@ const pause = (delay) => {
|
|
|
57
60
|
return addToQueue(undefined, undefined, delay)
|
|
58
61
|
}
|
|
59
62
|
|
|
60
|
-
const addToQueue = (message, politeness, delay = false) => {
|
|
63
|
+
const addToQueue = (message, politeness, delay = false, options = {}) => {
|
|
61
64
|
// keep track of the id so message can be canceled
|
|
62
65
|
const id = count++
|
|
63
66
|
|
|
@@ -88,8 +91,8 @@ const addToQueue = (message, politeness, delay = false) => {
|
|
|
88
91
|
// add message of pause
|
|
89
92
|
if (delay === false) {
|
|
90
93
|
politeness === 'assertive'
|
|
91
|
-
? queue.unshift({ message, resolveFn, id })
|
|
92
|
-
: queue.push({ message, resolveFn, id })
|
|
94
|
+
? queue.unshift({ message, resolveFn, id, options })
|
|
95
|
+
: queue.push({ message, resolveFn, id, options })
|
|
93
96
|
} else {
|
|
94
97
|
queue.push({ delay, resolveFn, id })
|
|
95
98
|
}
|
|
@@ -107,7 +110,7 @@ const processQueue = async () => {
|
|
|
107
110
|
if (isProcessing === true || queue.length === 0) return
|
|
108
111
|
isProcessing = true
|
|
109
112
|
|
|
110
|
-
const { message, resolveFn, delay, id } = queue.shift()
|
|
113
|
+
const { message, resolveFn, delay, id, options = {} } = queue.shift()
|
|
111
114
|
|
|
112
115
|
currentId = id
|
|
113
116
|
|
|
@@ -125,7 +128,12 @@ const processQueue = async () => {
|
|
|
125
128
|
Log.debug(`Announcer - speaking: "${message}" (id: ${id})`)
|
|
126
129
|
|
|
127
130
|
speechSynthesis
|
|
128
|
-
.speak({
|
|
131
|
+
.speak({
|
|
132
|
+
message,
|
|
133
|
+
id,
|
|
134
|
+
...globalDefaultOptions,
|
|
135
|
+
...options,
|
|
136
|
+
})
|
|
129
137
|
.then(() => {
|
|
130
138
|
Log.debug(`Announcer - finished speaking: "${message}" (id: ${id})`)
|
|
131
139
|
|
|
@@ -146,9 +154,9 @@ const processQueue = async () => {
|
|
|
146
154
|
}
|
|
147
155
|
}
|
|
148
156
|
|
|
149
|
-
const polite = (message) => speak(message, 'polite')
|
|
157
|
+
const polite = (message, options = {}) => speak(message, 'polite', options)
|
|
150
158
|
|
|
151
|
-
const assertive = (message) => speak(message, 'assertive')
|
|
159
|
+
const assertive = (message, options = {}) => speak(message, 'assertive', options)
|
|
152
160
|
|
|
153
161
|
const stop = () => {
|
|
154
162
|
speechSynthesis.cancel()
|
|
@@ -159,6 +167,10 @@ const clear = () => {
|
|
|
159
167
|
queue.length = 0
|
|
160
168
|
}
|
|
161
169
|
|
|
170
|
+
const configure = (options = {}) => {
|
|
171
|
+
globalDefaultOptions = { ...globalDefaultOptions, ...options }
|
|
172
|
+
}
|
|
173
|
+
|
|
162
174
|
export default {
|
|
163
175
|
speak,
|
|
164
176
|
polite,
|
|
@@ -169,4 +181,5 @@ export default {
|
|
|
169
181
|
toggle,
|
|
170
182
|
clear,
|
|
171
183
|
pause,
|
|
184
|
+
configure,
|
|
172
185
|
}
|
package/src/application.js
CHANGED
|
@@ -58,6 +58,11 @@ const Application = (config) => {
|
|
|
58
58
|
config.hooks[symbols.init] = function () {
|
|
59
59
|
// set the initial activation state of the announcer based on launch setting
|
|
60
60
|
this.$announcer.toggle(Settings.get('announcer', false))
|
|
61
|
+
// configure global default utterance options if provided in settings
|
|
62
|
+
const announcerOptions = Settings.get('announcerOptions', null)
|
|
63
|
+
if (announcerOptions && typeof announcerOptions === 'object') {
|
|
64
|
+
this.$announcer.configure(announcerOptions)
|
|
65
|
+
}
|
|
61
66
|
const keyMap = { ...defaultKeyMap, ...Settings.get('keymap', {}) }
|
|
62
67
|
|
|
63
68
|
/** @type {number} Input throttle time in milliseconds (0 = disabled) */
|
|
@@ -72,4 +72,18 @@ export default {
|
|
|
72
72
|
enumerable: true,
|
|
73
73
|
configurable: false,
|
|
74
74
|
},
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Removes all listeners for this component from all events.
|
|
78
|
+
*
|
|
79
|
+
* @returns {void}
|
|
80
|
+
*/
|
|
81
|
+
$clearListeners: {
|
|
82
|
+
value: function () {
|
|
83
|
+
eventListeners.removeListeners(this)
|
|
84
|
+
},
|
|
85
|
+
writable: false,
|
|
86
|
+
enumerable: true,
|
|
87
|
+
configurable: false,
|
|
88
|
+
},
|
|
75
89
|
}
|
package/src/component.js
CHANGED
|
@@ -266,20 +266,33 @@ const Component = (name = required('name'), config = required('config')) => {
|
|
|
266
266
|
this[symbols.rendererEventListeners] = []
|
|
267
267
|
// register hooks if component has hooks specified
|
|
268
268
|
if (config.hooks) {
|
|
269
|
-
//
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
269
|
+
// push to next tick to ensure
|
|
270
|
+
setTimeout(() => {
|
|
271
|
+
// frame tick event
|
|
272
|
+
if (config.hooks.frameTick) {
|
|
273
|
+
const cb = (r, data) => emit('frameTick', this[symbols.identifier], this, [data])
|
|
274
|
+
this[symbols.rendererEventListeners].push({ event: 'frameTick', cb })
|
|
275
|
+
renderer.on('frameTick', cb)
|
|
276
|
+
}
|
|
275
277
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
278
|
+
// idle event
|
|
279
|
+
if (config.hooks.idle) {
|
|
280
|
+
const cb = () => {
|
|
281
|
+
emit('idle', this[symbols.identifier], this)
|
|
282
|
+
}
|
|
283
|
+
this[symbols.rendererEventListeners].push({ event: 'idle', cb })
|
|
284
|
+
renderer.on('idle', cb)
|
|
279
285
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
286
|
+
|
|
287
|
+
// fpsUpdate event
|
|
288
|
+
if (config.hooks.fpsUpdate) {
|
|
289
|
+
const cb = (r, data) => {
|
|
290
|
+
emit('fpsUpdate', this[symbols.identifier], this, [data.fps])
|
|
291
|
+
}
|
|
292
|
+
this[symbols.rendererEventListeners].push({ event: 'fpsUpdate', cb })
|
|
293
|
+
renderer.on('fpsUpdate', cb)
|
|
294
|
+
}
|
|
295
|
+
})
|
|
283
296
|
|
|
284
297
|
// inBounds event emiting a lifecycle attach event
|
|
285
298
|
if (config.hooks.attach) {
|
package/src/components/Sprite.js
CHANGED
|
@@ -81,27 +81,65 @@ export default () =>
|
|
|
81
81
|
function anonymous(component, elms, context, components, rootComponent, effect) {
|
|
82
82
|
elms[0].set('color', component.color)
|
|
83
83
|
},
|
|
84
|
-
function anonymous(component, elms, context, components, rootComponent, effect) {
|
|
85
|
-
elms[0].set('effects', component.effects)
|
|
86
|
-
},
|
|
87
84
|
],
|
|
88
85
|
context: {},
|
|
89
86
|
},
|
|
90
87
|
props: ['image', 'map', 'frame', 'color', 'effects'],
|
|
88
|
+
|
|
91
89
|
state() {
|
|
92
90
|
return {
|
|
93
91
|
spriteTexture: null,
|
|
92
|
+
currentSrc: null,
|
|
94
93
|
}
|
|
95
94
|
},
|
|
95
|
+
|
|
96
96
|
computed: {
|
|
97
97
|
texture() {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
98
|
+
// If no image - nothing to render
|
|
99
|
+
if (this.image === undefined || this.image === null) {
|
|
100
|
+
return null
|
|
101
|
+
}
|
|
102
102
|
|
|
103
|
-
|
|
104
|
-
|
|
103
|
+
// Get renderer
|
|
104
|
+
const renderer = this[symbols.renderer]()
|
|
105
|
+
if (renderer === null || renderer === undefined || renderer.createTexture === undefined) {
|
|
106
|
+
return null
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Recreate texture only when image src changes
|
|
110
|
+
if (
|
|
111
|
+
this.spriteTexture === undefined ||
|
|
112
|
+
this.spriteTexture === null ||
|
|
113
|
+
this.currentSrc !== this.image
|
|
114
|
+
) {
|
|
115
|
+
this.spriteTexture = renderer.createTexture('ImageTexture', {
|
|
116
|
+
src: this.image,
|
|
117
|
+
})
|
|
118
|
+
this.currentSrc = this.image
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Resolve frame data from sprite map
|
|
122
|
+
let options = null
|
|
123
|
+
if (
|
|
124
|
+
this.map !== undefined &&
|
|
125
|
+
this.map !== null &&
|
|
126
|
+
this.frame !== undefined &&
|
|
127
|
+
this.frame !== null
|
|
128
|
+
) {
|
|
129
|
+
options =
|
|
130
|
+
'frames' in this.map
|
|
131
|
+
? Object.assign({}, this.map.defaults || {}, this.map.frames[this.frame])
|
|
132
|
+
: this.map[this.frame]
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// If no map but frame is object (manual subtexture)
|
|
136
|
+
if ((options === null || options === undefined) && typeof this.frame === 'object') {
|
|
137
|
+
options = this.frame
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Create SubTexture only if frame data exists
|
|
141
|
+
if (options !== null && options !== undefined) {
|
|
142
|
+
return renderer.createTexture('SubTexture', {
|
|
105
143
|
texture: this.spriteTexture,
|
|
106
144
|
x: options.x,
|
|
107
145
|
y: options.y,
|
|
@@ -109,13 +147,9 @@ export default () =>
|
|
|
109
147
|
height: options.h,
|
|
110
148
|
})
|
|
111
149
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
ready() {
|
|
116
|
-
this.spriteTexture = this[symbols.renderer]().createTexture('ImageTexture', {
|
|
117
|
-
src: this.image,
|
|
118
|
-
})
|
|
150
|
+
|
|
151
|
+
// Default fallback (single image)
|
|
152
|
+
return this.spriteTexture
|
|
119
153
|
},
|
|
120
154
|
},
|
|
121
155
|
})
|
package/src/launch.js
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
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 blitsPackageInfo from '../package.json'
|
|
21
|
+
import blitsPackageInfo from '../package.json' with { type: 'json' }
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* @typedef {Object} Font
|
package/src/router/router.js
CHANGED
|
@@ -78,7 +78,9 @@ let navigationData = {}
|
|
|
78
78
|
let navigatingBack = false
|
|
79
79
|
let navigatingBackTo = undefined
|
|
80
80
|
let previousFocus
|
|
81
|
-
|
|
81
|
+
// Skips internal router navigation when set to true only for the next "navigate"
|
|
82
|
+
// execution, needed for window.history management
|
|
83
|
+
let preventHashChangeNavigation = false
|
|
82
84
|
/**
|
|
83
85
|
* Get the current hash
|
|
84
86
|
* @returns {Hash}
|
|
@@ -243,7 +245,7 @@ export const navigate = async function () {
|
|
|
243
245
|
Announcer.clear()
|
|
244
246
|
state.navigating = true
|
|
245
247
|
let reuse = false
|
|
246
|
-
if (this.parent[symbols.routes]) {
|
|
248
|
+
if (preventHashChangeNavigation === false && this.parent[symbols.routes]) {
|
|
247
249
|
let previousRoute = currentRoute //? Object.assign({}, currentRoute) : undefined
|
|
248
250
|
let route = matchHash(getHash(document.location.hash), this.parent[symbols.routes])
|
|
249
251
|
|
|
@@ -255,10 +257,22 @@ export const navigate = async function () {
|
|
|
255
257
|
if (this.parent[symbols.routerHooks]) {
|
|
256
258
|
const hooks = this.parent[symbols.routerHooks]
|
|
257
259
|
if (hooks.beforeEach) {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
+
try {
|
|
261
|
+
beforeEachResult = await hooks.beforeEach.call(this.parent, route, previousRoute)
|
|
262
|
+
if (isString(beforeEachResult)) {
|
|
263
|
+
currentRoute = previousRoute
|
|
264
|
+
to(beforeEachResult)
|
|
265
|
+
return
|
|
266
|
+
}
|
|
267
|
+
} catch (error) {
|
|
268
|
+
Log.error('Error or Rejected Promise in "BeforeEach" Hook', error)
|
|
269
|
+
|
|
270
|
+
preventHashChangeNavigation = true
|
|
260
271
|
currentRoute = previousRoute
|
|
261
|
-
|
|
272
|
+
window.history.back()
|
|
273
|
+
|
|
274
|
+
navigatingBack = false
|
|
275
|
+
state.navigating = false
|
|
262
276
|
return
|
|
263
277
|
}
|
|
264
278
|
// If the resolved result is an object, redirect if the path in the object was changed
|
|
@@ -267,15 +281,37 @@ export const navigate = async function () {
|
|
|
267
281
|
to(beforeEachResult.path, beforeEachResult.data, beforeEachResult.options)
|
|
268
282
|
return
|
|
269
283
|
}
|
|
284
|
+
// If the resolved result is false, cancel navigation
|
|
285
|
+
if (beforeEachResult === false) {
|
|
286
|
+
preventHashChangeNavigation = true
|
|
287
|
+
currentRoute = previousRoute
|
|
288
|
+
window.history.back()
|
|
289
|
+
|
|
290
|
+
navigatingBack = false
|
|
291
|
+
state.navigating = false
|
|
292
|
+
return
|
|
293
|
+
}
|
|
270
294
|
}
|
|
271
295
|
}
|
|
272
296
|
|
|
273
297
|
let beforeHookOutput
|
|
274
298
|
if (route.hooks.before) {
|
|
275
|
-
|
|
276
|
-
|
|
299
|
+
try {
|
|
300
|
+
beforeHookOutput = await route.hooks.before.call(this.parent, route, previousRoute)
|
|
301
|
+
if (isString(beforeHookOutput)) {
|
|
302
|
+
currentRoute = previousRoute
|
|
303
|
+
to(beforeHookOutput)
|
|
304
|
+
return
|
|
305
|
+
}
|
|
306
|
+
} catch (error) {
|
|
307
|
+
Log.error('Error or Rejected Promise in "Before" Hook', error)
|
|
308
|
+
|
|
309
|
+
preventHashChangeNavigation = true
|
|
277
310
|
currentRoute = previousRoute
|
|
278
|
-
|
|
311
|
+
window.history.back()
|
|
312
|
+
|
|
313
|
+
navigatingBack = false
|
|
314
|
+
state.navigating = false
|
|
279
315
|
return
|
|
280
316
|
}
|
|
281
317
|
// If the resolved result is an object, redirect if the path in the object was changed
|
|
@@ -284,6 +320,16 @@ export const navigate = async function () {
|
|
|
284
320
|
to(beforeHookOutput.path, beforeHookOutput.data, beforeHookOutput.options)
|
|
285
321
|
return
|
|
286
322
|
}
|
|
323
|
+
// If the resolved result is false, cancel navigation
|
|
324
|
+
if (beforeHookOutput === false) {
|
|
325
|
+
preventHashChangeNavigation = true
|
|
326
|
+
currentRoute = previousRoute
|
|
327
|
+
window.history.back()
|
|
328
|
+
|
|
329
|
+
navigatingBack = false
|
|
330
|
+
state.navigating = false
|
|
331
|
+
return
|
|
332
|
+
}
|
|
287
333
|
}
|
|
288
334
|
// add the previous route (technically still the current route at this point)
|
|
289
335
|
// into the history stack when inHistory is true and we're not navigating back
|
|
@@ -462,6 +508,7 @@ export const navigate = async function () {
|
|
|
462
508
|
// reset navigating indicators
|
|
463
509
|
navigatingBack = false
|
|
464
510
|
state.navigating = false
|
|
511
|
+
preventHashChangeNavigation = false
|
|
465
512
|
}
|
|
466
513
|
|
|
467
514
|
/**
|
package/vite/index.js
CHANGED
|
@@ -19,10 +19,18 @@ import preCompiler from './preCompiler.js'
|
|
|
19
19
|
import msdfGenerator from './msdfGenerator.js'
|
|
20
20
|
import blitsFileConverter from './blitsFileConverter.js'
|
|
21
21
|
import reactivityGuard from './reactivityGuard.js'
|
|
22
|
+
import injectDevConfig from './inject-dev-config.js'
|
|
22
23
|
|
|
23
24
|
export { default as preCompiler } from './preCompiler.js'
|
|
24
25
|
export { default as msdfGenerator } from './msdfGenerator.js'
|
|
25
26
|
export { default as blitsFileConverter } from './blitsFileConverter.js'
|
|
26
27
|
export { default as reactivityGuard } from './reactivityGuard.js'
|
|
28
|
+
export { default as injectDevConfig } from './inject-dev-config.js'
|
|
27
29
|
|
|
28
|
-
export default [
|
|
30
|
+
export default [
|
|
31
|
+
injectDevConfig(),
|
|
32
|
+
blitsFileConverter(),
|
|
33
|
+
reactivityGuard(),
|
|
34
|
+
preCompiler(),
|
|
35
|
+
msdfGenerator(),
|
|
36
|
+
]
|