feat: Added keyboard shortcut for displaying tooltip (#9755)

* feat: Added keyboard shortcut for displaying tooltip

* fix for tooltips not appearing near the focused block
This commit is contained in:
lizschwab
2026-04-24 15:56:41 -07:00
committed by GitHub
parent c211a8955b
commit 6d2a62ceb5
6 changed files with 121 additions and 0 deletions
+29
View File
@@ -23,6 +23,7 @@ import {Direction, KeyboardMover} from './keyboard_nav/keyboard_mover.js';
import {keyboardNavigationController} from './keyboard_navigation_controller.js';
import {Msg} from './msg.js';
import {KeyboardShortcut, ShortcutRegistry} from './shortcut_registry.js';
import * as Tooltip from './tooltip.js';
import {aria} from './utils.js';
import {Coordinate} from './utils/coordinate.js';
import {KeyCodes} from './utils/keycodes.js';
@@ -64,6 +65,7 @@ export enum names {
PERFORM_ACTION = 'perform_action',
DUPLICATE = 'duplicate',
CLEANUP = 'cleanup',
SHOW_TOOLTIP = 'show_tooltip',
}
/**
@@ -973,6 +975,32 @@ export function registerCleanup() {
ShortcutRegistry.registry.register(cleanupShortcut);
}
/**
* Registers keyboard shortcut to display the tooltip for the focused element.
*/
export function registerShowTooltip() {
const ctrlJ = ShortcutRegistry.registry.createSerializedKey(KeyCodes.J, [
KeyCodes.CTRL_CMD,
]);
const showTooltip: KeyboardShortcut = {
name: names.SHOW_TOOLTIP,
preconditionFn: (workspace) => !workspace.isDragging(),
callback: (workspace) => {
const target = getFocusManager().getFocusedNode();
if (target !== null) {
keyboardNavigationController.setIsActive(true);
Tooltip.display(target, workspace);
}
return true;
},
keyCodes: [ctrlJ],
allowCollision: true,
displayText: () => Msg['SHORTCUTS_SHOW_TOOLTIP'],
};
ShortcutRegistry.registry.register(showTooltip);
}
/**
* Registers all default keyboard shortcut item. This should be called once per
* instance of KeyboardShortcutRegistry.
@@ -1004,6 +1032,7 @@ export function registerKeyboardNavigationShortcuts() {
registerPerformAction();
registerDuplicate();
registerCleanup();
registerShowTooltip();
}
/**
+36
View File
@@ -8,6 +8,7 @@
import * as browserEvents from './browser_events.js';
import * as common from './common.js';
import type {IFocusableNode} from './interfaces/i_focusable_node.js';
import * as blocklyString from './utils/string.js';
import type {WorkspaceSvg} from './workspace_svg.js';
@@ -363,6 +364,41 @@ export function hide() {
}
}
/**
* Display the tooltip for a given target.
*
* @internal
* @param target The node upon which the tooltip should be displayed.
* @param workspace The target node's workspace.
*/
export function display(target: IFocusableNode, workspace?: WorkspaceSvg) {
// If the target is not the same element currently displaying a tooltip, hide
// the existing tooltip and set the target as our element.
if (element !== target) {
hide();
poisonedElement = null;
element = target;
}
if (!element || !(element as AnyDuringMigration).tooltip) {
// No tooltip here to show.
return;
} else if (blocked) {
// Someone doesn't want us to show tooltips. We are probably handling a
// user gesture, such as a click or drag.
return;
}
// Set the position to just below the element with horizontal alignment based
// on the target's RTL/LTR orientation.
const targetRect = target.getFocusableElement().getBoundingClientRect();
const rtl = element.RTL;
lastX = rtl ? targetRect.x + targetRect.width : targetRect.x;
lastY = targetRect.y + targetRect.height;
show(workspace);
}
/**
* Hide any in-progress tooltips and block showing new tooltips until the next
* call to unblock().
+1
View File
@@ -451,6 +451,7 @@
"SHORTCUTS_PERFORM_ACTION": "Edit or confirm",
"SHORTCUTS_DUPLICATE": "Duplicate",
"SHORTCUTS_CLEANUP": "Clean up workspace",
"SHORTCUTS_SHOW_TOOLTIP": "Show tooltip",
"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.",
+1
View File
@@ -458,6 +458,7 @@
"SHORTCUTS_PERFORM_ACTION": "shortcut display text for the perform action shortcut, which triggers an action on the focused element.",
"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.",
"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.",
+3
View File
@@ -1786,6 +1786,9 @@ Blockly.Msg.SHORTCUTS_DUPLICATE = 'Duplicate';
/// shortcut display text for the cleanup shortcut, which organizes blocks on the workspace.
Blockly.Msg.SHORTCUTS_CLEANUP = 'Clean up workspace';
/** @type {string} */
/// 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} */
/// 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';
@@ -1435,4 +1435,55 @@ suite('Keyboard Shortcut Items', function () {
assert.deepEqual(oldBounds, newBounds);
});
});
suite('Show tooltip (Ctrl/Cmd+J)', function () {
const event = createKeyDownEvent(Blockly.utils.KeyCodes.J, [
Blockly.utils.KeyCodes.CTRL_CMD,
]);
test('Displays tooltip on a block using the keyboard shortcut', function () {
const block = this.workspace.newBlock('controls_if');
Blockly.getFocusManager().focusNode(block);
block.setTooltip('Tooltip Text');
this.injectionDiv.dispatchEvent(event);
assert.isTrue(Blockly.Tooltip.isVisible());
});
test('Displays new tooltip on a block using the keyboard shortcut if tooltip for another block is already displayed', function () {
const block1 = this.workspace.newBlock('controls_if');
const block2 = this.workspace.newBlock('logic_compare');
block1.setTooltip('block1');
block2.setTooltip('block2');
// Set focus to block1 and show its tooltip
Blockly.getFocusManager().focusNode(block1);
this.injectionDiv.dispatchEvent(event);
// We have block1 focused; we should see block1's tooltip
assert.isTrue(Blockly.Tooltip.isVisible());
assert.isTrue(Blockly.Tooltip.getDiv().innerText === 'block1');
// Set focus to block2 and show its tooltip
Blockly.getFocusManager().focusNode(block2);
this.injectionDiv.dispatchEvent(event);
// Now we have block2 focused; we should see block2's tooltip
assert.isTrue(Blockly.Tooltip.isVisible());
assert.isTrue(Blockly.Tooltip.getDiv().innerText === 'block2');
});
test('Do not show tooltip if drag in progress', function () {
sinon.stub(this.workspace, 'isDragging').returns(true);
this.injectionDiv.dispatchEvent(event);
const block = this.workspace.newBlock('controls_if');
Blockly.getFocusManager().focusNode(block);
block.setTooltip('Tooltip Text');
this.injectionDiv.dispatchEvent(event);
assert.isFalse(Blockly.Tooltip.isVisible());
});
});
});