@lightningjs/blits 1.12.0 → 1.13.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.
@@ -13,12 +13,12 @@ jobs:
13
13
  - name: Trigger Deployment
14
14
  timeout-minutes: 5
15
15
  run: |
16
- response=$(curl -X POST -w "%{http_code}" -s -o /dev/null \
16
+ response=$(curl -X POST -d {} -H "Content-Type:application/json" -w "%{http_code}" -s -o /dev/null \
17
17
  --max-time 30 \
18
18
  --connect-timeout 10 \
19
19
  --retry 2 \
20
20
  --retry-delay 5 \
21
- ${{ secrets.WEBSITE_DEPLOY_URL }})
21
+ "${{ secrets.WEBSITE_DEPLOY_URL }}")
22
22
  if [ "$response" -ge 200 ] && [ "$response" -lt 300 ]; then
23
23
  echo "Deployment triggered successfully"
24
24
  else
package/CHANGELOG.md CHANGED
@@ -1,7 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## v1.13.0
4
+
5
+ _5 dec 2024_
6
+
7
+ - Added `padding` attribute to Layout component
8
+ - Added logging of Blits and Renderer version used in App
9
+ - Fixed show attribute for elements without predefined width and height
10
+ - Added automatic proxy fallback (when no browser support) for reactivity
11
+ - Fixed and added tests related to Blits Element
12
+ - Fixed memory issue with effects used in for-loop
13
+ - Fixed memory issue when changing props in dynamic shader effects
14
+ - Updated renderer to v2.8.0
15
+
3
16
  ## v1.12.0
4
17
 
18
+ _26 nov 2024_
19
+
5
20
  - Fixed `$hasFocus` state variable not being set when navigating back to a page with `keepAlive` enabled
6
21
  - Added `@updated`-event to Layout component
7
22
  - Fixed issue with simple config for Theme plugin
@@ -61,6 +61,30 @@ It's also possible to nest layouts. Simply place a new `<Layout>`-tag, with it's
61
61
 
62
62
  And of course you can nest _vertical_ Layouts inside a _horizontal_ one - and vice versa.
63
63
 
64
+ ### Padding
65
+
66
+ By default a `<Layout />`-tag will be resized to the exact dimensions as the content it is containing. The `padding`-attribute can be used to add spacing between the content and the edges of the Layout Component.
67
+
68
+ The `padding`-attribute accepts a `number` or an `object`. When passed a number, that padding will be applied equally to all sides. With an object value, the padding can be controlled for each side individually.
69
+
70
+ Valid keys in the _padding-object_ are: `top`, `bottom`, `left`, `right`, `x` and `y`. The `x` and `y` keys can be used to define the same values for `top` and `bottom`, and `left` and `right` in one go. When a value is not specified for a side, it will default to `0`.
71
+
72
+ ```xml
73
+ <Layout color="silver" padding="10" >
74
+ <Element w="40" h="40" color="red" />
75
+ <Element w="80" h="40" color="blue" />
76
+ <Element w="40" h="40" color="green" />
77
+ </Layout>
78
+ ```
79
+
80
+ ```xml
81
+ <Layout color="silver" padding="{x: 20, top: 30, bottom: 10}">
82
+ <Element w="40" h="40" color="red" />
83
+ <Element w="80" h="40" color="blue" />
84
+ <Element w="40" h="40" color="green" />
85
+ </Layout>
86
+ ```
87
+
64
88
  ### Transitioning layouts
65
89
 
66
90
  The `<Layout>`-component can also take into account when dimensions of children change with a transition applied to it (i.e. `<Element :w.transition="$myWidth">`). The component will recalculate the position of its children as the transition progresses, making sure that elements are always perfectly positioned relative to one another.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightningjs/blits",
3
- "version": "1.12.0",
3
+ "version": "1.13.0",
4
4
  "description": "Blits: The Lightning 3 App Development Framework",
5
5
  "bin": "bin/index.js",
6
6
  "exports": {
@@ -50,7 +50,7 @@
50
50
  },
51
51
  "dependencies": {
52
52
  "@lightningjs/msdf-generator": "^1.1.0",
53
- "@lightningjs/renderer": "^2.7.1"
53
+ "@lightningjs/renderer": "^2.8.0"
54
54
  },
55
55
  "repository": {
56
56
  "type": "git",
@@ -64,6 +64,14 @@ export default {
64
64
  enumerable: true,
65
65
  configurable: false,
66
66
  },
67
+ [symbols.removeGlobalEffects]: {
68
+ value: function (effects = []) {
69
+ removeGlobalEffects(effects)
70
+ },
71
+ writable: false,
72
+ enumerable: false,
73
+ configurable: false,
74
+ },
67
75
  select: {
68
76
  value: function (ref) {
69
77
  Log.warn('this.select is deprecated, use this.$select instead')
@@ -22,13 +22,57 @@ import { Log } from '../../lib/log.js'
22
22
  import symbols from '../../lib/symbols.js'
23
23
  import Settings from '../../settings.js'
24
24
 
25
+ const createPaddingObject = (padding, direction) => {
26
+ if (padding === undefined) {
27
+ return { start: 0, end: 0, oppositeStart: 0, oppositeEnd: 0 }
28
+ }
29
+
30
+ if (typeof padding === 'number') {
31
+ return { start: padding, end: padding, oppositeStart: padding, oppositeEnd: padding }
32
+ }
33
+
34
+ // todo: do we need to do this runtime every time? or can we optimize this?
35
+ if (isObjectString(padding) === true) {
36
+ padding = parseToObject(padding)
37
+ }
38
+
39
+ if (typeof padding === 'object') {
40
+ const {
41
+ top = undefined,
42
+ right = undefined,
43
+ bottom = undefined,
44
+ left = undefined,
45
+ x = 0,
46
+ y = 0,
47
+ } = padding
48
+
49
+ // use specific values if provided, otherwise fall back to x or y
50
+ return direction === 'vertical'
51
+ ? {
52
+ start: top !== undefined ? top : y,
53
+ end: bottom !== undefined ? bottom : y,
54
+ oppositeStart: left !== undefined ? left : x,
55
+ oppositeEnd: right !== undefined ? right : x,
56
+ }
57
+ : {
58
+ start: left !== undefined ? left : x,
59
+ end: right !== undefined ? right : x,
60
+ oppositeStart: top !== undefined ? top : y,
61
+ oppositeEnd: bottom !== undefined ? bottom : y,
62
+ }
63
+ }
64
+ return { start: 0, end: 0, oppositeStart: 0, oppositeEnd: 0 }
65
+ }
66
+
25
67
  const layoutFn = function (config) {
26
- let offset = 0
27
68
  const position = config.direction === 'vertical' ? 'y' : 'x'
28
69
  const oppositePosition = config.direction === 'vertical' ? 'x' : 'y'
29
70
  const oppositeMount = config.direction === 'vertical' ? 'mountX' : 'mountY'
30
71
  const dimension = config.direction === 'vertical' ? 'height' : 'width'
31
72
  const oppositeDimension = config.direction === 'vertical' ? 'width' : 'height'
73
+ const padding = createPaddingObject(config.padding, config.direction)
74
+
75
+ let offset = padding.start
32
76
 
33
77
  const children = this.node.children
34
78
  const childrenLength = children.length
@@ -37,10 +81,11 @@ const layoutFn = function (config) {
37
81
  for (let i = 0; i < childrenLength; i++) {
38
82
  const node = children[i]
39
83
  node[position] = offset
84
+ node[oppositePosition] = padding.oppositeStart
40
85
  // todo: temporary text check, due to 1px width of empty text node
41
86
  if (dimension === 'width') {
42
87
  offset += node.width + (node.width !== ('text' in node ? 1 : 0) ? gap : 0)
43
- } else if (dimension === 'height') {
88
+ } else {
44
89
  offset +=
45
90
  'text' in node
46
91
  ? node.width > 1
@@ -50,10 +95,13 @@ const layoutFn = function (config) {
50
95
  ? node.height + gap
51
96
  : 0
52
97
  }
53
- otherDimension = Math.max(otherDimension, node[oppositeDimension])
98
+ otherDimension = Math.max(
99
+ otherDimension,
100
+ node[oppositeDimension] + padding.oppositeStart + padding.oppositeEnd
101
+ )
54
102
  }
55
103
  // adjust the size of the layout container
56
- this.node[dimension] = offset - gap
104
+ this.node[dimension] = offset - gap + padding.end
57
105
  this.node[oppositeDimension] = otherDimension
58
106
 
59
107
  const align = {
@@ -248,8 +296,8 @@ const propsTransformer = {
248
296
  set show(v) {
249
297
  if (v) {
250
298
  this.props['alpha'] = 1
251
- this.props['width'] = this.raw['w'] || this.raw['width']
252
- this.props['height'] = this.raw['h'] || this.raw['height']
299
+ this.props['width'] = this.raw['w'] || this.raw['width'] || 0
300
+ this.props['height'] = this.raw['h'] || this.raw['height'] || 0
253
301
  } else {
254
302
  this.props['alpha'] = 0
255
303
  this.props['width'] = 0
@@ -272,9 +320,23 @@ const propsTransformer = {
272
320
  v[i].props.color = colors.normalize(v[i].props.color)
273
321
  }
274
322
  }
275
- this.props['shader'] = renderer.createShader('DynamicShader', {
276
- effects: v,
277
- })
323
+
324
+ if (this.element.node === undefined) {
325
+ this.props['shader'] = renderer.createShader('DynamicShader', {
326
+ effects: v.map((effect) => {
327
+ return renderer.createEffect(effect.type, effect.props, effect.type)
328
+ }),
329
+ })
330
+ } else {
331
+ for (let i = 0; i < v.length; i++) {
332
+ const target = this.element.node.shader.props[v[i].type]
333
+ const props = Object.keys(v[i].props)
334
+
335
+ for (let j = 0; j < props.length; j++) {
336
+ target[props[j]] = v[i].props[props[j]]
337
+ }
338
+ }
339
+ }
278
340
  },
279
341
  set clipping(v) {
280
342
  this.props['clipping'] = v
@@ -24,6 +24,8 @@ import symbols from '../../lib/symbols.js'
24
24
 
25
25
  initLog()
26
26
 
27
+ let elementRef
28
+
27
29
  test('Type', (assert) => {
28
30
  assert.ok(element instanceof Function, 'Element should be a factory function')
29
31
  assert.end()
@@ -105,10 +107,98 @@ test('Element - Set `w` property', (assert) => {
105
107
  assert.end()
106
108
  })
107
109
 
110
+ test('Element- Set `w` property in percentage', (assert) => {
111
+ assert.capture(renderer, 'createNode', () => new EventEmitter())
112
+ const el = createElement()
113
+
114
+ el.set('w', '50%')
115
+
116
+ assert.equal(el.node['width'], 960, 'Node width parameter should be set to 960')
117
+ assert.equal(el.props.props['width'], 960, 'Props width parameter should be set')
118
+ assert.equal(el.props.raw['w'], '50%', "Props' raw map entry should be added")
119
+
120
+ assert.end()
121
+ })
122
+
123
+ test('Element - Set `h` property', (assert) => {
124
+ assert.capture(renderer, 'createNode', () => new EventEmitter())
125
+ const el = createElement()
126
+
127
+ el.set('h', 100)
128
+
129
+ assert.equal(el.node['height'], 100, 'Node height parameter should be set')
130
+ assert.equal(el.props.props['height'], 100, 'Props height parameter should be set')
131
+ assert.equal(el.props.raw['h'], 100, "Props' raw map entry should be added")
132
+ assert.end()
133
+ })
134
+
135
+ test('Element- Set `h` property in percentage', (assert) => {
136
+ assert.capture(renderer, 'createNode', () => new EventEmitter())
137
+ const el = createElement()
138
+
139
+ el.set('h', '50%')
140
+
141
+ assert.equal(el.node['height'], 540, 'Node width parameter should be set to 540')
142
+ assert.equal(el.props.props['height'], 540, 'Props width parameter should be set')
143
+ assert.equal(el.props.raw['h'], '50%', "Props' raw map entry should be added")
144
+
145
+ assert.end()
146
+ })
147
+
148
+ test('Element - Set `x` property', (assert) => {
149
+ assert.capture(renderer, 'createNode', () => new EventEmitter())
150
+ const el = createElement()
151
+
152
+ el.set('x', 100)
153
+
154
+ assert.equal(el.node['x'], 100, 'Node x parameter should be set')
155
+ assert.equal(el.props.props['x'], 100, 'Props x parameter should be set')
156
+ assert.equal(el.props.raw['x'], 100, "Props' raw map entry should be added")
157
+ assert.end()
158
+ })
159
+
160
+ test('Element- Set `x` property in percentage', (assert) => {
161
+ assert.capture(renderer, 'createNode', () => new EventEmitter())
162
+ const el = createElement()
163
+
164
+ el.set('x', '50%')
165
+
166
+ assert.equal(el.node['x'], 960, 'Node x parameter should be set to 960')
167
+ assert.equal(el.props.props['x'], 960, 'Props x parameter should be set')
168
+ assert.equal(el.props.raw['x'], '50%', "Props' raw map entry should be added")
169
+
170
+ assert.end()
171
+ })
172
+
173
+ test('Element - Set `y` property', (assert) => {
174
+ assert.capture(renderer, 'createNode', () => new EventEmitter())
175
+ const el = createElement()
176
+
177
+ el.set('y', 100)
178
+
179
+ assert.equal(el.node['y'], 100, 'Node y parameter should be set')
180
+ assert.equal(el.props.props['y'], 100, 'Props y parameter should be set')
181
+ assert.equal(el.props.raw['y'], 100, "Props' raw map entry should be added")
182
+ assert.end()
183
+ })
184
+
185
+ test('Element- Set `y` property in percentage', (assert) => {
186
+ assert.capture(renderer, 'createNode', () => new EventEmitter())
187
+ const el = createElement()
188
+
189
+ el.set('y', '50%')
190
+
191
+ assert.equal(el.node['y'], 540, 'Node y parameter should be set to 540')
192
+ assert.equal(el.props.props['y'], 540, 'Props y parameter should be set')
193
+ assert.equal(el.props.raw['y'], '50%', "Props' raw map entry should be added")
194
+
195
+ assert.end()
196
+ })
197
+
108
198
  test('Element - Set `mount` property', (assert) => {
109
199
  assert.capture(renderer, 'createNode', () => new EventEmitter())
110
200
  const el = createElement()
111
- const value = { x: 10, y: 20 }
201
+ const value = '{ x: 10, y: 20 }'
112
202
 
113
203
  el.set('mount', value)
114
204
 
@@ -271,8 +361,87 @@ test('Element - Set `fit` property should not set not required keys', (assert) =
271
361
  )
272
362
  assert.end()
273
363
  })
364
+
365
+ test('Element - Set `float` property with value `center`', (assert) => {
366
+ assert.capture(renderer, 'createNode', () => new EventEmitter())
367
+ const el = createElement()
368
+
369
+ el.set('float', 'center')
370
+
371
+ assert.equal(el.node['mountX'], 0.5, 'Node mountX parameter should set to 0.5')
372
+ assert.equal(el.props.props['mountX'], 0.5, 'Props mountX parameter should set to 0.5')
373
+ assert.equal(el.node['x'], 960, 'Node x parameter should set to half of parent node width, 960')
374
+ assert.equal(el.props.props['x'], 960, 'props x parameter should be set to 960')
375
+
376
+ assert.end()
377
+ })
378
+
379
+ test('Element - Set `float` property with value `right`', (assert) => {
380
+ assert.capture(renderer, 'createNode', () => new EventEmitter())
381
+ const el = createElement()
382
+
383
+ el.set('float', 'right')
384
+
385
+ assert.equal(el.node['mountX'], 1, 'Node mountX parameter should set to 1')
386
+ assert.equal(el.props.props['mountX'], 1, 'Props mountX parameter should set to 1')
387
+ assert.equal(el.node['x'], 1920, 'Node x parameter should set to parent node full width, 1920')
388
+ assert.equal(el.props.props['x'], 1920, 'props x parameter should be set to 1920')
389
+
390
+ assert.end()
391
+ })
392
+
393
+ test('Element - Set `float` property with value `unknown`', (assert) => {
394
+ assert.capture(renderer, 'createNode', () => new EventEmitter())
395
+ const el = createElement()
396
+
397
+ el.set('float', 'unknown')
398
+
399
+ assert.equal(el.node['mountX'], undefined, 'Node mountX parameter should not be changed')
400
+ assert.equal(el.props.props['mountX'], undefined, 'Props mountX parameter should not be changed')
401
+ assert.equal(el.node['x'], undefined, 'Node x parameter should not get updated')
402
+ assert.equal(el.props.props['x'], undefined, 'props x parameter should not be changed')
403
+
404
+ assert.end()
405
+ })
406
+
407
+ test('Element - Set `show` property as false', (assert) => {
408
+ assert.capture(renderer, 'createNode', () => new EventEmitter())
409
+ const el = createElement()
410
+
411
+ el.set('w', 960)
412
+ el.set('h', 540)
413
+
414
+ el.set('show', false)
415
+
416
+ elementRef = el
417
+
418
+ assert.equal(el.node['alpha'], 0, 'Node alpha parameter should set to 0')
419
+ assert.equal(el.props.props['alpha'], 0, 'Props alpha parameter should set')
420
+ assert.equal(el.node['width'], 0, 'Node width parameter should set to 0')
421
+ assert.equal(el.props.props['width'], 0, 'props width parameter should set to 0')
422
+ assert.equal(el.node['height'], 0, 'Node height parameter should set to 0')
423
+ assert.equal(el.props.props['height'], 0, 'props height parameter should set to 0')
424
+
425
+ assert.end()
426
+ })
427
+
428
+ test('Element - Set `show` property as false', (assert) => {
429
+ const el = elementRef
430
+
431
+ el.set('show', true)
432
+
433
+ assert.equal(el.node['alpha'], 1, 'Node alpha parameter should be set')
434
+ assert.equal(el.props.props['alpha'], 1, 'Props alpha parameter should be set')
435
+ assert.equal(el.node['width'], 960, 'Node width parameter should be set')
436
+ assert.equal(el.props.props['width'], 960, 'props width parameter should be set')
437
+ assert.equal(el.node['height'], 540, 'Node height parameter should be set')
438
+ assert.equal(el.props.props['height'], 540, 'props height parameter should be set')
439
+
440
+ assert.end()
441
+ })
442
+
274
443
  function createElement() {
275
- const el = element({}, {})
444
+ const el = element({ parent: { node: { width: 1920, height: 1080 } } }, {})
276
445
  el.populate({
277
446
  parent: {
278
447
  node: new EventEmitter(),
package/src/launch.js CHANGED
@@ -16,8 +16,9 @@
16
16
  */
17
17
 
18
18
  import Settings from './settings.js'
19
- import { initLog } from './lib/log.js'
19
+ import { initLog, Log } from './lib/log.js'
20
20
  import engine from './engine.js'
21
+ import packageInfo from '../package.json'
21
22
 
22
23
  export let renderer = {}
23
24
  export const stage = {}
@@ -26,6 +27,8 @@ export default (App, target, settings) => {
26
27
  Settings.set(settings)
27
28
 
28
29
  initLog()
30
+ Log.info('Blits Version ', packageInfo.version)
31
+ Log.info('Renderer Version ', packageInfo.dependencies['@lightningjs/renderer'])
29
32
 
30
33
  stage.element = engine.Element
31
34
 
@@ -337,6 +337,7 @@ const generateForLoopCode = function (templateObject, parent) {
337
337
  ctx.renderCode.push(`
338
338
  created.length = 0
339
339
  const length = rawCollection.length
340
+ const effects = []
340
341
  for(let __index = 0; __index < length; __index++) {
341
342
  const scope = Object.create(component)
342
343
  parent = ${parent}
@@ -405,7 +406,7 @@ const generateForLoopCode = function (templateObject, parent) {
405
406
  ${effect}
406
407
  }
407
408
  effect(eff${index}, ${key})
408
- component[Symbol.for('effects')].push(eff${index})
409
+ effects.push(eff${index})
409
410
  `)
410
411
  } else {
411
412
  // props shouldn't be wrapped in an effect, but simply passed on
@@ -416,6 +417,7 @@ const generateForLoopCode = function (templateObject, parent) {
416
417
  })
417
418
  ctx.renderCode.push(`
418
419
  }
420
+ return effects
419
421
  }`)
420
422
 
421
423
  // generate code that destroys items
@@ -436,7 +438,7 @@ const generateForLoopCode = function (templateObject, parent) {
436
438
  `)
437
439
  }
438
440
  destroyCode.push(`
439
- }
441
+ }
440
442
  }
441
443
  `)
442
444
 
@@ -451,8 +453,14 @@ const generateForLoopCode = function (templateObject, parent) {
451
453
  }
452
454
 
453
455
  ctx.renderCode.push(`
456
+ let forEffects${forStartCounter}
454
457
  effect(() => {
455
- forloop${forStartCounter}(${cast(result[2], ':for')}, elms, created${forStartCounter})
458
+ component[Symbol.for('removeGlobalEffects')](forEffects${forStartCounter})
459
+ forEffects${forStartCounter} = null
460
+ forEffects${forStartCounter} = forloop${forStartCounter}(${cast(
461
+ result[2],
462
+ ':for'
463
+ )}, elms, created${forStartCounter})
456
464
  }, '${effectKey}' )
457
465
  `)
458
466
 
@@ -164,7 +164,13 @@ const reactiveDefineProperty = (target, global) => {
164
164
  return target
165
165
  }
166
166
 
167
+ const hasProxySupport = typeof Proxy !== 'undefined'
168
+
167
169
  export const reactive = (target, mode = 'Proxy', global = false) => {
170
+ // force defineProperty reactivity mode when no proxy support
171
+ if (hasProxySupport === false) {
172
+ mode = 'defineProperty'
173
+ }
168
174
  return mode === 'defineProperty'
169
175
  ? reactiveDefineProperty(target, global)
170
176
  : reactiveProxy(target, undefined, undefined, global)
@@ -48,4 +48,6 @@ export default {
48
48
  isComponent: Symbol.for('isComponent'),
49
49
  // Symbol 'effects' utilized within generated code
50
50
  effects: Symbol.for('effects'),
51
+ // Symbol 'removeGlobalEffects' utilized within generated code
52
+ removeGlobalEffects: Symbol.for('removeGlobalEffects'),
51
53
  }