@lightningjs/blits 2.0.0 → 2.1.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 +22 -0
- package/index.d.ts +7 -0
- package/package.json +1 -1
- package/src/application.js +2 -2
- package/src/component/base/methods.js +3 -3
- package/src/component/base/timeouts_intervals.js +10 -0
- package/src/components/RouterView.js +1 -1
- package/src/constants.js +0 -8
- package/src/engines/L3/element.js +20 -6
- package/src/engines/L3/fontLoader.js +1 -1
- package/src/focus/focus.js +3 -4
- package/src/lib/codegenerator/generator.js +25 -5
- package/src/lib/events/internalkeyboardevent.js +29 -0
- package/src/lib/reactivityguard/computedprops.js +19 -2
- package/src/lib/utils.js +9 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v2.1.0
|
|
4
|
+
|
|
5
|
+
_9 apr 2026_
|
|
6
|
+
|
|
7
|
+
- Fixed left over `KeyboardEvent.code` references
|
|
8
|
+
- Fixed issue with allowing $-prefix to be used as text content
|
|
9
|
+
- Fixed issue with internal keyboard event not working on older browsers
|
|
10
|
+
- Added tests to router hooks
|
|
11
|
+
- Fixed issue with transitions not being skipped when duration is set to `0`
|
|
12
|
+
- Added `this.$nextTick` utility helper
|
|
13
|
+
- Fixed issue with transitioning colours in `border`-attribute
|
|
14
|
+
|
|
15
|
+
## v2.0.1
|
|
16
|
+
|
|
17
|
+
_23 mar 2026_
|
|
18
|
+
|
|
19
|
+
- Fixed issue with height dimension of router view
|
|
20
|
+
- Fixed issue with array or rounded corners not being reactive
|
|
21
|
+
- Fixed issue with font not being mapped correctly when using `png` in the font config
|
|
22
|
+
- Removed obsolete key config
|
|
23
|
+
- Fixed reactivity guard in computed props to handle deep nested references more safely
|
|
24
|
+
|
|
3
25
|
## v2.0.0
|
|
4
26
|
|
|
5
27
|
_20 mar 2026_
|
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
package/src/application.js
CHANGED
|
@@ -118,9 +118,9 @@ const Application = (config) => {
|
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
keyUpHandler = (e) => {
|
|
121
|
-
const cb = keyUpCallbacks.get(e.
|
|
121
|
+
const cb = keyUpCallbacks.get(e.keyCode)
|
|
122
122
|
if (cb !== undefined && typeof cb === 'function') {
|
|
123
|
-
keyUpCallbacks.delete(e.
|
|
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.
|
|
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.
|
|
69
|
-
keyUpCallbacks.set(event.
|
|
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
|
}
|
|
@@ -94,7 +94,7 @@ export default () =>
|
|
|
94
94
|
)
|
|
95
95
|
|
|
96
96
|
elementConfigs[0]['w'] = parent.node.w * (100 / 100)
|
|
97
|
-
elementConfigs[0]['
|
|
97
|
+
elementConfigs[0]['h'] = parent.node.h * (100 / 100)
|
|
98
98
|
elms[0].populate(elementConfigs[0])
|
|
99
99
|
|
|
100
100
|
if (inSlot === true) {
|
package/src/constants.js
CHANGED
|
@@ -18,14 +18,6 @@
|
|
|
18
18
|
export const DEFAULT_HOLD_TIMEOUT_MS = 50
|
|
19
19
|
|
|
20
20
|
export const DEFAULT_KEYMAP = {
|
|
21
|
-
ArrowLeft: 'left',
|
|
22
|
-
ArrowRight: 'right',
|
|
23
|
-
ArrowUp: 'up',
|
|
24
|
-
ArrowDown: 'down',
|
|
25
|
-
Enter: 'enter',
|
|
26
|
-
' ': 'space',
|
|
27
|
-
Backspace: 'back',
|
|
28
|
-
Escape: 'escape',
|
|
29
21
|
37: 'left',
|
|
30
22
|
39: 'right',
|
|
31
23
|
38: 'up',
|
|
@@ -16,7 +16,13 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import { renderer } from './launch.js'
|
|
19
|
-
import {
|
|
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'
|
|
@@ -362,7 +368,10 @@ const propsTransformer = {
|
|
|
362
368
|
set rounded(v) {
|
|
363
369
|
this.props['rounded'] = v
|
|
364
370
|
if (this.element.node !== undefined && this.elementShader === true) {
|
|
365
|
-
if (
|
|
371
|
+
if (
|
|
372
|
+
Array.isArray(v) === false &&
|
|
373
|
+
(typeof v === 'object' || (isObjectString(v) === true && (v = parseToObject(v))))
|
|
374
|
+
) {
|
|
366
375
|
this.element.node.props['shader'].props = v
|
|
367
376
|
} else {
|
|
368
377
|
if (isArrayString(v) === true) {
|
|
@@ -374,14 +383,17 @@ const propsTransformer = {
|
|
|
374
383
|
},
|
|
375
384
|
set border(v) {
|
|
376
385
|
this.props['border'] = v
|
|
386
|
+
|
|
377
387
|
if (
|
|
378
388
|
this.element.node !== undefined &&
|
|
379
389
|
this.elementShader === true &&
|
|
380
390
|
(typeof v === 'object' || isObjectString(v) === true)
|
|
381
391
|
) {
|
|
382
392
|
v = shaders.parseProps(v)
|
|
393
|
+
const shader = this.element.node.props['shader']
|
|
394
|
+
let prefix = shader.shaderKey.startsWith('rounded') ? 'border-' : ''
|
|
383
395
|
for (const key in v) {
|
|
384
|
-
this.element.node.props['shader'].props[
|
|
396
|
+
this.element.node.props['shader'].props[prefix + key] = v[key]
|
|
385
397
|
}
|
|
386
398
|
}
|
|
387
399
|
},
|
|
@@ -393,8 +405,10 @@ const propsTransformer = {
|
|
|
393
405
|
(typeof v === 'object' || isObjectString(v) === true)
|
|
394
406
|
) {
|
|
395
407
|
v = shaders.parseProps(v)
|
|
408
|
+
const shader = this.element.node.props['shader']
|
|
409
|
+
let prefix = shader.shaderKey.startsWith('rounded') ? 'shadow-' : ''
|
|
396
410
|
for (const key in v) {
|
|
397
|
-
this.element.node.props['shader'].props[
|
|
411
|
+
this.element.node.props['shader'].props[prefix + key] = v[key]
|
|
398
412
|
}
|
|
399
413
|
}
|
|
400
414
|
},
|
|
@@ -660,7 +674,7 @@ const Element = {
|
|
|
660
674
|
const propsKeys = Object.keys(this.props.props)
|
|
661
675
|
|
|
662
676
|
if (propsKeys.length === 1) {
|
|
663
|
-
if (isTransition(value) === true) {
|
|
677
|
+
if (isTransition(value) === true && isZeroDurationTransition(value) === false) {
|
|
664
678
|
return this.animate(propsKeys[0], this.props.props[propsKeys[0]], value.transition)
|
|
665
679
|
}
|
|
666
680
|
// set the prop to the value on the node
|
|
@@ -668,7 +682,7 @@ const Element = {
|
|
|
668
682
|
} else {
|
|
669
683
|
for (let i = 0; i < propsKeys.length; i++) {
|
|
670
684
|
// todo: fix code duplication
|
|
671
|
-
if (isTransition(value) === true) {
|
|
685
|
+
if (isTransition(value) === true && isZeroDurationTransition(value) === false) {
|
|
672
686
|
this.animate(propsKeys[i], this.props.props[propsKeys[i]], value.transition)
|
|
673
687
|
} else {
|
|
674
688
|
// set the prop to the value on the node
|
|
@@ -36,7 +36,7 @@ export default () => {
|
|
|
36
36
|
if (type === 'sdf') {
|
|
37
37
|
stage.loadFont('sdf', {
|
|
38
38
|
fontFamily: font.family,
|
|
39
|
-
atlasUrl: font.
|
|
39
|
+
atlasUrl: font.png || (font.file && font.file.replace(/\.[^.]+$/, `.${font.type}.png`)),
|
|
40
40
|
atlasDataUrl:
|
|
41
41
|
font.json || (font.file && font.file.replace(/\.[^.]+$/, `.${font.type}.json`)),
|
|
42
42
|
})
|
package/src/focus/focus.js
CHANGED
|
@@ -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.
|
|
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
|
|
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'
|
|
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
|
+
}
|
|
@@ -196,14 +196,19 @@ export default (code) => {
|
|
|
196
196
|
// Skip if already modified
|
|
197
197
|
if (
|
|
198
198
|
prop.body.includes(commentText.trim()) ||
|
|
199
|
-
Array.from(thisRefs).some((ref) =>
|
|
199
|
+
Array.from(thisRefs).some((ref) => {
|
|
200
|
+
if (prop.body.startsWith(ref)) return true
|
|
201
|
+
// stripped && guard: body starts with the first segment, e.g. 'this.media &&'
|
|
202
|
+
const firstSegEnd = ref.indexOf('.', 5)
|
|
203
|
+
return firstSegEnd !== -1 && prop.body.startsWith(ref.substring(0, firstSegEnd))
|
|
204
|
+
})
|
|
200
205
|
) {
|
|
201
206
|
continue
|
|
202
207
|
}
|
|
203
208
|
|
|
204
209
|
// reactivity code
|
|
205
210
|
const refCode = Array.from(thisRefs)
|
|
206
|
-
.map((ref) => `${ref};`)
|
|
211
|
+
.map((ref) => `${toSafeRef(ref)};`)
|
|
207
212
|
.join(' ')
|
|
208
213
|
|
|
209
214
|
// replacement with the same whitespace
|
|
@@ -290,6 +295,18 @@ export default (code) => {
|
|
|
290
295
|
return null
|
|
291
296
|
}
|
|
292
297
|
|
|
298
|
+
const toSafeRef = (ref) => {
|
|
299
|
+
// Converts 'this.a.b.c' to 'this.a && this.a.b && this.a.b.c'
|
|
300
|
+
const normalized = ref.replace(/\?\./g, '.')
|
|
301
|
+
const segments = normalized.split('.')
|
|
302
|
+
if (segments.length <= 2) return normalized
|
|
303
|
+
const parts = []
|
|
304
|
+
for (let i = 2; i <= segments.length; i++) {
|
|
305
|
+
parts.push(segments.slice(0, i).join('.'))
|
|
306
|
+
}
|
|
307
|
+
return parts.join(' && ')
|
|
308
|
+
}
|
|
309
|
+
|
|
293
310
|
const extractThisReferences = (funcBody) => {
|
|
294
311
|
const thisRefs = new Set()
|
|
295
312
|
|
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.
|