@lightningjs/blits 1.32.1 → 1.34.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 +21 -0
- package/NOTICE +4 -0
- package/index.d.ts +13 -2
- package/package.json +4 -2
- package/src/announcer/announcer.js +1 -0
- package/src/announcer/speechSynthesis.js +21 -4
- package/src/application.js +5 -1
- package/src/component/base/methods.js +5 -0
- package/src/component/setup/routes.js +9 -12
- package/src/component/setup/state.js +8 -2
- package/src/component.js +12 -10
- package/src/components/Circle.js +30 -8
- package/src/components/FPScounter.js +643 -493
- package/src/components/RouterView.js +28 -6
- package/src/components/Sprite.js +30 -8
- package/src/engines/L3/launch.js +3 -2
- package/src/launch.js +1 -0
- package/src/lib/codegenerator/generator.js +84 -40
- package/src/lib/log.js +6 -6
- package/src/lib/symbols.js +2 -0
- package/src/plugins/index.js +1 -0
- package/src/plugins/storage/localCookie.js +133 -0
- package/src/plugins/storage/storage.js +49 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
_10 Jul 2025_
|
|
4
|
+
|
|
5
|
+
## v1.34.0
|
|
6
|
+
|
|
7
|
+
- Added storage plugin
|
|
8
|
+
- Improved test coverage
|
|
9
|
+
- Fixed announcer plugin to work on RDK devices where early GC was happening
|
|
10
|
+
- Added check to throttle only the same input key in a row
|
|
11
|
+
- Updated docs on transitions
|
|
12
|
+
- Added warning when using `hasFocus` in component state
|
|
13
|
+
|
|
14
|
+
_08 Jul 2025_
|
|
15
|
+
|
|
16
|
+
## v1.33.0
|
|
17
|
+
|
|
18
|
+
- Added dereferencing of previousFocus pointer to ensure memory cleanup
|
|
19
|
+
- Improved cleanup of components (via explicit dereference of closure scope)
|
|
20
|
+
- Optimized time function in logger
|
|
21
|
+
- Optimized initial registration of routes
|
|
22
|
+
- Added `maxFPS` launch setting
|
|
23
|
+
|
|
3
24
|
_04 Jul 2025_
|
|
4
25
|
|
|
5
26
|
## v1.32.1
|
package/NOTICE
CHANGED
|
@@ -10,3 +10,7 @@ within this component.
|
|
|
10
10
|
This repository uses components from the Lightning-SDK. Any components from the Lightning-SDK are:
|
|
11
11
|
Copyright (c) 2020 Metrological
|
|
12
12
|
Licensed under the Apache License, Version 2.0
|
|
13
|
+
|
|
14
|
+
This repository uses components from WebPlatformForEmbedded
|
|
15
|
+
Copyright (c) 2019 Metrological
|
|
16
|
+
Licensed under the Apache License, Version 2.0
|
package/index.d.ts
CHANGED
|
@@ -970,8 +970,10 @@ declare module '@lightningjs/blits' {
|
|
|
970
970
|
/**
|
|
971
971
|
* Input throttle time in milliseconds to prevent rapid successive inputs
|
|
972
972
|
*
|
|
973
|
-
* Within the throttle window, only one input will be processed immediately
|
|
974
|
-
* inputs are ignored until the scheduled input is processed.
|
|
973
|
+
* Within the throttle window, only one input will be processed immediately.
|
|
974
|
+
* Subsequent inputs _of the same key_ are ignored until the scheduled input is processed.
|
|
975
|
+
*
|
|
976
|
+
* Pressing a different key will be processed immediately.
|
|
975
977
|
*
|
|
976
978
|
* Set to `0` to disable input throttling.
|
|
977
979
|
*
|
|
@@ -1015,6 +1017,15 @@ declare module '@lightningjs/blits' {
|
|
|
1015
1017
|
* @default false
|
|
1016
1018
|
*/
|
|
1017
1019
|
announcer?: boolean
|
|
1020
|
+
/**
|
|
1021
|
+
* Maximum FPS at which the App will be rendered
|
|
1022
|
+
*
|
|
1023
|
+
* Lowering the maximum FPS value can improve the overall experience on lower end devices.
|
|
1024
|
+
* Targetting a lower FPS may gives the CPU more time to construct each frame leading to a smoother rendering.
|
|
1025
|
+
*
|
|
1026
|
+
* Defaults to `0` which means no maximum
|
|
1027
|
+
*/
|
|
1028
|
+
maxFPS?: number
|
|
1018
1029
|
}
|
|
1019
1030
|
|
|
1020
1031
|
interface State {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lightningjs/blits",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.34.0",
|
|
4
4
|
"description": "Blits: The Lightning 3 App Development Framework",
|
|
5
5
|
"bin": "bin/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
"./blitsFileConverter": "./src/lib/blitsfileconverter/blitsfileconverter.js"
|
|
14
14
|
},
|
|
15
15
|
"scripts": {
|
|
16
|
-
"test": "c8
|
|
16
|
+
"test": "c8 node scripts/runTests.js",
|
|
17
|
+
"test:ci": "c8 npm run test:run",
|
|
17
18
|
"test:run": "node -r global-jsdom/register ./node_modules/.bin/tape '{,!(node_modules|packages)/**/}*.test.js' | tap-diff",
|
|
18
19
|
"lint": "eslint '**/*.js'",
|
|
19
20
|
"lint:fix": "eslint '**/*.js' --fix",
|
|
@@ -38,6 +39,7 @@
|
|
|
38
39
|
"eslint": "^8.8.0",
|
|
39
40
|
"eslint-config-prettier": "^8.3.0",
|
|
40
41
|
"eslint-plugin-prettier": "^4.0.0",
|
|
42
|
+
"fast-glob": "^3.3.3",
|
|
41
43
|
"global-jsdom": "24.0.0",
|
|
42
44
|
"husky": "^9.1.7",
|
|
43
45
|
"jsdom": "24.0.0",
|
|
@@ -21,9 +21,17 @@ const syn = window.speechSynthesis
|
|
|
21
21
|
|
|
22
22
|
const isAndroid = /android/i.test((window.navigator || {}).userAgent || '')
|
|
23
23
|
|
|
24
|
+
const utterances = new Map() // Strong references with unique keys
|
|
25
|
+
|
|
24
26
|
let initialized = false
|
|
25
27
|
let infinityTimer = null
|
|
26
|
-
|
|
28
|
+
|
|
29
|
+
const clear = () => {
|
|
30
|
+
if (infinityTimer) {
|
|
31
|
+
clearTimeout(infinityTimer)
|
|
32
|
+
infinityTimer = null
|
|
33
|
+
}
|
|
34
|
+
}
|
|
27
35
|
|
|
28
36
|
const resumeInfinity = (target) => {
|
|
29
37
|
if (!target || infinityTimer) {
|
|
@@ -31,7 +39,9 @@ const resumeInfinity = (target) => {
|
|
|
31
39
|
}
|
|
32
40
|
|
|
33
41
|
syn.pause()
|
|
34
|
-
|
|
42
|
+
setTimeout(() => {
|
|
43
|
+
syn.resume()
|
|
44
|
+
}, 0)
|
|
35
45
|
|
|
36
46
|
infinityTimer = setTimeout(() => {
|
|
37
47
|
resumeInfinity(target)
|
|
@@ -47,18 +57,20 @@ const defaultUtteranceProps = {
|
|
|
47
57
|
}
|
|
48
58
|
|
|
49
59
|
const initialize = () => {
|
|
50
|
-
|
|
60
|
+
const voices = syn.getVoices()
|
|
61
|
+
defaultUtteranceProps.voice = voices[0] || null
|
|
51
62
|
initialized = true
|
|
52
63
|
}
|
|
53
64
|
|
|
54
65
|
const speak = (options) => {
|
|
55
66
|
const utterance = new SpeechSynthesisUtterance(options.message)
|
|
56
|
-
|
|
67
|
+
const id = Date.now() + Math.random() // Unique ID for tracking
|
|
57
68
|
utterance.lang = options.lang || defaultUtteranceProps.lang
|
|
58
69
|
utterance.pitch = options.pitch || defaultUtteranceProps.pitch
|
|
59
70
|
utterance.rate = options.rate || defaultUtteranceProps.rate
|
|
60
71
|
utterance.voice = options.voice || defaultUtteranceProps.voice
|
|
61
72
|
utterance.volume = options.volume || defaultUtteranceProps.volume
|
|
73
|
+
utterances.set(id, utterance) // Strong reference
|
|
62
74
|
|
|
63
75
|
if (isAndroid === false) {
|
|
64
76
|
utterance.onstart = () => {
|
|
@@ -72,9 +84,14 @@ const speak = (options) => {
|
|
|
72
84
|
|
|
73
85
|
return new Promise((resolve, reject) => {
|
|
74
86
|
utterance.onend = () => {
|
|
87
|
+
clear()
|
|
88
|
+
utterances.delete(id) // Cleanup
|
|
75
89
|
resolve()
|
|
76
90
|
}
|
|
91
|
+
|
|
77
92
|
utterance.onerror = (e) => {
|
|
93
|
+
clear()
|
|
94
|
+
utterances.delete(id) // Cleanup
|
|
78
95
|
reject(e)
|
|
79
96
|
}
|
|
80
97
|
|
package/src/application.js
CHANGED
|
@@ -48,6 +48,7 @@ const Application = (config) => {
|
|
|
48
48
|
let keyUpHandler
|
|
49
49
|
let holdTimeout
|
|
50
50
|
let lastInputTime = 0
|
|
51
|
+
let lastInputKey = null
|
|
51
52
|
|
|
52
53
|
config.hooks[symbols.destroy] = function () {
|
|
53
54
|
document.removeEventListener('keydown', keyDownHandler)
|
|
@@ -66,8 +67,11 @@ const Application = (config) => {
|
|
|
66
67
|
const currentTime = performance.now()
|
|
67
68
|
|
|
68
69
|
const key = keyMap[e.key] || keyMap[e.keyCode] || e.key || e.keyCode
|
|
70
|
+
const sameKey = lastInputKey === key
|
|
71
|
+
lastInputKey = key
|
|
69
72
|
// execute immediately when no throttle is specified or event is internal (bubbled up by focus manager)
|
|
70
|
-
|
|
73
|
+
// or key is different from the last used key
|
|
74
|
+
if (throttleMs === 0 || e[symbols.internalEvent] === true || sameKey === false) {
|
|
71
75
|
return await processInput.call(this, e, key)
|
|
72
76
|
}
|
|
73
77
|
|
|
@@ -40,6 +40,7 @@ export default {
|
|
|
40
40
|
* @this {import('../../component').BlitsComponent}
|
|
41
41
|
*/
|
|
42
42
|
value: function (e) {
|
|
43
|
+
if (this.eol === true) return
|
|
43
44
|
this[symbols.state].hasFocus = true
|
|
44
45
|
Focus.set(this, e)
|
|
45
46
|
},
|
|
@@ -103,11 +104,15 @@ export default {
|
|
|
103
104
|
delete this.componentId
|
|
104
105
|
delete this[symbols.id]
|
|
105
106
|
delete this.ref
|
|
107
|
+
delete this[symbols.state].hasFocus
|
|
106
108
|
|
|
107
109
|
this[symbols.holder].destroy()
|
|
108
110
|
this[symbols.holder] = null
|
|
109
111
|
delete this[symbols.holder]
|
|
110
112
|
|
|
113
|
+
this[symbols.cleanup]()
|
|
114
|
+
delete this[symbols.cleanup]
|
|
115
|
+
|
|
111
116
|
Log.debug(`Destroyed component ${this.componentId}`)
|
|
112
117
|
},
|
|
113
118
|
writable: false,
|
|
@@ -24,19 +24,16 @@ export default (component, data) => {
|
|
|
24
24
|
routes = data.routes
|
|
25
25
|
}
|
|
26
26
|
component[symbols.routes] = []
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
|
|
28
|
+
const l = routes.length
|
|
29
|
+
for (let i = 0; i < l; i++) {
|
|
30
|
+
component[symbols.routes][i] = routes[i]
|
|
31
|
+
// merge default route options with route specific options
|
|
32
|
+
component[symbols.routes][i].options = {
|
|
31
33
|
...{
|
|
32
|
-
|
|
33
|
-
options: {
|
|
34
|
-
...{
|
|
35
|
-
inHistory: true,
|
|
36
|
-
},
|
|
37
|
-
...routes[key].options,
|
|
38
|
-
},
|
|
34
|
+
inHistory: true,
|
|
39
35
|
},
|
|
36
|
+
...routes[i].options,
|
|
40
37
|
}
|
|
41
|
-
}
|
|
38
|
+
}
|
|
42
39
|
}
|
|
@@ -28,8 +28,14 @@ export default (component, state = () => {}) => {
|
|
|
28
28
|
})
|
|
29
29
|
|
|
30
30
|
const stateKeys = Object.keys(state.apply(component) || {})
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
if (stateKeys.indexOf('hasFocus') > -1) {
|
|
32
|
+
Log.warn(
|
|
33
|
+
'State `hasFocus` already exists as a built-in Component variable (to indicate whether the component currently has focus). Avoid using your own `hasFocus` key in the Component state'
|
|
34
|
+
)
|
|
35
|
+
} else {
|
|
36
|
+
// add built-in hasFocus key
|
|
37
|
+
stateKeys.push(['hasFocus'])
|
|
38
|
+
}
|
|
33
39
|
const stateKeysLength = stateKeys.length
|
|
34
40
|
|
|
35
41
|
for (let i = 0; i < stateKeysLength; i++) {
|
package/src/component.js
CHANGED
|
@@ -243,16 +243,18 @@ const Component = (name = required('name'), config = required('config')) => {
|
|
|
243
243
|
|
|
244
244
|
// execute the render code that constructs the initial state of the component
|
|
245
245
|
// and store the children result (a flat map of elements and components)
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
246
|
+
const { elms, cleanup } = config.code.render.apply(stage, [
|
|
247
|
+
parentEl,
|
|
248
|
+
this,
|
|
249
|
+
config,
|
|
250
|
+
globalComponents,
|
|
251
|
+
effect,
|
|
252
|
+
getRaw,
|
|
253
|
+
Log,
|
|
254
|
+
]) || { elms: [], cleanup: () => {} }
|
|
255
|
+
|
|
256
|
+
this[symbols.children] = elms
|
|
257
|
+
this[symbols.cleanup] = cleanup
|
|
256
258
|
|
|
257
259
|
// create a reference to the wrapper element of the component (i.e. the root Element of the component)
|
|
258
260
|
this[symbols.wrapper] = this[symbols.children][0]
|
package/src/components/Circle.js
CHANGED
|
@@ -23,30 +23,52 @@ export default () =>
|
|
|
23
23
|
code: {
|
|
24
24
|
render: function anonymous(parent, component, context, components, effect, getRaw, Log) {
|
|
25
25
|
const elms = []
|
|
26
|
+
const elementConfigs = []
|
|
27
|
+
const forloops = []
|
|
28
|
+
const props = []
|
|
29
|
+
const created = []
|
|
30
|
+
const skips = []
|
|
26
31
|
let componentType
|
|
27
|
-
|
|
32
|
+
let rootComponent = component
|
|
28
33
|
let propData
|
|
29
34
|
let slotComponent
|
|
30
35
|
let inSlot = false
|
|
31
36
|
let slotChildCounter = 0
|
|
32
|
-
|
|
37
|
+
let cmps = []
|
|
38
|
+
elementConfigs[0] = {}
|
|
33
39
|
|
|
34
40
|
elms[0] = this.element(
|
|
35
41
|
{ parent: parent || 'root' },
|
|
36
42
|
inSlot === true ? slotComponent : component
|
|
37
43
|
)
|
|
38
44
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
elms[0].populate(
|
|
45
|
+
elementConfigs[0]['color'] = component.color
|
|
46
|
+
elementConfigs[0]['w'] = component.size
|
|
47
|
+
elementConfigs[0]['h'] = component.size
|
|
48
|
+
elementConfigs[0]['effects'] = [component.shader('radius', { radius: component.radius })]
|
|
49
|
+
elms[0].populate(elementConfigs[0])
|
|
44
50
|
|
|
45
51
|
if (inSlot === true) {
|
|
46
52
|
slotChildCounter -= 1
|
|
47
53
|
}
|
|
48
54
|
|
|
49
|
-
return
|
|
55
|
+
return {
|
|
56
|
+
elms,
|
|
57
|
+
cleanup: () => {
|
|
58
|
+
rootComponent = null
|
|
59
|
+
propData = null
|
|
60
|
+
slotComponent = null
|
|
61
|
+
component = null
|
|
62
|
+
parent = null
|
|
63
|
+
cmps.length = 0
|
|
64
|
+
elms.length = 0
|
|
65
|
+
components.length = 0
|
|
66
|
+
elementConfigs.length = 0
|
|
67
|
+
forloops.length = 0
|
|
68
|
+
props.length = 0
|
|
69
|
+
skips.length = 0
|
|
70
|
+
},
|
|
71
|
+
}
|
|
50
72
|
},
|
|
51
73
|
effects: [
|
|
52
74
|
function anonymous(component, elms, context, components, rootComponent, effect) {
|