@lightningjs/blits 2.8.9 → 2.10.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 +28 -0
- package/index.d.ts +19 -8
- package/package.json +21 -13
- package/src/announcer/announcer.js +18 -3
- package/src/application.js +13 -6
- package/src/component/base/methods.js +10 -4
- package/src/engines/L3/element.js +28 -8
- package/src/focus/focus.js +7 -1
- package/src/helpers/fetchJson.js +10 -2
- package/src/lib/colors/colors.js +3 -1
- package/src/lib/colors/htmlColors.d.ts +4 -0
- package/src/lib/reactivity/effect.js +20 -10
- package/src/lib/reactivity/reactive.js +16 -2
- package/src/platforms/dom/announcer.d.ts +28 -0
- package/src/platforms/dom/announcer.js +111 -0
- package/src/plugins/language.js +2 -1
- package/src/plugins/storage/storage.js +2 -2
- package/src/plugins/theme.js +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,7 +1,35 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.10.0
|
|
4
|
+
|
|
5
|
+
_21 sep 2026_
|
|
6
|
+
|
|
7
|
+
- Fixed / improved type definitions
|
|
8
|
+
- Fixed issue with accidental sideeffect running when `$hasFocus` triggers after destroy
|
|
9
|
+
- Fixed issue where an error in an effect can break execution of other effects
|
|
10
|
+
- Added support for reactivity of `array.length`
|
|
11
|
+
- Improved reactivity in the case of nested array operations
|
|
12
|
+
- Fixed case where transition callbacks could run after destroy
|
|
13
|
+
- Fixed case where keyup callback could fire after component destroy
|
|
14
|
+
- Improved error handling and parse rejection for fetchJson method
|
|
15
|
+
- Fixed typing of `fpsUpdate`
|
|
16
|
+
- Added textCalculated listener to trigger layout changes
|
|
17
|
+
- Added `mouseMoveThrottle` setting to configure responsiveness of mouse movements
|
|
18
|
+
- Fixed issue where hover is not recognized after keyboard use
|
|
19
|
+
- Added support for numeric rgba colors
|
|
20
|
+
- Bumped renderer to 3.5.0
|
|
21
|
+
|
|
22
|
+
## 2.9.0
|
|
23
|
+
|
|
24
|
+
_9 sep 2026_
|
|
25
|
+
|
|
26
|
+
- Added DOM based Aria announcer implementation
|
|
27
|
+
|
|
28
|
+
|
|
3
29
|
## 2.8.9
|
|
4
30
|
|
|
31
|
+
_12 aug 2026_
|
|
32
|
+
|
|
5
33
|
- Fixed focus issue related to multiple named router views
|
|
6
34
|
|
|
7
35
|
## 2.8.8
|
package/index.d.ts
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
// blits file type reference
|
|
19
19
|
/// <reference path="./blits.d.ts" />
|
|
20
20
|
|
|
21
|
-
import {type ShaderEffect as RendererShaderEffect, type RendererMainSettings} from '@lightningjs/renderer'
|
|
21
|
+
import {type ShaderEffect as RendererShaderEffect, type RendererMainSettings, type FrameCounter} from '@lightningjs/renderer'
|
|
22
22
|
import { CanvasShaderType } from '@lightningjs/renderer/canvas';
|
|
23
23
|
import { WebGlShaderType } from '@lightningjs/renderer/webgl';
|
|
24
24
|
|
|
@@ -242,7 +242,7 @@ declare module '@lightningjs/blits' {
|
|
|
242
242
|
*
|
|
243
243
|
* Note: This event fire multiple times
|
|
244
244
|
*/
|
|
245
|
-
fpsUpdate?: (fps: number) => void;
|
|
245
|
+
fpsUpdate?: (fps: number, frameCounter: FrameCounter) => void;
|
|
246
246
|
}
|
|
247
247
|
|
|
248
248
|
export interface Input {
|
|
@@ -642,7 +642,9 @@ declare module '@lightningjs/blits' {
|
|
|
642
642
|
type InferProp<T> = T extends (...args: any[]) => any ? ReturnType<T> : T
|
|
643
643
|
|
|
644
644
|
type InferProps<T extends Record<string, any>> = {
|
|
645
|
-
|
|
645
|
+
// Since InferProps is no longer used for `Computed` props,
|
|
646
|
+
// then we do not need to infer the return type of a function-typed prop in `Props`.
|
|
647
|
+
[K in keyof T]: T[K]
|
|
646
648
|
}
|
|
647
649
|
|
|
648
650
|
type Computed<T extends Record<string, () => any>> = {
|
|
@@ -653,7 +655,7 @@ declare module '@lightningjs/blits' {
|
|
|
653
655
|
P extends Record<string, any>,
|
|
654
656
|
S,
|
|
655
657
|
M,
|
|
656
|
-
C extends Record<string, () => any>,
|
|
658
|
+
C extends Record<string, () => any> = Record<never, never>,
|
|
657
659
|
> = ThisType<
|
|
658
660
|
Readonly<InferProps<P>> & S & M & Computed<C> & ChildComponentBase
|
|
659
661
|
>
|
|
@@ -662,14 +664,14 @@ declare module '@lightningjs/blits' {
|
|
|
662
664
|
P extends Record<string, any>,
|
|
663
665
|
S,
|
|
664
666
|
M,
|
|
665
|
-
C,
|
|
667
|
+
C extends Record<string, () => any> = Record<never, never>,
|
|
666
668
|
> = ThisType<Readonly<InferProps<P>> & S & M & Computed<C> & ApplicationBase>
|
|
667
669
|
|
|
668
670
|
export interface ComponentConfig<
|
|
669
671
|
P extends Props = {},
|
|
670
672
|
S,
|
|
671
673
|
M,
|
|
672
|
-
C extends Record<string, () => any>,
|
|
674
|
+
C extends Record<string, () => any> = Record<never, never>,
|
|
673
675
|
W,
|
|
674
676
|
> {
|
|
675
677
|
components?: {
|
|
@@ -1408,6 +1410,14 @@ declare module '@lightningjs/blits' {
|
|
|
1408
1410
|
*/
|
|
1409
1411
|
enableMouse?: boolean,
|
|
1410
1412
|
/**
|
|
1413
|
+
* Minimum time in milliseconds between two pointer moves that are processed for hover.
|
|
1414
|
+
*
|
|
1415
|
+
* Moves arriving sooner are dropped, not delayed.
|
|
1416
|
+
*
|
|
1417
|
+
* @default 100
|
|
1418
|
+
*/
|
|
1419
|
+
mouseMoveThrottle?: number,
|
|
1420
|
+
/**
|
|
1411
1421
|
* Maximum FPS at which the App will be rendered
|
|
1412
1422
|
*
|
|
1413
1423
|
* Lowering the maximum FPS value can improve the overall experience on lower end devices.
|
|
@@ -1478,7 +1488,7 @@ declare module '@lightningjs/blits' {
|
|
|
1478
1488
|
P extends Props,
|
|
1479
1489
|
S extends State,
|
|
1480
1490
|
M extends Methods,
|
|
1481
|
-
C extends Computed,
|
|
1491
|
+
C extends Computed = Record<never, never>,
|
|
1482
1492
|
W extends Watch>(config: ApplicationConfig<P, S, M, C, W>) : ComponentFactory
|
|
1483
1493
|
/**
|
|
1484
1494
|
* Blits Component
|
|
@@ -1489,7 +1499,7 @@ declare module '@lightningjs/blits' {
|
|
|
1489
1499
|
P extends Props,
|
|
1490
1500
|
S extends State,
|
|
1491
1501
|
M extends Methods,
|
|
1492
|
-
C extends Computed,
|
|
1502
|
+
C extends Computed = Record<never, never>,
|
|
1493
1503
|
W extends Watch>(name: string, config: ComponentConfig<P, S, M, C, W>) : ComponentFactory
|
|
1494
1504
|
/**
|
|
1495
1505
|
* Blits Launch
|
|
@@ -1526,4 +1536,5 @@ declare module '@lightningjs/blits' {
|
|
|
1526
1536
|
const Blits: Blits;
|
|
1527
1537
|
|
|
1528
1538
|
export default Blits;
|
|
1539
|
+
|
|
1529
1540
|
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lightningjs/blits",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.10.0",
|
|
4
4
|
"description": "Blits: The Lightning 3 App Development Framework",
|
|
5
5
|
"bin": "bin/index.js",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": "./index.js",
|
|
8
|
+
"./colors": {
|
|
9
|
+
"types": "./src/lib/colors/htmlColors.d.ts",
|
|
10
|
+
"default": "./src/lib/colors/htmlColors.js"
|
|
11
|
+
},
|
|
8
12
|
"./platforms": {
|
|
9
13
|
"types": "./src/platforms.d.ts",
|
|
10
14
|
"default": "./src/platforms.js"
|
|
@@ -36,6 +40,10 @@
|
|
|
36
40
|
"types": "./src/platforms/webOS/announcer.d.ts",
|
|
37
41
|
"default": "./src/platforms/webOS/announcer.js"
|
|
38
42
|
},
|
|
43
|
+
"./platforms/dom/announcer": {
|
|
44
|
+
"types": "./src/platforms/dom/announcer.d.ts",
|
|
45
|
+
"default": "./src/platforms/dom/announcer.js"
|
|
46
|
+
},
|
|
39
47
|
"./testing": {
|
|
40
48
|
"types": "./src/testing/index.d.ts",
|
|
41
49
|
"default": "./src/testing/index.js"
|
|
@@ -66,28 +74,28 @@
|
|
|
66
74
|
"author": "Michiel van der Geest <hello@michielvandergeest.com>",
|
|
67
75
|
"license": "Apache-2.0",
|
|
68
76
|
"devDependencies": {
|
|
69
|
-
"@babel/eslint-parser": "^7.
|
|
70
|
-
"@babel/plugin-syntax-import-assertions": "^7.
|
|
71
|
-
"@eslint/eslintrc": "^3.3.
|
|
72
|
-
"@eslint/js": "^9.
|
|
73
|
-
"c8": "^
|
|
74
|
-
"eslint": "^9.
|
|
77
|
+
"@babel/eslint-parser": "^7.28.6",
|
|
78
|
+
"@babel/plugin-syntax-import-assertions": "^7.28.6",
|
|
79
|
+
"@eslint/eslintrc": "^3.3.4",
|
|
80
|
+
"@eslint/js": "^9.39.3",
|
|
81
|
+
"c8": "^10.1.3",
|
|
82
|
+
"eslint": "^9.39.3",
|
|
75
83
|
"eslint-config-prettier": "^10.1.8",
|
|
76
84
|
"eslint-plugin-prettier": "^5.5.4",
|
|
77
85
|
"fast-glob": "^3.3.3",
|
|
78
|
-
"global-jsdom": "
|
|
86
|
+
"global-jsdom": "^26.0.0",
|
|
79
87
|
"globals": "^16.3.0",
|
|
80
88
|
"husky": "^9.1.7",
|
|
81
|
-
"jsdom": "
|
|
82
|
-
"lint-staged": "^15.5.
|
|
89
|
+
"jsdom": "^26.0.0",
|
|
90
|
+
"lint-staged": "^15.5.2",
|
|
83
91
|
"prettier": "^3.6.2",
|
|
84
|
-
"sinon": "^21.0.
|
|
92
|
+
"sinon": "^21.0.1",
|
|
85
93
|
"tap-diff": "^0.1.1",
|
|
86
94
|
"tap-filter": "^1.0.3",
|
|
87
|
-
"tape": "^5.
|
|
95
|
+
"tape": "^5.9.0"
|
|
88
96
|
},
|
|
89
97
|
"dependencies": {
|
|
90
|
-
"@lightningjs/renderer": "3.
|
|
98
|
+
"@lightningjs/renderer": "3.5.0",
|
|
91
99
|
"magic-string": "^0.30.21"
|
|
92
100
|
},
|
|
93
101
|
"repository": {
|
|
@@ -81,15 +81,30 @@ const addToQueue = (message, politeness, delay = false, options = {}) => {
|
|
|
81
81
|
|
|
82
82
|
// setup a promise to allow developer to chain functionality
|
|
83
83
|
// when specific utterances are done
|
|
84
|
-
let
|
|
84
|
+
let resolvePromise
|
|
85
|
+
let settled = false
|
|
85
86
|
const done = new Promise((resolve) => {
|
|
86
|
-
|
|
87
|
+
resolvePromise = resolve
|
|
87
88
|
})
|
|
88
89
|
|
|
90
|
+
const resolveFn = (status) => {
|
|
91
|
+
if (settled === true) return
|
|
92
|
+
settled = true
|
|
93
|
+
resolvePromise(status)
|
|
94
|
+
}
|
|
95
|
+
|
|
89
96
|
// augment the promise with a cancel / remove function
|
|
90
97
|
done.remove = done.cancel = () => {
|
|
91
98
|
const index = queue.findIndex((item) => item.id === id)
|
|
92
|
-
if (index !== -1)
|
|
99
|
+
if (index !== -1) {
|
|
100
|
+
queue.splice(index, 1)
|
|
101
|
+
} else if (id === currentId && debounce !== null) {
|
|
102
|
+
clearDebounceTimer()
|
|
103
|
+
currentId = null
|
|
104
|
+
currentResolveFn = null
|
|
105
|
+
isProcessing = false
|
|
106
|
+
processQueue()
|
|
107
|
+
}
|
|
93
108
|
Log.debug(`Announcer - removed from queue: "${message}" (id: ${id})`)
|
|
94
109
|
resolveFn('canceled')
|
|
95
110
|
}
|
package/src/application.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* SPDX-License-Identifier: Apache-2.0
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import { default as Component
|
|
18
|
+
import { componentMap, default as Component } from './component.js'
|
|
19
19
|
import { default as Focus, keyUpCallbacks } from './focus/focus.js'
|
|
20
20
|
import Hover from './focus/hover.js'
|
|
21
21
|
|
|
@@ -80,6 +80,7 @@ const Application = (config) => {
|
|
|
80
80
|
const throttleMs = Settings.get('inputThrottle', 0)
|
|
81
81
|
|
|
82
82
|
const mouseEnabled = Settings.get('enableMouse', false)
|
|
83
|
+
const mouseMoveThrottle = Settings.get('mouseMoveThrottle', 100)
|
|
83
84
|
const { createKeyboardEvent, input, isKeyboardEvent, now = Date.now, viewport } = platform
|
|
84
85
|
|
|
85
86
|
keyDownHandler = async (e) => {
|
|
@@ -114,7 +115,11 @@ const Application = (config) => {
|
|
|
114
115
|
}
|
|
115
116
|
|
|
116
117
|
Focus.input(key, e)
|
|
117
|
-
if (mouseEnabled === true)
|
|
118
|
+
if (mouseEnabled === true) {
|
|
119
|
+
Hover.clear()
|
|
120
|
+
// forget the node under the pointer too, so moving on it hovers it again
|
|
121
|
+
currentNode = undefined
|
|
122
|
+
}
|
|
118
123
|
clearTimeout(holdTimeout)
|
|
119
124
|
holdTimeout = setTimeout(
|
|
120
125
|
() => {
|
|
@@ -125,10 +130,12 @@ const Application = (config) => {
|
|
|
125
130
|
}
|
|
126
131
|
|
|
127
132
|
keyUpHandler = (e) => {
|
|
128
|
-
const
|
|
129
|
-
if (
|
|
133
|
+
const entry = keyUpCallbacks.get(e.keyCode)
|
|
134
|
+
if (entry !== undefined) {
|
|
130
135
|
keyUpCallbacks.delete(e.keyCode)
|
|
131
|
-
|
|
136
|
+
if (entry.component.eol !== true && typeof entry.callback === 'function') {
|
|
137
|
+
entry.callback()
|
|
138
|
+
}
|
|
132
139
|
}
|
|
133
140
|
clearTimeout(holdTimeout)
|
|
134
141
|
Focus.hold = false
|
|
@@ -147,7 +154,7 @@ const Application = (config) => {
|
|
|
147
154
|
|
|
148
155
|
// limit the amount of move events per time frame
|
|
149
156
|
mouseMoveHandler = (e) => {
|
|
150
|
-
if (e.timeStamp - lastMoved <
|
|
157
|
+
if (e.timeStamp - lastMoved < mouseMoveThrottle) return
|
|
151
158
|
lastMoved = e.timeStamp
|
|
152
159
|
|
|
153
160
|
this.$emit('mouse::move', e)
|
|
@@ -16,7 +16,12 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import symbols from '../../lib/symbols.js'
|
|
19
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
default as Focus,
|
|
21
|
+
keyUpCallbacks,
|
|
22
|
+
getComponentWithInputEvent,
|
|
23
|
+
removeKeyUpCallbacks,
|
|
24
|
+
} from '../../focus/focus.js'
|
|
20
25
|
import eventListeners from '../../lib/eventListeners.js'
|
|
21
26
|
import { trigger } from '../../lib/reactivity/effect.js'
|
|
22
27
|
import { Log } from '../../lib/log.js'
|
|
@@ -71,7 +76,7 @@ export default {
|
|
|
71
76
|
}
|
|
72
77
|
|
|
73
78
|
if (cb !== undefined && event.keyCode) {
|
|
74
|
-
keyUpCallbacks.set(event.keyCode, cb)
|
|
79
|
+
keyUpCallbacks.set(event.keyCode, { callback: cb, component: componentWithInputEvent })
|
|
75
80
|
}
|
|
76
81
|
|
|
77
82
|
return true
|
|
@@ -98,14 +103,15 @@ export default {
|
|
|
98
103
|
*/
|
|
99
104
|
value: function () {
|
|
100
105
|
this.eol = true
|
|
106
|
+
removeKeyUpCallbacks(this)
|
|
101
107
|
this[symbols.lifecycle].state = 'destroy'
|
|
102
108
|
|
|
109
|
+
removeEffects(this[symbols.effects])
|
|
110
|
+
|
|
103
111
|
// when destroying a component that currently has focus
|
|
104
112
|
// pass focus to the parent so we don't get lost in focus limbo
|
|
105
113
|
if (this.$hasFocus === true) this[symbols.parent].$focus()
|
|
106
114
|
|
|
107
|
-
removeEffects(this[symbols.effects])
|
|
108
|
-
|
|
109
115
|
// @todo - is this really necessary?
|
|
110
116
|
// This cause an issue with auto sizing of parent (and required an extra eol check there)
|
|
111
117
|
for (let key in this[symbols.state]) {
|
|
@@ -275,7 +275,9 @@ const propsTransformer = {
|
|
|
275
275
|
this.props['zIndex'] = v
|
|
276
276
|
},
|
|
277
277
|
set color(v) {
|
|
278
|
-
if (typeof v === '
|
|
278
|
+
if (typeof v === 'number') {
|
|
279
|
+
this.props['color'] = v
|
|
280
|
+
} else if (typeof v === 'string' && v.startsWith('{') === false) {
|
|
279
281
|
this.props['color'] = colors.normalize(v)
|
|
280
282
|
} else if (typeof v === 'object' || (isObjectString(v) === true && (v = parseToObject(v)))) {
|
|
281
283
|
this.props['color'] = 0
|
|
@@ -649,10 +651,11 @@ const Element = {
|
|
|
649
651
|
if (this.props.props['color'] === undefined && '__textnode' in props === false) {
|
|
650
652
|
this.props.props['color'] = 0
|
|
651
653
|
}
|
|
652
|
-
|
|
653
|
-
this.node =
|
|
654
|
-
|
|
655
|
-
|
|
654
|
+
const isTextNode = props.__textnode
|
|
655
|
+
this.node =
|
|
656
|
+
isTextNode === true
|
|
657
|
+
? renderer.createTextNode({ ...textDefaults, ...this.props.props })
|
|
658
|
+
: renderer.createNode(this.props.props)
|
|
656
659
|
|
|
657
660
|
if (this.props['holder'] === true) {
|
|
658
661
|
holderComponentMap.set(this.node, this.component)
|
|
@@ -680,7 +683,8 @@ const Element = {
|
|
|
680
683
|
|
|
681
684
|
if (this.config.parent.props !== undefined && this.config.parent.props.__layout === true) {
|
|
682
685
|
this.config.parent.triggerLayout(this.config.parent.props)
|
|
683
|
-
|
|
686
|
+
|
|
687
|
+
this.node.on(isTextNode === true ? 'textCalculated' : 'loaded', () => {
|
|
684
688
|
if (this.eol === true) return
|
|
685
689
|
this.config.parent.triggerLayout(this.config.parent.props)
|
|
686
690
|
})
|
|
@@ -809,6 +813,20 @@ const Element = {
|
|
|
809
813
|
// @ts-ignore
|
|
810
814
|
this.props.raw[prop] = value
|
|
811
815
|
|
|
816
|
+
if (prop === 'color' && typeof value === 'number') {
|
|
817
|
+
// A gradient can share its base color with the new solid color (notably zero).
|
|
818
|
+
// The renderer skips equal base colors, so reset its sides in that case.
|
|
819
|
+
if (this.node.color === value) {
|
|
820
|
+
this.node.colorTop = value
|
|
821
|
+
this.node.colorBottom = value
|
|
822
|
+
this.node.colorLeft = value
|
|
823
|
+
this.node.colorRight = value
|
|
824
|
+
}
|
|
825
|
+
// Use the renderer setter to invalidate colors, including text colors.
|
|
826
|
+
this.node.color = value
|
|
827
|
+
return
|
|
828
|
+
}
|
|
829
|
+
|
|
812
830
|
this.props.props = {}
|
|
813
831
|
// @ts-ignore
|
|
814
832
|
this.props[prop] = unpackTransition(value)
|
|
@@ -908,6 +926,7 @@ const Element = {
|
|
|
908
926
|
if (transition.start !== undefined && typeof transition.start === 'function') {
|
|
909
927
|
// fire transition start callback when animation really starts (depending on specified delay)
|
|
910
928
|
f.once('animating', () => {
|
|
929
|
+
if (this.eol === true) return
|
|
911
930
|
transition.start.call(this.component, this, prop, startValue)
|
|
912
931
|
})
|
|
913
932
|
}
|
|
@@ -929,6 +948,7 @@ const Element = {
|
|
|
929
948
|
}
|
|
930
949
|
|
|
931
950
|
f.once('stopped', () => {
|
|
951
|
+
if (this.eol === true) return
|
|
932
952
|
if (
|
|
933
953
|
this.scheduledTransitions[prop] !== undefined &&
|
|
934
954
|
this.scheduledTransitions[prop].canceled === true
|
|
@@ -936,7 +956,7 @@ const Element = {
|
|
|
936
956
|
return
|
|
937
957
|
}
|
|
938
958
|
// fire transition end callback when animation ends (if specified)
|
|
939
|
-
if (
|
|
959
|
+
if (transition.end && typeof transition.end === 'function') {
|
|
940
960
|
transition.end.call(this.component, this, prop, this.node[prop])
|
|
941
961
|
}
|
|
942
962
|
// remove the prop from scheduled transitions
|
|
@@ -1010,7 +1030,7 @@ const Element = {
|
|
|
1010
1030
|
delete this.forComponent
|
|
1011
1031
|
|
|
1012
1032
|
this.node.destroy()
|
|
1013
|
-
this.node =
|
|
1033
|
+
this.node = undefined
|
|
1014
1034
|
},
|
|
1015
1035
|
get nodeId() {
|
|
1016
1036
|
return this.node && this.node.id
|
package/src/focus/focus.js
CHANGED
|
@@ -29,6 +29,12 @@ let setFocusTimeout
|
|
|
29
29
|
|
|
30
30
|
export const keyUpCallbacks = new Map()
|
|
31
31
|
|
|
32
|
+
export const removeKeyUpCallbacks = (component) => {
|
|
33
|
+
for (const [keyCode, entry] of keyUpCallbacks) {
|
|
34
|
+
if (entry.component === component) keyUpCallbacks.delete(keyCode)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
32
38
|
export default {
|
|
33
39
|
_hold: false,
|
|
34
40
|
set hold(v) {
|
|
@@ -99,7 +105,7 @@ export default {
|
|
|
99
105
|
}
|
|
100
106
|
|
|
101
107
|
if (cb !== undefined) {
|
|
102
|
-
keyUpCallbacks.set(event.keyCode, cb)
|
|
108
|
+
keyUpCallbacks.set(event.keyCode, { callback: cb, component: componentWithInputEvent })
|
|
103
109
|
}
|
|
104
110
|
},
|
|
105
111
|
}
|
package/src/helpers/fetchJson.js
CHANGED
|
@@ -22,10 +22,18 @@ export default (file) => {
|
|
|
22
22
|
if (xhr.readyState == XMLHttpRequest.DONE) {
|
|
23
23
|
// file protocol returns 0
|
|
24
24
|
// http(s) protocol returns 200
|
|
25
|
-
if (xhr.status === 0 || xhr.status === 200)
|
|
26
|
-
|
|
25
|
+
if (xhr.status === 0 || xhr.status === 200) {
|
|
26
|
+
try {
|
|
27
|
+
resolve(JSON.parse(xhr.responseText))
|
|
28
|
+
} catch (err) {
|
|
29
|
+
reject(err)
|
|
30
|
+
}
|
|
31
|
+
} else reject(xhr.statusText || xhr.status)
|
|
27
32
|
}
|
|
28
33
|
}
|
|
34
|
+
|
|
35
|
+
xhr.onerror = () => reject(new Error('Network request failed'))
|
|
36
|
+
|
|
29
37
|
xhr.open('GET', file)
|
|
30
38
|
xhr.send(null)
|
|
31
39
|
})
|
package/src/lib/colors/colors.js
CHANGED
|
@@ -15,7 +15,9 @@
|
|
|
15
15
|
* SPDX-License-Identifier: Apache-2.0
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import
|
|
18
|
+
import htmlColors from './htmlColors.js'
|
|
19
|
+
|
|
20
|
+
const colorsMap = { ...htmlColors }
|
|
19
21
|
|
|
20
22
|
const hex = /^#?([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i
|
|
21
23
|
const rgba =
|
|
@@ -18,14 +18,17 @@
|
|
|
18
18
|
let currentEffect = null
|
|
19
19
|
let currentKey = null
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
// counter that tracks all pauseTracking invocations
|
|
22
|
+
// to ensure nested array operations don't prematurely
|
|
23
|
+
// resume before all operations have completed
|
|
24
|
+
let pauseCounter = 0
|
|
22
25
|
|
|
23
26
|
export const pauseTracking = () => {
|
|
24
|
-
|
|
27
|
+
pauseCounter++
|
|
25
28
|
}
|
|
26
29
|
|
|
27
30
|
export const resumeTracking = () => {
|
|
28
|
-
|
|
31
|
+
if (pauseCounter > 0) pauseCounter--
|
|
29
32
|
}
|
|
30
33
|
|
|
31
34
|
const objectMap = new WeakMap()
|
|
@@ -45,7 +48,7 @@ export const removeEffects = (effectsToRemove) => {
|
|
|
45
48
|
|
|
46
49
|
export const track = (target, key) => {
|
|
47
50
|
if (currentEffect !== null) {
|
|
48
|
-
if (
|
|
51
|
+
if (pauseCounter > 0) {
|
|
49
52
|
return
|
|
50
53
|
}
|
|
51
54
|
// note: nesting the conditions like this seems to perform better ¯\_(ツ)_/¯
|
|
@@ -76,7 +79,7 @@ export const track = (target, key) => {
|
|
|
76
79
|
}
|
|
77
80
|
|
|
78
81
|
export const trigger = (target, key, force = false) => {
|
|
79
|
-
if (
|
|
82
|
+
if (pauseCounter > 0) return
|
|
80
83
|
const effectsMap = objectMap.get(target)
|
|
81
84
|
if (effectsMap === undefined) {
|
|
82
85
|
return
|
|
@@ -89,10 +92,17 @@ export const trigger = (target, key, force = false) => {
|
|
|
89
92
|
}
|
|
90
93
|
}
|
|
91
94
|
|
|
92
|
-
export const effect = (
|
|
93
|
-
|
|
95
|
+
export const effect = (effectFn, key = null) => {
|
|
96
|
+
const previousEffect = currentEffect
|
|
97
|
+
const previousKey = currentKey
|
|
98
|
+
|
|
99
|
+
currentEffect = effectFn
|
|
94
100
|
currentKey = key
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
effectFn()
|
|
104
|
+
} finally {
|
|
105
|
+
currentEffect = previousEffect
|
|
106
|
+
currentKey = previousKey
|
|
107
|
+
}
|
|
98
108
|
}
|
|
@@ -67,9 +67,19 @@ const reactiveProxy = (original, _parent = null, _key) => {
|
|
|
67
67
|
// augment array path methods (that change the length of the array)
|
|
68
68
|
if (arrayPatchMethods.indexOf(key) !== -1) {
|
|
69
69
|
return function (...args) {
|
|
70
|
+
const oldLength = target.length
|
|
70
71
|
pauseTracking()
|
|
71
|
-
|
|
72
|
-
|
|
72
|
+
let result
|
|
73
|
+
try {
|
|
74
|
+
result = target[key].apply(this, args)
|
|
75
|
+
} finally {
|
|
76
|
+
resumeTracking()
|
|
77
|
+
}
|
|
78
|
+
// trigger a change when the length of the array has changed
|
|
79
|
+
// by the array operation
|
|
80
|
+
if (_parent === null && target.length !== oldLength) {
|
|
81
|
+
trigger(target, 'length')
|
|
82
|
+
}
|
|
73
83
|
// trigger a change on the parent object and the key
|
|
74
84
|
// i.e. when pushing a new item to `obj.data`, _parent will equal `obj`
|
|
75
85
|
// and _key will equal `data`
|
|
@@ -78,6 +88,10 @@ const reactiveProxy = (original, _parent = null, _key) => {
|
|
|
78
88
|
}
|
|
79
89
|
}
|
|
80
90
|
if (key === 'length') {
|
|
91
|
+
// only track root level to prevent double reactive executions
|
|
92
|
+
if (_parent === null) {
|
|
93
|
+
track(target, 'length')
|
|
94
|
+
}
|
|
81
95
|
return original.length
|
|
82
96
|
}
|
|
83
97
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { AnnouncerDriver } from '@lightningjs/blits'
|
|
2
|
+
|
|
3
|
+
export interface DOMAnnouncerOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Document in which the live region is created. Defaults to the global document.
|
|
6
|
+
*/
|
|
7
|
+
document?: Document | null
|
|
8
|
+
/**
|
|
9
|
+
* Element that receives the live region. Defaults to document.body.
|
|
10
|
+
*/
|
|
11
|
+
container?: HTMLElement
|
|
12
|
+
/**
|
|
13
|
+
* ID used to find or create the single live region.
|
|
14
|
+
*
|
|
15
|
+
* @default 'blits-announcer'
|
|
16
|
+
*/
|
|
17
|
+
id?: string
|
|
18
|
+
/**
|
|
19
|
+
* Delay between clearing the region and inserting the next message.
|
|
20
|
+
*
|
|
21
|
+
* @default 0
|
|
22
|
+
*/
|
|
23
|
+
updateDelay?: number
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
declare function createAnnouncer(options?: DOMAnnouncerOptions): AnnouncerDriver
|
|
27
|
+
|
|
28
|
+
export default createAnnouncer
|
|
@@ -0,0 +1,111 @@
|
|
|
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 defaultId = 'blits-announcer'
|
|
20
|
+
|
|
21
|
+
const visuallyHiddenStyles = {
|
|
22
|
+
position: 'absolute',
|
|
23
|
+
width: '1px',
|
|
24
|
+
height: '1px',
|
|
25
|
+
padding: '0',
|
|
26
|
+
margin: '-1px',
|
|
27
|
+
overflow: 'hidden',
|
|
28
|
+
clip: 'rect(0, 0, 0, 0)',
|
|
29
|
+
whiteSpace: 'nowrap',
|
|
30
|
+
border: '0',
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const createAnnouncer = (options = {}) => {
|
|
34
|
+
const documentRef = Object.prototype.hasOwnProperty.call(options, 'document')
|
|
35
|
+
? options.document
|
|
36
|
+
: globalScope.document
|
|
37
|
+
const id = options.id || defaultId
|
|
38
|
+
const updateDelay = options.updateDelay === undefined ? 0 : options.updateDelay
|
|
39
|
+
|
|
40
|
+
let region
|
|
41
|
+
let pendingUpdate
|
|
42
|
+
|
|
43
|
+
const getRegion = () => {
|
|
44
|
+
if (!documentRef || typeof documentRef.createElement !== 'function') return
|
|
45
|
+
if (region && region.isConnected !== false) return region
|
|
46
|
+
|
|
47
|
+
region = typeof documentRef.getElementById === 'function' && documentRef.getElementById(id)
|
|
48
|
+
|
|
49
|
+
if (!region) {
|
|
50
|
+
const parent = options.container || documentRef.body || documentRef.documentElement
|
|
51
|
+
if (!parent || typeof parent.appendChild !== 'function') return
|
|
52
|
+
|
|
53
|
+
region = documentRef.createElement('div')
|
|
54
|
+
region.id = id
|
|
55
|
+
parent.appendChild(region)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
Object.assign(region.style, visuallyHiddenStyles)
|
|
59
|
+
region.setAttribute('role', 'alert')
|
|
60
|
+
region.setAttribute('aria-live', 'assertive')
|
|
61
|
+
region.setAttribute('aria-atomic', 'true')
|
|
62
|
+
|
|
63
|
+
return region
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const cancelPendingUpdate = (error = 'canceled') => {
|
|
67
|
+
if (pendingUpdate === undefined) return
|
|
68
|
+
clearTimeout(pendingUpdate.timer)
|
|
69
|
+
pendingUpdate.reject({ error })
|
|
70
|
+
pendingUpdate = undefined
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Create the empty region before the first announcement so assistive
|
|
74
|
+
// technology can observe subsequent content changes reliably.
|
|
75
|
+
getRegion()
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
speak(announcement) {
|
|
79
|
+
if (!announcement || announcement.message === undefined || announcement.message === null) {
|
|
80
|
+
return Promise.reject({ error: 'Missing message' })
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const liveRegion = getRegion()
|
|
84
|
+
if (!liveRegion) {
|
|
85
|
+
return Promise.reject({ error: 'unavailable' })
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
cancelPendingUpdate()
|
|
89
|
+
liveRegion.textContent = ''
|
|
90
|
+
|
|
91
|
+
return new Promise((resolve, reject) => {
|
|
92
|
+
const update = {
|
|
93
|
+
reject,
|
|
94
|
+
timer: setTimeout(() => {
|
|
95
|
+
if (pendingUpdate !== update) return
|
|
96
|
+
liveRegion.textContent = String(announcement.message)
|
|
97
|
+
pendingUpdate = undefined
|
|
98
|
+
resolve()
|
|
99
|
+
}, updateDelay),
|
|
100
|
+
}
|
|
101
|
+
pendingUpdate = update
|
|
102
|
+
})
|
|
103
|
+
},
|
|
104
|
+
cancel() {
|
|
105
|
+
cancelPendingUpdate()
|
|
106
|
+
if (region) region.textContent = ''
|
|
107
|
+
},
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export default createAnnouncer
|
package/src/plugins/language.js
CHANGED
|
@@ -55,7 +55,7 @@ export default {
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
const setDict = (language) => {
|
|
58
|
-
return new Promise((resolve) => {
|
|
58
|
+
return new Promise((resolve, reject) => {
|
|
59
59
|
const translationObj = translations[language]
|
|
60
60
|
if (typeof translationObj === 'object') {
|
|
61
61
|
dictionary = translationObj
|
|
@@ -71,6 +71,7 @@ export default {
|
|
|
71
71
|
})
|
|
72
72
|
.catch((e) => {
|
|
73
73
|
Log.error(e)
|
|
74
|
+
reject(e)
|
|
74
75
|
})
|
|
75
76
|
}
|
|
76
77
|
})
|