@lightningjs/blits 0.9.8 → 0.10.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 (44) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/boilerplate/common/public/fonts/Lato-Regular.ttf +0 -0
  3. package/boilerplate/common/public/fonts/Raleway-ExtraBold.ttf +0 -0
  4. package/docs/transitions_animations/transitions.md +3 -3
  5. package/package.json +3 -4
  6. package/src/application.d.ts +10 -10
  7. package/src/component/base/announcer.js +27 -0
  8. package/src/component/base/events.js +37 -0
  9. package/src/component/base/index.js +29 -0
  10. package/src/component/base/log.js +27 -0
  11. package/src/component/base/methods.js +136 -0
  12. package/src/component/base/router.js +40 -0
  13. package/src/component/base/timeouts_intervals.js +92 -0
  14. package/src/component/base/utils.js +29 -0
  15. package/src/{lib → component}/setup/computed.js +3 -3
  16. package/src/component/setup/index.js +61 -0
  17. package/src/{lib → component}/setup/input.js +4 -4
  18. package/src/{lib → component}/setup/methods.js +3 -3
  19. package/src/{lib → component}/setup/props.js +3 -3
  20. package/src/{lib → component}/setup/routes.js +3 -3
  21. package/src/{lib → component}/setup/state.js +13 -6
  22. package/src/{lib → component}/setup/watch.js +5 -5
  23. package/src/component.d.ts +157 -38
  24. package/src/component.js +145 -154
  25. package/src/components/Circle.js +5 -5
  26. package/src/components/FPScounter.js +249 -185
  27. package/src/components/Image.js +2 -2
  28. package/src/components/RouterView.js +1 -1
  29. package/src/components/Sprite.js +5 -5
  30. package/src/components/Sprite.orig.js +2 -2
  31. package/src/components/Text.js +11 -11
  32. package/src/components/index.js +32 -0
  33. package/src/components/index.orig.js +32 -0
  34. package/src/engines/L3/element.js +41 -35
  35. package/src/engines/L3/fontLoader.js +16 -2
  36. package/src/engines/L3/launch.js +1 -1
  37. package/src/lib/codegenerator/generator.js +18 -8
  38. package/vite/msdfGenerator.js +52 -53
  39. package/boilerplate/common/public/fonts/Lato-Regular.msdf.json +0 -5383
  40. package/boilerplate/common/public/fonts/Lato-Regular.msdf.png +0 -0
  41. package/boilerplate/common/public/fonts/Raleway-ExtraBold.msdf.json +0 -10838
  42. package/boilerplate/common/public/fonts/Raleway-ExtraBold.msdf.png +0 -0
  43. package/src/lib/setup/base.js +0 -288
  44. /package/src/{lib → component}/setup/props.test.js +0 -0
package/src/component.js CHANGED
@@ -15,198 +15,156 @@
15
15
  * SPDX-License-Identifier: Apache-2.0
16
16
  */
17
17
 
18
+ import { Log } from './lib/log.js'
18
19
  import parser from './lib/templateparser/parser.js'
19
20
  import codegenerator from './lib/codegenerator/generator.js'
20
-
21
- import { stage, renderer } from './launch.js'
22
-
23
21
  import { createHumanReadableId, createInternalId } from './lib/componentId.js'
24
- import { registerHooks, emit } from './lib/hooks.js'
25
-
22
+ import { emit } from './lib/hooks.js'
26
23
  import { reactive } from './lib/reactivity/reactive.js'
27
- import setupBase from './lib/setup/base.js'
28
- import setupProps from './lib/setup/props.js'
29
- import setupMethods from './lib/setup/methods.js'
30
- import setupState from './lib/setup/state.js'
31
- import setupComputed from './lib/setup/computed.js'
32
- import setupInput from './lib/setup/input.js'
33
- import setupRoutes from './lib/setup/routes.js'
34
- import setupWatch from './lib/setup/watch.js'
35
24
  import { effect } from './lib/reactivity/effect.js'
36
- import { Log } from './lib/log.js'
37
- import lifecycle from './lib/lifecycle.js'
25
+ import Lifecycle from './lib/lifecycle.js'
26
+ import symbols from './lib/symbols.js'
27
+
28
+ import { stage, renderer } from './launch.js'
29
+
30
+ import Base from './component/base/index.js'
38
31
 
39
32
  import Settings from './settings.js'
40
- import symbols from './lib/symbols.js'
41
33
 
42
- let counter = 0
34
+ import setupComponent from './component/setup/index.js'
35
+ import components from './components/index.js'
36
+
37
+ // object to store global components
38
+ let globalComponents
43
39
 
44
40
  const required = (name) => {
45
41
  throw new Error(`Parameter ${name} is required`)
46
42
  }
47
43
 
44
+ /**
45
+ * Component factory function
46
+ * @param {string} name - The name of the component
47
+ * @param {object} config - The configuration object for the component
48
+ * @returns {function} - A factory function that creates a new component instance
49
+ *
50
+ */
48
51
  const Component = (name = required('name'), config = required('config')) => {
49
- const setupComponent = (parentComponent) => {
50
- // code generation
51
- if (!config.code) {
52
- Log.debug(`Generating code for ${name} component`)
53
- config.code = codegenerator.call(config, parser(config.template, name, parentComponent))
54
- }
55
-
56
- component[symbols.identifier] = ++counter
57
-
58
- setupBase(component, name)
59
-
60
- // setup hooks
61
- registerHooks(config.hooks, component[symbols.identifier])
62
-
63
- // setup props
64
- // if (config.props) // because of the default props like id - might change this
65
- setupProps(component, config.props)
66
-
67
- // setup methods
68
- if (config.methods) setupMethods(component, config.methods)
69
-
70
- // setup state
71
- setupState(component, config.state)
72
-
73
- // setup computed
74
- if (config.computed) setupComputed(component, config.computed)
75
-
76
- // setup watchers
77
- if (config.watch) setupWatch(component, config.watch)
78
-
79
- // setup routes
80
- if (config.routes) setupRoutes(component, config.routes)
81
-
82
- // setup input
83
- if (config.input) setupInput(component, config.input)
84
-
85
- component.setup = true
86
- }
52
+ let base
87
53
 
88
54
  const component = function (opts, parentEl, parentComponent) {
89
- this.lifecycle = Object.assign(Object.create(lifecycle), {
55
+ // generate a human readable ID for the component instance (i.e. Blits::ComponentName1)
56
+ this.componentId = createHumanReadableId(name)
57
+
58
+ // instantiate a lifecycle object for this instance
59
+ this.lifecycle = Object.assign(Object.create(Lifecycle), {
90
60
  component: this,
91
61
  previous: null,
92
62
  current: null,
93
63
  })
94
64
 
95
- if (!component.setup) {
96
- setupComponent(parentComponent)
97
- }
98
- if (config.hooks && config.hooks.frameTick) {
99
- renderer.on('frameTick', (r, data) =>
100
- emit('frameTick', component[symbols.identifier], this, [data])
101
- )
102
- }
103
-
65
+ // set a reference to the parent component
104
66
  this.parent = parentComponent
67
+
68
+ // set a reference to the holder / parentElement
69
+ // Components are wrapped in a holder node (used to apply positioning and transforms
70
+ // such as rotation and scale to components)
105
71
  this[symbols.holder] = parentEl
106
72
 
107
- Object.defineProperties(this, {
108
- componentId: {
109
- value: createHumanReadableId(name),
110
- writable: false,
111
- enumerable: true,
112
- configurable: false,
113
- },
114
- [symbols.id]: {
115
- value: createInternalId(),
116
- writable: false,
117
- enumerable: false,
118
- configurable: false,
119
- },
120
- [symbols.props]: {
121
- value: reactive(opts.props || {}, Settings.get('reactivityMode')),
122
- writable: false,
123
- enumerable: false,
124
- configurable: false,
125
- },
126
- [symbols.timeouts]: {
127
- value: [],
128
- writable: true,
129
- enumerable: false,
130
- configurable: false,
131
- },
132
- [symbols.intervals]: {
133
- value: [],
134
- writable: true,
135
- enumerable: false,
136
- configurable: false,
137
- },
138
- })
73
+ // generate an internal id (simple counter)
74
+ this[symbols.id] = createInternalId()
139
75
 
140
- Object.defineProperty(this, symbols.originalState, {
141
- value: {
142
- ...((config.state && typeof config.state === 'function' && config.state.apply(this)) || {}),
143
- ...{ hasFocus: false },
144
- },
145
- writable: false,
146
- enumerable: false,
147
- configurable: false,
148
- })
76
+ // set the internal props and make them reactive (based on the reactivity mode)
77
+ this[symbols.props] = reactive(opts.props || {}, Settings.get('reactivityMode'))
149
78
 
150
- Object.defineProperty(this, symbols.state, {
151
- value: reactive(this[symbols.originalState], Settings.get('reactivityMode')),
152
- writable: false,
153
- enumerable: false,
154
- configurable: false,
155
- })
79
+ // create an empty array for storing timeouts created by this component (via this.$setTimeout)
80
+ this[symbols.timeouts] = []
156
81
 
157
- this.lifecycle.state = 'init'
82
+ // create an empty array for storing intervals created by this component (via this.$setInterval)
83
+ this[symbols.intervals] = []
158
84
 
159
- Object.defineProperty(this, symbols.children, {
160
- value: config.code.render.apply(stage, [parentEl, this, config]),
161
- writable: false,
162
- enumerable: false,
163
- configurable: false,
164
- })
85
+ // apply the state function (passing in the this reference to utilize configured props)
86
+ // and store a reference to this original state
87
+ // hasFocus key is sprinkled in
88
+ this[symbols.originalState] = {
89
+ ...((config.state && typeof config.state === 'function' && config.state.apply(this)) || {}),
90
+ ...{ hasFocus: false },
91
+ }
165
92
 
166
- Object.defineProperty(this, symbols.wrapper, {
167
- value: this[symbols.children][0],
168
- writable: false,
169
- enumerable: false,
170
- configurable: false,
171
- })
93
+ // generate a reactive state (using the result of previously execute state function)
94
+ // and store it
95
+ this[symbols.state] = reactive(this[symbols.originalState], Settings.get('reactivityMode'))
172
96
 
173
- if (config.hooks && config.hooks.attach) {
174
- this[symbols.wrapper].node.on('inBounds', () => {
175
- this.lifecycle.state = 'attach'
176
- })
177
- }
97
+ // all basic setup has been done now, set the lifecycle to state 'init'
98
+ this.lifecycle.state = 'init'
178
99
 
179
- if (config.hooks && config.hooks.detach) {
180
- this[symbols.wrapper].node.on('outOfBounds', (node, { previous }) => {
181
- if (previous > 0) this.lifecycle.state = 'detach'
182
- })
183
- }
100
+ // execute the render code that constructs the initial state of the component
101
+ // 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
+ ])
108
+
109
+ // create a reference to the wrapper element of the component (i.e. the root Element of the component)
110
+ this[symbols.wrapper] = this[symbols.children][0]
111
+
112
+ // create a reference to an array of children that are slots
113
+ this[symbols.slots] = this[symbols.children].filter((child) => child[symbols.isSlot])
114
+
115
+ // register hooks if component has hooks specified
116
+ if (config.hooks) {
117
+ // frame tick event
118
+ if (config.hooks.frameTick) {
119
+ renderer.on('frameTick', (r, data) =>
120
+ emit('frameTick', this[symbols.identifier], this, [data])
121
+ )
122
+ }
123
+
124
+ if (config.hooks.idle) {
125
+ renderer.on('idle', () => {
126
+ emit('idle', this[symbols.identifier], this)
127
+ })
128
+ }
184
129
 
185
- if (config.hooks && config.hooks.enter) {
186
- this[symbols.wrapper].node.on('inViewport', () => {
187
- this.lifecycle.state = 'enter'
188
- })
189
- }
130
+ // inBounds event emiting a lifecycle attach event
131
+ if (config.hooks.attach) {
132
+ this[symbols.wrapper].node.on('inBounds', () => {
133
+ this.lifecycle.state = 'attach'
134
+ })
135
+ }
190
136
 
191
- if (config.hooks && config.hooks.enter) {
192
- this[symbols.wrapper].node.on('outOfViewport', () => {
193
- this.lifecycle.state = 'exit'
194
- })
195
- }
137
+ // outOfBounds event emiting a lifeycle detach event
138
+ if (config.hooks.detach) {
139
+ this[symbols.wrapper].node.on('outOfBounds', (node, { previous }) => {
140
+ if (previous > 0) this.lifecycle.state = 'detach'
141
+ })
142
+ }
196
143
 
197
- Object.defineProperty(this, symbols.slots, {
198
- value: this[symbols.children].filter((child) => child[symbols.isSlot]),
199
- writable: false,
200
- enumerable: false,
201
- configurable: false,
202
- })
144
+ // inViewport event emiting a lifecycle enter event
145
+ if (config.hooks.enter) {
146
+ this[symbols.wrapper].node.on('inViewport', () => {
147
+ this.lifecycle.state = 'enter'
148
+ })
149
+ }
150
+
151
+ // outOfViewport event emitting a lifecycle exit event
152
+ if (config.hooks.exit) {
153
+ this[symbols.wrapper].node.on('outOfViewport', () => {
154
+ this.lifecycle.state = 'exit'
155
+ })
156
+ }
157
+ }
203
158
 
159
+ // setup (and execute) all the generated side effects based on the
160
+ // reactive bindings define in the template
204
161
  config.code.effects.forEach((eff) => {
205
162
  effect(() => {
206
- eff.apply(stage, [this, this[symbols.children], config])
163
+ eff.apply(stage, [this, this[symbols.children], config, globalComponents])
207
164
  })
208
165
  })
209
166
 
167
+ // setup watchers if the components has watchers specified
210
168
  if (this[symbols.watchers]) {
211
169
  Object.keys(this[symbols.watchers]).forEach((watchKey) => {
212
170
  let old = this[watchKey]
@@ -219,14 +177,47 @@ const Component = (name = required('name'), config = required('config')) => {
219
177
  })
220
178
  }
221
179
 
222
- // next tick
180
+ // set all symbol based properties to non-enumerable and non-configurable
181
+ Object.getOwnPropertySymbols(this).forEach((property) => {
182
+ Object.defineProperties(this, {
183
+ [property]: {
184
+ enumerable: false,
185
+ configurable: false,
186
+ },
187
+ })
188
+ })
189
+
190
+ // finaly set the lifecycle state to ready (in the next tick)
223
191
  setTimeout(() => (this.lifecycle.state = 'ready'))
192
+
193
+ // and return this
194
+ return this
224
195
  }
225
196
 
226
197
  const factory = (options = {}, parentEl, parentComponent) => {
227
- return new component(options, parentEl, parentComponent)
198
+ // setup the component once, using Base as the prototype
199
+ if (!base) {
200
+ Log.debug(`Setting up ${name} component`)
201
+ base = setupComponent(Object.create(Base), config)
202
+ }
203
+
204
+ // one time code generation (only if precompilation is turned off)
205
+ if (!config.code) {
206
+ Log.debug(`Generating code for ${name} component`)
207
+ config.code = codegenerator.call(config, parser(config.template, name))
208
+ }
209
+
210
+ // register global components once
211
+ if (!globalComponents) {
212
+ globalComponents = components()
213
+ }
214
+
215
+ // create an instance of the component, using base as the prototype (which contains Base)
216
+ return component.call(Object.create(base), options, parentEl, parentComponent)
228
217
  }
229
218
 
219
+ // store the config on the factory, in order to access the config
220
+ // during the code generation step
230
221
  factory.config = config
231
222
 
232
223
  return factory
@@ -21,7 +21,7 @@ export default () =>
21
21
  Component('Circle', {
22
22
  /* eslint-disable no-unused-vars */
23
23
  code: {
24
- render: function anonymous(parent, component, context) {
24
+ render: function anonymous(parent, component, context, components) {
25
25
  const elms = []
26
26
 
27
27
  if (!elms[0]) {
@@ -36,16 +36,16 @@ export default () =>
36
36
  return elms
37
37
  },
38
38
  effects: [
39
- function anonymous(component, elms, context) {
39
+ function anonymous(component, elms, context, components) {
40
40
  elms[0].set('color', component.color)
41
41
  },
42
- function anonymous(component, elms, context) {
42
+ function anonymous(component, elms, context, components) {
43
43
  elms[0].set('w', component.size)
44
44
  },
45
- function anonymous(component, elms, context) {
45
+ function anonymous(component, elms, context, components) {
46
46
  elms[0].set('h', component.size)
47
47
  },
48
- function anonymous(component, elms, context) {
48
+ function anonymous(component, elms, context, components) {
49
49
  elms[0].set('effects', [component.shader('radius', { radius: component.radius })])
50
50
  },
51
51
  ],