@lightningjs/blits 1.49.0 → 1.50.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 +11 -2
- package/index.d.ts +19 -0
- package/package.json +3 -2
- package/src/application.js +95 -3
- package/src/component/base/methods.js +1 -2
- package/src/component/setup/state.js +2 -0
- package/src/component.js +35 -28
- package/src/components/FPScounter.js +1406 -1391
- package/src/components/RouterView.js +1 -1
- package/src/components/index.js +0 -2
- package/src/engines/L3/element.js +9 -0
- package/src/engines/L3/launch.js +3 -1
- package/src/{focus.js → focus/focus.js} +6 -18
- package/src/focus/helpers.js +29 -0
- package/src/focus/hover.js +93 -0
- package/src/launch.js +1 -0
- package/src/lib/codegenerator/generator.js +4 -0
- package/src/lib/lifecycle.js +7 -0
- package/src/router/router.js +29 -6
- package/vscode/data/template-attributes.json +335 -86
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v1.50.0
|
|
4
|
+
|
|
5
|
+
_27 feb 2026_
|
|
6
|
+
|
|
7
|
+
- Fixed issue with router navigation options not properly being overriden
|
|
8
|
+
- Fixed FPS counter component and made it into a separate importable component (`import { FPScounter } from @lightningjs/blits/components`)
|
|
9
|
+
- Added mouse (magic wand) support (including new `hover` lifecycle hook and `isHovered` state indicator)
|
|
10
|
+
- Updated template attributes for VScode extension
|
|
11
|
+
|
|
3
12
|
|
|
4
13
|
## v1.49.0
|
|
5
14
|
|
|
@@ -8,8 +17,8 @@ _20 feb 2026_
|
|
|
8
17
|
- Added `data-blits-renderstate` inspector attribute
|
|
9
18
|
- Fixed `data-blits-hasfocus` inspector attribute to always be available
|
|
10
19
|
- Added boolean attribute to `idle` lifecycle hook to indicate `idle` or `active` state
|
|
11
|
-
- Fixed type definitions for
|
|
12
|
-
- Fixed double
|
|
20
|
+
- Fixed type definitions for router beforeEach hooks
|
|
21
|
+
- Fixed double processing of reactivity guard
|
|
13
22
|
|
|
14
23
|
## v1.48.0
|
|
15
24
|
|
package/index.d.ts
CHANGED
|
@@ -170,6 +170,8 @@ declare module '@lightningjs/blits' {
|
|
|
170
170
|
* Fires when the Component is being destroyed and removed.
|
|
171
171
|
*/
|
|
172
172
|
destroy?: () => void;
|
|
173
|
+
hover?: () => void;
|
|
174
|
+
unhover?: () => void;
|
|
173
175
|
/**
|
|
174
176
|
* Fires upon each frame start (allowing you to tap directly into the renderloop)
|
|
175
177
|
*
|
|
@@ -394,6 +396,13 @@ declare module '@lightningjs/blits' {
|
|
|
394
396
|
*/
|
|
395
397
|
hasFocus: boolean,
|
|
396
398
|
|
|
399
|
+
/**
|
|
400
|
+
* Indicates whether the component currently is hovered
|
|
401
|
+
*
|
|
402
|
+
* @returns Boolean
|
|
403
|
+
*/
|
|
404
|
+
isHovered: boolean,
|
|
405
|
+
|
|
397
406
|
/**
|
|
398
407
|
* Listen to events emitted by other components
|
|
399
408
|
*/
|
|
@@ -1253,6 +1262,16 @@ declare module '@lightningjs/blits' {
|
|
|
1253
1262
|
* ```
|
|
1254
1263
|
*/
|
|
1255
1264
|
announcerOptions?: AnnouncerUtteranceOptions,
|
|
1265
|
+
/**
|
|
1266
|
+
* Enable mouse support (hover and click-to-focus).
|
|
1267
|
+
*
|
|
1268
|
+
* When set to `true`, pointer movement over the canvas updates hover state on components
|
|
1269
|
+
* and click dispatches focus and Enter key input to the component under the cursor.
|
|
1270
|
+
* When set to `false`, no mouse or pointer listeners are registered.
|
|
1271
|
+
*
|
|
1272
|
+
* @default false
|
|
1273
|
+
*/
|
|
1274
|
+
enableMouse?: boolean,
|
|
1256
1275
|
/**
|
|
1257
1276
|
* Maximum FPS at which the App will be rendered
|
|
1258
1277
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lightningjs/blits",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.50.0",
|
|
4
4
|
"description": "Blits: The Lightning 3 App Development Framework",
|
|
5
5
|
"bin": "bin/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -29,7 +29,8 @@
|
|
|
29
29
|
"default": "./src/plugins/storage/storage.js"
|
|
30
30
|
},
|
|
31
31
|
"./symbols": "./src/lib/symbols.js",
|
|
32
|
-
"./blitsFileConverter": "./src/lib/blitsfileconverter/blitsfileconverter.js"
|
|
32
|
+
"./blitsFileConverter": "./src/lib/blitsfileconverter/blitsfileconverter.js",
|
|
33
|
+
"./components": "./src/components/FPScounter.js"
|
|
33
34
|
},
|
|
34
35
|
"scripts": {
|
|
35
36
|
"test": "c8 node scripts/runTests.js",
|
package/src/application.js
CHANGED
|
@@ -15,12 +15,14 @@
|
|
|
15
15
|
* SPDX-License-Identifier: Apache-2.0
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import Component from './component.js'
|
|
19
|
-
import { default as Focus, keyUpCallbacks } from './focus.js'
|
|
20
|
-
import
|
|
18
|
+
import { default as Component, componentMap } from './component.js'
|
|
19
|
+
import { default as Focus, keyUpCallbacks } from './focus/focus.js'
|
|
20
|
+
import Hover from './focus/hover.js'
|
|
21
21
|
|
|
22
|
+
import Settings from './settings.js'
|
|
22
23
|
import symbols from './lib/symbols.js'
|
|
23
24
|
import { DEFAULT_HOLD_TIMEOUT_MS, DEFAULT_KEYMAP } from './constants.js'
|
|
25
|
+
import { renderer } from './launch.js'
|
|
24
26
|
|
|
25
27
|
/**
|
|
26
28
|
* Merged keyMap (default + custom settings).
|
|
@@ -34,13 +36,24 @@ const Application = (config) => {
|
|
|
34
36
|
|
|
35
37
|
let keyDownHandler
|
|
36
38
|
let keyUpHandler
|
|
39
|
+
let mouseMoveHandler
|
|
40
|
+
let mouseClickHandler
|
|
41
|
+
let updateCanvasRect
|
|
37
42
|
let holdTimeout
|
|
38
43
|
let lastInputTime = 0
|
|
39
44
|
let lastInputKey = null
|
|
45
|
+
let mouseListenersAdded = false
|
|
40
46
|
|
|
41
47
|
config.hooks[symbols.destroy] = function () {
|
|
42
48
|
document.removeEventListener('keydown', keyDownHandler)
|
|
43
49
|
document.removeEventListener('keyup', keyUpHandler)
|
|
50
|
+
if (mouseListenersAdded) {
|
|
51
|
+
document.removeEventListener('mousemove', mouseMoveHandler)
|
|
52
|
+
document.removeEventListener('click', mouseClickHandler)
|
|
53
|
+
window.removeEventListener('resize', updateCanvasRect)
|
|
54
|
+
window.removeEventListener('scroll', updateCanvasRect)
|
|
55
|
+
Hover.clear()
|
|
56
|
+
}
|
|
44
57
|
}
|
|
45
58
|
|
|
46
59
|
config.hooks[symbols.init] = function () {
|
|
@@ -57,6 +70,8 @@ const Application = (config) => {
|
|
|
57
70
|
/** @type {number} Input throttle time in milliseconds (0 = disabled) */
|
|
58
71
|
const throttleMs = Settings.get('inputThrottle', 0)
|
|
59
72
|
|
|
73
|
+
const mouseEnabled = Settings.get('enableMouse', false)
|
|
74
|
+
|
|
60
75
|
keyDownHandler = async (e) => {
|
|
61
76
|
const currentTime = performance.now()
|
|
62
77
|
|
|
@@ -89,6 +104,7 @@ const Application = (config) => {
|
|
|
89
104
|
}
|
|
90
105
|
|
|
91
106
|
Focus.input(key, e)
|
|
107
|
+
if (mouseEnabled === true) Hover.clear()
|
|
92
108
|
clearTimeout(holdTimeout)
|
|
93
109
|
holdTimeout = setTimeout(
|
|
94
110
|
() => {
|
|
@@ -108,8 +124,84 @@ const Application = (config) => {
|
|
|
108
124
|
Focus.hold = false
|
|
109
125
|
}
|
|
110
126
|
|
|
127
|
+
let lastMoved = 0
|
|
128
|
+
let currentNode = undefined
|
|
129
|
+
let currentComponent = undefined
|
|
130
|
+
let canvasRect = null
|
|
131
|
+
|
|
132
|
+
updateCanvasRect = () => {
|
|
133
|
+
if (renderer.canvas !== undefined) {
|
|
134
|
+
canvasRect = renderer.canvas.getBoundingClientRect()
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// limit the amount of move events per time frame
|
|
139
|
+
mouseMoveHandler = (e) => {
|
|
140
|
+
if (e.timeStamp - lastMoved < 100) return
|
|
141
|
+
lastMoved = e.timeStamp
|
|
142
|
+
|
|
143
|
+
this.$emit('mouse::move', e)
|
|
144
|
+
|
|
145
|
+
if (canvasRect === null && renderer.canvas != null) {
|
|
146
|
+
updateCanvasRect()
|
|
147
|
+
}
|
|
148
|
+
if (canvasRect === null) {
|
|
149
|
+
return
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const stage = renderer.stage
|
|
153
|
+
if (stage == null) {
|
|
154
|
+
return
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Convert viewport → canvas display coordinates
|
|
158
|
+
const x = e.clientX - canvasRect.left
|
|
159
|
+
const y = e.clientY - canvasRect.top
|
|
160
|
+
|
|
161
|
+
const node = stage.getNodeFromPosition({ x, y })
|
|
162
|
+
|
|
163
|
+
if (node === null) {
|
|
164
|
+
currentComponent = undefined
|
|
165
|
+
Hover.clear()
|
|
166
|
+
return
|
|
167
|
+
}
|
|
168
|
+
if (node === currentNode) return
|
|
169
|
+
|
|
170
|
+
currentNode = node
|
|
171
|
+
currentComponent = componentMap.get(currentNode)
|
|
172
|
+
|
|
173
|
+
if (currentComponent === undefined) {
|
|
174
|
+
Hover.clear()
|
|
175
|
+
return
|
|
176
|
+
}
|
|
177
|
+
Hover.set(currentComponent)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
mouseClickHandler = () => {
|
|
181
|
+
if (currentComponent === undefined || currentComponent.eol === true) return
|
|
182
|
+
|
|
183
|
+
const e = new KeyboardEvent('keydown', {
|
|
184
|
+
key: 'Enter',
|
|
185
|
+
code: 'Enter',
|
|
186
|
+
keyCode: 13,
|
|
187
|
+
bubbles: true,
|
|
188
|
+
cancelable: true,
|
|
189
|
+
composed: true,
|
|
190
|
+
})
|
|
191
|
+
currentComponent.$focus()
|
|
192
|
+
currentComponent.$input(e)
|
|
193
|
+
}
|
|
194
|
+
|
|
111
195
|
document.addEventListener('keydown', keyDownHandler)
|
|
112
196
|
document.addEventListener('keyup', keyUpHandler)
|
|
197
|
+
if (mouseEnabled === true) {
|
|
198
|
+
updateCanvasRect()
|
|
199
|
+
document.addEventListener('mousemove', mouseMoveHandler)
|
|
200
|
+
document.addEventListener('click', mouseClickHandler)
|
|
201
|
+
window.addEventListener('resize', updateCanvasRect)
|
|
202
|
+
window.addEventListener('scroll', updateCanvasRect)
|
|
203
|
+
mouseListenersAdded = true
|
|
204
|
+
}
|
|
113
205
|
|
|
114
206
|
// next tick
|
|
115
207
|
setTimeout(() => Focus.set(this))
|
|
@@ -16,8 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import symbols from '../../lib/symbols.js'
|
|
19
|
-
import Focus from '../../focus.js'
|
|
20
|
-
import { keyUpCallbacks, getComponentWithInputEvent } from '../../focus.js'
|
|
19
|
+
import { default as Focus, keyUpCallbacks, getComponentWithInputEvent } from '../../focus/focus.js'
|
|
21
20
|
import eventListeners from '../../lib/eventListeners.js'
|
|
22
21
|
import { trigger } from '../../lib/reactivity/effect.js'
|
|
23
22
|
import { Log } from '../../lib/log.js'
|
|
@@ -36,6 +36,8 @@ export default (component, state = () => {}) => {
|
|
|
36
36
|
// add built-in hasFocus key
|
|
37
37
|
stateKeys.push(['hasFocus'])
|
|
38
38
|
}
|
|
39
|
+
// add built-in isHovered key
|
|
40
|
+
stateKeys.push(['isHovered'])
|
|
39
41
|
const stateKeysLength = stateKeys.length
|
|
40
42
|
|
|
41
43
|
for (let i = 0; i < stateKeysLength; i++) {
|
package/src/component.js
CHANGED
|
@@ -43,6 +43,9 @@ const required = (name) => {
|
|
|
43
43
|
throw new Error(`Parameter ${name} is required`)
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
// map that holds component to their holder node
|
|
47
|
+
export const componentMap = new WeakMap()
|
|
48
|
+
|
|
46
49
|
/**
|
|
47
50
|
* @typedef {function} BlitsComponentFactory
|
|
48
51
|
* @param {object} opts - The options for the component instance
|
|
@@ -216,6 +219,10 @@ const Component = (name = required('name'), config = required('config')) => {
|
|
|
216
219
|
// such as rotation and scale to components)
|
|
217
220
|
this[symbols.holder] = parentEl
|
|
218
221
|
|
|
222
|
+
if (parentEl !== undefined && parentEl.node !== undefined) {
|
|
223
|
+
componentMap.set(parentEl.node, this)
|
|
224
|
+
}
|
|
225
|
+
|
|
219
226
|
// generate an internal id (simple counter)
|
|
220
227
|
this[symbols.id] = createInternalId()
|
|
221
228
|
|
|
@@ -238,6 +245,9 @@ const Component = (name = required('name'), config = required('config')) => {
|
|
|
238
245
|
// add hasFocus key in
|
|
239
246
|
this[symbols.originalState]['hasFocus'] = false
|
|
240
247
|
|
|
248
|
+
// add isHovered key in
|
|
249
|
+
this[symbols.originalState]['isHovered'] = false
|
|
250
|
+
|
|
241
251
|
// generate a reactive state (using the result of previously execute state function)
|
|
242
252
|
// and store it
|
|
243
253
|
this[symbols.state] = reactive(this[symbols.originalState], Settings.get('reactivityMode'))
|
|
@@ -291,38 +301,35 @@ const Component = (name = required('name'), config = required('config')) => {
|
|
|
291
301
|
|
|
292
302
|
// register hooks if component has hooks specified
|
|
293
303
|
if (config.hooks) {
|
|
294
|
-
//
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
this[symbols.rendererEventListeners].push({ event: 'frameTick', cb })
|
|
301
|
-
}
|
|
304
|
+
// frame tick event
|
|
305
|
+
if (config.hooks.frameTick) {
|
|
306
|
+
const cb = (_r, data) => emit('frameTick', this[symbols.identifier], this, [data])
|
|
307
|
+
renderer.on('frameTick', cb)
|
|
308
|
+
this[symbols.rendererEventListeners].push({ event: 'frameTick', cb })
|
|
309
|
+
}
|
|
302
310
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
}
|
|
308
|
-
const activeCb = () => {
|
|
309
|
-
emit('idle', this[symbols.identifier], this, [false])
|
|
310
|
-
}
|
|
311
|
-
renderer.on('idle', idleCb)
|
|
312
|
-
renderer.on('active', activeCb)
|
|
313
|
-
this[symbols.rendererEventListeners].push({ event: 'idle', cb: idleCb })
|
|
314
|
-
this[symbols.rendererEventListeners].push({ event: 'active', cb: activeCb })
|
|
311
|
+
// idle event
|
|
312
|
+
if (config.hooks.idle) {
|
|
313
|
+
const idleCb = () => {
|
|
314
|
+
emit('idle', this[symbols.identifier], this, [true])
|
|
315
315
|
}
|
|
316
|
+
const activeCb = () => {
|
|
317
|
+
emit('idle', this[symbols.identifier], this, [false])
|
|
318
|
+
}
|
|
319
|
+
renderer.on('idle', idleCb)
|
|
320
|
+
renderer.on('active', activeCb)
|
|
321
|
+
this[symbols.rendererEventListeners].push({ event: 'idle', cb: idleCb })
|
|
322
|
+
this[symbols.rendererEventListeners].push({ event: 'active', cb: activeCb })
|
|
323
|
+
}
|
|
316
324
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
}
|
|
322
|
-
renderer.on('fpsUpdate', cb)
|
|
323
|
-
this[symbols.rendererEventListeners].push({ event: 'fpsUpdate', cb })
|
|
325
|
+
// fpsUpdate event
|
|
326
|
+
if (config.hooks.fpsUpdate) {
|
|
327
|
+
const cb = (_r, data) => {
|
|
328
|
+
emit('fpsUpdate', this[symbols.identifier], this, [data.fps])
|
|
324
329
|
}
|
|
325
|
-
|
|
330
|
+
renderer.on('fpsUpdate', cb)
|
|
331
|
+
this[symbols.rendererEventListeners].push({ event: 'fpsUpdate', cb })
|
|
332
|
+
}
|
|
326
333
|
|
|
327
334
|
// inBounds event emiting a lifecycle attach event
|
|
328
335
|
if (config.hooks.attach) {
|