@lightningjs/blits 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/.editorconfig +7 -0
  2. package/.eslintignore +1 -0
  3. package/.eslintrc.cjs +60 -0
  4. package/.vscode/extensions.json +6 -0
  5. package/.vscode/settings.json +9 -0
  6. package/LICENSE +202 -0
  7. package/NOTICE +3 -0
  8. package/README.md +11 -0
  9. package/bin/index.cjs +25 -0
  10. package/boilerplate/.editorconfig +7 -0
  11. package/boilerplate/.eslintignore +3 -0
  12. package/boilerplate/.eslintrc.js +38 -0
  13. package/boilerplate/.vscode/extensions.json +7 -0
  14. package/boilerplate/.vscode/launch.json +12 -0
  15. package/boilerplate/.vscode/settings.json +9 -0
  16. package/boilerplate/index.html +11 -0
  17. package/boilerplate/package.json +36 -0
  18. package/boilerplate/public/assets/logo.png +0 -0
  19. package/boilerplate/src/App.js +12 -0
  20. package/boilerplate/src/components/Loader.js +23 -0
  21. package/boilerplate/src/index.js +11 -0
  22. package/boilerplate/src/pages/Home.js +15 -0
  23. package/boilerplate/src/renderer.js +3 -0
  24. package/boilerplate/vite.config.js +23 -0
  25. package/docs/.nojekyll +0 -0
  26. package/docs/README.md +30 -0
  27. package/docs/_sidebar.md +9 -0
  28. package/docs/component_state.md +21 -0
  29. package/docs/components.md +27 -0
  30. package/docs/computed_properties.md +18 -0
  31. package/docs/getting_started.md +40 -0
  32. package/docs/index.html +47 -0
  33. package/docs/lifecycle_events.md +46 -0
  34. package/docs/props.md +44 -0
  35. package/docs/template_syntax.md +31 -0
  36. package/docs/user_input.md +64 -0
  37. package/docs/watchers.md +38 -0
  38. package/index.js +26 -0
  39. package/package.json +44 -0
  40. package/src/application.js +55 -0
  41. package/src/component.js +192 -0
  42. package/src/component.test.js +51 -0
  43. package/src/components/Circle.js +31 -0
  44. package/src/components/Image.js +33 -0
  45. package/src/components/RouterView.js +38 -0
  46. package/src/components/Sprite.js +56 -0
  47. package/src/components/Text.js +61 -0
  48. package/src/element.js +236 -0
  49. package/src/focus.js +65 -0
  50. package/src/helpers/sequence.js +26 -0
  51. package/src/launch.js +61 -0
  52. package/src/lib/codegenerator/generator.js +276 -0
  53. package/src/lib/codegenerator/generator.test.js +970 -0
  54. package/src/lib/colors/colors.js +70 -0
  55. package/src/lib/colors/colors.test.js +190 -0
  56. package/src/lib/colors/htmlColors.js +168 -0
  57. package/src/lib/componentId.js +27 -0
  58. package/src/lib/componentId.test.js +83 -0
  59. package/src/lib/eventListeners.js +55 -0
  60. package/src/lib/hooks.js +29 -0
  61. package/src/lib/log.js +106 -0
  62. package/src/lib/reactivity/effect.js +54 -0
  63. package/src/lib/reactivity/effect.test.js +35 -0
  64. package/src/lib/reactivity/reactive.js +113 -0
  65. package/src/lib/reactivity/reactive.test.js +27 -0
  66. package/src/lib/setup/base.js +218 -0
  67. package/src/lib/setup/computed.js +43 -0
  68. package/src/lib/setup/input.js +28 -0
  69. package/src/lib/setup/methods.js +35 -0
  70. package/src/lib/setup/props.js +53 -0
  71. package/src/lib/setup/props.test.js +205 -0
  72. package/src/lib/setup/routes.js +24 -0
  73. package/src/lib/setup/state.js +45 -0
  74. package/src/lib/setup/watch.js +33 -0
  75. package/src/lib/templateparser/parser.js +103 -0
  76. package/src/lib/templateparser/parser.test.js +769 -0
  77. package/src/router.js +67 -0
  78. package/src/settings.js +34 -0
  79. package/vite/index.js +1 -0
package/src/focus.js ADDED
@@ -0,0 +1,65 @@
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
+ let focusedComponent = null
19
+
20
+ export default {
21
+ get() {
22
+ return focusedComponent
23
+ },
24
+ set(component, event) {
25
+ if (component !== focusedComponent) {
26
+ if (focusedComponent && focusedComponent !== component.parent) {
27
+ focusedComponent.unfocus()
28
+ }
29
+ focusedComponent = component
30
+ focusedComponent.lifecycle.state = 'focus'
31
+ if (event instanceof KeyboardEvent) {
32
+ document.dispatchEvent(new KeyboardEvent('keydown', event))
33
+ }
34
+ }
35
+ },
36
+ input(key, event) {
37
+ const focusChain = walkChain([focusedComponent], key)
38
+ const componentWithInputEvent = focusChain.shift()
39
+
40
+ if (componentWithInputEvent) {
41
+ if (componentWithInputEvent !== focusedComponent) {
42
+ focusChain.reverse().forEach((component) => component.unfocus())
43
+ componentWithInputEvent.focus()
44
+ }
45
+ if (componentWithInputEvent.___inputEvents[key]) {
46
+ componentWithInputEvent.___inputEvents[key].call(componentWithInputEvent, event)
47
+ } else if (componentWithInputEvent.___inputEvents.any) {
48
+ componentWithInputEvent.___inputEvents.any.call(componentWithInputEvent, event)
49
+ }
50
+ }
51
+ },
52
+ }
53
+
54
+ const walkChain = (components, key) => {
55
+ if (
56
+ components[0].___inputEvents &&
57
+ (typeof components[0].___inputEvents[key] === 'function' ||
58
+ typeof components[0].___inputEvents.any === 'function')
59
+ ) {
60
+ return components
61
+ } else if (components[0].parent) {
62
+ components.unshift(components[0].parent)
63
+ return walkChain(components, key)
64
+ } else return []
65
+ }
@@ -0,0 +1,26 @@
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
+ export default (steps) => {
19
+ return steps.reduce((promise, method) => {
20
+ return promise
21
+ .then(function () {
22
+ return method(...arguments)
23
+ })
24
+ .catch((e) => Promise.reject(e))
25
+ }, Promise.resolve(null))
26
+ }
package/src/launch.js ADDED
@@ -0,0 +1,61 @@
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 { MainRenderDriver, RendererMain, ThreadXRenderDriver } from '@lightningjs/renderer'
19
+ // import RendererWorker from '@lightningjs/renderer/workers/renderer?worker'
20
+ import Settings from './settings.js'
21
+ import { initLog } from './lib/log.js'
22
+
23
+ export let renderer
24
+
25
+ export default (App, target, settings) => {
26
+ Settings.set(settings)
27
+
28
+ initLog()
29
+
30
+ const driver = new MainRenderDriver()
31
+ // settings.multithreaded === true
32
+ // ? new ThreadXRenderDriver({
33
+ // RendererWorker,
34
+ // })
35
+ // : new MainRenderDriver()
36
+
37
+ renderer = new RendererMain(
38
+ {
39
+ width: settings.width || 1920,
40
+ height: settings.height || 1080,
41
+ coreExtensionModule: settings.coreExtensionModule,
42
+ },
43
+ target,
44
+ driver
45
+ )
46
+
47
+ let app
48
+
49
+ const handler = (e) => {
50
+ if (e.key === 'Escape') {
51
+ document.removeEventListener('keydown', handler)
52
+ app.destroy()
53
+ app = null
54
+ renderer = null
55
+ }
56
+ }
57
+
58
+ document.addEventListener('keydown', handler)
59
+
60
+ renderer.init().then(() => (app = App()))
61
+ }
@@ -0,0 +1,276 @@
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
+ let counter
19
+
20
+ export default function (templateObject = { children: [] }) {
21
+ const ctx = {
22
+ renderCode: ['const elms = []'],
23
+ effectsCode: [],
24
+ context: { props: [], components: this.components },
25
+ }
26
+
27
+ counter = -1
28
+ generateCode.call(ctx, templateObject)
29
+ ctx.renderCode.push('return elms')
30
+
31
+ return {
32
+ render: new Function('parent', 'component', 'context', ctx.renderCode.join('\n')),
33
+ effects: ctx.effectsCode.map((code) => new Function('component', 'elms', 'context', code)),
34
+ context: ctx.context,
35
+ }
36
+ }
37
+
38
+ const generateElementCode = function (
39
+ templateObject,
40
+ parent = false,
41
+ options = { index: false, component: 'component.', forceEffect: false }
42
+ ) {
43
+ const renderCode = options.forceEffect ? this.effectsCode : this.renderCode
44
+ if (parent) {
45
+ renderCode.push(`parent = ${parent}`)
46
+ }
47
+
48
+ const elm = options.index ? `elms[${counter}][${options.index}]` : `elms[${counter}]`
49
+
50
+ if (options.index) {
51
+ renderCode.push(`
52
+ elms[${counter}] = elms[${counter}] || []
53
+ `)
54
+ }
55
+
56
+ renderCode.push(`
57
+ ${elm} = this.element({componentId: component.___id, parentId: parent && parent.nodeId || 'root'})
58
+ const elementConfig${counter} = {}
59
+ `)
60
+
61
+ const children = templateObject['children']
62
+ delete templateObject['children']
63
+
64
+ Object.keys(templateObject).forEach((key) => {
65
+ if (key === 'type') return
66
+
67
+ if (isReactiveKey(key)) {
68
+ this.effectsCode.push(
69
+ `${elm}.set('${key.substring(1)}', ${interpolate(templateObject[key], options.component)})`
70
+ )
71
+ } else {
72
+ renderCode.push(
73
+ `elementConfig${counter}['${key}'] = ${cast(templateObject[key], key, options.component)}`
74
+ )
75
+ }
76
+ })
77
+
78
+ if (options.holder === true) {
79
+ renderCode.push(`
80
+ if(typeof cmp${counter} !== 'undefined') {
81
+ for(key in cmp${counter}.config.props) {
82
+ delete elementConfig${counter}[cmp${counter}.config.props[key]]
83
+ }
84
+ }
85
+ `)
86
+ }
87
+
88
+ renderCode.push(`${elm}.populate(elementConfig${counter})`)
89
+
90
+ if (children) {
91
+ generateCode.call(this, { children }, `elms[${counter}]`)
92
+ }
93
+ }
94
+
95
+ const generateComponentCode = function (
96
+ templateObject,
97
+ parent = false,
98
+ options = { index: false, component: 'component.', forceEffect: false, holder: false }
99
+ ) {
100
+ const renderCode = options.forceEffect ? this.effectsCode : this.renderCode
101
+
102
+ renderCode.push(`
103
+ const cmp${counter} =
104
+ (context.components && context.components['${templateObject.type}']) ||
105
+ component.___components['${templateObject.type}']
106
+ `)
107
+
108
+ generateElementCode.call(this, templateObject, parent, { ...options, ...{ holder: true } })
109
+
110
+ parent = options.index ? `elms[${counter}][${options.index}]` : `elms[${counter}]`
111
+
112
+ counter++
113
+
114
+ const elm = options.index ? `elms[${counter}][${options.index}]` : `elms[${counter}]`
115
+
116
+ if (options.index) {
117
+ renderCode.push(`
118
+ elms[${counter}] = elms[${counter}] || []
119
+ `)
120
+ }
121
+
122
+ if (parent) {
123
+ renderCode.push(`parent = ${parent}`)
124
+ }
125
+
126
+ renderCode.push(`const props${counter} = {}`)
127
+ Object.keys(templateObject).forEach((key) => {
128
+ if (key === 'type') return
129
+ if (isReactiveKey(key)) {
130
+ this.effectsCode.push(`
131
+ ${elm}.___props['${key.substring(1)}'] = ${interpolate(
132
+ templateObject[key],
133
+ options.component
134
+ )}`)
135
+ renderCode.push(
136
+ `props${counter}['${key.substring(1)}'] = ${interpolate(
137
+ templateObject[key],
138
+ options.component
139
+ )}`
140
+ )
141
+ } else {
142
+ renderCode.push(
143
+ `props${counter}['${key}'] = ${cast(templateObject[key], key, options.component)}`
144
+ )
145
+ }
146
+ })
147
+
148
+ renderCode.push(`
149
+ ${elm} = (context.components && context.components['${templateObject.type}'] || component.___components['${templateObject.type}'] || (() => { console.log('component ${templateObject.type} not found')})).call(null, {props: props${counter}}, ${parent}, component)
150
+ `)
151
+ }
152
+
153
+ const generateForLoopCode = function (templateObject, parent) {
154
+ const forLoop = templateObject[':for']
155
+ delete templateObject[':for']
156
+
157
+ const regex = /(.+)\s+in\s+(.+)/gi
158
+ // const regex = /(:?\(*)(.+)\s+in\s+(.+)/gi
159
+
160
+ const result = regex.exec(forLoop)
161
+
162
+ // can be improved with a smarter regex
163
+ const [item, index = 'index'] = result[1]
164
+ .replace('(', '')
165
+ .replace(')', '')
166
+ .split(/\s*,\s*/)
167
+
168
+ // local context
169
+ const ctx = {
170
+ renderCode: [],
171
+ effectsCode: [],
172
+ context: { props: [], components: this.components },
173
+ }
174
+
175
+ if (parent) {
176
+ ctx.renderCode.push(`parent = ${parent}`)
177
+ }
178
+ ctx.renderCode.push(`
179
+ const collection = ${cast(result[2], ':for')}
180
+ for(let __index = 0; __index < collection.length; __index++) {
181
+ parent = ${parent}
182
+ const scope = {
183
+ ${index}: __index,
184
+ ${item}: collection[__index]
185
+ }
186
+ `)
187
+
188
+ if (templateObject.type !== 'Element') {
189
+ generateComponentCode.call(ctx, templateObject, false, {
190
+ index: `scope.${index}`,
191
+ component: 'scope.',
192
+ forceEffect: false,
193
+ })
194
+ } else {
195
+ generateElementCode.call(ctx, templateObject, parent, {
196
+ index: `scope.${index}`,
197
+ component: 'scope.',
198
+ forceEffect: false,
199
+ })
200
+ }
201
+ ctx.renderCode = ctx.renderCode.concat(ctx.effectsCode)
202
+ ctx.renderCode.push('}')
203
+
204
+ this.effectsCode.push(ctx.renderCode.join('\n'))
205
+ }
206
+
207
+ const generateCode = function (templateObject, parent = false) {
208
+ templateObject.children.forEach((childTemplateObject) => {
209
+ counter++
210
+
211
+ if (Object.keys(childTemplateObject).indexOf(':for') > -1) {
212
+ generateForLoopCode.call(this, childTemplateObject, parent)
213
+ } else {
214
+ if (childTemplateObject.type !== 'Element') {
215
+ generateComponentCode.call(this, childTemplateObject, parent)
216
+ } else {
217
+ generateElementCode.call(this, childTemplateObject, parent)
218
+ }
219
+ }
220
+ })
221
+ }
222
+
223
+ const interpolate = (val, component = 'component.') => {
224
+ const replaceString = /('.*?')+/gi
225
+ const replaceDollar = /\$/gi
226
+ const matches = val.matchAll(replaceString)
227
+ const restore = []
228
+
229
+ let i = 0
230
+
231
+ // replace string and store in temp
232
+ for (const match of matches) {
233
+ restore.push(match[0])
234
+ val = val.replace(match[0], `[@@REPLACEMENT${i}@@]`)
235
+ i++
236
+ }
237
+
238
+ // replace remaining dollar signs
239
+ val = val.replace(replaceDollar, component)
240
+
241
+ // restore string replacemenet
242
+ restore.forEach((el, idx) => {
243
+ val = val.replace(`[@@REPLACEMENT${idx}@@]`, el)
244
+ })
245
+
246
+ return val
247
+ }
248
+
249
+ const cast = (val = '', key = false, component = 'component.') => {
250
+ let castedValue
251
+
252
+ // numeric
253
+ if (key !== 'color' && !isNaN(parseFloat(val))) {
254
+ castedValue = parseFloat(val)
255
+ }
256
+ // boolean true
257
+ else if (val.toLowerCase() === 'true') {
258
+ castedValue = true
259
+ }
260
+ // boolean false
261
+ else if (val.toLowerCase() === 'false') {
262
+ castedValue = false
263
+ }
264
+ // dynamic value
265
+ else if (val.startsWith('$')) {
266
+ castedValue = `${component}${val.replace('$', '')}`
267
+ }
268
+ // static string
269
+ else {
270
+ castedValue = `"${val}"`
271
+ }
272
+
273
+ return castedValue
274
+ }
275
+
276
+ const isReactiveKey = (str) => str.startsWith(':')