@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.
- package/CHANGELOG.md +23 -0
- package/boilerplate/js/default/src/index.js +1 -0
- package/docs/essentials/displaying_text.md +1 -1
- package/package.json +5 -5
- package/src/application.js +2 -1
- package/src/component/base/events.js +1 -1
- package/src/component/base/utils.js +34 -1
- package/src/component/setup/props.js +3 -1
- package/src/component/setup/props.test.js +51 -19
- package/src/component.d.ts +2 -1
- package/src/component.js +20 -13
- package/src/component.test.js +340 -4
- package/src/components/Circle.js +13 -12
- package/src/components/FPScounter.js +388 -536
- package/src/components/Image.js +7 -9
- package/src/components/Layout.js +49 -0
- package/src/components/Layout.orig.js +34 -0
- package/src/components/RouterView.js +6 -10
- package/src/components/Sprite.js +10 -12
- package/src/components/index.js +2 -2
- package/src/components/index.orig.js +2 -2
- package/src/constants.js +14 -0
- package/src/engine.js +1 -1
- package/src/engines/L3/element.js +193 -180
- package/src/engines/L3/element.test.js +170 -0
- package/src/engines/L3/fontLoader.js +4 -5
- package/src/engines/L3/index.js +2 -2
- package/src/engines/L3/launch.js +10 -16
- package/src/engines/L3/shaderLoader.js +12 -0
- package/src/engines/index.js +1 -1
- package/src/focus.js +3 -2
- package/src/launch.d.ts +31 -3
- package/src/launch.js +1 -1
- package/src/lib/codegenerator/generator.js +212 -84
- package/src/lib/codegenerator/generator.test.js +657 -942
- package/src/lib/colors/colors.js +59 -46
- package/src/lib/componentId.js +9 -0
- package/src/lib/componentId.test.js +2 -1
- package/src/lib/precompiler/precompiler.js +3 -4
- package/src/lib/reactivity/effect.js +10 -13
- package/src/lib/reactivity/reactive.js +24 -14
- package/src/lib/symbols.js +1 -0
- package/src/router/router.js +17 -3
- package/vite/index.js +1 -4
- package/vite/preCompiler.js +3 -1
- package/boilerplate/js/default/src/renderer.js +0 -4
- package/src/components/Text.js +0 -119
- 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_
|
|
@@ -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 `
|
|
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": "
|
|
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.
|
|
48
|
-
"@lightningjs/renderer": "0.
|
|
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",
|
package/src/application.js
CHANGED
|
@@ -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
|
-
},
|
|
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 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
|
-
:
|
|
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 '
|
|
21
|
-
|
|
22
|
-
import symbols from '
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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.
|
|
149
|
-
assert.
|
|
150
|
-
assert.
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
231
|
+
function initLogTest(assert) {
|
|
232
|
+
assert.capture(Settings, 'get', (key) => {
|
|
233
|
+
if (key === 'debugLevel') {
|
|
234
|
+
return 1
|
|
235
|
+
}
|
|
236
|
+
})
|
|
237
|
+
initLog()
|
|
238
|
+
}
|
package/src/component.d.ts
CHANGED
|
@@ -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> |
|
|
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] =
|
|
103
|
-
parentEl,
|
|
104
|
-
|
|
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.
|
|
161
|
+
for (let i = 0; i < config.code.effects.length; i++) {
|
|
162
162
|
effect(() => {
|
|
163
|
-
|
|
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
|