From f4ee27d8eccf5943bd0cc240449545d020c1ccb9 Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Wed, 13 May 2026 13:57:25 -0700 Subject: [PATCH 1/5] feat!: Add keyboard shortcut to toggle screenreader mode --- packages/blockly/core/block_svg.ts | 9 +++ .../core/keyboard_navigation_controller.ts | 17 +++++ packages/blockly/core/shortcut_items.ts | 67 +++++++++++++++++-- packages/blockly/core/workspace_audio.ts | 32 ++++++++- packages/blockly/core/workspace_svg.ts | 4 +- packages/blockly/msg/json/en.json | 7 +- packages/blockly/msg/json/qqq.json | 19 ++---- packages/blockly/msg/messages.js | 11 ++- .../tests/mocha/shortcut_items_test.js | 36 ++++++++++ 9 files changed, 174 insertions(+), 28 deletions(-) diff --git a/packages/blockly/core/block_svg.ts b/packages/blockly/core/block_svg.ts index 65687440cfe..649e838b4b9 100644 --- a/packages/blockly/core/block_svg.ts +++ b/packages/blockly/core/block_svg.ts @@ -1879,6 +1879,15 @@ export class BlockSvg } } + /** + * Returns the number of blocks that this block is nested inside of. + * @internal + */ + getNestingLevel(): number { + const surroundParent = this.getSurroundParent(); + return surroundParent ? surroundParent.getNestingLevel() + 1 : 0; + } + /** See IFocusableNode.getFocusableElement. */ getFocusableElement(): HTMLElement | SVGElement { // For full-block fields, we focus the field itself diff --git a/packages/blockly/core/keyboard_navigation_controller.ts b/packages/blockly/core/keyboard_navigation_controller.ts index d0a766daff2..a8521cbf4a7 100644 --- a/packages/blockly/core/keyboard_navigation_controller.ts +++ b/packages/blockly/core/keyboard_navigation_controller.ts @@ -12,6 +12,7 @@ export class KeyboardNavigationController { /** Whether the user is actively using keyboard navigation. */ private isActive = false; + private levelChangeBeepsEnabled = false; /** Css class name added to body if keyboard nav is active. */ private activeClassName = 'blocklyKeyboardNavigation'; @@ -49,6 +50,22 @@ export class KeyboardNavigationController { return this.isActive; } + /** + * Sets whether or not audio cues should be played when keyboard navigation + * transitions between blocks of different nesting levels. + */ + setLevelChangeBeepsEnabled(enabled: boolean) { + this.levelChangeBeepsEnabled = enabled; + } + + /** + * Returns whether or not audio cues should be played when keyboard navigation + * transitions between blocks of different nesting levels. + */ + getLevelChangeBeepsEnabled() { + return this.levelChangeBeepsEnabled; + } + /** Adds or removes the css class that indicates keyboard navigation is active. */ private updateActiveVisualization() { if (this.isActive) { diff --git a/packages/blockly/core/shortcut_items.ts b/packages/blockly/core/shortcut_items.ts index 1aa800a20d1..c50ce2ff354 100644 --- a/packages/blockly/core/shortcut_items.ts +++ b/packages/blockly/core/shortcut_items.ts @@ -67,6 +67,7 @@ export enum names { DUPLICATE = 'duplicate', CLEANUP = 'cleanup', SHOW_TOOLTIP = 'show_tooltip', + TOGGLE_SCREENREADER = 'toggle_screenreader', } /** @@ -623,7 +624,10 @@ export function registerArrowNavigation() { const node = workspace.RTL ? getFocusManager().getFocusedTree()?.getNavigator().getOutNode() : getFocusManager().getFocusedTree()?.getNavigator().getInNode(); - if (!node) return false; + if (!node) { + workspace.getAudioManager().playErrorBeep(); + return false; + } getFocusManager().focusNode(node); return true; }, @@ -645,7 +649,10 @@ export function registerArrowNavigation() { const node = workspace.RTL ? getFocusManager().getFocusedTree()?.getNavigator().getInNode() : getFocusManager().getFocusedTree()?.getNavigator().getOutNode(); - if (!node) return false; + if (!node) { + workspace.getAudioManager().playErrorBeep(); + return false; + } getFocusManager().focusNode(node); return true; }, @@ -661,14 +668,18 @@ export function registerArrowNavigation() { !workspace.isDragging() && !dropDownDiv.isVisible() && !widgetDiv.isVisible(), - callback: (_workspace, e) => { + callback: (workspace, e) => { e.preventDefault(); keyboardNavigationController.setIsActive(true); const node = getFocusManager() .getFocusedTree() ?.getNavigator() .getNextNode(); - if (!node) return false; + if (!node) { + workspace.getAudioManager().playErrorBeep(); + return false; + } + workspace.getAudioManager().maybePlayLevelChangeBeep(node); getFocusManager().focusNode(node); return true; }, @@ -683,14 +694,18 @@ export function registerArrowNavigation() { !workspace.isDragging() && !dropDownDiv.isVisible() && !widgetDiv.isVisible(), - callback: (_workspace, e) => { + callback: (workspace, e) => { e.preventDefault(); keyboardNavigationController.setIsActive(true); const node = getFocusManager() .getFocusedTree() ?.getNavigator() .getPreviousNode(); - if (!node) return false; + if (!node) { + workspace.getAudioManager().playErrorBeep(); + return false; + } + workspace.getAudioManager().maybePlayLevelChangeBeep(node); getFocusManager().focusNode(node); return true; }, @@ -1020,6 +1035,45 @@ export function registerShowTooltip() { ShortcutRegistry.registry.register(showTooltip); } +/** + * Registers keyboard shortcut to toggle on or off various behaviors that + * improve the experience for individuals using screenreaders. + */ +export function registerToggleScreenreaderMode() { + const shortcut = ShortcutRegistry.registry.createSerializedKey(KeyCodes.Z, [ + KeyCodes.CTRL_CMD, + KeyCodes.ALT, + ]); + + let enabled = false; + + const toggleScreenreader: KeyboardShortcut = { + name: names.TOGGLE_SCREENREADER, + preconditionFn: () => true, + callback: (workspace) => { + enabled = !enabled; + keyboardNavigationController.setLevelChangeBeepsEnabled(enabled); + workspace.getNavigator().setNavigationLoops(!enabled); + workspace.getToolbox()?.getNavigator().setNavigationLoops(!enabled); + workspace + .getFlyout() + ?.getWorkspace() + .getNavigator() + .setNavigationLoops(!enabled); + aria.announceDynamicAriaState( + enabled + ? Msg['SCREENREADER_MODE_ENABLED'] + : Msg['SCREENREADER_MODE_DISABLED'], + ); + return true; + }, + keyCodes: [shortcut], + allowCollision: true, + displayText: () => Msg['SHORTCUTS_TOGGLE_SCREENREADER_MODE'], + }; + ShortcutRegistry.registry.register(toggleScreenreader); +} + /** * Registers all default keyboard shortcut item. This should be called once per * instance of KeyboardShortcutRegistry. @@ -1059,6 +1113,7 @@ export function registerKeyboardNavigationShortcuts() { */ export function registerScreenReaderShortcuts() { registerWorkspaceOverview(); + registerToggleScreenreaderMode(); } registerDefaultShortcuts(); diff --git a/packages/blockly/core/workspace_audio.ts b/packages/blockly/core/workspace_audio.ts index 66d555cd44b..5dcbf2e5f85 100644 --- a/packages/blockly/core/workspace_audio.ts +++ b/packages/blockly/core/workspace_audio.ts @@ -12,6 +12,9 @@ */ // Former goog.module ID: Blockly.WorkspaceAudio +import {getFocusManager} from './focus_manager.js'; +import type {IFocusableNode} from './interfaces/i_focusable_node.js'; +import {keyboardNavigationController} from './keyboard_navigation_controller.js'; import type {WorkspaceSvg} from './workspace_svg.js'; /** @@ -38,7 +41,7 @@ export class WorkspaceAudio { /** * @param parentWorkspace The parent of the workspace this audio object - * belongs to, or null. + * belongs to if it has one, or the workspace that owns this instance. */ constructor(private parentWorkspace: WorkspaceSvg) { if (window.AudioContext) { @@ -145,6 +148,33 @@ export class WorkspaceAudio { return this.beep(260); } + /** + * If enabled, plays a tone corresponding to the nesting level of the given + * node when it differs from the nesting level of the currently focused node. + * These tones are generally used for accessibility purposes to indicate a + * scope transition to users who use a screenreader. This method must be + * called before focus transitions to the given node. + * + * @internal + * @param newNode The soon-to-be-focused node. + */ + maybePlayLevelChangeBeep(newNode: IFocusableNode) { + if (!keyboardNavigationController.getLevelChangeBeepsEnabled()) return; + const navigator = this.parentWorkspace.getNavigator(); + const oldBlock = navigator.getSourceBlockFromNode( + getFocusManager().getFocusedNode(), + ); + const newBlock = navigator.getSourceBlockFromNode(newNode); + let level = 0; + if ( + oldBlock && + newBlock && + oldBlock.getNestingLevel() !== (level = newBlock.getNestingLevel()) + ) { + this.beep(400 + level * 60); + } + } + /** * Returns whether or not playing sounds is currently allowed. * diff --git a/packages/blockly/core/workspace_svg.ts b/packages/blockly/core/workspace_svg.ts index 68a9db28b5a..4f227d617c0 100644 --- a/packages/blockly/core/workspace_svg.ts +++ b/packages/blockly/core/workspace_svg.ts @@ -379,9 +379,7 @@ export class WorkspaceSvg /** * Object in charge of loading, storing, and playing audio for a workspace. */ - this.audioManager = new WorkspaceAudio( - options.parentWorkspace as WorkspaceSvg, - ); + this.audioManager = new WorkspaceAudio(options.parentWorkspace ?? this); /** This workspace's grid object or null. */ this.grid = this.options.gridPattern diff --git a/packages/blockly/msg/json/en.json b/packages/blockly/msg/json/en.json index 4f2135a9761..364793bc1c7 100644 --- a/packages/blockly/msg/json/en.json +++ b/packages/blockly/msg/json/en.json @@ -1,7 +1,7 @@ { "@metadata": { "author": "Ellen Spertus ", - "lastupdated": "2026-05-11 14:15:58.197621", + "lastupdated": "2026-05-13 13:57:14.854829", "locale": "en", "messagedocumentation" : "qqq" }, @@ -451,6 +451,7 @@ "SHORTCUTS_DUPLICATE": "Duplicate", "SHORTCUTS_CLEANUP": "Clean up workspace", "SHORTCUTS_SHOW_TOOLTIP": "Show tooltip", + "SHORTCUTS_TOGGLE_SCREENREADER_MODE": "Toggle screenreader mode", "KEYBOARD_NAV_UNCONSTRAINED_MOVE_HINT": "Hold %1 and use arrow keys to move freely, then %2 to accept the position.", "KEYBOARD_NAV_CONSTRAINED_MOVE_HINT": "Use the arrow keys to move, then %1 to accept the position.", "KEYBOARD_NAV_COPIED_HINT": "Copied. Press %1 to paste.", @@ -517,5 +518,7 @@ "ICON_LABEL_WARNING_OPEN": "Close Warning", "ARIA_LABEL_COMMENT": "Comment", "ARIA_LABEL_COMMENT_COLLAPSE": "Collapse Comment", - "ARIA_LABEL_COMMENT_EXPAND": "Expand Comment" + "ARIA_LABEL_COMMENT_EXPAND": "Expand Comment", + "SCREENREADER_MODE_ENABLED": "Screenreader mode is on", + "SCREENREADER_MODE_DISABLED": "Screenreader mode is off" } diff --git a/packages/blockly/msg/json/qqq.json b/packages/blockly/msg/json/qqq.json index 49e642f935f..bd547513704 100644 --- a/packages/blockly/msg/json/qqq.json +++ b/packages/blockly/msg/json/qqq.json @@ -1,18 +1,4 @@ { - "@metadata": { - "authors": [ - "Ajeje Brazorf", - "Amire80", - "Espertus", - "Liuxinyu970226", - "McDutchie", - "Metalhead64", - "Nike", - "Robby", - "Shirayuki", - "YaronSh" - ] - }, "VARIABLES_DEFAULT_NAME": "default name - A simple, general default name for a variable, preferably short. For more context, see [[Translating:Blockly#infrequent_message_types]].\n{{Identical|Item}}", "UNNAMED_KEY": "default name - A simple, default name for an unnamed function or variable. Preferably indicates that the item is unnamed.", "TODAY": "button text - Button that sets a calendar to today's date.\n{{Identical|Today}}", @@ -459,6 +445,7 @@ "SHORTCUTS_DUPLICATE": "shortcut display text for the duplicate shortcut, which duplicates the focused block or comment.", "SHORTCUTS_CLEANUP": "shortcut display text for the cleanup shortcut, which organizes blocks on the workspace.", "SHORTCUTS_SHOW_TOOLTIP": "shortcut display text for the show tooltip shortcut, which displays a short help text for the focused element.", + "SHORTCUTS_TOGGLE_SCREENREADER_MODE": "shortcut display text for a shortcut that toggles various behaviors to improve the experience of individuals using screenreaders.", "KEYBOARD_NAV_UNCONSTRAINED_MOVE_HINT": "Message shown to inform users how to move blocks to arbitrary locations with the keyboard.", "KEYBOARD_NAV_CONSTRAINED_MOVE_HINT": "Message shown to inform users how to move blocks with the keyboard.", "KEYBOARD_NAV_COPIED_HINT": "Message shown when an item is copied in keyboard navigation mode.", @@ -525,5 +512,7 @@ "ICON_LABEL_WARNING_OPEN": "Label for an icon, used by screen readers to identify an open warning. Clicking on the icon closes the warning's bubble.", "ARIA_LABEL_COMMENT": "ARIA label for a comment.", "ARIA_LABEL_COMMENT_COLLAPSE": "ARIA label for an expanded comment's collapse button.", - "ARIA_LABEL_COMMENT_EXPAND": "ARIA label for a collapsed comment's expand button." + "ARIA_LABEL_COMMENT_EXPAND": "ARIA label for a collapsed comment's expand button.", + "SCREENREADER_MODE_ENABLED": "Message announced when screenreader optimization mode is turned on.", + "SCREENREADER_MODE_DISABLED": "Message announced when screenreader optimization mode is turned off." } diff --git a/packages/blockly/msg/messages.js b/packages/blockly/msg/messages.js index 71c3aff5ce8..04ba6fb4de4 100644 --- a/packages/blockly/msg/messages.js +++ b/packages/blockly/msg/messages.js @@ -1784,6 +1784,9 @@ Blockly.Msg.SHORTCUTS_CLEANUP = 'Clean up workspace'; /// shortcut display text for the show tooltip shortcut, which displays a short help text for the focused element. Blockly.Msg.SHORTCUTS_SHOW_TOOLTIP = 'Show tooltip'; /** @type {string} */ +/// shortcut display text for a shortcut that toggles various behaviors to improve the experience of individuals using screenreaders. +Blockly.Msg.SHORTCUTS_TOGGLE_SCREENREADER_MODE = 'Toggle screenreader mode'; +/** @type {string} */ /// Message shown to inform users how to move blocks to arbitrary locations /// with the keyboard. Blockly.Msg.KEYBOARD_NAV_UNCONSTRAINED_MOVE_HINT = 'Hold %1 and use arrow keys to move freely, then %2 to accept the position.'; @@ -2046,4 +2049,10 @@ Blockly.Msg.ARIA_LABEL_COMMENT = 'Comment'; Blockly.Msg.ARIA_LABEL_COMMENT_COLLAPSE = 'Collapse Comment'; /** @type {string} */ /// ARIA label for a collapsed comment's expand button. -Blockly.Msg.ARIA_LABEL_COMMENT_EXPAND = 'Expand Comment'; \ No newline at end of file +Blockly.Msg.ARIA_LABEL_COMMENT_EXPAND = 'Expand Comment'; +/** @type {string} */ +/// Message announced when screenreader optimization mode is turned on. +Blockly.Msg.SCREENREADER_MODE_ENABLED = 'Screenreader mode is on'; +/** @type {string} */ +/// Message announced when screenreader optimization mode is turned off. +Blockly.Msg.SCREENREADER_MODE_DISABLED = 'Screenreader mode is off'; \ No newline at end of file diff --git a/packages/blockly/tests/mocha/shortcut_items_test.js b/packages/blockly/tests/mocha/shortcut_items_test.js index 581e38b2d30..aea7911d744 100644 --- a/packages/blockly/tests/mocha/shortcut_items_test.js +++ b/packages/blockly/tests/mocha/shortcut_items_test.js @@ -1740,4 +1740,40 @@ suite('Keyboard Shortcut Items', function () { assert.isFalse(Blockly.Tooltip.isVisible()); }); }); + + suite('Toggle screenreader mode (Ctrl+Alt+Z / Cmd+Option+Z)', function () { + const event = createKeyDownEvent(Blockly.utils.KeyCodes.Z, [ + Blockly.utils.KeyCodes.CTRL_CMD, + Blockly.utils.KeyCodes.ALT, + ]); + + setup(function(){ + this.liveRegion = document.getElementById('blocklyAriaAnnounce'); + }); + + test('Can be toggled', function () { + assert.isTrue(this.workspace.getNavigator().getNavigationLoops()); + assert.isTrue(this.workspace.getToolbox().getNavigator().getNavigationLoops()); + assert.isTrue(this.workspace.getFlyout().getWorkspace().getNavigator().getNavigationLoops()); + assert.isFalse(Blockly.keyboardNavigationController.getLevelChangeBeepsEnabled()); + + this.injectionDiv.dispatchEvent(event); + this.clock.runAll(); + + assert.isFalse(this.workspace.getNavigator().getNavigationLoops()); + assert.isFalse(this.workspace.getToolbox().getNavigator().getNavigationLoops()); + assert.isFalse(this.workspace.getFlyout().getWorkspace().getNavigator().getNavigationLoops()); + assert.isTrue(Blockly.keyboardNavigationController.getLevelChangeBeepsEnabled()); + assert.include(this.liveRegion.textContent, 'Screenreader mode is on'); + + this.injectionDiv.dispatchEvent(event); + this.clock.runAll(); + + assert.isTrue(this.workspace.getNavigator().getNavigationLoops()); + assert.isTrue(this.workspace.getToolbox().getNavigator().getNavigationLoops()); + assert.isTrue(this.workspace.getFlyout().getWorkspace().getNavigator().getNavigationLoops()); + assert.isFalse(Blockly.keyboardNavigationController.getLevelChangeBeepsEnabled()); + assert.include(this.liveRegion.textContent, 'Screenreader mode is off'); + }); + }) }); From 3440d92a9f307718acfaac4cc876435ae4a53cfd Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Wed, 13 May 2026 14:59:44 -0700 Subject: [PATCH 2/5] chore: Run formatter --- .../tests/mocha/shortcut_items_test.js | 52 +++++++++++++++---- 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/packages/blockly/tests/mocha/shortcut_items_test.js b/packages/blockly/tests/mocha/shortcut_items_test.js index cac53fe1f05..b0c9ac16916 100644 --- a/packages/blockly/tests/mocha/shortcut_items_test.js +++ b/packages/blockly/tests/mocha/shortcut_items_test.js @@ -1843,33 +1843,63 @@ suite('Keyboard Shortcut Items', function () { Blockly.utils.KeyCodes.ALT, ]); - setup(function(){ + setup(function () { this.liveRegion = document.getElementById('blocklyAriaAnnounce'); }); test('Can be toggled', function () { assert.isTrue(this.workspace.getNavigator().getNavigationLoops()); - assert.isTrue(this.workspace.getToolbox().getNavigator().getNavigationLoops()); - assert.isTrue(this.workspace.getFlyout().getWorkspace().getNavigator().getNavigationLoops()); - assert.isFalse(Blockly.keyboardNavigationController.getLevelChangeBeepsEnabled()); + assert.isTrue( + this.workspace.getToolbox().getNavigator().getNavigationLoops(), + ); + assert.isTrue( + this.workspace + .getFlyout() + .getWorkspace() + .getNavigator() + .getNavigationLoops(), + ); + assert.isFalse( + Blockly.keyboardNavigationController.getLevelChangeBeepsEnabled(), + ); this.injectionDiv.dispatchEvent(event); this.clock.runAll(); assert.isFalse(this.workspace.getNavigator().getNavigationLoops()); - assert.isFalse(this.workspace.getToolbox().getNavigator().getNavigationLoops()); - assert.isFalse(this.workspace.getFlyout().getWorkspace().getNavigator().getNavigationLoops()); - assert.isTrue(Blockly.keyboardNavigationController.getLevelChangeBeepsEnabled()); + assert.isFalse( + this.workspace.getToolbox().getNavigator().getNavigationLoops(), + ); + assert.isFalse( + this.workspace + .getFlyout() + .getWorkspace() + .getNavigator() + .getNavigationLoops(), + ); + assert.isTrue( + Blockly.keyboardNavigationController.getLevelChangeBeepsEnabled(), + ); assert.include(this.liveRegion.textContent, 'Screenreader mode is on'); this.injectionDiv.dispatchEvent(event); this.clock.runAll(); assert.isTrue(this.workspace.getNavigator().getNavigationLoops()); - assert.isTrue(this.workspace.getToolbox().getNavigator().getNavigationLoops()); - assert.isTrue(this.workspace.getFlyout().getWorkspace().getNavigator().getNavigationLoops()); - assert.isFalse(Blockly.keyboardNavigationController.getLevelChangeBeepsEnabled()); + assert.isTrue( + this.workspace.getToolbox().getNavigator().getNavigationLoops(), + ); + assert.isTrue( + this.workspace + .getFlyout() + .getWorkspace() + .getNavigator() + .getNavigationLoops(), + ); + assert.isFalse( + Blockly.keyboardNavigationController.getLevelChangeBeepsEnabled(), + ); assert.include(this.liveRegion.textContent, 'Screenreader mode is off'); }); - }) + }); }); From 7f3b3b1106d437371af2e9d23edab666b004f13a Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Wed, 13 May 2026 15:03:31 -0700 Subject: [PATCH 3/5] chore: Fix lint --- packages/blockly/core/block_svg.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/blockly/core/block_svg.ts b/packages/blockly/core/block_svg.ts index 649e838b4b9..0f3d385d0a7 100644 --- a/packages/blockly/core/block_svg.ts +++ b/packages/blockly/core/block_svg.ts @@ -1881,6 +1881,7 @@ export class BlockSvg /** * Returns the number of blocks that this block is nested inside of. + * * @internal */ getNestingLevel(): number { From e31a634c09971e9f2321fe7702c000cbbee85179 Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Thu, 14 May 2026 08:50:58 -0700 Subject: [PATCH 4/5] fix: Announce screenreader mode changes via toast --- packages/blockly/core/hints.ts | 22 ++++++++++++++++++++++ packages/blockly/core/shortcut_items.ts | 13 +++++++------ packages/blockly/msg/json/en.json | 6 +++--- packages/blockly/msg/json/qqq.json | 14 -------------- packages/blockly/msg/messages.js | 4 ++-- 5 files changed, 34 insertions(+), 25 deletions(-) diff --git a/packages/blockly/core/hints.ts b/packages/blockly/core/hints.ts index d6b12cca030..063c5137622 100644 --- a/packages/blockly/core/hints.ts +++ b/packages/blockly/core/hints.ts @@ -18,6 +18,7 @@ const blockNavigationHintId = 'blockNavigationHint'; const workspaceNavigationHintId = 'workspaceNavigationHint'; const copiedHintId = 'copiedHint'; const cutHintId = 'cutHint'; +const screenreaderHintId = 'screenreaderHint'; /** * Nudge the user to use unconstrained movement. @@ -153,3 +154,24 @@ export function clearPasteHints(workspace: WorkspaceSvg) { Toast.hide(workspace, cutHintId); Toast.hide(workspace, copiedHintId); } + +/** + * Inform the user about screenreader optimization mode being toggled, and how + * to undo it. + * + * @param workspace The workspace where screenreader mode was toggled. + * @param enabled True if screenreader mode is now enabled, otherwise false. + */ +export function showScreenreaderModeHint( + workspace: WorkspaceSvg, + enabled: boolean, +) { + Toast.show(workspace, { + message: (enabled + ? Msg['SCREENREADER_MODE_ENABLED'] + : Msg['SCREENREADER_MODE_DISABLED'] + ).replace('%1', getShortcutKeysShort(names.TOGGLE_SCREENREADER)), + duration: 7, + id: screenreaderHintId, + }); +} diff --git a/packages/blockly/core/shortcut_items.ts b/packages/blockly/core/shortcut_items.ts index d801e2d41a6..7fc1716f9c3 100644 --- a/packages/blockly/core/shortcut_items.ts +++ b/packages/blockly/core/shortcut_items.ts @@ -14,7 +14,12 @@ import * as contextmenu from './contextmenu.js'; import * as dropDownDiv from './dropdowndiv.js'; import * as eventUtils from './events/utils.js'; import {getFocusManager} from './focus_manager.js'; -import {clearPasteHints, showCopiedHint, showCutHint} from './hints.js'; +import { + clearPasteHints, + showCopiedHint, + showCutHint, + showScreenreaderModeHint, +} from './hints.js'; import {hasContextMenu} from './interfaces/i_contextmenu.js'; import {isCopyable as isICopyable} from './interfaces/i_copyable.js'; import {isDeletable as isIDeletable} from './interfaces/i_deletable.js'; @@ -1147,11 +1152,7 @@ export function registerToggleScreenreaderMode() { ?.getWorkspace() .getNavigator() .setNavigationLoops(!enabled); - aria.announceDynamicAriaState( - enabled - ? Msg['SCREENREADER_MODE_ENABLED'] - : Msg['SCREENREADER_MODE_DISABLED'], - ); + showScreenreaderModeHint(workspace, enabled); return true; }, keyCodes: [shortcut], diff --git a/packages/blockly/msg/json/en.json b/packages/blockly/msg/json/en.json index c916f840c5c..7c24c37a7db 100644 --- a/packages/blockly/msg/json/en.json +++ b/packages/blockly/msg/json/en.json @@ -1,7 +1,7 @@ { "@metadata": { "author": "Ellen Spertus ", - "lastupdated": "2026-05-14 08:05:42.601410", + "lastupdated": "2026-05-14 08:47:43.920300", "locale": "en", "messagedocumentation" : "qqq" }, @@ -525,8 +525,8 @@ "ARIA_LABEL_COMMENT": "Comment", "ARIA_LABEL_COMMENT_COLLAPSE": "Collapse Comment", "ARIA_LABEL_COMMENT_EXPAND": "Expand Comment", - "SCREENREADER_MODE_ENABLED": "Screenreader mode is on", - "SCREENREADER_MODE_DISABLED": "Screenreader mode is off", + "SCREENREADER_MODE_ENABLED": "Screenreader mode is on, press %1 to turn it off", + "SCREENREADER_MODE_DISABLED": "Screenreader mode is off, press %1 to turn it on", "CURRENT_BLOCK_ANNOUNCEMENT": "Current block: %1", "PARENT_BLOCKS_ANNOUNCEMENT": "Parent blocks: %1", "NO_PARENT_ANNOUNCEMENT": "Current block has no parent" diff --git a/packages/blockly/msg/json/qqq.json b/packages/blockly/msg/json/qqq.json index eed8225e4f5..1d1dca07940 100644 --- a/packages/blockly/msg/json/qqq.json +++ b/packages/blockly/msg/json/qqq.json @@ -1,18 +1,4 @@ { - "@metadata": { - "authors": [ - "Ajeje Brazorf", - "Amire80", - "Espertus", - "Liuxinyu970226", - "McDutchie", - "Metalhead64", - "Nike", - "Robby", - "Shirayuki", - "YaronSh" - ] - }, "VARIABLES_DEFAULT_NAME": "default name - A simple, general default name for a variable, preferably short. For more context, see [[Translating:Blockly#infrequent_message_types]].\n{{Identical|Item}}", "UNNAMED_KEY": "default name - A simple, default name for an unnamed function or variable. Preferably indicates that the item is unnamed.", "TODAY": "button text - Button that sets a calendar to today's date.\n{{Identical|Today}}", diff --git a/packages/blockly/msg/messages.js b/packages/blockly/msg/messages.js index 601fe403d0a..103e533d91e 100644 --- a/packages/blockly/msg/messages.js +++ b/packages/blockly/msg/messages.js @@ -2077,10 +2077,10 @@ Blockly.Msg.ARIA_LABEL_COMMENT_COLLAPSE = 'Collapse Comment'; Blockly.Msg.ARIA_LABEL_COMMENT_EXPAND = 'Expand Comment'; /** @type {string} */ /// Message announced when screenreader optimization mode is turned on. -Blockly.Msg.SCREENREADER_MODE_ENABLED = 'Screenreader mode is on'; +Blockly.Msg.SCREENREADER_MODE_ENABLED = 'Screenreader mode is on, press %1 to turn it off'; /** @type {string} */ /// Message announced when screenreader optimization mode is turned off. -Blockly.Msg.SCREENREADER_MODE_DISABLED = 'Screenreader mode is off'; +Blockly.Msg.SCREENREADER_MODE_DISABLED = 'Screenreader mode is off, press %1 to turn it on'; /** @type {string} */ /// Screenreader announcement providing context about the currently focused block. Blockly.Msg.CURRENT_BLOCK_ANNOUNCEMENT = 'Current block: %1'; From 07c8176a90b342c342e6822475d980cf1c60300a Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Thu, 14 May 2026 09:12:42 -0700 Subject: [PATCH 5/5] chore: Adjust naming --- .../blockly/core/keyboard_navigation_controller.ts | 11 ++++++----- packages/blockly/core/shortcut_items.ts | 6 +++--- packages/blockly/core/workspace_audio.ts | 4 ++-- packages/blockly/tests/mocha/shortcut_items_test.js | 6 +++--- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/packages/blockly/core/keyboard_navigation_controller.ts b/packages/blockly/core/keyboard_navigation_controller.ts index a8521cbf4a7..37fc447d8ac 100644 --- a/packages/blockly/core/keyboard_navigation_controller.ts +++ b/packages/blockly/core/keyboard_navigation_controller.ts @@ -12,7 +12,8 @@ export class KeyboardNavigationController { /** Whether the user is actively using keyboard navigation. */ private isActive = false; - private levelChangeBeepsEnabled = false; + /** Whether to play audio cues when navigating between scope levels. */ + private scopeChangeAudioCuesEnabled = false; /** Css class name added to body if keyboard nav is active. */ private activeClassName = 'blocklyKeyboardNavigation'; @@ -54,16 +55,16 @@ export class KeyboardNavigationController { * Sets whether or not audio cues should be played when keyboard navigation * transitions between blocks of different nesting levels. */ - setLevelChangeBeepsEnabled(enabled: boolean) { - this.levelChangeBeepsEnabled = enabled; + setScopeChangeAudioCuesEnabled(enabled: boolean) { + this.scopeChangeAudioCuesEnabled = enabled; } /** * Returns whether or not audio cues should be played when keyboard navigation * transitions between blocks of different nesting levels. */ - getLevelChangeBeepsEnabled() { - return this.levelChangeBeepsEnabled; + getScopeChangeAudioCuesEnabled() { + return this.scopeChangeAudioCuesEnabled; } /** Adds or removes the css class that indicates keyboard navigation is active. */ diff --git a/packages/blockly/core/shortcut_items.ts b/packages/blockly/core/shortcut_items.ts index 7fc1716f9c3..bbb38016ca4 100644 --- a/packages/blockly/core/shortcut_items.ts +++ b/packages/blockly/core/shortcut_items.ts @@ -686,7 +686,7 @@ export function registerArrowNavigation() { workspace.getAudioManager().playErrorBeep(); return false; } - workspace.getAudioManager().maybePlayLevelChangeBeep(node); + workspace.getAudioManager().maybePlayScopeChangeAudioCue(node); getFocusManager().focusNode(node); return true; }, @@ -712,7 +712,7 @@ export function registerArrowNavigation() { workspace.getAudioManager().playErrorBeep(); return false; } - workspace.getAudioManager().maybePlayLevelChangeBeep(node); + workspace.getAudioManager().maybePlayScopeChangeAudioCue(node); getFocusManager().focusNode(node); return true; }, @@ -1144,7 +1144,7 @@ export function registerToggleScreenreaderMode() { preconditionFn: () => true, callback: (workspace) => { enabled = !enabled; - keyboardNavigationController.setLevelChangeBeepsEnabled(enabled); + keyboardNavigationController.setScopeChangeAudioCuesEnabled(enabled); workspace.getNavigator().setNavigationLoops(!enabled); workspace.getToolbox()?.getNavigator().setNavigationLoops(!enabled); workspace diff --git a/packages/blockly/core/workspace_audio.ts b/packages/blockly/core/workspace_audio.ts index 5dcbf2e5f85..6b1aa4c1464 100644 --- a/packages/blockly/core/workspace_audio.ts +++ b/packages/blockly/core/workspace_audio.ts @@ -158,8 +158,8 @@ export class WorkspaceAudio { * @internal * @param newNode The soon-to-be-focused node. */ - maybePlayLevelChangeBeep(newNode: IFocusableNode) { - if (!keyboardNavigationController.getLevelChangeBeepsEnabled()) return; + maybePlayScopeChangeAudioCue(newNode: IFocusableNode) { + if (!keyboardNavigationController.getScopeChangeAudioCuesEnabled()) return; const navigator = this.parentWorkspace.getNavigator(); const oldBlock = navigator.getSourceBlockFromNode( getFocusManager().getFocusedNode(), diff --git a/packages/blockly/tests/mocha/shortcut_items_test.js b/packages/blockly/tests/mocha/shortcut_items_test.js index b0c9ac16916..67cabe650c9 100644 --- a/packages/blockly/tests/mocha/shortcut_items_test.js +++ b/packages/blockly/tests/mocha/shortcut_items_test.js @@ -1860,7 +1860,7 @@ suite('Keyboard Shortcut Items', function () { .getNavigationLoops(), ); assert.isFalse( - Blockly.keyboardNavigationController.getLevelChangeBeepsEnabled(), + Blockly.keyboardNavigationController.getScopeChangeAudioCuesEnabled(), ); this.injectionDiv.dispatchEvent(event); @@ -1878,7 +1878,7 @@ suite('Keyboard Shortcut Items', function () { .getNavigationLoops(), ); assert.isTrue( - Blockly.keyboardNavigationController.getLevelChangeBeepsEnabled(), + Blockly.keyboardNavigationController.getScopeChangeAudioCuesEnabled(), ); assert.include(this.liveRegion.textContent, 'Screenreader mode is on'); @@ -1897,7 +1897,7 @@ suite('Keyboard Shortcut Items', function () { .getNavigationLoops(), ); assert.isFalse( - Blockly.keyboardNavigationController.getLevelChangeBeepsEnabled(), + Blockly.keyboardNavigationController.getScopeChangeAudioCuesEnabled(), ); assert.include(this.liveRegion.textContent, 'Screenreader mode is off'); });