@lightningjs/blits 1.4.3 → 1.5.1
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 +16 -0
- package/docs/components/user_input.md +48 -2
- package/docs/plugins/language.md +1 -1
- package/package.json +4 -3
- package/src/application.js +7 -2
- package/src/component/setup/props.js +8 -5
- package/src/component/setup/state.js +15 -6
- package/src/component.js +20 -27
- package/src/engines/L3/element.js +10 -10
- package/src/engines/L3/launch.js +8 -1
- package/src/focus.js +8 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
|
|
4
|
+
## v1.5.1
|
|
5
|
+
|
|
6
|
+
_7 oct 2024_
|
|
7
|
+
|
|
8
|
+
- Added export of symbols
|
|
9
|
+
- Various small performance improvements related to component creation
|
|
10
|
+
|
|
11
|
+
## v1.5.0
|
|
12
|
+
|
|
13
|
+
_1 oct 2024_
|
|
14
|
+
|
|
15
|
+
- Fixed issue with interrupting running / scheduled transitions with a new transition on the same prop
|
|
16
|
+
- Fixed issue with (incompatible) SDF text renderer being loaded when `renderMode` is set to canvas
|
|
17
|
+
- Added support for handling key release (i.e `keyup`) events
|
|
18
|
+
|
|
3
19
|
## v1.4.3
|
|
4
20
|
|
|
5
21
|
_26 sept 2024_
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# Handling User Input
|
|
2
2
|
|
|
3
|
-
In order to allow users to interact with your app, you will want to capture and handle _user input_. If you are developing a TV based app this will
|
|
4
|
-
often be key input via a remote control.
|
|
3
|
+
In order to allow users to interact with your app, you will want to capture and handle _user input_. If you are developing a TV based app this will often be key input via a remote control.
|
|
5
4
|
|
|
6
5
|
Blits offers an intuitive and straightforward interface to handle key input in Components.
|
|
7
6
|
|
|
@@ -73,6 +72,53 @@ When a component handles a key press by having a corresponding function specifie
|
|
|
73
72
|
}
|
|
74
73
|
```
|
|
75
74
|
|
|
75
|
+
## Key-up handling
|
|
76
|
+
|
|
77
|
+
The functions specified in the `input` configuration are invoked when a key is _pressed down_ (i.e. the `keydown` event listener). But sometimes you may also want to execute some logic when a key is _released_ (i.e. the `keyup` event listener).
|
|
78
|
+
|
|
79
|
+
Instead of introducing a separate key on the Component configuration object for key release callbacks, Blits relies on the concept that a `keyup` event is always preceeded by a `keydown` event.
|
|
80
|
+
|
|
81
|
+
Following this logic, whenever you return a function in an input (key down) handler, this function will be executed upon release (i.e. the `keyup` event) of that key .
|
|
82
|
+
|
|
83
|
+
When an input key is being a hold down, it will execute the key down handler multipe times. Upon key release, only the last returned key up callback function will be executed.
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
```javascript
|
|
87
|
+
Blits.Component('MyComponemnt', {
|
|
88
|
+
//
|
|
89
|
+
input: {
|
|
90
|
+
enter() {
|
|
91
|
+
// execute logic on key down
|
|
92
|
+
this.pressedEnter = true
|
|
93
|
+
return () => {
|
|
94
|
+
// execute logic on key up
|
|
95
|
+
this.pressedEnter = false
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
space(e) {
|
|
99
|
+
// not logic on key down
|
|
100
|
+
return () => {
|
|
101
|
+
// only execute logic on key up
|
|
102
|
+
console.log('Space key released')
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
left() {
|
|
106
|
+
// some logic on key down here ..
|
|
107
|
+
this.leftHold = true
|
|
108
|
+
// return a reference to a Component method
|
|
109
|
+
// (instead of creating a new function on the fly)
|
|
110
|
+
return this.leftKeyUp
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
methods: {
|
|
114
|
+
leftKeyUp() {
|
|
115
|
+
console.log('Left key up')
|
|
116
|
+
this.leftHold = false
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
76
122
|
## Custom Keycode mapping
|
|
77
123
|
|
|
78
124
|
Blits comes with a default keycode mapping. This mapping is a sensible default that works in your desktop browser and with most RDK based devices.
|
package/docs/plugins/language.md
CHANGED
|
@@ -121,7 +121,7 @@ Blits.Component('MyComponent', {
|
|
|
121
121
|
|
|
122
122
|
## Getting the current language
|
|
123
123
|
|
|
124
|
-
The current language can be retrieved via the `this.$
|
|
124
|
+
The current language can be retrieved via the `this.$languagelanguage` property. It will return the currently set language code.
|
|
125
125
|
|
|
126
126
|
## Translating
|
|
127
127
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lightningjs/blits",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.1",
|
|
4
4
|
"description": "Blits: The Lightning 3 App Development Framework",
|
|
5
5
|
"bin": "bin/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
"./vite": "./vite/index.js",
|
|
9
9
|
"./transitions": "./src/router/transitions/index.js",
|
|
10
10
|
"./precompiler": "./src/lib/precompiler/precompiler.js",
|
|
11
|
-
"./plugins": "./src/plugins/index.js"
|
|
11
|
+
"./plugins": "./src/plugins/index.js",
|
|
12
|
+
"./symbols": "./src/lib/symbols.js"
|
|
12
13
|
},
|
|
13
14
|
"scripts": {
|
|
14
15
|
"test": "c8 npm run test:run",
|
|
@@ -47,7 +48,7 @@
|
|
|
47
48
|
},
|
|
48
49
|
"dependencies": {
|
|
49
50
|
"@lightningjs/msdf-generator": "^1.0.2",
|
|
50
|
-
"@lightningjs/renderer": "^2.
|
|
51
|
+
"@lightningjs/renderer": "^2.3.0",
|
|
51
52
|
"execa": "^8.0.1",
|
|
52
53
|
"kolorist": "^1.8.0",
|
|
53
54
|
"ora": "^7.0.1",
|
package/src/application.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import Component from './component.js'
|
|
19
|
-
import Focus from './focus.js'
|
|
19
|
+
import { default as Focus, keyUpCallbacks } from './focus.js'
|
|
20
20
|
import Settings from './settings.js'
|
|
21
21
|
|
|
22
22
|
import symbols from './lib/symbols.js'
|
|
@@ -65,7 +65,12 @@ const Application = (config) => {
|
|
|
65
65
|
}, Settings.get('holdTimeout', DEFAULT_HOLD_TIMEOUT_MS))
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
keyUpHandler = () => {
|
|
68
|
+
keyUpHandler = (e) => {
|
|
69
|
+
const cb = keyUpCallbacks.get(e.code)
|
|
70
|
+
if (cb !== undefined && typeof cb === 'function') {
|
|
71
|
+
keyUpCallbacks.delete(e.code)
|
|
72
|
+
cb()
|
|
73
|
+
}
|
|
69
74
|
clearTimeout(holdTimeout)
|
|
70
75
|
Focus.hold = false
|
|
71
76
|
}
|
|
@@ -29,20 +29,23 @@ export default (component, props = []) => {
|
|
|
29
29
|
props.push('ref')
|
|
30
30
|
}
|
|
31
31
|
component[symbols.propKeys] = []
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
|
|
33
|
+
const propsLength = props.length
|
|
34
|
+
|
|
35
|
+
for (let i = 0; i < propsLength; i++) {
|
|
36
|
+
const prop = { ...baseProp, ...(typeof props[i] === 'object' ? props[i] : { key: props[i] }) }
|
|
34
37
|
component[symbols.propKeys].push(prop.key)
|
|
35
38
|
Object.defineProperty(component, prop.key, {
|
|
36
39
|
get() {
|
|
37
40
|
const value = prop.cast(
|
|
38
|
-
this[symbols.props] && prop.key in this[symbols.props]
|
|
41
|
+
this[symbols.props] !== undefined && prop.key in this[symbols.props]
|
|
39
42
|
? this[symbols.props][prop.key]
|
|
40
43
|
: 'default' in prop
|
|
41
44
|
? prop.default
|
|
42
45
|
: undefined
|
|
43
46
|
)
|
|
44
47
|
|
|
45
|
-
if (prop.required && value === undefined) {
|
|
48
|
+
if (prop.required === true && value === undefined) {
|
|
46
49
|
Log.warn(`${prop.key} is required`)
|
|
47
50
|
}
|
|
48
51
|
|
|
@@ -53,5 +56,5 @@ export default (component, props = []) => {
|
|
|
53
56
|
this[symbols.props][prop.key] = v
|
|
54
57
|
},
|
|
55
58
|
})
|
|
56
|
-
}
|
|
59
|
+
}
|
|
57
60
|
}
|
|
@@ -27,13 +27,23 @@ export default (component, state = () => {}) => {
|
|
|
27
27
|
writable: false,
|
|
28
28
|
})
|
|
29
29
|
|
|
30
|
-
const stateKeys = Object.keys(state.apply(component) || {})
|
|
30
|
+
const stateKeys = Object.keys(state.apply(component) || {})
|
|
31
|
+
// add built-in hasFocus key
|
|
32
|
+
stateKeys.push(['hasFocus'])
|
|
33
|
+
const stateKeysLength = stateKeys.length
|
|
31
34
|
|
|
32
|
-
|
|
33
|
-
|
|
35
|
+
for (let i = 0; i < stateKeysLength; i++) {
|
|
36
|
+
const key = stateKeys[i]
|
|
37
|
+
if (
|
|
38
|
+
component[symbols.propKeys] !== undefined &&
|
|
39
|
+
component[symbols.propKeys].indexOf(key) > -1
|
|
40
|
+
) {
|
|
34
41
|
Log.error(`State ${key} already exists as a prop`)
|
|
35
42
|
}
|
|
36
|
-
if (
|
|
43
|
+
if (
|
|
44
|
+
component[symbols.methodKeys] !== undefined &&
|
|
45
|
+
component[symbols.methodKeys].indexOf(key) > -1
|
|
46
|
+
) {
|
|
37
47
|
Log.error(`State ${key} already exists as a method`)
|
|
38
48
|
}
|
|
39
49
|
component[symbols.stateKeys].push(key)
|
|
@@ -44,7 +54,6 @@ export default (component, state = () => {}) => {
|
|
|
44
54
|
set(v) {
|
|
45
55
|
this[symbols.state][key] = v
|
|
46
56
|
},
|
|
47
|
-
// enumerable: true,
|
|
48
57
|
})
|
|
49
|
-
}
|
|
58
|
+
}
|
|
50
59
|
}
|
package/src/component.js
CHANGED
|
@@ -51,7 +51,7 @@ const required = (name) => {
|
|
|
51
51
|
*
|
|
52
52
|
*/
|
|
53
53
|
const Component = (name = required('name'), config = required('config')) => {
|
|
54
|
-
let base
|
|
54
|
+
let base = undefined
|
|
55
55
|
|
|
56
56
|
const component = function (opts, parentEl, parentComponent, rootComponent) {
|
|
57
57
|
// generate a human readable ID for the component instance (i.e. Blits::ComponentName1)
|
|
@@ -89,11 +89,10 @@ const Component = (name = required('name'), config = required('config')) => {
|
|
|
89
89
|
|
|
90
90
|
// apply the state function (passing in the this reference to utilize configured props)
|
|
91
91
|
// and store a reference to this original state
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
92
|
+
this[symbols.originalState] =
|
|
93
|
+
(config.state && typeof config.state === 'function' && config.state.apply(this)) || {}
|
|
94
|
+
// add hasFocus key in
|
|
95
|
+
this[symbols.originalState]['hasFocus'] = false
|
|
97
96
|
|
|
98
97
|
// generate a reactive state (using the result of previously execute state function)
|
|
99
98
|
// and store it
|
|
@@ -162,7 +161,6 @@ const Component = (name = required('name'), config = required('config')) => {
|
|
|
162
161
|
// reactive bindings define in the template
|
|
163
162
|
const effects = config.code.effects
|
|
164
163
|
for (let i = 0; i < effects.length; i++) {
|
|
165
|
-
// console.log(config.code.effects[i].toString())
|
|
166
164
|
effect(() => {
|
|
167
165
|
effects[i](this, this[symbols.children], config, globalComponents, rootComponent, effect)
|
|
168
166
|
})
|
|
@@ -170,12 +168,14 @@ const Component = (name = required('name'), config = required('config')) => {
|
|
|
170
168
|
|
|
171
169
|
// setup watchers if the components has watchers specified
|
|
172
170
|
if (this[symbols.watchers]) {
|
|
173
|
-
Object.keys(this[symbols.watchers])
|
|
171
|
+
const watcherkeys = Object.keys(this[symbols.watchers])
|
|
172
|
+
const watcherkeysLength = watcherkeys.length
|
|
173
|
+
for (let i = 0; i < watcherkeysLength; i++) {
|
|
174
174
|
let target = this
|
|
175
|
-
let key =
|
|
175
|
+
let key = watcherkeys[i]
|
|
176
176
|
// when dot notation used, find the nested target
|
|
177
|
-
if (
|
|
178
|
-
const keys =
|
|
177
|
+
if (key.indexOf('.') > -1) {
|
|
178
|
+
const keys = key.split('.')
|
|
179
179
|
key = keys.pop()
|
|
180
180
|
for (let i = 0; i < keys.length; i++) {
|
|
181
181
|
target = target[keys[i]]
|
|
@@ -187,23 +187,13 @@ const Component = (name = required('name'), config = required('config')) => {
|
|
|
187
187
|
effect((force = false) => {
|
|
188
188
|
const newValue = target[key]
|
|
189
189
|
if (old !== newValue || force === true) {
|
|
190
|
-
this[symbols.watchers][
|
|
190
|
+
this[symbols.watchers][key].apply(this, [newValue, old])
|
|
191
191
|
old = newValue
|
|
192
192
|
}
|
|
193
193
|
})
|
|
194
|
-
}
|
|
194
|
+
}
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
-
// set all symbol based properties to non-enumerable and non-configurable
|
|
198
|
-
Object.getOwnPropertySymbols(this).forEach((property) => {
|
|
199
|
-
Object.defineProperties(this, {
|
|
200
|
-
[property]: {
|
|
201
|
-
enumerable: false,
|
|
202
|
-
configurable: false,
|
|
203
|
-
},
|
|
204
|
-
})
|
|
205
|
-
})
|
|
206
|
-
|
|
207
197
|
// finaly set the lifecycle state to ready (in the next tick)
|
|
208
198
|
setTimeout(() => (this.lifecycle.state = 'ready'))
|
|
209
199
|
|
|
@@ -214,7 +204,10 @@ const Component = (name = required('name'), config = required('config')) => {
|
|
|
214
204
|
const factory = (options = {}, parentEl, parentComponent, rootComponent) => {
|
|
215
205
|
if (Base[symbols['launched']] === false) {
|
|
216
206
|
// Register user defined plugins once on the Base object (after launch)
|
|
217
|
-
Object.keys(plugins)
|
|
207
|
+
const pluginKeys = Object.keys(plugins)
|
|
208
|
+
const pluginKeysLength = pluginKeys.length
|
|
209
|
+
for (let i = 0; i < pluginKeysLength; i++) {
|
|
210
|
+
const pluginName = pluginKeys[i]
|
|
218
211
|
const prefixedPluginName = `$${pluginName}`
|
|
219
212
|
if (prefixedPluginName in Base) {
|
|
220
213
|
Log.warn(
|
|
@@ -231,7 +224,7 @@ const Component = (name = required('name'), config = required('config')) => {
|
|
|
231
224
|
enumerable: true,
|
|
232
225
|
configurable: false,
|
|
233
226
|
})
|
|
234
|
-
}
|
|
227
|
+
}
|
|
235
228
|
|
|
236
229
|
// register global components once
|
|
237
230
|
globalComponents = components()
|
|
@@ -241,13 +234,13 @@ const Component = (name = required('name'), config = required('config')) => {
|
|
|
241
234
|
}
|
|
242
235
|
|
|
243
236
|
// setup the component once per component type, using Base as the prototype
|
|
244
|
-
if (
|
|
237
|
+
if (base === undefined) {
|
|
245
238
|
Log.debug(`Setting up ${name} component`)
|
|
246
239
|
base = setupComponent(Object.create(Base), config)
|
|
247
240
|
}
|
|
248
241
|
|
|
249
242
|
// one time code generation (only if precompilation is turned off)
|
|
250
|
-
if (
|
|
243
|
+
if (config.code === undefined) {
|
|
251
244
|
Log.debug(`Generating code for ${name} component`)
|
|
252
245
|
config.code = codegenerator.call(config, parser(config.template, name))
|
|
253
246
|
}
|
|
@@ -344,16 +344,17 @@ const Element = {
|
|
|
344
344
|
// }
|
|
345
345
|
},
|
|
346
346
|
animate(prop, value, transition) {
|
|
347
|
+
// check if a transition is already scheduled to run on the same prop
|
|
348
|
+
// and cancels it if it does
|
|
349
|
+
if (
|
|
350
|
+
this.scheduledTransitions[prop] !== undefined &&
|
|
351
|
+
this.scheduledTransitions[prop].f.state === 'scheduled'
|
|
352
|
+
) {
|
|
353
|
+
this.scheduledTransitions[prop].f.stop()
|
|
354
|
+
}
|
|
355
|
+
|
|
347
356
|
// if current value is the same as the value to animate to, instantly resolve
|
|
348
357
|
if (this.node[prop] === value) return
|
|
349
|
-
// check if a transition is already scheduled or running on the same prop
|
|
350
|
-
if (this.scheduledTransitions[prop]) {
|
|
351
|
-
if (this.scheduledTransitions[prop].f.state === 'running') {
|
|
352
|
-
// fastforward to final value
|
|
353
|
-
this.node[prop] = this.scheduledTransitions[prop].v
|
|
354
|
-
// this.scheduledTransitions[prop].f.stop()
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
358
|
|
|
358
359
|
const props = {}
|
|
359
360
|
props[prop] = value
|
|
@@ -382,7 +383,6 @@ const Element = {
|
|
|
382
383
|
// removed if another transition for the same prop starts in the mean time
|
|
383
384
|
this.scheduledTransitions[prop] = {
|
|
384
385
|
v: props[prop],
|
|
385
|
-
cancel: false,
|
|
386
386
|
f,
|
|
387
387
|
}
|
|
388
388
|
|
|
@@ -395,7 +395,7 @@ const Element = {
|
|
|
395
395
|
|
|
396
396
|
f.once('stopped', () => {
|
|
397
397
|
// remove the prop from scheduled transitions
|
|
398
|
-
this.scheduledTransitions[prop] =
|
|
398
|
+
this.scheduledTransitions[prop] = undefined
|
|
399
399
|
// fire transition end callback when animation ends (if specified)
|
|
400
400
|
if (transition.end && typeof transition.end === 'function') {
|
|
401
401
|
transition.end.call(this.component, this, prop, this.node[prop])
|
package/src/engines/L3/launch.js
CHANGED
|
@@ -35,6 +35,13 @@ const renderEngine = (settings) => {
|
|
|
35
35
|
if (renderMode === 'canvas') return CanvasCoreRenderer
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
const textRenderEngines = (settings) => {
|
|
39
|
+
const renderMode = 'renderMode' in settings ? settings.renderMode : 'webgl'
|
|
40
|
+
|
|
41
|
+
if (renderMode === 'webgl') return [SdfTextRenderer, CanvasTextRenderer]
|
|
42
|
+
if (renderMode === 'canvas') return [CanvasTextRenderer]
|
|
43
|
+
}
|
|
44
|
+
|
|
38
45
|
export default (App, target, settings = {}) => {
|
|
39
46
|
renderer = new RendererMain(
|
|
40
47
|
{
|
|
@@ -57,7 +64,7 @@ export default (App, target, settings = {}) => {
|
|
|
57
64
|
txMemByteThreshold:
|
|
58
65
|
'gpuMemoryLimit' in settings ? settings.gpuMemoryLimit * 1024 * 1024 : 200 * 1024 * 1024,
|
|
59
66
|
renderEngine: renderEngine(settings),
|
|
60
|
-
fontEngines:
|
|
67
|
+
fontEngines: textRenderEngines(settings),
|
|
61
68
|
},
|
|
62
69
|
target
|
|
63
70
|
)
|
package/src/focus.js
CHANGED
|
@@ -24,6 +24,8 @@ let focusedComponent = null
|
|
|
24
24
|
let focusChain = []
|
|
25
25
|
let setFocusTimeout
|
|
26
26
|
|
|
27
|
+
export const keyUpCallbacks = new Map()
|
|
28
|
+
|
|
27
29
|
export default {
|
|
28
30
|
_hold: false,
|
|
29
31
|
set hold(v) {
|
|
@@ -60,10 +62,14 @@ export default {
|
|
|
60
62
|
const componentWithInputEvent = focusChain.shift()
|
|
61
63
|
|
|
62
64
|
if (componentWithInputEvent) {
|
|
65
|
+
let cb
|
|
63
66
|
if (componentWithInputEvent[symbols.inputEvents][key]) {
|
|
64
|
-
componentWithInputEvent[symbols.inputEvents][key].call(componentWithInputEvent, event)
|
|
67
|
+
cb = componentWithInputEvent[symbols.inputEvents][key].call(componentWithInputEvent, event)
|
|
65
68
|
} else if (componentWithInputEvent[symbols.inputEvents].any) {
|
|
66
|
-
componentWithInputEvent[symbols.inputEvents].any.call(componentWithInputEvent, event)
|
|
69
|
+
cb = componentWithInputEvent[symbols.inputEvents].any.call(componentWithInputEvent, event)
|
|
70
|
+
}
|
|
71
|
+
if (cb !== undefined) {
|
|
72
|
+
keyUpCallbacks.set(event.code, cb)
|
|
67
73
|
}
|
|
68
74
|
}
|
|
69
75
|
},
|