@lightningjs/blits 2.8.0 → 2.8.2

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,18 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.8.2
4
+
5
+ _22 jul 2026_
6
+
7
+ - Added end of life guard to layout retrigger
8
+
9
+ ## 2.8.1
10
+
11
+ _22 jul 2026_
12
+
13
+ - Fixed cleanup of reactive effects with multiple dependencies
14
+ - Fixed stale reactive effects when removing items from (nested) loops
15
+
3
16
  ## 2.8.0
4
17
 
5
18
  _16 jul 2026_
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightningjs/blits",
3
- "version": "2.8.0",
3
+ "version": "2.8.2",
4
4
  "description": "Blits: The Lightning 3 App Development Framework",
5
5
  "bin": "bin/index.js",
6
6
  "exports": {
@@ -20,7 +20,7 @@ import { default as Focus, keyUpCallbacks, getComponentWithInputEvent } from '..
20
20
  import eventListeners from '../../lib/eventListeners.js'
21
21
  import { trigger } from '../../lib/reactivity/effect.js'
22
22
  import { Log } from '../../lib/log.js'
23
- import { removeGlobalEffects } from '../../lib/reactivity/effect.js'
23
+ import { removeEffects } from '../../lib/reactivity/effect.js'
24
24
  import { renderer } from '../../launch.js'
25
25
  import { keyMap } from '../../application.js'
26
26
  import { platform } from '../../platform.js'
@@ -130,7 +130,7 @@ export default {
130
130
 
131
131
  deleteChildren(this[symbols.children])
132
132
  this[symbols.children].length = 0
133
- removeGlobalEffects(this[symbols.effects])
133
+ removeEffects(this[symbols.effects])
134
134
 
135
135
  this[symbols.state] = {}
136
136
 
@@ -171,12 +171,12 @@ export default {
171
171
  enumerable: true,
172
172
  configurable: false,
173
173
  },
174
- [symbols.removeGlobalEffects]: {
174
+ [symbols.removeEffects]: {
175
175
  /**
176
176
  * @this {import('../../component').BlitsComponent}
177
177
  */
178
178
  value: function (effects = []) {
179
- removeGlobalEffects(effects)
179
+ removeEffects(effects)
180
180
  },
181
181
  writable: false,
182
182
  enumerable: false,
@@ -674,6 +674,7 @@ const Element = {
674
674
  if (this.config.parent.props !== undefined && this.config.parent.props.__layout === true) {
675
675
  this.config.parent.triggerLayout(this.config.parent.props)
676
676
  this.node.on('loaded', () => {
677
+ if (this.eol === true) return
677
678
  this.config.parent.triggerLayout(this.config.parent.props)
678
679
  })
679
680
  }
@@ -559,6 +559,7 @@ const generateForLoopCode = function (templateObject, parent) {
559
559
  keys.add('' + ${interpolate(key, '') || 'l'})
560
560
  }
561
561
  }
562
+
562
563
  `)
563
564
 
564
565
  // keep track of the index in the render code so we can inject
@@ -570,9 +571,9 @@ const generateForLoopCode = function (templateObject, parent) {
570
571
  created.length = 0
571
572
  const length = rawCollection.length
572
573
 
573
- component !== null && component[Symbol.for('removeGlobalEffects')](effects[${forStartCounter}])
574
+ component !== null && component[Symbol.for('removeEffects')](effects[${forStartCounter}])
574
575
 
575
- const effectsToRemove = new Set(effects[${forStartCounter}].slice(0))
576
+ const effectsToRemove = new Set(effects[${forStartCounter}])
576
577
  if (effectsToRemove.size > 0) {
577
578
  const componentEffects = component?.[Symbol.for('effects')] || []
578
579
  let writeIndex = 0
@@ -29,23 +29,21 @@ export const resumeTracking = () => {
29
29
  }
30
30
 
31
31
  const objectMap = new WeakMap()
32
- const globalEffectsMap = new Map()
32
+ const effectDependenciesMap = new WeakMap()
33
33
 
34
- export const removeGlobalEffects = (effectsToRemove) => {
35
- if (globalEffectsMap.size === 0 || effectsToRemove.length === 0) return
36
- const effectsToRemoveSet = new Set(effectsToRemove)
37
- for (const [effect, target] of globalEffectsMap) {
38
- if (!effectsToRemoveSet.has(effect)) continue
39
- const effectsSet = objectMap.get(target)
40
- if (effectsSet === undefined) continue
41
- for (const set of effectsSet.values()) {
42
- set.delete(effect)
43
- globalEffectsMap.delete(effect)
34
+ export const removeEffects = (effectsToRemove) => {
35
+ for (let i = 0; i < effectsToRemove.length; i++) {
36
+ const effect = effectsToRemove[i]
37
+ const dependencies = effectDependenciesMap.get(effect)
38
+ if (dependencies === undefined) continue
39
+ for (let j = 0; j < dependencies.length; j++) {
40
+ dependencies[j].delete(effect)
44
41
  }
42
+ effectDependenciesMap.delete(effect)
45
43
  }
46
44
  }
47
45
 
48
- export const track = (target, key, global = false) => {
46
+ export const track = (target, key) => {
49
47
  if (currentEffect !== null) {
50
48
  if (paused) {
51
49
  return
@@ -65,9 +63,15 @@ export const track = (target, key, global = false) => {
65
63
  effects = new Set()
66
64
  effectsMap.set(key, effects)
67
65
  }
68
- effects.add(currentEffect)
69
-
70
- if (global === true) globalEffectsMap.set(currentEffect, target)
66
+ if (effects.has(currentEffect) === false) {
67
+ let dependencies = effectDependenciesMap.get(currentEffect)
68
+ if (dependencies === undefined) {
69
+ dependencies = []
70
+ effectDependenciesMap.set(currentEffect, dependencies)
71
+ }
72
+ dependencies.push(effects)
73
+ effects.add(currentEffect)
74
+ }
71
75
  }
72
76
  }
73
77
 
@@ -28,7 +28,7 @@ export const getRaw = (value) => {
28
28
  return raw ? getRaw(raw) : value
29
29
  }
30
30
 
31
- const reactiveProxy = (original, _parent = null, _key, global) => {
31
+ const reactiveProxy = (original, _parent = null, _key) => {
32
32
  // only create a proxy for plain objects. Other classes, Blits components, renderer textures etc
33
33
  // are returned unmodified and will not become reactive
34
34
  if (
@@ -59,10 +59,10 @@ const reactiveProxy = (original, _parent = null, _key, global) => {
59
59
  if (Array.isArray(target) === true) {
60
60
  if (typeof target[key] === 'object' && target[key] !== null) {
61
61
  if (Array.isArray(target[key]) === true) {
62
- track(target, key, global)
62
+ track(target, key)
63
63
  }
64
64
  // create a new reactive proxy
65
- return reactiveProxy(getRaw(target[key]), target, key, global)
65
+ return reactiveProxy(getRaw(target[key]), target, key)
66
66
  }
67
67
  // augment array path methods (that change the length of the array)
68
68
  if (arrayPatchMethods.indexOf(key) !== -1) {
@@ -87,15 +87,15 @@ const reactiveProxy = (original, _parent = null, _key, global) => {
87
87
  // handling objects (but not null values, which have object type in JS)
88
88
  if (typeof target[key] === 'object' && target[key] !== null) {
89
89
  if (Array.isArray(target[key]) === true) {
90
- track(target, key, global)
90
+ track(target, key)
91
91
  }
92
92
  // create a new reactive proxy
93
- return reactiveProxy(getRaw(target[key]), target, key, global)
93
+ return reactiveProxy(getRaw(target[key]), target, key)
94
94
  }
95
95
 
96
96
  // handling all other types
97
97
  // track the key on the target
98
- track(target, key, global)
98
+ track(target, key)
99
99
  // return the reflected value
100
100
  return Reflect.get(target, key, receiver)
101
101
  },
@@ -147,7 +147,7 @@ const reactiveProxy = (original, _parent = null, _key, global) => {
147
147
  return proxy
148
148
  }
149
149
 
150
- const reactiveDefineProperty = (target, global) => {
150
+ const reactiveDefineProperty = (target) => {
151
151
  Object.keys(target).forEach((key) => {
152
152
  let internalValue = target[key]
153
153
 
@@ -168,7 +168,7 @@ const reactiveDefineProperty = (target, global) => {
168
168
  enumerable: true,
169
169
  configurable: true,
170
170
  get() {
171
- track(target, key, global)
171
+ track(target, key)
172
172
  return internalValue
173
173
  },
174
174
  set(newValue) {
@@ -187,14 +187,12 @@ const reactiveDefineProperty = (target, global) => {
187
187
 
188
188
  const hasProxySupport = typeof Proxy !== 'undefined'
189
189
 
190
- export const reactive = (target, mode = 'Proxy', global = false) => {
190
+ export const reactive = (target, mode = 'Proxy') => {
191
191
  // force defineProperty reactivity mode when no proxy support
192
192
  if (hasProxySupport === false) {
193
193
  mode = 'defineProperty'
194
194
  }
195
- return mode === 'defineProperty'
196
- ? reactiveDefineProperty(target, global)
197
- : reactiveProxy(target, undefined, undefined, global)
195
+ return mode === 'defineProperty' ? reactiveDefineProperty(target) : reactiveProxy(target)
198
196
  }
199
197
 
200
198
  export const memo = (raw) => {
@@ -68,7 +68,7 @@
68
68
  * @property {symbol} componentType
69
69
  * @property {symbol} isComponent
70
70
  * @property {symbol} effects
71
- * @property {symbol} removeGlobalEffects
71
+ * @property {symbol} removeEffects
72
72
  * @property {symbol} tagContent
73
73
  */
74
74
 
@@ -140,8 +140,8 @@ export default {
140
140
  isComponent: Symbol.for('isComponent'),
141
141
  // Symbol 'effects' utilized within generated code
142
142
  effects: Symbol.for('effects'),
143
- // Symbol 'removeGlobalEffects' utilized within generated code
144
- removeGlobalEffects: Symbol.for('removeGlobalEffects'),
145
- // Symbol 'removeGlobalEffects' utilized within generated code
143
+ // Symbol 'removeEffects' utilized within generated code
144
+ removeEffects: Symbol.for('removeEffects'),
145
+ // Symbol 'tagContent' utilized within generated code
146
146
  tagContent: Symbol.for('tagContent'),
147
147
  }