diff --git a/core/keyboard_nav/block_navigation_policy.ts b/core/keyboard_nav/block_navigation_policy.ts index f79efcf2529..3449c73f534 100644 --- a/core/keyboard_nav/block_navigation_policy.ts +++ b/core/keyboard_nav/block_navigation_policy.ts @@ -181,7 +181,8 @@ function getBlockNavigationCandidates( * `delta` relative to the current element's stack when navigating backwards. */ export function navigateStacks(current: ISelectable, delta: number) { - const stacks: IFocusableNode[] = (current.workspace as WorkspaceSvg) + const workspace = current.workspace as WorkspaceSvg; + const stacks: IFocusableNode[] = workspace .getTopBoundedElements(true) .filter((element: IBoundedElement) => isFocusableNode(element)); const currentIndex = stacks.indexOf( @@ -189,12 +190,15 @@ export function navigateStacks(current: ISelectable, delta: number) { ); const targetIndex = currentIndex + delta; let result: IFocusableNode | null = null; + const loop = workspace.getCursor().getNavigationLoops(); if (targetIndex >= 0 && targetIndex < stacks.length) { result = stacks[targetIndex]; - } else if (targetIndex < 0) { + } else if (loop && targetIndex < 0) { result = stacks[stacks.length - 1]; - } else if (targetIndex >= stacks.length) { + } else if (loop && targetIndex >= stacks.length) { result = stacks[0]; + } else { + return null; } // When navigating to a previous block stack, our previous sibling is the last diff --git a/core/keyboard_nav/line_cursor.ts b/core/keyboard_nav/line_cursor.ts index 549e51a9352..fe2779e48a9 100644 --- a/core/keyboard_nav/line_cursor.ts +++ b/core/keyboard_nav/line_cursor.ts @@ -42,6 +42,9 @@ export class LineCursor extends Marker { /** Locations to try moving the cursor to after a deletion. */ private potentialNodes: IFocusableNode[] | null = null; + /** Whether or not navigation loops around when reaching the end. */ + private navigationLoops = true; + /** * @param workspace The workspace this cursor belongs to. */ @@ -64,7 +67,7 @@ export class LineCursor extends Marker { const newNode = this.getNextNode( curNode, this.getValidationFunction(NavigationDirection.NEXT), - true, + this.getNavigationLoops(), ); if (newNode) { @@ -89,7 +92,7 @@ export class LineCursor extends Marker { const newNode = this.getNextNode( curNode, this.getValidationFunction(NavigationDirection.IN), - true, + this.getNavigationLoops(), ); if (newNode) { @@ -112,7 +115,7 @@ export class LineCursor extends Marker { const newNode = this.getPreviousNode( curNode, this.getValidationFunction(NavigationDirection.PREVIOUS), - true, + this.getNavigationLoops(), ); if (newNode) { @@ -137,7 +140,7 @@ export class LineCursor extends Marker { const newNode = this.getPreviousNode( curNode, this.getValidationFunction(NavigationDirection.OUT), - true, + this.getNavigationLoops(), ); if (newNode) { @@ -158,12 +161,12 @@ export class LineCursor extends Marker { const inNode = this.getNextNode( curNode, this.getValidationFunction(NavigationDirection.IN), - true, + this.getNavigationLoops(), ); const nextNode = this.getNextNode( curNode, this.getValidationFunction(NavigationDirection.NEXT), - true, + this.getNavigationLoops(), ); return inNode === nextNode; @@ -219,11 +222,22 @@ export class LineCursor extends Marker { getNextNode( node: IFocusableNode | null, isValid: (p1: IFocusableNode | null) => boolean, + // TODO: Consider deprecating and removing this argument. loop: boolean, ): IFocusableNode | null { - if (!node || (!loop && this.getLastNode() === node)) return null; + const originalLoop = this.getNavigationLoops(); + this.setNavigationLoops(loop); + + let result: IFocusableNode | null; + if (!node || (!loop && this.getLastNode() === node)) { + result = null; + } else { + result = this.getNextNodeImpl(node, isValid); + } - return this.getNextNodeImpl(node, isValid); + this.setNavigationLoops(originalLoop); + + return result; } /** @@ -273,11 +287,22 @@ export class LineCursor extends Marker { getPreviousNode( node: IFocusableNode | null, isValid: (p1: IFocusableNode | null) => boolean, + // TODO: Consider deprecating and removing this argument. loop: boolean, ): IFocusableNode | null { - if (!node || (!loop && this.getFirstNode() === node)) return null; + const originalLoop = this.getNavigationLoops(); + this.setNavigationLoops(loop); + + let result: IFocusableNode | null; + if (!node || (!loop && this.getFirstNode() === node)) { + result = null; + } else { + result = this.getPreviousNodeImpl(node, isValid); + } - return this.getPreviousNodeImpl(node, isValid); + this.setNavigationLoops(originalLoop); + + return result; } /** @@ -538,6 +563,24 @@ export class LineCursor extends Marker { const first = this.getFirstNode(); return this.getPreviousNode(first, () => true, true); } + + /** + * Sets whether or not navigation should loop around when reaching the end + * of the workspace. + * + * @param loops True if navigation should loop around, otherwise false. + */ + setNavigationLoops(loops: boolean) { + this.navigationLoops = loops; + } + + /** + * Returns whether or not navigation loops around when reaching the end of + * the workspace. + */ + getNavigationLoops(): boolean { + return this.navigationLoops; + } } registry.register(registry.Type.CURSOR, registry.DEFAULT, LineCursor);