@lightningjs/blits 2.0.1 → 2.1.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,23 @@
1
1
  # Changelog
2
2
 
3
+ ## v2.1.1
4
+
5
+ _9 apr 2026_
6
+
7
+ - Fixed import file extension issue
8
+
9
+ ## v2.1.0
10
+
11
+ _9 apr 2026_
12
+
13
+ - Fixed left over `KeyboardEvent.code` references
14
+ - Fixed issue with allowing $-prefix to be used as text content
15
+ - Fixed issue with internal keyboard event not working on older browsers
16
+ - Added tests to router hooks
17
+ - Fixed issue with transitions not being skipped when duration is set to `0`
18
+ - Added `this.$nextTick` utility helper
19
+ - Fixed issue with transitioning colours in `border`-attribute
20
+
3
21
  ## v2.0.1
4
22
 
5
23
  _23 mar 2026_
@@ -7,7 +25,7 @@ _23 mar 2026_
7
25
  - Fixed issue with height dimension of router view
8
26
  - Fixed issue with array or rounded corners not being reactive
9
27
  - Fixed issue with font not being mapped correctly when using `png` in the font config
10
- - Removed obselete key config
28
+ - Removed obsolete key config
11
29
  - Fixed reactivity guard in computed props to handle deep nested references more safely
12
30
 
13
31
  ## v2.0.0
package/index.d.ts CHANGED
@@ -474,6 +474,13 @@ declare module '@lightningjs/blits' {
474
474
  */
475
475
  $clearDebounces: () => void
476
476
 
477
+ /**
478
+ * Defer a callback to the next tick (setTimeout 0), automatically cleaned upon component destroy
479
+ * @param callback - Function to execute on the next tick
480
+ * @param args - Arguments to pass to the callback
481
+ */
482
+ $nextTick: (callback: (...args: any[]) => void, ...args: any[]) => ReturnType<typeof setTimeout>
483
+
477
484
  /**
478
485
  * Set an interval that is automatically cleaned upon component destroy
479
486
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightningjs/blits",
3
- "version": "2.0.1",
3
+ "version": "2.1.1",
4
4
  "description": "Blits: The Lightning 3 App Development Framework",
5
5
  "bin": "bin/index.js",
6
6
  "exports": {
@@ -118,9 +118,9 @@ const Application = (config) => {
118
118
  }
119
119
 
120
120
  keyUpHandler = (e) => {
121
- const cb = keyUpCallbacks.get(e.code)
121
+ const cb = keyUpCallbacks.get(e.keyCode)
122
122
  if (cb !== undefined && typeof cb === 'function') {
123
- keyUpCallbacks.delete(e.code)
123
+ keyUpCallbacks.delete(e.keyCode)
124
124
  cb()
125
125
  }
126
126
  clearTimeout(holdTimeout)
@@ -51,7 +51,7 @@ export default {
51
51
  if (event === null || event === undefined || event instanceof KeyboardEvent === false)
52
52
  return false
53
53
 
54
- const key = keyMap[event.key] || keyMap[event.keyCode] || event.key || event.keyCode
54
+ const key = keyMap[event.keyCode] || event.keyCode
55
55
 
56
56
  const componentWithInputEvent = getComponentWithInputEvent(this, key)
57
57
  if (componentWithInputEvent === null) return false
@@ -65,8 +65,8 @@ export default {
65
65
  cb = inputEvents.any.call(componentWithInputEvent, event)
66
66
  }
67
67
 
68
- if (cb !== undefined && event.code) {
69
- keyUpCallbacks.set(event.code, cb)
68
+ if (cb !== undefined && event.keyCode) {
69
+ keyUpCallbacks.set(event.keyCode, cb)
70
70
  }
71
71
 
72
72
  return true
@@ -152,4 +152,14 @@ export default {
152
152
  enumerable: true,
153
153
  configurable: false,
154
154
  },
155
+ $nextTick: {
156
+ value: function (fn, ...params) {
157
+ // early exit when component is marked as end of life
158
+ if (this.eol === true) return
159
+ return this.$setTimeout(fn, 0, ...params)
160
+ },
161
+ writable: false,
162
+ enumerable: true,
163
+ configurable: false,
164
+ },
155
165
  }
@@ -16,7 +16,13 @@
16
16
  */
17
17
 
18
18
  import { renderer } from './launch.js'
19
- import { parseToObject, isObjectString, isArrayString, isTransition } from '../../lib/utils.js'
19
+ import {
20
+ parseToObject,
21
+ isObjectString,
22
+ isArrayString,
23
+ isTransition,
24
+ isZeroDurationTransition,
25
+ } from '../../lib/utils.js'
20
26
  import colors from '../../lib/colors/colors.js'
21
27
 
22
28
  import { Log } from '../../lib/log.js'
@@ -377,14 +383,17 @@ const propsTransformer = {
377
383
  },
378
384
  set border(v) {
379
385
  this.props['border'] = v
386
+
380
387
  if (
381
388
  this.element.node !== undefined &&
382
389
  this.elementShader === true &&
383
390
  (typeof v === 'object' || isObjectString(v) === true)
384
391
  ) {
385
392
  v = shaders.parseProps(v)
393
+ const shader = this.element.node.props['shader']
394
+ let prefix = shader.shaderKey.startsWith('rounded') ? 'border-' : ''
386
395
  for (const key in v) {
387
- this.element.node.props['shader'].props[`border-${key}`] = v[key]
396
+ this.element.node.props['shader'].props[prefix + key] = v[key]
388
397
  }
389
398
  }
390
399
  },
@@ -396,8 +405,10 @@ const propsTransformer = {
396
405
  (typeof v === 'object' || isObjectString(v) === true)
397
406
  ) {
398
407
  v = shaders.parseProps(v)
408
+ const shader = this.element.node.props['shader']
409
+ let prefix = shader.shaderKey.startsWith('rounded') ? 'shadow-' : ''
399
410
  for (const key in v) {
400
- this.element.node.props['shader'].props[`shadow-${key}`] = v[key]
411
+ this.element.node.props['shader'].props[prefix + key] = v[key]
401
412
  }
402
413
  }
403
414
  },
@@ -663,7 +674,7 @@ const Element = {
663
674
  const propsKeys = Object.keys(this.props.props)
664
675
 
665
676
  if (propsKeys.length === 1) {
666
- if (isTransition(value) === true) {
677
+ if (isTransition(value) === true && isZeroDurationTransition(value) === false) {
667
678
  return this.animate(propsKeys[0], this.props.props[propsKeys[0]], value.transition)
668
679
  }
669
680
  // set the prop to the value on the node
@@ -671,7 +682,7 @@ const Element = {
671
682
  } else {
672
683
  for (let i = 0; i < propsKeys.length; i++) {
673
684
  // todo: fix code duplication
674
- if (isTransition(value) === true) {
685
+ if (isTransition(value) === true && isZeroDurationTransition(value) === false) {
675
686
  this.animate(propsKeys[i], this.props.props[propsKeys[i]], value.transition)
676
687
  } else {
677
688
  // set the prop to the value on the node
@@ -21,6 +21,7 @@ import Settings from '../settings.js'
21
21
  import { DEFAULT_HOLD_TIMEOUT_MS } from '../constants.js'
22
22
  import { Log } from '../lib/log.js'
23
23
  import { getAncestors } from './helpers.js'
24
+ import InternalKeyboardEvent from '../lib/events/internalkeyboardevent.js'
24
25
 
25
26
  let focusedComponent = null
26
27
  let focusChain = []
@@ -95,7 +96,7 @@ export default {
95
96
  }
96
97
 
97
98
  if (cb !== undefined) {
98
- keyUpCallbacks.set(event.code, cb)
99
+ keyUpCallbacks.set(event.keyCode, cb)
99
100
  }
100
101
  },
101
102
  }
@@ -133,9 +134,7 @@ const setFocus = (component, event) => {
133
134
  component[symbols.lifecycle].state = 'focus'
134
135
 
135
136
  if (event instanceof KeyboardEvent) {
136
- const internalEvent = new KeyboardEvent('keydown', event)
137
- // @ts-ignore - this is an internal event
138
- internalEvent[symbols.internalEvent] = true
137
+ const internalEvent = new InternalKeyboardEvent('keydown', event)
139
138
  document.dispatchEvent(internalEvent)
140
139
  }
141
140
  }
@@ -116,7 +116,27 @@ export default function (templateObject = { children: [] }, devMode = false) {
116
116
  }
117
117
 
118
118
  // This is used to get only variable from expression
119
- const extractVariables = function (value) {
119
+ const extractVariables = function (value, isContentKey = false) {
120
+ if (isContentKey) {
121
+ // Strip string literals (single and double quoted) so that $variables
122
+ // embedded in plain text (e.g. "'this.$notifications.add()'") are not
123
+ // mistakenly extracted as component variable references.
124
+ const stripped = value.replace(/'[^']*'|"[^"]*"/g, '')
125
+
126
+ // A $variable is a real component reference only if:
127
+ // 1. The value starts with a bare $variable (e.g. "$notification.add(ms,)")
128
+ // 2. The stripped text contains string concatenation (e.g. "'Count: ' + $notifications.count")
129
+ // Plain text with embedded $variables (e.g. "Custom plugin with $reactive state")
130
+ // should not trigger verification.
131
+ const isBareDollarVar = /^\s*\$\$?\w+/.test(stripped)
132
+ const hasConcatExpression = /\+/.test(stripped)
133
+
134
+ if (!isBareDollarVar && !hasConcatExpression) {
135
+ return false
136
+ }
137
+ value = stripped
138
+ }
139
+
120
140
  const regEx = /\$\$?\w+(\.\w+)?/g
121
141
  const matches = value.match(regEx)
122
142
  if (matches !== null) {
@@ -127,8 +147,8 @@ const extractVariables = function (value) {
127
147
  }
128
148
  }
129
149
 
130
- const verifyVariables = function (value, renderCode, type = 'dynamic') {
131
- const variablesToBeVerified = extractVariables(value)
150
+ const verifyVariables = function (value, renderCode, isContentKey = false, type = 'dynamic') {
151
+ const variablesToBeVerified = extractVariables(value, isContentKey)
132
152
  if (variablesToBeVerified !== false) {
133
153
  for (let i = 0; i < variablesToBeVerified.length; i++) {
134
154
  let variable = variablesToBeVerified[i]
@@ -222,7 +242,7 @@ const generateElementCode = function (
222
242
  }
223
243
  // value.includes('.') === false &&
224
244
  if (isDev === true && options.component !== 'scope.' && value.includes('$')) {
225
- verifyVariables(value, renderCode, 'reactive')
245
+ verifyVariables(value, renderCode, key === ':content', 'reactive')
226
246
  }
227
247
  renderCode.push(
228
248
  `elementConfigs[${counter}]['${key.substring(1)}'] = ${interpolate(
@@ -232,7 +252,7 @@ const generateElementCode = function (
232
252
  )
233
253
  } else {
234
254
  if (isDev === true && options.component !== 'scope.' && value.includes('$')) {
235
- verifyVariables(value, renderCode)
255
+ verifyVariables(value, renderCode, key === 'content')
236
256
  }
237
257
  renderCode.push(
238
258
  `elementConfigs[${counter}]['${key}'] = ${cast(value, key, options.component)}`
@@ -0,0 +1,29 @@
1
+ import symbols from '../symbols.js'
2
+
3
+ /**
4
+ * Internal keyboard event class that extends the native KeyboardEvent.
5
+ * This allows us to set props on the event from the eventInitDict,
6
+ * which is not correctly implemented on some browsers.
7
+ *
8
+ * @extends {KeyboardEvent}
9
+ */
10
+ export default class InternalKeyboardEvent extends KeyboardEvent {
11
+ [symbols.internalEvent] = true
12
+ _keyCode = 0
13
+
14
+ get keyCode() {
15
+ return this._keyCode
16
+ }
17
+
18
+ /**
19
+ * @param {string} type - A string with the name of the event
20
+ * @param {KeyboardEventInit} [eventInitDict] - An object that configures the event
21
+ */
22
+ constructor(type, eventInitDict) {
23
+ super(type, eventInitDict)
24
+
25
+ if ('keyCode' in eventInitDict) {
26
+ this._keyCode = eventInitDict.keyCode
27
+ }
28
+ }
29
+ }
package/src/lib/utils.js CHANGED
@@ -52,6 +52,15 @@ export const isTransition = (value) => {
52
52
  return value !== null && typeof value === 'object' && 'transition' in value === true
53
53
  }
54
54
 
55
+ /**
56
+ * Checks if a transition object has a duration of zero.
57
+ * @param {any} value - The value to check.
58
+ * @returns {boolean} True if the transition duration is zero, false otherwise.
59
+ */
60
+ export const isZeroDurationTransition = (value) => {
61
+ return value.transition.duration === 0
62
+ }
63
+
55
64
  /**
56
65
  * Checks if a string is an object string (starts and ends with curly braces).
57
66
  * @param {string} str - The string to check.