@lightningjs/blits 2.0.0-beta.1 → 2.0.0-beta.4
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 +76 -0
- package/README.md +2 -0
- package/element-test-full.txt +0 -0
- package/index.d.ts +106 -13
- package/package.json +25 -4
- package/scripts/prepublishOnly.js +13 -4
- package/scripts/prepush.js +21 -1
- package/src/announcer/announcer.js +50 -4
- package/src/announcer/speechSynthesis.js +156 -37
- package/src/application.js +95 -3
- package/src/component/base/methods.js +5 -2
- package/src/component/base/router.js +6 -0
- package/src/component/base/timeouts_intervals.js +56 -0
- package/src/component/setup/routes.js +5 -0
- package/src/component/setup/state.js +2 -0
- package/src/component.js +64 -24
- package/src/components/Circle.js +5 -3
- package/src/components/FPScounter.js +1591 -1577
- package/src/components/RouterView.js +141 -124
- package/src/components/Sprite.js +29 -8
- package/src/components/index.js +0 -2
- package/src/engines/L3/element.js +101 -7
- package/src/engines/L3/launch.js +4 -1
- package/src/{focus.js → focus/focus.js} +6 -18
- package/src/focus/helpers.js +31 -0
- package/src/focus/hover.js +94 -0
- package/src/launch.js +1 -0
- package/src/lib/codegenerator/generator.js +44 -8
- package/src/lib/lifecycle.js +34 -3
- package/src/lib/log.js +4 -4
- package/src/lib/precompiler/precompiler.js +23 -14
- package/src/lib/reactivity/effect.js +3 -2
- package/src/lib/reactivityguard/computedprops.js +86 -15
- package/src/lib/shaders/shaders.js +2 -1
- package/src/lib/symbols.js +2 -0
- package/src/plugins/appstate.d.ts +26 -0
- package/src/plugins/index.d.ts +28 -0
- package/src/plugins/language.d.ts +37 -0
- package/src/plugins/storage/storage.d.ts +31 -0
- package/src/plugins/theme.d.ts +43 -0
- package/src/plugins/theme.js +30 -3
- package/src/router/router.js +78 -14
- package/test-output.txt +0 -0
- package/vite/preCompiler.js +15 -5
- package/vscode/data/template-attributes.json +335 -86
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 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 { getAncestors } from './helpers.js'
|
|
20
|
+
import symbols from '../lib/symbols.js'
|
|
21
|
+
|
|
22
|
+
let hoveredComponent = null
|
|
23
|
+
let hoverChain = []
|
|
24
|
+
|
|
25
|
+
export default {
|
|
26
|
+
get() {
|
|
27
|
+
return hoveredComponent
|
|
28
|
+
},
|
|
29
|
+
set(component) {
|
|
30
|
+
if (component === undefined || component.eol === true) return
|
|
31
|
+
|
|
32
|
+
// early return if already hovered
|
|
33
|
+
if (component === hoveredComponent) return
|
|
34
|
+
|
|
35
|
+
if (hoveredComponent === null) {
|
|
36
|
+
hoverChain = getAncestors([component])
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// unhover currently hovered components
|
|
40
|
+
if (hoveredComponent !== null) {
|
|
41
|
+
if (hoverChain[hoverChain.length - 1] === component.$parent) {
|
|
42
|
+
hoverChain.push(component)
|
|
43
|
+
} else {
|
|
44
|
+
const newhoverChain = getAncestors([component])
|
|
45
|
+
let i = hoverChain.length
|
|
46
|
+
while (i--) {
|
|
47
|
+
// don't unhover when part of the new hover chain
|
|
48
|
+
if (newhoverChain.indexOf(hoverChain[i]) > -1) break
|
|
49
|
+
// skip if component is destroyed
|
|
50
|
+
if (hoverChain[i].eol !== true) {
|
|
51
|
+
hoverChain[i][symbols.lifecycle].state = 'unhover'
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
hoverChain = newhoverChain
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ensure that all components in the hover path have hover state
|
|
59
|
+
let i = 0
|
|
60
|
+
while (i < hoverChain.length - 1) {
|
|
61
|
+
// skip if component is destroyed
|
|
62
|
+
if (hoverChain[i].eol !== true) {
|
|
63
|
+
hoverChain[i][symbols.lifecycle].state = 'hover'
|
|
64
|
+
}
|
|
65
|
+
i++
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
setHover(component)
|
|
69
|
+
},
|
|
70
|
+
clear() {
|
|
71
|
+
if (hoveredComponent === null) return
|
|
72
|
+
for (let i = 0; i < hoverChain.length; i++) {
|
|
73
|
+
if (hoverChain[i].eol !== true) {
|
|
74
|
+
hoverChain[i][symbols.lifecycle].state = 'unhover'
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
hoveredComponent = null
|
|
78
|
+
hoverChain = []
|
|
79
|
+
},
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Set the hover to the Component
|
|
84
|
+
* @param {Object} component - The component to hover
|
|
85
|
+
*/
|
|
86
|
+
const setHover = (component) => {
|
|
87
|
+
Log.info(
|
|
88
|
+
'\nHover chain:\n',
|
|
89
|
+
hoverChain.map((c, index) => '\t'.repeat(index) + '↳ ' + c.$componentId).join('\n')
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
hoveredComponent = component
|
|
93
|
+
component[symbols.lifecycle].state = 'hover'
|
|
94
|
+
}
|
package/src/launch.js
CHANGED
|
@@ -88,6 +88,7 @@ async function rendererVersion() {
|
|
|
88
88
|
* @property {number} [textureProcessingTimeLimit] - Max time renderer can process textures in a frame
|
|
89
89
|
* @property {Partial<import('@lightningjs/renderer').RendererMainSettings>} [advanced] - Advanced renderer settings
|
|
90
90
|
* @property {boolean} [announcer] - Whether or not the announcer should be activated on initialization
|
|
91
|
+
* @property {boolean} [enableMouse] - Enable mouse support (hover and click-to-focus). Defaults to false.
|
|
91
92
|
* @property {number} [maxFPS] - Maximum FPS
|
|
92
93
|
*/
|
|
93
94
|
|
|
@@ -163,6 +163,10 @@ const generateElementCode = function (
|
|
|
163
163
|
|
|
164
164
|
renderCode.push(`elementConfigs[${counter}] = {}`)
|
|
165
165
|
|
|
166
|
+
if (counter === 0) {
|
|
167
|
+
renderCode.push(`elementConfigs[${counter}]['___wrapper'] = true `)
|
|
168
|
+
}
|
|
169
|
+
|
|
166
170
|
if (options.forloop) {
|
|
167
171
|
renderCode.push(`if(${elm} === undefined) {`)
|
|
168
172
|
}
|
|
@@ -182,6 +186,10 @@ const generateElementCode = function (
|
|
|
182
186
|
renderCode.push(`elementConfigs[${counter}][Symbol.for('isSlot')] = true`)
|
|
183
187
|
}
|
|
184
188
|
|
|
189
|
+
if (options.holder) {
|
|
190
|
+
renderCode.push(`elementConfigs[${counter}]['holder'] = true`)
|
|
191
|
+
}
|
|
192
|
+
|
|
185
193
|
Object.keys(templateObject).forEach((key) => {
|
|
186
194
|
if (key === 'slot') {
|
|
187
195
|
renderCode.push(`
|
|
@@ -325,9 +333,11 @@ const generateComponentCode = function (
|
|
|
325
333
|
|
|
326
334
|
const children = templateObject.children
|
|
327
335
|
delete templateObject.children
|
|
336
|
+
// Capture holder counter before generating element code (which may process children and increment counter)
|
|
337
|
+
const holderCounter = counter
|
|
328
338
|
generateElementCode.call(this, templateObject, parent, { ...options, ...{ holder: true } })
|
|
329
339
|
|
|
330
|
-
parent = options.key ? `elms[${
|
|
340
|
+
parent = options.key ? `elms[${holderCounter}][${options.key}]` : `elms[${holderCounter}]`
|
|
331
341
|
|
|
332
342
|
counter++
|
|
333
343
|
|
|
@@ -396,6 +406,21 @@ const generateComponentCode = function (
|
|
|
396
406
|
}
|
|
397
407
|
`)
|
|
398
408
|
|
|
409
|
+
if (isDev === true) {
|
|
410
|
+
const templateTagName = templateObject[Symbol.for('componentType')]
|
|
411
|
+
const holderElm = options.key
|
|
412
|
+
? `elms[${holderCounter}][${options.key}]`
|
|
413
|
+
: `elms[${holderCounter}]`
|
|
414
|
+
const componentDisplayName = `typeof componentType === 'string'
|
|
415
|
+
? componentType
|
|
416
|
+
: (componentType?.[Symbol.for('componentType')] || '${templateTagName}')`
|
|
417
|
+
renderCode.push(`
|
|
418
|
+
if (${holderElm} !== undefined && typeof ${holderElm}.setInspectorMetadata === 'function') {
|
|
419
|
+
${holderElm}.setInspectorMetadata({ 'blits-componentType': ${componentDisplayName} })
|
|
420
|
+
}
|
|
421
|
+
`)
|
|
422
|
+
}
|
|
423
|
+
|
|
399
424
|
this.cleanupCode.push(`components[${counter}] = null`)
|
|
400
425
|
|
|
401
426
|
if (options.forloop) {
|
|
@@ -451,7 +476,7 @@ const generateForLoopCode = function (templateObject, parent) {
|
|
|
451
476
|
.replace(')', '')
|
|
452
477
|
.split(/\s*,\s*/)
|
|
453
478
|
|
|
454
|
-
const scopeRegex = new RegExp(`(scope\\.(?!${item}\\.|${index}|key)(
|
|
479
|
+
const scopeRegex = new RegExp(`(scope\\.(?!${item}\\.|${index}|key)([\\w$]+))`, 'gi')
|
|
455
480
|
|
|
456
481
|
// local context
|
|
457
482
|
const ctx = {
|
|
@@ -524,10 +549,16 @@ const generateForLoopCode = function (templateObject, parent) {
|
|
|
524
549
|
|
|
525
550
|
component !== null && component[Symbol.for('removeGlobalEffects')](effects[${forStartCounter}])
|
|
526
551
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
const
|
|
530
|
-
|
|
552
|
+
const effectsToRemove = new Set(effects[${forStartCounter}].slice(0))
|
|
553
|
+
if (effectsToRemove.size > 0) {
|
|
554
|
+
const componentEffects = component?.[Symbol.for('effects')] || []
|
|
555
|
+
let writeIndex = 0
|
|
556
|
+
for (let readIndex = 0; readIndex < componentEffects.length; readIndex++) {
|
|
557
|
+
if (!effectsToRemove.has(componentEffects[readIndex])) {
|
|
558
|
+
componentEffects[writeIndex++] = componentEffects[readIndex]
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
componentEffects.length = writeIndex
|
|
531
562
|
}
|
|
532
563
|
|
|
533
564
|
effects[${forStartCounter}].length = 0
|
|
@@ -825,8 +856,13 @@ const cast = (val = '', key = false, component = 'component.') => {
|
|
|
825
856
|
}
|
|
826
857
|
// @-listener
|
|
827
858
|
else if (key.startsWith('@') && val) {
|
|
828
|
-
const
|
|
829
|
-
|
|
859
|
+
const trimmed = val.trim()
|
|
860
|
+
if (/^\$?\w+$/.test(trimmed)) {
|
|
861
|
+
const c = component.slice(0, -1)
|
|
862
|
+
castedValue = `${c}['${trimmed.replace('$', '')}'] && ${c}['${trimmed.replace('$', '')}'].bind(${c})`
|
|
863
|
+
} else {
|
|
864
|
+
castedValue = interpolate(trimmed, component)
|
|
865
|
+
}
|
|
830
866
|
}
|
|
831
867
|
// dynamic value
|
|
832
868
|
else if (val.startsWith('$')) {
|
package/src/lib/lifecycle.js
CHANGED
|
@@ -18,6 +18,9 @@
|
|
|
18
18
|
import { Log } from './log.js'
|
|
19
19
|
import { emit, privateEmit } from './hooks.js'
|
|
20
20
|
import symbols from './symbols.js'
|
|
21
|
+
import Settings from '../settings.js'
|
|
22
|
+
|
|
23
|
+
let inspectorEnabled = null
|
|
21
24
|
|
|
22
25
|
/**
|
|
23
26
|
* List of valid lifecycle states for a component.
|
|
@@ -28,6 +31,8 @@ const states = [
|
|
|
28
31
|
'ready', // fired when component instantiated, reactivity setup done and template spawned
|
|
29
32
|
'focus', // fired when receiving focus (can occur multiple times)
|
|
30
33
|
'unfocus', // fired when losing focus (can occur multiple times)
|
|
34
|
+
'hover', // fired when receiving focus (can occur multiple times)
|
|
35
|
+
'unhover', // fired when losing focus (can occur multiple times)
|
|
31
36
|
'destroy', // fired when component is destroyed and removed
|
|
32
37
|
'attach', // fired when entering the viewport margin and attached to the render tree
|
|
33
38
|
'detach', // fired when leaving the viewport margin and detached from the render tree
|
|
@@ -81,9 +86,35 @@ export default {
|
|
|
81
86
|
privateEmit(v, this.component[symbols.identifier], this.component)
|
|
82
87
|
// emit 'public' hook
|
|
83
88
|
emit(v, this.component[symbols.identifier], this.component)
|
|
84
|
-
// update the built-in hasFocus state variable
|
|
85
|
-
if (v === 'focus'
|
|
86
|
-
|
|
89
|
+
// update the built-in $hasFocus state variable
|
|
90
|
+
if (v === 'focus' || v === 'unfocus') {
|
|
91
|
+
if (inspectorEnabled === null) {
|
|
92
|
+
inspectorEnabled = Settings.get('inspector', false)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// update the built-in isHovered state variable
|
|
96
|
+
if (v === 'hover') this.component[symbols.state].isHovered = true
|
|
97
|
+
if (v === 'unhover') this.component[symbols.state].isHovered = false
|
|
98
|
+
if (v === 'focus') {
|
|
99
|
+
this.component[symbols.state].$hasFocus = true
|
|
100
|
+
if (
|
|
101
|
+
inspectorEnabled === true &&
|
|
102
|
+
this.component[symbols.holder] &&
|
|
103
|
+
typeof this.component[symbols.holder].setInspectorMetadata === 'function'
|
|
104
|
+
) {
|
|
105
|
+
this.component[symbols.holder].setInspectorMetadata({ 'blits-hasFocus': true })
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (v === 'unfocus') {
|
|
109
|
+
this.component[symbols.state].$hasFocus = false
|
|
110
|
+
if (
|
|
111
|
+
inspectorEnabled === true &&
|
|
112
|
+
this.component[symbols.holder] &&
|
|
113
|
+
typeof this.component[symbols.holder].setInspectorMetadata === 'function'
|
|
114
|
+
) {
|
|
115
|
+
this.component[symbols.holder].setInspectorMetadata({ 'blits-hasFocus': false })
|
|
116
|
+
}
|
|
117
|
+
}
|
|
87
118
|
}
|
|
88
119
|
},
|
|
89
120
|
}
|
package/src/lib/log.js
CHANGED
|
@@ -46,7 +46,7 @@ const logger = (context) => {
|
|
|
46
46
|
return (
|
|
47
47
|
((level >= 1 || (Array.isArray(level) && level.indexOf('info') > -1)) &&
|
|
48
48
|
console.info.bind(
|
|
49
|
-
|
|
49
|
+
console,
|
|
50
50
|
`%c ⚡️ ${context} %c ${time()}`,
|
|
51
51
|
'background-color: #0284c7; color: white; padding: 3px 6px 3px 1px; border-radius: 3px',
|
|
52
52
|
'color: ##94a3b8;'
|
|
@@ -61,7 +61,7 @@ const logger = (context) => {
|
|
|
61
61
|
return (
|
|
62
62
|
((level >= 1 || (Array.isArray(level) && level.indexOf('warn') > -1)) &&
|
|
63
63
|
console.warn.bind(
|
|
64
|
-
|
|
64
|
+
console,
|
|
65
65
|
`%c ⚡️ ${context} %c ${time()}`,
|
|
66
66
|
'background-color: #fbbf24; color: white; padding: 3px 6px 3px 1px; border-radius: 3px',
|
|
67
67
|
'color: ##94a3b8;'
|
|
@@ -76,7 +76,7 @@ const logger = (context) => {
|
|
|
76
76
|
return (
|
|
77
77
|
((level >= 1 || (Array.isArray(level) && level.indexOf('error') > -1)) &&
|
|
78
78
|
console.error.bind(
|
|
79
|
-
|
|
79
|
+
console,
|
|
80
80
|
`%c ⚡️ ${context} %c ${time()}`,
|
|
81
81
|
'background-color: #dc2626; color: white; padding: 3px 6px 3px 1px; border-radius: 3px',
|
|
82
82
|
'color: ##94a3b8;'
|
|
@@ -91,7 +91,7 @@ const logger = (context) => {
|
|
|
91
91
|
return (
|
|
92
92
|
((level >= 2 || (Array.isArray(level) && level.indexOf('debug') > -1)) &&
|
|
93
93
|
console.debug.bind(
|
|
94
|
-
|
|
94
|
+
console,
|
|
95
95
|
`%c ⚡️ ${context} %c (${new Date().toLocaleTimeString([], {
|
|
96
96
|
hour: '2-digit',
|
|
97
97
|
minute: '2-digit',
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
import parser from '../templateparser/parser.js'
|
|
19
19
|
import generator from '../codegenerator/generator.js'
|
|
20
|
+
import MagicString from 'magic-string'
|
|
20
21
|
|
|
21
22
|
export default (source, filePath, mode) => {
|
|
22
23
|
if (
|
|
@@ -26,12 +27,9 @@ export default (source, filePath, mode) => {
|
|
|
26
27
|
/\{.*?template\s*:\s*(['"`])((?:\\?.)*?)\1.*?\}/s.test(source) // object with template key
|
|
27
28
|
) {
|
|
28
29
|
const templates = source.matchAll(/(?<!\/\/\s*)template\s*:\s*(['"`])((?:\\?.)*?)\1/gs)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
track of the offset caused by the previous replacements
|
|
33
|
-
*/
|
|
34
|
-
let offset = 0
|
|
30
|
+
|
|
31
|
+
// Use MagicString to track changes
|
|
32
|
+
const s = new MagicString(source)
|
|
35
33
|
|
|
36
34
|
for (const template of templates) {
|
|
37
35
|
if (template[2]) {
|
|
@@ -39,7 +37,8 @@ export default (source, filePath, mode) => {
|
|
|
39
37
|
|
|
40
38
|
// Only process if it looks like a Blits template
|
|
41
39
|
if (templateContent.match(/^\s*(<!--[\s\S]*?-->|<[A-Za-z][^>]*>)/s)) {
|
|
42
|
-
|
|
40
|
+
// Use original indices - MagicString handles position tracking automatically
|
|
41
|
+
const templateStartIndex = template.index
|
|
43
42
|
const templateEndIndex = templateStartIndex + template[0].length
|
|
44
43
|
|
|
45
44
|
// Parse the template
|
|
@@ -58,16 +57,26 @@ export default (source, filePath, mode) => {
|
|
|
58
57
|
(fn) => fn.toString()
|
|
59
58
|
)}], context: {}}`
|
|
60
59
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
newSource =
|
|
64
|
-
newSource.substring(0, templateStartIndex) +
|
|
65
|
-
replacement +
|
|
66
|
-
newSource.substring(templateEndIndex)
|
|
60
|
+
// MagicString tracks all changes automatically, no offset needed
|
|
61
|
+
s.overwrite(templateStartIndex, templateEndIndex, replacement)
|
|
67
62
|
}
|
|
68
63
|
}
|
|
69
64
|
}
|
|
70
|
-
|
|
65
|
+
|
|
66
|
+
const newSource = s.toString()
|
|
67
|
+
|
|
68
|
+
if (newSource !== source) {
|
|
69
|
+
const fileName = filePath.split(/[\\/]/).pop()
|
|
70
|
+
return {
|
|
71
|
+
code: newSource,
|
|
72
|
+
map: s.generateMap({
|
|
73
|
+
hires: true,
|
|
74
|
+
source: fileName,
|
|
75
|
+
includeContent: true,
|
|
76
|
+
file: fileName,
|
|
77
|
+
}),
|
|
78
|
+
}
|
|
79
|
+
}
|
|
71
80
|
}
|
|
72
81
|
return source
|
|
73
82
|
}
|
|
@@ -32,9 +32,10 @@ const objectMap = new WeakMap()
|
|
|
32
32
|
const globalEffectsMap = new Map()
|
|
33
33
|
|
|
34
34
|
export const removeGlobalEffects = (effectsToRemove) => {
|
|
35
|
-
if (globalEffectsMap.size === 0) return
|
|
35
|
+
if (globalEffectsMap.size === 0 || effectsToRemove.length === 0) return
|
|
36
|
+
const effectsToRemoveSet = new Set(effectsToRemove)
|
|
36
37
|
for (const [effect, target] of globalEffectsMap) {
|
|
37
|
-
if (
|
|
38
|
+
if (!effectsToRemoveSet.has(effect)) continue
|
|
38
39
|
const effectsSet = objectMap.get(target)
|
|
39
40
|
if (effectsSet === undefined) continue
|
|
40
41
|
for (const set of effectsSet.values()) {
|
|
@@ -35,10 +35,12 @@ const findMatchingBrace = (text, startPos) => {
|
|
|
35
35
|
// Skip comments when not in strings
|
|
36
36
|
else if (!inString && char === '/' && text[position + 1] === '/') {
|
|
37
37
|
// Skip line comment
|
|
38
|
-
|
|
38
|
+
const nextNewline = text.indexOf('\n', position + 2)
|
|
39
|
+
position = nextNewline === -1 ? text.length - 1 : nextNewline
|
|
39
40
|
} else if (!inString && char === '/' && text[position + 1] === '*') {
|
|
40
41
|
// Skip block comment
|
|
41
|
-
|
|
42
|
+
const end = text.indexOf('*/', position + 2)
|
|
43
|
+
position = end === -1 ? text.length - 1 : end + 1
|
|
42
44
|
}
|
|
43
45
|
// Count braces only outside strings and comments
|
|
44
46
|
else if (!inString) {
|
|
@@ -60,6 +62,44 @@ const findComputedFunctionEnd = (computedObj, openingBracePos) => {
|
|
|
60
62
|
return findMatchingBrace(computedObj, openingBracePos)
|
|
61
63
|
}
|
|
62
64
|
|
|
65
|
+
// Skip whitespace + commas + comments (line and block) starting from `pos`, return next code position.
|
|
66
|
+
const skipNonCode = (text, pos) => {
|
|
67
|
+
let p = pos
|
|
68
|
+
while (p < text.length) {
|
|
69
|
+
const c = text[p]
|
|
70
|
+
|
|
71
|
+
// whitespace
|
|
72
|
+
if (c === ' ' || c === '\t' || c === '\n' || c === '\r') {
|
|
73
|
+
p++
|
|
74
|
+
continue
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// separators between object members
|
|
78
|
+
if (c === ',') {
|
|
79
|
+
p++
|
|
80
|
+
continue
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// line comment
|
|
84
|
+
if (c === '/' && text[p + 1] === '/') {
|
|
85
|
+
const nextNewline = text.indexOf('\n', p + 2)
|
|
86
|
+
p = nextNewline === -1 ? text.length : nextNewline + 1
|
|
87
|
+
continue
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// block comment
|
|
91
|
+
if (c === '/' && text[p + 1] === '*') {
|
|
92
|
+
const end = text.indexOf('*/', p + 2)
|
|
93
|
+
p = end === -1 ? text.length : end + 2
|
|
94
|
+
continue
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return p
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return p
|
|
101
|
+
}
|
|
102
|
+
|
|
63
103
|
export default (code) => {
|
|
64
104
|
// Find component declarations
|
|
65
105
|
const componentRegex =
|
|
@@ -83,6 +123,8 @@ export default (code) => {
|
|
|
83
123
|
|
|
84
124
|
if (computedEnd !== -1) {
|
|
85
125
|
const computedObj = configObject.substring(computedStart, computedEnd)
|
|
126
|
+
const computedBlockStart = computedKeywordMatch.index
|
|
127
|
+
const computedBlockEnd = computedEnd
|
|
86
128
|
|
|
87
129
|
// Process each computed property
|
|
88
130
|
const computedProps = []
|
|
@@ -93,15 +135,20 @@ export default (code) => {
|
|
|
93
135
|
// using cursor-based parsing gives better accuracy
|
|
94
136
|
while (currentPos < computedObj.length - 1) {
|
|
95
137
|
// -1 to exclude the closing brace
|
|
96
|
-
//
|
|
97
|
-
|
|
138
|
+
// Skip whitespace/comments so we don't match commented-out computed functions
|
|
139
|
+
currentPos = skipNonCode(computedObj, currentPos)
|
|
140
|
+
if (currentPos >= computedObj.length - 1) break
|
|
141
|
+
|
|
142
|
+
// Find the next function name (computed properties are `name() { ... }`)
|
|
143
|
+
const functionNameMatch = /^(\w+)\s*\(\)\s*/.exec(computedObj.substring(currentPos))
|
|
98
144
|
if (!functionNameMatch) break
|
|
99
145
|
|
|
100
146
|
const funcName = functionNameMatch[1]
|
|
101
|
-
const funcNamePos = currentPos
|
|
147
|
+
const funcNamePos = currentPos
|
|
102
148
|
|
|
103
149
|
// Find opening brace for this function
|
|
104
|
-
const
|
|
150
|
+
const afterSignaturePos = funcNamePos + functionNameMatch[0].length
|
|
151
|
+
const openingBracePos = computedObj.indexOf('{', afterSignaturePos)
|
|
105
152
|
if (openingBracePos === -1) break
|
|
106
153
|
|
|
107
154
|
// Find closing brace for individual computed function
|
|
@@ -133,21 +180,22 @@ export default (code) => {
|
|
|
133
180
|
}
|
|
134
181
|
|
|
135
182
|
// Store the original computed object for later replacement
|
|
136
|
-
const originalComputedObj = configObject.substring(
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
)
|
|
183
|
+
const originalComputedObj = configObject.substring(computedBlockStart, computedBlockEnd)
|
|
184
|
+
const computedObjOffsetInOriginal = computedStart - computedBlockStart
|
|
185
|
+
const computedObjEndInOriginal = computedObjOffsetInOriginal + computedObj.length
|
|
140
186
|
|
|
141
187
|
let modifiedComputedObj = originalComputedObj
|
|
142
188
|
let hasChanges = false
|
|
143
189
|
|
|
190
|
+
const replacements = []
|
|
191
|
+
|
|
144
192
|
for (const prop of computedProps) {
|
|
145
193
|
const thisRefs = extractThisReferences(prop.body)
|
|
146
194
|
|
|
147
195
|
if (thisRefs.size > 0) {
|
|
148
196
|
// Skip if already modified
|
|
149
197
|
if (
|
|
150
|
-
prop.body.includes(commentText) ||
|
|
198
|
+
prop.body.includes(commentText.trim()) ||
|
|
151
199
|
Array.from(thisRefs).some((ref) => prop.body.startsWith(ref))
|
|
152
200
|
) {
|
|
153
201
|
continue
|
|
@@ -181,13 +229,31 @@ export default (code) => {
|
|
|
181
229
|
prop.fullText.substring(0, openBraceIndex).match(/^(\s*)/)[0] +
|
|
182
230
|
'}'
|
|
183
231
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
232
|
+
replacements.push({
|
|
233
|
+
start: prop.startPos,
|
|
234
|
+
end: prop.endPos,
|
|
235
|
+
text: modifiedPropText,
|
|
236
|
+
})
|
|
187
237
|
}
|
|
188
238
|
}
|
|
189
239
|
}
|
|
190
240
|
|
|
241
|
+
if (replacements.length > 0) {
|
|
242
|
+
let modifiedComputedInner = computedObj
|
|
243
|
+
|
|
244
|
+
replacements.sort((a, b) => b.start - a.start)
|
|
245
|
+
for (const r of replacements) {
|
|
246
|
+
modifiedComputedInner =
|
|
247
|
+
modifiedComputedInner.slice(0, r.start) + r.text + modifiedComputedInner.slice(r.end)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
modifiedComputedObj =
|
|
251
|
+
originalComputedObj.slice(0, computedObjOffsetInOriginal) +
|
|
252
|
+
modifiedComputedInner +
|
|
253
|
+
originalComputedObj.slice(computedObjEndInOriginal)
|
|
254
|
+
hasChanges = true
|
|
255
|
+
}
|
|
256
|
+
|
|
191
257
|
if (hasChanges) {
|
|
192
258
|
// Store the modification of the entire computed object
|
|
193
259
|
modifications.push({
|
|
@@ -213,7 +279,12 @@ export default (code) => {
|
|
|
213
279
|
modifiedCode = modifiedCode.replace(mod.original, mod.replacement)
|
|
214
280
|
}
|
|
215
281
|
|
|
216
|
-
|
|
282
|
+
// Returning code without a source map
|
|
283
|
+
// Vite will automatically chain this with the next plugin's source map (preCompiler)
|
|
284
|
+
return {
|
|
285
|
+
code: modifiedCode,
|
|
286
|
+
map: { mappings: '' },
|
|
287
|
+
}
|
|
217
288
|
}
|
|
218
289
|
|
|
219
290
|
return null
|
|
@@ -139,11 +139,12 @@ export default {
|
|
|
139
139
|
return v
|
|
140
140
|
}
|
|
141
141
|
if (typeof v === 'string') {
|
|
142
|
-
return colors.normalize(v)
|
|
142
|
+
return colors.normalize(v, v)
|
|
143
143
|
}
|
|
144
144
|
if (Array.isArray(v) === true) {
|
|
145
145
|
return v.map((i) => this.parseProp(i))
|
|
146
146
|
}
|
|
147
|
+
return v
|
|
147
148
|
},
|
|
148
149
|
parseProps(v) {
|
|
149
150
|
if (typeof v === 'string') {
|
package/src/lib/symbols.js
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* @property {symbol} inputEvents
|
|
18
18
|
* @property {symbol} internalEvent
|
|
19
19
|
* @property {symbol} intervals
|
|
20
|
+
* @property {symbol} debounces
|
|
20
21
|
* @property {symbol} isProxy
|
|
21
22
|
* @property {symbol} launched
|
|
22
23
|
* @property {symbol} level
|
|
@@ -73,6 +74,7 @@ export default {
|
|
|
73
74
|
inputEvents: Symbol('inputEvents'),
|
|
74
75
|
internalEvent: Symbol('internalEvent'),
|
|
75
76
|
intervals: Symbol('intervals'),
|
|
77
|
+
debounces: Symbol('debounces'),
|
|
76
78
|
isProxy: Symbol('isProxy'),
|
|
77
79
|
launched: Symbol('launched'),
|
|
78
80
|
level: Symbol('level'),
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 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 type AppStatePlugin<TState extends Record<string, unknown> = Record<string, unknown>> = TState
|
|
19
|
+
|
|
20
|
+
declare const appState: {
|
|
21
|
+
readonly name: 'appState'
|
|
22
|
+
plugin: <TState extends Record<string, unknown>>(state?: TState) => AppStatePlugin<TState>
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default appState
|
|
26
|
+
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 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 as language } from './language.js'
|
|
19
|
+
export { default as theme } from './theme.js'
|
|
20
|
+
export { default as appState } from './appstate.js'
|
|
21
|
+
export { default as storage } from './storage/storage.js'
|
|
22
|
+
|
|
23
|
+
// Re-export plugin interfaces for convenience
|
|
24
|
+
export type { LanguagePlugin, LanguagePluginOptions } from './language.js'
|
|
25
|
+
export type { ThemePlugin, ThemePluginConfig } from './theme.js'
|
|
26
|
+
export type { StoragePlugin } from './storage/storage.js'
|
|
27
|
+
export type { AppStatePlugin } from './appstate.js'
|
|
28
|
+
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 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 interface LanguagePlugin {
|
|
19
|
+
translate(key: string, ...replacements: any[]): string
|
|
20
|
+
readonly language: string
|
|
21
|
+
set(language: string): void
|
|
22
|
+
translations(translationsObject: Record<string, unknown>): void
|
|
23
|
+
load(file: string): Promise<void>
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface LanguagePluginOptions {
|
|
27
|
+
file?: string
|
|
28
|
+
language?: string
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
declare const language: {
|
|
32
|
+
readonly name: 'language'
|
|
33
|
+
plugin: (options?: LanguagePluginOptions) => LanguagePlugin
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export default language
|
|
37
|
+
|