Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
3170c73
fix: resumeInfinity/onresume interaction
tuncayuk Nov 26, 2025
15614f1
chore: ensure target is a SpeechSynthesisUtterance in resumeInfinity …
tuncayuk Dec 2, 2025
f35ab5e
fix: ensure infinityTimer is checked against null in clear and resume…
tuncayuk Dec 2, 2025
910df0c
fix: ensure resumeFromKeepAlive is strictly checked for true in onres…
tuncayuk Dec 2, 2025
85b01b5
feat(router): add afterEach hook to RouterHooks for post-navigation h…
tuncayuk Dec 2, 2025
ecb2ddc
fix: improve utterance management and error handling in speech synthesis
tuncayuk Dec 3, 2025
d43ee72
chore: add placeholder for getVoices method in speech synthesis
tuncayuk Dec 3, 2025
3e56c9d
chore: improve error handling in speak function of speech synthesis
tuncayuk Dec 3, 2025
d2721b6
chore: enhance queue management and debounce handling in announcer
tuncayuk Dec 3, 2025
6aa4a19
fix: add optional chaining to safely check timer state in resumeInfin…
tuncayuk Dec 3, 2025
a1ebc94
fix: enhance stop and clear functions to ensure clean state in speech…
tuncayuk Dec 3, 2025
9910a08
fix: add double-check for utterance existence before resuming in resu…
tuncayuk Dec 3, 2025
624604f
fix: add early return in clear function to handle non-existent state
tuncayuk Dec 4, 2025
fa88731
chore: remove unused pause event handling in speak function
tuncayuk Dec 4, 2025
4fb38de
fix: add cancelPrevious option to AnnouncerUtterance for managing spe…
tuncayuk Dec 4, 2025
cc30b5e
chore: simplify stop api
tuncayuk Dec 4, 2025
a2a86ae
feat(router): add after hook to RouteHooks for post-navigation handling
tuncayuk Dec 4, 2025
ba64e4c
feat(router): enhance after hooks in RouterHooks for improved navigat…
tuncayuk Dec 4, 2025
3a1a66e
fix: increase debounce duration in processQueue to improve performance
tuncayuk Dec 5, 2025
7d842ee
fix: add waitForSynthReady function to ensure speech synthesis engine…
tuncayuk Dec 5, 2025
41aa289
fix: add enableUtteranceKeepAlive option to improve speech synthesis …
tuncayuk Dec 5, 2025
6f3a1ae
fix: remove previous resolve function in clear method
tuncayuk Dec 8, 2025
2c5255f
fix: remove redundant utterances.delete calls in clear and startKeepA…
tuncayuk Dec 8, 2025
8e9d109
fix: log result of speaking in processQueue for better debugging
tuncayuk Dec 8, 2025
4dc14df
fix: include result in onend callback of speak function for better ha…
tuncayuk Dec 8, 2025
fd77676
feat(router): add runtime navigateHistory toggle and forward Back to …
il-sairamg Dec 16, 2025
806ccc5
Updated document
il-sairamg Dec 16, 2025
6acc761
feat(router): refactor afterEach and after hooks for improved paramet…
tuncayuk Dec 18, 2025
78d6698
feat(router): move afterEach and after hooks to the end of the transi…
tuncayuk Dec 18, 2025
737950a
feat(router): refactor navigate function to use broader scope for old…
tuncayuk Dec 19, 2025
d2078d3
feat(router): update afterEach and after hooks to allow undefined and…
tuncayuk Dec 19, 2025
3559bdf
feat(router): await removeView in navigate function for proper asynch…
tuncayuk Dec 19, 2025
8441944
Removed unneeded variable assignments.
michielvandergeest Dec 23, 2025
9959d92
Removed debug log.
michielvandergeest Dec 23, 2025
1d4884e
Removed debug log and renamed variable.
michielvandergeest Dec 23, 2025
3ca78b2
Updated package lock.
michielvandergeest Dec 30, 2025
e3816b2
Added test cases for prepublishOnly script
suresh-gangumalla Dec 30, 2025
6e128c3
fix: update clear function to accept cancelPrevious option for better…
tuncayuk Jan 2, 2026
0be466c
fix: replace optional chaining with explicit null checks for timer in…
tuncayuk Jan 2, 2026
eea2d60
fix: remove invalid utterance check in startKeepAlive function
tuncayuk Jan 2, 2026
ab06ead
fix: ensure initialized is set to false when speech synthesis API is …
tuncayuk Jan 2, 2026
3cc5d28
fix: change log level from warn to debug for speech synthesis readiness
tuncayuk Jan 2, 2026
7689854
feat(router): fix RouterHooks init type to ensure proper Promise<void…
tuncayuk Jan 5, 2026
71f68e5
fix(router): correct index reference in navigate function for transit…
tuncayuk Jan 5, 2026
2a605d0
Merge remote-tracking branch 'upstream/dev' into tct/pre-publish
suresh-gangumalla Jan 6, 2026
d5e41be
updated fileURL path check
suresh-gangumalla Jan 6, 2026
915d5e0
fix(types): remove plugin interfaces from index.d.ts and co-locate wi…
il-sairamg Jan 6, 2026
e6e09af
rename navigateHistory to backNavigation and use getter/setter
il-sairamg Jan 6, 2026
08c2feb
Merge pull request #566 from il-sairamg/dev
michielvandergeest Jan 6, 2026
1a36fa0
Updated document
il-sairamg Jan 6, 2026
86e5309
Merge pull request #554 from il-sairamg/feat/router-navigate-history-…
michielvandergeest Jan 6, 2026
cedd57d
Merge pull request #563 from suresh-gangumalla/tct/pre-publish
michielvandergeest Jan 7, 2026
560067e
fix: refactor enableUtteranceKeepAlive option to use a constant for b…
tuncayuk Jan 7, 2026
f2d7329
feat: add method for component-scoped debouncing
il-sairamg Jan 7, 2026
0c9143a
Updated documentation
il-sairamg Jan 7, 2026
56b887e
Upadted name
il-sairamg Jan 7, 2026
d1f6602
Updated documentation
il-sairamg Jan 8, 2026
f6cf338
feat: add automatic inspector metadata keys
il-sairamg Jan 13, 2026
41db800
Updated test cases
il-sairamg Jan 13, 2026
a6b6905
Merge pull request #567 from il-sairamg/feature/add-debounce-method
michielvandergeest Jan 13, 2026
1aac2d9
Added some optimization / fixed some coding style issues.
michielvandergeest Jan 13, 2026
d0c8edb
Removed cancelPrevious concept in favour of clear readable function c…
michielvandergeest Jan 13, 2026
408f87f
Fixed typo.
michielvandergeest Jan 14, 2026
84e012b
Merge pull request #544 from tuncayuk/fix/announcer-issue
michielvandergeest Jan 14, 2026
41bb5c8
Updated the code to generate componentType on component
il-sairamg Jan 16, 2026
b01f97c
Updated document
il-sairamg Jan 16, 2026
a44aa7e
Updated documentation
il-sairamg Jan 16, 2026
4fc6af0
feat(router): simplify afterEach and after hooks by removing componen…
tuncayuk Jan 20, 2026
9d0178e
Updated condition for early exit in prod
il-sairamg Jan 21, 2026
c34e41a
Added functionality that automatically sizes a holder node when a wra…
michielvandergeest Jan 21, 2026
a2c2261
Merge pull request #569 from il-sairamg/feat/auto-inspector-metadata
michielvandergeest Jan 21, 2026
09daaa3
Merge pull request #550 from tuncayuk/feat/add-after-each-hook
michielvandergeest Jan 21, 2026
a588245
Merge branch 'master' into dev
michielvandergeest Jan 21, 2026
c0c8fd5
Merge pull request #574 from lightning-js/feature/autosize-holder-nod…
michielvandergeest Jan 22, 2026
adbed8e
Bumped version to 1.46.0 and updated changelog.
michielvandergeest Jan 22, 2026
27e99c4
Bumped renderer to 2.20.4.
michielvandergeest Jan 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
# Changelog

## v1.46.0

_22 jan 2026_

- Improved Announcer on Comcast devices
- Added new `afterEach` router hook
- Added router option to dynamically disable `back`-key handling by the router
- Added `this.$debounce`-method
- Added automatic inspector data items (`$componentType`, `$hasFocus` and `$isTransitioning`)
- Added functionality to auto size the holder node of a Component when the wrapper Element has dimensions
- Bumped Lightning renderer to v2.20.4 with correct handling of failed textures

## v1.45.2

_29 dec 2026_
_13 jan 2026_

- Updated renderer to v2.20.2

Expand Down
70 changes: 70 additions & 0 deletions docs/components/utility_methods.md
Original file line number Diff line number Diff line change
Expand Up @@ -428,3 +428,73 @@ export default Blits.Component('MyComponent', {
}
})
```

## Debounce

Debouncing is a technique to limit the rate at which a function executes. This is particularly useful when navigating through lists or handling rapid user input, where you want to execute a method only after the user has stopped the action for a specified delay.

Similar to timeouts and intervals, debounced functions can cause memory leaks if not properly cleaned up. Blits provides built-in debounce methods that automatically handle cleanup when a component is destroyed.

### $debounce

The `this.$debounce()`-method creates a debounced function that delays execution until after a specified delay has passed since the last invocation. If the same name is debounced again before the delay completes, the previous debounce is cancelled and a new one is created.

The first argument is a `name` (string) that uniquely identifies this debounce instance within the component. The second argument is the `callback` function to execute. The third argument is the `delay` in milliseconds. Additional arguments can be passed and will be forwarded to the callback function.

The method returns a `debounce id`, which can be used to manually clear the debounce.

**Key Features:**
- **Name-based**: Each debounce is identified by a unique name (unique per component instance)
- **Automatic replacement**: Calling `$debounce` with the same name replaces the previous debounce
- **Memory efficient**: Only stores debounce IDs internally, function is captured in closure
- **Automatic cleanup**: All debounces are cleared when component is destroyed

### $clearDebounce

The `this.$clearDebounce()`-method clears a specific debounce by its name. This prevents the debounced function from executing.

### $clearDebounces

The `this.$clearDebounces()`-method clears all debounces registered on the component in one go. This method is automatically called when destroying a Component, preventing memory leaks due to dangling debounce timers.

```js
export default Blits.Component('ListComponent', {
state() {
return {
items: [],
currentIndex: 0
}
},
input: {
down() {
// Debounce navigation - only load data after 300ms of no navigation
this.$debounce('navigate', () => {
this.loadCurrentItem()
}, 300)
},
up() {
// Different debounce for different operation
this.$debounce('update', () => {
this.updateUI()
}, 200)
}
},
methods: {
loadCurrentItem() {
// This will only execute after 300ms of no navigation
const item = this.items[this.currentIndex]
// ... load item data
},
updateUI() {
// This will only execute after 200ms of no update calls
// ... update UI
}
},
hooks: {
unfocus() {
// Optionally clear all debounces when component loses focus
this.$clearDebounces()
}
}
})
```
18 changes: 18 additions & 0 deletions docs/essentials/element_attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,3 +193,21 @@ By default contents inside an Element (i.e. child Elements) will overflow the bo
In order to contain / cut off the content inside an Element's `w` and `h`, you can add the `clipping="true"`-attribute. Setting `clipping` to `false` restores the default behaviour of content overflowing.

Alternatively you can also use the `overflow`-attribute (and pass it `true` or `false`), which works similar to clipping just mapped inversly (i.e. `overflow="false"` ensures content that surpasses the parent dimensions is clipped-off).

## Inspector Data

The `inspector-data` attribute allows you to attach custom metadata to elements and components for debugging and automated testing. This data is visible in the Lightning inspector tool when enabled.

```xml
<Element inspector-data="{testId: 'button-primary', role: 'navigation'}" />
<Button inspector-data="{testId: 'submit-button', role: 'action'}" />
```

The framework automatically provides the following inspector metadata keys for **Components only** (prefixed with `$` to prevent naming collisions):
- `$componentType` - The component name (e.g., 'MyComponent', 'Button', etc.)
- `$hasFocus` - Whether the component currently has focus (automatically updates on focus/unfocus events)
- `$isTransitioning` - Whether the element is currently animating/transitioning (automatically updates)

> **Note:**
> - The `inspector-data` attribute is only processed in development mode when the inspector is enabled. It's automatically filtered out in production builds for performance.
> - Automatic framework metadata (`$componentType`, `$hasFocus`, `$isTransitioning`) is only set for Components, to keep the render path lightweight.
2 changes: 1 addition & 1 deletion docs/plugins/text-to-speech-announcer.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ Blits.Component('MyComponent', {

### Execute logic when done

The `speak()`-function returns a promise that resolves when it's done speaking, or when it's interupted or errors out. The reason of the `speak()` method resolving is passed as an argument into the resolve function.
The `speak()`-function returns a promise that resolves when it's done speaking, or when it's interrupted or errors out. The reason of the `speak()` method resolving is passed as an argument into the resolve function.

```js
this.$announcer.speak('Hello').then((reason) => console.log('Speaking hello finished', reason))
Expand Down
2 changes: 2 additions & 0 deletions docs/router/basics.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ export default Blits.Component('Poster', {

Whenever you navigate to a new page, the URL hash will automatically be updated. Unless specified otherwise, navigating to a new page, will add that route to the history stack. The `back` input action is automatically wired up to navigate back down the history stack.

If you want to disable this automatic history navigation on Back (for example, to let a top-level navigation component handle Back), set `this.$router.backNavigation = false`. Set `this.$router.backNavigation = true` to restore the default behavior.

By default, every time you navigate to a new route, the application focus will be automatically passed to the newly loaded page. If you instead want to maintain the current focus (for example in a widget that sits above your RouterView), you can use `passFocus: false` as part of the router options.

## Deeplinking
Expand Down
23 changes: 23 additions & 0 deletions docs/router/hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,29 @@ In order to configure global router hooks the `routes` key in the Application co

Alongside the `router.routes` key we can now define a `router.hooks` object, which can have any of the following pre-defined hook functions:

### Router Settings

The router configuration also supports router settings alongside `router.routes` and `router.hooks`:

- `backNavigation` - Enable or disable RouterView history navigation on Back input (default: `true`). When set to `false`, the Back input will be passed to the parent component instead of navigating back through the history stack. This setting can be configured in the router config or changed at runtime via `this.$router.backNavigation`.

> **Note:** `backNavigation` is an app-wide setting that affects all `RouterView` instances in your application, as the router state is global and shared.

```js
export default Blits.Application({
router: {
backNavigation: false,
routes: [
{ path: '/', component: Home },
{ path: '/details', component: Details },
],
hooks: {
// router hooks can be defined here
}
}
})
```

### `beforeEach()`

Similar to the `before`-hook, the `beforeEach`-hook will be execute for every router navigation. This can be useful if you find yourself repeating the same functionality for many routes, for example an _authentication check_ or sending _telemetry_.
Expand Down
80 changes: 55 additions & 25 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ declare module '@lightningjs/blits' {
* @default 1
*/
volume?: number,
/**
* Whether to enable utterance keep-alive (prevents pausing on some platforms)
*
* @default undefined
*/
enableUtteranceKeepAlive?: boolean
}

export interface AnnouncerUtterance<T = any> extends Promise<T> {
Expand Down Expand Up @@ -321,24 +327,29 @@ declare module '@lightningjs/blits' {
*/
back(): boolean;

/**
* Enable or disable RouterView history navigation on Back input
*/
backNavigation: boolean;

/**
* Get the current route read-only
*/
readonly currentRoute: Route;

/**
* Get the list of all routes
*/
*/
readonly routes: Route[];

/**
* Get navigating state
*/
*/
readonly navigating: boolean;

/**
* Reactive router state
*/
*/
state: {
/**
* Path of the current route
Expand Down Expand Up @@ -371,28 +382,6 @@ declare module '@lightningjs/blits' {
// Empty by design: extend in your app via TypeScript module augmentation.
}

export interface LanguagePlugin {
translate(key: string, ...replacements: any[]): string
readonly language: string
set(language: string): void
translations(translationsObject: Record<string, unknown>): void
load(file: string): Promise<void>
}

export interface ThemePlugin {
get<T = unknown>(key: string): T | undefined
get<T>(key: string, fallback: T): T
set(theme: string): void
}

export interface StoragePlugin {
get<T = unknown>(key: string): T | null
set(key: string, value: unknown): boolean
remove(key: string): void
clear(): void
}

export type AppStatePlugin<TState extends Record<string, unknown> = Record<string, unknown>> = TState

export interface ComponentBase extends CustomComponentProperties {
/**
Expand Down Expand Up @@ -444,6 +433,26 @@ declare module '@lightningjs/blits' {
*/
$clearTimeout: (id: ReturnType<typeof setTimeout>) => void

/**
* Debounce a function execution, preventing memory leaks and function re-allocation
* @param name - Unique identifier for this debounce instance (unique per component instance)
* @param callback - Function to debounce
* @param ms - Delay in milliseconds
* @param args - Arguments to pass to the callback
*/
$debounce: (name: string, callback: (...args: any[]) => void, ms?: number, ...args: any[]) => ReturnType<typeof setTimeout>

/**
* Clear a specific debounce by name
* @param name - The name of the debounce to clear
*/
$clearDebounce: (name: string) => void

/**
* Clear all debounces registered on the component (automatically called on component destroy)
*/
$clearDebounces: () => void

/**
* Set an interval that is automatically cleaned upon component destroy
*/
Expand Down Expand Up @@ -658,6 +667,7 @@ declare module '@lightningjs/blits' {
export interface RouterHooks {
init?: () => Promise<void> | void;
beforeEach?: (to: Route, from: Route) => string | Route | Promise<string | Route> | void;
afterEach?: (to: Route, from: Route) => string | Route | Promise<string | Route> | void;
error?: (err: string) => string | Route | Promise<string | Route> | void;
}

Expand All @@ -681,6 +691,25 @@ declare module '@lightningjs/blits' {
* ```
*/
routes?: Route[]

/**
* Enable or disable RouterView history navigation on Back input
*
* @default true
*
* @remarks
* This is an app-wide setting that affects all RouterView instances in your application.
* The router state is global and shared across all router instances.
*
* @example
* ```js
* router: {
* backNavigation: false, // Disable automatic back navigation
* routes: [...]
* }
* ```
*/
backNavigation?: boolean
}

export type ApplicationConfig<P extends Props, S, M, C, W> = ComponentConfig<P, S, M, C, W> & (
Expand Down Expand Up @@ -781,6 +810,7 @@ declare module '@lightningjs/blits' {

export interface RouteHooks {
before?: (to: Route, from: Route) => string | Route | Promise<string | Route>;
after?: (to: Route, from: Route) => string | Route | Promise<string | Route>;
}

export type Route = {
Expand Down
12 changes: 6 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@lightningjs/blits",
"version": "1.45.2",
"version": "1.46.0",
"description": "Blits: The Lightning 3 App Development Framework",
"bin": "bin/index.js",
"exports": {
Expand Down Expand Up @@ -74,7 +74,7 @@
},
"dependencies": {
"@lightningjs/msdf-generator": "^1.2.0",
"@lightningjs/renderer": "^2.20.2",
"@lightningjs/renderer": "^2.20.4",
"magic-string": "^0.30.21"
},
"repository": {
Expand Down
8 changes: 7 additions & 1 deletion scripts/prepublishOnly.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import fs from 'fs'
import path from 'path'
import { fileURLToPath } from 'url'
import compiler from '../src/lib/precompiler/precompiler.js'
import { exec } from 'child_process'

Expand Down Expand Up @@ -84,4 +85,9 @@ function formatFileWithESLint(filePath) {
})
}

precompileComponents()
// Only run if this file is executed directly (not imported)
if (fileURLToPath(import.meta.url) === process.argv[1]) {
precompileComponents()
}

export { precompileComponents, processDirectory, processFile, formatFileWithESLint }
Loading