@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
package/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # Changelog
2
2
 
3
+ ## v1.1.0
4
+
5
+ _25 july 2024_
6
+
7
+ - Made `holdTimeout` configurable and added to launch settings
8
+ - Improved test coverage
9
+ - Removed imporChunkUrl dependency as it’s no longer needed
10
+ - Abstracted path from pre-compiler, to make it more reusable (in playground for example)
11
+ - Added `defaultFont` launch setting to boilerplate example
12
+
13
+ ## v1.0.0
14
+
15
+ _15 july 2024_
16
+
17
+ - Added multiple optimizations that improve the performance
18
+ - Fixed mountY prop setting
19
+ - Added functionality for providing custom shaders
20
+ - Added initial support for accessing children
21
+ - Added first version of Layout component
22
+ - Improved test coverage
23
+ - Refactored transitions and fixed promise resolve issues
24
+ - Upgraded to renderer v1.0.0
25
+
3
26
  ## v0.10.0
4
27
 
5
28
  _17 june 2024_
@@ -6,6 +6,7 @@ Blits.Launch(App, 'app', {
6
6
  h: 1080,
7
7
  multithreaded: false,
8
8
  debugLevel: 1,
9
+ defaultFont: 'lato',
9
10
  fonts: [
10
11
  {
11
12
  family: 'lato',
@@ -22,7 +22,7 @@ You can use the Text-tag anywhere in your template, without the need to explicit
22
22
  The Text-tag accepts the following attributes:
23
23
 
24
24
  - `content` - the text to be displayed. Can be a hardcoded text, a dynamic value, or a reactive value
25
- - `font` - the font family, defaults to `lato`, or the default font specified in the launch settings
25
+ - `font` - the font family, defaults to `sans-serif`, or the default font specified in the launch settings
26
26
  - `size` - the font size, defaults to `32`
27
27
  - `color` - the color to display for the text, defaults to `white` and can be any of the supported Blits color formats (HTML, hexadecimal or rgb(a))
28
28
  - `letterspacing` - letterspacing in pixels, defaults to `0`
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@lightningjs/blits",
3
- "version": "0.10.0",
3
+ "version": "1.1.0",
4
4
  "description": "Blits: The Lightning 3 App Development Framework",
5
5
  "bin": "bin/index.js",
6
6
  "exports": {
7
7
  ".": "./index.js",
8
8
  "./vite": "./vite/index.js",
9
- "./transitions": "./src/router/transitions/index.js"
9
+ "./transitions": "./src/router/transitions/index.js",
10
+ "./precompiler": "./src/lib/precompiler/precompiler.js"
10
11
  },
11
12
  "scripts": {
12
13
  "test": "c8 npm run test:run",
@@ -44,9 +45,8 @@
44
45
  "tape": "^5.5.0"
45
46
  },
46
47
  "dependencies": {
47
- "@lightningjs/msdf-generator": "^1.0.1",
48
- "@lightningjs/renderer": "0.9.1",
49
- "@lightningjs/vite-plugin-import-chunk-url": "^0.3.0",
48
+ "@lightningjs/msdf-generator": "^1.0.2",
49
+ "@lightningjs/renderer": "^1.0.0",
50
50
  "execa": "^8.0.1",
51
51
  "kolorist": "^1.8.0",
52
52
  "ora": "^7.0.1",
@@ -20,6 +20,7 @@ import Focus from './focus.js'
20
20
  import Settings from './settings.js'
21
21
 
22
22
  import symbols from './lib/symbols.js'
23
+ import { DEFAULT_HOLD_TIMEOUT_MS } from './constants.js'
23
24
 
24
25
  const Application = (config) => {
25
26
  const defaultKeyMap = {
@@ -61,7 +62,7 @@ const Application = (config) => {
61
62
  clearTimeout(holdTimeout)
62
63
  holdTimeout = setTimeout(() => {
63
64
  Focus.hold = true
64
- }, 50)
65
+ }, Settings.get('holdTimeout', DEFAULT_HOLD_TIMEOUT_MS))
65
66
  }
66
67
 
67
68
  keyUpHandler = () => {
@@ -15,7 +15,7 @@
15
15
  * SPDX-License-Identifier: Apache-2.0
16
16
  */
17
17
 
18
- import eventListeners from '../../lib/eventListeners'
18
+ import eventListeners from '../../lib/eventListeners.js'
19
19
 
20
20
  export default {
21
21
  $emit: {
@@ -15,7 +15,7 @@
15
15
  * SPDX-License-Identifier: Apache-2.0
16
16
  */
17
17
 
18
- import symbols from '../../lib/symbols'
18
+ import symbols from '../../lib/symbols.js'
19
19
 
20
20
  import { renderer } from '../../launch.js'
21
21
 
@@ -26,4 +26,37 @@ export default {
26
26
  enumerable: true,
27
27
  configurable: false,
28
28
  },
29
+ [symbols.getChildren]: {
30
+ value() {
31
+ const parent = this.rootParent || this.parent
32
+ return (this[symbols.children] || []).concat(
33
+ (parent &&
34
+ parent[symbols.getChildren]()
35
+ .map((child) => {
36
+ if (Object.getPrototypeOf(child) === Object.prototype) {
37
+ return Object.values(child).map((c) => {
38
+ // ugly hack .. but the point is to reference the right component
39
+ c.forComponent = c.config && c.config.parent.component
40
+ return c
41
+ })
42
+ }
43
+ return child
44
+ })
45
+ .flat()
46
+ .filter((child) => {
47
+ // problem is that component of a forloop in a slot has component of root component
48
+ if (child && child.component) {
49
+ return (
50
+ (child.component && child.component.componentId === this.componentId) ||
51
+ (child.forComponent && child.forComponent.componentId === this.componentId)
52
+ )
53
+ }
54
+ })) ||
55
+ []
56
+ )
57
+ },
58
+ writable: false,
59
+ enumerable: true,
60
+ configurable: false,
61
+ },
29
62
  }
@@ -37,7 +37,9 @@ export default (component, props = []) => {
37
37
  const value = prop.cast(
38
38
  this[symbols.props] && prop.key in this[symbols.props]
39
39
  ? this[symbols.props][prop.key]
40
- : prop.default || undefined
40
+ : 'default' in prop
41
+ ? prop.default
42
+ : undefined
41
43
  )
42
44
 
43
45
  if (prop.required && value === undefined) {
@@ -17,11 +17,9 @@
17
17
 
18
18
  import test from 'tape'
19
19
  import propsFn from './props.js'
20
- import { initLog } from '../log.js'
21
-
22
- import symbols from '../symbols.js'
23
-
24
- initLog()
20
+ import { initLog } from '../../lib/log.js'
21
+ import Settings from '../../settings.js'
22
+ import symbols from '../../lib/symbols.js'
25
23
 
26
24
  test('Type props function', (assert) => {
27
25
  const expected = 'function'
@@ -49,7 +47,7 @@ test('Pass props as an array', (assert) => {
49
47
  assert.equal(props.length, props.map(prop => component[symbols.propKeys].indexOf(prop) > -1).filter(prop => prop === true).length, 'All passed props should be stored on propKeys')
50
48
 
51
49
  props.forEach((prop) => {
52
- assert.true(typeof Object.getOwnPropertyDescriptor(component.prototype, prop).get === 'function', `A getter should have been created for property ${prop}`)
50
+ assert.true(typeof Object.getOwnPropertyDescriptor(component, prop).get === 'function', `A getter should have been created for property ${prop}`)
53
51
  })
54
52
 
55
53
  assert.end()
@@ -59,14 +57,14 @@ test('Pass props as an array', (assert) => {
59
57
  test('Get value of props', (assert) => {
60
58
  const component = new Function()
61
59
 
62
- const componentInstance = new component
60
+ const componentInstance = Object.create(component)
63
61
  componentInstance[symbols.props] = {
64
62
  index: 1,
65
63
  img: 'lorem-ipsum.jpg',
66
64
  url: 'http://localhost'
67
65
  }
68
66
 
69
- const componentInstance2 = new component
67
+ const componentInstance2 = Object.create(component)
70
68
  componentInstance2[symbols.props] = {
71
69
  index: 2,
72
70
  img: 'bla.jpg',
@@ -95,7 +93,7 @@ test('Passing props as an object', (assert) => {
95
93
  assert.equal(props.length, props.map(prop => component[symbols.propKeys].indexOf(typeof prop === 'object' ? prop.key : prop) > -1).filter(prop => prop === true).length, 'All passed props should be stored on propKeys')
96
94
 
97
95
  props.forEach((prop) => {
98
- assert.true(typeof Object.getOwnPropertyDescriptor(component.prototype, typeof prop === 'object' ? prop.key : prop).get === 'function', `A getter should have been created for property ${prop}`)
96
+ assert.true(typeof Object.getOwnPropertyDescriptor(component, typeof prop === 'object' ? prop.key : prop).get === 'function', `A getter should have been created for property ${prop}`)
99
97
  })
100
98
 
101
99
  assert.end()
@@ -111,7 +109,7 @@ test('Passing props as an object mixed with single keys', (assert) => {
111
109
  assert.equal(props.length, props.map(prop => component[symbols.propKeys].indexOf(typeof prop === 'object' ? prop.key : prop) > -1).filter(prop => prop === true).length, 'All passed props should be stored on propKeys')
112
110
 
113
111
  props.forEach((prop) => {
114
- assert.true(typeof Object.getOwnPropertyDescriptor(component.prototype, typeof prop === 'object' ? prop.key : prop).get === 'function', `A getter should have been created for property ${prop}`)
112
+ assert.true(typeof Object.getOwnPropertyDescriptor(component, typeof prop === 'object' ? prop.key : prop).get === 'function', `A getter should have been created for property ${prop}`)
115
113
  })
116
114
 
117
115
  assert.end()
@@ -120,7 +118,7 @@ test('Passing props as an object mixed with single keys', (assert) => {
120
118
  test('Casting props to a type', (assert) => {
121
119
  const component = new Function()
122
120
 
123
- const componentInstance = new component
121
+ const componentInstance = Object.create(component)
124
122
  componentInstance[symbols.props] = {
125
123
  number: '1',
126
124
  string: 100,
@@ -145,9 +143,9 @@ test('Casting props to a type', (assert) => {
145
143
  }]
146
144
  propsFn(component, props)
147
145
 
148
- assert.true(typeof componentInstance.number === 'number', 'Should cast prop value to a Number')
149
- assert.true(typeof componentInstance.string === 'string', 'Should cast prop value to a String')
150
- assert.true(typeof componentInstance.boolean === 'boolean', 'Should cast prop value to a Boolean')
146
+ assert.equal(typeof componentInstance.number, 'number', 'Should cast prop value to a Number')
147
+ assert.equal(typeof componentInstance.string, 'string', 'Should cast prop value to a String')
148
+ assert.equal(typeof componentInstance.boolean, 'boolean', 'Should cast prop value to a Boolean')
151
149
  assert.equal(componentInstance.image, 'http://localhost/my_image.jpg','Should cast according to a custom function')
152
150
 
153
151
  assert.end()
@@ -156,7 +154,7 @@ test('Casting props to a type', (assert) => {
156
154
  test('Setting default value for undefined props', (assert) => {
157
155
  const component = new Function()
158
156
 
159
- const componentInstance = new component
157
+ const componentInstance = Object.create(component)
160
158
 
161
159
  const props = [{
162
160
  key: 'missing',
@@ -172,7 +170,7 @@ test('Setting default value for undefined props', (assert) => {
172
170
  test('Required props with default', (assert) => {
173
171
  const component = new Function()
174
172
 
175
- const componentInstance = new component
173
+ const componentInstance = Object.create(component)
176
174
 
177
175
  const props = [{
178
176
  key: 'missing',
@@ -187,9 +185,11 @@ test('Required props with default', (assert) => {
187
185
  })
188
186
 
189
187
  test('Required props without default', (assert) => {
188
+ initLogTest(assert)
189
+ const capture = assert.capture(console, 'warn')
190
190
  const component = new Function()
191
191
 
192
- const componentInstance = new component
192
+ const componentInstance = Object.create(component)
193
193
 
194
194
  const props = [{
195
195
  key: 'missing',
@@ -198,9 +198,41 @@ test('Required props without default', (assert) => {
198
198
  propsFn(component, props)
199
199
 
200
200
  assert.equal(componentInstance.missing, undefined, 'Should return undefined prop value when undefined')
201
- // todo: should log a warning about prop being required
201
+ const logs = capture()
202
+ assert.equal(logs.length, 1)
203
+ assert.equal(logs[0].args.pop(), 'missing is required', 'Should log warning message')
204
+
205
+ assert.end()
206
+ })
207
+
208
+ test('Setting prop value directly', (assert) => {
209
+ initLogTest(assert)
210
+ const capture = assert.capture(console, 'warn')
211
+ const component = new Function()
212
+ const componentInstance = Object.create(component)
213
+ componentInstance[symbols.props] = {
214
+ property: 'foo'
215
+ }
216
+ const props = [{
217
+ key: 'property',
218
+ }]
219
+
220
+ propsFn(component, props)
221
+ componentInstance.property = 'bar'
222
+
223
+ assert.equal(componentInstance[symbols.props].property, 'bar', 'Should be possible to mutate the property')
224
+ let logs = capture()
225
+ assert.equal(logs.length, 1)
226
+ assert.equal(logs[0].args.pop(), 'Warning! Avoid mutating props directly (property)', 'Should log warning message')
202
227
 
203
228
  assert.end()
204
229
  })
205
230
 
206
- // todo add test when setting a prop (should work, but should log a warning about avoiding mutating a prop)
231
+ function initLogTest(assert) {
232
+ assert.capture(Settings, 'get', (key) => {
233
+ if (key === 'debugLevel') {
234
+ return 1
235
+ }
236
+ })
237
+ initLog()
238
+ }
@@ -91,6 +91,7 @@ declare namespace Component {
91
91
  }
92
92
  type PropsArray<T extends string> = T[]
93
93
  type AdvancedProps = AdvancedProp[]
94
+ type MixedProps<T extends string> = (T | AdvancedProp)[];
94
95
 
95
96
  // type ExtractPropNames<T extends Prop[]> = T extends (infer U)[]
96
97
  // ? U extends string
@@ -394,7 +395,7 @@ declare namespace Component {
394
395
  * }]
395
396
  * ```
396
397
  */
397
- props?: PropsArray<Props> | AdvancedProp[],
398
+ props?: PropsArray<Props> | AdvancedProps | MixedProps<Props>
398
399
  /**
399
400
  * Computed properties
400
401
  */
package/src/component.js CHANGED
@@ -20,7 +20,7 @@ import parser from './lib/templateparser/parser.js'
20
20
  import codegenerator from './lib/codegenerator/generator.js'
21
21
  import { createHumanReadableId, createInternalId } from './lib/componentId.js'
22
22
  import { emit } from './lib/hooks.js'
23
- import { reactive } from './lib/reactivity/reactive.js'
23
+ import { reactive, getRaw } from './lib/reactivity/reactive.js'
24
24
  import { effect } from './lib/reactivity/effect.js'
25
25
  import Lifecycle from './lib/lifecycle.js'
26
26
  import symbols from './lib/symbols.js'
@@ -51,7 +51,7 @@ const required = (name) => {
51
51
  const Component = (name = required('name'), config = required('config')) => {
52
52
  let base
53
53
 
54
- const component = function (opts, parentEl, parentComponent) {
54
+ const component = function (opts, parentEl, parentComponent, rootComponent) {
55
55
  // generate a human readable ID for the component instance (i.e. Blits::ComponentName1)
56
56
  this.componentId = createHumanReadableId(name)
57
57
 
@@ -65,6 +65,9 @@ const Component = (name = required('name'), config = required('config')) => {
65
65
  // set a reference to the parent component
66
66
  this.parent = parentComponent
67
67
 
68
+ //
69
+ this.rootParent = rootComponent
70
+
68
71
  // set a reference to the holder / parentElement
69
72
  // Components are wrapped in a holder node (used to apply positioning and transforms
70
73
  // such as rotation and scale to components)
@@ -99,12 +102,9 @@ const Component = (name = required('name'), config = required('config')) => {
99
102
 
100
103
  // execute the render code that constructs the initial state of the component
101
104
  // and store the children result (a flat map of elements and components)
102
- this[symbols.children] = config.code.render.apply(stage, [
103
- parentEl,
104
- this,
105
- config,
106
- globalComponents,
107
- ])
105
+ this[symbols.children] =
106
+ config.code.render.apply(stage, [parentEl, this, config, globalComponents, effect, getRaw]) ||
107
+ []
108
108
 
109
109
  // create a reference to the wrapper element of the component (i.e. the root Element of the component)
110
110
  this[symbols.wrapper] = this[symbols.children][0]
@@ -158,11 +158,18 @@ const Component = (name = required('name'), config = required('config')) => {
158
158
 
159
159
  // setup (and execute) all the generated side effects based on the
160
160
  // reactive bindings define in the template
161
- config.code.effects.forEach((eff) => {
161
+ for (let i = 0; i < config.code.effects.length; i++) {
162
162
  effect(() => {
163
- eff.apply(stage, [this, this[symbols.children], config, globalComponents])
163
+ config.code.effects[i].apply(stage, [
164
+ this,
165
+ this[symbols.children],
166
+ config,
167
+ globalComponents,
168
+ rootComponent,
169
+ effect,
170
+ ])
164
171
  })
165
- })
172
+ }
166
173
 
167
174
  // setup watchers if the components has watchers specified
168
175
  if (this[symbols.watchers]) {
@@ -194,7 +201,7 @@ const Component = (name = required('name'), config = required('config')) => {
194
201
  return this
195
202
  }
196
203
 
197
- const factory = (options = {}, parentEl, parentComponent) => {
204
+ const factory = (options = {}, parentEl, parentComponent, rootComponent) => {
198
205
  // setup the component once, using Base as the prototype
199
206
  if (!base) {
200
207
  Log.debug(`Setting up ${name} component`)
@@ -213,7 +220,7 @@ const Component = (name = required('name'), config = required('config')) => {
213
220
  }
214
221
 
215
222
  // create an instance of the component, using base as the prototype (which contains Base)
216
- return component.call(Object.create(base), options, parentEl, parentComponent)
223
+ return component.call(Object.create(base), options, parentEl, parentComponent, rootComponent)
217
224
  }
218
225
 
219
226
  // store the config on the factory, in order to access the config