@lightningjs/blits 0.10.0 → 1.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.
Files changed (48) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/boilerplate/js/default/src/index.js +1 -0
  3. package/docs/essentials/displaying_text.md +1 -1
  4. package/package.json +5 -5
  5. package/src/application.js +2 -1
  6. package/src/component/base/events.js +1 -1
  7. package/src/component/base/utils.js +34 -1
  8. package/src/component/setup/props.js +3 -1
  9. package/src/component/setup/props.test.js +51 -19
  10. package/src/component.d.ts +2 -1
  11. package/src/component.js +20 -13
  12. package/src/component.test.js +340 -4
  13. package/src/components/Circle.js +13 -12
  14. package/src/components/FPScounter.js +388 -536
  15. package/src/components/Image.js +7 -9
  16. package/src/components/Layout.js +49 -0
  17. package/src/components/Layout.orig.js +34 -0
  18. package/src/components/RouterView.js +6 -10
  19. package/src/components/Sprite.js +10 -12
  20. package/src/components/index.js +2 -2
  21. package/src/components/index.orig.js +2 -2
  22. package/src/constants.js +14 -0
  23. package/src/engine.js +1 -1
  24. package/src/engines/L3/element.js +193 -180
  25. package/src/engines/L3/element.test.js +170 -0
  26. package/src/engines/L3/fontLoader.js +4 -5
  27. package/src/engines/L3/index.js +2 -2
  28. package/src/engines/L3/launch.js +10 -16
  29. package/src/engines/L3/shaderLoader.js +12 -0
  30. package/src/engines/index.js +1 -1
  31. package/src/focus.js +3 -2
  32. package/src/launch.d.ts +31 -3
  33. package/src/launch.js +1 -1
  34. package/src/lib/codegenerator/generator.js +212 -84
  35. package/src/lib/codegenerator/generator.test.js +657 -942
  36. package/src/lib/colors/colors.js +59 -46
  37. package/src/lib/componentId.js +9 -0
  38. package/src/lib/componentId.test.js +2 -1
  39. package/src/lib/precompiler/precompiler.js +3 -4
  40. package/src/lib/reactivity/effect.js +10 -13
  41. package/src/lib/reactivity/reactive.js +24 -14
  42. package/src/lib/symbols.js +1 -0
  43. package/src/router/router.js +17 -3
  44. package/vite/index.js +1 -4
  45. package/vite/preCompiler.js +3 -1
  46. package/boilerplate/js/default/src/renderer.js +0 -4
  47. package/src/components/Text.js +0 -119
  48. package/src/components/Text.orig.js +0 -81
@@ -19,7 +19,7 @@ let counter
19
19
 
20
20
  export default function (templateObject = { children: [] }) {
21
21
  const ctx = {
22
- renderCode: ['const elms = []'],
22
+ renderCode: ['const elms = []', 'let componentType', 'const rootComponent = component'],
23
23
  effectsCode: [],
24
24
  context: { props: [], components: this.components },
25
25
  }
@@ -29,9 +29,18 @@ export default function (templateObject = { children: [] }) {
29
29
  ctx.renderCode.push('return elms')
30
30
 
31
31
  return {
32
- render: new Function('parent', 'component', 'context', 'components', ctx.renderCode.join('\n')),
32
+ render: new Function(
33
+ 'parent',
34
+ 'component',
35
+ 'context',
36
+ 'components',
37
+ 'effect',
38
+ 'getRaw',
39
+ ctx.renderCode.join('\n')
40
+ ),
33
41
  effects: ctx.effectsCode.map(
34
- (code) => new Function('component', 'elms', 'context', 'components', code)
42
+ (code) =>
43
+ new Function('component', 'elms', 'context', 'components', 'rootComponent', 'effect', code)
35
44
  ),
36
45
  context: ctx.context,
37
46
  }
@@ -40,7 +49,7 @@ export default function (templateObject = { children: [] }) {
40
49
  const generateElementCode = function (
41
50
  templateObject,
42
51
  parent = false,
43
- options = { key: false, component: 'component.', forceEffect: false }
52
+ options = { key: false, component: 'component.', forceEffect: false, forloop: false }
44
53
  ) {
45
54
  const renderCode = options.forceEffect ? this.effectsCode : this.renderCode
46
55
  if (parent) {
@@ -52,17 +61,26 @@ const generateElementCode = function (
52
61
 
53
62
  if (options.key) {
54
63
  renderCode.push(`
55
- elms[${counter}] = elms[${counter}] || {}
64
+ if(elms[${counter}] === undefined) {
65
+ elms[${counter}] = {}
66
+ }
56
67
  `)
57
68
  }
58
69
 
70
+ renderCode.push(`const elementConfig${counter} = {}`)
71
+
72
+ if (options.forloop) {
73
+ renderCode.push(`if(!${elm}) {`)
74
+ }
75
+
59
76
  renderCode.push(`
60
- if(!${elm}) {
61
- ${elm} = this.element({parent: parent || 'root'}, component)
62
- }
63
- const elementConfig${counter} = {}
77
+ ${elm} = this.element({parent: parent || 'root'}, component)
64
78
  `)
65
79
 
80
+ if (options.forloop) {
81
+ renderCode.push('}')
82
+ }
83
+
66
84
  const children = templateObject['children']
67
85
  delete templateObject['children']
68
86
 
@@ -77,11 +95,19 @@ const generateElementCode = function (
77
95
  `)
78
96
  }
79
97
 
98
+ if (key === 'key') return
99
+
80
100
  if (isReactiveKey(key)) {
81
101
  if (options.holder && key === ':color') return
82
102
  this.effectsCode.push(
83
103
  `${elm}.set('${key.substring(1)}', ${interpolate(templateObject[key], options.component)})`
84
104
  )
105
+ renderCode.push(
106
+ `elementConfig${counter}['${key.substring(1)}'] = ${interpolate(
107
+ templateObject[key],
108
+ options.component
109
+ )}`
110
+ )
85
111
  } else {
86
112
  renderCode.push(
87
113
  `elementConfig${counter}['${key}'] = ${cast(templateObject[key], key, options.component)}`
@@ -99,11 +125,15 @@ const generateElementCode = function (
99
125
  `)
100
126
  }
101
127
 
102
- renderCode.push(`
103
- if(!${elm}.nodeId) {
104
- ${elm}.populate(elementConfig${counter})
105
- }
106
- `)
128
+ if (options.forloop) {
129
+ renderCode.push(`if(!${elm}.nodeId) {`)
130
+ }
131
+
132
+ renderCode.push(`${elm}.populate(elementConfig${counter})`)
133
+
134
+ if (options.forloop) {
135
+ renderCode.push('}')
136
+ }
107
137
 
108
138
  if (children) {
109
139
  generateCode.call(this, { children }, `${elm}`, options)
@@ -113,7 +143,13 @@ const generateElementCode = function (
113
143
  const generateComponentCode = function (
114
144
  templateObject,
115
145
  parent = false,
116
- options = { key: false, component: 'component.', forceEffect: false, holder: false }
146
+ options = {
147
+ key: false,
148
+ component: 'component.',
149
+ forceEffect: false,
150
+ holder: false,
151
+ forloop: false,
152
+ }
117
153
  ) {
118
154
  const renderCode = options.forceEffect ? this.effectsCode : this.renderCode
119
155
 
@@ -140,7 +176,9 @@ const generateComponentCode = function (
140
176
 
141
177
  if (options.key) {
142
178
  renderCode.push(`
143
- elms[${counter}] = elms[${counter}] || {}
179
+ if(elms[${counter}] === undefined) {
180
+ elms[${counter}] = {}
181
+ }
144
182
  `)
145
183
  }
146
184
 
@@ -169,43 +207,56 @@ const generateComponentCode = function (
169
207
  }
170
208
  })
171
209
 
210
+ if (options.forloop) {
211
+ renderCode.push(`if(!${elm}) {`)
212
+ }
213
+
172
214
  renderCode.push(`
173
- if(!${elm}) {
174
- const componentType = props${counter}['is'] || '${
175
- templateObject[Symbol.for('componentType')]
176
- }'
177
-
178
- let component${counter}
179
- if(typeof componentType === 'string') {
180
- component${counter} = context.components && context.components[componentType] || components[componentType]
181
- if(!component${counter}) {
182
- throw new Error('Component "${templateObject[Symbol.for('componentType')]}" not found')
183
- }
184
- } else if(typeof componentType === 'function' && componentType.name === 'factory') {
185
- component${counter} = componentType
215
+ componentType = props${counter}['is'] || '${templateObject[Symbol.for('componentType')]}'
216
+
217
+ let component${counter}
218
+ if(typeof componentType === 'string') {
219
+ component${counter} = context.components && context.components[componentType] || components[componentType]
220
+ if(!component${counter}) {
221
+ throw new Error('Component "${templateObject[Symbol.for('componentType')]}" not found')
186
222
  }
223
+ } else if(typeof componentType === 'function' && componentType.name === 'factory') {
224
+ component${counter} = componentType
225
+ }
187
226
 
188
- ${elm} = component${counter}.call(null, {props: props${counter}}, ${parent}, component)
227
+ ${elm} = component${counter}.call(null, {props: props${counter}}, ${parent}, component)
189
228
 
190
- if (${elm}[Symbol.for('slots')][0]) {
191
- parent = ${elm}[Symbol.for('slots')][0]
192
- component = ${elm}
193
- } else {
194
- parent = ${elm}[Symbol.for('children')][0]
195
- }
229
+ if (${elm}[Symbol.for('slots')][0]) {
230
+ parent = ${elm}[Symbol.for('slots')][0]
231
+ component = ${elm}
232
+ } else {
233
+ parent = ${elm}[Symbol.for('children')][0]
196
234
  }
197
235
  `)
198
236
 
237
+ if (options.forloop) {
238
+ renderCode.push('}')
239
+ }
240
+
199
241
  if (children) {
200
242
  counter++
201
243
  generateElementCode.call(this, { children }, false, { ...options })
202
244
  }
245
+ // if (!options.forloop) {
246
+ // renderCode.push(`
247
+ // component = rootComponent
248
+ // `)
249
+ // }
203
250
  }
204
251
 
205
252
  const generateForLoopCode = function (templateObject, parent) {
206
253
  const forLoop = templateObject[':for']
207
254
  delete templateObject[':for']
208
255
 
256
+ const key = templateObject['key']
257
+ const forKey = interpolate(key, 'scope.')
258
+
259
+ delete templateObject['key']
209
260
  const regex = /(.+)\s+in\s+(.+)/gi
210
261
  // const regex = /(:?\(*)(.+)\s+in\s+(.+)/gi
211
262
 
@@ -217,6 +268,8 @@ const generateForLoopCode = function (templateObject, parent) {
217
268
  .replace(')', '')
218
269
  .split(/\s*,\s*/)
219
270
 
271
+ const scopeRegex = new RegExp(`(scope\\.(?!${item}\\.|${index}|key)(\\w+))`, 'gi')
272
+
220
273
  // local context
221
274
  const ctx = {
222
275
  renderCode: [],
@@ -225,87 +278,157 @@ const generateForLoopCode = function (templateObject, parent) {
225
278
  }
226
279
 
227
280
  if (parent) {
228
- ctx.renderCode.push(`let parent = ${parent}`)
281
+ ctx.renderCode.push(`parent = ${parent}`)
229
282
  }
230
283
 
231
284
  const forStartCounter = counter
232
285
 
233
- // reference all reactive attributes (except those referencing the iterator item),
234
- // to ensure that reactivity is registered, even when the initial array is empty
235
- Object.keys(templateObject).forEach((k) => {
236
- if (isReactiveKey(k) && templateObject[k].indexOf('$' + item) === -1) {
237
- ctx.renderCode.push(`
238
- void ${interpolate(templateObject[k], 'component.')}
239
- `)
240
- }
241
- })
242
-
243
286
  ctx.renderCode.push(`
244
- const collection = ${cast(result[2], ':for')} || []
245
- const keys = []
246
- for(let __index = 0; __index < collection.length; __index++) {
247
- parent = ${parent}
248
- if(!component.key) keys.length = 0
249
- const scope = Object.assign(component, {
250
- key: Math.random().toString(),
251
- ${index}: __index,
252
- ${item}: collection[__index],
253
- `)
287
+ const created${forStartCounter} = []
288
+ const forloop${forStartCounter} = (collection = [], elms, created) => {
289
+ const rawCollection = getRaw(collection)
290
+ const keys = new Set(rawCollection.map((${item}) => '' + ${interpolate(key, '')}))
291
+ `)
292
+
293
+ // keep track of the index in the render code so we can inject
294
+ // the code that takes care of destroying elements (generated later on)
295
+ // in the right spot
296
+ const indexToInjectDestroyCode = ctx.renderCode.length
254
297
 
298
+ ctx.renderCode.push(`
299
+ created.length = 0
300
+ const length = collection.length
301
+ for(let __index = 0; __index < length; __index++) {
302
+ const scope = Object.create(component)
303
+ parent = ${parent}
304
+ scope['${index}'] = __index
305
+ scope['${item}'] = rawCollection[__index]
306
+ scope['key'] = ${forKey || '__index'}
307
+ `)
255
308
  if ('ref' in templateObject && templateObject.ref.indexOf('$') === -1) {
256
309
  // automatically map the ref for each item in the loop based on the given ref key
257
310
  ctx.renderCode.push(`
258
- __ref: '${templateObject.ref}' + __index
311
+ scope['__ref'] = '${templateObject.ref}' + __index
259
312
  `)
260
313
  templateObject.ref = '$__ref'
261
314
  }
315
+
262
316
  ctx.renderCode.push(`
263
- })
317
+ created.push(scope.key)
264
318
  `)
265
319
 
266
- if ('key' in templateObject) {
267
- ctx.renderCode.push(`
268
- keys.push(${interpolate(templateObject.key, 'scope.')}.toString())
269
- `)
270
- } else {
271
- ctx.renderCode.push(`
272
- keys.push(scope.key.toString())
273
- `)
274
- }
275
-
276
320
  if (
277
321
  templateObject[Symbol.for('componentType')] === 'Element' ||
278
- templateObject[Symbol.for('componentType')] === 'Slot'
322
+ templateObject[Symbol.for('componentType')] === 'Slot' ||
323
+ templateObject[Symbol.for('componentType')] === 'Text'
279
324
  ) {
280
325
  generateElementCode.call(ctx, templateObject, parent, {
281
326
  key: 'scope.key',
282
327
  component: 'scope.',
283
328
  forceEffect: false,
329
+ forloop: true,
284
330
  })
285
331
  } else {
286
332
  generateComponentCode.call(ctx, templateObject, false, {
287
333
  key: 'scope.key',
288
334
  component: 'scope.',
289
335
  forceEffect: false,
336
+ forloop: true,
290
337
  })
291
338
  }
292
- ctx.renderCode = ctx.renderCode.concat(ctx.effectsCode)
293
- ctx.renderCode.push('}')
294
339
 
340
+ // separate effects that only rely on variables in the itteration
341
+ const innerScopeEffects = ctx.effectsCode.filter(
342
+ (effect) => [...effect.matchAll(scopeRegex)].length === 0
343
+ )
344
+
345
+ // separate effects that (also) rely on variables in the outer scope
346
+ const outerScopeEffects = ctx.effectsCode.filter(
347
+ (effect) => [...effect.matchAll(scopeRegex)].length !== 0
348
+ )
349
+
350
+ ctx.renderCode.push(`
351
+ scope['${item}'] = collection[__index]
352
+ `)
353
+
354
+ // inner scope variables are part of the main forloop
355
+ innerScopeEffects.forEach((effect) => {
356
+ const key = effect.indexOf(`scope.${index}`) > -1 ? `'${interpolate(result[2], '')}'` : null
357
+ ctx.renderCode.push(`
358
+ effect(() => {
359
+ ${effect}
360
+ }, ${key})
361
+ `)
362
+ })
363
+
364
+ ctx.renderCode.push(`
365
+ if(elms[${forStartCounter}][0] && elms[${forStartCounter}][0].forComponent && elms[${forStartCounter}][0].forComponent.___layout) {
366
+ elms[${forStartCounter}][0].forComponent.___layout()
367
+ }
368
+ }
369
+ }`)
370
+
371
+ // generate code that destroys items
372
+ const destroyCode = []
373
+ destroyCode.push(`
374
+ let i = created.length
375
+
376
+ while (i--) {
377
+ const key = created[i]
378
+ if (!keys.has(key)) {
379
+ `)
295
380
  const forEndCounter = counter
381
+
382
+ for (let i = forStartCounter; i <= forEndCounter; i++) {
383
+ destroyCode.push(`
384
+ elms[${i}][key] && elms[${i}][key].destroy()
385
+ delete elms[${i}][key]
386
+ `)
387
+ }
388
+ destroyCode.push(`
389
+ }
390
+ }
391
+ `)
392
+
393
+ // inject the destroy code in the correct spot
394
+ ctx.renderCode.splice(indexToInjectDestroyCode, 0, ...destroyCode)
296
395
  ctx.renderCode.push(`
297
- if(elms[${forStartCounter}]) {
298
- Object.keys(elms[${forStartCounter}]).forEach(key => {
299
- if(keys.indexOf(key) === -1) {
300
- for(let i=${forStartCounter}; i <= ${forEndCounter}; i++){
301
- elms[i][key].destroy && elms[i][key].destroy()
302
- delete elms[i][key]
303
- }
396
+ effect(() => {
397
+ forloop${forStartCounter}(${cast(result[2], ':for')}, elms, created${forStartCounter})
398
+ }, '${interpolate(result[2], '')}')
399
+ `)
400
+
401
+ outerScopeEffects.forEach((effect) => {
402
+ const matches = [...effect.matchAll(scopeRegex)]
403
+
404
+ let l = matches.length
405
+ const refs = []
406
+ while (l--) {
407
+ const match = matches[l]
408
+ const ref = `component.${match[2]}`
409
+ refs.indexOf(ref) === -1 && refs.push(ref)
410
+ effect =
411
+ effect.substring(0, match.index) + ref + effect.substring(match.index + match[1].length)
412
+ }
413
+
414
+ ctx.renderCode.push(`
415
+ effect(() => {
416
+ void ${refs.join(', ')}
417
+ for(let __index = 0; __index < ${interpolate(result[2])}.length; __index++) {
418
+ const scope = {}
419
+ scope['${index}'] = __index
420
+ scope['${item}'] = ${interpolate(result[2])}[__index]
421
+ scope['key'] = ${forKey || '__index'}
422
+ `)
423
+
424
+ ctx.renderCode.push(`
425
+ ${effect}
304
426
  }
305
427
  })
306
- }
307
- `)
308
- this.effectsCode.push(ctx.renderCode.join('\n'))
428
+ `)
429
+ })
430
+
431
+ this.renderCode.push(ctx.renderCode.join('\n'))
309
432
  }
310
433
 
311
434
  const generateCode = function (templateObject, parent = false, options = {}) {
@@ -318,8 +441,12 @@ const generateCode = function (templateObject, parent = false, options = {}) {
318
441
  } else {
319
442
  if (
320
443
  childTemplateObject[Symbol.for('componentType')] === 'Element' ||
321
- childTemplateObject[Symbol.for('componentType')] === 'Slot'
444
+ childTemplateObject[Symbol.for('componentType')] === 'Slot' ||
445
+ childTemplateObject[Symbol.for('componentType')] === 'Text'
322
446
  ) {
447
+ if (childTemplateObject[Symbol.for('componentType')] === 'Text') {
448
+ childTemplateObject.__textnode = 'true'
449
+ }
323
450
  generateElementCode.call(this, childTemplateObject, parent, options)
324
451
  } else {
325
452
  generateComponentCode.call(this, childTemplateObject, parent, options)
@@ -329,6 +456,7 @@ const generateCode = function (templateObject, parent = false, options = {}) {
329
456
  }
330
457
 
331
458
  const interpolate = (val, component = 'component.') => {
459
+ if (val === undefined) return val
332
460
  const replaceString = /('.*?')+/gi
333
461
  const replaceDollar = /\$/gi
334
462
  const matches = val.matchAll(replaceString)
@@ -344,7 +472,7 @@ const interpolate = (val, component = 'component.') => {
344
472
  }
345
473
 
346
474
  // replace remaining dollar signs
347
- val = val.replace(replaceDollar, component)
475
+ val = val.replace(replaceDollar, component || '')
348
476
 
349
477
  // restore string replacemenet
350
478
  restore.forEach((el, idx) => {