@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.
- package/CHANGELOG.md +1035 -1019
- package/LICENSE +202 -202
- package/NOTICE +12 -12
- package/README.md +33 -33
- package/bin/index.js +8 -8
- package/blits.d.ts +6 -6
- package/index.d.ts +1099 -1099
- package/index.js +28 -28
- package/package.json +57 -56
- package/public/assets/fps_sprite.base64.js +1 -1
- package/scripts/postpublish.js +59 -59
- package/scripts/prepublishOnly.js +84 -84
- package/scripts/prepush.js +78 -78
- package/src/announcer/announcer.js +162 -162
- package/src/announcer/speechSynthesis.js +107 -107
- package/src/application.js +100 -100
- package/src/component/base/announcer.js +27 -27
- package/src/component/base/events.js +69 -69
- package/src/component/base/index.js +33 -33
- package/src/component/base/methods.js +244 -244
- package/src/component/base/router.js +41 -41
- package/src/component/base/timeouts_intervals.js +99 -99
- package/src/component/base/utils.js +72 -72
- package/src/component/setup/computed.js +48 -48
- package/src/component/setup/index.js +62 -62
- package/src/component/setup/input.js +30 -30
- package/src/component/setup/methods.js +37 -37
- package/src/component/setup/props.js +62 -62
- package/src/component/setup/routes.js +42 -42
- package/src/component/setup/state.js +59 -59
- package/src/component/setup/watch.js +35 -35
- package/src/component.js +437 -437
- package/src/components/Circle.js +73 -73
- package/src/components/FPScounter.js +1510 -1510
- package/src/components/RouterView.js +81 -81
- package/src/components/Sprite.js +94 -94
- package/src/components/index.js +28 -28
- package/src/constants.js +21 -21
- package/src/engine.js +1 -1
- package/src/engines/L3/element.js +744 -744
- package/src/engines/L3/fontLoader.js +38 -38
- package/src/engines/L3/index.js +7 -7
- package/src/engines/L3/launch.js +123 -123
- package/src/engines/L3/shaderLoader.js +12 -12
- package/src/engines/index.js +1 -1
- package/src/focus.js +90 -90
- package/src/helpers/deepEqualArray.js +50 -50
- package/src/helpers/fetchJson.js +34 -34
- package/src/helpers/sequence.js +26 -26
- package/src/launch.js +123 -123
- package/src/lib/blitsfileconverter/blitsfileconverter.js +52 -52
- package/src/lib/codegenerator/generator.js +788 -788
- package/src/lib/colors/colors.js +92 -92
- package/src/lib/colors/htmlColors.js +168 -168
- package/src/lib/componentId.js +49 -49
- package/src/lib/eventListeners.js +123 -123
- package/src/lib/hooks.js +59 -59
- package/src/lib/lifecycle.js +85 -85
- package/src/lib/log.js +117 -117
- package/src/lib/precompiler/precompiler.js +73 -73
- package/src/lib/reactivity/effect.js +93 -93
- package/src/lib/reactivity/reactive.js +208 -208
- package/src/lib/reactivityguard/computedprops.js +218 -218
- package/src/lib/symbols.js +108 -108
- package/src/lib/templateparser/parser.js +386 -386
- package/src/lib/utils.js +27 -27
- package/src/plugin.js +27 -27
- package/src/plugins/appstate.js +26 -26
- package/src/plugins/index.js +20 -20
- package/src/plugins/language.js +152 -152
- package/src/plugins/log.js +25 -25
- package/src/plugins/theme.js +94 -94
- package/src/router/router.js +516 -514
- package/src/router/transitions/fadeIn.js +27 -27
- package/src/router/transitions/fadeInOut.js +33 -33
- package/src/router/transitions/index.js +21 -21
- package/src/router/transitions/slideIn.js +28 -28
- package/src/router/transitions/slideInOut.js +33 -33
- package/src/settings.js +40 -40
- package/vite/blitsFileConverter.js +67 -67
- package/vite/index.js +28 -28
- package/vite/msdfGenerator.js +269 -269
- package/vite/preCompiler.js +47 -42
- package/vite/reactivityGuard.js +45 -45
- package/vscode/data/template-attributes.json +589 -589
- package/.editorconfig +0 -7
- package/.eslintignore +0 -1
- package/.eslintrc.cjs +0 -65
- package/.github/workflows/comment-lint-results.yml +0 -125
- package/.github/workflows/comment-test-results.yml +0 -109
- package/.github/workflows/deploy-docs.yml +0 -27
- package/.github/workflows/run-blits-tests.yml +0 -142
- package/.github/workflows/run-lint.yml +0 -128
- package/.husky/pre-commit +0 -1
- package/.husky/pre-push +0 -1
- package/.vscode/extensions.json +0 -7
- package/.vscode/settings.json +0 -9
- package/COPYING +0 -1
- package/banner.png +0 -0
- package/docs/.nojekyll +0 -0
- package/docs/README.md +0 -19
- package/docs/_sidebar.md +0 -37
- package/docs/built-in/directives.md +0 -50
- package/docs/built-in/for-loop.md +0 -204
- package/docs/built-in/layout.md +0 -168
- package/docs/components/component_state.md +0 -90
- package/docs/components/computed_properties.md +0 -45
- package/docs/components/lifecycle_events.md +0 -42
- package/docs/components/methods.md +0 -45
- package/docs/components/props.md +0 -56
- package/docs/components/user_input.md +0 -186
- package/docs/components/utility_methods.md +0 -320
- package/docs/components/watchers.md +0 -34
- package/docs/essentials/application_root.md +0 -19
- package/docs/essentials/components.md +0 -68
- package/docs/essentials/displaying_images.md +0 -67
- package/docs/essentials/displaying_text.md +0 -135
- package/docs/essentials/element_attributes.md +0 -195
- package/docs/essentials/settings.md +0 -117
- package/docs/essentials/template_syntax.md +0 -83
- package/docs/getting_started/file_structure.md +0 -58
- package/docs/getting_started/getting_started.md +0 -45
- package/docs/getting_started/intro.md +0 -20
- package/docs/index.html +0 -47
- package/docs/performance/lazy-loading.md +0 -161
- package/docs/plugins/global_app_state.md +0 -98
- package/docs/plugins/language.md +0 -172
- package/docs/plugins/text-to-speech-announcer.md +0 -120
- package/docs/plugins/theme.md +0 -176
- package/docs/router/basics.md +0 -175
- package/docs/router/hooks.md +0 -70
- package/docs/router/transitions.md +0 -146
- package/docs/sidebar.json +0 -171
- package/docs/transitions_animations/transitions.md +0 -150
- package/jsconfig.json +0 -12
- package/src/component/setup/props.test.js +0 -238
- package/src/component.test.js +0 -388
- package/src/components/Circle.orig.js +0 -31
- package/src/components/FPScounter.orig.js +0 -121
- package/src/components/RouterView.orig.js +0 -54
- package/src/components/Sprite.orig.js +0 -58
- package/src/components/index.orig.js +0 -28
- package/src/engines/L3/element.test.js +0 -579
- package/src/helpers/deepEqualArray.test.js +0 -127
- package/src/lib/codegenerator/generator.test.js +0 -3801
- package/src/lib/colors/colors.test.js +0 -290
- package/src/lib/componentId.test.js +0 -84
- package/src/lib/eventListeners.test.js +0 -236
- package/src/lib/reactivity/effect.test.js +0 -35
- package/src/lib/reactivity/reactive.test.js +0 -27
- package/src/lib/templateparser/parser.test.js +0 -1411
- package/src/router/router.test.js +0 -473
|
@@ -1,788 +1,788 @@
|
|
|
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
|
-
let isDev
|
|
20
|
-
|
|
21
|
-
export default function (templateObject = { children: [] }, devMode = false) {
|
|
22
|
-
const ctx = {
|
|
23
|
-
renderCode: [
|
|
24
|
-
'const elms = []',
|
|
25
|
-
'let componentType',
|
|
26
|
-
'const rootComponent = component',
|
|
27
|
-
'let propData',
|
|
28
|
-
'let slotComponent',
|
|
29
|
-
'let inSlot = false',
|
|
30
|
-
'let slotChildCounter = 0',
|
|
31
|
-
],
|
|
32
|
-
effectsCode: [],
|
|
33
|
-
context: { props: [], components: this.components },
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
counter = -1
|
|
37
|
-
isDev = devMode
|
|
38
|
-
if (isDev === true) {
|
|
39
|
-
ctx.renderCode.push(`
|
|
40
|
-
function propInComponent(prop, kind="dynamic") {
|
|
41
|
-
const property = prop.includes('.') ? prop.split('.')[0] : prop
|
|
42
|
-
if (kind === 'reactive' || prop.includes('.') === false) {
|
|
43
|
-
if (property in component === false) {
|
|
44
|
-
Log.warn('Property ' + property + ' was accessed during render but is not defined on instance')
|
|
45
|
-
}
|
|
46
|
-
} else {
|
|
47
|
-
const nestedKeys = prop.split('.')
|
|
48
|
-
let base = component
|
|
49
|
-
for(let i =0; i<nestedKeys.length;i++){
|
|
50
|
-
if (base[nestedKeys[i]] === undefined) {
|
|
51
|
-
Log.warn('Property ' + nestedKeys.slice(0,i+1).join('.') + ' was accessed during render but is not defined on instance')
|
|
52
|
-
}
|
|
53
|
-
base = base[nestedKeys[i]]
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
`)
|
|
58
|
-
}
|
|
59
|
-
generateCode.call(ctx, templateObject)
|
|
60
|
-
ctx.renderCode.push('return elms')
|
|
61
|
-
|
|
62
|
-
return {
|
|
63
|
-
render: new Function(
|
|
64
|
-
'parent',
|
|
65
|
-
'component',
|
|
66
|
-
'context',
|
|
67
|
-
'components',
|
|
68
|
-
'effect',
|
|
69
|
-
'getRaw',
|
|
70
|
-
'Log',
|
|
71
|
-
ctx.renderCode.join('\n')
|
|
72
|
-
),
|
|
73
|
-
effects: ctx.effectsCode.map(
|
|
74
|
-
(code) =>
|
|
75
|
-
new Function('component', 'elms', 'context', 'components', 'rootComponent', 'effect', code)
|
|
76
|
-
),
|
|
77
|
-
context: ctx.context,
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// This is used to get only variable from expression
|
|
82
|
-
const extractVariables = function (value) {
|
|
83
|
-
const regEx = /\$\$?\w+(\.\w+)?/g
|
|
84
|
-
const matches = value.match(regEx)
|
|
85
|
-
if (matches !== null) {
|
|
86
|
-
const shaderRegex = /\$shader/
|
|
87
|
-
return matches.filter((match) => !shaderRegex.test(match))
|
|
88
|
-
} else {
|
|
89
|
-
return false
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const verifyVariables = function (value, renderCode, type = 'dynamic') {
|
|
94
|
-
const variablesToBeVerified = extractVariables(value)
|
|
95
|
-
if (variablesToBeVerified !== false) {
|
|
96
|
-
for (let i = 0; i < variablesToBeVerified.length; i++) {
|
|
97
|
-
let variable = variablesToBeVerified[i]
|
|
98
|
-
if (type === 'reactive' && variable.includes('.')) {
|
|
99
|
-
variable = variable.split('.')[0]
|
|
100
|
-
}
|
|
101
|
-
renderCode.push(`propInComponent('${variable.replace('$', '')}', '${type}')`)
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const generateElementCode = function (
|
|
107
|
-
templateObject,
|
|
108
|
-
parent = false,
|
|
109
|
-
options = { key: false, component: 'component.', forceEffect: false, forloop: false }
|
|
110
|
-
) {
|
|
111
|
-
const renderCode = options.forceEffect ? this.effectsCode : this.renderCode
|
|
112
|
-
if (parent) {
|
|
113
|
-
renderCode.push(`parent = ${parent}`)
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if ('key' in templateObject) options.key = interpolate(templateObject.key, options.component)
|
|
117
|
-
const elm = options.key ? `elms[${counter}][${options.key}]` : `elms[${counter}]`
|
|
118
|
-
|
|
119
|
-
if (options.key) {
|
|
120
|
-
renderCode.push(`
|
|
121
|
-
if(elms[${counter}] === undefined) {
|
|
122
|
-
elms[${counter}] = {}
|
|
123
|
-
}
|
|
124
|
-
`)
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
renderCode.push(`const elementConfig${counter} = {}`)
|
|
128
|
-
|
|
129
|
-
if (options.forloop) {
|
|
130
|
-
renderCode.push(`if(${elm} === undefined) {`)
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
renderCode.push(`
|
|
134
|
-
${elm} = this.element({parent: parent || 'root'}, inSlot === true ? slotComponent : component)
|
|
135
|
-
`)
|
|
136
|
-
|
|
137
|
-
if (options.forloop) {
|
|
138
|
-
renderCode.push('}')
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const children = templateObject['children']
|
|
142
|
-
delete templateObject['children']
|
|
143
|
-
|
|
144
|
-
if (templateObject[Symbol.for('componentType')] === 'Slot') {
|
|
145
|
-
renderCode.push(`elementConfig${counter}[Symbol.for('isSlot')] = true`)
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
Object.keys(templateObject).forEach((key) => {
|
|
149
|
-
if (key === 'slot') {
|
|
150
|
-
renderCode.push(`
|
|
151
|
-
elementConfig${counter}['parent'] = slotComponent[Symbol.for('slots')] !== undefined && Array.isArray(slotComponent[Symbol.for('slots')]) === true && slotComponent[Symbol.for('slots')].filter(slot => slot.ref === '${templateObject.slot}').shift() || parent
|
|
152
|
-
`)
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
if (key === 'key') return
|
|
156
|
-
|
|
157
|
-
const value = templateObject[key]
|
|
158
|
-
|
|
159
|
-
if (isReactiveKey(key)) {
|
|
160
|
-
if (options.holder && key === ':color') return
|
|
161
|
-
if (options.holder) {
|
|
162
|
-
this.effectsCode.push(`
|
|
163
|
-
if(typeof skip${counter} === 'undefined' ||
|
|
164
|
-
skip${counter}.indexOf('${key.substring(1)}') === -1)
|
|
165
|
-
${elm}.set('${key.substring(1)}', ${interpolate(templateObject[key], options.component)})
|
|
166
|
-
`)
|
|
167
|
-
} else {
|
|
168
|
-
this.effectsCode.push(`
|
|
169
|
-
${elm}.set('${key.substring(1)}', ${interpolate(
|
|
170
|
-
templateObject[key],
|
|
171
|
-
options.component
|
|
172
|
-
)})
|
|
173
|
-
`)
|
|
174
|
-
}
|
|
175
|
-
// value.includes('.') === false &&
|
|
176
|
-
if (isDev === true && options.component !== 'scope.' && value.includes('$')) {
|
|
177
|
-
verifyVariables(value, renderCode, 'reactive')
|
|
178
|
-
}
|
|
179
|
-
renderCode.push(
|
|
180
|
-
`elementConfig${counter}['${key.substring(1)}'] = ${interpolate(value, options.component)}`
|
|
181
|
-
)
|
|
182
|
-
} else {
|
|
183
|
-
if (isDev === true && options.component !== 'scope.' && value.includes('$')) {
|
|
184
|
-
verifyVariables(value, renderCode)
|
|
185
|
-
}
|
|
186
|
-
renderCode.push(`elementConfig${counter}['${key}'] = ${cast(value, key, options.component)}`)
|
|
187
|
-
}
|
|
188
|
-
})
|
|
189
|
-
|
|
190
|
-
if (options.holder === true) {
|
|
191
|
-
renderCode.push(`
|
|
192
|
-
const skip${counter} = []
|
|
193
|
-
if(typeof cmp${counter} !== 'undefined') {
|
|
194
|
-
for(let key in cmp${counter}[Symbol.for('config')].props) {
|
|
195
|
-
delete elementConfig${counter}[cmp${counter}[Symbol.for('config')].props[key]]
|
|
196
|
-
skip${counter}.push(cmp${counter}[Symbol.for('config')].props[key])
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
`)
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
if (options.forloop) {
|
|
203
|
-
renderCode.push(`if(${elm}.nodeId === undefined) {`)
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
renderCode.push(`${elm}.populate(elementConfig${counter})`)
|
|
207
|
-
|
|
208
|
-
renderCode.push(`
|
|
209
|
-
if(inSlot === true) {
|
|
210
|
-
slotChildCounter -= 1
|
|
211
|
-
}
|
|
212
|
-
`)
|
|
213
|
-
|
|
214
|
-
if (options.forloop) {
|
|
215
|
-
renderCode.push('}')
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
if (children) {
|
|
219
|
-
generateCode.call(this, { children }, `${elm}`, options)
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const generateComponentCode = function (
|
|
224
|
-
templateObject,
|
|
225
|
-
parent = false,
|
|
226
|
-
options = {
|
|
227
|
-
key: false,
|
|
228
|
-
component: 'component.',
|
|
229
|
-
forceEffect: false,
|
|
230
|
-
holder: false,
|
|
231
|
-
forloop: false,
|
|
232
|
-
}
|
|
233
|
-
) {
|
|
234
|
-
const renderCode = options.forceEffect ? this.effectsCode : this.renderCode
|
|
235
|
-
|
|
236
|
-
renderCode.push(`
|
|
237
|
-
const cmp${counter} =
|
|
238
|
-
(context.components && context.components['${
|
|
239
|
-
templateObject[Symbol.for('componentType')]
|
|
240
|
-
}']) || components['${templateObject[Symbol.for('componentType')]}']
|
|
241
|
-
`)
|
|
242
|
-
|
|
243
|
-
if ('key' in templateObject) {
|
|
244
|
-
options.key = interpolate(templateObject.key, options.component)
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
const children = templateObject.children
|
|
248
|
-
delete templateObject.children
|
|
249
|
-
generateElementCode.call(this, templateObject, parent, { ...options, ...{ holder: true } })
|
|
250
|
-
|
|
251
|
-
parent = options.key ? `elms[${counter}][${options.key}]` : `elms[${counter}]`
|
|
252
|
-
|
|
253
|
-
counter++
|
|
254
|
-
|
|
255
|
-
const elm = options.key ? `elms[${counter}][${options.key}]` : `elms[${counter}]`
|
|
256
|
-
|
|
257
|
-
if (options.key) {
|
|
258
|
-
renderCode.push(`
|
|
259
|
-
if(elms[${counter}] === undefined) {
|
|
260
|
-
elms[${counter}] = {}
|
|
261
|
-
}
|
|
262
|
-
`)
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
if (parent) {
|
|
266
|
-
renderCode.push(`parent = ${parent}`)
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
renderCode.push(`const props${counter} = {}`)
|
|
270
|
-
|
|
271
|
-
if (options.forloop) {
|
|
272
|
-
renderCode.push(`if(${elm} === undefined) {`)
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
Object.keys(templateObject).forEach((key) => {
|
|
276
|
-
if (isReactiveKey(key)) {
|
|
277
|
-
this.effectsCode.push(`
|
|
278
|
-
${elm}[Symbol.for('props')]['${key.substring(1)}'] = ${interpolate(
|
|
279
|
-
templateObject[key],
|
|
280
|
-
options.component
|
|
281
|
-
)}`)
|
|
282
|
-
renderCode.push(`
|
|
283
|
-
propData = ${interpolate(templateObject[key], options.component)}
|
|
284
|
-
if (Array.isArray(propData) === true) {
|
|
285
|
-
propData = getRaw(propData).slice(0)
|
|
286
|
-
}
|
|
287
|
-
props${counter}['${key.substring(1)}'] = propData`)
|
|
288
|
-
} else {
|
|
289
|
-
renderCode.push(
|
|
290
|
-
`props${counter}['${key}'] = ${cast(templateObject[key], key, options.component)}`
|
|
291
|
-
)
|
|
292
|
-
}
|
|
293
|
-
})
|
|
294
|
-
|
|
295
|
-
renderCode.push(`
|
|
296
|
-
componentType = props${counter}['is'] || '${templateObject[Symbol.for('componentType')]}'
|
|
297
|
-
|
|
298
|
-
let component${counter}
|
|
299
|
-
if(typeof componentType === 'string') {
|
|
300
|
-
component${counter} = context.components && context.components[componentType] || components[componentType]
|
|
301
|
-
if(!component${counter}) {
|
|
302
|
-
throw new Error('Component "${templateObject[Symbol.for('componentType')]}" not found')
|
|
303
|
-
}
|
|
304
|
-
} else if(typeof componentType === 'function' && componentType[Symbol.for('isComponent')] === true) {
|
|
305
|
-
component${counter} = componentType
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
${elm} = component${counter}.call(null, {props: props${counter}}, ${parent}, component)
|
|
309
|
-
|
|
310
|
-
if (${elm}[Symbol.for('slots')][0]) {
|
|
311
|
-
parent = ${elm}[Symbol.for('slots')][0]
|
|
312
|
-
slotComponent = ${elm}
|
|
313
|
-
inSlot = true
|
|
314
|
-
} else {
|
|
315
|
-
parent = ${elm}[Symbol.for('children')][0]
|
|
316
|
-
}
|
|
317
|
-
`)
|
|
318
|
-
|
|
319
|
-
if (options.forloop) {
|
|
320
|
-
renderCode.push('}')
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
if (children) {
|
|
324
|
-
if (!options.forloop) {
|
|
325
|
-
renderCode.push(`
|
|
326
|
-
if(inSlot === true) {
|
|
327
|
-
slotChildCounter = ${children.length} + 1
|
|
328
|
-
}
|
|
329
|
-
`)
|
|
330
|
-
}
|
|
331
|
-
counter++
|
|
332
|
-
generateElementCode.call(this, { children }, false, { ...options })
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
if (!options.forloop) {
|
|
336
|
-
renderCode.push(`
|
|
337
|
-
if (inSlot === true && slotChildCounter === 0) {
|
|
338
|
-
inSlot = false
|
|
339
|
-
}
|
|
340
|
-
`)
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
const generateForLoopCode = function (templateObject, parent) {
|
|
345
|
-
const forLoop = templateObject[':for']
|
|
346
|
-
delete templateObject[':for']
|
|
347
|
-
const range = templateObject['range'] || templateObject[':range'] || '{}'
|
|
348
|
-
delete templateObject['range']
|
|
349
|
-
delete templateObject[':range']
|
|
350
|
-
|
|
351
|
-
const key = templateObject['key']
|
|
352
|
-
const forKey = interpolate(key, 'scope.')
|
|
353
|
-
|
|
354
|
-
const shallow = !!!(
|
|
355
|
-
templateObject['$shallow'] && templateObject['$shallow'].toLowerCase() === 'false'
|
|
356
|
-
)
|
|
357
|
-
delete templateObject['$shallow']
|
|
358
|
-
|
|
359
|
-
delete templateObject['key']
|
|
360
|
-
const regex = /(.+)\s+in\s+(.+)/gi
|
|
361
|
-
// const regex = /(:?\(*)(.+)\s+in\s+(.+)/gi
|
|
362
|
-
|
|
363
|
-
const result = regex.exec(forLoop)
|
|
364
|
-
|
|
365
|
-
// can be improved with a smarter regex
|
|
366
|
-
const [item, index] = result[1]
|
|
367
|
-
.replace('(', '')
|
|
368
|
-
.replace(')', '')
|
|
369
|
-
.split(/\s*,\s*/)
|
|
370
|
-
|
|
371
|
-
const scopeRegex = new RegExp(`(scope\\.(?!${item}\\.|${index}|key)(\\w+))`, 'gi')
|
|
372
|
-
|
|
373
|
-
// local context
|
|
374
|
-
const ctx = {
|
|
375
|
-
renderCode: [],
|
|
376
|
-
effectsCode: [],
|
|
377
|
-
context: { props: [], components: this.components },
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
if (parent) {
|
|
381
|
-
ctx.renderCode.push(`parent = ${parent}`)
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
// If the index variable is not defined, the key attribute would not reference it.
|
|
385
|
-
if (index !== undefined) {
|
|
386
|
-
const indexRegex = new RegExp(`\\$${index}(?!['\\w])`)
|
|
387
|
-
const indexResult = indexRegex.exec(key)
|
|
388
|
-
if (Array.isArray(indexResult)) {
|
|
389
|
-
ctx.renderCode.push(
|
|
390
|
-
`console.warn(" Using '${index}' in the key, like key=${key}, is not recommended")`
|
|
391
|
-
)
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
const forStartCounter = counter
|
|
396
|
-
|
|
397
|
-
ctx.renderCode.push(`
|
|
398
|
-
const created${forStartCounter} = []
|
|
399
|
-
|
|
400
|
-
let from${forStartCounter}
|
|
401
|
-
let to${forStartCounter}
|
|
402
|
-
|
|
403
|
-
const forloop${forStartCounter} = (collection = [], elms, created) => {
|
|
404
|
-
const rawCollection = getRaw(collection)
|
|
405
|
-
const keys = new Set()
|
|
406
|
-
let l = rawCollection.length
|
|
407
|
-
|
|
408
|
-
const range = ${interpolate(range)} || {}
|
|
409
|
-
from${forStartCounter} = range['from'] || 0
|
|
410
|
-
to${forStartCounter} = 'to' in range ? range['to'] : rawCollection.length
|
|
411
|
-
|
|
412
|
-
while(l--) {
|
|
413
|
-
const ${item} = rawCollection[l]
|
|
414
|
-
`)
|
|
415
|
-
// push reference of index variable
|
|
416
|
-
if (index !== undefined) {
|
|
417
|
-
ctx.renderCode.push(`
|
|
418
|
-
const ${index} = l
|
|
419
|
-
`)
|
|
420
|
-
}
|
|
421
|
-
ctx.renderCode.push(`
|
|
422
|
-
if(l < to${forStartCounter} && l >= from${forStartCounter}) {
|
|
423
|
-
keys.add('' + ${interpolate(key, '') || 'l'})
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
`)
|
|
427
|
-
|
|
428
|
-
// keep track of the index in the render code so we can inject
|
|
429
|
-
// the code that takes care of destroying elements (generated later on)
|
|
430
|
-
// in the right spot
|
|
431
|
-
const indexToInjectDestroyCode = ctx.renderCode.length
|
|
432
|
-
|
|
433
|
-
ctx.renderCode.push(`
|
|
434
|
-
created.length = 0
|
|
435
|
-
const length = rawCollection.length
|
|
436
|
-
const effects = []
|
|
437
|
-
for(let __index = 0; __index < length; __index++) {
|
|
438
|
-
if(__index < from${forStartCounter} || __index >= to${forStartCounter}) continue
|
|
439
|
-
const scope = Object.create(component)
|
|
440
|
-
parent = ${parent}
|
|
441
|
-
scope['${item}'] = rawCollection[__index]
|
|
442
|
-
`)
|
|
443
|
-
// If the index variable is declared, include it in the scope object
|
|
444
|
-
if (index !== '') {
|
|
445
|
-
ctx.renderCode.push(`
|
|
446
|
-
scope['${index}'] = __index
|
|
447
|
-
`)
|
|
448
|
-
}
|
|
449
|
-
ctx.renderCode.push(`
|
|
450
|
-
scope['key'] = '' + ${forKey || '__index'}
|
|
451
|
-
`)
|
|
452
|
-
if ('ref' in templateObject && templateObject.ref.indexOf('$') === -1) {
|
|
453
|
-
// automatically map the ref for each item in the loop based on the given ref key
|
|
454
|
-
ctx.renderCode.push(`
|
|
455
|
-
scope['__ref'] = '${templateObject.ref}' + __index
|
|
456
|
-
`)
|
|
457
|
-
templateObject.ref = '$__ref'
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
ctx.renderCode.push(`
|
|
461
|
-
created.push(scope.key)
|
|
462
|
-
`)
|
|
463
|
-
|
|
464
|
-
if (
|
|
465
|
-
templateObject[Symbol.for('componentType')] === 'Element' ||
|
|
466
|
-
templateObject[Symbol.for('componentType')] === 'Slot' ||
|
|
467
|
-
templateObject[Symbol.for('componentType')] === 'Text'
|
|
468
|
-
) {
|
|
469
|
-
if (templateObject[Symbol.for('componentType')] === 'Text') {
|
|
470
|
-
templateObject.__textnode = 'true'
|
|
471
|
-
}
|
|
472
|
-
generateElementCode.call(ctx, templateObject, parent, {
|
|
473
|
-
key: 'scope.key',
|
|
474
|
-
component: 'scope.',
|
|
475
|
-
forceEffect: false,
|
|
476
|
-
forloop: true,
|
|
477
|
-
})
|
|
478
|
-
} else {
|
|
479
|
-
generateComponentCode.call(ctx, templateObject, false, {
|
|
480
|
-
key: 'scope.key',
|
|
481
|
-
component: 'scope.',
|
|
482
|
-
forceEffect: false,
|
|
483
|
-
forloop: true,
|
|
484
|
-
})
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
// separate effects that only rely on variables in the itteration
|
|
488
|
-
const innerScopeEffects = ctx.effectsCode.filter(
|
|
489
|
-
(effect) => [...effect.matchAll(scopeRegex)].length === 0
|
|
490
|
-
)
|
|
491
|
-
|
|
492
|
-
// separate effects that (also) rely on variables in the outer scope
|
|
493
|
-
const outerScopeEffects = ctx.effectsCode.filter(
|
|
494
|
-
(effect) => [...effect.matchAll(scopeRegex)].length !== 0
|
|
495
|
-
)
|
|
496
|
-
|
|
497
|
-
if (shallow === false) {
|
|
498
|
-
ctx.renderCode.push(`
|
|
499
|
-
scope['${item}'] = null
|
|
500
|
-
scope['${item}'] = collection[__index]
|
|
501
|
-
`)
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
// inner scope variables are part of the main forloop
|
|
505
|
-
innerScopeEffects.forEach((effect, index) => {
|
|
506
|
-
const key = effect.indexOf(`scope.${index}`) > -1 ? `'${interpolate(result[2], '')}'` : null
|
|
507
|
-
if (effect.indexOf("Symbol.for('props')") === -1) {
|
|
508
|
-
ctx.renderCode.push(`
|
|
509
|
-
let eff${index} = () => {
|
|
510
|
-
${effect}
|
|
511
|
-
}
|
|
512
|
-
effect(eff${index}, ${key})
|
|
513
|
-
effects.push(eff${index})
|
|
514
|
-
component[Symbol.for('effects')].push(eff${index})
|
|
515
|
-
`)
|
|
516
|
-
} else {
|
|
517
|
-
// props shouldn't be wrapped in an effect, but simply passed on
|
|
518
|
-
ctx.renderCode.push(`
|
|
519
|
-
${effect}
|
|
520
|
-
`)
|
|
521
|
-
}
|
|
522
|
-
})
|
|
523
|
-
ctx.renderCode.push(`
|
|
524
|
-
}
|
|
525
|
-
return effects
|
|
526
|
-
}`)
|
|
527
|
-
|
|
528
|
-
// generate code that destroys items
|
|
529
|
-
const destroyCode = []
|
|
530
|
-
destroyCode.push(`
|
|
531
|
-
let i = created.length
|
|
532
|
-
|
|
533
|
-
while (i--) {
|
|
534
|
-
if (keys.has(created[i]) === false) {
|
|
535
|
-
const key = created[i]
|
|
536
|
-
`)
|
|
537
|
-
const forEndCounter = counter
|
|
538
|
-
|
|
539
|
-
for (let i = forStartCounter; i <= forEndCounter; i++) {
|
|
540
|
-
destroyCode.push(`
|
|
541
|
-
elms[${i}][key] && elms[${i}][key].destroy()
|
|
542
|
-
delete elms[${i}][key]
|
|
543
|
-
`)
|
|
544
|
-
}
|
|
545
|
-
destroyCode.push(`
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
`)
|
|
549
|
-
|
|
550
|
-
// inject the destroy code in the correct spot
|
|
551
|
-
ctx.renderCode.splice(indexToInjectDestroyCode, 0, ...destroyCode)
|
|
552
|
-
|
|
553
|
-
let effectKey = `${interpolate(result[2], '')}`
|
|
554
|
-
|
|
555
|
-
// Get the last property from nested path
|
|
556
|
-
if (effectKey && effectKey.includes('.')) {
|
|
557
|
-
effectKey = effectKey.match(/[^.]+$/)[0]
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
// get the reference to range from and to
|
|
561
|
-
const effectKeysRegex = /\$([^,} ]+)/g
|
|
562
|
-
const effectKeys = [...range.matchAll(effectKeysRegex)].map((match) => `'${match[1]}'`)
|
|
563
|
-
|
|
564
|
-
ctx.renderCode.push(`
|
|
565
|
-
let forEffects${forStartCounter}
|
|
566
|
-
const eff${forStartCounter} = () => {
|
|
567
|
-
component[Symbol.for('removeGlobalEffects')](forEffects${forStartCounter})
|
|
568
|
-
forEffects${forStartCounter} = null
|
|
569
|
-
forEffects${forStartCounter} = forloop${forStartCounter}(${cast(
|
|
570
|
-
result[2],
|
|
571
|
-
':for'
|
|
572
|
-
)}, elms, created${forStartCounter})
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
component[Symbol.for('effects')].push(eff${forStartCounter})
|
|
576
|
-
|
|
577
|
-
effect(eff${forStartCounter}, ['${effectKey}', ${effectKeys.join(',')}] )
|
|
578
|
-
`)
|
|
579
|
-
|
|
580
|
-
outerScopeEffects.forEach((effect, outerScopeEffectsIndex) => {
|
|
581
|
-
const matches = [...effect.matchAll(scopeRegex)]
|
|
582
|
-
|
|
583
|
-
let l = matches.length
|
|
584
|
-
const refs = []
|
|
585
|
-
while (l--) {
|
|
586
|
-
const match = matches[l]
|
|
587
|
-
const ref = `component.${match[2]}`
|
|
588
|
-
refs.indexOf(ref) === -1 && refs.push(ref)
|
|
589
|
-
// don't update the effect to point to component, if we're referring to a scope item
|
|
590
|
-
if (match[2] !== item) {
|
|
591
|
-
effect =
|
|
592
|
-
effect.substring(0, match.index) + ref + effect.substring(match.index + match[1].length)
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
ctx.renderCode.push(`
|
|
597
|
-
const eff${forStartCounter}_${outerScopeEffectsIndex} = () => {
|
|
598
|
-
void ${refs.join(', ')}
|
|
599
|
-
for(let __index = 0; __index < ${interpolate(result[2])}.length; __index++) {
|
|
600
|
-
if(__index < from${forStartCounter} || __index >= to${forStartCounter}) continue
|
|
601
|
-
const scope = {}
|
|
602
|
-
scope['${index}'] = __index
|
|
603
|
-
scope['${item}'] = ${interpolate(result[2])}[__index]
|
|
604
|
-
scope['key'] = ${forKey || '__index'}
|
|
605
|
-
`)
|
|
606
|
-
|
|
607
|
-
ctx.renderCode.push(`
|
|
608
|
-
${effect}
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
component[Symbol.for('effects')].push(eff${forStartCounter}_${outerScopeEffectsIndex})
|
|
612
|
-
effect(eff${forStartCounter}_${outerScopeEffectsIndex})
|
|
613
|
-
`)
|
|
614
|
-
})
|
|
615
|
-
|
|
616
|
-
this.renderCode.push(ctx.renderCode.join('\n'))
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
const generateCode = function (templateObject, parent = false, options = {}) {
|
|
620
|
-
templateObject.children &&
|
|
621
|
-
templateObject.children.forEach((childTemplateObject) => {
|
|
622
|
-
counter++
|
|
623
|
-
|
|
624
|
-
if (Object.keys(childTemplateObject).indexOf(':for') > -1) {
|
|
625
|
-
generateForLoopCode.call(this, childTemplateObject, parent)
|
|
626
|
-
} else {
|
|
627
|
-
if (
|
|
628
|
-
childTemplateObject[Symbol.for('componentType')] === 'Element' ||
|
|
629
|
-
childTemplateObject[Symbol.for('componentType')] === 'Slot' ||
|
|
630
|
-
childTemplateObject[Symbol.for('componentType')] === 'Text' ||
|
|
631
|
-
childTemplateObject[Symbol.for('componentType')] === 'Layout'
|
|
632
|
-
) {
|
|
633
|
-
if (childTemplateObject[Symbol.for('componentType')] === 'Text') {
|
|
634
|
-
childTemplateObject.__textnode = 'true'
|
|
635
|
-
}
|
|
636
|
-
if (childTemplateObject[Symbol.for('componentType')] === 'Layout') {
|
|
637
|
-
childTemplateObject.__layout = 'true'
|
|
638
|
-
}
|
|
639
|
-
generateElementCode.call(this, childTemplateObject, parent, options)
|
|
640
|
-
} else {
|
|
641
|
-
generateComponentCode.call(this, childTemplateObject, parent, options)
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
})
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
const interpolate = (val, component = 'component.') => {
|
|
648
|
-
if (val === undefined) return val
|
|
649
|
-
const replaceString = /('.*?')+/gi
|
|
650
|
-
const replaceDollar = /\$(\$(?=\$)|\$?)/g
|
|
651
|
-
const matches = val.matchAll(replaceString)
|
|
652
|
-
const restore = []
|
|
653
|
-
|
|
654
|
-
let i = 0
|
|
655
|
-
|
|
656
|
-
// replace string and store in temp
|
|
657
|
-
for (const match of matches) {
|
|
658
|
-
restore.push(match[0])
|
|
659
|
-
val = val.replace(match[0], `[@@REPLACEMENT${i}@@]`)
|
|
660
|
-
i++
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
// replace dollar signs using the regex and function
|
|
664
|
-
val = val.replace(replaceDollar, (match, group1) => {
|
|
665
|
-
// single dollar sign
|
|
666
|
-
if (group1 === '') {
|
|
667
|
-
return component
|
|
668
|
-
}
|
|
669
|
-
// consecutive dollar sign, replace only the first $
|
|
670
|
-
else if (group1 === '$') {
|
|
671
|
-
return component + '$'
|
|
672
|
-
}
|
|
673
|
-
})
|
|
674
|
-
|
|
675
|
-
// restore string replacement
|
|
676
|
-
restore.forEach((el, idx) => {
|
|
677
|
-
val = val.replace(`[@@REPLACEMENT${idx}@@]`, el)
|
|
678
|
-
})
|
|
679
|
-
|
|
680
|
-
return val
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
const cast = (val = '', key = false, component = 'component.') => {
|
|
684
|
-
const dynamicArgumentRegex = /\$\w+/gi
|
|
685
|
-
let castedValue
|
|
686
|
-
|
|
687
|
-
// inline content
|
|
688
|
-
if (key === 'content') {
|
|
689
|
-
if (val.startsWith('$')) {
|
|
690
|
-
castedValue = `${component}${val.replace('$', '')}`
|
|
691
|
-
} else {
|
|
692
|
-
// unescaped single quotes must be escaped while preserving escaped backslashes
|
|
693
|
-
const escapedVal = val
|
|
694
|
-
.replace(/\\\\/g, '__DOUBLE_BACKSLASH__')
|
|
695
|
-
.replace(/(^|[^\\])'/g, "$1\\'")
|
|
696
|
-
.replace(/__DOUBLE_BACKSLASH__/g, '\\\\')
|
|
697
|
-
castedValue = `'${parseInlineContent(escapedVal, component)}'`
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
// numeric
|
|
701
|
-
else if (key !== 'color' && /[^0-9%\s.eE]/.test(val) === false && !isNaN(parseFloat(val))) {
|
|
702
|
-
castedValue = parseFloat(val)
|
|
703
|
-
if (val.endsWith('%')) {
|
|
704
|
-
const map = {
|
|
705
|
-
w: 'width',
|
|
706
|
-
width: 'width',
|
|
707
|
-
x: 'width',
|
|
708
|
-
h: 'height',
|
|
709
|
-
height: 'height',
|
|
710
|
-
y: 'height',
|
|
711
|
-
}
|
|
712
|
-
const base = map[key]
|
|
713
|
-
if (base) {
|
|
714
|
-
castedValue = `parent.node.${base} * (${castedValue} / 100)`
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
// boolean true
|
|
719
|
-
else if (val.toLowerCase() === 'true') {
|
|
720
|
-
castedValue = true
|
|
721
|
-
}
|
|
722
|
-
// boolean false
|
|
723
|
-
else if (val.toLowerCase() === 'false') {
|
|
724
|
-
castedValue = false
|
|
725
|
-
}
|
|
726
|
-
// @-listener
|
|
727
|
-
else if (key.startsWith('@') && val) {
|
|
728
|
-
const c = component.slice(0, -1)
|
|
729
|
-
castedValue = `${c}['${val.replace('$', '')}'] && ${c}['${val.replace('$', '')}'].bind(${c})`
|
|
730
|
-
}
|
|
731
|
-
// dynamic value
|
|
732
|
-
else if (val.startsWith('$')) {
|
|
733
|
-
castedValue = `${component}${val.replace('$', '')}`
|
|
734
|
-
}
|
|
735
|
-
// dynamic value in object
|
|
736
|
-
else if (dynamicArgumentRegex.exec(val)) {
|
|
737
|
-
const rex = /\w+\s*:\s*(?:[^\s,}]+|".*?"|'.*?')/g
|
|
738
|
-
const results = val.match(rex)
|
|
739
|
-
castedValue = {}
|
|
740
|
-
if (results) {
|
|
741
|
-
for (let i = 0; i < results.length; i++) {
|
|
742
|
-
const members = results[i].split(/\s*:\s*/)
|
|
743
|
-
if (members) {
|
|
744
|
-
populateFields(members[0], members[1], component, castedValue)
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
return interpolateObject(castedValue)
|
|
749
|
-
}
|
|
750
|
-
// static string
|
|
751
|
-
else {
|
|
752
|
-
castedValue = `"${val}"`
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
return castedValue
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
const isReactiveKey = (str) => str.startsWith(':')
|
|
759
|
-
|
|
760
|
-
const parseInlineContent = (val, component) => {
|
|
761
|
-
const dynamicParts = /\{\{\s*(\$\S+)\s*\}\}/g
|
|
762
|
-
const matches = [...val.matchAll(dynamicParts)]
|
|
763
|
-
|
|
764
|
-
if (matches.length) {
|
|
765
|
-
for (let [match, arg] of matches) {
|
|
766
|
-
val = val.replace(match, `${arg.replace('$', `'+${component}`)}+'`)
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
return val
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
// Populate value into target's prop
|
|
773
|
-
const populateFields = (prop, val, component, target) => {
|
|
774
|
-
if (val.startsWith('$')) {
|
|
775
|
-
target[prop] = `${component}${val.replace('$', '')}`
|
|
776
|
-
} else {
|
|
777
|
-
target[prop] = val
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
const interpolateObject = (input) => {
|
|
782
|
-
const interpolatedResults = []
|
|
783
|
-
Object.keys(input).forEach((key) => {
|
|
784
|
-
interpolatedResults.push(`${key}: ${input[key]}`)
|
|
785
|
-
})
|
|
786
|
-
|
|
787
|
-
return ` { ${interpolatedResults.join(', ')} }`
|
|
788
|
-
}
|
|
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
|
+
let isDev
|
|
20
|
+
|
|
21
|
+
export default function (templateObject = { children: [] }, devMode = false) {
|
|
22
|
+
const ctx = {
|
|
23
|
+
renderCode: [
|
|
24
|
+
'const elms = []',
|
|
25
|
+
'let componentType',
|
|
26
|
+
'const rootComponent = component',
|
|
27
|
+
'let propData',
|
|
28
|
+
'let slotComponent',
|
|
29
|
+
'let inSlot = false',
|
|
30
|
+
'let slotChildCounter = 0',
|
|
31
|
+
],
|
|
32
|
+
effectsCode: [],
|
|
33
|
+
context: { props: [], components: this.components },
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
counter = -1
|
|
37
|
+
isDev = devMode
|
|
38
|
+
if (isDev === true) {
|
|
39
|
+
ctx.renderCode.push(`
|
|
40
|
+
function propInComponent(prop, kind="dynamic") {
|
|
41
|
+
const property = prop.includes('.') ? prop.split('.')[0] : prop
|
|
42
|
+
if (kind === 'reactive' || prop.includes('.') === false) {
|
|
43
|
+
if (property in component === false) {
|
|
44
|
+
Log.warn('Property ' + property + ' was accessed during render but is not defined on instance')
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
const nestedKeys = prop.split('.')
|
|
48
|
+
let base = component
|
|
49
|
+
for(let i =0; i<nestedKeys.length;i++){
|
|
50
|
+
if (base[nestedKeys[i]] === undefined) {
|
|
51
|
+
Log.warn('Property ' + nestedKeys.slice(0,i+1).join('.') + ' was accessed during render but is not defined on instance')
|
|
52
|
+
}
|
|
53
|
+
base = base[nestedKeys[i]]
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
`)
|
|
58
|
+
}
|
|
59
|
+
generateCode.call(ctx, templateObject)
|
|
60
|
+
ctx.renderCode.push('return elms')
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
render: new Function(
|
|
64
|
+
'parent',
|
|
65
|
+
'component',
|
|
66
|
+
'context',
|
|
67
|
+
'components',
|
|
68
|
+
'effect',
|
|
69
|
+
'getRaw',
|
|
70
|
+
'Log',
|
|
71
|
+
ctx.renderCode.join('\n')
|
|
72
|
+
),
|
|
73
|
+
effects: ctx.effectsCode.map(
|
|
74
|
+
(code) =>
|
|
75
|
+
new Function('component', 'elms', 'context', 'components', 'rootComponent', 'effect', code)
|
|
76
|
+
),
|
|
77
|
+
context: ctx.context,
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// This is used to get only variable from expression
|
|
82
|
+
const extractVariables = function (value) {
|
|
83
|
+
const regEx = /\$\$?\w+(\.\w+)?/g
|
|
84
|
+
const matches = value.match(regEx)
|
|
85
|
+
if (matches !== null) {
|
|
86
|
+
const shaderRegex = /\$shader/
|
|
87
|
+
return matches.filter((match) => !shaderRegex.test(match))
|
|
88
|
+
} else {
|
|
89
|
+
return false
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const verifyVariables = function (value, renderCode, type = 'dynamic') {
|
|
94
|
+
const variablesToBeVerified = extractVariables(value)
|
|
95
|
+
if (variablesToBeVerified !== false) {
|
|
96
|
+
for (let i = 0; i < variablesToBeVerified.length; i++) {
|
|
97
|
+
let variable = variablesToBeVerified[i]
|
|
98
|
+
if (type === 'reactive' && variable.includes('.')) {
|
|
99
|
+
variable = variable.split('.')[0]
|
|
100
|
+
}
|
|
101
|
+
renderCode.push(`propInComponent('${variable.replace('$', '')}', '${type}')`)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const generateElementCode = function (
|
|
107
|
+
templateObject,
|
|
108
|
+
parent = false,
|
|
109
|
+
options = { key: false, component: 'component.', forceEffect: false, forloop: false }
|
|
110
|
+
) {
|
|
111
|
+
const renderCode = options.forceEffect ? this.effectsCode : this.renderCode
|
|
112
|
+
if (parent) {
|
|
113
|
+
renderCode.push(`parent = ${parent}`)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if ('key' in templateObject) options.key = interpolate(templateObject.key, options.component)
|
|
117
|
+
const elm = options.key ? `elms[${counter}][${options.key}]` : `elms[${counter}]`
|
|
118
|
+
|
|
119
|
+
if (options.key) {
|
|
120
|
+
renderCode.push(`
|
|
121
|
+
if(elms[${counter}] === undefined) {
|
|
122
|
+
elms[${counter}] = {}
|
|
123
|
+
}
|
|
124
|
+
`)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
renderCode.push(`const elementConfig${counter} = {}`)
|
|
128
|
+
|
|
129
|
+
if (options.forloop) {
|
|
130
|
+
renderCode.push(`if(${elm} === undefined) {`)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
renderCode.push(`
|
|
134
|
+
${elm} = this.element({parent: parent || 'root'}, inSlot === true ? slotComponent : component)
|
|
135
|
+
`)
|
|
136
|
+
|
|
137
|
+
if (options.forloop) {
|
|
138
|
+
renderCode.push('}')
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const children = templateObject['children']
|
|
142
|
+
delete templateObject['children']
|
|
143
|
+
|
|
144
|
+
if (templateObject[Symbol.for('componentType')] === 'Slot') {
|
|
145
|
+
renderCode.push(`elementConfig${counter}[Symbol.for('isSlot')] = true`)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
Object.keys(templateObject).forEach((key) => {
|
|
149
|
+
if (key === 'slot') {
|
|
150
|
+
renderCode.push(`
|
|
151
|
+
elementConfig${counter}['parent'] = slotComponent[Symbol.for('slots')] !== undefined && Array.isArray(slotComponent[Symbol.for('slots')]) === true && slotComponent[Symbol.for('slots')].filter(slot => slot.ref === '${templateObject.slot}').shift() || parent
|
|
152
|
+
`)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (key === 'key') return
|
|
156
|
+
|
|
157
|
+
const value = templateObject[key]
|
|
158
|
+
|
|
159
|
+
if (isReactiveKey(key)) {
|
|
160
|
+
if (options.holder && key === ':color') return
|
|
161
|
+
if (options.holder) {
|
|
162
|
+
this.effectsCode.push(`
|
|
163
|
+
if(typeof skip${counter} === 'undefined' ||
|
|
164
|
+
skip${counter}.indexOf('${key.substring(1)}') === -1)
|
|
165
|
+
${elm}.set('${key.substring(1)}', ${interpolate(templateObject[key], options.component)})
|
|
166
|
+
`)
|
|
167
|
+
} else {
|
|
168
|
+
this.effectsCode.push(`
|
|
169
|
+
${elm}.set('${key.substring(1)}', ${interpolate(
|
|
170
|
+
templateObject[key],
|
|
171
|
+
options.component
|
|
172
|
+
)})
|
|
173
|
+
`)
|
|
174
|
+
}
|
|
175
|
+
// value.includes('.') === false &&
|
|
176
|
+
if (isDev === true && options.component !== 'scope.' && value.includes('$')) {
|
|
177
|
+
verifyVariables(value, renderCode, 'reactive')
|
|
178
|
+
}
|
|
179
|
+
renderCode.push(
|
|
180
|
+
`elementConfig${counter}['${key.substring(1)}'] = ${interpolate(value, options.component)}`
|
|
181
|
+
)
|
|
182
|
+
} else {
|
|
183
|
+
if (isDev === true && options.component !== 'scope.' && value.includes('$')) {
|
|
184
|
+
verifyVariables(value, renderCode)
|
|
185
|
+
}
|
|
186
|
+
renderCode.push(`elementConfig${counter}['${key}'] = ${cast(value, key, options.component)}`)
|
|
187
|
+
}
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
if (options.holder === true) {
|
|
191
|
+
renderCode.push(`
|
|
192
|
+
const skip${counter} = []
|
|
193
|
+
if(typeof cmp${counter} !== 'undefined') {
|
|
194
|
+
for(let key in cmp${counter}[Symbol.for('config')].props) {
|
|
195
|
+
delete elementConfig${counter}[cmp${counter}[Symbol.for('config')].props[key]]
|
|
196
|
+
skip${counter}.push(cmp${counter}[Symbol.for('config')].props[key])
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
`)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (options.forloop) {
|
|
203
|
+
renderCode.push(`if(${elm}.nodeId === undefined) {`)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
renderCode.push(`${elm}.populate(elementConfig${counter})`)
|
|
207
|
+
|
|
208
|
+
renderCode.push(`
|
|
209
|
+
if(inSlot === true) {
|
|
210
|
+
slotChildCounter -= 1
|
|
211
|
+
}
|
|
212
|
+
`)
|
|
213
|
+
|
|
214
|
+
if (options.forloop) {
|
|
215
|
+
renderCode.push('}')
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (children) {
|
|
219
|
+
generateCode.call(this, { children }, `${elm}`, options)
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const generateComponentCode = function (
|
|
224
|
+
templateObject,
|
|
225
|
+
parent = false,
|
|
226
|
+
options = {
|
|
227
|
+
key: false,
|
|
228
|
+
component: 'component.',
|
|
229
|
+
forceEffect: false,
|
|
230
|
+
holder: false,
|
|
231
|
+
forloop: false,
|
|
232
|
+
}
|
|
233
|
+
) {
|
|
234
|
+
const renderCode = options.forceEffect ? this.effectsCode : this.renderCode
|
|
235
|
+
|
|
236
|
+
renderCode.push(`
|
|
237
|
+
const cmp${counter} =
|
|
238
|
+
(context.components && context.components['${
|
|
239
|
+
templateObject[Symbol.for('componentType')]
|
|
240
|
+
}']) || components['${templateObject[Symbol.for('componentType')]}']
|
|
241
|
+
`)
|
|
242
|
+
|
|
243
|
+
if ('key' in templateObject) {
|
|
244
|
+
options.key = interpolate(templateObject.key, options.component)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const children = templateObject.children
|
|
248
|
+
delete templateObject.children
|
|
249
|
+
generateElementCode.call(this, templateObject, parent, { ...options, ...{ holder: true } })
|
|
250
|
+
|
|
251
|
+
parent = options.key ? `elms[${counter}][${options.key}]` : `elms[${counter}]`
|
|
252
|
+
|
|
253
|
+
counter++
|
|
254
|
+
|
|
255
|
+
const elm = options.key ? `elms[${counter}][${options.key}]` : `elms[${counter}]`
|
|
256
|
+
|
|
257
|
+
if (options.key) {
|
|
258
|
+
renderCode.push(`
|
|
259
|
+
if(elms[${counter}] === undefined) {
|
|
260
|
+
elms[${counter}] = {}
|
|
261
|
+
}
|
|
262
|
+
`)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (parent) {
|
|
266
|
+
renderCode.push(`parent = ${parent}`)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
renderCode.push(`const props${counter} = {}`)
|
|
270
|
+
|
|
271
|
+
if (options.forloop) {
|
|
272
|
+
renderCode.push(`if(${elm} === undefined) {`)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
Object.keys(templateObject).forEach((key) => {
|
|
276
|
+
if (isReactiveKey(key)) {
|
|
277
|
+
this.effectsCode.push(`
|
|
278
|
+
${elm}[Symbol.for('props')]['${key.substring(1)}'] = ${interpolate(
|
|
279
|
+
templateObject[key],
|
|
280
|
+
options.component
|
|
281
|
+
)}`)
|
|
282
|
+
renderCode.push(`
|
|
283
|
+
propData = ${interpolate(templateObject[key], options.component)}
|
|
284
|
+
if (Array.isArray(propData) === true) {
|
|
285
|
+
propData = getRaw(propData).slice(0)
|
|
286
|
+
}
|
|
287
|
+
props${counter}['${key.substring(1)}'] = propData`)
|
|
288
|
+
} else {
|
|
289
|
+
renderCode.push(
|
|
290
|
+
`props${counter}['${key}'] = ${cast(templateObject[key], key, options.component)}`
|
|
291
|
+
)
|
|
292
|
+
}
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
renderCode.push(`
|
|
296
|
+
componentType = props${counter}['is'] || '${templateObject[Symbol.for('componentType')]}'
|
|
297
|
+
|
|
298
|
+
let component${counter}
|
|
299
|
+
if(typeof componentType === 'string') {
|
|
300
|
+
component${counter} = context.components && context.components[componentType] || components[componentType]
|
|
301
|
+
if(!component${counter}) {
|
|
302
|
+
throw new Error('Component "${templateObject[Symbol.for('componentType')]}" not found')
|
|
303
|
+
}
|
|
304
|
+
} else if(typeof componentType === 'function' && componentType[Symbol.for('isComponent')] === true) {
|
|
305
|
+
component${counter} = componentType
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
${elm} = component${counter}.call(null, {props: props${counter}}, ${parent}, component)
|
|
309
|
+
|
|
310
|
+
if (${elm}[Symbol.for('slots')][0]) {
|
|
311
|
+
parent = ${elm}[Symbol.for('slots')][0]
|
|
312
|
+
slotComponent = ${elm}
|
|
313
|
+
inSlot = true
|
|
314
|
+
} else {
|
|
315
|
+
parent = ${elm}[Symbol.for('children')][0]
|
|
316
|
+
}
|
|
317
|
+
`)
|
|
318
|
+
|
|
319
|
+
if (options.forloop) {
|
|
320
|
+
renderCode.push('}')
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (children) {
|
|
324
|
+
if (!options.forloop) {
|
|
325
|
+
renderCode.push(`
|
|
326
|
+
if(inSlot === true) {
|
|
327
|
+
slotChildCounter = ${children.length} + 1
|
|
328
|
+
}
|
|
329
|
+
`)
|
|
330
|
+
}
|
|
331
|
+
counter++
|
|
332
|
+
generateElementCode.call(this, { children }, false, { ...options })
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (!options.forloop) {
|
|
336
|
+
renderCode.push(`
|
|
337
|
+
if (inSlot === true && slotChildCounter === 0) {
|
|
338
|
+
inSlot = false
|
|
339
|
+
}
|
|
340
|
+
`)
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const generateForLoopCode = function (templateObject, parent) {
|
|
345
|
+
const forLoop = templateObject[':for']
|
|
346
|
+
delete templateObject[':for']
|
|
347
|
+
const range = templateObject['range'] || templateObject[':range'] || '{}'
|
|
348
|
+
delete templateObject['range']
|
|
349
|
+
delete templateObject[':range']
|
|
350
|
+
|
|
351
|
+
const key = templateObject['key']
|
|
352
|
+
const forKey = interpolate(key, 'scope.')
|
|
353
|
+
|
|
354
|
+
const shallow = !!!(
|
|
355
|
+
templateObject['$shallow'] && templateObject['$shallow'].toLowerCase() === 'false'
|
|
356
|
+
)
|
|
357
|
+
delete templateObject['$shallow']
|
|
358
|
+
|
|
359
|
+
delete templateObject['key']
|
|
360
|
+
const regex = /(.+)\s+in\s+(.+)/gi
|
|
361
|
+
// const regex = /(:?\(*)(.+)\s+in\s+(.+)/gi
|
|
362
|
+
|
|
363
|
+
const result = regex.exec(forLoop)
|
|
364
|
+
|
|
365
|
+
// can be improved with a smarter regex
|
|
366
|
+
const [item, index] = result[1]
|
|
367
|
+
.replace('(', '')
|
|
368
|
+
.replace(')', '')
|
|
369
|
+
.split(/\s*,\s*/)
|
|
370
|
+
|
|
371
|
+
const scopeRegex = new RegExp(`(scope\\.(?!${item}\\.|${index}|key)(\\w+))`, 'gi')
|
|
372
|
+
|
|
373
|
+
// local context
|
|
374
|
+
const ctx = {
|
|
375
|
+
renderCode: [],
|
|
376
|
+
effectsCode: [],
|
|
377
|
+
context: { props: [], components: this.components },
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (parent) {
|
|
381
|
+
ctx.renderCode.push(`parent = ${parent}`)
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// If the index variable is not defined, the key attribute would not reference it.
|
|
385
|
+
if (index !== undefined) {
|
|
386
|
+
const indexRegex = new RegExp(`\\$${index}(?!['\\w])`)
|
|
387
|
+
const indexResult = indexRegex.exec(key)
|
|
388
|
+
if (Array.isArray(indexResult)) {
|
|
389
|
+
ctx.renderCode.push(
|
|
390
|
+
`console.warn(" Using '${index}' in the key, like key=${key}, is not recommended")`
|
|
391
|
+
)
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const forStartCounter = counter
|
|
396
|
+
|
|
397
|
+
ctx.renderCode.push(`
|
|
398
|
+
const created${forStartCounter} = []
|
|
399
|
+
|
|
400
|
+
let from${forStartCounter}
|
|
401
|
+
let to${forStartCounter}
|
|
402
|
+
|
|
403
|
+
const forloop${forStartCounter} = (collection = [], elms, created) => {
|
|
404
|
+
const rawCollection = getRaw(collection)
|
|
405
|
+
const keys = new Set()
|
|
406
|
+
let l = rawCollection.length
|
|
407
|
+
|
|
408
|
+
const range = ${interpolate(range)} || {}
|
|
409
|
+
from${forStartCounter} = range['from'] || 0
|
|
410
|
+
to${forStartCounter} = 'to' in range ? range['to'] : rawCollection.length
|
|
411
|
+
|
|
412
|
+
while(l--) {
|
|
413
|
+
const ${item} = rawCollection[l]
|
|
414
|
+
`)
|
|
415
|
+
// push reference of index variable
|
|
416
|
+
if (index !== undefined) {
|
|
417
|
+
ctx.renderCode.push(`
|
|
418
|
+
const ${index} = l
|
|
419
|
+
`)
|
|
420
|
+
}
|
|
421
|
+
ctx.renderCode.push(`
|
|
422
|
+
if(l < to${forStartCounter} && l >= from${forStartCounter}) {
|
|
423
|
+
keys.add('' + ${interpolate(key, '') || 'l'})
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
`)
|
|
427
|
+
|
|
428
|
+
// keep track of the index in the render code so we can inject
|
|
429
|
+
// the code that takes care of destroying elements (generated later on)
|
|
430
|
+
// in the right spot
|
|
431
|
+
const indexToInjectDestroyCode = ctx.renderCode.length
|
|
432
|
+
|
|
433
|
+
ctx.renderCode.push(`
|
|
434
|
+
created.length = 0
|
|
435
|
+
const length = rawCollection.length
|
|
436
|
+
const effects = []
|
|
437
|
+
for(let __index = 0; __index < length; __index++) {
|
|
438
|
+
if(__index < from${forStartCounter} || __index >= to${forStartCounter}) continue
|
|
439
|
+
const scope = Object.create(component)
|
|
440
|
+
parent = ${parent}
|
|
441
|
+
scope['${item}'] = rawCollection[__index]
|
|
442
|
+
`)
|
|
443
|
+
// If the index variable is declared, include it in the scope object
|
|
444
|
+
if (index !== '') {
|
|
445
|
+
ctx.renderCode.push(`
|
|
446
|
+
scope['${index}'] = __index
|
|
447
|
+
`)
|
|
448
|
+
}
|
|
449
|
+
ctx.renderCode.push(`
|
|
450
|
+
scope['key'] = '' + ${forKey || '__index'}
|
|
451
|
+
`)
|
|
452
|
+
if ('ref' in templateObject && templateObject.ref.indexOf('$') === -1) {
|
|
453
|
+
// automatically map the ref for each item in the loop based on the given ref key
|
|
454
|
+
ctx.renderCode.push(`
|
|
455
|
+
scope['__ref'] = '${templateObject.ref}' + __index
|
|
456
|
+
`)
|
|
457
|
+
templateObject.ref = '$__ref'
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
ctx.renderCode.push(`
|
|
461
|
+
created.push(scope.key)
|
|
462
|
+
`)
|
|
463
|
+
|
|
464
|
+
if (
|
|
465
|
+
templateObject[Symbol.for('componentType')] === 'Element' ||
|
|
466
|
+
templateObject[Symbol.for('componentType')] === 'Slot' ||
|
|
467
|
+
templateObject[Symbol.for('componentType')] === 'Text'
|
|
468
|
+
) {
|
|
469
|
+
if (templateObject[Symbol.for('componentType')] === 'Text') {
|
|
470
|
+
templateObject.__textnode = 'true'
|
|
471
|
+
}
|
|
472
|
+
generateElementCode.call(ctx, templateObject, parent, {
|
|
473
|
+
key: 'scope.key',
|
|
474
|
+
component: 'scope.',
|
|
475
|
+
forceEffect: false,
|
|
476
|
+
forloop: true,
|
|
477
|
+
})
|
|
478
|
+
} else {
|
|
479
|
+
generateComponentCode.call(ctx, templateObject, false, {
|
|
480
|
+
key: 'scope.key',
|
|
481
|
+
component: 'scope.',
|
|
482
|
+
forceEffect: false,
|
|
483
|
+
forloop: true,
|
|
484
|
+
})
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// separate effects that only rely on variables in the itteration
|
|
488
|
+
const innerScopeEffects = ctx.effectsCode.filter(
|
|
489
|
+
(effect) => [...effect.matchAll(scopeRegex)].length === 0
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
// separate effects that (also) rely on variables in the outer scope
|
|
493
|
+
const outerScopeEffects = ctx.effectsCode.filter(
|
|
494
|
+
(effect) => [...effect.matchAll(scopeRegex)].length !== 0
|
|
495
|
+
)
|
|
496
|
+
|
|
497
|
+
if (shallow === false) {
|
|
498
|
+
ctx.renderCode.push(`
|
|
499
|
+
scope['${item}'] = null
|
|
500
|
+
scope['${item}'] = collection[__index]
|
|
501
|
+
`)
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// inner scope variables are part of the main forloop
|
|
505
|
+
innerScopeEffects.forEach((effect, index) => {
|
|
506
|
+
const key = effect.indexOf(`scope.${index}`) > -1 ? `'${interpolate(result[2], '')}'` : null
|
|
507
|
+
if (effect.indexOf("Symbol.for('props')") === -1) {
|
|
508
|
+
ctx.renderCode.push(`
|
|
509
|
+
let eff${index} = () => {
|
|
510
|
+
${effect}
|
|
511
|
+
}
|
|
512
|
+
effect(eff${index}, ${key})
|
|
513
|
+
effects.push(eff${index})
|
|
514
|
+
component[Symbol.for('effects')].push(eff${index})
|
|
515
|
+
`)
|
|
516
|
+
} else {
|
|
517
|
+
// props shouldn't be wrapped in an effect, but simply passed on
|
|
518
|
+
ctx.renderCode.push(`
|
|
519
|
+
${effect}
|
|
520
|
+
`)
|
|
521
|
+
}
|
|
522
|
+
})
|
|
523
|
+
ctx.renderCode.push(`
|
|
524
|
+
}
|
|
525
|
+
return effects
|
|
526
|
+
}`)
|
|
527
|
+
|
|
528
|
+
// generate code that destroys items
|
|
529
|
+
const destroyCode = []
|
|
530
|
+
destroyCode.push(`
|
|
531
|
+
let i = created.length
|
|
532
|
+
|
|
533
|
+
while (i--) {
|
|
534
|
+
if (keys.has(created[i]) === false) {
|
|
535
|
+
const key = created[i]
|
|
536
|
+
`)
|
|
537
|
+
const forEndCounter = counter
|
|
538
|
+
|
|
539
|
+
for (let i = forStartCounter; i <= forEndCounter; i++) {
|
|
540
|
+
destroyCode.push(`
|
|
541
|
+
elms[${i}][key] && elms[${i}][key].destroy()
|
|
542
|
+
delete elms[${i}][key]
|
|
543
|
+
`)
|
|
544
|
+
}
|
|
545
|
+
destroyCode.push(`
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
`)
|
|
549
|
+
|
|
550
|
+
// inject the destroy code in the correct spot
|
|
551
|
+
ctx.renderCode.splice(indexToInjectDestroyCode, 0, ...destroyCode)
|
|
552
|
+
|
|
553
|
+
let effectKey = `${interpolate(result[2], '')}`
|
|
554
|
+
|
|
555
|
+
// Get the last property from nested path
|
|
556
|
+
if (effectKey && effectKey.includes('.')) {
|
|
557
|
+
effectKey = effectKey.match(/[^.]+$/)[0]
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// get the reference to range from and to
|
|
561
|
+
const effectKeysRegex = /\$([^,} ]+)/g
|
|
562
|
+
const effectKeys = [...range.matchAll(effectKeysRegex)].map((match) => `'${match[1]}'`)
|
|
563
|
+
|
|
564
|
+
ctx.renderCode.push(`
|
|
565
|
+
let forEffects${forStartCounter}
|
|
566
|
+
const eff${forStartCounter} = () => {
|
|
567
|
+
component[Symbol.for('removeGlobalEffects')](forEffects${forStartCounter})
|
|
568
|
+
forEffects${forStartCounter} = null
|
|
569
|
+
forEffects${forStartCounter} = forloop${forStartCounter}(${cast(
|
|
570
|
+
result[2],
|
|
571
|
+
':for'
|
|
572
|
+
)}, elms, created${forStartCounter})
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
component[Symbol.for('effects')].push(eff${forStartCounter})
|
|
576
|
+
|
|
577
|
+
effect(eff${forStartCounter}, ['${effectKey}', ${effectKeys.join(',')}] )
|
|
578
|
+
`)
|
|
579
|
+
|
|
580
|
+
outerScopeEffects.forEach((effect, outerScopeEffectsIndex) => {
|
|
581
|
+
const matches = [...effect.matchAll(scopeRegex)]
|
|
582
|
+
|
|
583
|
+
let l = matches.length
|
|
584
|
+
const refs = []
|
|
585
|
+
while (l--) {
|
|
586
|
+
const match = matches[l]
|
|
587
|
+
const ref = `component.${match[2]}`
|
|
588
|
+
refs.indexOf(ref) === -1 && refs.push(ref)
|
|
589
|
+
// don't update the effect to point to component, if we're referring to a scope item
|
|
590
|
+
if (match[2] !== item) {
|
|
591
|
+
effect =
|
|
592
|
+
effect.substring(0, match.index) + ref + effect.substring(match.index + match[1].length)
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
ctx.renderCode.push(`
|
|
597
|
+
const eff${forStartCounter}_${outerScopeEffectsIndex} = () => {
|
|
598
|
+
void ${refs.join(', ')}
|
|
599
|
+
for(let __index = 0; __index < ${interpolate(result[2])}.length; __index++) {
|
|
600
|
+
if(__index < from${forStartCounter} || __index >= to${forStartCounter}) continue
|
|
601
|
+
const scope = {}
|
|
602
|
+
scope['${index}'] = __index
|
|
603
|
+
scope['${item}'] = ${interpolate(result[2])}[__index]
|
|
604
|
+
scope['key'] = ${forKey || '__index'}
|
|
605
|
+
`)
|
|
606
|
+
|
|
607
|
+
ctx.renderCode.push(`
|
|
608
|
+
${effect}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
component[Symbol.for('effects')].push(eff${forStartCounter}_${outerScopeEffectsIndex})
|
|
612
|
+
effect(eff${forStartCounter}_${outerScopeEffectsIndex})
|
|
613
|
+
`)
|
|
614
|
+
})
|
|
615
|
+
|
|
616
|
+
this.renderCode.push(ctx.renderCode.join('\n'))
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
const generateCode = function (templateObject, parent = false, options = {}) {
|
|
620
|
+
templateObject.children &&
|
|
621
|
+
templateObject.children.forEach((childTemplateObject) => {
|
|
622
|
+
counter++
|
|
623
|
+
|
|
624
|
+
if (Object.keys(childTemplateObject).indexOf(':for') > -1) {
|
|
625
|
+
generateForLoopCode.call(this, childTemplateObject, parent)
|
|
626
|
+
} else {
|
|
627
|
+
if (
|
|
628
|
+
childTemplateObject[Symbol.for('componentType')] === 'Element' ||
|
|
629
|
+
childTemplateObject[Symbol.for('componentType')] === 'Slot' ||
|
|
630
|
+
childTemplateObject[Symbol.for('componentType')] === 'Text' ||
|
|
631
|
+
childTemplateObject[Symbol.for('componentType')] === 'Layout'
|
|
632
|
+
) {
|
|
633
|
+
if (childTemplateObject[Symbol.for('componentType')] === 'Text') {
|
|
634
|
+
childTemplateObject.__textnode = 'true'
|
|
635
|
+
}
|
|
636
|
+
if (childTemplateObject[Symbol.for('componentType')] === 'Layout') {
|
|
637
|
+
childTemplateObject.__layout = 'true'
|
|
638
|
+
}
|
|
639
|
+
generateElementCode.call(this, childTemplateObject, parent, options)
|
|
640
|
+
} else {
|
|
641
|
+
generateComponentCode.call(this, childTemplateObject, parent, options)
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
})
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
const interpolate = (val, component = 'component.') => {
|
|
648
|
+
if (val === undefined) return val
|
|
649
|
+
const replaceString = /('.*?')+/gi
|
|
650
|
+
const replaceDollar = /\$(\$(?=\$)|\$?)/g
|
|
651
|
+
const matches = val.matchAll(replaceString)
|
|
652
|
+
const restore = []
|
|
653
|
+
|
|
654
|
+
let i = 0
|
|
655
|
+
|
|
656
|
+
// replace string and store in temp
|
|
657
|
+
for (const match of matches) {
|
|
658
|
+
restore.push(match[0])
|
|
659
|
+
val = val.replace(match[0], `[@@REPLACEMENT${i}@@]`)
|
|
660
|
+
i++
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// replace dollar signs using the regex and function
|
|
664
|
+
val = val.replace(replaceDollar, (match, group1) => {
|
|
665
|
+
// single dollar sign
|
|
666
|
+
if (group1 === '') {
|
|
667
|
+
return component
|
|
668
|
+
}
|
|
669
|
+
// consecutive dollar sign, replace only the first $
|
|
670
|
+
else if (group1 === '$') {
|
|
671
|
+
return component + '$'
|
|
672
|
+
}
|
|
673
|
+
})
|
|
674
|
+
|
|
675
|
+
// restore string replacement
|
|
676
|
+
restore.forEach((el, idx) => {
|
|
677
|
+
val = val.replace(`[@@REPLACEMENT${idx}@@]`, el)
|
|
678
|
+
})
|
|
679
|
+
|
|
680
|
+
return val
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
const cast = (val = '', key = false, component = 'component.') => {
|
|
684
|
+
const dynamicArgumentRegex = /\$\w+/gi
|
|
685
|
+
let castedValue
|
|
686
|
+
|
|
687
|
+
// inline content
|
|
688
|
+
if (key === 'content') {
|
|
689
|
+
if (val.startsWith('$')) {
|
|
690
|
+
castedValue = `${component}${val.replace('$', '')}`
|
|
691
|
+
} else {
|
|
692
|
+
// unescaped single quotes must be escaped while preserving escaped backslashes
|
|
693
|
+
const escapedVal = val
|
|
694
|
+
.replace(/\\\\/g, '__DOUBLE_BACKSLASH__')
|
|
695
|
+
.replace(/(^|[^\\])'/g, "$1\\'")
|
|
696
|
+
.replace(/__DOUBLE_BACKSLASH__/g, '\\\\')
|
|
697
|
+
castedValue = `'${parseInlineContent(escapedVal, component)}'`
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
// numeric
|
|
701
|
+
else if (key !== 'color' && /[^0-9%\s.eE]/.test(val) === false && !isNaN(parseFloat(val))) {
|
|
702
|
+
castedValue = parseFloat(val)
|
|
703
|
+
if (val.endsWith('%')) {
|
|
704
|
+
const map = {
|
|
705
|
+
w: 'width',
|
|
706
|
+
width: 'width',
|
|
707
|
+
x: 'width',
|
|
708
|
+
h: 'height',
|
|
709
|
+
height: 'height',
|
|
710
|
+
y: 'height',
|
|
711
|
+
}
|
|
712
|
+
const base = map[key]
|
|
713
|
+
if (base) {
|
|
714
|
+
castedValue = `parent.node.${base} * (${castedValue} / 100)`
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
// boolean true
|
|
719
|
+
else if (val.toLowerCase() === 'true') {
|
|
720
|
+
castedValue = true
|
|
721
|
+
}
|
|
722
|
+
// boolean false
|
|
723
|
+
else if (val.toLowerCase() === 'false') {
|
|
724
|
+
castedValue = false
|
|
725
|
+
}
|
|
726
|
+
// @-listener
|
|
727
|
+
else if (key.startsWith('@') && val) {
|
|
728
|
+
const c = component.slice(0, -1)
|
|
729
|
+
castedValue = `${c}['${val.replace('$', '')}'] && ${c}['${val.replace('$', '')}'].bind(${c})`
|
|
730
|
+
}
|
|
731
|
+
// dynamic value
|
|
732
|
+
else if (val.startsWith('$')) {
|
|
733
|
+
castedValue = `${component}${val.replace('$', '')}`
|
|
734
|
+
}
|
|
735
|
+
// dynamic value in object
|
|
736
|
+
else if (dynamicArgumentRegex.exec(val)) {
|
|
737
|
+
const rex = /\w+\s*:\s*(?:[^\s,}]+|".*?"|'.*?')/g
|
|
738
|
+
const results = val.match(rex)
|
|
739
|
+
castedValue = {}
|
|
740
|
+
if (results) {
|
|
741
|
+
for (let i = 0; i < results.length; i++) {
|
|
742
|
+
const members = results[i].split(/\s*:\s*/)
|
|
743
|
+
if (members) {
|
|
744
|
+
populateFields(members[0], members[1], component, castedValue)
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
return interpolateObject(castedValue)
|
|
749
|
+
}
|
|
750
|
+
// static string
|
|
751
|
+
else {
|
|
752
|
+
castedValue = `"${val}"`
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
return castedValue
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
const isReactiveKey = (str) => str.startsWith(':')
|
|
759
|
+
|
|
760
|
+
const parseInlineContent = (val, component) => {
|
|
761
|
+
const dynamicParts = /\{\{\s*(\$\S+)\s*\}\}/g
|
|
762
|
+
const matches = [...val.matchAll(dynamicParts)]
|
|
763
|
+
|
|
764
|
+
if (matches.length) {
|
|
765
|
+
for (let [match, arg] of matches) {
|
|
766
|
+
val = val.replace(match, `${arg.replace('$', `'+${component}`)}+'`)
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
return val
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
// Populate value into target's prop
|
|
773
|
+
const populateFields = (prop, val, component, target) => {
|
|
774
|
+
if (val.startsWith('$')) {
|
|
775
|
+
target[prop] = `${component}${val.replace('$', '')}`
|
|
776
|
+
} else {
|
|
777
|
+
target[prop] = val
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
const interpolateObject = (input) => {
|
|
782
|
+
const interpolatedResults = []
|
|
783
|
+
Object.keys(input).forEach((key) => {
|
|
784
|
+
interpolatedResults.push(`${key}: ${input[key]}`)
|
|
785
|
+
})
|
|
786
|
+
|
|
787
|
+
return ` { ${interpolatedResults.join(', ')} }`
|
|
788
|
+
}
|