@lightningjs/blits 1.35.5 → 1.37.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 +19 -0
- package/eslint.config.cjs +85 -0
- package/index.d.ts +36 -2
- package/package.json +9 -6
- package/src/announcer/announcer.js +15 -6
- package/src/announcer/speechSynthesis.js +1 -1
- package/src/application.js +6 -3
- package/src/component/setup/props.js +2 -2
- package/src/engines/L3/element.js +2 -2
- package/src/lib/codegenerator/generator.js +6 -6
- package/src/plugins/language.js +11 -8
- package/src/router/router.js +4 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v1.37.0
|
|
4
|
+
|
|
5
|
+
_22 Aug 2025_
|
|
6
|
+
|
|
7
|
+
- Added Route options check before checking passFocus flag
|
|
8
|
+
- Added support for additional metadata in Route config (for associating arbitrary data to a route)
|
|
9
|
+
- Upgraded dev dependencies to latest versions
|
|
10
|
+
|
|
11
|
+
## v1.36.0
|
|
12
|
+
|
|
13
|
+
_11 Aug 2025_
|
|
14
|
+
|
|
15
|
+
- Added `passFocus` option to route options to prevent focus being passed to page navigated to
|
|
16
|
+
- Added missing type definitions for Route options
|
|
17
|
+
- Fixed sidebar in docs
|
|
18
|
+
- Added debug log messages to Announcer
|
|
19
|
+
- Added `remove()` function as (preferred) alias for announcer `message.cancel()`
|
|
20
|
+
- Fixed issue with removing a messages causing an interrupt of current message being read out
|
|
21
|
+
|
|
3
22
|
## v1.35.5
|
|
4
23
|
|
|
5
24
|
_06 Aug 2025_
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2023 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 {
|
|
19
|
+
defineConfig,
|
|
20
|
+
globalIgnores,
|
|
21
|
+
} = require("eslint/config");
|
|
22
|
+
|
|
23
|
+
const globals = require("globals");
|
|
24
|
+
const prettier = require("eslint-plugin-prettier");
|
|
25
|
+
const babelParser = require("@babel/eslint-parser");
|
|
26
|
+
const js = require("@eslint/js");
|
|
27
|
+
|
|
28
|
+
const {
|
|
29
|
+
FlatCompat,
|
|
30
|
+
} = require("@eslint/eslintrc");
|
|
31
|
+
|
|
32
|
+
const compat = new FlatCompat({
|
|
33
|
+
baseDirectory: __dirname,
|
|
34
|
+
recommendedConfig: js.configs.recommended,
|
|
35
|
+
allConfig: js.configs.all
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
module.exports = defineConfig([{
|
|
39
|
+
languageOptions: {
|
|
40
|
+
globals: {
|
|
41
|
+
...globals.browser,
|
|
42
|
+
...globals.node,
|
|
43
|
+
globalThis: false,
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
parser: babelParser,
|
|
47
|
+
ecmaVersion: 2018,
|
|
48
|
+
sourceType: "module",
|
|
49
|
+
|
|
50
|
+
parserOptions: {
|
|
51
|
+
requireConfigFile: false,
|
|
52
|
+
|
|
53
|
+
babelOptions: {
|
|
54
|
+
plugins: ["@babel/plugin-syntax-import-assertions"],
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
plugins: {
|
|
60
|
+
prettier,
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
extends: compat.extends("eslint:recommended", "plugin:prettier/recommended", "prettier"),
|
|
64
|
+
|
|
65
|
+
rules: {
|
|
66
|
+
"no-console": process.env.NODE_ENV === "production" ? "error" : "off",
|
|
67
|
+
"no-debugger": process.env.NODE_ENV === "production" ? "error" : "off",
|
|
68
|
+
quotes: [2, "single", "avoid-escape"],
|
|
69
|
+
semi: [2, "never"],
|
|
70
|
+
"no-extra-boolean-cast": "off",
|
|
71
|
+
|
|
72
|
+
"no-unused-vars": [1, {
|
|
73
|
+
argsIgnorePattern: "res|next|^err",
|
|
74
|
+
}],
|
|
75
|
+
|
|
76
|
+
"prettier/prettier": ["error", {
|
|
77
|
+
trailingComma: "es5",
|
|
78
|
+
singleQuote: true,
|
|
79
|
+
tabWidth: 2,
|
|
80
|
+
semi: false,
|
|
81
|
+
printWidth: 100,
|
|
82
|
+
endOfLine: "auto",
|
|
83
|
+
}],
|
|
84
|
+
},
|
|
85
|
+
}, globalIgnores(["**/node_modules"])]);
|
package/index.d.ts
CHANGED
|
@@ -187,7 +187,30 @@ declare module '@lightningjs/blits' {
|
|
|
187
187
|
|
|
188
188
|
// todo: specify valid route options
|
|
189
189
|
export interface RouteOptions {
|
|
190
|
-
|
|
190
|
+
/**
|
|
191
|
+
* Whether the page navigation should be added to the history stack
|
|
192
|
+
* used when navigating back using `this.$router.back()`
|
|
193
|
+
*
|
|
194
|
+
* @default true
|
|
195
|
+
*/
|
|
196
|
+
inHistory?: Boolean
|
|
197
|
+
/**
|
|
198
|
+
* Whether the page should be kept alive when navigating away. Can be useful
|
|
199
|
+
* for a homepage where the state should be fully retained when navigating back
|
|
200
|
+
* from a details page
|
|
201
|
+
*
|
|
202
|
+
* @default false
|
|
203
|
+
*/
|
|
204
|
+
keepAlive?: Boolean
|
|
205
|
+
/**
|
|
206
|
+
* Whether the focus should be delegated to the page that's being navigated to.
|
|
207
|
+
* Can be useful when navigating to a new page from a widget / menu overlaying the
|
|
208
|
+
* RouterView, where the widget should maintain the focus (instead of the new page, which
|
|
209
|
+
* is the default behaviour)
|
|
210
|
+
*
|
|
211
|
+
* @default true
|
|
212
|
+
*/
|
|
213
|
+
passFocus?: Boolean
|
|
191
214
|
}
|
|
192
215
|
|
|
193
216
|
export interface Router {
|
|
@@ -628,7 +651,7 @@ declare module '@lightningjs/blits' {
|
|
|
628
651
|
/**
|
|
629
652
|
* Extra route options
|
|
630
653
|
*/
|
|
631
|
-
options?:
|
|
654
|
+
options?: RouteOptions
|
|
632
655
|
/**
|
|
633
656
|
* Message to be announced when visiting the route (often used for accessibility purposes)
|
|
634
657
|
*
|
|
@@ -656,6 +679,17 @@ declare module '@lightningjs/blits' {
|
|
|
656
679
|
data?: {
|
|
657
680
|
[key: string]: any
|
|
658
681
|
}
|
|
682
|
+
|
|
683
|
+
/**
|
|
684
|
+
* Metadata attached to the route, can be used to add any arbitrary
|
|
685
|
+
* data to a route such as `auth: true/false`, a route ID or route description
|
|
686
|
+
* Note that this data is not reactive and not passed as props to components.
|
|
687
|
+
* The metadata is available in the router `before` or `beforeEach` hooks
|
|
688
|
+
*
|
|
689
|
+
*/
|
|
690
|
+
meta?: {
|
|
691
|
+
[key: string]: any
|
|
692
|
+
}
|
|
659
693
|
}
|
|
660
694
|
|
|
661
695
|
type ComponentFactory = () => void
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lightningjs/blits",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.37.0",
|
|
4
4
|
"description": "Blits: The Lightning 3 App Development Framework",
|
|
5
5
|
"bin": "bin/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -35,16 +35,19 @@
|
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@babel/eslint-parser": "^7.26.5",
|
|
37
37
|
"@babel/plugin-syntax-import-assertions": "^7.26.0",
|
|
38
|
-
"
|
|
39
|
-
"eslint": "^
|
|
40
|
-
"
|
|
41
|
-
"eslint
|
|
38
|
+
"@eslint/eslintrc": "^3.3.1",
|
|
39
|
+
"@eslint/js": "^9.33.0",
|
|
40
|
+
"c8": "^9.1.0",
|
|
41
|
+
"eslint": "^9.33.0",
|
|
42
|
+
"eslint-config-prettier": "^10.1.8",
|
|
43
|
+
"eslint-plugin-prettier": "^5.5.4",
|
|
42
44
|
"fast-glob": "^3.3.3",
|
|
43
45
|
"global-jsdom": "24.0.0",
|
|
46
|
+
"globals": "^16.3.0",
|
|
44
47
|
"husky": "^9.1.7",
|
|
45
48
|
"jsdom": "24.0.0",
|
|
46
49
|
"lint-staged": "^15.5.0",
|
|
47
|
-
"prettier": "^
|
|
50
|
+
"prettier": "^3.6.2",
|
|
48
51
|
"sinon": "^21.0.0",
|
|
49
52
|
"tap-diff": "^0.1.1",
|
|
50
53
|
"tape": "^5.5.0"
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* SPDX-License-Identifier: Apache-2.0
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
+
import { Log } from '../lib/log.js'
|
|
18
19
|
import speechSynthesis from './speechSynthesis.js'
|
|
19
20
|
|
|
20
21
|
let active = false
|
|
@@ -28,6 +29,7 @@ const noopAnnouncement = {
|
|
|
28
29
|
then() {},
|
|
29
30
|
done() {},
|
|
30
31
|
cancel() {},
|
|
32
|
+
remove() {},
|
|
31
33
|
stop() {},
|
|
32
34
|
}
|
|
33
35
|
|
|
@@ -66,11 +68,11 @@ const addToQueue = (message, politeness, delay = false) => {
|
|
|
66
68
|
resolveFn = resolve
|
|
67
69
|
})
|
|
68
70
|
|
|
69
|
-
// augment the promise with a cancel function
|
|
70
|
-
done.cancel = () => {
|
|
71
|
+
// augment the promise with a cancel / remove function
|
|
72
|
+
done.remove = done.cancel = () => {
|
|
71
73
|
const index = queue.findIndex((item) => item.id === id)
|
|
72
74
|
if (index !== -1) queue.splice(index, 1)
|
|
73
|
-
|
|
75
|
+
Log.debug(`Announcer - removed from queue: "${message}" (id: ${id})`)
|
|
74
76
|
resolveFn('canceled')
|
|
75
77
|
}
|
|
76
78
|
|
|
@@ -92,6 +94,8 @@ const addToQueue = (message, politeness, delay = false) => {
|
|
|
92
94
|
queue.push({ delay, resolveFn, id })
|
|
93
95
|
}
|
|
94
96
|
|
|
97
|
+
Log.debug(`Announcer - added to queue: "${message}" (id: ${id})`)
|
|
98
|
+
|
|
95
99
|
setTimeout(() => {
|
|
96
100
|
processQueue()
|
|
97
101
|
}, 100)
|
|
@@ -118,17 +122,22 @@ const processQueue = async () => {
|
|
|
118
122
|
if (debounce !== null) clearTimeout(debounce)
|
|
119
123
|
// add some easing when speaking the messages to reduce stuttering
|
|
120
124
|
debounce = setTimeout(() => {
|
|
125
|
+
Log.debug(`Announcer - speaking: "${message}" (id: ${id})`)
|
|
126
|
+
|
|
121
127
|
speechSynthesis
|
|
122
|
-
.speak({ message })
|
|
128
|
+
.speak({ message, id })
|
|
123
129
|
.then(() => {
|
|
124
|
-
|
|
130
|
+
Log.debug(`Announcer - finished speaking: "${message}" (id: ${id})`)
|
|
131
|
+
|
|
125
132
|
currentId = null
|
|
133
|
+
isProcessing = false
|
|
126
134
|
resolveFn('finished')
|
|
127
135
|
processQueue()
|
|
128
136
|
})
|
|
129
137
|
.catch((e) => {
|
|
130
|
-
isProcessing = false
|
|
131
138
|
currentId = null
|
|
139
|
+
isProcessing = false
|
|
140
|
+
Log.debug(`Announcer - error ("${e.error}") while speaking: "${message}" (id: ${id})`)
|
|
132
141
|
resolveFn(e.error)
|
|
133
142
|
processQueue()
|
|
134
143
|
})
|
|
@@ -64,7 +64,7 @@ const initialize = () => {
|
|
|
64
64
|
|
|
65
65
|
const speak = (options) => {
|
|
66
66
|
const utterance = new SpeechSynthesisUtterance(options.message)
|
|
67
|
-
const id =
|
|
67
|
+
const id = options.id
|
|
68
68
|
utterance.lang = options.lang || defaultUtteranceProps.lang
|
|
69
69
|
utterance.pitch = options.pitch || defaultUtteranceProps.pitch
|
|
70
70
|
utterance.rate = options.rate || defaultUtteranceProps.rate
|
package/src/application.js
CHANGED
|
@@ -96,9 +96,12 @@ const Application = (config) => {
|
|
|
96
96
|
|
|
97
97
|
Focus.input(key, e)
|
|
98
98
|
clearTimeout(holdTimeout)
|
|
99
|
-
holdTimeout = setTimeout(
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
holdTimeout = setTimeout(
|
|
100
|
+
() => {
|
|
101
|
+
Focus.hold = true
|
|
102
|
+
},
|
|
103
|
+
Settings.get('holdTimeout', DEFAULT_HOLD_TIMEOUT_MS)
|
|
104
|
+
)
|
|
102
105
|
}
|
|
103
106
|
|
|
104
107
|
keyUpHandler = (e) => {
|
|
@@ -41,8 +41,8 @@ export default (component, props = []) => {
|
|
|
41
41
|
this[symbols.props] !== undefined && prop.key in this[symbols.props]
|
|
42
42
|
? this[symbols.props][prop.key]
|
|
43
43
|
: 'default' in prop
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
? prop.default
|
|
45
|
+
: undefined
|
|
46
46
|
)
|
|
47
47
|
|
|
48
48
|
if (prop.required === true && value === undefined) {
|
|
@@ -191,9 +191,9 @@ const generateElementCode = function (
|
|
|
191
191
|
} else {
|
|
192
192
|
this.effectsCode.push(`
|
|
193
193
|
${elm}.set('${key.substring(1)}', ${interpolate(
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
194
|
+
templateObject[key],
|
|
195
|
+
options.component
|
|
196
|
+
)})
|
|
197
197
|
`)
|
|
198
198
|
}
|
|
199
199
|
// value.includes('.') === false &&
|
|
@@ -310,9 +310,9 @@ const generateComponentCode = function (
|
|
|
310
310
|
if (isReactiveKey(key)) {
|
|
311
311
|
this.effectsCode.push(`
|
|
312
312
|
${elm}[Symbol.for('props')]['${key.substring(1)}'] = ${interpolate(
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
313
|
+
templateObject[key],
|
|
314
|
+
options.component
|
|
315
|
+
)}`)
|
|
316
316
|
renderCode.push(`
|
|
317
317
|
propData = ${interpolate(templateObject[key], options.component)}
|
|
318
318
|
if (Array.isArray(propData) === true) {
|
package/src/plugins/language.js
CHANGED
|
@@ -41,8 +41,8 @@ export default {
|
|
|
41
41
|
Object.keys(translations).length === 0
|
|
42
42
|
? 'No translations loaded. Please load a file with translations or specify a translations object manually'
|
|
43
43
|
: language in translations === false
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
? `Language ${language} not available in the loaded translations`
|
|
45
|
+
: false
|
|
46
46
|
if (warningMsg) {
|
|
47
47
|
Log.warn(warningMsg)
|
|
48
48
|
// set the current language in the reactive state
|
|
@@ -127,12 +127,15 @@ export default {
|
|
|
127
127
|
return Object.keys(
|
|
128
128
|
// maps array input to an object {0: 'item1', 1: 'item2'}
|
|
129
129
|
Array.isArray(replacements) ? Object.assign({}, replacements) : replacements
|
|
130
|
-
).reduce(
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
130
|
+
).reduce(
|
|
131
|
+
(text, replacementKey) => {
|
|
132
|
+
return text.replace(
|
|
133
|
+
new RegExp('{\\s?' + replacementKey + '\\s?}', 'g'),
|
|
134
|
+
replacements[replacementKey]
|
|
135
|
+
)
|
|
136
|
+
},
|
|
137
|
+
(dictionary && dictionary[key]) || key
|
|
138
|
+
)
|
|
136
139
|
}
|
|
137
140
|
},
|
|
138
141
|
get language() {
|
package/src/router/router.js
CHANGED
|
@@ -340,8 +340,10 @@ export const navigate = async function () {
|
|
|
340
340
|
const children = this[symbols.children]
|
|
341
341
|
this.activeView = children[children.length - 1]
|
|
342
342
|
|
|
343
|
-
// set focus to the view that we're routing to
|
|
344
|
-
|
|
343
|
+
// set focus to the view that we're routing to (unless explicitly disabling passing focus)
|
|
344
|
+
if (route.options === undefined || route.options.passFocus !== false) {
|
|
345
|
+
focus ? focus.$focus() : /** @type {BlitsComponent} */ (view).$focus()
|
|
346
|
+
}
|
|
345
347
|
|
|
346
348
|
// apply before settings to holder element
|
|
347
349
|
if (route.transition.before) {
|