Skip to content

Commit 656624a

Browse files
authored
feat: use keyboard mode tracker from core (#555)
* feat: use keyboard mode tracker from core * feat: enable keyboard mode for toolbox shortcut * chore: add tests * fix: rename test method after rebase * chore: fix tests * chore: try to fix tests
1 parent 306d4fc commit 656624a

File tree

10 files changed

+186
-60
lines changed

10 files changed

+186
-60
lines changed

src/actions/arrow_navigation.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@
44
* SPDX-License-Identifier: Apache-2.0
55
*/
66

7-
import {ShortcutRegistry, utils as BlocklyUtils, Field} from 'blockly/core';
7+
import {
8+
ShortcutRegistry,
9+
utils as BlocklyUtils,
10+
Field,
11+
keyboardNavigationController,
12+
} from 'blockly/core';
813

914
import type {Toolbox, WorkspaceSvg} from 'blockly/core';
1015

@@ -122,6 +127,7 @@ export class ArrowNavigation {
122127
preconditionFn: (workspace) =>
123128
this.navigation.canCurrentlyNavigate(workspace),
124129
callback: (workspace, e, shortcut) => {
130+
keyboardNavigationController.setIsActive(true);
125131
return workspace.RTL
126132
? navigateOut(workspace, e, shortcut)
127133
: navigateIn(workspace, e, shortcut);
@@ -135,6 +141,7 @@ export class ArrowNavigation {
135141
preconditionFn: (workspace) =>
136142
this.navigation.canCurrentlyNavigate(workspace),
137143
callback: (workspace, e, shortcut) => {
144+
keyboardNavigationController.setIsActive(true);
138145
return workspace.RTL
139146
? navigateIn(workspace, e, shortcut)
140147
: navigateOut(workspace, e, shortcut);
@@ -148,6 +155,7 @@ export class ArrowNavigation {
148155
preconditionFn: (workspace) =>
149156
this.navigation.canCurrentlyNavigate(workspace),
150157
callback: (workspace, e, shortcut) => {
158+
keyboardNavigationController.setIsActive(true);
151159
const toolbox = workspace.getToolbox() as Toolbox;
152160
const flyout = workspace.getFlyout();
153161
let isHandled = false;
@@ -205,6 +213,7 @@ export class ArrowNavigation {
205213
preconditionFn: (workspace) =>
206214
this.navigation.canCurrentlyNavigate(workspace),
207215
callback: (workspace, e, shortcut) => {
216+
keyboardNavigationController.setIsActive(true);
208217
const flyout = workspace.getFlyout();
209218
const toolbox = workspace.getToolbox() as Toolbox;
210219
let isHandled = false;

src/actions/disconnect.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
utils as BlocklyUtils,
1212
Connection,
1313
ConnectionType,
14+
keyboardNavigationController,
1415
} from 'blockly';
1516
import * as Constants from '../constants';
1617
import type {WorkspaceSvg} from 'blockly';
@@ -56,6 +57,7 @@ export class DisconnectAction {
5657
preconditionFn: (workspace) =>
5758
this.navigation.canCurrentlyEdit(workspace),
5859
callback: (workspace) => {
60+
keyboardNavigationController.setIsActive(true);
5961
switch (this.navigation.getState(workspace)) {
6062
case Constants.STATE.WORKSPACE:
6163
this.disconnectBlocks(workspace);

src/actions/edit.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@
44
* SPDX-License-Identifier: Apache-2.0
55
*/
66

7-
import {ContextMenuRegistry, LineCursor, Msg} from 'blockly';
7+
import {
8+
ContextMenuRegistry,
9+
LineCursor,
10+
Msg,
11+
keyboardNavigationController,
12+
} from 'blockly';
813
import {Navigation} from 'src/navigation';
914
import {getShortActionShortcut} from '../shortcut_formatting';
1015
import * as Constants from '../constants';
@@ -67,6 +72,7 @@ export class EditAction {
6772
return cursor.atEndOfLine() ? 'hidden' : 'enabled';
6873
},
6974
callback: (scope: ContextMenuRegistry.Scope) => {
75+
keyboardNavigationController.setIsActive(true);
7076
const workspace = scope.block?.workspace;
7177
if (!workspace) return false;
7278
workspace.getCursor()?.in();

src/actions/move.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
ShortcutRegistry,
1212
utils,
1313
WorkspaceSvg,
14+
keyboardNavigationController,
1415
} from 'blockly';
1516
import {Direction} from '../drag_direction';
1617
import {Mover} from './mover';
@@ -40,6 +41,7 @@ export class MoveActions {
4041
return !!startBlock && this.mover.canMove(workspace, startBlock);
4142
},
4243
callback: (workspace) => {
44+
keyboardNavigationController.setIsActive(true);
4345
const startBlock = this.getCurrentBlock(workspace);
4446
return (
4547
!!startBlock && this.mover.startMove(workspace, startBlock, null)

src/actions/ws_movement.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@
44
* SPDX-License-Identifier: Apache-2.0
55
*/
66

7-
import {ShortcutRegistry, utils as BlocklyUtils} from 'blockly';
7+
import {
8+
ShortcutRegistry,
9+
utils as BlocklyUtils,
10+
keyboardNavigationController,
11+
} from 'blockly';
812
import * as Constants from '../constants';
913
import type {WorkspaceSvg} from 'blockly';
1014
import {Navigation} from 'src/navigation';
@@ -66,7 +70,10 @@ export class WorkspaceMovement {
6670
name: Constants.SHORTCUT_NAMES.CREATE_WS_CURSOR,
6771
preconditionFn: (workspace) =>
6872
this.navigation.canCurrentlyEdit(workspace),
69-
callback: (workspace) => this.createWSCursor(workspace),
73+
callback: (workspace) => {
74+
keyboardNavigationController.setIsActive(true);
75+
return this.createWSCursor(workspace);
76+
},
7077
keyCodes: [KeyCodes.W],
7178
},
7279
];

src/index.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import * as Blockly from 'blockly/core';
88
import {NavigationController} from './navigation_controller';
99
import {enableBlocksOnDrag} from './disabled_blocks';
10-
import {InputModeTracker} from './input_mode_tracker';
1110

1211
/** Plugin for keyboard navigation. */
1312
export class KeyboardNavigation {
@@ -20,11 +19,6 @@ export class KeyboardNavigation {
2019
/** Cursor for the main workspace. */
2120
private cursor: Blockly.LineCursor;
2221

23-
/**
24-
* Input mode tracking.
25-
*/
26-
private inputModeTracker: InputModeTracker;
27-
2822
/**
2923
* Focus ring in the workspace.
3024
*/
@@ -54,7 +48,6 @@ export class KeyboardNavigation {
5448
this.navigationController.init();
5549
this.navigationController.addWorkspace(workspace);
5650
this.navigationController.enable(workspace);
57-
this.inputModeTracker = new InputModeTracker(workspace);
5851

5952
this.cursor = new Blockly.LineCursor(workspace);
6053

@@ -124,7 +117,6 @@ export class KeyboardNavigation {
124117
// Remove the event listener that enables blocks on drag
125118
this.workspace.removeChangeListener(enableBlocksOnDrag);
126119
this.navigationController.dispose();
127-
this.inputModeTracker.dispose();
128120
}
129121

130122
/**

src/input_mode_tracker.ts

Lines changed: 0 additions & 48 deletions
This file was deleted.

src/navigation_controller.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
Toolbox,
1919
utils as BlocklyUtils,
2020
WorkspaceSvg,
21+
keyboardNavigationController,
2122
} from 'blockly/core';
2223

2324
import * as Constants from './constants';
@@ -198,6 +199,7 @@ export class NavigationController {
198199
preconditionFn: (workspace) =>
199200
!workspace.isDragging() && this.navigation.canCurrentlyEdit(workspace),
200201
callback: (workspace) => {
202+
keyboardNavigationController.setIsActive(true);
201203
switch (this.navigation.getState(workspace)) {
202204
case Constants.STATE.WORKSPACE:
203205
Blockly.getFocusManager().focusTree(

test/webdriverio/test/basic_test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ suite('Keyboard navigation on Blocks', function () {
4343

4444
test('Selected block', async function () {
4545
await tabNavigateToWorkspace(this.browser);
46+
await this.browser.pause(PAUSE_TIME);
4647

4748
await keyDown(this.browser, 14);
4849

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
/**
2+
* @license
3+
* Copyright 2025 Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
import * as chai from 'chai';
8+
import * as Blockly from 'blockly';
9+
import {
10+
focusOnBlock,
11+
testSetup,
12+
testFileLocations,
13+
PAUSE_TIME,
14+
getBlockElementById,
15+
tabNavigateToWorkspace,
16+
} from './test_setup.js';
17+
import {Key} from 'webdriverio';
18+
19+
const isKeyboardNavigating = function (browser: WebdriverIO.Browser) {
20+
return browser.execute(() => {
21+
return document.body.classList.contains('blocklyKeyboardNavigation');
22+
});
23+
};
24+
25+
suite(
26+
'Keyboard navigation mode set on mouse or keyboard interaction',
27+
function () {
28+
// Setting timeout to unlimited as these tests take a longer time to run than most mocha tests
29+
this.timeout(0);
30+
31+
setup(async function () {
32+
// Reload the page between tests
33+
this.browser = await testSetup(testFileLocations.NAVIGATION_TEST_BLOCKS);
34+
35+
await this.browser.pause(PAUSE_TIME);
36+
37+
// Reset the keyboard navigation state between tests.
38+
await this.browser.execute(() => {
39+
Blockly.keyboardNavigationController.setIsActive(false);
40+
});
41+
42+
// Start with the workspace focused.
43+
await tabNavigateToWorkspace(this.browser);
44+
});
45+
46+
test('T to open toolbox enables keyboard mode', async function () {
47+
await this.browser.pause(PAUSE_TIME);
48+
await this.browser.keys('t');
49+
await this.browser.pause(PAUSE_TIME);
50+
51+
chai.assert.isTrue(await isKeyboardNavigating(this.browser));
52+
});
53+
54+
test('M for move mode enables keyboard mode', async function () {
55+
await focusOnBlock(this.browser, 'controls_if_2');
56+
await this.browser.pause(PAUSE_TIME);
57+
await this.browser.keys('m');
58+
59+
chai.assert.isTrue(await isKeyboardNavigating(this.browser));
60+
});
61+
62+
test('W for workspace cursor enables keyboard mode', async function () {
63+
await this.browser.pause(PAUSE_TIME);
64+
await this.browser.keys('w');
65+
await this.browser.pause(PAUSE_TIME);
66+
67+
chai.assert.isTrue(await isKeyboardNavigating(this.browser));
68+
});
69+
70+
test('X to disconnect enables keyboard mode', async function () {
71+
await focusOnBlock(this.browser, 'controls_if_2');
72+
await this.browser.pause(PAUSE_TIME);
73+
await this.browser.keys('x');
74+
await this.browser.pause(PAUSE_TIME);
75+
76+
chai.assert.isTrue(await isKeyboardNavigating(this.browser));
77+
});
78+
79+
test('Copy does not change keyboard mode state', async function () {
80+
// Make sure we're on a copyable block so that copy occurs
81+
await focusOnBlock(this.browser, 'controls_if_2');
82+
await this.browser.pause(PAUSE_TIME);
83+
await this.browser.keys(Key.Ctrl);
84+
await this.browser.keys('c');
85+
await this.browser.keys(Key.Ctrl); // release ctrl key
86+
await this.browser.pause(PAUSE_TIME);
87+
88+
chai.assert.isFalse(await isKeyboardNavigating(this.browser));
89+
90+
this.browser.execute(() => {
91+
Blockly.keyboardNavigationController.setIsActive(true);
92+
});
93+
94+
await this.browser.pause(PAUSE_TIME);
95+
await this.browser.keys(Key.Ctrl);
96+
await this.browser.keys('c');
97+
await this.browser.keys(Key.Ctrl); // release ctrl key
98+
await this.browser.pause(PAUSE_TIME);
99+
100+
chai.assert.isTrue(await isKeyboardNavigating(this.browser));
101+
});
102+
103+
test('Delete does not change keyboard mode state', async function () {
104+
// Make sure we're on a deletable block so that delete occurs
105+
await focusOnBlock(this.browser, 'controls_if_2');
106+
await this.browser.pause(PAUSE_TIME);
107+
await this.browser.keys(Key.Backspace);
108+
await this.browser.pause(PAUSE_TIME);
109+
110+
chai.assert.isFalse(await isKeyboardNavigating(this.browser));
111+
112+
this.browser.execute(() => {
113+
Blockly.keyboardNavigationController.setIsActive(true);
114+
});
115+
116+
// Focus a different deletable block
117+
await focusOnBlock(this.browser, 'controls_if_1');
118+
await this.browser.pause(PAUSE_TIME);
119+
await this.browser.keys(Key.Backspace);
120+
await this.browser.pause(PAUSE_TIME);
121+
122+
chai.assert.isTrue(await isKeyboardNavigating(this.browser));
123+
});
124+
125+
test('Right clicking a block disables keyboard mode', async function () {
126+
await this.browser.execute(() => {
127+
Blockly.keyboardNavigationController.setIsActive(true);
128+
});
129+
130+
await this.browser.pause(PAUSE_TIME);
131+
// Right click a block
132+
const element = await getBlockElementById(this.browser, 'controls_if_1');
133+
await element.click({button: 'right'});
134+
await this.browser.pause(PAUSE_TIME);
135+
136+
chai.assert.isFalse(await isKeyboardNavigating(this.browser));
137+
});
138+
139+
test('Dragging a block with mouse disables keyboard mode', async function () {
140+
await this.browser.execute(() => {
141+
Blockly.keyboardNavigationController.setIsActive(true);
142+
});
143+
144+
await this.browser.pause(PAUSE_TIME);
145+
// Drag a block
146+
const element = await getBlockElementById(this.browser, 'controls_if_1');
147+
await element.dragAndDrop({x: 10, y: 10});
148+
await this.browser.pause(PAUSE_TIME);
149+
150+
chai.assert.isFalse(await isKeyboardNavigating(this.browser));
151+
});
152+
},
153+
);

0 commit comments

Comments
 (0)