@lightningjs/blits 1.30.0 → 1.30.2

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 (149) hide show
  1. package/.editorconfig +7 -7
  2. package/.eslintignore +1 -1
  3. package/.eslintrc.cjs +65 -65
  4. package/.github/workflows/comment-lint-results.yml +124 -124
  5. package/.github/workflows/comment-test-results.yml +108 -108
  6. package/.github/workflows/deploy-docs.yml +27 -27
  7. package/.github/workflows/run-blits-tests.yml +141 -141
  8. package/.github/workflows/run-lint.yml +127 -127
  9. package/.husky/pre-commit +1 -1
  10. package/.vscode/extensions.json +7 -7
  11. package/.vscode/settings.json +9 -9
  12. package/CHANGELOG.md +1019 -1005
  13. package/COPYING +1 -0
  14. package/LICENSE +202 -202
  15. package/NOTICE +12 -12
  16. package/README.md +33 -33
  17. package/bin/index.js +8 -8
  18. package/blits.d.ts +6 -6
  19. package/docs/README.md +19 -19
  20. package/docs/_sidebar.md +37 -37
  21. package/docs/built-in/directives.md +50 -50
  22. package/docs/built-in/for-loop.md +203 -203
  23. package/docs/built-in/layout.md +168 -168
  24. package/docs/components/component_state.md +90 -90
  25. package/docs/components/computed_properties.md +45 -45
  26. package/docs/components/lifecycle_events.md +42 -42
  27. package/docs/components/methods.md +45 -45
  28. package/docs/components/props.md +56 -56
  29. package/docs/components/user_input.md +186 -186
  30. package/docs/components/utility_methods.md +320 -320
  31. package/docs/components/watchers.md +34 -34
  32. package/docs/essentials/application_root.md +19 -19
  33. package/docs/essentials/components.md +68 -68
  34. package/docs/essentials/displaying_images.md +67 -67
  35. package/docs/essentials/displaying_text.md +135 -135
  36. package/docs/essentials/element_attributes.md +195 -195
  37. package/docs/essentials/settings.md +117 -117
  38. package/docs/essentials/template_syntax.md +83 -83
  39. package/docs/getting_started/file_structure.md +58 -58
  40. package/docs/getting_started/getting_started.md +45 -45
  41. package/docs/getting_started/intro.md +20 -20
  42. package/docs/index.html +47 -47
  43. package/docs/performance/lazy-loading.md +161 -161
  44. package/docs/plugins/global_app_state.md +98 -98
  45. package/docs/plugins/language.md +172 -172
  46. package/docs/plugins/text-to-speech-announcer.md +119 -119
  47. package/docs/plugins/theme.md +176 -176
  48. package/docs/router/basics.md +175 -175
  49. package/docs/router/hooks.md +70 -70
  50. package/docs/router/transitions.md +146 -146
  51. package/docs/sidebar.json +171 -171
  52. package/docs/transitions_animations/transitions.md +150 -150
  53. package/index.d.ts +1099 -1099
  54. package/index.js +28 -28
  55. package/jsconfig.json +12 -12
  56. package/package.json +56 -56
  57. package/public/assets/fps_sprite.base64.js +1 -1
  58. package/scripts/postpublish.js +59 -59
  59. package/scripts/prepublishOnly.js +84 -84
  60. package/scripts/prepush.js +78 -78
  61. package/src/announcer/announcer.js +162 -162
  62. package/src/announcer/speechSynthesis.js +107 -107
  63. package/src/application.js +100 -100
  64. package/src/component/base/announcer.js +27 -27
  65. package/src/component/base/events.js +69 -69
  66. package/src/component/base/index.js +33 -33
  67. package/src/component/base/methods.js +244 -244
  68. package/src/component/base/router.js +41 -41
  69. package/src/component/base/timeouts_intervals.js +99 -99
  70. package/src/component/base/utils.js +72 -72
  71. package/src/component/setup/computed.js +48 -48
  72. package/src/component/setup/index.js +62 -62
  73. package/src/component/setup/input.js +30 -30
  74. package/src/component/setup/methods.js +37 -37
  75. package/src/component/setup/props.js +62 -62
  76. package/src/component/setup/props.test.js +238 -238
  77. package/src/component/setup/routes.js +42 -42
  78. package/src/component/setup/state.js +59 -59
  79. package/src/component/setup/watch.js +35 -35
  80. package/src/component.js +437 -437
  81. package/src/component.test.js +388 -388
  82. package/src/components/Circle.js +73 -73
  83. package/src/components/Circle.orig.js +31 -31
  84. package/src/components/FPScounter.js +1510 -1510
  85. package/src/components/FPScounter.orig.js +121 -121
  86. package/src/components/RouterView.js +81 -81
  87. package/src/components/RouterView.orig.js +54 -54
  88. package/src/components/Sprite.js +94 -94
  89. package/src/components/Sprite.orig.js +58 -58
  90. package/src/components/index.js +28 -28
  91. package/src/components/index.orig.js +28 -28
  92. package/src/constants.js +21 -21
  93. package/src/engine.js +1 -1
  94. package/src/engines/L3/element.js +744 -744
  95. package/src/engines/L3/element.test.js +579 -579
  96. package/src/engines/L3/fontLoader.js +38 -38
  97. package/src/engines/L3/index.js +7 -7
  98. package/src/engines/L3/launch.js +123 -123
  99. package/src/engines/L3/shaderLoader.js +12 -12
  100. package/src/engines/index.js +1 -1
  101. package/src/focus.js +90 -90
  102. package/src/helpers/deepEqualArray.js +50 -50
  103. package/src/helpers/deepEqualArray.test.js +127 -127
  104. package/src/helpers/fetchJson.js +34 -34
  105. package/src/helpers/sequence.js +26 -26
  106. package/src/launch.js +123 -123
  107. package/src/lib/blitsfileconverter/blitsfileconverter.js +52 -52
  108. package/src/lib/codegenerator/generator.js +788 -788
  109. package/src/lib/codegenerator/generator.test.js +3801 -3801
  110. package/src/lib/colors/colors.js +92 -92
  111. package/src/lib/colors/colors.test.js +290 -290
  112. package/src/lib/colors/htmlColors.js +168 -168
  113. package/src/lib/componentId.js +49 -49
  114. package/src/lib/componentId.test.js +84 -84
  115. package/src/lib/eventListeners.js +123 -123
  116. package/src/lib/eventListeners.test.js +236 -236
  117. package/src/lib/hooks.js +59 -59
  118. package/src/lib/lifecycle.js +85 -85
  119. package/src/lib/log.js +117 -117
  120. package/src/lib/precompiler/precompiler.js +73 -73
  121. package/src/lib/reactivity/effect.js +93 -93
  122. package/src/lib/reactivity/effect.test.js +35 -35
  123. package/src/lib/reactivity/reactive.js +208 -208
  124. package/src/lib/reactivity/reactive.test.js +27 -27
  125. package/src/lib/reactivityguard/computedprops.js +218 -218
  126. package/src/lib/symbols.js +108 -108
  127. package/src/lib/templateparser/parser.js +386 -386
  128. package/src/lib/templateparser/parser.test.js +1411 -1411
  129. package/src/lib/utils.js +27 -27
  130. package/src/plugin.js +27 -27
  131. package/src/plugins/appstate.js +26 -26
  132. package/src/plugins/index.js +20 -20
  133. package/src/plugins/language.js +152 -152
  134. package/src/plugins/log.js +25 -25
  135. package/src/plugins/theme.js +94 -94
  136. package/src/router/router.js +514 -514
  137. package/src/router/router.test.js +473 -473
  138. package/src/router/transitions/fadeIn.js +27 -27
  139. package/src/router/transitions/fadeInOut.js +33 -33
  140. package/src/router/transitions/index.js +21 -21
  141. package/src/router/transitions/slideIn.js +28 -28
  142. package/src/router/transitions/slideInOut.js +33 -33
  143. package/src/settings.js +40 -40
  144. package/vite/blitsFileConverter.js +67 -67
  145. package/vite/index.js +28 -28
  146. package/vite/msdfGenerator.js +269 -269
  147. package/vite/preCompiler.js +42 -42
  148. package/vite/reactivityGuard.js +45 -45
  149. package/vscode/data/template-attributes.json +589 -589
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