@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
Binary file
package/index.d.ts CHANGED
@@ -18,6 +18,10 @@
18
18
  // blits file type reference
19
19
  /// <reference path="./blits.d.ts" />
20
20
 
21
+ import {type ShaderEffect as RendererShaderEffect, type RendererMainSettings} from '@lightningjs/renderer'
22
+ import { CanvasShaderType } from '@lightningjs/renderer/canvas';
23
+ import { WebGlShaderType } from '@lightningjs/renderer/webgl';
24
+
21
25
  declare module '@lightningjs/blits' {
22
26
  type RendererShaderEffect = import('@lightningjs/renderer').ShaderEffect
23
27
  type WebGlCoreShader = import('@lightningjs/renderer').WebGlCoreShader
@@ -394,14 +398,21 @@ declare module '@lightningjs/blits' {
394
398
  *
395
399
  * @returns Boolean
396
400
  */
397
- hasFocus: boolean,
401
+ readonly $hasFocus: boolean,
402
+
403
+ /**
404
+ * Parent component instance.
405
+ * Note: will be undefined for the root Application component or after destroy.
406
+ *
407
+ */
408
+ readonly $parent: ComponentBase,
398
409
 
399
410
  /**
400
411
  * Indicates whether the component currently is hovered
401
412
  *
402
413
  * @returns Boolean
403
414
  */
404
- isHovered: boolean,
415
+ $isHovered: boolean,
405
416
 
406
417
  /**
407
418
  * Listen to events emitted by other components
@@ -490,12 +501,6 @@ declare module '@lightningjs/blits' {
490
501
  * @returns Returns true if this component or a parent component handled the event, false otherwise
491
502
  */
492
503
  $input: (event: KeyboardEvent) => boolean
493
- /**
494
- * @deprecated
495
- * Deprecated: use `this.$focus()` instead
496
- */
497
- focus: (event?: KeyboardEvent) => void
498
-
499
504
  /**
500
505
  * Select a child Element or Component by ref
501
506
  *
@@ -513,12 +518,6 @@ declare module '@lightningjs/blits' {
513
518
  */
514
519
  $select: (ref: string) => ComponentBase
515
520
 
516
- /**
517
- * @deprecated
518
- * Deprecated: use `this.$select()` instead
519
- */
520
- select: (ref: string) => ComponentBase
521
-
522
521
  /**
523
522
  * Announcer methods for screen reader support
524
523
  */
@@ -528,13 +527,6 @@ declare module '@lightningjs/blits' {
528
527
  * Triggers a forced update on state variables.
529
528
  */
530
529
  $trigger: (key: string) => void
531
- /**
532
- * @deprecated
533
- *
534
- * Triggers a forced update on state variables.
535
- * Deprecated: use `this.$trigger()` instead
536
- */
537
- trigger: (key: string) => void
538
530
  /**
539
531
  * Router instance
540
532
  */
@@ -584,20 +576,22 @@ declare module '@lightningjs/blits' {
584
576
  cast?: () => any
585
577
  };
586
578
 
587
- // Props Array
588
- export type Props = (string | PropObject)[];
579
+ export type Props = Record<string, any>
589
580
 
590
- // Extract the prop names from the props array
591
- type ExtractPropNames<P extends Props> = {
592
- readonly [K in P[number] as K extends string ? K : K extends { key: infer Key } ? Key : never]: any;
593
- };
581
+ type InferProp<T> = T extends (...args: any[]) => any ? ReturnType<T> : T
594
582
 
595
- // Update the PropsDefinition to handle props as strings or objects
596
- export type PropsDefinition<P extends Props> = ExtractPropNames<P>;
583
+ type InferProps<T extends Record<string, any>> = {
584
+ [K in keyof T]: InferProp<T[K]>
585
+ }
597
586
 
598
- export type ComponentContext<P extends Props, S, M, C> = ThisType<PropsDefinition<P> & S & M & C & ComponentBase>
587
+ export type ComponentContext<
588
+ P extends Record<string, any>,
589
+ S,
590
+ M,
591
+ C
592
+ > = ThisType<Readonly<InferProps<P>> & S & M & Readonly<C> & ComponentBase>
599
593
 
600
- export interface ComponentConfig<P extends Props, S, M, C, W> {
594
+ export interface ComponentConfig<P extends Props = {}, S, M, C, W> {
601
595
  components?: {
602
596
  [key: string]: ComponentFactory,
603
597
  },
@@ -653,7 +647,7 @@ declare module '@lightningjs/blits' {
653
647
  * }
654
648
  * ```
655
649
  */
656
- state?: (this: PropsDefinition<P>) => S;
650
+ state?: (this: Readonly<InferProps<P>> & ComponentBase) => S;
657
651
  /**
658
652
  * Methods for abstracting more complex business logic into separate function
659
653
  */
@@ -942,7 +936,7 @@ declare module '@lightningjs/blits' {
942
936
 
943
937
  type Shader = {
944
938
  name: string,
945
- type: WebGlCoreShader
939
+ type: WebGlShaderType | CanvasShaderType
946
940
  }
947
941
 
948
942
  type ScreenResolutions = 'hd' | '720p' | 720 | 'fhd' | 'fullhd' | '1080p' | 1080 | '4k' | '2160p' | 2160
@@ -978,10 +972,6 @@ declare module '@lightningjs/blits' {
978
972
  * Fonts to be used in the Application
979
973
  */
980
974
  fonts?: Font[],
981
- /**
982
- * Effects to be used by DynamicShader
983
- */
984
- effects?: ShaderEffect[],
985
975
  /**
986
976
  * Shaders to be used in the application
987
977
  */
@@ -1114,18 +1104,6 @@ declare module '@lightningjs/blits' {
1114
1104
  * Defaults to `0`
1115
1105
  */
1116
1106
  viewportMargin?: number | [number, number, number, number],
1117
- /**
1118
- * Threshold in `Megabytes` after which all the textures that are currently not visible
1119
- * within the configured viewport margin will be be freed and cleaned up
1120
- *
1121
- * When passed `0` the threshold is disabled and textures will not be actively freed
1122
- * and cleaned up
1123
- *
1124
- * Defaults to `200` (mb)
1125
- * @deprecated
1126
- * Deprecated: use `gpuMemory` launch setting instead
1127
- */
1128
- gpuMemoryLimit?: number,
1129
1107
  /**
1130
1108
  * Configures the gpu memory settings used by the renderer
1131
1109
  */
@@ -1280,7 +1258,14 @@ declare module '@lightningjs/blits' {
1280
1258
  *
1281
1259
  * Defaults to `0` which means no maximum
1282
1260
  */
1283
- maxFPS?: number
1261
+ maxFPS?: number,
1262
+ /**
1263
+ * Custom platform layer to be used by the App (experimental)
1264
+ *
1265
+ * Defaults to `null` which will be resolved internally to WebPlatform
1266
+ */
1267
+ platform?: any
1268
+
1284
1269
  }
1285
1270
 
1286
1271
  interface State {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightningjs/blits",
3
- "version": "1.51.0",
3
+ "version": "2.0.0-beta.10",
4
4
  "description": "Blits: The Lightning 3 App Development Framework",
5
5
  "bin": "bin/index.js",
6
6
  "exports": {
@@ -33,9 +33,9 @@
33
33
  "./components": "./src/components/FPScounter.js"
34
34
  },
35
35
  "scripts": {
36
- "test": "c8 node scripts/runTests.js",
37
- "test:ci": "c8 npm run test:run",
38
- "test:run": "node -r global-jsdom/register ./node_modules/.bin/tape '{,!(node_modules|packages)/**/}*.test.js' | tap-diff",
36
+ "test": "c8 npm run test:run | tap-diff",
37
+ "test:run": "node -r global-jsdom/register node_modules/tape/bin/tape \"scripts/**/*.test.js\" \"src/**/*.test.js\"",
38
+ "test:run:failures": "node -r global-jsdom/register node_modules/tape/bin/tape \"scripts/**/*.test.js\" \"src/**/*.test.js\" | tap-filter test-fail | tap-diff",
39
39
  "lint": "node scripts/lint.js",
40
40
  "lint:fix": "node scripts/lint.js fix",
41
41
  "prepublishOnly": "node scripts/prepublishOnly.js",
@@ -71,11 +71,11 @@
71
71
  "prettier": "^3.6.2",
72
72
  "sinon": "^21.0.0",
73
73
  "tap-diff": "^0.1.1",
74
+ "tap-filter": "^1.0.3",
74
75
  "tape": "^5.5.0"
75
76
  },
76
77
  "dependencies": {
77
- "@lightningjs/msdf-generator": "^1.2.0",
78
- "@lightningjs/renderer": "^2.23.0",
78
+ "@lightningjs/renderer": "3.0.0-beta23",
79
79
  "magic-string": "^0.30.21"
80
80
  },
81
81
  "repository": {
package/scripts/lint.js CHANGED
@@ -1,3 +1,20 @@
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
+
1
18
  import fg from 'fast-glob'
2
19
  import { resolve } from 'path'
3
20
  import { fileURLToPath } from 'url'
@@ -62,8 +62,8 @@ function processFile(filePath) {
62
62
  const newSource = typeof result === 'object' && result.code ? result.code : result
63
63
  fs.writeFileSync(filePath, newSource)
64
64
 
65
- // only format the file if it was changed
66
- if (source !== newSource) {
65
+ // only format the file if it was changed (compiler returns undefined for non-Blits files)
66
+ if (newSource !== undefined && source !== newSource) {
67
67
  formatFileWithESLint(filePath)
68
68
  }
69
69
  }
@@ -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 2023 Comcast
6
- *
7
- * Licensed under the Apache License, Version 2.0 (the License);
2
+ * Copyright 2025 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
  import { execSync } from 'child_process'
@@ -25,26 +25,14 @@ import { renderer } from '../../launch.js'
25
25
  import { keyMap } from '../../application.js'
26
26
 
27
27
  export default {
28
- focus: {
29
- /**
30
- * @this {import('../../component').BlitsComponent}
31
- */
32
- value: function (e) {
33
- Log.warn('this.focus is deprecated, use this.$focus instead')
34
- return this.$focus(e)
35
- },
36
- writable: false,
37
- enumerable: true,
38
- configurable: false,
39
- },
40
28
  $focus: {
41
29
  /**
42
30
  * @this {import('../../component').BlitsComponent}
43
31
  */
44
32
  value: function (e) {
45
33
  // force refocus when the component is already in focused state
46
- if (this.lifecycle.state === 'focus') {
47
- this.lifecycle.state = 'refocus'
34
+ if (this[symbols.lifecycle].state === 'focus') {
35
+ this[symbols.lifecycle].state = 'refocus'
48
36
  }
49
37
  Focus.set(this, e)
50
38
  },
@@ -92,8 +80,8 @@ export default {
92
80
  * @this {import('../../component').BlitsComponent}
93
81
  */
94
82
  value: function () {
95
- this[symbols.state].hasFocus = false
96
- this.lifecycle.state = 'unfocus'
83
+ this[symbols.state].$hasFocus = false
84
+ this[symbols.lifecycle].state = 'unfocus'
97
85
  },
98
86
  writable: false,
99
87
  enumerable: true,
@@ -105,11 +93,11 @@ export default {
105
93
  */
106
94
  value: function () {
107
95
  this.eol = true
108
- this.lifecycle.state = 'destroy'
96
+ this[symbols.lifecycle].state = 'destroy'
109
97
 
110
98
  // when destroying a component that currently has focus
111
99
  // pass focus to the parent so we don't get lost in focus limbo
112
- if (this.hasFocus === true) this.parent.$focus()
100
+ if (this.$hasFocus === true) this[symbols.parent].$focus()
113
101
 
114
102
  // @todo - is this really necessary?
115
103
  // This cause an issue with auto sizing of parent (and required an extra eol check there)
@@ -142,26 +130,25 @@ export default {
142
130
  this[symbols.state] = {}
143
131
 
144
132
  this[symbols.props] = {}
145
- this[symbols.computed] = null
146
- this.lifecycle = {}
133
+ this[symbols.computedKeys] = null
134
+ this[symbols.lifecycle] = {}
147
135
  this[symbols.effects].length = 0
148
- this.parent = null
149
- this.rootParent = null
136
+ this[symbols.parent] = null
137
+ this[symbols.rootParent] = null
150
138
  this[symbols.wrapper] = null
151
139
  this[symbols.originalState] = null
152
140
  this[symbols.slots].length = 0
153
141
 
154
- delete this[symbols.computed]
155
- delete this.parent
156
- delete this.rootParent
142
+ delete this[symbols.computedKeys]
143
+ delete this[symbols.parent]
144
+ delete this[symbols.rootParent]
157
145
  delete this[symbols.wrapper]
158
146
  delete this[symbols.originalState]
159
147
  delete this[symbols.children]
160
148
  delete this[symbols.slots]
161
- delete this.componentId
162
149
  delete this[symbols.id]
163
150
  delete this.ref
164
- delete this[symbols.state].hasFocus
151
+ delete this[symbols.state].$hasFocus
165
152
 
166
153
  this[symbols.holder].destroy()
167
154
  this[symbols.holder] = null
@@ -172,7 +159,8 @@ export default {
172
159
 
173
160
  delete this[symbols.effects]
174
161
 
175
- Log.debug(`Destroyed component ${this.componentId}`)
162
+ Log.debug(`Destroyed component ${this.$componentId}`)
163
+ delete this.$componentId
176
164
  },
177
165
  writable: false,
178
166
  enumerable: true,
@@ -189,18 +177,6 @@ export default {
189
177
  enumerable: false,
190
178
  configurable: false,
191
179
  },
192
- select: {
193
- /**
194
- * @this {import('../../component').BlitsComponent}
195
- */
196
- value: function (ref) {
197
- Log.warn('this.select is deprecated, use this.$select instead')
198
- return this.$select(ref)
199
- },
200
- writable: false,
201
- enumerable: true,
202
- configurable: false,
203
- },
204
180
  $select: {
205
181
  /**
206
182
  * @this {import('../../component').BlitsComponent}
@@ -229,18 +205,6 @@ export default {
229
205
  enumerable: true,
230
206
  configurable: false,
231
207
  },
232
- trigger: {
233
- /**
234
- * @this {import('../../component').BlitsComponent}
235
- */
236
- value: function (key) {
237
- Log.warn('this.trigger is deprecated, use this.$trigger instead')
238
- return this.$trigger(key)
239
- },
240
- writable: false,
241
- enumerable: true,
242
- configurable: false,
243
- },
244
208
  $trigger: {
245
209
  /**
246
210
  * @this {import('../../component').BlitsComponent}
@@ -262,30 +226,6 @@ export default {
262
226
  enumerable: true,
263
227
  configurable: false,
264
228
  },
265
- shader: {
266
- /**
267
- * @this {import('../../component').BlitsComponent}
268
- */
269
- value: function (type, args) {
270
- return {
271
- type: type,
272
- props: args,
273
- }
274
- // const shaders = renderer.driver.stage.shManager.getRegisteredEffects()
275
-
276
- // if (target in shaders) {
277
- // return {
278
- // type: target,
279
- // props: args,
280
- // }
281
- // } else {
282
- // Log.error(`Shader ${type} not found`)
283
- // }
284
- },
285
- writable: false,
286
- enumerable: true,
287
- configurable: false,
288
- },
289
229
  }
290
230
 
291
231
  /**
@@ -29,6 +29,20 @@ export default {
29
29
  enumerable: true,
30
30
  configurable: false,
31
31
  },
32
+ $parent: {
33
+ get: function () {
34
+ return this[symbols.parent]
35
+ },
36
+ enumerable: true,
37
+ configurable: false,
38
+ },
39
+ $id: {
40
+ get: function () {
41
+ return this[symbols.id]
42
+ },
43
+ enumerable: true,
44
+ configurable: false,
45
+ },
32
46
  [symbols.renderer]: {
33
47
  value: () => renderer,
34
48
  writable: false,
@@ -37,7 +51,7 @@ export default {
37
51
  },
38
52
  [symbols.getChildren]: {
39
53
  value() {
40
- const parent = this.rootParent || this.parent
54
+ const parent = this[symbols.rootParent] || this[symbols.parent]
41
55
  return (this[symbols.children] || []).concat(
42
56
  (parent &&
43
57
  parent[symbols.getChildren]()
@@ -46,7 +60,7 @@ export default {
46
60
  return Object.values(child).map((c) => {
47
61
  // ugly hack .. but the point is to reference the right component
48
62
  c.forComponent =
49
- c[Symbol.for('config')] && c[Symbol.for('config')].parent.component
63
+ c[Symbol.for('config')] && c[Symbol.for('config')][symbols.parent].component
50
64
  return c
51
65
  })
52
66
  }
@@ -57,8 +71,8 @@ export default {
57
71
  // problem is that component of a forloop in a slot has component of root component
58
72
  if (child && child.component) {
59
73
  return (
60
- (child.component && child.component.componentId === this.componentId) ||
61
- (child.forComponent && child.forComponent.componentId === this.componentId)
74
+ (child.component && child.component.$componentId === this.$componentId) ||
75
+ (child.forComponent && child.forComponent.$componentId === this.$componentId)
62
76
  )
63
77
  }
64
78
  })) ||
@@ -16,46 +16,55 @@
16
16
  */
17
17
 
18
18
  import { Log } from '../../lib/log.js'
19
-
20
19
  import symbols from '../../lib/symbols.js'
21
20
 
22
- const baseProp = {
23
- cast: (v) => v,
24
- required: false,
21
+ const normalizeProps = (props) => {
22
+ Log.warn(
23
+ 'Defining props as an Array has been deprecated and will stop working in future versions. Please use the new notation instead (an object with key values pairs).',
24
+ props
25
+ )
26
+ const out = {}
27
+ const propLength = props.length
28
+ for (let i = 0; i < propLength; i++) {
29
+ const prop = props[i]
30
+ if (typeof prop === 'string') {
31
+ out[prop] = undefined
32
+ } else {
33
+ Log.error(
34
+ 'Defining props as an array of objects is no longer supported. Please use the new format: an object with key-value pairs.'
35
+ )
36
+ break
37
+ }
38
+ }
39
+ return out
25
40
  }
26
41
 
27
- export default (component, props = []) => {
28
- if (props.indexOf('ref') === -1) {
29
- props.push('ref')
42
+ export default (component, props = {}) => {
43
+ if (Array.isArray(props) === true) {
44
+ props = normalizeProps(props)
45
+ }
46
+ if (!('ref' in props)) {
47
+ props.ref = undefined
30
48
  }
31
- component[symbols.propKeys] = []
49
+ const keys = Object.keys(props)
50
+ component[symbols.propKeys] = keys
32
51
 
33
- const propsLength = props.length
52
+ for (let i = 0; i < keys.length; i++) {
53
+ const key = keys[i]
54
+ const defaultPropValue = props[key]
34
55
 
35
- for (let i = 0; i < propsLength; i++) {
36
- const prop = { ...baseProp, ...(typeof props[i] === 'object' ? props[i] : { key: props[i] }) }
37
- component[symbols.propKeys].push(prop.key)
38
- Object.defineProperty(component, prop.key, {
56
+ Object.defineProperty(component, key, {
39
57
  get() {
40
- const value = prop.cast(
41
- this[symbols.props] !== undefined && prop.key in this[symbols.props]
42
- ? this[symbols.props][prop.key]
43
- : 'default' in prop
44
- ? prop.default
45
- : undefined
46
- )
47
-
48
- if (prop.required === true && value === undefined) {
49
- Log.warn(`${prop.key} is required`)
50
- }
51
-
52
- return value
58
+ if (this[symbols.props] === undefined) return undefined
59
+ // if the key is specified independent of the value (falsy, null, undefined) use that value
60
+ // otherwise return the default prop value
61
+ return key in this[symbols.props] ? this[symbols.props][key] : defaultPropValue
53
62
  },
54
63
  set(v) {
55
64
  Log.warn(
56
- `Warning! Avoid mutating props directly (prop "${prop.key}" in component "${this.componentId}")`
65
+ `Warning! Avoid mutating props directly (prop "${key}" in component "${this.componentId}")`
57
66
  )
58
- this[symbols.props][prop.key] = v
67
+ this[symbols.props][key] = v
59
68
  },
60
69
  })
61
70
  }
@@ -28,16 +28,16 @@ export default (component, state = () => {}) => {
28
28
  })
29
29
 
30
30
  const stateKeys = Object.keys(state.apply(component) || {})
31
- if (stateKeys.indexOf('hasFocus') > -1) {
31
+ if (stateKeys.indexOf('$hasFocus') > -1) {
32
32
  Log.warn(
33
- 'State `hasFocus` already exists as a built-in Component variable (to indicate whether the component currently has focus). Avoid using your own `hasFocus` key in the Component state'
33
+ 'State `$hasFocus` already exists as a built-in Component variable (to indicate whether the component currently has focus). Avoid using your own `$hasFocus` key in the Component state'
34
34
  )
35
35
  } else {
36
- // add built-in hasFocus key
37
- stateKeys.push(['hasFocus'])
36
+ // add built-in $hasFocus key
37
+ stateKeys.push('$hasFocus')
38
38
  }
39
39
  // add built-in isHovered key
40
- stateKeys.push(['isHovered'])
40
+ stateKeys.push(['$isHovered'])
41
41
  const stateKeysLength = stateKeys.length
42
42
 
43
43
  for (let i = 0; i < stateKeysLength; i++) {