@lightningjs/blits 0.3.14 → 0.4.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 CHANGED
@@ -1,5 +1,27 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.4.0
4
+
5
+ _9 nov 2023_
6
+
7
+ - Fixed bug related to animating percentage based values
8
+ - Added (customizable) navigation transitions to the router
9
+ - Renamed `.eslintrc.js` to `.eslintrc.cjs` in boilerplate code to make linting work on new projects
10
+ - Fixed issue in Settings when the default value was set to `false`
11
+ - Replace underscored keys for private properties with Symbols
12
+ - Added `keepAlive` option to the router (i.e. keeping a page in memory when navigating to a new page)
13
+ - Introduced a `this.$trigger` function to force a reevaluation of a reactive value (without changing the value)
14
+ - Exposed `currentRoute`, `routes` and `navigating` on the `this.$router` object
15
+ - Upgraded to lates version of `@lightningjs/renderer` (v0.5.0)
16
+
17
+ ## v0.3.15
18
+
19
+ _23 oct 2023_
20
+
21
+ - Added support for slots
22
+ - Added support for defining custom keymapping
23
+ - Improved error handling of the template parser
24
+
3
25
  ## v0.3.14
4
26
 
5
27
  _19 oct 2023_
@@ -31,6 +31,6 @@
31
31
  "vite": "^4.0.4"
32
32
  },
33
33
  "dependencies": {
34
- "@lightningjs/blits": "^0.3.14"
34
+ "@lightningjs/blits": "^0.4.0"
35
35
  }
36
36
  }
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@lightningjs/blits",
3
- "version": "0.3.14",
3
+ "version": "0.4.0",
4
4
  "description": "Blits: The Lightning 3 App Development Framework",
5
5
  "bin": "bin/index.cjs",
6
6
  "exports": {
7
7
  ".": "./index.js",
8
8
  "./vite": "./vite/index.js",
9
- "./fontloader": "./src/fontLoader.js"
9
+ "./fontloader": "./src/fontLoader.js",
10
+ "./transitions": "./src/router/transitions/index.js"
10
11
  },
11
12
  "scripts": {
12
13
  "test": "c8 npm run test:run",
@@ -41,7 +42,7 @@
41
42
  "tape": "^5.5.0"
42
43
  },
43
44
  "dependencies": {
44
- "@lightningjs/renderer": "^0.4.0",
45
+ "@lightningjs/renderer": "^0.5.0",
45
46
  "@lightningjs/vite-plugin-import-chunk-url": "^0.3.0"
46
47
  }
47
48
  }
@@ -19,7 +19,66 @@ import {default as Component, ComponentInstance} from './component'
19
19
 
20
20
  declare namespace Application {
21
21
 
22
- interface Route {
22
+ interface Transition {
23
+ /**
24
+ * Name of the prop to transition (i.e. 'x', 'y', 'alpha', 'color')
25
+ */
26
+ prop: string,
27
+ /**
28
+ * Value the prop should transition to (i.e. 0, 100, '#223388')
29
+ */
30
+ value: any,
31
+ /**
32
+ * Duration of the transition in milliseconds, defaults to 300
33
+ */
34
+ duration?: number,
35
+ /**
36
+ * Easing function to apply to the transition
37
+ */
38
+ function?: string,
39
+ /**
40
+ * Delay before the transition starts in milliseconds
41
+ */
42
+ delay?: number
43
+ }
44
+
45
+ interface Before {
46
+ /**
47
+ * Name of the prop to set before the transition starts
48
+ */
49
+ prop: string,
50
+ /**
51
+ * Value the prop to set before the transition starts
52
+ */
53
+ value: any,
54
+ }
55
+
56
+ interface RouteTransition {
57
+ /**
58
+ * Setting or Array of Settings before new view enters into the router view
59
+ */
60
+ before: Before | Before[],
61
+ /**
62
+ * Transition or Array of Transitions for new view to enters into the router view
63
+ */
64
+ in: Transition | Transition[],
65
+ /**
66
+ * Transition or Array of Transitions for old view to leave the router view
67
+ */
68
+ out: Transition | Transition[],
69
+ }
70
+
71
+ type RouteTransitionFunction = (previousRoute: Route, currentRoute: Route) => RequireAtLeastOne<RouteTransition>
72
+
73
+ function Application(
74
+ config: Application.ApplicationConfig
75
+ ) : Application.ApplicationInstance
76
+
77
+ type RequireAtLeastOne<T> = {
78
+ [K in keyof T]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<keyof T, K>>>
79
+ }[keyof T]
80
+
81
+ type Route = {
23
82
  /**
24
83
  * URI path for the route
25
84
  */
@@ -27,7 +86,15 @@ declare namespace Application {
27
86
  /**
28
87
  * Component to load when activating the route
29
88
  */
30
- component: typeof ComponentInstance
89
+ component: typeof ComponentInstance,
90
+ /**
91
+ * Transition configuration for the route
92
+ */
93
+ transition?: RequireAtLeastOne<RouteTransition> | RouteTransitionFunction,
94
+ /**
95
+ * Extra route options
96
+ */
97
+ options?: object // todo: specify which options are available
31
98
  }
32
99
 
33
100
  export interface ApplicationInstance extends ComponentInstance {}
@@ -46,7 +113,7 @@ declare namespace Application {
46
113
  * ]
47
114
  * ```
48
115
  */
49
- routes: Route[]
116
+ routes?: Route[]
50
117
  }
51
118
  }
52
119
 
@@ -16,12 +16,13 @@
16
16
  */
17
17
 
18
18
  import Component from './component.js'
19
-
20
19
  import Focus from './focus.js'
20
+ import Settings from './settings.js'
21
+
22
+ import symbols from './lib/symbols.js'
21
23
 
22
24
  const Application = (config) => {
23
- // make configurable?
24
- const mapping = {
25
+ const defaultKeyMap = {
25
26
  ArrowLeft: 'left',
26
27
  ArrowRight: 'right',
27
28
  ArrowUp: 'up',
@@ -30,22 +31,36 @@ const Application = (config) => {
30
31
  ' ': 'space',
31
32
  Backspace: 'back',
32
33
  Escape: 'escape',
34
+ 37: 'left',
35
+ 39: 'right',
36
+ 38: 'up',
37
+ 40: 'down',
38
+ 13: 'enter',
39
+ 32: 'space',
40
+ 8: 'back',
41
+ 27: 'escape',
33
42
  }
34
43
 
35
- const handler = (e) => {
36
- const key = mapping[e.key] || e.key
37
- Focus.input(key, e)
38
- }
44
+ config.hooks = config.hooks || {}
39
45
 
40
- document.addEventListener('keydown', handler)
46
+ let handler
41
47
 
42
- config.hooks = config.hooks || {}
43
- config.hooks.___init = function () {
44
- Focus.set(this)
48
+ config.hooks[symbols.destroy] = function () {
49
+ document.removeEventListener('keydown', handler)
45
50
  }
46
51
 
47
- config.hooks.___destroy = function () {
48
- document.removeEventListener('keydown', handler)
52
+ config.hooks[symbols.init] = function () {
53
+ const keyMap = { ...defaultKeyMap, ...Settings.get('keymap', {}) }
54
+
55
+ handler = (e) => {
56
+ const key = keyMap[e.key] || keyMap[e.keyCode] || e.key || e.keyCode
57
+ Focus.input(key, e)
58
+ }
59
+
60
+ document.addEventListener('keydown', handler)
61
+
62
+ // next tick
63
+ setTimeout(() => Focus.set(this))
49
64
  }
50
65
 
51
66
  const App = Component('App', config)
package/src/component.js CHANGED
@@ -21,7 +21,7 @@ import codegenerator from './lib/codegenerator/generator.js'
21
21
  import element from './element.js'
22
22
 
23
23
  import { createHumanReadableId, createInternalId } from './lib/componentId.js'
24
- import { registerHooks, emit } from './lib/hooks.js'
24
+ import { registerHooks, emit, privateEmit } from './lib/hooks.js'
25
25
 
26
26
  import { reactive } from './lib/reactivity/reactive.js'
27
27
  import setupBase from './lib/setup/base.js'
@@ -35,6 +35,8 @@ import setupWatch from './lib/setup/watch.js'
35
35
  import { effect } from './lib/reactivity/effect.js'
36
36
  import { Log } from './lib/log.js'
37
37
 
38
+ import symbols from './lib/symbols.js'
39
+
38
40
  const stage = {
39
41
  element,
40
42
  }
@@ -101,7 +103,7 @@ const Component = (name = required('name'), config = required('config')) => {
101
103
  )
102
104
  this.previous = this.current
103
105
  // emit 'private' hook
104
- emit(`___${v}`, name, scope)
106
+ privateEmit(v, name, scope)
105
107
  // emit 'public' hook
106
108
  emit(v, name, scope)
107
109
  this.current = v
@@ -118,21 +120,28 @@ const Component = (name = required('name'), config = required('config')) => {
118
120
  }
119
121
 
120
122
  this.parent = parentComponent
123
+ this.wrapper = parentEl
121
124
 
122
125
  Object.defineProperties(this, {
126
+ type: {
127
+ value: name,
128
+ writable: false,
129
+ enumerable: true,
130
+ configurable: false,
131
+ },
123
132
  componentId: {
124
133
  value: createHumanReadableId(name),
125
134
  writable: false,
126
135
  enumerable: true,
127
136
  configurable: false,
128
137
  },
129
- ___id: {
138
+ [symbols.id]: {
130
139
  value: createInternalId(),
131
140
  writable: false,
132
141
  enumerable: false,
133
142
  configurable: false,
134
143
  },
135
- ___props: {
144
+ [symbols.props]: {
136
145
  value: reactive(opts.props || {}),
137
146
  writable: false,
138
147
  enumerable: false,
@@ -140,10 +149,15 @@ const Component = (name = required('name'), config = required('config')) => {
140
149
  },
141
150
  })
142
151
 
143
- Object.defineProperty(this, '___state', {
144
- value: reactive(
145
- (config.state && typeof config.state === 'function' && config.state.apply(this)) || {}
146
- ),
152
+ Object.defineProperty(this, symbols.originalState, {
153
+ value: (config.state && typeof config.state === 'function' && config.state.apply(this)) || {},
154
+ writable: false,
155
+ enumerable: false,
156
+ configurable: false,
157
+ })
158
+
159
+ Object.defineProperty(this, symbols.state, {
160
+ value: reactive(this[symbols.originalState]),
147
161
  writable: false,
148
162
  enumerable: false,
149
163
  configurable: false,
@@ -151,25 +165,32 @@ const Component = (name = required('name'), config = required('config')) => {
151
165
 
152
166
  this.lifecycle.state = 'init'
153
167
 
154
- Object.defineProperty(this, '___children', {
168
+ Object.defineProperty(this, symbols.children, {
155
169
  value: code.render.apply(stage, [parentEl, this, code.context]),
156
170
  writable: false,
157
171
  enumerable: false,
158
172
  configurable: false,
159
173
  })
160
174
 
175
+ Object.defineProperty(this, symbols.slots, {
176
+ value: this[symbols.children].filter((child) => child[symbols.isSlot]),
177
+ writable: false,
178
+ enumerable: false,
179
+ configurable: false,
180
+ })
181
+
161
182
  code.effects.forEach((eff) => {
162
183
  effect(() => {
163
- eff.apply(stage, [this, this.___children, code.context])
184
+ eff.apply(stage, [this, this[symbols.children], code.context])
164
185
  })
165
186
  })
166
187
 
167
- if (this.___watchers) {
168
- Object.keys(this.___watchers).forEach((watchKey) => {
188
+ if (this[symbols.watchers]) {
189
+ Object.keys(this[symbols.watchers]).forEach((watchKey) => {
169
190
  let old = this[watchKey]
170
- effect(() => {
171
- if (old !== this[watchKey]) {
172
- this.___watchers[watchKey].apply(this, [this[watchKey], old])
191
+ effect((force = false) => {
192
+ if (old !== this[watchKey] || force === true) {
193
+ this[symbols.watchers][watchKey].apply(this, [this[watchKey], old])
173
194
  old = this[watchKey]
174
195
  }
175
196
  })
@@ -16,14 +16,14 @@
16
16
  */
17
17
 
18
18
  import Component from '../component.js'
19
- import Router from '../router.js'
19
+ import Router from '../router/router.js'
20
20
 
21
21
  let handler
22
22
 
23
23
  export default () =>
24
24
  Component('RouterView', {
25
25
  template: `
26
- <Element></Element>
26
+ <Element w="100%" height="100%"></Element>
27
27
  `,
28
28
  hooks: {
29
29
  ready() {
@@ -17,6 +17,8 @@
17
17
 
18
18
  import Component from '../component.js'
19
19
 
20
+ import symbols from '../lib/symbols.js'
21
+
20
22
  export default () =>
21
23
  Component('Sprite', {
22
24
  template: `
@@ -36,7 +38,7 @@ export default () =>
36
38
  : this.map[this.frame]
37
39
 
38
40
  if (this.spriteTexture && options) {
39
- return this.___renderer.createTexture('SubTexture', {
41
+ return this[symbols.renderer].createTexture('SubTexture', {
40
42
  texture: this.spriteTexture,
41
43
  x: options.x,
42
44
  y: options.y,
@@ -48,7 +50,7 @@ export default () =>
48
50
  },
49
51
  hooks: {
50
52
  ready() {
51
- this.spriteTexture = this.___renderer.createTexture('ImageTexture', {
53
+ this.spriteTexture = this[symbols.renderer].createTexture('ImageTexture', {
52
54
  src: `${window.location.protocol}//${window.location.host}/${this.image}`,
53
55
  })
54
56
  },
package/src/element.js CHANGED
@@ -19,6 +19,7 @@ import { renderer } from './launch.js'
19
19
  import colors from './lib/colors/colors.js'
20
20
 
21
21
  import { Log } from './lib/log.js'
22
+ import symbols from './lib/symbols.js'
22
23
 
23
24
  const isTransition = (value) => {
24
25
  return typeof value === 'object' && 'transition' in value
@@ -174,6 +175,10 @@ const Element = {
174
175
  }
175
176
  this.initData = data
176
177
 
178
+ if (props[symbols.isSlot]) {
179
+ this[symbols.isSlot] = true
180
+ }
181
+
177
182
  transformations.remap(props)
178
183
  Object.keys(props).forEach((prop) => {
179
184
  if (transformations[prop]) {
@@ -188,15 +193,13 @@ const Element = {
188
193
  this.node = props.__textnode ? renderer.createTextNode(props) : renderer.createNode(props)
189
194
 
190
195
  if (props['@loaded']) {
191
- const event = props.__textnode ? 'textLoaded' : 'txLoaded'
192
- this.node.on(event, (el, { width: w, height: h }) => {
193
- props['@loaded']({ w, h }, this)
196
+ this.node.on('loaded', (el, { type, dimensions }) => {
197
+ props['@loaded']({ w: dimensions.width, h: dimensions.height, type }, this)
194
198
  })
195
199
  }
196
200
 
197
201
  if (props['@error']) {
198
- const event = props.__textnode ? 'textFailed' : 'txFailed'
199
- this.node.on(event, (el, error) => {
202
+ this.node.on('failed', (el, error) => {
200
203
  props['@error'](error, this)
201
204
  })
202
205
  }
@@ -249,7 +252,7 @@ const Element = {
249
252
  // works for now
250
253
  prop = Object.keys(props).pop()
251
254
 
252
- if (typeof value === 'string' && props[prop].endsWith('%')) {
255
+ if (typeof props[prop] === 'string' && props[prop].endsWith('%')) {
253
256
  transformations.percentage.call(this, props, prop)
254
257
  }
255
258
 
@@ -259,8 +262,11 @@ const Element = {
259
262
  easing:
260
263
  typeof value === 'object' ? ('function' in value ? value.function : 'ease') : 'ease',
261
264
  })
262
-
263
- value.delay ? setTimeout(() => f.start(), value.delay) : f.start()
265
+ return new Promise((resolve) => {
266
+ value.delay
267
+ ? setTimeout(() => f.start().waitUntilStopped().then(resolve), value.delay)
268
+ : f.start().waitUntilStopped().then(resolve)
269
+ })
264
270
  }
265
271
  },
266
272
  }
package/src/focus.js CHANGED
@@ -15,6 +15,9 @@
15
15
  * SPDX-License-Identifier: Apache-2.0
16
16
  */
17
17
 
18
+ import symbols from './lib/symbols.js'
19
+ import { navigating } from './router/router.js'
20
+
18
21
  let focusedComponent = null
19
22
 
20
23
  export default {
@@ -34,6 +37,7 @@ export default {
34
37
  }
35
38
  },
36
39
  input(key, event) {
40
+ if (navigating === true) return
37
41
  const focusChain = walkChain([focusedComponent], key)
38
42
  const componentWithInputEvent = focusChain.shift()
39
43
 
@@ -42,10 +46,10 @@ export default {
42
46
  focusChain.reverse().forEach((component) => component.unfocus())
43
47
  componentWithInputEvent.focus()
44
48
  }
45
- if (componentWithInputEvent.___inputEvents[key]) {
46
- componentWithInputEvent.___inputEvents[key].call(componentWithInputEvent, event)
47
- } else if (componentWithInputEvent.___inputEvents.any) {
48
- componentWithInputEvent.___inputEvents.any.call(componentWithInputEvent, event)
49
+ if (componentWithInputEvent[symbols.inputEvents][key]) {
50
+ componentWithInputEvent[symbols.inputEvents][key].call(componentWithInputEvent, event)
51
+ } else if (componentWithInputEvent[symbols.inputEvents].any) {
52
+ componentWithInputEvent[symbols.inputEvents].any.call(componentWithInputEvent, event)
49
53
  }
50
54
  }
51
55
  },
@@ -53,9 +57,9 @@ export default {
53
57
 
54
58
  const walkChain = (components, key) => {
55
59
  if (
56
- components[0].___inputEvents &&
57
- (typeof components[0].___inputEvents[key] === 'function' ||
58
- typeof components[0].___inputEvents.any === 'function')
60
+ components[0][symbols.inputEvents] &&
61
+ (typeof components[0][symbols.inputEvents][key] === 'function' ||
62
+ typeof components[0][symbols.inputEvents].any === 'function')
59
63
  ) {
60
64
  return components
61
65
  } else if (components[0].parent) {
package/src/launch.d.ts CHANGED
@@ -86,7 +86,11 @@ export interface Settings {
86
86
  /**
87
87
  * Fonts to be used in the Application
88
88
  */
89
- fonts?: Font[]
89
+ fonts?: Font[],
90
+ /**
91
+ * Custom keymapping
92
+ */
93
+ keymap?: object
90
94
  }
91
95
 
92
96
  /**
@@ -56,7 +56,7 @@ const generateElementCode = function (
56
56
 
57
57
  renderCode.push(`
58
58
  if(!${elm}) {
59
- ${elm} = this.element({componentId: component.___id, parent: parent || 'root'})
59
+ ${elm} = this.element({componentId: component[Symbol.for('id')], parent: parent || 'root'})
60
60
  }
61
61
  const elementConfig${counter} = {}
62
62
  `)
@@ -65,7 +65,18 @@ const generateElementCode = function (
65
65
  delete templateObject['children']
66
66
 
67
67
  Object.keys(templateObject).forEach((key) => {
68
- if (key === 'type') return
68
+ if (key === 'type') {
69
+ if (templateObject[key] === 'Slot') {
70
+ renderCode.push(`elementConfig${counter}[Symbol.for('isSlot')] = true`)
71
+ }
72
+ return
73
+ }
74
+
75
+ if (key === 'slot') {
76
+ renderCode.push(`
77
+ elementConfig${counter}['parent'] = component[Symbol.for('slots')].filter(slot => slot.ref === '${templateObject.slot}').shift() || component[Symbol.for('slots')][0] || parent
78
+ `)
79
+ }
69
80
 
70
81
  if (isReactiveKey(key)) {
71
82
  this.effectsCode.push(
@@ -109,13 +120,15 @@ const generateComponentCode = function (
109
120
  renderCode.push(`
110
121
  const cmp${counter} =
111
122
  (context.components && context.components['${templateObject.type}']) ||
112
- component.___components['${templateObject.type}']
123
+ component[Symbol.for('components')]['${templateObject.type}']
113
124
  `)
114
125
 
115
126
  if ('key' in templateObject) {
116
127
  options.key = interpolate(templateObject.key, options.component)
117
128
  }
118
129
 
130
+ const children = templateObject.children
131
+ delete templateObject.children
119
132
  generateElementCode.call(this, templateObject, parent, { ...options, ...{ holder: true } })
120
133
 
121
134
  parent = options.key ? `elms[${counter}][${options.key}]` : `elms[${counter}]`
@@ -139,7 +152,7 @@ const generateComponentCode = function (
139
152
  if (key === 'type') return
140
153
  if (isReactiveKey(key)) {
141
154
  this.effectsCode.push(`
142
- ${elm}.___props['${key.substring(1)}'] = ${interpolate(
155
+ ${elm}[Symbol.for('props')]['${key.substring(1)}'] = ${interpolate(
143
156
  templateObject[key],
144
157
  options.component
145
158
  )}`)
@@ -158,9 +171,18 @@ const generateComponentCode = function (
158
171
 
159
172
  renderCode.push(`
160
173
  if(!${elm}) {
161
- ${elm} = (context.components && context.components['${templateObject.type}'] || component.___components['${templateObject.type}'] || (() => { console.log('component ${templateObject.type} not found')})).call(null, {props: props${counter}}, ${parent}, component)
174
+ ${elm} = (context.components && context.components['${templateObject.type}'] || component[Symbol.for('components')]['${templateObject.type}'] || (() => { console.log('component ${templateObject.type} not found')})).call(null, {props: props${counter}}, ${parent}, component)
175
+ if (${elm}[Symbol.for('slots')][0]) {
176
+ parent = ${elm}[Symbol.for('slots')][0]
177
+ component = ${elm}
178
+ } else {
179
+ parent = ${elm}[Symbol.for('children')][0]
180
+ }
162
181
  }
163
182
  `)
183
+
184
+ counter++
185
+ generateElementCode.call(this, { children }, false, { ...options })
164
186
  }
165
187
 
166
188
  const generateForLoopCode = function (templateObject, parent) {
@@ -210,14 +232,14 @@ const generateForLoopCode = function (templateObject, parent) {
210
232
  `)
211
233
  }
212
234
 
213
- if (templateObject.type !== 'Element') {
214
- generateComponentCode.call(ctx, templateObject, false, {
235
+ if (templateObject.type === 'Element' || templateObject.type === 'Slot') {
236
+ generateElementCode.call(ctx, templateObject, parent, {
215
237
  key: 'scope.key',
216
238
  component: 'scope.',
217
239
  forceEffect: false,
218
240
  })
219
241
  } else {
220
- generateElementCode.call(ctx, templateObject, parent, {
242
+ generateComponentCode.call(ctx, templateObject, false, {
221
243
  key: 'scope.key',
222
244
  component: 'scope.',
223
245
  forceEffect: false,
@@ -248,10 +270,10 @@ const generateCode = function (templateObject, parent = false) {
248
270
  if (Object.keys(childTemplateObject).indexOf(':for') > -1) {
249
271
  generateForLoopCode.call(this, childTemplateObject, parent)
250
272
  } else {
251
- if (childTemplateObject.type !== 'Element') {
252
- generateComponentCode.call(this, childTemplateObject, parent)
253
- } else {
273
+ if (childTemplateObject.type === 'Element' || childTemplateObject.type === 'Slot') {
254
274
  generateElementCode.call(this, childTemplateObject, parent)
275
+ } else {
276
+ generateComponentCode.call(this, childTemplateObject, parent)
255
277
  }
256
278
  }
257
279
  })