Skip to content

Commit 37f3650

Browse files
committed
feat: Add keyboard shortcuts to navigate between stacks
1 parent 15a4d50 commit 37f3650

File tree

2 files changed

+75
-1
lines changed

2 files changed

+75
-1
lines changed

packages/blockly/core/keyboard_nav/navigators/navigator.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,7 @@ export class Navigator {
456456
/**
457457
* Returns the next/previous stack relative to the given element's stack.
458458
*
459+
* @internal
459460
* @param current The element whose stack will be navigated relative to.
460461
* @param delta The difference in index to navigate; positive values navigate
461462
* to the nth next stack, while negative values navigate to the nth
@@ -464,7 +465,7 @@ export class Navigator {
464465
* current element's stack, or the last element in the stack offset by
465466
* `delta` relative to the current element's stack when navigating backwards.
466467
*/
467-
protected navigateStacks(current: IFocusableNode, delta: number) {
468+
navigateStacks(current: IFocusableNode, delta: number) {
468469
const stacks = this.getTopLevelItems(current);
469470
const root =
470471
this.getSourceBlockFromNode(current)?.getRootBlock() ?? current;

packages/blockly/core/shortcut_items.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {isCopyable as isICopyable} from './interfaces/i_copyable.js';
1717
import {isDeletable as isIDeletable} from './interfaces/i_deletable.js';
1818
import {type IDraggable, isDraggable} from './interfaces/i_draggable.js';
1919
import {type IFocusableNode} from './interfaces/i_focusable_node.js';
20+
import {isSelectable} from './interfaces/i_selectable.js';
2021
import {Direction, KeyboardMover} from './keyboard_nav/keyboard_mover.js';
2122
import {keyboardNavigationController} from './keyboard_navigation_controller.js';
2223
import {KeyboardShortcut, ShortcutRegistry} from './shortcut_registry.js';
@@ -53,6 +54,8 @@ export enum names {
5354
NAVIGATE_UP = 'up',
5455
NAVIGATE_DOWN = 'down',
5556
DISCONNECT = 'disconnect',
57+
NEXT_STACK = 'next_stack',
58+
PREVIOUS_STACK = 'previous_stack',
5659
}
5760

5861
/**
@@ -716,6 +719,75 @@ export function registerDisconnectBlock() {
716719
ShortcutRegistry.registry.register(disconnectShortcut);
717720
}
718721

722+
/**
723+
* Registers keyboard shortcuts to jump between stacks/top-level items on the
724+
* workspace.
725+
*/
726+
export function registerStackNavigation() {
727+
/**
728+
* Finds the stack root of the currently focused or specified item.
729+
*/
730+
const resolveStack = (
731+
workspace: WorkspaceSvg,
732+
node = getFocusManager().getFocusedNode(),
733+
) => {
734+
const navigator = workspace.getNavigator();
735+
736+
for (
737+
let parent: IFocusableNode | null = node;
738+
parent && parent !== workspace;
739+
parent = navigator.getParent(node)
740+
) {
741+
node = parent;
742+
}
743+
744+
if (!isSelectable(node)) return null;
745+
746+
return node;
747+
};
748+
749+
const nextStackShortcut: KeyboardShortcut = {
750+
name: names.NEXT_STACK,
751+
preconditionFn: (workspace) =>
752+
!workspace.isDragging() && !!resolveStack(workspace),
753+
callback: (workspace) => {
754+
keyboardNavigationController.setIsActive(true);
755+
const start = resolveStack(workspace);
756+
if (!start) return false;
757+
const target = workspace.getNavigator().navigateStacks(start, 1);
758+
if (!target) return false;
759+
getFocusManager().focusNode(target);
760+
return true;
761+
},
762+
keyCodes: [KeyCodes.N],
763+
};
764+
765+
const previousStackShortcut: KeyboardShortcut = {
766+
name: names.PREVIOUS_STACK,
767+
preconditionFn: (workspace) =>
768+
!workspace.isDragging() && !!resolveStack(workspace),
769+
callback: (workspace) => {
770+
keyboardNavigationController.setIsActive(true);
771+
const start = resolveStack(workspace);
772+
if (!start) return false;
773+
// navigateStacks() returns the last connection in the stack when going
774+
// backwards, but we want the root block, so resolve the stack from the
775+
// element we get back.
776+
const target = resolveStack(
777+
workspace,
778+
workspace.getNavigator().navigateStacks(start, -1),
779+
);
780+
if (!target) return false;
781+
getFocusManager().focusNode(target);
782+
return true;
783+
},
784+
keyCodes: [KeyCodes.B],
785+
};
786+
787+
ShortcutRegistry.registry.register(nextStackShortcut);
788+
ShortcutRegistry.registry.register(previousStackShortcut);
789+
}
790+
719791
/**
720792
* Registers all default keyboard shortcut item. This should be called once per
721793
* instance of KeyboardShortcutRegistry.
@@ -743,6 +815,7 @@ export function registerKeyboardNavigationShortcuts() {
743815
registerFocusToolbox();
744816
registerArrowNavigation();
745817
registerDisconnectBlock();
818+
registerStackNavigation();
746819
}
747820

748821
registerDefaultShortcuts();

0 commit comments

Comments
 (0)