@lightningjs/blits 1.51.0 → 2.0.0-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/element-test-full.txt +0 -0
  2. package/index.d.ts +35 -50
  3. package/package.json +6 -6
  4. package/scripts/lint.js +17 -0
  5. package/scripts/prepublishOnly.js +2 -2
  6. package/scripts/prepush.js +4 -6
  7. package/src/component/base/methods.js +16 -76
  8. package/src/component/base/utils.js +18 -4
  9. package/src/component/setup/props.js +37 -28
  10. package/src/component/setup/state.js +5 -5
  11. package/src/component.js +28 -19
  12. package/src/components/Circle.js +52 -9
  13. package/src/components/FPScounter.js +224 -88
  14. package/src/components/RouterView.js +58 -6
  15. package/src/components/Sprite.js +72 -8
  16. package/src/constants.js +17 -0
  17. package/src/engine.js +17 -0
  18. package/src/engines/L3/element.js +109 -86
  19. package/src/engines/L3/fontLoader.js +43 -31
  20. package/src/engines/L3/index.js +17 -0
  21. package/src/engines/L3/launch.js +1 -0
  22. package/src/engines/L3/shaderLoader.js +73 -6
  23. package/src/engines/index.js +17 -0
  24. package/src/focus/focus.js +7 -7
  25. package/src/focus/helpers.js +4 -2
  26. package/src/focus/hover.js +7 -6
  27. package/src/helpers/fetchJson.js +4 -6
  28. package/src/launch.js +1 -2
  29. package/src/lib/codegenerator/generator.js +127 -16
  30. package/src/lib/colors/colors.js +0 -1
  31. package/src/lib/lifecycle.js +8 -10
  32. package/src/lib/log.js +4 -4
  33. package/src/lib/reactivityguard/computedprops.js +27 -13
  34. package/src/lib/shaders/shaders.js +164 -0
  35. package/src/lib/symbols.js +25 -0
  36. package/src/lib/templateparser/parser.js +6 -2
  37. package/src/lib/utils.js +53 -0
  38. package/src/plugin.js +17 -0
  39. package/src/plugins/storage/localCookie.js +4 -6
  40. package/src/router/router.js +23 -15
  41. package/test-output.txt +0 -0
  42. package/vite/blitsTips.js +57 -0
  43. package/vite/inject-dev-config.js +17 -0
  44. package/vite/msdfGenerator.js +52 -6
  45. package/vscode/data/template-attributes.json +112 -0
@@ -1,12 +1,79 @@
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
+
1
18
  import Settings from '../../settings.js'
2
19
  import { renderer } from './launch.js'
20
+ import * as webglShaders from '@lightningjs/renderer/webgl/shaders'
21
+ import * as canvasShaders from '@lightningjs/renderer/canvas/shaders'
22
+
23
+ function registerBlitsDefaultShaders(
24
+ shManager,
25
+ Rounded,
26
+ Border,
27
+ Shadow,
28
+ RoundedWithBorder,
29
+ RoundedWithShadow,
30
+ RoundedWithBorderAndShadow,
31
+ HolePunch,
32
+ RadialGradient,
33
+ LinearGradient
34
+ ) {
35
+ shManager.registerShaderType('rounded', Rounded)
36
+ shManager.registerShaderType('border', Border)
37
+ shManager.registerShaderType('shadow', Shadow)
38
+ shManager.registerShaderType('roundedWithBorder', RoundedWithBorder)
39
+ shManager.registerShaderType('roundedWithShadow', RoundedWithShadow)
40
+ shManager.registerShaderType('roundedWithBorderAndShadow', RoundedWithBorderAndShadow)
41
+ shManager.registerShaderType('holePunch', HolePunch)
42
+ shManager.registerShaderType('radialGradient', RadialGradient)
43
+ shManager.registerShaderType('linearGradient', LinearGradient)
44
+ }
3
45
 
4
46
  export default () => {
5
47
  const stage = renderer.stage
6
- Settings.get('shaders', []).forEach((shader) => {
7
- stage.shManager.registerShaderType(shader.name, shader.type)
8
- })
9
- Settings.get('effects', []).forEach((effect) => {
10
- stage.shManager.registerShaderType(effect.name, effect.type)
11
- })
48
+
49
+ const {
50
+ Rounded,
51
+ Border,
52
+ Shadow,
53
+ RoundedWithBorder,
54
+ RoundedWithShadow,
55
+ RoundedWithBorderAndShadow,
56
+ HolePunch,
57
+ RadialGradient,
58
+ LinearGradient,
59
+ } = Settings.get('renderMode', 'webgl') === 'webgl' ? webglShaders : canvasShaders
60
+
61
+ registerBlitsDefaultShaders(
62
+ stage.shManager,
63
+ Rounded,
64
+ Border,
65
+ Shadow,
66
+ RoundedWithBorder,
67
+ RoundedWithShadow,
68
+ RoundedWithBorderAndShadow,
69
+ HolePunch,
70
+ RadialGradient,
71
+ LinearGradient
72
+ )
73
+
74
+ const shaders = Settings.get('shaders', [])
75
+ const len = shaders.length
76
+ for (let i = 0; i < len; i++) {
77
+ stage.shManager.registerShaderType(shaders[i].name, shaders[i].type)
78
+ }
12
79
  }
@@ -1 +1,18 @@
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
+
1
18
  export { default as L3 } from './L3/index.js'
@@ -51,7 +51,7 @@ export default {
51
51
 
52
52
  // unfocus currently focused components
53
53
  if (focusedComponent !== null) {
54
- if (focusChain[focusChain.length - 1] === component.parent) {
54
+ if (focusChain[focusChain.length - 1] === component[symbols.parent]) {
55
55
  focusChain.push(component)
56
56
  } else {
57
57
  const newFocusChain = getAncestors([component])
@@ -59,7 +59,7 @@ export default {
59
59
  while (i--) {
60
60
  // don't unfocus when part of the new focus chain
61
61
  if (newFocusChain.indexOf(focusChain[i]) > -1) break
62
- focusChain[i].lifecycle.state = 'unfocus'
62
+ focusChain[i][symbols.lifecycle].state = 'unfocus'
63
63
  }
64
64
  focusChain = newFocusChain
65
65
  }
@@ -68,7 +68,7 @@ export default {
68
68
  // ensure that all components in the focus path have focus state
69
69
  let i = 0
70
70
  while (i < focusChain.length - 1) {
71
- focusChain[i].lifecycle.state = 'focus'
71
+ focusChain[i][symbols.lifecycle].state = 'focus'
72
72
  i++
73
73
  }
74
74
 
@@ -113,8 +113,8 @@ export const getComponentWithInputEvent = (component, key) => {
113
113
  typeof component[symbols.inputEvents].any === 'function')
114
114
  ) {
115
115
  return component
116
- } else if (component.parent !== undefined) {
117
- return getComponentWithInputEvent(component.parent, key)
116
+ } else if (component[symbols.parent] !== undefined) {
117
+ return getComponentWithInputEvent(component[symbols.parent], key)
118
118
  } else return null
119
119
  }
120
120
 
@@ -126,11 +126,11 @@ export const getComponentWithInputEvent = (component, key) => {
126
126
  const setFocus = (component, event) => {
127
127
  Log.info(
128
128
  '\nFocus chain:\n',
129
- focusChain.map((c, index) => '\t'.repeat(index) + '↳ ' + c.componentId).join('\n')
129
+ focusChain.map((c, index) => '\t'.repeat(index) + '↳ ' + c.$componentId).join('\n')
130
130
  )
131
131
 
132
132
  focusedComponent = component
133
- component.lifecycle.state = 'focus'
133
+ component[symbols.lifecycle].state = 'focus'
134
134
 
135
135
  if (event instanceof KeyboardEvent) {
136
136
  const internalEvent = new KeyboardEvent('keydown', event)
@@ -15,14 +15,16 @@
15
15
  * SPDX-License-Identifier: Apache-2.0
16
16
  */
17
17
 
18
+ import symbols from '../lib/symbols.js'
19
+
18
20
  /**
19
21
  * Recursive function that retrieves the ancestors of a component
20
22
  * @param {Array} components
21
23
  * @returns array components
22
24
  */
23
25
  export const getAncestors = (components) => {
24
- if (components[0].parent !== undefined) {
25
- components.unshift(components[0].parent)
26
+ if (components[0][symbols.parent] !== undefined) {
27
+ components.unshift(components[0][symbols.parent])
26
28
  return getAncestors(components)
27
29
  }
28
30
  return components
@@ -17,6 +17,7 @@
17
17
 
18
18
  import { Log } from '../lib/log.js'
19
19
  import { getAncestors } from './helpers.js'
20
+ import symbols from '../lib/symbols.js'
20
21
 
21
22
  let hoveredComponent = null
22
23
  let hoverChain = []
@@ -37,7 +38,7 @@ export default {
37
38
 
38
39
  // unhover currently hovered components
39
40
  if (hoveredComponent !== null) {
40
- if (hoverChain[hoverChain.length - 1] === component.parent) {
41
+ if (hoverChain[hoverChain.length - 1] === component.$parent) {
41
42
  hoverChain.push(component)
42
43
  } else {
43
44
  const newhoverChain = getAncestors([component])
@@ -47,7 +48,7 @@ export default {
47
48
  if (newhoverChain.indexOf(hoverChain[i]) > -1) break
48
49
  // skip if component is destroyed
49
50
  if (hoverChain[i].eol !== true) {
50
- hoverChain[i].lifecycle.state = 'unhover'
51
+ hoverChain[i][symbols.lifecycle].state = 'unhover'
51
52
  }
52
53
  }
53
54
  hoverChain = newhoverChain
@@ -59,7 +60,7 @@ export default {
59
60
  while (i < hoverChain.length - 1) {
60
61
  // skip if component is destroyed
61
62
  if (hoverChain[i].eol !== true) {
62
- hoverChain[i].lifecycle.state = 'hover'
63
+ hoverChain[i][symbols.lifecycle].state = 'hover'
63
64
  }
64
65
  i++
65
66
  }
@@ -70,7 +71,7 @@ export default {
70
71
  if (hoveredComponent === null) return
71
72
  for (let i = 0; i < hoverChain.length; i++) {
72
73
  if (hoverChain[i].eol !== true) {
73
- hoverChain[i].lifecycle.state = 'unhover'
74
+ hoverChain[i][symbols.lifecycle].state = 'unhover'
74
75
  }
75
76
  }
76
77
  hoveredComponent = null
@@ -85,9 +86,9 @@ export default {
85
86
  const setHover = (component) => {
86
87
  Log.info(
87
88
  '\nHover chain:\n',
88
- hoverChain.map((c, index) => '\t'.repeat(index) + '↳ ' + c.componentId).join('\n')
89
+ hoverChain.map((c, index) => '\t'.repeat(index) + '↳ ' + c.$componentId).join('\n')
89
90
  )
90
91
 
91
92
  hoveredComponent = component
92
- component.lifecycle.state = 'hover'
93
+ component[symbols.lifecycle].state = 'hover'
93
94
  }
@@ -1,10 +1,6 @@
1
1
  /*
2
- * If not stated otherwise in this file or this component's LICENSE file the
3
- * following copyright and licenses apply:
4
- *
5
- * Copyright 2020 Metrological
6
- *
7
- * Licensed under the Apache License, Version 2.0 (the License);
2
+ * Copyright 2024 Comcast Cable Communications Management, LLC
3
+ * Licensed under the Apache License, Version 2.0 (the "License");
8
4
  * you may not use this file except in compliance with the License.
9
5
  * You may obtain a copy of the License at
10
6
  *
@@ -15,6 +11,8 @@
15
11
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
12
  * See the License for the specific language governing permissions and
17
13
  * limitations under the License.
14
+ *
15
+ * SPDX-License-Identifier: Apache-2.0
18
16
  */
19
17
 
20
18
  export default (file) => {
package/src/launch.js CHANGED
@@ -41,9 +41,8 @@ import blitsPackageInfo from '../package.json' with { type: 'json' }
41
41
 
42
42
  /**
43
43
  * @typedef {import('@lightningjs/renderer').RendererMain} RendererMain
44
- * @type {RendererMain}
44
+ * @type {RendererMain | {}}
45
45
  */
46
- // @ts-ignore - We ignore this because it will be replaced by the renderer once the app is launched
47
46
  export let renderer = {}
48
47
  export const stage = {}
49
48
 
@@ -18,10 +18,13 @@
18
18
  let counter
19
19
  let isDev
20
20
 
21
+ import { elementAttributes } from '../../engines/L3/element.js'
22
+
21
23
  export default function (templateObject = { children: [] }, devMode = false) {
22
24
  const ctx = {
23
25
  renderCode: [
24
26
  'const elms = []',
27
+ `const validAttributes = ${JSON.stringify(elementAttributes)}`,
25
28
  'const elementConfigs = []',
26
29
  'const forloops = []',
27
30
  'const props = []',
@@ -81,7 +84,7 @@ export default function (templateObject = { children: [] }, devMode = false) {
81
84
  forloops.length = 0
82
85
  props.length = 0
83
86
  skips.length = 0
84
- }}
87
+ }, skips}
85
88
  `)
86
89
 
87
90
  return {
@@ -97,7 +100,16 @@ export default function (templateObject = { children: [] }, devMode = false) {
97
100
  ),
98
101
  effects: ctx.effectsCode.map(
99
102
  (code) =>
100
- new Function('component', 'elms', 'context', 'components', 'rootComponent', 'effect', code)
103
+ new Function(
104
+ 'component',
105
+ 'elms',
106
+ 'context',
107
+ 'components',
108
+ 'rootComponent',
109
+ 'skips',
110
+ 'effect',
111
+ code
112
+ )
101
113
  ),
102
114
  context: ctx.context,
103
115
  }
@@ -228,13 +240,43 @@ const generateElementCode = function (
228
240
  }
229
241
  })
230
242
 
243
+ if (templateObject[Symbol.for('tagContent')] !== undefined) {
244
+ const val = templateObject[Symbol.for('tagContent')]
245
+
246
+ // Check if the string contains interpolation (`{{ }}`).
247
+ const regex = /\{\{\s*.+?\s*\}\}/
248
+ const containsInterpolation = regex.test(val)
249
+
250
+ if (containsInterpolation === false) {
251
+ // Inline text is treated as static content when interpolation is not defined.
252
+ renderCode.push(`elementConfigs[${counter}]['content'] = '${val}'`)
253
+ } else {
254
+ const output = parseTagContent(val, options.component)
255
+ renderCode.push(`elementConfigs[${counter}]['content'] = ${output}`)
256
+
257
+ const variableRegEx = /\{\{\s*(?=[^}]*\$).+?\s*\}\}/
258
+ // Check if the interpolation contains $ variable
259
+ const isReactive = variableRegEx.test(val)
260
+ if (isReactive === true) {
261
+ this.effectsCode.push(`
262
+ ${elm}.set('content', ${output})
263
+ `)
264
+ }
265
+ }
266
+ }
267
+
231
268
  if (options.holder === true) {
232
269
  renderCode.push(`
233
270
  skips[${counter}] = []
234
- if(typeof cmps[${counter}] !== 'undefined') {
235
- for(let key in cmps[${counter}][Symbol.for('config')].props) {
236
- delete elementConfigs[${counter}][cmps[${counter}][Symbol.for('config')].props[key]]
237
- skips[${counter}].push(cmps[${counter}][Symbol.for('config')].props[key])
271
+ if(typeof cmps[${counter}] !== 'undefined' && cmps[${counter}][Symbol.for('config')].props !== undefined) {
272
+ // attributes that are a prop should be removed from element config (even if it's a know element prop)
273
+ let props = cmps[${counter}][Symbol.for('config')].props
274
+ if(Array.isArray(props) === false) props = Object.keys(cmps[${counter}][Symbol.for('config')].props)
275
+ for(let k = 0; k < props.length; k++) {
276
+ const key = props[k]
277
+ if(validAttributes.indexOf(key) !== -1) continue
278
+ delete elementConfigs[${counter}][key]
279
+ skips[${counter}].push(key)
238
280
  }
239
281
  }
240
282
  `)
@@ -571,9 +613,9 @@ const generateForLoopCode = function (templateObject, parent) {
571
613
  }
572
614
 
573
615
  // separate effects that only rely on variables in the itteration
574
- const innerScopeEffects = ctx.effectsCode.filter(
575
- (effect) => [...effect.matchAll(scopeRegex)].length === 0
576
- )
616
+ const innerScopeEffects = ctx.effectsCode.filter((effect) => {
617
+ return [...effect.matchAll(scopeRegex)].length === 0
618
+ })
577
619
 
578
620
  // separate effects that (also) rely on variables in the outer scope
579
621
  const outerScopeEffects = ctx.effectsCode.filter(
@@ -622,11 +664,21 @@ const generateForLoopCode = function (templateObject, parent) {
622
664
  const forEndCounter = counter
623
665
 
624
666
  for (let i = forStartCounter; i <= forEndCounter; i++) {
625
- destroyCode.push(`
667
+ if (i == forStartCounter) {
668
+ destroyCode.push(`
626
669
  elms[${i}][key] && elms[${i}][key].destroy()
627
670
  elms[${i}][key] = null
628
671
  delete elms[${i}][key]
629
672
  `)
673
+ } else {
674
+ destroyCode.push(`
675
+ if (elms[${i}][key] && elms[${i}][key].$componentId !== undefined) {
676
+ elms[${i}][key] && elms[${i}][key].destroy()
677
+ }
678
+ elms[${i}][key] = null
679
+ delete elms[${i}][key]
680
+ `)
681
+ }
630
682
  }
631
683
  destroyCode.push(`
632
684
  }
@@ -791,12 +843,12 @@ const cast = (val = '', key = false, component = 'component.') => {
791
843
  castedValue = parseFloat(val)
792
844
  if (val.endsWith('%')) {
793
845
  const map = {
794
- w: 'width',
795
- width: 'width',
796
- x: 'width',
797
- h: 'height',
798
- height: 'height',
799
- y: 'height',
846
+ w: 'w',
847
+ width: 'w',
848
+ x: 'w',
849
+ h: 'h',
850
+ height: 'h',
851
+ y: 'h',
800
852
  }
801
853
  const base = map[key]
802
854
  if (base) {
@@ -849,6 +901,65 @@ const cast = (val = '', key = false, component = 'component.') => {
849
901
  return castedValue
850
902
  }
851
903
 
904
+ function escapeSingleQuotes(str) {
905
+ return str.replace(/(\\*)'/g, (match, backslashes) => {
906
+ // If the number of backslashes is odd, quote is already escaped
907
+ if (backslashes.length % 2 === 1) {
908
+ return match
909
+ }
910
+ // Otherwise, escape the quote
911
+ return backslashes + "\\'"
912
+ })
913
+ }
914
+
915
+ const parseTagContent = (val = '', component = 'component.') => {
916
+ // unescaped single quotes must be escaped while preserving escaped backslashes
917
+ let escapedVal = escapeSingleQuotes(val)
918
+
919
+ const dynamicParts = /\{\{\s*.+?\s*\}\}/g
920
+ const matches = [...escapedVal.matchAll(dynamicParts)]
921
+
922
+ if (matches.length > 0) {
923
+ const isValStartsWithBrace = escapedVal.startsWith('{{')
924
+ const isValEndsWithBrace = escapedVal.endsWith('}}')
925
+ for (let matchObj of matches) {
926
+ const { 0: match, index } = matchObj
927
+ const isMatchAtStart = index === 0
928
+ const isMatchAtLast = val[index + match.length] === undefined ? true : false
929
+
930
+ let parsedMatch = match
931
+
932
+ const replaceDollar = /\$(\$(?=\$)|\$?)/g
933
+ const dollarMatches = [...parsedMatch.matchAll(replaceDollar)]
934
+ if (dollarMatches.length > 0) {
935
+ parsedMatch = parsedMatch.replace(replaceDollar, (match, group1) => {
936
+ if (group1 === '') {
937
+ return component
938
+ } else if (group1 === '$') {
939
+ return component + '$'
940
+ }
941
+ })
942
+ }
943
+
944
+ parsedMatch = parsedMatch.replace('{{', '(').replace('}}', ')')
945
+ if (isMatchAtStart === false) {
946
+ parsedMatch = `"+${parsedMatch}`
947
+ }
948
+ if (isMatchAtLast === false) {
949
+ parsedMatch = `${parsedMatch}+"`
950
+ }
951
+ escapedVal = escapedVal.replace(match, parsedMatch)
952
+ }
953
+ if (isValStartsWithBrace === false) {
954
+ escapedVal = '"' + escapedVal
955
+ }
956
+ if (isValEndsWithBrace === false) {
957
+ escapedVal = escapedVal + '"'
958
+ }
959
+ }
960
+ return escapedVal
961
+ }
962
+
852
963
  const isReactiveKey = (str) => str.startsWith(':')
853
964
 
854
965
  const parseInlineContent = (val, component) => {
@@ -86,7 +86,6 @@ export default {
86
86
  console.warn('HSL(A) color format is not supported yet')
87
87
  return '0xffffffff'
88
88
  }
89
-
90
89
  return defaultColor
91
90
  },
92
91
  }
@@ -31,8 +31,8 @@ const states = [
31
31
  'ready', // fired when component instantiated, reactivity setup done and template spawned
32
32
  'focus', // fired when receiving focus (can occur multiple times)
33
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)
34
+ 'hover', // fired when pointer enters the component (mouse support only; can occur multiple times)
35
+ 'unhover', // fired when pointer leaves the component (mouse support only; can occur multiple times)
36
36
  'destroy', // fired when component is destroyed and removed
37
37
  'attach', // fired when entering the viewport margin and attached to the render tree
38
38
  'detach', // fired when leaving the viewport margin and detached from the render tree
@@ -77,7 +77,7 @@ export default {
77
77
  set state(v) {
78
78
  if ((states.indexOf(v) > -1 && v !== this.current) || v === 'refocus') {
79
79
  Log.debug(
80
- `Setting lifecycle state from ${this.current} to ${v} for ${this.component.componentId}`
80
+ `Setting lifecycle state from ${this.current} to ${v} for ${this.component.$componentId}`
81
81
  )
82
82
  this.previous = this.current
83
83
  this.current = v
@@ -86,19 +86,17 @@ export default {
86
86
  privateEmit(v, this.component[symbols.identifier], this.component)
87
87
  // emit 'public' hook
88
88
  emit(v, this.component[symbols.identifier], this.component)
89
- // update the built-in hasFocus state variable
90
- if (v === 'focus') this.component[symbols.state].hasFocus = true
91
- if (v === 'unfocus') this.component[symbols.state].hasFocus = false
89
+ // update the built-in $hasFocus state variable
92
90
  if (v === 'focus' || v === 'unfocus') {
93
91
  if (inspectorEnabled === null) {
94
92
  inspectorEnabled = Settings.get('inspector', false)
95
93
  }
96
94
  }
97
95
  // update the built-in isHovered state variable
98
- if (v === 'hover') this.component[symbols.state].isHovered = true
99
- if (v === 'unhover') this.component[symbols.state].isHovered = false
96
+ if (v === 'hover') this.component[symbols.state].$isHovered = true
97
+ if (v === 'unhover') this.component[symbols.state].$isHovered = false
100
98
  if (v === 'focus') {
101
- this.component[symbols.state].hasFocus = true
99
+ this.component[symbols.state].$hasFocus = true
102
100
  if (
103
101
  inspectorEnabled === true &&
104
102
  this.component[symbols.holder] &&
@@ -108,7 +106,7 @@ export default {
108
106
  }
109
107
  }
110
108
  if (v === 'unfocus') {
111
- this.component[symbols.state].hasFocus = false
109
+ this.component[symbols.state].$hasFocus = false
112
110
  if (
113
111
  inspectorEnabled === true &&
114
112
  this.component[symbols.holder] &&
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
- window.console,
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
- window.console,
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
- window.console,
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
- window.console,
94
+ console,
95
95
  `%c ⚡️ ${context} %c (${new Date().toLocaleTimeString([], {
96
96
  hour: '2-digit',
97
97
  minute: '2-digit',
@@ -293,24 +293,38 @@ export default (code) => {
293
293
  const extractThisReferences = (funcBody) => {
294
294
  const thisRefs = new Set()
295
295
 
296
- // Find all this.X references, but exclude method calls
297
- const refRegex = /this\.\w+(?!\s*\()/g // matches this.X but not this.X() or this.X.Y()
296
+ // Match full property chains, including optional chaining
297
+ const refRegex = /this(?:\.[$\w]+|\?\.[$\w]+)+/g
298
298
  let refMatch
299
299
 
300
300
  while ((refMatch = refRegex.exec(funcBody)) !== null) {
301
301
  const fullRef = refMatch[0]
302
302
 
303
- // Extract just the variable part (this.X)
304
- // We need to avoid method calls or chained methods
305
- const varRef = fullRef.split('.').slice(0, 2).join('.')
306
-
307
- // Don't add if it's part of a method call
308
- if (
309
- !funcBody
310
- .substring(refMatch.index)
311
- .match(new RegExp(`^${varRef.replace(/\./g, '\\.')}\\s*\\(`))
312
- ) {
313
- thisRefs.add(varRef)
303
+ const segmentRegex = /(\??\.)([$\w]+)/g
304
+ let segMatch
305
+ let lastValidRef = 'this'
306
+
307
+ while ((segMatch = segmentRegex.exec(fullRef.substring(4))) !== null) {
308
+ const candidate = 'this' + fullRef.substring(4, 4 + segMatch.index + segMatch[0].length)
309
+ const afterCandidate = funcBody.substring(refMatch.index + candidate.length)
310
+ if (afterCandidate.match(/^\s*\(/)) {
311
+ break
312
+ }
313
+ lastValidRef = candidate
314
+ }
315
+
316
+ if (lastValidRef !== 'this') {
317
+ thisRefs.add(lastValidRef)
318
+ }
319
+ }
320
+
321
+ // Remove shorter prefixes when a longer chain already covers them
322
+ for (const ref of [...thisRefs]) {
323
+ for (const other of thisRefs) {
324
+ if (other !== ref && other.startsWith(ref + '.')) {
325
+ thisRefs.delete(ref)
326
+ break
327
+ }
314
328
  }
315
329
  }
316
330