From 56756870a6c3055b82e50d064499f1ff99b73d95 Mon Sep 17 00:00:00 2001 From: Matt Wooding <44930483+WoodingMP@users.noreply.github.com> Date: Wed, 30 Jul 2025 13:49:52 +0100 Subject: [PATCH 01/13] feat: add passFocus option to router to prevent focusing RouterView on navigate --- docs/router/basics.md | 2 ++ src/router/router.js | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/router/basics.md b/docs/router/basics.md index c25279f1..1576c1be 100644 --- a/docs/router/basics.md +++ b/docs/router/basics.md @@ -96,6 +96,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. +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 The Router plugin has support for deeplinking. When the App is loaded with a URL hash (i.e. `#/pages/settings/network`), the router will try to match that hash to a defined route. This means that your app can be deep linked into, by simply providing the correct URL hash. diff --git a/src/router/router.js b/src/router/router.js index 97049434..29dc32a7 100644 --- a/src/router/router.js +++ b/src/router/router.js @@ -337,8 +337,10 @@ export const navigate = async function () { // keep reference to the previous focus for storing in cache previousFocus = Focus.get() - // set focus to the view that we're routing to - focus ? focus.$focus() : /** @type {BlitsComponent} */ (view).$focus() + // set focus to the view that we're routing to (unless explicitly disabling passing focus) + if (route.options.passFocus !== false) { + focus ? focus.$focus() : /** @type {BlitsComponent} */ (view).$focus() + } // apply before settings to holder element if (route.transition.before) { From 7def7b3b28c81a0a4102342c7454c8ec0954afe3 Mon Sep 17 00:00:00 2001 From: Michiel van der Geest Date: Mon, 4 Aug 2025 09:35:21 +0200 Subject: [PATCH 02/13] Added missing type definitions for RouteOptions. --- index.d.ts | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/index.d.ts b/index.d.ts index 2903e4c3..65265194 100644 --- a/index.d.ts +++ b/index.d.ts @@ -187,7 +187,30 @@ declare module '@lightningjs/blits' { // todo: specify valid route options export interface RouteOptions { - [key: string]: any + /** + * Whether the page navigation should be added to the history stack + * used when navigating back using `this.$router.back()` + * + * @default true + */ + inHistory: Boolean + /** + * Whether the page should be kept alive when navigating away. Can be useful + * for a homepage where the state should be fully retained when navigating back + * from a details page + * + * @default false + */ + keepAlive: Boolean + /** + * Whether the focus should be delegated to the page that's being navigated to. + * Can be useful when navigating to a new page from a widget / menu overlaying the + * RouterView, where the widget should maintain the focus (instead of the new page, which + * is the default behaviour) + * + * @default true + */ + passFocus: Boolean } export interface Router { @@ -628,7 +651,7 @@ declare module '@lightningjs/blits' { /** * Extra route options */ - options?: object // todo: specify which options are available, + options?: RouteOptions /** * Message to be announced when visiting the route (often used for accessibility purposes) * From 20506b5189afc71a3df170f01e1ab8c81871655c Mon Sep 17 00:00:00 2001 From: Michiel van der Geest Date: Tue, 5 Aug 2025 09:07:10 +0200 Subject: [PATCH 03/13] Marked route options as optional in the type definitions. --- index.d.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/index.d.ts b/index.d.ts index 65265194..c4f49979 100644 --- a/index.d.ts +++ b/index.d.ts @@ -193,7 +193,7 @@ declare module '@lightningjs/blits' { * * @default true */ - inHistory: Boolean + inHistory?: Boolean /** * Whether the page should be kept alive when navigating away. Can be useful * for a homepage where the state should be fully retained when navigating back @@ -201,7 +201,7 @@ declare module '@lightningjs/blits' { * * @default false */ - keepAlive: Boolean + keepAlive?: Boolean /** * Whether the focus should be delegated to the page that's being navigated to. * Can be useful when navigating to a new page from a widget / menu overlaying the @@ -210,7 +210,7 @@ declare module '@lightningjs/blits' { * * @default true */ - passFocus: Boolean + passFocus?: Boolean } export interface Router { From 2783b93a0a7b3902072588c17565c28e531b0134 Mon Sep 17 00:00:00 2001 From: Michiel van der Geest Date: Tue, 5 Aug 2025 22:25:07 +0200 Subject: [PATCH 04/13] Fixed timing issue where routerview would sometimes try to focus the previous active page. --- src/components/RouterView.js | 5 ++++- src/router/router.js | 5 +++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/components/RouterView.js b/src/components/RouterView.js index 296e099a..f6a3d97a 100644 --- a/src/components/RouterView.js +++ b/src/components/RouterView.js @@ -18,6 +18,7 @@ import Component from '../component.js' import Router from '../router/router.js' import symbols from '../lib/symbols.js' +import Focus from '../focus.js' let hashchangeHandler = null @@ -44,7 +45,9 @@ export default () => window.removeEventListener('hashchange', hashchangeHandler, false) }, focus() { - this.activeView && this.activeView.$focus() + if (this.activeView && Focus.get() === this) { + this.activeView.$focus() + } }, }, input: { diff --git a/src/router/router.js b/src/router/router.js index 2365529d..dee317b1 100644 --- a/src/router/router.js +++ b/src/router/router.js @@ -337,6 +337,9 @@ export const navigate = async function () { // keep reference to the previous focus for storing in cache previousFocus = Focus.get() + const children = this[symbols.children] + this.activeView = children[children.length - 1] + // set focus to the view that we're routing to (unless explicitly disabling passing focus) if (route.options.passFocus !== false) { focus ? focus.$focus() : /** @type {BlitsComponent} */ (view).$focus() @@ -382,8 +385,6 @@ export const navigate = async function () { await setOrAnimate(holder, route.transition.in, shouldAnimate) } } - const children = this[symbols.children] - this.activeView = children[children.length - 1] } else { Log.error(`Route ${hash} not found`) const routerHooks = this.parent[symbols.routerHooks] From 5e22526387cd8ae3d01a62106ebf90dddfac08ce Mon Sep 17 00:00:00 2001 From: Michiel van der Geest Date: Wed, 6 Aug 2025 14:47:25 +0200 Subject: [PATCH 05/13] Bumped version to 1.35.5 and updated changelog. --- CHANGELOG.md | 9 ++++++++- package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 53097e3f..6b01b0e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ # Changelog -## v1.35.3 +## v1.35.5 + +_06 Aug 2025_ + +- Fixed timing routerview causing focus to be passed to previous page + + +## v1.35.4 _03 Aug 2025_ diff --git a/package-lock.json b/package-lock.json index 918bcfef..d7003bd6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@lightningjs/blits", - "version": "1.35.4", + "version": "1.35.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@lightningjs/blits", - "version": "1.35.4", + "version": "1.35.5", "license": "Apache-2.0", "dependencies": { "@lightningjs/msdf-generator": "^1.1.1", diff --git a/package.json b/package.json index f60e0883..e1b392ad 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lightningjs/blits", - "version": "1.35.4", + "version": "1.35.5", "description": "Blits: The Lightning 3 App Development Framework", "bin": "bin/index.js", "exports": { From 89cf83499a74767d73b36f5749f373cac025fda8 Mon Sep 17 00:00:00 2001 From: sairamg Date: Fri, 8 Aug 2025 15:15:53 +0530 Subject: [PATCH 06/13] docs(plugins): add storage plugin documentation and remove duplicate performance section --- docs/_sidebar.md | 1 + docs/sidebar.json | 13 ++++--------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/docs/_sidebar.md b/docs/_sidebar.md index 90f6f5e7..77641bd7 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -33,5 +33,6 @@ - [Language](/plugins/language.md) - [Theme](/plugins/theme.md) - [Global App State](/plugins/global_app_state.md) + - [Storage](/plugins/storage.md) - Performance - [Lazy loading]('/performance/lazy-loading.md') diff --git a/docs/sidebar.json b/docs/sidebar.json index d0bb88f4..2942c487 100644 --- a/docs/sidebar.json +++ b/docs/sidebar.json @@ -112,15 +112,6 @@ } ] }, - { - "text": "Performance", - "items": [ - { - "text": "Lazy Loading", - "link": "/performance/lazy-loading" - } - ] - }, { "text": "Router", "items": [ @@ -156,6 +147,10 @@ { "text": "Global App State", "link": "/plugins/global_app_state" + }, + { + "text": "Storage", + "link": "/plugins/storage" } ] }, From 54c1bdf768062956c4f0e0d155fa51aceca1d633 Mon Sep 17 00:00:00 2001 From: Michiel van der Geest Date: Fri, 8 Aug 2025 15:00:42 +0200 Subject: [PATCH 07/13] Added debug logs to announcer. Fixed (?) issues with isProcessing. Optimzed unique id in speechSynthesis. --- src/announcer/announcer.js | 16 ++++++++++++---- src/announcer/speechSynthesis.js | 2 +- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/announcer/announcer.js b/src/announcer/announcer.js index ddecd7b7..67b0a7b2 100644 --- a/src/announcer/announcer.js +++ b/src/announcer/announcer.js @@ -15,6 +15,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { Log } from '../lib/log.js' import speechSynthesis from './speechSynthesis.js' let active = false @@ -70,7 +71,7 @@ const addToQueue = (message, politeness, delay = false) => { done.cancel = () => { const index = queue.findIndex((item) => item.id === id) if (index !== -1) queue.splice(index, 1) - isProcessing = false + Log.debug(`Announcer - removed from queue: "${message}" (id: ${id})`) resolveFn('canceled') } @@ -92,6 +93,8 @@ const addToQueue = (message, politeness, delay = false) => { queue.push({ delay, resolveFn, id }) } + Log.debug(`Announcer - added to queue: "${message}" (id: ${id})`) + setTimeout(() => { processQueue() }, 100) @@ -118,17 +121,22 @@ const processQueue = async () => { if (debounce !== null) clearTimeout(debounce) // add some easing when speaking the messages to reduce stuttering debounce = setTimeout(() => { + Log.debug(`Announcer - speaking: "${message}" (id: ${id})`) + speechSynthesis - .speak({ message }) + .speak({ message, id }) .then(() => { - isProcessing = false + Log.debug(`Announcer - finished speaking: "${message}" (id: ${id})`) + currentId = null + isProcessing = false resolveFn('finished') processQueue() }) .catch((e) => { - isProcessing = false currentId = null + isProcessing = false + Log.debug(`Announcer - error ("${e.error}") while speaking: "${message}" (id: ${id})`) resolveFn(e.error) processQueue() }) diff --git a/src/announcer/speechSynthesis.js b/src/announcer/speechSynthesis.js index 697a8356..5c454ba4 100644 --- a/src/announcer/speechSynthesis.js +++ b/src/announcer/speechSynthesis.js @@ -64,7 +64,7 @@ const initialize = () => { const speak = (options) => { const utterance = new SpeechSynthesisUtterance(options.message) - const id = Date.now() + Math.random() // Unique ID for tracking + const id = options.id utterance.lang = options.lang || defaultUtteranceProps.lang utterance.pitch = options.pitch || defaultUtteranceProps.pitch utterance.rate = options.rate || defaultUtteranceProps.rate From 3c1fd0bec040f2b9f84279ba54cc0674f0928c77 Mon Sep 17 00:00:00 2001 From: Michiel van der Geest Date: Mon, 11 Aug 2025 14:02:49 +0200 Subject: [PATCH 08/13] Added remove as an alias (and the prefered name) for canceling a message. --- docs/plugins/text-to-speech-announcer.md | 8 ++++---- src/announcer/announcer.js | 10 +++++++++- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/docs/plugins/text-to-speech-announcer.md b/docs/plugins/text-to-speech-announcer.md index 1ad2da19..8162c660 100644 --- a/docs/plugins/text-to-speech-announcer.md +++ b/docs/plugins/text-to-speech-announcer.md @@ -79,7 +79,7 @@ In some cases you may not want to clear the entire queue, but instead cancel out Imagine an App with a row of tiles, it's possible that before the title of the role is being spoken out, the user already navigates through the tiles within the row. Traditionally you'd use the focus event to speak out info about each tile (i.e. adding tot the queue). You don't want all previously focused tiles to still be announced, but would still want the category of the row to be announced, making clearing the queue not required. -The `speak()`-method return a Promise that also contains a `cancel()` function. When called, it will cancel that specific message and remove it from the queue before it can be spoken out. +The `speak()`-method return a Promise that also contains a `remove()` function. When called, it will remove it from the queue before it can be spoken out. Additionally if you want to _interrupt_ a specific messages as it's being spoken out as well and go straight to the next message in the queue (i.e. the newly focused item, for example). You can use the `stop()` message that is returned on the Promise returned by the `speak()`-method. @@ -100,8 +100,8 @@ Blits.Component('MyTile', { unfocus() { // when unfocused interrupt the message if it's already being spoken out this.message.stop() - // and cancel the message to remove it from the queue - this.message.cancel() + // and remove the message to remove it from the queue + this.message.remove() } } }) @@ -117,4 +117,4 @@ Alternatively the announcer can be enabled or disabled run time by using one of - `this.$announcer.enable()` - activates the announcer - `this.$announcer.disable()` - deactivates the announcer -- `this.$announcer.disable(true/false)` - turns the announcer or on off \ No newline at end of file +- `this.$announcer.disable(true/false)` - turns the announcer or on off diff --git a/src/announcer/announcer.js b/src/announcer/announcer.js index 67b0a7b2..5e1dc0c6 100644 --- a/src/announcer/announcer.js +++ b/src/announcer/announcer.js @@ -29,6 +29,7 @@ const noopAnnouncement = { then() {}, done() {}, cancel() {}, + remove() {}, stop() {}, } @@ -68,7 +69,14 @@ const addToQueue = (message, politeness, delay = false) => { }) // augment the promise with a cancel function - done.cancel = () => { + done.remove = done.cancel = () => { + const index = queue.findIndex((item) => item.id === id) + if (index !== -1) queue.splice(index, 1) + Log.debug(`Announcer - removed from queue: "${message}" (id: ${id})`) + resolveFn('canceled') + } + + done.remove = done.cancel = () => { const index = queue.findIndex((item) => item.id === id) if (index !== -1) queue.splice(index, 1) Log.debug(`Announcer - removed from queue: "${message}" (id: ${id})`) From c6bdd7c7d8c0d86356682ce251b9829c74f70e70 Mon Sep 17 00:00:00 2001 From: Michiel van der Geest Date: Mon, 11 Aug 2025 14:20:31 +0200 Subject: [PATCH 09/13] Fixed typo in docs. --- docs/plugins/text-to-speech-announcer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/plugins/text-to-speech-announcer.md b/docs/plugins/text-to-speech-announcer.md index 8162c660..53848757 100644 --- a/docs/plugins/text-to-speech-announcer.md +++ b/docs/plugins/text-to-speech-announcer.md @@ -117,4 +117,4 @@ Alternatively the announcer can be enabled or disabled run time by using one of - `this.$announcer.enable()` - activates the announcer - `this.$announcer.disable()` - deactivates the announcer -- `this.$announcer.disable(true/false)` - turns the announcer or on off +- `this.$announcer.disable(true/false)` - turns the announcer on or off From 1397e115bc4f7f3c9eb308b8d11cf6671d524e5e Mon Sep 17 00:00:00 2001 From: Michiel van der Geest Date: Mon, 11 Aug 2025 14:26:53 +0200 Subject: [PATCH 10/13] Removed duplication. --- src/announcer/announcer.js | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/announcer/announcer.js b/src/announcer/announcer.js index 5e1dc0c6..01ac6df6 100644 --- a/src/announcer/announcer.js +++ b/src/announcer/announcer.js @@ -68,14 +68,7 @@ const addToQueue = (message, politeness, delay = false) => { resolveFn = resolve }) - // augment the promise with a cancel function - done.remove = done.cancel = () => { - const index = queue.findIndex((item) => item.id === id) - if (index !== -1) queue.splice(index, 1) - Log.debug(`Announcer - removed from queue: "${message}" (id: ${id})`) - resolveFn('canceled') - } - + // augment the promise with a cancel / remove function done.remove = done.cancel = () => { const index = queue.findIndex((item) => item.id === id) if (index !== -1) queue.splice(index, 1) From 479a850affc8de264f1dc3fb213fc20bdc074f3e Mon Sep 17 00:00:00 2001 From: Michiel van der Geest Date: Mon, 11 Aug 2025 14:28:52 +0200 Subject: [PATCH 11/13] Fixed example in documentation. --- docs/plugins/text-to-speech-announcer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/plugins/text-to-speech-announcer.md b/docs/plugins/text-to-speech-announcer.md index 53848757..c3c907db 100644 --- a/docs/plugins/text-to-speech-announcer.md +++ b/docs/plugins/text-to-speech-announcer.md @@ -100,7 +100,7 @@ Blits.Component('MyTile', { unfocus() { // when unfocused interrupt the message if it's already being spoken out this.message.stop() - // and remove the message to remove it from the queue + // and remove the message from the queue this.message.remove() } } From 4a79f5ae6661f3e2db78cd7e6d6ef1c25500a624 Mon Sep 17 00:00:00 2001 From: Michiel van der Geest Date: Mon, 11 Aug 2025 14:46:12 +0200 Subject: [PATCH 12/13] Bumped version to 1.36.0 and updated changelog. --- CHANGELOG.md | 11 +++++++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b01b0e4..d6b5aa87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## v1.36.0 + +_11 Aug 2025_ + +- Added `passFocus` option to route options to prevent focus being passed to page navigated to +- Added missing type definitions for Route options +- Fixed sidebar in docs +- Added debug log messages to Announcer +- Added `remove()` function as (preferred) alias for announcer `message.cancel()` +- Fixed issue with removing a messages causing an interupt of current message being read out + ## v1.35.5 _06 Aug 2025_ diff --git a/package-lock.json b/package-lock.json index d7003bd6..5844b86e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@lightningjs/blits", - "version": "1.35.5", + "version": "1.36.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@lightningjs/blits", - "version": "1.35.5", + "version": "1.36.0", "license": "Apache-2.0", "dependencies": { "@lightningjs/msdf-generator": "^1.1.1", diff --git a/package.json b/package.json index e1b392ad..115513a7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lightningjs/blits", - "version": "1.35.5", + "version": "1.36.0", "description": "Blits: The Lightning 3 App Development Framework", "bin": "bin/index.js", "exports": { From 76968addd6356b5599959578da0b16e54342be5c Mon Sep 17 00:00:00 2001 From: Michiel van der Geest Date: Mon, 11 Aug 2025 14:47:39 +0200 Subject: [PATCH 13/13] Fixed typo in changelog. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6b5aa87..3de87a9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ _11 Aug 2025_ - Fixed sidebar in docs - Added debug log messages to Announcer - Added `remove()` function as (preferred) alias for announcer `message.cancel()` -- Fixed issue with removing a messages causing an interupt of current message being read out +- Fixed issue with removing a messages causing an interrupt of current message being read out ## v1.35.5