@lightningjs/blits 2.0.0-beta.1 → 2.0.0-beta.2

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 CHANGED
@@ -1,5 +1,62 @@
1
1
  # Changelog
2
2
 
3
+
4
+ ## v1.48.0
5
+
6
+ _10 feb 2026_
7
+
8
+ - Improved cleanup of for-loop effects
9
+ - Fixed router issue with params not being reset
10
+ - Added next tick debouncing for transitions on multiple props
11
+ - Bumped renderer to 2.21.0
12
+ - Added support for using function-notation in `@loaded`, `@error` and `@updated` callbacks
13
+ - Added displaying of correct component type in inspector data when using `is`-attribute
14
+ - Replaced `$`-prefix for automatic inspector data with `blits-` prefix (fixes fatal error in Safari)
15
+
16
+
17
+ ## v1.47.0
18
+
19
+ _26 jan 2026_
20
+
21
+ - Fixed issue with reactivity guard vite plugin failing for commented-out code
22
+ - Fixed issue with autosizing of holder node by adding extra `eol` check
23
+ - Added support for _variants_ to the Theme plugin
24
+
25
+ ## v1.46.0
26
+
27
+ _22 jan 2026_
28
+
29
+ - Improved Announcer on Comcast devices
30
+ - Added new `afterEach` router hook
31
+ - Added router option to dynamically disable `back`-key handling by the router
32
+ - Added `this.$debounce`-method
33
+ - Added automatic inspector data items (`$componentType`, `$hasFocus` and `$isTransitioning`)
34
+ - Added functionality to auto size the holder node of a Component when the wrapper Element has dimensions
35
+ - Bumped Lightning renderer to v2.20.4 with correct handling of failed textures
36
+
37
+ ## v1.45.2
38
+
39
+ _13 jan 2026_
40
+
41
+ - Updated renderer to v2.20.2
42
+
43
+ ## v1.45.1
44
+
45
+ _29 dec 2025_
46
+
47
+ - fixed issue `magic-string` package being a dev dependency
48
+
49
+ ## v1.45.0
50
+
51
+ _29 dec 2025_
52
+
53
+ - Added support for defining types for custom plugins on the Blits component definitions
54
+ - Added importable type definitions for built-in plugins
55
+ - Added support for JS sourcemaps
56
+ - Added reactivity for shader props (individual shaders, not effects via dynamic shader)
57
+ - Fixed issue with usage of Blits plugins not working in for loops
58
+ - Improved test coverage
59
+
3
60
  ## v1.44.0
4
61
 
5
62
  _20 Nov 2025_
package/README.md CHANGED
@@ -2,6 +2,8 @@
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
+
5
7
  With Blits, the App Development Framework for Lightning 3, it becomes a breeze to build great Lightning 3 applications.
6
8
 
7
9
  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!
Binary file
package/index.d.ts CHANGED
@@ -23,7 +23,9 @@ import { CanvasShaderType } from '@lightningjs/renderer/canvas';
23
23
  import { WebGlShaderType } from '@lightningjs/renderer/webgl';
24
24
 
25
25
  declare module '@lightningjs/blits' {
26
-
26
+ type RendererShaderEffect = import('@lightningjs/renderer').ShaderEffect
27
+ type WebGlCoreShader = import('@lightningjs/renderer').WebGlCoreShader
28
+ type RendererMainSettings = import('@lightningjs/renderer').RendererMainSettings
27
29
 
28
30
  export interface AnnouncerUtteranceOptions {
29
31
  /**
@@ -56,6 +58,12 @@ declare module '@lightningjs/blits' {
56
58
  * @default 1
57
59
  */
58
60
  volume?: number,
61
+ /**
62
+ * Whether to enable utterance keep-alive (prevents pausing on some platforms)
63
+ *
64
+ * @default undefined
65
+ */
66
+ enableUtteranceKeepAlive?: boolean
59
67
  }
60
68
 
61
69
  export interface AnnouncerUtterance<T = any> extends Promise<T> {
@@ -211,7 +219,7 @@ declare module '@lightningjs/blits' {
211
219
  }
212
220
 
213
221
  export interface Input {
214
- [key: string]: (event: KeyboardEvent) => void | undefined | unknown,
222
+ [key: string]: ((event: KeyboardEvent) => unknown) | undefined,
215
223
  /**
216
224
  * Catch all input function
217
225
  *
@@ -323,6 +331,11 @@ declare module '@lightningjs/blits' {
323
331
  */
324
332
  back(): boolean;
325
333
 
334
+ /**
335
+ * Enable or disable RouterView history navigation on Back input
336
+ */
337
+ backNavigation: boolean;
338
+
326
339
  /**
327
340
  * Get the current route read-only
328
341
  */
@@ -330,17 +343,17 @@ declare module '@lightningjs/blits' {
330
343
 
331
344
  /**
332
345
  * Get the list of all routes
333
- */
346
+ */
334
347
  readonly routes: Route[];
335
348
 
336
349
  /**
337
350
  * Get navigating state
338
- */
351
+ */
339
352
  readonly navigating: boolean;
340
353
 
341
354
  /**
342
355
  * Reactive router state
343
- */
356
+ */
344
357
  state: {
345
358
  /**
346
359
  * Path of the current route
@@ -364,7 +377,17 @@ declare module '@lightningjs/blits' {
364
377
  }
365
378
  }
366
379
 
367
- export type ComponentBase = {
380
+ // Extension point for app- and plugin-specific fields on the component `this`.
381
+ // Add your own properties (e.g., `$telemetry`, `componentName`) via TypeScript
382
+ // module augmentation in your app, without changing core types.
383
+ // Note: `ComponentBase` extends this interface, so augmented fields appear in all
384
+ // hooks, methods, input, computed, and watch.
385
+ export interface CustomComponentProperties {
386
+ // Empty by design: extend in your app via TypeScript module augmentation.
387
+ }
388
+
389
+
390
+ export interface ComponentBase extends CustomComponentProperties {
368
391
  /**
369
392
  * Indicates whether the component currently has focus
370
393
  *
@@ -414,6 +437,26 @@ declare module '@lightningjs/blits' {
414
437
  */
415
438
  $clearTimeout: (id: ReturnType<typeof setTimeout>) => void
416
439
 
440
+ /**
441
+ * Debounce a function execution, preventing memory leaks and function re-allocation
442
+ * @param name - Unique identifier for this debounce instance (unique per component instance)
443
+ * @param callback - Function to debounce
444
+ * @param ms - Delay in milliseconds
445
+ * @param args - Arguments to pass to the callback
446
+ */
447
+ $debounce: (name: string, callback: (...args: any[]) => void, ms?: number, ...args: any[]) => ReturnType<typeof setTimeout>
448
+
449
+ /**
450
+ * Clear a specific debounce by name
451
+ * @param name - The name of the debounce to clear
452
+ */
453
+ $clearDebounce: (name: string) => void
454
+
455
+ /**
456
+ * Clear all debounces registered on the component (automatically called on component destroy)
457
+ */
458
+ $clearDebounces: () => void
459
+
417
460
  /**
418
461
  * Set an interval that is automatically cleaned upon component destroy
419
462
  */
@@ -609,8 +652,9 @@ declare module '@lightningjs/blits' {
609
652
  }
610
653
 
611
654
  export interface RouterHooks {
612
- init?: () => Promise<> | void;
655
+ init?: () => Promise<void> | void;
613
656
  beforeEach?: (to: Route, from: Route) => string | Route | Promise<string | Route> | void;
657
+ afterEach?: (to: Route, from: Route) => string | Route | Promise<string | Route> | void;
614
658
  error?: (err: string) => string | Route | Promise<string | Route> | void;
615
659
  }
616
660
 
@@ -634,6 +678,25 @@ declare module '@lightningjs/blits' {
634
678
  * ```
635
679
  */
636
680
  routes?: Route[]
681
+
682
+ /**
683
+ * Enable or disable RouterView history navigation on Back input
684
+ *
685
+ * @default true
686
+ *
687
+ * @remarks
688
+ * This is an app-wide setting that affects all RouterView instances in your application.
689
+ * The router state is global and shared across all router instances.
690
+ *
691
+ * @example
692
+ * ```js
693
+ * router: {
694
+ * backNavigation: false, // Disable automatic back navigation
695
+ * routes: [...]
696
+ * }
697
+ * ```
698
+ */
699
+ backNavigation?: boolean
637
700
  }
638
701
 
639
702
  export type ApplicationConfig<P extends Props, S, M, C, W> = ComponentConfig<P, S, M, C, W> & (
@@ -734,6 +797,7 @@ declare module '@lightningjs/blits' {
734
797
 
735
798
  export interface RouteHooks {
736
799
  before?: (to: Route, from: Route) => string | Route | Promise<string | Route>;
800
+ after?: (to: Route, from: Route) => string | Route | Promise<string | Route>;
737
801
  }
738
802
 
739
803
  export type Route = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightningjs/blits",
3
- "version": "2.0.0-beta.1",
3
+ "version": "2.0.0-beta.2",
4
4
  "description": "Blits: The Lightning 3 App Development Framework",
5
5
  "bin": "bin/index.js",
6
6
  "exports": {
@@ -8,7 +8,26 @@
8
8
  "./vite": "./vite/index.js",
9
9
  "./transitions": "./src/router/transitions/index.js",
10
10
  "./precompiler": "./src/lib/precompiler/precompiler.js",
11
- "./plugins": "./src/plugins/index.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
+ },
12
31
  "./symbols": "./src/lib/symbols.js",
13
32
  "./blitsFileConverter": "./src/lib/blitsfileconverter/blitsfileconverter.js"
14
33
  },
@@ -56,7 +75,8 @@
56
75
  "tape": "^5.5.0"
57
76
  },
58
77
  "dependencies": {
59
- "@lightningjs/renderer": "3.0.0-beta18"
78
+ "@lightningjs/renderer": "3.0.0-beta21",
79
+ "magic-string": "^0.30.21"
60
80
  },
61
81
  "repository": {
62
82
  "type": "git",
@@ -17,6 +17,7 @@
17
17
 
18
18
  import fs from 'fs'
19
19
  import path from 'path'
20
+ import { fileURLToPath } from 'url'
20
21
  import compiler from '../src/lib/precompiler/precompiler.js'
21
22
  import { exec } from 'child_process'
22
23
 
@@ -55,7 +56,10 @@ function processFile(filePath) {
55
56
  fs.copyFileSync(filePath, backupFilePath)
56
57
 
57
58
  const source = fs.readFileSync(filePath, 'utf-8')
58
- const newSource = compiler(source, filePath)
59
+ const result = compiler(source, filePath)
60
+
61
+ // Handle both string and object return types
62
+ const newSource = typeof result === 'object' && result.code ? result.code : result
59
63
  fs.writeFileSync(filePath, newSource)
60
64
 
61
65
  // only format the file if it was changed
@@ -81,4 +85,9 @@ function formatFileWithESLint(filePath) {
81
85
  })
82
86
  }
83
87
 
84
- precompileComponents()
88
+ // Only run if this file is executed directly (not imported)
89
+ if (fileURLToPath(import.meta.url) === process.argv[1]) {
90
+ precompileComponents()
91
+ }
92
+
93
+ export { precompileComponents, processDirectory, processFile, formatFileWithESLint }
@@ -69,7 +69,6 @@ try {
69
69
  // Run ESLint on the changed files
70
70
  execSync(`npx eslint ${fileList.join(' ')}`, { stdio: 'inherit' })
71
71
  console.log('Linting passed successfully!\n')
72
- process.exit(0)
73
72
  } catch (error) {
74
73
  console.log('\n==========================================================================')
75
74
  console.log('ESLint found issues in your code. Please fix them before pushing.')
@@ -79,3 +78,24 @@ try {
79
78
  console.log('==========================================================================\n')
80
79
  process.exit(1)
81
80
  }
81
+
82
+ // By default, always run tests unless RUN_TEST=skip
83
+ const runTestsFlag = process.env.RUN_TEST || ''
84
+
85
+ if (runTestsFlag.toLowerCase() === 'skip') {
86
+ console.log('Skipping tests due to RUN_TEST=skip. Proceeding with push.')
87
+ process.exit(0)
88
+ } else {
89
+ // For any other value (including empty or unknown), run tests
90
+ try {
91
+ console.log('Running tests...\n')
92
+ execSync('npm run test', { stdio: 'inherit' })
93
+ console.log('Tests passed successfully!\n')
94
+ process.exit(0)
95
+ } catch (error) {
96
+ console.log('\n==========================================================================')
97
+ console.log('Some tests failed. Please fix them before pushing.')
98
+ console.log('==========================================================================\n')
99
+ process.exit(1)
100
+ }
101
+ }
@@ -26,7 +26,9 @@ let currentId = null
26
26
  let debounce = null
27
27
 
28
28
  // Global default utterance options
29
- let globalDefaultOptions = {}
29
+ let globalDefaultOptions = {
30
+ enableUtteranceKeepAlive: !/android/i.test((window.navigator || {}).userAgent || ''),
31
+ }
30
32
 
31
33
  const noopAnnouncement = {
32
34
  then() {},
@@ -50,7 +52,6 @@ const toggle = (v) => {
50
52
 
51
53
  const speak = (message, politeness = 'off', options = {}) => {
52
54
  if (active === false) return noopAnnouncement
53
-
54
55
  return addToQueue(message, politeness, false, options)
55
56
  }
56
57
 
@@ -106,6 +107,8 @@ const addToQueue = (message, politeness, delay = false, options = {}) => {
106
107
  return done
107
108
  }
108
109
 
110
+ let currentResolveFn = null
111
+
109
112
  const processQueue = async () => {
110
113
  if (isProcessing === true || queue.length === 0) return
111
114
  isProcessing = true
@@ -113,11 +116,13 @@ const processQueue = async () => {
113
116
  const { message, resolveFn, delay, id, options = {} } = queue.shift()
114
117
 
115
118
  currentId = id
119
+ currentResolveFn = resolveFn
116
120
 
117
121
  if (delay) {
118
122
  setTimeout(() => {
119
123
  isProcessing = false
120
124
  currentId = null
125
+ currentResolveFn = null
121
126
  resolveFn('finished')
122
127
  processQueue()
123
128
  }, delay)
@@ -138,19 +143,21 @@ const processQueue = async () => {
138
143
  Log.debug(`Announcer - finished speaking: "${message}" (id: ${id})`)
139
144
 
140
145
  currentId = null
146
+ currentResolveFn = null
141
147
  isProcessing = false
142
148
  resolveFn('finished')
143
149
  processQueue()
144
150
  })
145
151
  .catch((e) => {
146
152
  currentId = null
153
+ currentResolveFn = null
147
154
  isProcessing = false
148
155
  Log.debug(`Announcer - error ("${e.error}") while speaking: "${message}" (id: ${id})`)
149
156
  resolveFn(e.error)
150
157
  processQueue()
151
158
  })
152
159
  debounce = null
153
- }, 200)
160
+ }, 300)
154
161
  }
155
162
  }
156
163
 
@@ -158,13 +165,52 @@ const polite = (message, options = {}) => speak(message, 'polite', options)
158
165
 
159
166
  const assertive = (message, options = {}) => speak(message, 'assertive', options)
160
167
 
168
+ // Clear debounce timer
169
+ const clearDebounceTimer = () => {
170
+ if (debounce !== null) {
171
+ clearTimeout(debounce)
172
+ debounce = null
173
+ }
174
+ }
175
+
161
176
  const stop = () => {
177
+ // Clear debounce timer if speech hasn't started yet
178
+ clearDebounceTimer()
179
+
180
+ // Always cancel speech synthesis to ensure clean state
162
181
  speechSynthesis.cancel()
182
+
183
+ // Store resolve function before resetting state
184
+ const resolveFn = currentResolveFn
185
+
186
+ // Reset state
187
+ currentId = null
188
+ currentResolveFn = null
189
+ isProcessing = false
190
+
191
+ // Resolve promise if there was an active utterance
192
+ if (resolveFn !== null) {
193
+ resolveFn('interrupted')
194
+ }
163
195
  }
164
196
 
165
197
  const clear = () => {
198
+ // Clear debounce timer
199
+ clearDebounceTimer()
200
+
201
+ // Resolve all pending items in queue
202
+ while (queue.length > 0) {
203
+ const item = queue.shift()
204
+ if (item.resolveFn) {
205
+ Log.debug(`Announcer - clearing queued item: "${item.message}" (id: ${item.id})`)
206
+ item.resolveFn('cleared')
207
+ }
208
+ }
209
+
210
+ // Reset state
211
+ currentId = null
212
+ currentResolveFn = null
166
213
  isProcessing = false
167
- queue.length = 0
168
214
  }
169
215
 
170
216
  const configure = (options = {}) => {