@lightningjs/blits 1.30.2 → 1.31.1

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 (152) hide show
  1. package/CHANGELOG.md +1035 -1019
  2. package/LICENSE +202 -202
  3. package/NOTICE +12 -12
  4. package/README.md +33 -33
  5. package/bin/index.js +8 -8
  6. package/blits.d.ts +6 -6
  7. package/index.d.ts +1099 -1099
  8. package/index.js +28 -28
  9. package/package.json +57 -56
  10. package/public/assets/fps_sprite.base64.js +1 -1
  11. package/scripts/postpublish.js +59 -59
  12. package/scripts/prepublishOnly.js +84 -84
  13. package/scripts/prepush.js +78 -78
  14. package/src/announcer/announcer.js +162 -162
  15. package/src/announcer/speechSynthesis.js +107 -107
  16. package/src/application.js +100 -100
  17. package/src/component/base/announcer.js +27 -27
  18. package/src/component/base/events.js +69 -69
  19. package/src/component/base/index.js +33 -33
  20. package/src/component/base/methods.js +244 -244
  21. package/src/component/base/router.js +41 -41
  22. package/src/component/base/timeouts_intervals.js +99 -99
  23. package/src/component/base/utils.js +72 -72
  24. package/src/component/setup/computed.js +48 -48
  25. package/src/component/setup/index.js +62 -62
  26. package/src/component/setup/input.js +30 -30
  27. package/src/component/setup/methods.js +37 -37
  28. package/src/component/setup/props.js +62 -62
  29. package/src/component/setup/routes.js +42 -42
  30. package/src/component/setup/state.js +59 -59
  31. package/src/component/setup/watch.js +35 -35
  32. package/src/component.js +437 -437
  33. package/src/components/Circle.js +73 -73
  34. package/src/components/FPScounter.js +1510 -1510
  35. package/src/components/RouterView.js +81 -81
  36. package/src/components/Sprite.js +94 -94
  37. package/src/components/index.js +28 -28
  38. package/src/constants.js +21 -21
  39. package/src/engine.js +1 -1
  40. package/src/engines/L3/element.js +744 -744
  41. package/src/engines/L3/fontLoader.js +38 -38
  42. package/src/engines/L3/index.js +7 -7
  43. package/src/engines/L3/launch.js +123 -123
  44. package/src/engines/L3/shaderLoader.js +12 -12
  45. package/src/engines/index.js +1 -1
  46. package/src/focus.js +90 -90
  47. package/src/helpers/deepEqualArray.js +50 -50
  48. package/src/helpers/fetchJson.js +34 -34
  49. package/src/helpers/sequence.js +26 -26
  50. package/src/launch.js +123 -123
  51. package/src/lib/blitsfileconverter/blitsfileconverter.js +52 -52
  52. package/src/lib/codegenerator/generator.js +788 -788
  53. package/src/lib/colors/colors.js +92 -92
  54. package/src/lib/colors/htmlColors.js +168 -168
  55. package/src/lib/componentId.js +49 -49
  56. package/src/lib/eventListeners.js +123 -123
  57. package/src/lib/hooks.js +59 -59
  58. package/src/lib/lifecycle.js +85 -85
  59. package/src/lib/log.js +117 -117
  60. package/src/lib/precompiler/precompiler.js +73 -73
  61. package/src/lib/reactivity/effect.js +93 -93
  62. package/src/lib/reactivity/reactive.js +208 -208
  63. package/src/lib/reactivityguard/computedprops.js +218 -218
  64. package/src/lib/symbols.js +108 -108
  65. package/src/lib/templateparser/parser.js +386 -386
  66. package/src/lib/utils.js +27 -27
  67. package/src/plugin.js +27 -27
  68. package/src/plugins/appstate.js +26 -26
  69. package/src/plugins/index.js +20 -20
  70. package/src/plugins/language.js +152 -152
  71. package/src/plugins/log.js +25 -25
  72. package/src/plugins/theme.js +94 -94
  73. package/src/router/router.js +516 -514
  74. package/src/router/transitions/fadeIn.js +27 -27
  75. package/src/router/transitions/fadeInOut.js +33 -33
  76. package/src/router/transitions/index.js +21 -21
  77. package/src/router/transitions/slideIn.js +28 -28
  78. package/src/router/transitions/slideInOut.js +33 -33
  79. package/src/settings.js +40 -40
  80. package/vite/blitsFileConverter.js +67 -67
  81. package/vite/index.js +28 -28
  82. package/vite/msdfGenerator.js +269 -269
  83. package/vite/preCompiler.js +47 -42
  84. package/vite/reactivityGuard.js +45 -45
  85. package/vscode/data/template-attributes.json +589 -589
  86. package/.editorconfig +0 -7
  87. package/.eslintignore +0 -1
  88. package/.eslintrc.cjs +0 -65
  89. package/.github/workflows/comment-lint-results.yml +0 -125
  90. package/.github/workflows/comment-test-results.yml +0 -109
  91. package/.github/workflows/deploy-docs.yml +0 -27
  92. package/.github/workflows/run-blits-tests.yml +0 -142
  93. package/.github/workflows/run-lint.yml +0 -128
  94. package/.husky/pre-commit +0 -1
  95. package/.husky/pre-push +0 -1
  96. package/.vscode/extensions.json +0 -7
  97. package/.vscode/settings.json +0 -9
  98. package/COPYING +0 -1
  99. package/banner.png +0 -0
  100. package/docs/.nojekyll +0 -0
  101. package/docs/README.md +0 -19
  102. package/docs/_sidebar.md +0 -37
  103. package/docs/built-in/directives.md +0 -50
  104. package/docs/built-in/for-loop.md +0 -204
  105. package/docs/built-in/layout.md +0 -168
  106. package/docs/components/component_state.md +0 -90
  107. package/docs/components/computed_properties.md +0 -45
  108. package/docs/components/lifecycle_events.md +0 -42
  109. package/docs/components/methods.md +0 -45
  110. package/docs/components/props.md +0 -56
  111. package/docs/components/user_input.md +0 -186
  112. package/docs/components/utility_methods.md +0 -320
  113. package/docs/components/watchers.md +0 -34
  114. package/docs/essentials/application_root.md +0 -19
  115. package/docs/essentials/components.md +0 -68
  116. package/docs/essentials/displaying_images.md +0 -67
  117. package/docs/essentials/displaying_text.md +0 -135
  118. package/docs/essentials/element_attributes.md +0 -195
  119. package/docs/essentials/settings.md +0 -117
  120. package/docs/essentials/template_syntax.md +0 -83
  121. package/docs/getting_started/file_structure.md +0 -58
  122. package/docs/getting_started/getting_started.md +0 -45
  123. package/docs/getting_started/intro.md +0 -20
  124. package/docs/index.html +0 -47
  125. package/docs/performance/lazy-loading.md +0 -161
  126. package/docs/plugins/global_app_state.md +0 -98
  127. package/docs/plugins/language.md +0 -172
  128. package/docs/plugins/text-to-speech-announcer.md +0 -120
  129. package/docs/plugins/theme.md +0 -176
  130. package/docs/router/basics.md +0 -175
  131. package/docs/router/hooks.md +0 -70
  132. package/docs/router/transitions.md +0 -146
  133. package/docs/sidebar.json +0 -171
  134. package/docs/transitions_animations/transitions.md +0 -150
  135. package/jsconfig.json +0 -12
  136. package/src/component/setup/props.test.js +0 -238
  137. package/src/component.test.js +0 -388
  138. package/src/components/Circle.orig.js +0 -31
  139. package/src/components/FPScounter.orig.js +0 -121
  140. package/src/components/RouterView.orig.js +0 -54
  141. package/src/components/Sprite.orig.js +0 -58
  142. package/src/components/index.orig.js +0 -28
  143. package/src/engines/L3/element.test.js +0 -579
  144. package/src/helpers/deepEqualArray.test.js +0 -127
  145. package/src/lib/codegenerator/generator.test.js +0 -3801
  146. package/src/lib/colors/colors.test.js +0 -290
  147. package/src/lib/componentId.test.js +0 -84
  148. package/src/lib/eventListeners.test.js +0 -236
  149. package/src/lib/reactivity/effect.test.js +0 -35
  150. package/src/lib/reactivity/reactive.test.js +0 -27
  151. package/src/lib/templateparser/parser.test.js +0 -1411
  152. package/src/router/router.test.js +0 -473
package/src/component.js CHANGED
@@ -1,437 +1,437 @@
1
- /*
2
- * Copyright 2023 Comcast Cable Communications Management, LLC
3
- * Licensed under the Apache License, Version 2.0 (the "License");
4
- * you may not use this file except in compliance with the License.
5
- * You may obtain a copy of the License at
6
- *
7
- * http://www.apache.org/licenses/LICENSE-2.0
8
- *
9
- * Unless required by applicable law or agreed to in writing, software
10
- * distributed under the License is distributed on an "AS IS" BASIS,
11
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- * See the License for the specific language governing permissions and
13
- * limitations under the License.
14
- *
15
- * SPDX-License-Identifier: Apache-2.0
16
- */
17
-
18
- import { Log } from './lib/log.js'
19
- import parser from './lib/templateparser/parser.js'
20
- import codegenerator from './lib/codegenerator/generator.js'
21
- import { createHumanReadableId, createInternalId } from './lib/componentId.js'
22
- import { emit } from './lib/hooks.js'
23
- import { reactive, getRaw } from './lib/reactivity/reactive.js'
24
- import { effect } from './lib/reactivity/effect.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 { default as Base, shared } from './component/base/index.js'
31
-
32
- import Settings from './settings.js'
33
-
34
- import setupComponent from './component/setup/index.js'
35
- import components from './components/index.js'
36
-
37
- import { plugins } from './plugin.js'
38
-
39
- // object to store global components
40
- let globalComponents
41
-
42
- const required = (name) => {
43
- throw new Error(`Parameter ${name} is required`)
44
- }
45
-
46
- /**
47
- * @typedef {function} BlitsComponentFactory
48
- * @param {object} opts - The options for the component instance
49
- * @param {BlitsComponent} parentEl - The parent element for the component instance
50
- * @param {BlitsComponent} parentComponent - The parent component for the component instance
51
- * @param {BlitsComponent} rootComponent - The root component for the component instance
52
- * @returns {BlitsComponent} - The component instance
53
- */
54
-
55
- /**
56
- * Structure of a Blits component:
57
- * Component:
58
- * <Holder>
59
- * <Wrapper>
60
- * []<Elements>
61
- * </Wrapper>
62
- * </Holder>
63
- *
64
- * A Blits Element
65
- * @typedef {Object} BlitsAnnouncer
66
- * @property {(message: string) => void} assertive - Function to assertively announce a message.
67
- * @property {() => void} clear - Function to clear the announcer.
68
- * @property {() => void} disable - Function to disable the announcer.
69
- * @property {() => void} enable - Function to enable the announcer.
70
- * @property {(delay: number) => void} pause - Function to pause the announcer.
71
- * @property {(message: string) => void} polite - Function to politely announce a message.
72
- * @property {(message: string, politeness: 'off'|'polite'|'assertive') => void} announce - Function to announce a message with specified politeness.
73
- * @property {() => void} stop - Function to stop the announcer.
74
- * @property {(v: boolean) => void} toggle - Function to toggle the announcer.
75
- *
76
- * @typedef {Object} BlitsElementConfig
77
- * @property {BlitsElement} parent - The parent element of this element.
78
- * @property {any} node - The node object for the element.
79
- *
80
- * @typedef {Object} BlitsElementProps
81
- * @property {boolean} __textnode - Indicates if the element is a text node.
82
- * @property {boolean} __layout - Indicates if the element is a layout node.
83
- * @property {BlitsElement} element - The element to which the props belong?? Do we need this?
84
- * @property {BlitsElementConfig} config - Configuration object for the element.
85
- * @property {Object<string, any>} props - The props object containing the properties of the element.
86
- * @property {Object<string, any>} raw - The raw input props.
87
- * @property {Object<string, any>} scheduledTransitions - Tracks transitions by property name.
88
- *
89
- * @typedef {Object} BlitsElement
90
- * @property {BlitsComponent} component - Reference to the owning Blits component.
91
- * @property {BlitsElementConfig} config - Configuration object for the element.
92
- * @property {number} counter - Unique counter used for shader workarounds. FIXME?
93
- * @property {string[]} effectNames - Names of active shader effects.
94
- * @property {any} node - The underlying renderer node (e.g., WebGL node or text node).
95
- * @property {BlitsElementProps} props - Proxy-like object containing transformed props.
96
- * @property {any[]} children - WVB I cant see this populated? Filtered list of children owned by this element. FIXME?
97
- * @property {any} parent - WVB Shortcut to the parent CoreNode?
98
- * @property {number} nodeId - ID of the CoreNode, if available.
99
- * @property {string|null} ref - Ref name (if defined).
100
- * @property {function(Object):void} populate - Initializes the element with props and hooks.
101
- * @property {function(string, any):void} set - Updates a single property.
102
- * @property {function(string, any, Object):void} animate - Animates a property with transition options.
103
- * @property {function():void} destroy - Destroys the underlying node and cancels transitions.
104
- * @property {function(any): any} triggerLayout - Triggers a layout update for the element.
105
- *
106
- * @typedef {Object} BlitsLifecycle
107
- * @property {BlitsComponent} component - The Blits comonent instance this lifecycle belongs to.
108
- * @property {'init'|'ready'|'destroyed'} current - The current lifecycle state of the component.
109
- * @property {'init'|'ready'|'destroyed'|null} previous - The previous lifecycle state of the component.
110
- * @property {string} state - The current lifecycle state of the component.
111
- *
112
- * @typedef {object} BlitsComponent
113
- * Main properties of the Blits component:
114
- * @property {string} componentId - The unique identifier for the component instance
115
- * @property {BlitsLifecycle} lifecycle - The lifecycle object for the component instance
116
- * @property {BlitsComponent} parent - The parent component of the current component instance
117
- * @property {BlitsComponent} rootParent - Reference to the root component in case of slots, otherwise this is the same as parent
118
- * @property {BlitsComponent|BlitsElement[]} [children] - The children of the component instance
119
- * @property {BlitsElement} [holder] - The wrapper of the entire component instance
120
- * @property {number} [id] - The internal ID for the component instance
121
- * @property {number} [identifier] - The identifier for the component instance WVB Whats the difference with id?
122
- * @property {any[]} [intervals] - The intervals created by the component instance WVB Fixme
123
- * @property {Object<string, any>} [originalState] - The original state of the component instance
124
- * @property {Object<string, any>} [props] - The reactive properties of the component instance
125
- * @property {any[]} [slots] - The slots of the component instance WVB Fixme
126
- * @property {Object<string, any>} [state] - The reactive state of the component instance
127
- * @property {any[]} [timeouts] - The timeouts created by the component instance
128
- * @property {BlitsElement} [wrapper] - The reference to the outer element of the component instance
129
- * @property {object} [effects] - The effects of the component instance
130
- * @property {object} [watchers] - The watchers of the component instance
131
- * @property {Object<string, any>} computedKeys - The computed keys of the component instance
132
- * @property {any[]} [effects] - The effects of the component instance
133
- * @property {string[]} [propKeys] - The keys of the props of the component instance
134
- * @property {any[]} [stateKeys] - The keys of the state of the component instance
135
- * Single props:
136
- * @property {object} activeView - The active view of the component instance
137
- * @property {boolean} hasFocus - Indicates if the component has focus
138
- * @property {string} ref - The reference name of the component instance
139
- * @property {number|undefined} index - The index in a for loop
140
- * @property {number|undefined} activeRow - The active row in a for loop
141
- * @property {string} color - The color of the component instance
142
- * @property {number} radius - The radius of the component instance
143
- * @property {number} size - The size of the component instance
144
- * Methods:
145
- * @property {function(): void} destroy - Destroys the component and its children, clearing listeners and effects.
146
- * @property {function(any): any} focus - ⚠️ Deprecated. Use `$focus()` instead.
147
- * @property {function(string): any} select - ⚠️ Deprecated. Use `$select(ref)` instead.
148
- * @property {function(string, object): {type: string, props: object}} shader - Creates a shader definition object.
149
- * @property {function(string): void} trigger - ⚠️ Deprecated. Use `$trigger(key)` instead.
150
- * @property {function(): void} unfocus - Clears the focus state and sets lifecycle to 'unfocus'.
151
- * Builtins:
152
- * @property {BlitsAnnouncer} $announcer - The announcer object for the component instance
153
- * @property {() => void} $clearTimeouts - Clears all timeouts created by the component instance
154
- * @property {(timeoutId: number) => void} $clearTimeout - Clears all timeouts created by the component instance
155
- * @property {() => void} $clearIntervals - Clears all intervals created by the component instance
156
- * @property {(intervalId: number) => void} $clearInterval - Clears all intervals created by the component instance
157
- * @property {Object} $colors - The colors object for the component instance
158
- * @property {(event: string, params: object) => void} $emit - Emits an event with the specified parameters
159
- * @property {function(any): void} $focus - Sets the component focus state and delegates to the Focus manager.
160
- * @property {Object} $language - The language object for the component instance
161
- * @property {function(any): any} $listen - The listen object for the component instance
162
- * @property {Object} $log - The log object for the component instance
163
- * @property {Object} $router - The router object for the component instance
164
- * @property {function(string): any} $select - Selects a child component by `ref`.
165
- * @property {function(any): any} $setInterval - Sets an interval for the component instance
166
- * @property {function(any): any} $setTimeout - Sets a timeout for the component instance
167
- * @property {function(any): any} $size - The size object for the component instance
168
- * @property {function(any): any} $sizes - The sizes object for the component instance
169
- * @property {function(string): void} $trigger - Forces a reactivity trigger on a property in `originalState`.
170
- * @property {function(any): any} $unlisten - The unlisten object for the component instance
171
- *
172
- */
173
-
174
- /**
175
- * @typedef {Object} BlitsComponentConfig
176
- * @property {string} template - The template string for the component.
177
- * @property {function(this: BlitsComponent): Object} [state] - State factory function.
178
- * @property {Object} [hooks] - Lifecycle hooks (frameTick, idle, attach, detach, enter, exit, etc).
179
- * @property {Object} [code] - Compiled render/effects code (render: Function, effects: Function[]).
180
- * @property {string} [name] - Optional name for the component.
181
- * @property {any} [data] - Optional static data for the component.
182
- * @property {Object} [options] - Optional options for the component.
183
- */
184
-
185
- /**
186
- * Component factory function
187
- * @param {string} name - The name of the component
188
- * @param {BlitsComponentConfig} config - The configuration object for the component
189
- * @returns {BlitsComponentFactory} - The component factory function
190
- */
191
- const Component = (name = required('name'), config = required('config')) => {
192
- let base = undefined
193
-
194
- const component = function (opts, parentEl, parentComponent, rootComponent) {
195
- // generate a human readable ID for the component instance (i.e. Blits::ComponentName1)
196
- this.componentId = createHumanReadableId(name)
197
-
198
- this[symbols.effects] = []
199
-
200
- // instantiate a lifecycle object for this instance
201
- this.lifecycle = Object.assign(Object.create(Lifecycle), {
202
- component: this,
203
- previous: null,
204
- current: null,
205
- })
206
-
207
- // set a reference to the parent component
208
- this.parent = parentComponent
209
-
210
- //
211
- this.rootParent = rootComponent
212
-
213
- // set a reference to the holder / parentElement
214
- // Components are wrapped in a holder node (used to apply positioning and transforms
215
- // such as rotation and scale to components)
216
- this[symbols.holder] = parentEl
217
-
218
- // generate an internal id (simple counter)
219
- this[symbols.id] = createInternalId()
220
-
221
- // set the internal props and make them reactive (based on the reactivity mode)
222
- this[symbols.props] = reactive(opts.props || {}, Settings.get('reactivityMode'))
223
-
224
- // create an empty array for storing timeouts created by this component (via this.$setTimeout)
225
- this[symbols.timeouts] = []
226
-
227
- // create an empty array for storing intervals created by this component (via this.$setInterval)
228
- this[symbols.intervals] = []
229
-
230
- // apply the state function (passing in the this reference to utilize configured props)
231
- // and store a reference to this original state
232
- this[symbols.originalState] =
233
- (config.state && typeof config.state === 'function' && config.state.apply(this)) || {}
234
- // add hasFocus key in
235
- this[symbols.originalState]['hasFocus'] = false
236
-
237
- // generate a reactive state (using the result of previously execute state function)
238
- // and store it
239
- this[symbols.state] = reactive(this[symbols.originalState], Settings.get('reactivityMode'))
240
-
241
- // all basic setup has been done now, set the lifecycle to state 'init'
242
- this.lifecycle.state = 'init'
243
-
244
- // execute the render code that constructs the initial state of the component
245
- // and store the children result (a flat map of elements and components)
246
- this[symbols.children] =
247
- config.code.render.apply(stage, [
248
- parentEl,
249
- this,
250
- config,
251
- globalComponents,
252
- effect,
253
- getRaw,
254
- Log,
255
- ]) || []
256
-
257
- // create a reference to the wrapper element of the component (i.e. the root Element of the component)
258
- this[symbols.wrapper] = this[symbols.children][0]
259
-
260
- // create a reference to an array of children that are slots
261
- this[symbols.slots] = this[symbols.children].filter((child) => child[symbols.isSlot])
262
-
263
- // register hooks if component has hooks specified
264
- if (config.hooks) {
265
- // frame tick event
266
- if (config.hooks.frameTick) {
267
- renderer.on('frameTick', (r, data) =>
268
- emit('frameTick', this[symbols.identifier], this, [data])
269
- )
270
- }
271
-
272
- if (config.hooks.idle) {
273
- renderer.on('idle', () => {
274
- emit('idle', this[symbols.identifier], this)
275
- })
276
- }
277
-
278
- // inBounds event emiting a lifecycle attach event
279
- if (config.hooks.attach) {
280
- this[symbols.wrapper].node.on('inBounds', () => {
281
- this.lifecycle.state = 'attach'
282
- })
283
- }
284
-
285
- // outOfBounds event emiting a lifeycle detach event
286
- if (config.hooks.detach) {
287
- this[symbols.wrapper].node.on('outOfBounds', (node, { previous }) => {
288
- if (previous > 0) this.lifecycle.state = 'detach'
289
- })
290
- }
291
-
292
- // inViewport event emiting a lifecycle enter event
293
- if (config.hooks.enter) {
294
- this[symbols.wrapper].node.on('inViewport', () => {
295
- this.lifecycle.state = 'enter'
296
- })
297
- }
298
-
299
- // outOfViewport event emitting a lifecycle exit event
300
- if (config.hooks.exit) {
301
- this[symbols.wrapper].node.on('outOfBounds', () => {
302
- this.lifecycle.state = 'exit'
303
- })
304
- }
305
- }
306
-
307
- // setup (and execute) all the generated side effects based on the
308
- // reactive bindings define in the template
309
- const effects = config.code.effects
310
- for (let i = 0; i < effects.length; i++) {
311
- const eff = () => {
312
- effects[i](this, this[symbols.children], config, globalComponents, rootComponent, effect)
313
- }
314
- // store reference to the effect
315
- this[symbols.effects].push(eff)
316
- effect(eff)
317
- }
318
-
319
- // setup watchers if the components has watchers specified
320
- if (this[symbols.watchers]) {
321
- const watcherkeys = Object.keys(this[symbols.watchers])
322
- const watcherkeysLength = watcherkeys.length
323
- for (let i = 0; i < watcherkeysLength; i++) {
324
- let target = this
325
- let key = watcherkeys[i]
326
- const watchKey = key
327
- // when dot notation used, find the nested target
328
- if (key.indexOf('.') > -1) {
329
- const keys = key.split('.')
330
- key = keys.pop()
331
- for (let i = 0; i < keys.length; i++) {
332
- target = target[keys[i]]
333
- }
334
- }
335
-
336
- let old = this[key]
337
-
338
- const eff = (force = false) => {
339
- const newValue = target[key]
340
- if (old !== newValue || force === true) {
341
- this[symbols.watchers][watchKey].apply(this, [newValue, old])
342
- old = newValue
343
- }
344
- }
345
-
346
- // store reference to the effect
347
- this[symbols.effects].push(eff)
348
- effect(eff)
349
- }
350
- }
351
-
352
- // finaly set the lifecycle state to ready (in the next tick)
353
- setTimeout(() => (this.lifecycle.state = 'ready'))
354
-
355
- // and return this
356
- return this
357
- }
358
-
359
- /**
360
- * Component factory function
361
- * @typedef {BlitsComponentFactory}
362
- * @param {Object} options
363
- * @param {BlitsComponent} parentEl
364
- * @param {BlitsComponent} parentComponent
365
- * @param {BlitsComponent} rootComponent
366
- * @returns {BlitsComponent}
367
- */
368
- const factory = (options = {}, parentEl, parentComponent, rootComponent) => {
369
- if (Base[symbols['launched']] === false) {
370
- // Register user defined plugins once on the Base object (after launch)
371
- const pluginKeys = Object.keys(plugins)
372
- const pluginKeysLength = pluginKeys.length
373
- /** @type {Object} */
374
- const pluginInstances = {}
375
- for (let i = 0; i < pluginKeysLength; i++) {
376
- const pluginName = pluginKeys[i]
377
- const prefixedPluginName = `$${pluginName}`
378
- if (prefixedPluginName in Base) {
379
- Log.warn(
380
- `"${pluginName}" (this.${prefixedPluginName}) already exists as a property or plugin on the Base Component. You may be overwriting built-in functionality. Proceed with care!`
381
- )
382
- }
383
-
384
- const plugin = plugins[pluginName]
385
-
386
- pluginInstances[prefixedPluginName] = {
387
- // instantiate the plugin, passing in provided options
388
- value: Object.defineProperties(plugin.plugin(plugin.options), shared),
389
- writable: false,
390
- enumerable: true,
391
- configurable: true,
392
- }
393
- }
394
-
395
- Object.defineProperties(Base, pluginInstances)
396
-
397
- // expose other plugins inside each plugin
398
- for (const plugin in pluginInstances) {
399
- Object.defineProperties(Base[plugin], pluginInstances)
400
- // but remove reference to plugin itself
401
- delete Base[plugin][plugin]
402
- }
403
-
404
- // register global components once
405
- globalComponents = components()
406
-
407
- // mark launched true
408
- Base[symbols['launched']] = true
409
- }
410
-
411
- // setup the component once per component type, using Base as the prototype
412
- if (base === undefined) {
413
- Log.debug(`Setting up ${name} component`)
414
- base = setupComponent(Object.create(Base), config)
415
- }
416
-
417
- // one time code generation (only if precompilation is turned off)
418
- if (config.code === undefined) {
419
- Log.debug(`Generating code for ${name} component`)
420
- config.code = codegenerator.call(config, parser(config.template, name))
421
- }
422
-
423
- // create an instance of the component, using base as the prototype (which contains Base)
424
- return component.call(Object.create(base), options, parentEl, parentComponent, rootComponent)
425
- }
426
-
427
- // store the config on the factory, in order to access the config
428
- // during the code generation step
429
- factory[Symbol.for('config')] = config
430
-
431
- // To determine whether dynamic component is actual Blits component or not
432
- factory[symbols.isComponent] = true
433
-
434
- return factory
435
- }
436
-
437
- export default Component
1
+ /*
2
+ * Copyright 2023 Comcast Cable Communications Management, LLC
3
+ * Licensed under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License.
5
+ * You may obtain a copy of the License at
6
+ *
7
+ * http://www.apache.org/licenses/LICENSE-2.0
8
+ *
9
+ * Unless required by applicable law or agreed to in writing, software
10
+ * distributed under the License is distributed on an "AS IS" BASIS,
11
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ * See the License for the specific language governing permissions and
13
+ * limitations under the License.
14
+ *
15
+ * SPDX-License-Identifier: Apache-2.0
16
+ */
17
+
18
+ import { Log } from './lib/log.js'
19
+ import parser from './lib/templateparser/parser.js'
20
+ import codegenerator from './lib/codegenerator/generator.js'
21
+ import { createHumanReadableId, createInternalId } from './lib/componentId.js'
22
+ import { emit } from './lib/hooks.js'
23
+ import { reactive, getRaw } from './lib/reactivity/reactive.js'
24
+ import { effect } from './lib/reactivity/effect.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 { default as Base, shared } from './component/base/index.js'
31
+
32
+ import Settings from './settings.js'
33
+
34
+ import setupComponent from './component/setup/index.js'
35
+ import components from './components/index.js'
36
+
37
+ import { plugins } from './plugin.js'
38
+
39
+ // object to store global components
40
+ let globalComponents
41
+
42
+ const required = (name) => {
43
+ throw new Error(`Parameter ${name} is required`)
44
+ }
45
+
46
+ /**
47
+ * @typedef {function} BlitsComponentFactory
48
+ * @param {object} opts - The options for the component instance
49
+ * @param {BlitsComponent} parentEl - The parent element for the component instance
50
+ * @param {BlitsComponent} parentComponent - The parent component for the component instance
51
+ * @param {BlitsComponent} rootComponent - The root component for the component instance
52
+ * @returns {BlitsComponent} - The component instance
53
+ */
54
+
55
+ /**
56
+ * Structure of a Blits component:
57
+ * Component:
58
+ * <Holder>
59
+ * <Wrapper>
60
+ * []<Elements>
61
+ * </Wrapper>
62
+ * </Holder>
63
+ *
64
+ * A Blits Element
65
+ * @typedef {Object} BlitsAnnouncer
66
+ * @property {(message: string) => void} assertive - Function to assertively announce a message.
67
+ * @property {() => void} clear - Function to clear the announcer.
68
+ * @property {() => void} disable - Function to disable the announcer.
69
+ * @property {() => void} enable - Function to enable the announcer.
70
+ * @property {(delay: number) => void} pause - Function to pause the announcer.
71
+ * @property {(message: string) => void} polite - Function to politely announce a message.
72
+ * @property {(message: string, politeness: 'off'|'polite'|'assertive') => void} announce - Function to announce a message with specified politeness.
73
+ * @property {() => void} stop - Function to stop the announcer.
74
+ * @property {(v: boolean) => void} toggle - Function to toggle the announcer.
75
+ *
76
+ * @typedef {Object} BlitsElementConfig
77
+ * @property {BlitsElement} parent - The parent element of this element.
78
+ * @property {any} node - The node object for the element.
79
+ *
80
+ * @typedef {Object} BlitsElementProps
81
+ * @property {boolean} __textnode - Indicates if the element is a text node.
82
+ * @property {boolean} __layout - Indicates if the element is a layout node.
83
+ * @property {BlitsElement} element - The element to which the props belong?? Do we need this?
84
+ * @property {BlitsElementConfig} config - Configuration object for the element.
85
+ * @property {Object<string, any>} props - The props object containing the properties of the element.
86
+ * @property {Object<string, any>} raw - The raw input props.
87
+ * @property {Object<string, any>} scheduledTransitions - Tracks transitions by property name.
88
+ *
89
+ * @typedef {Object} BlitsElement
90
+ * @property {BlitsComponent} component - Reference to the owning Blits component.
91
+ * @property {BlitsElementConfig} config - Configuration object for the element.
92
+ * @property {number} counter - Unique counter used for shader workarounds. FIXME?
93
+ * @property {string[]} effectNames - Names of active shader effects.
94
+ * @property {any} node - The underlying renderer node (e.g., WebGL node or text node).
95
+ * @property {BlitsElementProps} props - Proxy-like object containing transformed props.
96
+ * @property {any[]} children - WVB I cant see this populated? Filtered list of children owned by this element. FIXME?
97
+ * @property {any} parent - WVB Shortcut to the parent CoreNode?
98
+ * @property {number} nodeId - ID of the CoreNode, if available.
99
+ * @property {string|null} ref - Ref name (if defined).
100
+ * @property {function(Object):void} populate - Initializes the element with props and hooks.
101
+ * @property {function(string, any):void} set - Updates a single property.
102
+ * @property {function(string, any, Object):void} animate - Animates a property with transition options.
103
+ * @property {function():void} destroy - Destroys the underlying node and cancels transitions.
104
+ * @property {function(any): any} triggerLayout - Triggers a layout update for the element.
105
+ *
106
+ * @typedef {Object} BlitsLifecycle
107
+ * @property {BlitsComponent} component - The Blits comonent instance this lifecycle belongs to.
108
+ * @property {'init'|'ready'|'destroyed'} current - The current lifecycle state of the component.
109
+ * @property {'init'|'ready'|'destroyed'|null} previous - The previous lifecycle state of the component.
110
+ * @property {string} state - The current lifecycle state of the component.
111
+ *
112
+ * @typedef {object} BlitsComponent
113
+ * Main properties of the Blits component:
114
+ * @property {string} componentId - The unique identifier for the component instance
115
+ * @property {BlitsLifecycle} lifecycle - The lifecycle object for the component instance
116
+ * @property {BlitsComponent} parent - The parent component of the current component instance
117
+ * @property {BlitsComponent} rootParent - Reference to the root component in case of slots, otherwise this is the same as parent
118
+ * @property {BlitsComponent|BlitsElement[]} [children] - The children of the component instance
119
+ * @property {BlitsElement} [holder] - The wrapper of the entire component instance
120
+ * @property {number} [id] - The internal ID for the component instance
121
+ * @property {number} [identifier] - The identifier for the component instance WVB Whats the difference with id?
122
+ * @property {any[]} [intervals] - The intervals created by the component instance WVB Fixme
123
+ * @property {Object<string, any>} [originalState] - The original state of the component instance
124
+ * @property {Object<string, any>} [props] - The reactive properties of the component instance
125
+ * @property {any[]} [slots] - The slots of the component instance WVB Fixme
126
+ * @property {Object<string, any>} [state] - The reactive state of the component instance
127
+ * @property {any[]} [timeouts] - The timeouts created by the component instance
128
+ * @property {BlitsElement} [wrapper] - The reference to the outer element of the component instance
129
+ * @property {object} [effects] - The effects of the component instance
130
+ * @property {object} [watchers] - The watchers of the component instance
131
+ * @property {Object<string, any>} computedKeys - The computed keys of the component instance
132
+ * @property {any[]} [effects] - The effects of the component instance
133
+ * @property {string[]} [propKeys] - The keys of the props of the component instance
134
+ * @property {any[]} [stateKeys] - The keys of the state of the component instance
135
+ * Single props:
136
+ * @property {object} activeView - The active view of the component instance
137
+ * @property {boolean} hasFocus - Indicates if the component has focus
138
+ * @property {string} ref - The reference name of the component instance
139
+ * @property {number|undefined} index - The index in a for loop
140
+ * @property {number|undefined} activeRow - The active row in a for loop
141
+ * @property {string} color - The color of the component instance
142
+ * @property {number} radius - The radius of the component instance
143
+ * @property {number} size - The size of the component instance
144
+ * Methods:
145
+ * @property {function(): void} destroy - Destroys the component and its children, clearing listeners and effects.
146
+ * @property {function(any): any} focus - ⚠️ Deprecated. Use `$focus()` instead.
147
+ * @property {function(string): any} select - ⚠️ Deprecated. Use `$select(ref)` instead.
148
+ * @property {function(string, object): {type: string, props: object}} shader - Creates a shader definition object.
149
+ * @property {function(string): void} trigger - ⚠️ Deprecated. Use `$trigger(key)` instead.
150
+ * @property {function(): void} unfocus - Clears the focus state and sets lifecycle to 'unfocus'.
151
+ * Builtins:
152
+ * @property {BlitsAnnouncer} $announcer - The announcer object for the component instance
153
+ * @property {() => void} $clearTimeouts - Clears all timeouts created by the component instance
154
+ * @property {(timeoutId: number) => void} $clearTimeout - Clears all timeouts created by the component instance
155
+ * @property {() => void} $clearIntervals - Clears all intervals created by the component instance
156
+ * @property {(intervalId: number) => void} $clearInterval - Clears all intervals created by the component instance
157
+ * @property {Object} $colors - The colors object for the component instance
158
+ * @property {(event: string, params: object) => void} $emit - Emits an event with the specified parameters
159
+ * @property {function(any): void} $focus - Sets the component focus state and delegates to the Focus manager.
160
+ * @property {Object} $language - The language object for the component instance
161
+ * @property {function(any): any} $listen - The listen object for the component instance
162
+ * @property {Object} $log - The log object for the component instance
163
+ * @property {Object} $router - The router object for the component instance
164
+ * @property {function(string): any} $select - Selects a child component by `ref`.
165
+ * @property {function(any): any} $setInterval - Sets an interval for the component instance
166
+ * @property {function(any): any} $setTimeout - Sets a timeout for the component instance
167
+ * @property {function(any): any} $size - The size object for the component instance
168
+ * @property {function(any): any} $sizes - The sizes object for the component instance
169
+ * @property {function(string): void} $trigger - Forces a reactivity trigger on a property in `originalState`.
170
+ * @property {function(any): any} $unlisten - The unlisten object for the component instance
171
+ *
172
+ */
173
+
174
+ /**
175
+ * @typedef {Object} BlitsComponentConfig
176
+ * @property {string} template - The template string for the component.
177
+ * @property {function(this: BlitsComponent): Object} [state] - State factory function.
178
+ * @property {Object} [hooks] - Lifecycle hooks (frameTick, idle, attach, detach, enter, exit, etc).
179
+ * @property {Object} [code] - Compiled render/effects code (render: Function, effects: Function[]).
180
+ * @property {string} [name] - Optional name for the component.
181
+ * @property {any} [data] - Optional static data for the component.
182
+ * @property {Object} [options] - Optional options for the component.
183
+ */
184
+
185
+ /**
186
+ * Component factory function
187
+ * @param {string} name - The name of the component
188
+ * @param {BlitsComponentConfig} config - The configuration object for the component
189
+ * @returns {BlitsComponentFactory} - The component factory function
190
+ */
191
+ const Component = (name = required('name'), config = required('config')) => {
192
+ let base = undefined
193
+
194
+ const component = function (opts, parentEl, parentComponent, rootComponent) {
195
+ // generate a human readable ID for the component instance (i.e. Blits::ComponentName1)
196
+ this.componentId = createHumanReadableId(name)
197
+
198
+ this[symbols.effects] = []
199
+
200
+ // instantiate a lifecycle object for this instance
201
+ this.lifecycle = Object.assign(Object.create(Lifecycle), {
202
+ component: this,
203
+ previous: null,
204
+ current: null,
205
+ })
206
+
207
+ // set a reference to the parent component
208
+ this.parent = parentComponent
209
+
210
+ //
211
+ this.rootParent = rootComponent
212
+
213
+ // set a reference to the holder / parentElement
214
+ // Components are wrapped in a holder node (used to apply positioning and transforms
215
+ // such as rotation and scale to components)
216
+ this[symbols.holder] = parentEl
217
+
218
+ // generate an internal id (simple counter)
219
+ this[symbols.id] = createInternalId()
220
+
221
+ // set the internal props and make them reactive (based on the reactivity mode)
222
+ this[symbols.props] = reactive(opts.props || {}, Settings.get('reactivityMode'))
223
+
224
+ // create an empty array for storing timeouts created by this component (via this.$setTimeout)
225
+ this[symbols.timeouts] = []
226
+
227
+ // create an empty array for storing intervals created by this component (via this.$setInterval)
228
+ this[symbols.intervals] = []
229
+
230
+ // apply the state function (passing in the this reference to utilize configured props)
231
+ // and store a reference to this original state
232
+ this[symbols.originalState] =
233
+ (config.state && typeof config.state === 'function' && config.state.apply(this)) || {}
234
+ // add hasFocus key in
235
+ this[symbols.originalState]['hasFocus'] = false
236
+
237
+ // generate a reactive state (using the result of previously execute state function)
238
+ // and store it
239
+ this[symbols.state] = reactive(this[symbols.originalState], Settings.get('reactivityMode'))
240
+
241
+ // all basic setup has been done now, set the lifecycle to state 'init'
242
+ this.lifecycle.state = 'init'
243
+
244
+ // execute the render code that constructs the initial state of the component
245
+ // and store the children result (a flat map of elements and components)
246
+ this[symbols.children] =
247
+ config.code.render.apply(stage, [
248
+ parentEl,
249
+ this,
250
+ config,
251
+ globalComponents,
252
+ effect,
253
+ getRaw,
254
+ Log,
255
+ ]) || []
256
+
257
+ // create a reference to the wrapper element of the component (i.e. the root Element of the component)
258
+ this[symbols.wrapper] = this[symbols.children][0]
259
+
260
+ // create a reference to an array of children that are slots
261
+ this[symbols.slots] = this[symbols.children].filter((child) => child[symbols.isSlot])
262
+
263
+ // register hooks if component has hooks specified
264
+ if (config.hooks) {
265
+ // frame tick event
266
+ if (config.hooks.frameTick) {
267
+ renderer.on('frameTick', (r, data) =>
268
+ emit('frameTick', this[symbols.identifier], this, [data])
269
+ )
270
+ }
271
+
272
+ if (config.hooks.idle) {
273
+ renderer.on('idle', () => {
274
+ emit('idle', this[symbols.identifier], this)
275
+ })
276
+ }
277
+
278
+ // inBounds event emiting a lifecycle attach event
279
+ if (config.hooks.attach) {
280
+ this[symbols.wrapper].node.on('inBounds', () => {
281
+ this.lifecycle.state = 'attach'
282
+ })
283
+ }
284
+
285
+ // outOfBounds event emiting a lifeycle detach event
286
+ if (config.hooks.detach) {
287
+ this[symbols.wrapper].node.on('outOfBounds', (node, { previous }) => {
288
+ if (previous > 0) this.lifecycle.state = 'detach'
289
+ })
290
+ }
291
+
292
+ // inViewport event emiting a lifecycle enter event
293
+ if (config.hooks.enter) {
294
+ this[symbols.wrapper].node.on('inViewport', () => {
295
+ this.lifecycle.state = 'enter'
296
+ })
297
+ }
298
+
299
+ // outOfViewport event emitting a lifecycle exit event
300
+ if (config.hooks.exit) {
301
+ this[symbols.wrapper].node.on('outOfBounds', () => {
302
+ this.lifecycle.state = 'exit'
303
+ })
304
+ }
305
+ }
306
+
307
+ // setup (and execute) all the generated side effects based on the
308
+ // reactive bindings define in the template
309
+ const effects = config.code.effects
310
+ for (let i = 0; i < effects.length; i++) {
311
+ const eff = () => {
312
+ effects[i](this, this[symbols.children], config, globalComponents, rootComponent, effect)
313
+ }
314
+ // store reference to the effect
315
+ this[symbols.effects].push(eff)
316
+ effect(eff)
317
+ }
318
+
319
+ // setup watchers if the components has watchers specified
320
+ if (this[symbols.watchers]) {
321
+ const watcherkeys = Object.keys(this[symbols.watchers])
322
+ const watcherkeysLength = watcherkeys.length
323
+ for (let i = 0; i < watcherkeysLength; i++) {
324
+ let target = this
325
+ let key = watcherkeys[i]
326
+ const watchKey = key
327
+ // when dot notation used, find the nested target
328
+ if (key.indexOf('.') > -1) {
329
+ const keys = key.split('.')
330
+ key = keys.pop()
331
+ for (let i = 0; i < keys.length; i++) {
332
+ target = target[keys[i]]
333
+ }
334
+ }
335
+
336
+ let old = this[key]
337
+
338
+ const eff = (force = false) => {
339
+ const newValue = target[key]
340
+ if (old !== newValue || force === true) {
341
+ this[symbols.watchers][watchKey].apply(this, [newValue, old])
342
+ old = newValue
343
+ }
344
+ }
345
+
346
+ // store reference to the effect
347
+ this[symbols.effects].push(eff)
348
+ effect(eff)
349
+ }
350
+ }
351
+
352
+ // finaly set the lifecycle state to ready (in the next tick)
353
+ setTimeout(() => (this.lifecycle.state = 'ready'))
354
+
355
+ // and return this
356
+ return this
357
+ }
358
+
359
+ /**
360
+ * Component factory function
361
+ * @typedef {BlitsComponentFactory}
362
+ * @param {Object} options
363
+ * @param {BlitsComponent} parentEl
364
+ * @param {BlitsComponent} parentComponent
365
+ * @param {BlitsComponent} rootComponent
366
+ * @returns {BlitsComponent}
367
+ */
368
+ const factory = (options = {}, parentEl, parentComponent, rootComponent) => {
369
+ if (Base[symbols['launched']] === false) {
370
+ // Register user defined plugins once on the Base object (after launch)
371
+ const pluginKeys = Object.keys(plugins)
372
+ const pluginKeysLength = pluginKeys.length
373
+ /** @type {Object} */
374
+ const pluginInstances = {}
375
+ for (let i = 0; i < pluginKeysLength; i++) {
376
+ const pluginName = pluginKeys[i]
377
+ const prefixedPluginName = `$${pluginName}`
378
+ if (prefixedPluginName in Base) {
379
+ Log.warn(
380
+ `"${pluginName}" (this.${prefixedPluginName}) already exists as a property or plugin on the Base Component. You may be overwriting built-in functionality. Proceed with care!`
381
+ )
382
+ }
383
+
384
+ const plugin = plugins[pluginName]
385
+
386
+ pluginInstances[prefixedPluginName] = {
387
+ // instantiate the plugin, passing in provided options
388
+ value: Object.defineProperties(plugin.plugin(plugin.options), shared),
389
+ writable: false,
390
+ enumerable: true,
391
+ configurable: true,
392
+ }
393
+ }
394
+
395
+ Object.defineProperties(Base, pluginInstances)
396
+
397
+ // expose other plugins inside each plugin
398
+ for (const plugin in pluginInstances) {
399
+ Object.defineProperties(Base[plugin], pluginInstances)
400
+ // but remove reference to plugin itself
401
+ delete Base[plugin][plugin]
402
+ }
403
+
404
+ // register global components once
405
+ globalComponents = components()
406
+
407
+ // mark launched true
408
+ Base[symbols['launched']] = true
409
+ }
410
+
411
+ // setup the component once per component type, using Base as the prototype
412
+ if (base === undefined) {
413
+ Log.debug(`Setting up ${name} component`)
414
+ base = setupComponent(Object.create(Base), config)
415
+ }
416
+
417
+ // one time code generation (only if precompilation is turned off)
418
+ if (config.code === undefined) {
419
+ Log.debug(`Generating code for ${name} component`)
420
+ config.code = codegenerator.call(config, parser(config.template, name))
421
+ }
422
+
423
+ // create an instance of the component, using base as the prototype (which contains Base)
424
+ return component.call(Object.create(base), options, parentEl, parentComponent, rootComponent)
425
+ }
426
+
427
+ // store the config on the factory, in order to access the config
428
+ // during the code generation step
429
+ factory[Symbol.for('config')] = config
430
+
431
+ // To determine whether dynamic component is actual Blits component or not
432
+ factory[symbols.isComponent] = true
433
+
434
+ return factory
435
+ }
436
+
437
+ export default Component