From dac8a7daf12f7c4bdfc43b2d423f8f41f19909a9 Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Wed, 13 May 2026 08:32:19 -0700 Subject: [PATCH 1/6] fix: Improve context announcement keyboard shortcuts --- packages/blockly/core/shortcut_items.ts | 98 ++++++++++++++++++++++--- 1 file changed, 87 insertions(+), 11 deletions(-) diff --git a/packages/blockly/core/shortcut_items.ts b/packages/blockly/core/shortcut_items.ts index 1aa800a20d1..62ba01815f5 100644 --- a/packages/blockly/core/shortcut_items.ts +++ b/packages/blockly/core/shortcut_items.ts @@ -6,6 +6,7 @@ // Former goog.module ID: Blockly.ShortcutItems +import {computeAriaLabel} from './block_aria_composer.js'; import {BlockSvg} from './block_svg.js'; import * as clipboard from './clipboard.js'; import {RenderedWorkspaceComment} from './comments.js'; @@ -63,6 +64,7 @@ export enum names { NEXT_STACK = 'next_stack', PREVIOUS_STACK = 'previous_stack', INFORMATION = 'information', + EXTENDED_INFORMATION = 'extended_information', PERFORM_ACTION = 'perform_action', DUPLICATE = 'duplicate', CLEANUP = 'cleanup', @@ -762,19 +764,32 @@ export function registerFocusToolbox() { } /** - * Registers keyboard shortcut to get count of block stacks and comments. + * Registers keyboard shortcut to announce information about the focused + * element. */ -export function registerWorkspaceOverview() { +export function registerReadInformation() { const shortcut: KeyboardShortcut = { name: names.INFORMATION, - preconditionFn: (workspace, scope) => { - const focused = scope.focusedNode; - return focused === workspace; - }, - callback: (_workspace) => { - const workspace = resolveWorkspace(_workspace); - const stackCount = workspace.getTopBlocks().length; - const commentCount = workspace.getTopComments().length; + preconditionFn: () => true, + callback: (workspace) => { + // Read information about the focused block, if any. + const block = workspace + .getNavigator() + .getSourceBlockFromNode(getFocusManager().getFocusedNode()); + if (block) { + const description = computeAriaLabel( + block, + aria.Verbosity.LOQUACIOUS, + true, + ); + aria.announceDynamicAriaState(description); + return true; + } + + // Fall back to describing the state of the workspace. + const rootWorkspace = resolveWorkspace(workspace); + const stackCount = rootWorkspace.getTopBlocks().length; + const commentCount = rootWorkspace.getTopComments().length; // Build base string with block stack count. let baseMsgKey; @@ -811,6 +826,66 @@ export function registerWorkspaceOverview() { ShortcutRegistry.registry.register(shortcut); } +/** + * Registers keyboard shortcut to announce an extended description of the + * focused element. + */ +export function registerReadExtendedInformation() { + const shiftI = ShortcutRegistry.registry.createSerializedKey(KeyCodes.I, [ + KeyCodes.SHIFT, + ]); + const shortcut: KeyboardShortcut = { + name: names.EXTENDED_INFORMATION, + preconditionFn: () => true, + callback: (workspace) => { + const block = workspace + .getNavigator() + .getSourceBlockFromNode(getFocusManager().getFocusedNode()); + if (!block) return false; + + const toAnnounce = []; + // First go up the chain of output connections and start finding parents + // from there because the outputs of a block are read anyway, so we don't + // need to repeat them. + let startBlock = block; + while (startBlock.outputConnection?.isConnected()) { + startBlock = startBlock.getParent()!; + } + + if (startBlock !== block) { + toAnnounce.push( + computeAriaLabel(startBlock, aria.Verbosity.TERSE, true), + ); + } + + let parent = startBlock.getParent(); + while (parent) { + toAnnounce.push(computeAriaLabel(parent, aria.Verbosity.TERSE, true)); + parent = parent.getParent(); + } + + if (toAnnounce.length) { + toAnnounce.reverse(); + if (!block.outputConnection?.isConnected()) { + // The current block was already read out earlier if it has an output + // connection. + toAnnounce.push( + `Current block: ${computeAriaLabel(block, aria.Verbosity.TERSE, true)}`, + ); + } + + aria.announceDynamicAriaState(`Parent blocks: ${toAnnounce.join(',')}`); + } else { + aria.announceDynamicAriaState('Current block has no parent'); + } + return true; + }, + keyCodes: [shiftI], + displayText: () => Msg['SHORTCUTS_EXTENDED_INFORMATION'], + }; + ShortcutRegistry.registry.register(shortcut); +} + /** * Registers keyboard shortcut to disconnect the focused block. */ @@ -1058,7 +1133,8 @@ export function registerKeyboardNavigationShortcuts() { * Registers keyboard shortcuts used to announce screen reader information. */ export function registerScreenReaderShortcuts() { - registerWorkspaceOverview(); + registerReadInformation(); + registerReadExtendedInformation(); } registerDefaultShortcuts(); From 9364af00ff7b668a65c9a9cc5ba1ab76ebd35f76 Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Wed, 13 May 2026 08:32:24 -0700 Subject: [PATCH 2/6] test: Add tests --- .../tests/mocha/shortcut_items_test.js | 102 ++++++++++++++++-- 1 file changed, 94 insertions(+), 8 deletions(-) diff --git a/packages/blockly/tests/mocha/shortcut_items_test.js b/packages/blockly/tests/mocha/shortcut_items_test.js index 581e38b2d30..33c1fc68943 100644 --- a/packages/blockly/tests/mocha/shortcut_items_test.js +++ b/packages/blockly/tests/mocha/shortcut_items_test.js @@ -772,14 +772,14 @@ suite('Keyboard Shortcut Items', function () { }); }); - suite('Workspace Information (I)', function () { + suite('Information (I)', function () { setup(function () { const keyEvent = createKeyDownEvent(Blockly.utils.KeyCodes.I); // Helper to trigger the shortcut and assert the live region text. this.assertAnnouncement = (expected) => { this.injectionDiv.dispatchEvent(keyEvent); // Wait for the live region to update after the event. - this.clock.tick(11); + this.clock.runAll(); // The announcement may include an additional non-breaking space. assert.include(this.liveRegion.textContent, expected); }; @@ -838,12 +838,98 @@ suite('Keyboard Shortcut Items', function () { ); }); - suite('Preconditions', function () { - test('Not called when focus is not on workspace', function () { - this.block = this.workspace.newBlock('stack_block'); - Blockly.getFocusManager().focusNode(this.block); - this.assertAnnouncement(''); - }); + test('Block', function () { + const block = this.workspace.newBlock('controls_if'); + block.initSvg(); + block.render(); + Blockly.getFocusManager().focusNode(block); + this.assertAnnouncement('Begin stack, if, do, has input'); + }); + + test('Icon', function () { + const block = this.workspace.newBlock('controls_if'); + block.initSvg(); + block.render(); + Blockly.getFocusManager().focusNode(block.getIcon(Blockly.icons.IconType.MUTATOR)); + this.assertAnnouncement('Begin stack, if, do, has input'); + }); + + test('Field', function () { + const block = this.workspace.newBlock('logic_boolean'); + block.initSvg(); + block.render(); + Blockly.getFocusManager().focusNode(block.getField('BOOL')); + this.assertAnnouncement('Begin stack, dropdown: true'); + }); + + test('Connection', function () { + const block = this.workspace.newBlock('controls_if'); + block.initSvg(); + block.render(); + Blockly.getFocusManager().focusNode(block.getInput('DO0').connection); + this.assertAnnouncement('Begin stack, if, do, has input'); + }); + }); + + suite('Extended Information (Shift + I)', function () { + setup(function () { + const keyEvent = createKeyDownEvent(Blockly.utils.KeyCodes.I, [Blockly.utils.KeyCodes.SHIFT]); + // Helper to trigger the shortcut and assert the live region text. + this.assertAnnouncement = (expected) => { + this.injectionDiv.dispatchEvent(keyEvent); + // Wait for the live region to update after the event. + this.clock.runAll(); + // The announcement may include an additional non-breaking space. + console.log(this.liveRegion.textContent); + assert.include(this.liveRegion.textContent, expected); + }; + this.liveRegion = document.getElementById('blocklyAriaAnnounce'); + }); + + test('Top level statement block', function () { + const block = this.workspace.newBlock('controls_if'); + block.initSvg(); + block.render(); + Blockly.getFocusManager().focusNode(block); + this.assertAnnouncement('Current block has no parent'); + }); + + test('Top level value block', function () { + const block = this.workspace.newBlock('logic_negate'); + block.initSvg(); + block.render(); + Blockly.getFocusManager().focusNode(block); + this.assertAnnouncement('Current block has no parent'); + }); + + test('Nested statement block', function () { + const ifBlock = this.workspace.newBlock('controls_if'); + const repeatBlock = this.workspace.newBlock('controls_repeat_ext'); + const printBlock = this.workspace.newBlock('text_print'); + for (const block of [ifBlock, repeatBlock, printBlock]) { + block.initSvg(); + block.render(); + } + printBlock.previousConnection.connect(repeatBlock.getInput('DO').connection); + repeatBlock.previousConnection.connect(ifBlock.getInput('DO0').connection); + + Blockly.getFocusManager().focusNode(printBlock); + this.assertAnnouncement('Parent blocks: if, do,repeat, times, do,Current block: print'); + }); + + test('Nested value block', function () { + const andBlock = this.workspace.newBlock('logic_operation'); + const notBlock = this.workspace.newBlock('logic_negate'); + const trueBlock = this.workspace.newBlock('logic_boolean'); + for (const block of [andBlock, notBlock, trueBlock]) { + block.initSvg(); + block.render(); + } + notBlock.outputConnection.connect(andBlock.getInput('B').connection); + trueBlock.outputConnection.connect(notBlock.getInput('BOOL').connection); + + Blockly.getFocusManager().focusNode(trueBlock); + this.assertAnnouncement('Parent blocks: and, not, true'); }); }); From c56e493129f15a2472d1d681bc4d0109b99ce7ab Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Wed, 13 May 2026 09:21:21 -0700 Subject: [PATCH 3/6] fix: Don't use custom input labels --- packages/blockly/core/shortcut_items.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/blockly/core/shortcut_items.ts b/packages/blockly/core/shortcut_items.ts index 62ba01815f5..2c61a29117b 100644 --- a/packages/blockly/core/shortcut_items.ts +++ b/packages/blockly/core/shortcut_items.ts @@ -780,7 +780,7 @@ export function registerReadInformation() { const description = computeAriaLabel( block, aria.Verbosity.LOQUACIOUS, - true, + false, ); aria.announceDynamicAriaState(description); return true; @@ -854,13 +854,13 @@ export function registerReadExtendedInformation() { if (startBlock !== block) { toAnnounce.push( - computeAriaLabel(startBlock, aria.Verbosity.TERSE, true), + computeAriaLabel(startBlock, aria.Verbosity.TERSE, false), ); } let parent = startBlock.getParent(); while (parent) { - toAnnounce.push(computeAriaLabel(parent, aria.Verbosity.TERSE, true)); + toAnnounce.push(computeAriaLabel(parent, aria.Verbosity.TERSE, false)); parent = parent.getParent(); } @@ -870,7 +870,7 @@ export function registerReadExtendedInformation() { // The current block was already read out earlier if it has an output // connection. toAnnounce.push( - `Current block: ${computeAriaLabel(block, aria.Verbosity.TERSE, true)}`, + `Current block: ${computeAriaLabel(block, aria.Verbosity.TERSE, false)}`, ); } From 95e6ef0b3d07d5b478704259dfe5258a762e5e9c Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Wed, 13 May 2026 09:28:45 -0700 Subject: [PATCH 4/6] fix: Use messages --- packages/blockly/core/shortcut_items.ts | 11 ++++++++--- packages/blockly/msg/json/en.json | 8 ++++++-- packages/blockly/msg/json/qqq.json | 20 +++++--------------- packages/blockly/msg/messages.js | 14 +++++++++++++- 4 files changed, 32 insertions(+), 21 deletions(-) diff --git a/packages/blockly/core/shortcut_items.ts b/packages/blockly/core/shortcut_items.ts index 2c61a29117b..39dad5cb0cd 100644 --- a/packages/blockly/core/shortcut_items.ts +++ b/packages/blockly/core/shortcut_items.ts @@ -870,13 +870,18 @@ export function registerReadExtendedInformation() { // The current block was already read out earlier if it has an output // connection. toAnnounce.push( - `Current block: ${computeAriaLabel(block, aria.Verbosity.TERSE, false)}`, + Msg['CURRENT_BLOCK_ANNOUNCEMENT'].replace( + '%1', + computeAriaLabel(block, aria.Verbosity.TERSE, false), + ), ); } - aria.announceDynamicAriaState(`Parent blocks: ${toAnnounce.join(',')}`); + aria.announceDynamicAriaState( + Msg['PARENT_BLOCKS_ANNOUNCEMENT'].replace('%1', toAnnounce.join(',')), + ); } else { - aria.announceDynamicAriaState('Current block has no parent'); + aria.announceDynamicAriaState(Msg['NO_PARENT_ANNOUNCEMENT']); } return true; }, diff --git a/packages/blockly/msg/json/en.json b/packages/blockly/msg/json/en.json index 4f2135a9761..a643fd8f9b7 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 09:26:53.422108", "locale": "en", "messagedocumentation" : "qqq" }, @@ -444,6 +444,7 @@ "SHORTCUTS_FOCUS_WORKSPACE": "Focus workspace", "SHORTCUTS_FOCUS_TOOLBOX": "Focus toolbox", "SHORTCUTS_INFORMATION": "Announce information", + "SHORTCUTS_EXTENDED_INFORMATION": "Announce detailed information", "SHORTCUTS_DISCONNECT": "Disconnect block", "SHORTCUTS_NEXT_STACK": "Next stack", "SHORTCUTS_PREVIOUS_STACK": "Previous stack", @@ -517,5 +518,8 @@ "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", + "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 49e642f935f..bdb9ce1952b 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}}", @@ -452,6 +438,7 @@ "SHORTCUTS_FOCUS_WORKSPACE": "shortcut display text for the focus workspace shortcut, which moves focus to the workspace.", "SHORTCUTS_FOCUS_TOOLBOX": "shortcut display text for the focus toolbox shortcut, which moves focus to the toolbox or flyout.", "SHORTCUTS_INFORMATION": "shortcut display text for the information shortcut, which announces information about a focused element.", + "SHORTCUTS_EXTENDED_INFORMATION": "Description for the Shift-I keyboard shortcut that announces extended context about the currently focused element to screenreaders.", "SHORTCUTS_DISCONNECT": "shortcut display text for the disconnect shortcut, which disconnects a block from its neighbor.", "SHORTCUTS_NEXT_STACK": "shortcut display text for the next stack shortcut, which navigates to the next block stack.", "SHORTCUTS_PREVIOUS_STACK": "shortcut display text for the previous stack shortcut, which navigates to the previous block stack.", @@ -525,5 +512,8 @@ "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.", + "CURRENT_BLOCK_ANNOUNCEMENT": "Screenreader announcement providing context about the currently focused block.", + "PARENT_BLOCKS_ANNOUNCEMENT": "Screenreader announcement providing context about the currently focused block's parents.", + "NO_PARENT_ANNOUNCEMENT": "Screenreader announcement informing users that the currently focused block has no parent blocks." } diff --git a/packages/blockly/msg/messages.js b/packages/blockly/msg/messages.js index 71c3aff5ce8..943750ff4ee 100644 --- a/packages/blockly/msg/messages.js +++ b/packages/blockly/msg/messages.js @@ -1763,6 +1763,9 @@ Blockly.Msg.SHORTCUTS_FOCUS_TOOLBOX = 'Focus toolbox'; /// shortcut display text for the information shortcut, which announces information about a focused element. Blockly.Msg.SHORTCUTS_INFORMATION = 'Announce information'; /** @type {string} */ +/// Description for the Shift-I keyboard shortcut that announces extended context about the currently focused element to screenreaders. +Blockly.Msg.SHORTCUTS_EXTENDED_INFORMATION = 'Announce detailed information'; +/** @type {string} */ /// shortcut display text for the disconnect shortcut, which disconnects a block from its neighbor. Blockly.Msg.SHORTCUTS_DISCONNECT = 'Disconnect block'; /** @type {string} */ @@ -2046,4 +2049,13 @@ 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} */ +/// Screenreader announcement providing context about the currently focused block. +Blockly.Msg.CURRENT_BLOCK_ANNOUNCEMENT = 'Current block: %1'; +/** @type {string} */ +/// Screenreader announcement providing context about the currently focused block's parents. +Blockly.Msg.PARENT_BLOCKS_ANNOUNCEMENT = 'Parent blocks: %1'; +/** @type {string} */ +/// Screenreader announcement informing users that the currently focused block has no parent blocks. +Blockly.Msg.NO_PARENT_ANNOUNCEMENT = 'Current block has no parent'; From 74e44b0cc217bfc3d2798ec78c8bca6934814691 Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Wed, 13 May 2026 09:29:01 -0700 Subject: [PATCH 5/6] chore: Run formatter --- .../tests/mocha/shortcut_items_test.js | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/packages/blockly/tests/mocha/shortcut_items_test.js b/packages/blockly/tests/mocha/shortcut_items_test.js index 33c1fc68943..239b9524fc5 100644 --- a/packages/blockly/tests/mocha/shortcut_items_test.js +++ b/packages/blockly/tests/mocha/shortcut_items_test.js @@ -850,7 +850,9 @@ suite('Keyboard Shortcut Items', function () { const block = this.workspace.newBlock('controls_if'); block.initSvg(); block.render(); - Blockly.getFocusManager().focusNode(block.getIcon(Blockly.icons.IconType.MUTATOR)); + Blockly.getFocusManager().focusNode( + block.getIcon(Blockly.icons.IconType.MUTATOR), + ); this.assertAnnouncement('Begin stack, if, do, has input'); }); @@ -873,7 +875,9 @@ suite('Keyboard Shortcut Items', function () { suite('Extended Information (Shift + I)', function () { setup(function () { - const keyEvent = createKeyDownEvent(Blockly.utils.KeyCodes.I, [Blockly.utils.KeyCodes.SHIFT]); + const keyEvent = createKeyDownEvent(Blockly.utils.KeyCodes.I, [ + Blockly.utils.KeyCodes.SHIFT, + ]); // Helper to trigger the shortcut and assert the live region text. this.assertAnnouncement = (expected) => { this.injectionDiv.dispatchEvent(keyEvent); @@ -910,11 +914,17 @@ suite('Keyboard Shortcut Items', function () { block.initSvg(); block.render(); } - printBlock.previousConnection.connect(repeatBlock.getInput('DO').connection); - repeatBlock.previousConnection.connect(ifBlock.getInput('DO0').connection); + printBlock.previousConnection.connect( + repeatBlock.getInput('DO').connection, + ); + repeatBlock.previousConnection.connect( + ifBlock.getInput('DO0').connection, + ); Blockly.getFocusManager().focusNode(printBlock); - this.assertAnnouncement('Parent blocks: if, do,repeat, times, do,Current block: print'); + this.assertAnnouncement( + 'Parent blocks: if, do,repeat, times, do,Current block: print', + ); }); test('Nested value block', function () { From ae1af196151426d0c379bb79d51399d692833d47 Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Wed, 13 May 2026 09:48:19 -0700 Subject: [PATCH 6/6] refactor: Improve organization of information announcement shortcut --- packages/blockly/core/shortcut_items.ts | 91 +++++++++++++------------ 1 file changed, 49 insertions(+), 42 deletions(-) diff --git a/packages/blockly/core/shortcut_items.ts b/packages/blockly/core/shortcut_items.ts index 39dad5cb0cd..87085e79e9b 100644 --- a/packages/blockly/core/shortcut_items.ts +++ b/packages/blockly/core/shortcut_items.ts @@ -768,57 +768,64 @@ export function registerFocusToolbox() { * element. */ export function registerReadInformation() { + const announceBlockInformation = (block: BlockSvg) => { + const description = computeAriaLabel( + block, + aria.Verbosity.LOQUACIOUS, + false, + ); + aria.announceDynamicAriaState(description); + }; + + const announceWorkspaceInformation = (workspace: WorkspaceSvg) => { + const rootWorkspace = resolveWorkspace(workspace); + const stackCount = rootWorkspace.getTopBlocks().length; + const commentCount = rootWorkspace.getTopComments().length; + + // Build base string with block stack count. + let baseMsgKey; + if (stackCount === 0) { + baseMsgKey = 'WORKSPACE_CONTENTS_BLOCKS_ZERO'; + } else if (stackCount === 1) { + baseMsgKey = 'WORKSPACE_CONTENTS_BLOCKS_ONE'; + } else { + baseMsgKey = 'WORKSPACE_CONTENTS_BLOCKS_MANY'; + } + + // Build comment suffix. + let suffix = ''; + if (commentCount > 0) { + suffix = Msg[ + commentCount === 1 + ? 'WORKSPACE_CONTENTS_COMMENTS_ONE' + : 'WORKSPACE_CONTENTS_COMMENTS_MANY' + ].replace('%1', String(commentCount)); + } + + // Build final message. + const msg = Msg[baseMsgKey] + .replace('%1', String(stackCount)) + .replace('%2', suffix); + + aria.announceDynamicAriaState(msg); + }; + const shortcut: KeyboardShortcut = { name: names.INFORMATION, preconditionFn: () => true, callback: (workspace) => { - // Read information about the focused block, if any. + const focusedNode = getFocusManager().getFocusedNode(); const block = workspace .getNavigator() - .getSourceBlockFromNode(getFocusManager().getFocusedNode()); + .getSourceBlockFromNode(focusedNode); if (block) { - const description = computeAriaLabel( - block, - aria.Verbosity.LOQUACIOUS, - false, - ); - aria.announceDynamicAriaState(description); + announceBlockInformation(block); + return true; + } else if (focusedNode === workspace) { + announceWorkspaceInformation(workspace); return true; } - - // Fall back to describing the state of the workspace. - const rootWorkspace = resolveWorkspace(workspace); - const stackCount = rootWorkspace.getTopBlocks().length; - const commentCount = rootWorkspace.getTopComments().length; - - // Build base string with block stack count. - let baseMsgKey; - if (stackCount === 0) { - baseMsgKey = 'WORKSPACE_CONTENTS_BLOCKS_ZERO'; - } else if (stackCount === 1) { - baseMsgKey = 'WORKSPACE_CONTENTS_BLOCKS_ONE'; - } else { - baseMsgKey = 'WORKSPACE_CONTENTS_BLOCKS_MANY'; - } - - // Build comment suffix. - let suffix = ''; - if (commentCount > 0) { - suffix = Msg[ - commentCount === 1 - ? 'WORKSPACE_CONTENTS_COMMENTS_ONE' - : 'WORKSPACE_CONTENTS_COMMENTS_MANY' - ].replace('%1', String(commentCount)); - } - - // Build final message. - const msg = Msg[baseMsgKey] - .replace('%1', String(stackCount)) - .replace('%2', suffix); - - aria.announceDynamicAriaState(msg); - - return true; + return false; }, keyCodes: [KeyCodes.I], displayText: () => Msg['SHORTCUTS_INFORMATION'],