Skip to content

Commit 8522bff

Browse files
authored
feat: Make navigation looping configurable (#9511)
* feat: Make navigation looping configurable * chore: Add TODO to clean up API
1 parent f35f4d8 commit 8522bff

File tree

2 files changed

+60
-13
lines changed

2 files changed

+60
-13
lines changed

core/keyboard_nav/block_navigation_policy.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -181,20 +181,24 @@ function getBlockNavigationCandidates(
181181
* `delta` relative to the current element's stack when navigating backwards.
182182
*/
183183
export function navigateStacks(current: ISelectable, delta: number) {
184-
const stacks: IFocusableNode[] = (current.workspace as WorkspaceSvg)
184+
const workspace = current.workspace as WorkspaceSvg;
185+
const stacks: IFocusableNode[] = workspace
185186
.getTopBoundedElements(true)
186187
.filter((element: IBoundedElement) => isFocusableNode(element));
187188
const currentIndex = stacks.indexOf(
188189
current instanceof BlockSvg ? current.getRootBlock() : current,
189190
);
190191
const targetIndex = currentIndex + delta;
191192
let result: IFocusableNode | null = null;
193+
const loop = workspace.getCursor().getNavigationLoops();
192194
if (targetIndex >= 0 && targetIndex < stacks.length) {
193195
result = stacks[targetIndex];
194-
} else if (targetIndex < 0) {
196+
} else if (loop && targetIndex < 0) {
195197
result = stacks[stacks.length - 1];
196-
} else if (targetIndex >= stacks.length) {
198+
} else if (loop && targetIndex >= stacks.length) {
197199
result = stacks[0];
200+
} else {
201+
return null;
198202
}
199203

200204
// When navigating to a previous block stack, our previous sibling is the last

core/keyboard_nav/line_cursor.ts

Lines changed: 53 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ export class LineCursor extends Marker {
4242
/** Locations to try moving the cursor to after a deletion. */
4343
private potentialNodes: IFocusableNode[] | null = null;
4444

45+
/** Whether or not navigation loops around when reaching the end. */
46+
private navigationLoops = true;
47+
4548
/**
4649
* @param workspace The workspace this cursor belongs to.
4750
*/
@@ -64,7 +67,7 @@ export class LineCursor extends Marker {
6467
const newNode = this.getNextNode(
6568
curNode,
6669
this.getValidationFunction(NavigationDirection.NEXT),
67-
true,
70+
this.getNavigationLoops(),
6871
);
6972

7073
if (newNode) {
@@ -89,7 +92,7 @@ export class LineCursor extends Marker {
8992
const newNode = this.getNextNode(
9093
curNode,
9194
this.getValidationFunction(NavigationDirection.IN),
92-
true,
95+
this.getNavigationLoops(),
9396
);
9497

9598
if (newNode) {
@@ -112,7 +115,7 @@ export class LineCursor extends Marker {
112115
const newNode = this.getPreviousNode(
113116
curNode,
114117
this.getValidationFunction(NavigationDirection.PREVIOUS),
115-
true,
118+
this.getNavigationLoops(),
116119
);
117120

118121
if (newNode) {
@@ -137,7 +140,7 @@ export class LineCursor extends Marker {
137140
const newNode = this.getPreviousNode(
138141
curNode,
139142
this.getValidationFunction(NavigationDirection.OUT),
140-
true,
143+
this.getNavigationLoops(),
141144
);
142145

143146
if (newNode) {
@@ -158,12 +161,12 @@ export class LineCursor extends Marker {
158161
const inNode = this.getNextNode(
159162
curNode,
160163
this.getValidationFunction(NavigationDirection.IN),
161-
true,
164+
this.getNavigationLoops(),
162165
);
163166
const nextNode = this.getNextNode(
164167
curNode,
165168
this.getValidationFunction(NavigationDirection.NEXT),
166-
true,
169+
this.getNavigationLoops(),
167170
);
168171

169172
return inNode === nextNode;
@@ -219,11 +222,22 @@ export class LineCursor extends Marker {
219222
getNextNode(
220223
node: IFocusableNode | null,
221224
isValid: (p1: IFocusableNode | null) => boolean,
225+
// TODO: Consider deprecating and removing this argument.
222226
loop: boolean,
223227
): IFocusableNode | null {
224-
if (!node || (!loop && this.getLastNode() === node)) return null;
228+
const originalLoop = this.getNavigationLoops();
229+
this.setNavigationLoops(loop);
230+
231+
let result: IFocusableNode | null;
232+
if (!node || (!loop && this.getLastNode() === node)) {
233+
result = null;
234+
} else {
235+
result = this.getNextNodeImpl(node, isValid);
236+
}
225237

226-
return this.getNextNodeImpl(node, isValid);
238+
this.setNavigationLoops(originalLoop);
239+
240+
return result;
227241
}
228242

229243
/**
@@ -273,11 +287,22 @@ export class LineCursor extends Marker {
273287
getPreviousNode(
274288
node: IFocusableNode | null,
275289
isValid: (p1: IFocusableNode | null) => boolean,
290+
// TODO: Consider deprecating and removing this argument.
276291
loop: boolean,
277292
): IFocusableNode | null {
278-
if (!node || (!loop && this.getFirstNode() === node)) return null;
293+
const originalLoop = this.getNavigationLoops();
294+
this.setNavigationLoops(loop);
295+
296+
let result: IFocusableNode | null;
297+
if (!node || (!loop && this.getFirstNode() === node)) {
298+
result = null;
299+
} else {
300+
result = this.getPreviousNodeImpl(node, isValid);
301+
}
279302

280-
return this.getPreviousNodeImpl(node, isValid);
303+
this.setNavigationLoops(originalLoop);
304+
305+
return result;
281306
}
282307

283308
/**
@@ -538,6 +563,24 @@ export class LineCursor extends Marker {
538563
const first = this.getFirstNode();
539564
return this.getPreviousNode(first, () => true, true);
540565
}
566+
567+
/**
568+
* Sets whether or not navigation should loop around when reaching the end
569+
* of the workspace.
570+
*
571+
* @param loops True if navigation should loop around, otherwise false.
572+
*/
573+
setNavigationLoops(loops: boolean) {
574+
this.navigationLoops = loops;
575+
}
576+
577+
/**
578+
* Returns whether or not navigation loops around when reaching the end of
579+
* the workspace.
580+
*/
581+
getNavigationLoops(): boolean {
582+
return this.navigationLoops;
583+
}
541584
}
542585

543586
registry.register(registry.Type.CURSOR, registry.DEFAULT, LineCursor);

0 commit comments

Comments
 (0)