@@ -17,6 +17,7 @@ import {isCopyable as isICopyable} from './interfaces/i_copyable.js';
1717import { isDeletable as isIDeletable } from './interfaces/i_deletable.js' ;
1818import { type IDraggable , isDraggable } from './interfaces/i_draggable.js' ;
1919import { type IFocusableNode } from './interfaces/i_focusable_node.js' ;
20+ import { isSelectable } from './interfaces/i_selectable.js' ;
2021import { Direction , KeyboardMover } from './keyboard_nav/keyboard_mover.js' ;
2122import { keyboardNavigationController } from './keyboard_navigation_controller.js' ;
2223import { 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
748821registerDefaultShortcuts ( ) ;
0 commit comments