@lightningjs/blits 1.40.1 → 1.42.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 CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## v1.42.0
4
+
5
+ _24 Oct 2025_
6
+
7
+
8
+ - Added effects prop support to Sprite component
9
+ - Added support for redirecting in before hooks returning a route object (with updated path)
10
+ - Fixed meta options on routes functionality
11
+ - Enhanced router hook test coverage
12
+ - Fixed scale animation snapping issue when using object notation with transitions
13
+
14
+ ## v1.41.0
15
+
16
+ _16 Oct 2025_
17
+
18
+ - Improved overall test coverage to 86%
19
+ - Filtered out inspector-data entirely in production
20
+ - Improved vite plugin that handles computed props to be more robust
21
+ - Fixed issue with global reactive object that are deeply nested
22
+ - Bumped MSDF generator dependency to address security issues in dependencies
3
23
 
4
24
  ## v1.40.1
5
25
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightningjs/blits",
3
- "version": "1.40.1",
3
+ "version": "1.42.0",
4
4
  "description": "Blits: The Lightning 3 App Development Framework",
5
5
  "bin": "bin/index.js",
6
6
  "exports": {
@@ -54,7 +54,7 @@
54
54
  "tape": "^5.5.0"
55
55
  },
56
56
  "dependencies": {
57
- "@lightningjs/msdf-generator": "^1.1.1",
57
+ "@lightningjs/msdf-generator": "^1.2.0",
58
58
  "@lightningjs/renderer": "^2.18.2"
59
59
  },
60
60
  "repository": {
@@ -78,7 +78,7 @@ export default {
78
78
  const index = this[symbols.intervals].indexOf(intervalId)
79
79
  if (index > -1) {
80
80
  this[symbols.intervals].splice(index, 1)
81
- clearTimeout(intervalId)
81
+ clearInterval(intervalId)
82
82
  }
83
83
  },
84
84
  writable: false,
@@ -49,6 +49,7 @@ export default () =>
49
49
  elementConfigs[0]['h'] = parent.node.height * (100 / 100)
50
50
  elementConfigs[0]['texture'] = component.texture
51
51
  elementConfigs[0]['color'] = component.color
52
+ elementConfigs[0]['effects'] = component.effects
52
53
  elms[0].populate(elementConfigs[0])
53
54
 
54
55
  if (inSlot === true) {
@@ -80,10 +81,13 @@ export default () =>
80
81
  function anonymous(component, elms, context, components, rootComponent, effect) {
81
82
  elms[0].set('color', component.color)
82
83
  },
84
+ function anonymous(component, elms, context, components, rootComponent, effect) {
85
+ elms[0].set('effects', component.effects)
86
+ },
83
87
  ],
84
88
  context: {},
85
89
  },
86
- props: ['image', 'map', 'frame', 'color'],
90
+ props: ['image', 'map', 'frame', 'color', 'effects'],
87
91
  state() {
88
92
  return {
89
93
  spriteTexture: null,
@@ -222,6 +222,12 @@ const colorMap = {
222
222
  */
223
223
  let textDefaults = null
224
224
 
225
+ /**
226
+ * Inspector enabled flag (initialized on first use).
227
+ * @type {boolean|null}
228
+ */
229
+ let inspectorEnabled = null
230
+
225
231
  /**
226
232
  * @typedef {import('../../component.js').BlitsElement} BlitsElement
227
233
  *
@@ -351,7 +357,8 @@ const propsTransformer = {
351
357
  this.props['scaleY'] = v.y
352
358
  }
353
359
  } else {
354
- this.props['scale'] = v
360
+ this.props['scaleX'] = v
361
+ this.props['scaleY'] = v
355
362
  }
356
363
  },
357
364
  set show(v) {
@@ -465,6 +472,11 @@ const propsTransformer = {
465
472
  }
466
473
  },
467
474
  set 'inspector-data'(v) {
475
+ // Skip processing if inspector is not enabled for performance optimization
476
+ if (inspectorEnabled === false) {
477
+ return
478
+ }
479
+
468
480
  if (typeof v === 'object' || (isObjectString(v) === true && (v = parseToObject(v)))) {
469
481
  this.props['data'] = v
470
482
  }
@@ -567,10 +579,11 @@ const Element = {
567
579
  for (let i = 0; i < propsKeys.length; i++) {
568
580
  // todo: fix code duplication
569
581
  if (isTransition(value) === true) {
570
- return this.animate(propsKeys[i], this.props.props[propsKeys[i]], value.transition)
582
+ this.animate(propsKeys[i], this.props.props[propsKeys[i]], value.transition)
583
+ } else {
584
+ // set the prop to the value on the node
585
+ this.node[propsKeys[i]] = this.props.props[propsKeys[i]]
571
586
  }
572
- // set the prop to the value on the node
573
- this.node[propsKeys[i]] = this.props.props[propsKeys[i]]
574
587
  }
575
588
  }
576
589
 
@@ -740,6 +753,9 @@ export default (config, component) => {
740
753
  fontFamily: Settings.get('defaultFont', 'sans-serif'),
741
754
  }
742
755
  }
756
+ if (inspectorEnabled === null) {
757
+ inspectorEnabled = Settings.get('inspector', false)
758
+ }
743
759
  return Object.assign(Object.create(Element), {
744
760
  props: Object.assign(Object.create(propsTransformer), { props: {} }),
745
761
  scheduledTransitions: {},
@@ -179,6 +179,9 @@ const generateElementCode = function (
179
179
 
180
180
  if (key === 'key') return
181
181
 
182
+ // Skip inspector-data in production builds for performance optimization
183
+ if (key === 'inspector-data' && !isDev) return
184
+
182
185
  const value = templateObject[key]
183
186
 
184
187
  if (isReactiveKey(key)) {
@@ -46,7 +46,7 @@ const reactiveProxy = (original, _parent = null, _key, global) => {
46
46
 
47
47
  const handler = {
48
48
  get(target, key, receiver) {
49
- // return the original object instead of the proxied
49
+ // indicate that this is a proxy object
50
50
  if (key === symbols.isProxy) {
51
51
  return true
52
52
  }
@@ -62,7 +62,7 @@ const reactiveProxy = (original, _parent = null, _key, global) => {
62
62
  track(target, key, global)
63
63
  }
64
64
  // create a new reactive proxy
65
- return reactiveProxy(getRaw(target[key]), target, key)
65
+ return reactiveProxy(getRaw(target[key]), target, key, global)
66
66
  }
67
67
  // augment array path methods (that change the length of the array)
68
68
  if (arrayPatchMethods.indexOf(key) !== -1) {
@@ -90,7 +90,7 @@ const reactiveProxy = (original, _parent = null, _key, global) => {
90
90
  track(target, key, global)
91
91
  }
92
92
  // create a new reactive proxy
93
- return reactiveProxy(getRaw(target[key]), target, key)
93
+ return reactiveProxy(getRaw(target[key]), target, key, global)
94
94
  }
95
95
 
96
96
  // handling all other types
@@ -15,6 +15,51 @@
15
15
  * SPDX-License-Identifier: Apache-2.0
16
16
  */
17
17
 
18
+ // Utility function: Character-wise brace matcher that handles strings and comments
19
+ const findMatchingBrace = (text, startPos) => {
20
+ let braceLevel = 1
21
+ let position = startPos + 1
22
+ let inString = false
23
+ let stringChar = ''
24
+
25
+ for (; position < text.length && braceLevel > 0; position++) {
26
+ const char = text[position]
27
+
28
+ // Handle string boundaries (ignore escaped quotes)
29
+ if (!inString && (char === '"' || char === "'" || char === '`')) {
30
+ inString = true
31
+ stringChar = char
32
+ } else if (inString && char === stringChar && text[position - 1] !== '\\') {
33
+ inString = false
34
+ }
35
+ // Skip comments when not in strings
36
+ else if (!inString && char === '/' && text[position + 1] === '/') {
37
+ // Skip line comment
38
+ position = text.indexOf('\n', position) || text.length - 1
39
+ } else if (!inString && char === '/' && text[position + 1] === '*') {
40
+ // Skip block comment
41
+ position = text.indexOf('*/', position + 2) || text.length - 1
42
+ }
43
+ // Count braces only outside strings and comments
44
+ else if (!inString) {
45
+ if (char === '{') braceLevel++
46
+ else if (char === '}') braceLevel--
47
+ }
48
+ }
49
+
50
+ return braceLevel === 0 ? position : -1
51
+ }
52
+
53
+ // Find computed object boundaries in component config
54
+ const findComputedObjectEnd = (configObject, computedStart) => {
55
+ return findMatchingBrace(configObject, computedStart)
56
+ }
57
+
58
+ // Find individual computed function boundaries
59
+ const findComputedFunctionEnd = (computedObj, openingBracePos) => {
60
+ return findMatchingBrace(computedObj, openingBracePos)
61
+ }
62
+
18
63
  export default (code) => {
19
64
  // Find component declarations
20
65
  const componentRegex =
@@ -33,18 +78,10 @@ export default (code) => {
33
78
  // Find start position of the computed block
34
79
  const computedStart = computedKeywordMatch.index + computedKeywordMatch[0].length - 1 // Position of the opening brace
35
80
 
36
- // Find the matching closing brace with balance tracking
37
- let braceLevel = 1
38
- let pos = computedStart + 1
81
+ // Find the matching closing brace for computed object in config
82
+ const computedEnd = findComputedObjectEnd(configObject, computedStart)
39
83
 
40
- while (braceLevel > 0 && pos < configObject.length) {
41
- if (configObject[pos] === '{') braceLevel++
42
- else if (configObject[pos] === '}') braceLevel--
43
- pos++
44
- }
45
-
46
- if (braceLevel === 0) {
47
- const computedEnd = pos
84
+ if (computedEnd !== -1) {
48
85
  const computedObj = configObject.substring(computedStart, computedEnd)
49
86
 
50
87
  // Process each computed property
@@ -67,18 +104,10 @@ export default (code) => {
67
104
  const openingBracePos = computedObj.indexOf('{', funcNamePos)
68
105
  if (openingBracePos === -1) break
69
106
 
70
- // Find closing brace with proper nesting
71
- let braceLevel = 1
72
- let closingBracePos = openingBracePos + 1
73
-
74
- while (braceLevel > 0 && closingBracePos < computedObj.length) {
75
- if (computedObj[closingBracePos] === '{') braceLevel++
76
- else if (computedObj[closingBracePos] === '}') braceLevel--
77
- closingBracePos++
78
- }
107
+ // Find closing brace for individual computed function
108
+ const closingBracePos = findComputedFunctionEnd(computedObj, openingBracePos)
79
109
 
80
- if (braceLevel !== 0) {
81
- // Could not find matching brace
110
+ if (closingBracePos === -1) {
82
111
  break
83
112
  }
84
113
 
package/src/plugin.js CHANGED
@@ -15,7 +15,7 @@ const registerPlugin = (plugin, nameOrOptions = '', options = {}) => {
15
15
  }
16
16
 
17
17
  if (typeof plugin === 'function') {
18
- if (name === undefined) {
18
+ if (name === undefined || name === '') {
19
19
  throw Error('Error registering plugin: name is required for plugin')
20
20
  }
21
21
  plugins[name] = { plugin, options }
@@ -220,6 +220,7 @@ const makeRouteObject = (route, overrides) => {
220
220
  hooks: route.hooks || {},
221
221
  data: { ...route.data, ...navigationData, ...overrides.queryParams },
222
222
  params: overrides.params || {},
223
+ meta: route.meta || {},
223
224
  }
224
225
 
225
226
  return cleanRoute
@@ -249,21 +250,25 @@ export const navigate = async function () {
249
250
  currentRoute = route
250
251
 
251
252
  if (route) {
253
+ const currentPath = currentRoute.path
252
254
  let beforeEachResult
253
255
  if (this.parent[symbols.routerHooks]) {
254
256
  const hooks = this.parent[symbols.routerHooks]
255
257
  if (hooks.beforeEach) {
256
258
  beforeEachResult = await hooks.beforeEach.call(this.parent, route, previousRoute)
257
259
  if (isString(beforeEachResult)) {
260
+ currentRoute = previousRoute
258
261
  to(beforeEachResult)
259
262
  return
260
263
  }
264
+ // If the resolved result is an object, redirect if the path in the object was changed
265
+ if (isObject(beforeEachResult) === true && beforeEachResult.path !== currentPath) {
266
+ currentRoute = previousRoute
267
+ to(beforeEachResult.path, beforeEachResult.data, beforeEachResult.options)
268
+ return
269
+ }
261
270
  }
262
271
  }
263
- // If the resolved result is an object, assign it to the target route object
264
- if (isObject(beforeEachResult) === true) {
265
- route = beforeEachResult
266
- }
267
272
 
268
273
  let beforeHookOutput
269
274
  if (route.hooks.before) {
@@ -273,10 +278,12 @@ export const navigate = async function () {
273
278
  to(beforeHookOutput)
274
279
  return
275
280
  }
276
- }
277
- // If the resolved result is an object, assign it to the target route object
278
- if (isObject(beforeHookOutput) === true) {
279
- route = beforeHookOutput
281
+ // If the resolved result is an object, redirect if the path in the object was changed
282
+ if (isObject(beforeHookOutput) === true && beforeHookOutput.path !== currentPath) {
283
+ currentRoute = previousRoute
284
+ to(beforeHookOutput.path, beforeHookOutput.data, beforeHookOutput.options)
285
+ return
286
+ }
280
287
  }
281
288
  // add the previous route (technically still the current route at this point)
282
289
  // into the history stack when inHistory is true and we're not navigating back