Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 52 additions & 8 deletions packages/blockly/core/shortcut_items.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ export enum names {
UNDO = 'undo',
REDO = 'redo',
MENU = 'menu',
FOCUS_WORKSPACE = 'focus_workspace',
START_MOVE = 'start_move',
FINISH_MOVE = 'finish_move',
ABORT_MOVE = 'abort_move',
MOVE_UP = 'move_up',
MOVE_DOWN = 'move_down',
MOVE_LEFT = 'move_left',
MOVE_RIGHT = 'move_right',
}

/**
Expand Down Expand Up @@ -391,7 +399,7 @@ export function registerMovementShortcuts() {

const shortcuts: ShortcutRegistry.KeyboardShortcut[] = [
{
name: 'start_move',
name: names.START_MOVE,
preconditionFn: (workspace) => {
const startDraggable = getCurrentDraggable(workspace);
return !!startDraggable && KeyboardMover.mover.canMove(startDraggable);
Expand All @@ -412,23 +420,23 @@ export function registerMovementShortcuts() {
keyCodes: [KeyCodes.M],
},
{
name: 'finish_move',
name: names.FINISH_MOVE,
preconditionFn: () => KeyboardMover.mover.isMoving(),
callback: (_workspace, e) =>
KeyboardMover.mover.finishMove(e as KeyboardEvent),
keyCodes: [KeyCodes.ENTER, KeyCodes.SPACE],
allowCollision: true,
},
{
name: 'abort_move',
name: names.ABORT_MOVE,
preconditionFn: () => KeyboardMover.mover.isMoving(),
callback: (_workspace, e) =>
KeyboardMover.mover.abortMove(e as KeyboardEvent),
keyCodes: [KeyCodes.ESC],
allowCollision: true,
},
{
name: 'move_left',
name: names.MOVE_LEFT,
preconditionFn: () => KeyboardMover.mover.isMoving(),
callback: (_workspace, e) => {
e.preventDefault();
Expand All @@ -443,7 +451,7 @@ export function registerMovementShortcuts() {
allowCollision: true,
},
{
name: 'move_right',
name: names.MOVE_RIGHT,
preconditionFn: () => KeyboardMover.mover.isMoving(),
callback: (_workspace, e) => {
e.preventDefault();
Expand All @@ -458,7 +466,7 @@ export function registerMovementShortcuts() {
allowCollision: true,
},
{
name: 'move_up',
name: names.MOVE_UP,
preconditionFn: () => KeyboardMover.mover.isMoving(),
callback: (_workspace, e) => {
e.preventDefault();
Expand All @@ -473,7 +481,7 @@ export function registerMovementShortcuts() {
allowCollision: true,
},
{
name: 'move_down',
name: names.MOVE_DOWN,
preconditionFn: () => KeyboardMover.mover.isMoving(),
callback: (_workspace, e) => {
e.preventDefault();
Expand Down Expand Up @@ -508,7 +516,7 @@ export function registerShowContextMenu() {
preconditionFn: (workspace) => {
return !workspace.isDragging();
},
callback: (workspace, e) => {
callback: (_workspace, e) => {
const target = getFocusManager().getFocusedNode();
if (hasContextMenu(target)) {
target.showContextMenu(e);
Expand All @@ -523,6 +531,33 @@ export function registerShowContextMenu() {
ShortcutRegistry.registry.register(contextMenuShortcut);
}

/**
* Registers keyboard shortcut to focus the workspace.
*/
export function registerFocusWorkspace() {
const resolveWorkspace = (workspace: WorkspaceSvg) => {
if (workspace.isFlyout) {
const target = workspace.targetWorkspace;
if (target) {
return resolveWorkspace(target);
}
}
return workspace.getRootWorkspace() ?? workspace;
};

const contextMenuShortcut: KeyboardShortcut = {
name: names.FOCUS_WORKSPACE,
preconditionFn: (workspace) => !workspace.isDragging(),
callback: (workspace) => {
keyboardNavigationController.setIsActive(true);
getFocusManager().focusNode(resolveWorkspace(workspace));
return true;
},
keyCodes: [KeyCodes.W],
};
ShortcutRegistry.registry.register(contextMenuShortcut);
}

/**
* Registers all default keyboard shortcut item. This should be called once per
* instance of KeyboardShortcutRegistry.
Expand All @@ -537,8 +572,17 @@ export function registerDefaultShortcuts() {
registerPaste();
registerUndo();
registerRedo();
}

/**
* Registers an extended set of keyboard shortcuts used to support deep keyboard
* navigation of Blockly.
*/
export function registerKeyboardNavigationShortcuts() {
registerShowContextMenu();
registerMovementShortcuts();
registerFocusWorkspace();
}

registerDefaultShortcuts();
registerKeyboardNavigationShortcuts();
64 changes: 63 additions & 1 deletion packages/blockly/tests/mocha/shortcut_items_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ import {createKeyDownEvent} from './test_helpers/user_input.js';
suite('Keyboard Shortcut Items', function () {
setup(function () {
sharedTestSetup.call(this);
this.workspace = Blockly.inject('blocklyDiv', {});
const toolbox = document.getElementById('toolbox-categories');
this.workspace = Blockly.inject('blocklyDiv', {toolbox});
this.injectionDiv = this.workspace.getInjectionDiv();
Blockly.ContextMenuRegistry.registry.reset();
Blockly.ContextMenuItems.registerDefaultOptions();
Expand Down Expand Up @@ -486,4 +487,65 @@ suite('Keyboard Shortcut Items', function () {
);
});
});

suite('Focus Workspace (W)', function () {
setup(function () {
this.testFocusChange = (startingElement) => {
Blockly.getFocusManager().focusNode(startingElement);
assert.strictEqual(
Blockly.getFocusManager().getFocusedNode(),
startingElement,
);
const event = createKeyDownEvent(Blockly.utils.KeyCodes.W);
this.workspace.getInjectionDiv().dispatchEvent(event);
assert.strictEqual(
Blockly.getFocusManager().getFocusedNode(),
this.workspace,
);
};
});

test('Does not change focus when workspace is already focused', function () {
this.testFocusChange(this.workspace);
});

test('Focuses workspace when toolbox is focused', function () {
this.testFocusChange(this.workspace.getToolbox());
});

test('Focuses workspace when flyout is focused', function () {
this.workspace.getToolbox().getFlyout().show();
const flyoutWorkspace = this.workspace
.getToolbox()
.getFlyout()
.getWorkspace();
this.testFocusChange(flyoutWorkspace);
});

test('Focuses workspace when a block is focused', function () {
const block = this.workspace.newBlock('controls_if');
this.testFocusChange(block);
});

suite('With mutator', function () {
test('Focuses root workspace when a mutator block is focused', async function () {
const block = this.workspace.newBlock('controls_if');
const icon = block.getIcon(Blockly.icons.MutatorIcon.TYPE);
await icon.setBubbleVisible(true);
const mutatorWorkspace = icon.getWorkspace();
this.testFocusChange(mutatorWorkspace.getAllBlocks()[0]);
});

test("Focuses workspace when a mutator's flyout is focused", async function () {
const block = this.workspace.newBlock('controls_if');
const icon = block.getIcon(Blockly.icons.MutatorIcon.TYPE);
await icon.setBubbleVisible(true);
const mutatorFlyoutWorkspace = icon
.getWorkspace()
.getFlyout()
.getWorkspace();
this.testFocusChange(mutatorFlyoutWorkspace);
});
});
});
});
Loading