@lightningjs/blits 1.45.1 → 2.0.0-beta.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.
Files changed (38) hide show
  1. package/CHANGELOG.md +0 -17
  2. package/README.md +0 -2
  3. package/index.d.ts +23 -86
  4. package/package.json +5 -24
  5. package/scripts/prepublishOnly.js +1 -4
  6. package/src/component/base/methods.js +16 -76
  7. package/src/component/base/utils.js +18 -4
  8. package/src/component/setup/props.js +37 -28
  9. package/src/component/setup/state.js +4 -4
  10. package/src/component.js +25 -16
  11. package/src/components/Circle.js +49 -7
  12. package/src/components/FPScounter.js +226 -88
  13. package/src/components/RouterView.js +50 -5
  14. package/src/components/Sprite.js +61 -20
  15. package/src/engines/L3/element.js +109 -86
  16. package/src/engines/L3/fontLoader.js +26 -31
  17. package/src/engines/L3/shaderLoader.js +56 -6
  18. package/src/focus.js +9 -9
  19. package/src/launch.js +1 -2
  20. package/src/lib/codegenerator/generator.js +117 -16
  21. package/src/lib/colors/colors.js +0 -1
  22. package/src/lib/lifecycle.js +3 -3
  23. package/src/lib/precompiler/precompiler.js +14 -23
  24. package/src/lib/reactivityguard/computedprops.js +1 -6
  25. package/src/lib/shaders/shaders.js +163 -0
  26. package/src/lib/symbols.js +8 -0
  27. package/src/lib/templateparser/parser.js +6 -2
  28. package/src/lib/utils.js +36 -0
  29. package/src/router/router.js +7 -7
  30. package/vite/blitsTips.js +57 -0
  31. package/vite/msdfGenerator.js +52 -6
  32. package/vite/preCompiler.js +5 -15
  33. package/vscode/data/template-attributes.json +112 -0
  34. package/src/plugins/appstate.d.ts +0 -29
  35. package/src/plugins/index.d.ts +0 -29
  36. package/src/plugins/language.d.ts +0 -34
  37. package/src/plugins/storage/storage.d.ts +0 -29
  38. package/src/plugins/theme.d.ts +0 -35
package/CHANGELOG.md CHANGED
@@ -1,22 +1,5 @@
1
1
  # Changelog
2
2
 
3
- ## v1.45.1
4
-
5
- _29 dec 2025_
6
-
7
- - fixed issue `magic-string` package being a dev dependency
8
-
9
- ## v1.45.0
10
-
11
- _29 dec 2025_
12
-
13
- - Added support for defining types for custom plugins on the Blits component definitions
14
- - Added importable type definitions for built-in plugins
15
- - Added support for JS sourcemaps
16
- - Added reactivity for shader props (individual shaders, not effects via dynamic shader)
17
- - Fixed issue with usage of Blits plugins not working in for loops
18
- - Improved test coverage
19
-
20
3
  ## v1.44.0
21
4
 
22
5
  _20 Nov 2025_
package/README.md CHANGED
@@ -2,8 +2,6 @@
2
2
 
3
3
  # Blits - Lightning 3 App Development Framework
4
4
 
5
- ![npm](https://img.shields.io/npm/v/@lightningjs/blits?label=Version&color=blue) ![License](https://img.shields.io/github/license/lightning-js/blits?label=License&color=blue) ![Tests](https://github.com/lightning-js/blits/workflows/Tests/badge.svg) ![Coverage](https://img.shields.io/codecov/c/github/lightning-js/blits/master?label=Coverage) ![npm](https://img.shields.io/npm/dm/@lightningjs/blits?label=Downloads&color=blue)
6
-
7
5
  With Blits, the App Development Framework for Lightning 3, it becomes a breeze to build great Lightning 3 applications.
8
6
 
9
7
  Blits is built on top of the Lightning 3 Rendererer and aims to provide a **great developer experience**, making it fun and easy to build your apps!
package/index.d.ts CHANGED
@@ -18,10 +18,12 @@
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
- type RendererShaderEffect = import('@lightningjs/renderer').ShaderEffect
23
- type WebGlCoreShader = import('@lightningjs/renderer').WebGlCoreShader
24
- type RendererMainSettings = import('@lightningjs/renderer').RendererMainSettings
26
+
25
27
 
26
28
  export interface AnnouncerUtteranceOptions {
27
29
  /**
@@ -209,7 +211,7 @@ declare module '@lightningjs/blits' {
209
211
  }
210
212
 
211
213
  export interface Input {
212
- [key: string]: ((event: KeyboardEvent) => unknown) | undefined,
214
+ [key: string]: (event: KeyboardEvent) => void | undefined | unknown,
213
215
  /**
214
216
  * Catch all input function
215
217
  *
@@ -362,45 +364,13 @@ declare module '@lightningjs/blits' {
362
364
  }
363
365
  }
364
366
 
365
- // Extension point for app- and plugin-specific fields on the component `this`.
366
- // Add your own properties (e.g., `$telemetry`, `componentName`) via TypeScript
367
- // module augmentation in your app, without changing core types.
368
- // Note: `ComponentBase` extends this interface, so augmented fields appear in all
369
- // hooks, methods, input, computed, and watch.
370
- export interface CustomComponentProperties {
371
- // Empty by design: extend in your app via TypeScript module augmentation.
372
- }
373
-
374
- export interface LanguagePlugin {
375
- translate(key: string, ...replacements: any[]): string
376
- readonly language: string
377
- set(language: string): void
378
- translations(translationsObject: Record<string, unknown>): void
379
- load(file: string): Promise<void>
380
- }
381
-
382
- export interface ThemePlugin {
383
- get<T = unknown>(key: string): T | undefined
384
- get<T>(key: string, fallback: T): T
385
- set(theme: string): void
386
- }
387
-
388
- export interface StoragePlugin {
389
- get<T = unknown>(key: string): T | null
390
- set(key: string, value: unknown): boolean
391
- remove(key: string): void
392
- clear(): void
393
- }
394
-
395
- export type AppStatePlugin<TState extends Record<string, unknown> = Record<string, unknown>> = TState
396
-
397
- export interface ComponentBase extends CustomComponentProperties {
367
+ export type ComponentBase = {
398
368
  /**
399
369
  * Indicates whether the component currently has focus
400
370
  *
401
371
  * @returns Boolean
402
372
  */
403
- hasFocus: boolean,
373
+ readonly $hasFocus: boolean,
404
374
 
405
375
  /**
406
376
  * Listen to events emitted by other components
@@ -469,12 +439,6 @@ declare module '@lightningjs/blits' {
469
439
  * @returns Returns true if this component or a parent component handled the event, false otherwise
470
440
  */
471
441
  $input: (event: KeyboardEvent) => boolean
472
- /**
473
- * @deprecated
474
- * Deprecated: use `this.$focus()` instead
475
- */
476
- focus: (event?: KeyboardEvent) => void
477
-
478
442
  /**
479
443
  * Select a child Element or Component by ref
480
444
  *
@@ -492,12 +456,6 @@ declare module '@lightningjs/blits' {
492
456
  */
493
457
  $select: (ref: string) => ComponentBase
494
458
 
495
- /**
496
- * @deprecated
497
- * Deprecated: use `this.$select()` instead
498
- */
499
- select: (ref: string) => ComponentBase
500
-
501
459
  /**
502
460
  * Announcer methods for screen reader support
503
461
  */
@@ -507,13 +465,6 @@ declare module '@lightningjs/blits' {
507
465
  * Triggers a forced update on state variables.
508
466
  */
509
467
  $trigger: (key: string) => void
510
- /**
511
- * @deprecated
512
- *
513
- * Triggers a forced update on state variables.
514
- * Deprecated: use `this.$trigger()` instead
515
- */
516
- trigger: (key: string) => void
517
468
  /**
518
469
  * Router instance
519
470
  */
@@ -563,20 +514,22 @@ declare module '@lightningjs/blits' {
563
514
  cast?: () => any
564
515
  };
565
516
 
566
- // Props Array
567
- export type Props = (string | PropObject)[];
517
+ export type Props = Record<string, any>
568
518
 
569
- // Extract the prop names from the props array
570
- type ExtractPropNames<P extends Props> = {
571
- readonly [K in P[number] as K extends string ? K : K extends { key: infer Key } ? Key : never]: any;
572
- };
519
+ type InferProp<T> = T extends (...args: any[]) => any ? ReturnType<T> : T
573
520
 
574
- // Update the PropsDefinition to handle props as strings or objects
575
- export type PropsDefinition<P extends Props> = ExtractPropNames<P>;
521
+ type InferProps<T extends Record<string, any>> = {
522
+ [K in keyof T]: InferProp<T[K]>
523
+ }
576
524
 
577
- export type ComponentContext<P extends Props, S, M, C> = ThisType<PropsDefinition<P> & S & M & C & ComponentBase>
525
+ export type ComponentContext<
526
+ P extends Record<string, any>,
527
+ S,
528
+ M,
529
+ C
530
+ > = ThisType<Readonly<InferProps<P>> & S & M & Readonly<C> & ComponentBase>
578
531
 
579
- export interface ComponentConfig<P extends Props, S, M, C, W> {
532
+ export interface ComponentConfig<P extends Props = {}, S, M, C, W> {
580
533
  components?: {
581
534
  [key: string]: ComponentFactory,
582
535
  },
@@ -632,7 +585,7 @@ declare module '@lightningjs/blits' {
632
585
  * }
633
586
  * ```
634
587
  */
635
- state?: (this: PropsDefinition<P>) => S;
588
+ state?: (this: InferProps<P>) => S;
636
589
  /**
637
590
  * Methods for abstracting more complex business logic into separate function
638
591
  */
@@ -656,7 +609,7 @@ declare module '@lightningjs/blits' {
656
609
  }
657
610
 
658
611
  export interface RouterHooks {
659
- init?: () => Promise<void> | void;
612
+ init?: () => Promise<> | void;
660
613
  beforeEach?: (to: Route, from: Route) => string | Route | Promise<string | Route> | void;
661
614
  error?: (err: string) => string | Route | Promise<string | Route> | void;
662
615
  }
@@ -900,7 +853,7 @@ declare module '@lightningjs/blits' {
900
853
 
901
854
  type Shader = {
902
855
  name: string,
903
- type: WebGlCoreShader
856
+ type: WebGlShaderType | CanvasShaderType
904
857
  }
905
858
 
906
859
  type ScreenResolutions = 'hd' | '720p' | 720 | 'fhd' | 'fullhd' | '1080p' | 1080 | '4k' | '2160p' | 2160
@@ -936,10 +889,6 @@ declare module '@lightningjs/blits' {
936
889
  * Fonts to be used in the Application
937
890
  */
938
891
  fonts?: Font[],
939
- /**
940
- * Effects to be used by DynamicShader
941
- */
942
- effects?: ShaderEffect[],
943
892
  /**
944
893
  * Shaders to be used in the application
945
894
  */
@@ -1072,18 +1021,6 @@ declare module '@lightningjs/blits' {
1072
1021
  * Defaults to `0`
1073
1022
  */
1074
1023
  viewportMargin?: number | [number, number, number, number],
1075
- /**
1076
- * Threshold in `Megabytes` after which all the textures that are currently not visible
1077
- * within the configured viewport margin will be be freed and cleaned up
1078
- *
1079
- * When passed `0` the threshold is disabled and textures will not be actively freed
1080
- * and cleaned up
1081
- *
1082
- * Defaults to `200` (mb)
1083
- * @deprecated
1084
- * Deprecated: use `gpuMemory` launch setting instead
1085
- */
1086
- gpuMemoryLimit?: number,
1087
1024
  /**
1088
1025
  * Configures the gpu memory settings used by the renderer
1089
1026
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightningjs/blits",
3
- "version": "1.45.1",
3
+ "version": "2.0.0-beta.1",
4
4
  "description": "Blits: The Lightning 3 App Development Framework",
5
5
  "bin": "bin/index.js",
6
6
  "exports": {
@@ -8,26 +8,7 @@
8
8
  "./vite": "./vite/index.js",
9
9
  "./transitions": "./src/router/transitions/index.js",
10
10
  "./precompiler": "./src/lib/precompiler/precompiler.js",
11
- "./plugins": {
12
- "types": "./src/plugins/index.d.ts",
13
- "default": "./src/plugins/index.js"
14
- },
15
- "./plugins/language": {
16
- "types": "./src/plugins/language.d.ts",
17
- "default": "./src/plugins/language.js"
18
- },
19
- "./plugins/theme": {
20
- "types": "./src/plugins/theme.d.ts",
21
- "default": "./src/plugins/theme.js"
22
- },
23
- "./plugins/appstate": {
24
- "types": "./src/plugins/appstate.d.ts",
25
- "default": "./src/plugins/appstate.js"
26
- },
27
- "./plugins/storage": {
28
- "types": "./src/plugins/storage/storage.d.ts",
29
- "default": "./src/plugins/storage/storage.js"
30
- },
11
+ "./plugins": "./src/plugins/index.js",
31
12
  "./symbols": "./src/lib/symbols.js",
32
13
  "./blitsFileConverter": "./src/lib/blitsfileconverter/blitsfileconverter.js"
33
14
  },
@@ -35,6 +16,7 @@
35
16
  "test": "c8 node scripts/runTests.js",
36
17
  "test:ci": "c8 npm run test:run",
37
18
  "test:run": "node -r global-jsdom/register ./node_modules/.bin/tape '{,!(node_modules|packages)/**/}*.test.js' | tap-diff",
19
+ "test:run:failures": "node -r global-jsdom/register ./node_modules/.bin/tape '{,!(node_modules|packages)/**/}*.test.js' | tap-filter test-fail | tap-diff",
38
20
  "lint": "node scripts/lint.js",
39
21
  "lint:fix": "node scripts/lint.js fix",
40
22
  "prepublishOnly": "node scripts/prepublishOnly.js",
@@ -70,12 +52,11 @@
70
52
  "prettier": "^3.6.2",
71
53
  "sinon": "^21.0.0",
72
54
  "tap-diff": "^0.1.1",
55
+ "tap-filter": "^1.0.3",
73
56
  "tape": "^5.5.0"
74
57
  },
75
58
  "dependencies": {
76
- "@lightningjs/msdf-generator": "^1.2.0",
77
- "@lightningjs/renderer": "^2.20.0",
78
- "magic-string": "^0.30.21"
59
+ "@lightningjs/renderer": "3.0.0-beta18"
79
60
  },
80
61
  "repository": {
81
62
  "type": "git",
@@ -55,10 +55,7 @@ function processFile(filePath) {
55
55
  fs.copyFileSync(filePath, backupFilePath)
56
56
 
57
57
  const source = fs.readFileSync(filePath, 'utf-8')
58
- const result = compiler(source, filePath)
59
-
60
- // Handle both string and object return types
61
- const newSource = typeof result === 'object' && result.code ? result.code : result
58
+ const newSource = compiler(source, filePath)
62
59
  fs.writeFileSync(filePath, newSource)
63
60
 
64
61
  // only format the file if it was changed
@@ -26,26 +26,14 @@ import { renderer } from '../../launch.js'
26
26
  import { keyMap } from '../../application.js'
27
27
 
28
28
  export default {
29
- focus: {
30
- /**
31
- * @this {import('../../component').BlitsComponent}
32
- */
33
- value: function (e) {
34
- Log.warn('this.focus is deprecated, use this.$focus instead')
35
- return this.$focus(e)
36
- },
37
- writable: false,
38
- enumerable: true,
39
- configurable: false,
40
- },
41
29
  $focus: {
42
30
  /**
43
31
  * @this {import('../../component').BlitsComponent}
44
32
  */
45
33
  value: function (e) {
46
34
  // force refocus when the component is already in focused state
47
- if (this.lifecycle.state === 'focus') {
48
- this.lifecycle.state = 'refocus'
35
+ if (this[symbols.lifecycle].state === 'focus') {
36
+ this[symbols.lifecycle].state = 'refocus'
49
37
  }
50
38
  Focus.set(this, e)
51
39
  },
@@ -93,8 +81,8 @@ export default {
93
81
  * @this {import('../../component').BlitsComponent}
94
82
  */
95
83
  value: function () {
96
- this[symbols.state].hasFocus = false
97
- this.lifecycle.state = 'unfocus'
84
+ this[symbols.state].$hasFocus = false
85
+ this[symbols.lifecycle].state = 'unfocus'
98
86
  },
99
87
  writable: false,
100
88
  enumerable: true,
@@ -106,11 +94,11 @@ export default {
106
94
  */
107
95
  value: function () {
108
96
  this.eol = true
109
- this.lifecycle.state = 'destroy'
97
+ this[symbols.lifecycle].state = 'destroy'
110
98
 
111
99
  // when destroying a component that currently has focus
112
100
  // pass focus to the parent so we don't get lost in focus limbo
113
- if (this.hasFocus === true) this.parent.$focus()
101
+ if (this.$hasFocus === true) this[symbols.parent].$focus()
114
102
 
115
103
  for (let key in this[symbols.state]) {
116
104
  if (Array.isArray(this[symbols.state][key])) {
@@ -139,26 +127,25 @@ export default {
139
127
  this[symbols.state] = {}
140
128
 
141
129
  this[symbols.props] = {}
142
- this[symbols.computed] = null
143
- this.lifecycle = {}
130
+ this[symbols.computedKeys] = null
131
+ this[symbols.lifecycle] = {}
144
132
  this[symbols.effects].length = 0
145
- this.parent = null
146
- this.rootParent = null
133
+ this[symbols.parent] = null
134
+ this[symbols.rootParent] = null
147
135
  this[symbols.wrapper] = null
148
136
  this[symbols.originalState] = null
149
137
  this[symbols.slots].length = 0
150
138
 
151
- delete this[symbols.computed]
152
- delete this.parent
153
- delete this.rootParent
139
+ delete this[symbols.computedKeys]
140
+ delete this[symbols.parent]
141
+ delete this[symbols.rootParent]
154
142
  delete this[symbols.wrapper]
155
143
  delete this[symbols.originalState]
156
144
  delete this[symbols.children]
157
145
  delete this[symbols.slots]
158
- delete this.componentId
159
146
  delete this[symbols.id]
160
147
  delete this.ref
161
- delete this[symbols.state].hasFocus
148
+ delete this[symbols.state].$hasFocus
162
149
 
163
150
  this[symbols.holder].destroy()
164
151
  this[symbols.holder] = null
@@ -169,7 +156,8 @@ export default {
169
156
 
170
157
  delete this[symbols.effects]
171
158
 
172
- Log.debug(`Destroyed component ${this.componentId}`)
159
+ Log.debug(`Destroyed component ${this.$componentId}`)
160
+ delete this.$componentId
173
161
  },
174
162
  writable: false,
175
163
  enumerable: true,
@@ -186,18 +174,6 @@ export default {
186
174
  enumerable: false,
187
175
  configurable: false,
188
176
  },
189
- select: {
190
- /**
191
- * @this {import('../../component').BlitsComponent}
192
- */
193
- value: function (ref) {
194
- Log.warn('this.select is deprecated, use this.$select instead')
195
- return this.$select(ref)
196
- },
197
- writable: false,
198
- enumerable: true,
199
- configurable: false,
200
- },
201
177
  $select: {
202
178
  /**
203
179
  * @this {import('../../component').BlitsComponent}
@@ -226,18 +202,6 @@ export default {
226
202
  enumerable: true,
227
203
  configurable: false,
228
204
  },
229
- trigger: {
230
- /**
231
- * @this {import('../../component').BlitsComponent}
232
- */
233
- value: function (key) {
234
- Log.warn('this.trigger is deprecated, use this.$trigger instead')
235
- return this.$trigger(key)
236
- },
237
- writable: false,
238
- enumerable: true,
239
- configurable: false,
240
- },
241
205
  $trigger: {
242
206
  /**
243
207
  * @this {import('../../component').BlitsComponent}
@@ -259,30 +223,6 @@ export default {
259
223
  enumerable: true,
260
224
  configurable: false,
261
225
  },
262
- shader: {
263
- /**
264
- * @this {import('../../component').BlitsComponent}
265
- */
266
- value: function (type, args) {
267
- return {
268
- type: type,
269
- props: args,
270
- }
271
- // const shaders = renderer.driver.stage.shManager.getRegisteredEffects()
272
-
273
- // if (target in shaders) {
274
- // return {
275
- // type: target,
276
- // props: args,
277
- // }
278
- // } else {
279
- // Log.error(`Shader ${type} not found`)
280
- // }
281
- },
282
- writable: false,
283
- enumerable: true,
284
- configurable: false,
285
- },
286
226
  }
287
227
 
288
228
  /**
@@ -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,13 +28,13 @@ 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
  const stateKeysLength = stateKeys.length
40
40