diff --git a/packages/blockly/core/shortcut_items.ts b/packages/blockly/core/shortcut_items.ts index 6b533e8bc..e8334f0aa 100644 --- a/packages/blockly/core/shortcut_items.ts +++ b/packages/blockly/core/shortcut_items.ts @@ -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(); } /** diff --git a/packages/blockly/core/tooltip.ts b/packages/blockly/core/tooltip.ts index 3c2a37fe6..3a70bc12c 100644 --- a/packages/blockly/core/tooltip.ts +++ b/packages/blockly/core/tooltip.ts @@ -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(). diff --git a/packages/blockly/msg/json/en.json b/packages/blockly/msg/json/en.json index 12fbfe682..349ba11df 100644 --- a/packages/blockly/msg/json/en.json +++ b/packages/blockly/msg/json/en.json @@ -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.", diff --git a/packages/blockly/msg/json/qqq.json b/packages/blockly/msg/json/qqq.json index d2fcf86cb..d3f6acb59 100644 --- a/packages/blockly/msg/json/qqq.json +++ b/packages/blockly/msg/json/qqq.json @@ -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.", diff --git a/packages/blockly/msg/messages.js b/packages/blockly/msg/messages.js index d5cd502d7..1dd563e22 100644 --- a/packages/blockly/msg/messages.js +++ b/packages/blockly/msg/messages.js @@ -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'; diff --git a/packages/blockly/tests/mocha/shortcut_items_test.js b/packages/blockly/tests/mocha/shortcut_items_test.js index 7b92b534c..53c284e4d 100644 --- a/packages/blockly/tests/mocha/shortcut_items_test.js +++ b/packages/blockly/tests/mocha/shortcut_items_test.js @@ -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()); + }); + }); });