@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 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.
@@ -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.$language.get()` method. It will return the currently set language code.
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.4.3",
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.2.0",
51
+ "@lightningjs/renderer": "^2.3.0",
51
52
  "execa": "^8.0.1",
52
53
  "kolorist": "^1.8.0",
53
54
  "ora": "^7.0.1",
@@ -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
- props.forEach((prop) => {
33
- prop = { ...baseProp, ...(typeof prop === 'object' ? prop : { key: prop }) }
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) || {}).concat(['hasFocus'])
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
- stateKeys.forEach((key) => {
33
- if (component[symbols.propKeys] && component[symbols.propKeys].indexOf(key) > -1) {
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 (component[symbols.methodKeys] && component[symbols.methodKeys].indexOf(key) > -1) {
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
- // hasFocus key is sprinkled in
93
- this[symbols.originalState] = {
94
- ...((config.state && typeof config.state === 'function' && config.state.apply(this)) || {}),
95
- ...{ hasFocus: false },
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]).forEach((watchKey) => {
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 = watchKey
175
+ let key = watcherkeys[i]
176
176
  // when dot notation used, find the nested target
177
- if (watchKey.indexOf('.') > -1) {
178
- const keys = watchKey.split('.')
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][watchKey].apply(this, [newValue, old])
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).forEach((pluginName) => {
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 (!base) {
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 (!config.code) {
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] = null
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])
@@ -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: [SdfTextRenderer, CanvasTextRenderer],
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
  },