Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
180 changes: 99 additions & 81 deletions core/keyboard_nav/block_navigation_policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@
*/

import {BlockSvg} from '../block_svg.js';
import {ConnectionType} from '../connection_type.js';
import type {Field} from '../field.js';
import type {Icon} from '../icons/icon.js';
import type {IFocusableNode} from '../interfaces/i_focusable_node.js';
import type {INavigationPolicy} from '../interfaces/i_navigation_policy.js';
import {RenderedConnection} from '../rendered_connection.js';
import {WorkspaceSvg} from '../workspace_svg.js';

/**
Expand All @@ -21,21 +24,8 @@ export class BlockNavigationPolicy implements INavigationPolicy<BlockSvg> {
* @returns The first field or input of the given block, if any.
*/
getFirstChild(current: BlockSvg): IFocusableNode | null {
const icons = current.getIcons();
if (icons.length) return icons[0];

for (const input of current.inputList) {
if (!input.isVisible()) {
continue;
}
for (const field of input.fieldRow) {
return field;
}
if (input.connection?.targetBlock())
return input.connection.targetBlock() as BlockSvg;
}

return null;
const candidates = getBlockNavigationCandidates(current);
return candidates[0];
}

/**
Expand Down Expand Up @@ -66,36 +56,10 @@ export class BlockNavigationPolicy implements INavigationPolicy<BlockSvg> {
getNextSibling(current: BlockSvg): IFocusableNode | null {
if (current.nextConnection?.targetBlock()) {
return current.nextConnection?.targetBlock();
}

const parent = this.getParent(current);
let navigatingCrossStacks = false;
let siblings: (BlockSvg | Field)[] = [];
if (parent instanceof BlockSvg) {
for (let i = 0, input; (input = parent.inputList[i]); i++) {
if (!input.isVisible()) {
continue;
}
siblings.push(...input.fieldRow);
const child = input.connection?.targetBlock();
if (child) {
siblings.push(child as BlockSvg);
}
}
} else if (parent instanceof WorkspaceSvg) {
siblings = parent.getTopBlocks(true);
navigatingCrossStacks = true;
} else {
return null;
}

const currentIndex = siblings.indexOf(
navigatingCrossStacks ? current.getRootBlock() : current,
);
if (currentIndex >= 0 && currentIndex < siblings.length - 1) {
return siblings[currentIndex + 1];
} else if (currentIndex === siblings.length - 1 && navigatingCrossStacks) {
return siblings[0];
} else if (current.outputConnection?.targetBlock()) {
return navigateBlock(current, 1);
} else if (this.getParent(current) instanceof WorkspaceSvg) {
return navigateStacks(current, 1);
}

return null;
Expand All @@ -111,44 +75,13 @@ export class BlockNavigationPolicy implements INavigationPolicy<BlockSvg> {
getPreviousSibling(current: BlockSvg): IFocusableNode | null {
if (current.previousConnection?.targetBlock()) {
return current.previousConnection?.targetBlock();
} else if (current.outputConnection?.targetBlock()) {
return navigateBlock(current, -1);
} else if (this.getParent(current) instanceof WorkspaceSvg) {
return navigateStacks(current, -1);
}

const parent = this.getParent(current);
let navigatingCrossStacks = false;
let siblings: (BlockSvg | Field)[] = [];
if (parent instanceof BlockSvg) {
for (let i = 0, input; (input = parent.inputList[i]); i++) {
if (!input.isVisible()) {
continue;
}
siblings.push(...input.fieldRow);
const child = input.connection?.targetBlock();
if (child) {
siblings.push(child as BlockSvg);
}
}
} else if (parent instanceof WorkspaceSvg) {
siblings = parent.getTopBlocks(true);
navigatingCrossStacks = true;
} else {
return null;
}

const currentIndex = siblings.indexOf(current);
let result: IFocusableNode | null = null;
if (currentIndex >= 1) {
result = siblings[currentIndex - 1];
} else if (currentIndex === 0 && navigatingCrossStacks) {
result = siblings[siblings.length - 1];
}

// If navigating to a previous stack, our previous sibling is the last
// block in it.
if (navigatingCrossStacks && result instanceof BlockSvg) {
return result.lastConnectionInStack(false)?.getSourceBlock() ?? result;
}

return result;
return null;
}

/**
Expand All @@ -171,3 +104,88 @@ export class BlockNavigationPolicy implements INavigationPolicy<BlockSvg> {
return current instanceof BlockSvg;
}
}

/**
* Returns a list of the navigable children of the given block.
*
* @param block The block to retrieve the navigable children of.
* @returns A list of navigable/focusable children of the given block.
*/
function getBlockNavigationCandidates(block: BlockSvg): IFocusableNode[] {
const candidates: IFocusableNode[] = block.getIcons();

for (const input of block.inputList) {
if (!input.isVisible()) continue;
candidates.push(...input.fieldRow);
if (input.connection?.targetBlock()) {
candidates.push(input.connection.targetBlock() as BlockSvg);
} else if (input.connection?.type === ConnectionType.INPUT_VALUE) {
candidates.push(input.connection as RenderedConnection);
}
}

return candidates;
}

/**
* Returns the next/previous stack relative to the given block's stack.
*
* @param current The block whose stack will be navigated relative to.
* @param delta The difference in index to navigate; positive values navigate
* to the nth next stack, while negative values navigate to the nth previous
* stack.
* @returns The first block in the stack offset by `delta` relative to the
* current block's stack, or the last block in the stack offset by `delta`
* relative to the current block's stack when navigating backwards.
*/
export function navigateStacks(current: BlockSvg, delta: number) {
const stacks = current.workspace.getTopBlocks(true);
const currentIndex = stacks.indexOf(current.getRootBlock());
const targetIndex = currentIndex + delta;
let result: BlockSvg | null = null;
if (targetIndex >= 0 && targetIndex < stacks.length) {
result = stacks[targetIndex];
} else if (targetIndex < 0) {
result = stacks[stacks.length - 1];
} else if (targetIndex >= stacks.length) {
result = stacks[0];
}

// When navigating to a previous stack, our previous sibling is the last
// block in it.
if (delta < 0 && result) {
return result.lastConnectionInStack(false)?.getSourceBlock() ?? result;
}

return result;
}

/**
* Returns the next navigable item relative to the provided block child.
*
* @param current The navigable block child item to navigate relative to.
* @param delta The difference in index to navigate; positive values navigate
* forward by n, while negative values navigate backwards by n.
* @returns The navigable block child offset by `delta` relative to `current`.
*/
export function navigateBlock(
current: Icon | Field | RenderedConnection | BlockSvg,
delta: number,
): IFocusableNode | null {
const block =
current instanceof BlockSvg
? current.outputConnection.targetBlock()
: current.getSourceBlock();
if (!(block instanceof BlockSvg)) return null;

const candidates = getBlockNavigationCandidates(block);
const currentIndex = candidates.indexOf(current);
if (currentIndex === -1) return null;

const targetIndex = currentIndex + delta;
if (targetIndex >= 0 && targetIndex < candidates.length) {
return candidates[targetIndex];
}

return null;
}
42 changes: 4 additions & 38 deletions core/keyboard_nav/connection_navigation_policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {ConnectionType} from '../connection_type.js';
import type {IFocusableNode} from '../interfaces/i_focusable_node.js';
import type {INavigationPolicy} from '../interfaces/i_navigation_policy.js';
import {RenderedConnection} from '../rendered_connection.js';
import {navigateBlock} from './block_navigation_policy.js';

/**
* Set of rules controlling keyboard navigation from a connection.
Expand Down Expand Up @@ -37,17 +38,7 @@ export class ConnectionNavigationPolicy
* @returns The given connection's parent connection or block.
*/
getParent(current: RenderedConnection): IFocusableNode | null {
if (current.type === ConnectionType.OUTPUT_VALUE) {
return current.targetConnection ?? current.getSourceBlock();
} else if (current.getParentInput()) {
return current.getSourceBlock();
}

const topBlock = current.getSourceBlock().getTopStackBlock();
return (
(this.getParentConnection(topBlock)?.targetConnection?.getParentInput()
?.connection as RenderedConnection) ?? topBlock
);
return current.getSourceBlock();
}

/**
Expand All @@ -58,19 +49,7 @@ export class ConnectionNavigationPolicy
*/
getNextSibling(current: RenderedConnection): IFocusableNode | null {
if (current.getParentInput()) {
const parentInput = current.getParentInput();
const block = parentInput?.getSourceBlock();
if (!block || !parentInput) return null;

const curIdx = block.inputList.indexOf(parentInput);
for (let i = curIdx + 1; i < block.inputList.length; i++) {
const input = block.inputList[i];
const fieldRow = input.fieldRow;
if (fieldRow.length) return fieldRow[0];
if (input.connection) return input.connection as RenderedConnection;
}

return null;
return navigateBlock(current, 1);
} else if (current.type === ConnectionType.NEXT_STATEMENT) {
const nextBlock = current.targetConnection;
// If this connection is the last one in the stack, our next sibling is
Expand Down Expand Up @@ -103,20 +82,7 @@ export class ConnectionNavigationPolicy
*/
getPreviousSibling(current: RenderedConnection): IFocusableNode | null {
if (current.getParentInput()) {
const parentInput = current.getParentInput();
const block = parentInput?.getSourceBlock();
if (!block || !parentInput) return null;

const curIdx = block.inputList.indexOf(parentInput);
for (let i = curIdx; i >= 0; i--) {
const input = block.inputList[i];
if (input.connection && input !== parentInput) {
return input.connection as RenderedConnection;
}
const fieldRow = input.fieldRow;
if (fieldRow.length) return fieldRow[fieldRow.length - 1];
}
return null;
return navigateBlock(current, -1);
} else if (
current.type === ConnectionType.PREVIOUS_STATEMENT ||
current.type === ConnectionType.OUTPUT_VALUE
Expand Down
44 changes: 3 additions & 41 deletions core/keyboard_nav/field_navigation_policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type {BlockSvg} from '../block_svg.js';
import {Field} from '../field.js';
import type {IFocusableNode} from '../interfaces/i_focusable_node.js';
import type {INavigationPolicy} from '../interfaces/i_navigation_policy.js';
import {navigateBlock} from './block_navigation_policy.js';

/**
* Set of rules controlling keyboard navigation from a field.
Expand Down Expand Up @@ -40,24 +41,7 @@ export class FieldNavigationPolicy implements INavigationPolicy<Field<any>> {
* @returns The next field or input in the given field's block.
*/
getNextSibling(current: Field<any>): IFocusableNode | null {
const input = current.getParentInput();
const block = current.getSourceBlock();
if (!block) return null;

const curIdx = block.inputList.indexOf(input);
let fieldIdx = input.fieldRow.indexOf(current) + 1;
for (let i = curIdx; i < block.inputList.length; i++) {
const newInput = block.inputList[i];
if (newInput.isVisible()) {
const fieldRow = newInput.fieldRow;
if (fieldIdx < fieldRow.length) return fieldRow[fieldIdx];
if (newInput.connection?.targetBlock()) {
return newInput.connection.targetBlock() as BlockSvg;
}
}
fieldIdx = 0;
}
return null;
return navigateBlock(current, 1);
}

/**
Expand All @@ -67,29 +51,7 @@ export class FieldNavigationPolicy implements INavigationPolicy<Field<any>> {
* @returns The preceding field or input in the given field's block.
*/
getPreviousSibling(current: Field<any>): IFocusableNode | null {
const parentInput = current.getParentInput();
const block = current.getSourceBlock();
if (!block) return null;

const curIdx = block.inputList.indexOf(parentInput);
let fieldIdx = parentInput.fieldRow.indexOf(current) - 1;
for (let i = curIdx; i >= 0; i--) {
const input = block.inputList[i];
if (input.isVisible()) {
if (input.connection?.targetBlock() && input !== parentInput) {
return input.connection.targetBlock() as BlockSvg;
}
const fieldRow = input.fieldRow;
if (fieldIdx > -1) return fieldRow[fieldIdx];
}
// Reset the fieldIdx to the length of the field row of the previous
// input.
if (i - 1 >= 0) {
fieldIdx = block.inputList[i - 1].fieldRow.length - 1;
}
}

return block.getIcons().pop() ?? null;
return navigateBlock(current, -1);
}

/**
Expand Down
26 changes: 3 additions & 23 deletions core/keyboard_nav/icon_navigation_policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {BlockSvg} from '../block_svg.js';
import {Icon} from '../icons/icon.js';
import type {IFocusableNode} from '../interfaces/i_focusable_node.js';
import type {INavigationPolicy} from '../interfaces/i_navigation_policy.js';
import {navigateBlock} from './block_navigation_policy.js';

/**
* Set of rules controlling keyboard navigation from an icon.
Expand Down Expand Up @@ -40,21 +41,7 @@ export class IconNavigationPolicy implements INavigationPolicy<Icon> {
* @returns The next icon, field or input following this icon, if any.
*/
getNextSibling(current: Icon): IFocusableNode | null {
const block = current.getSourceBlock() as BlockSvg;
const icons = block.getIcons();
const currentIndex = icons.indexOf(current);
if (currentIndex >= 0 && currentIndex + 1 < icons.length) {
return icons[currentIndex + 1];
}

for (const input of block.inputList) {
if (input.fieldRow.length) return input.fieldRow[0];

if (input.connection?.targetBlock())
return input.connection.targetBlock() as BlockSvg;
}

return null;
return navigateBlock(current, 1);
}

/**
Expand All @@ -64,14 +51,7 @@ export class IconNavigationPolicy implements INavigationPolicy<Icon> {
* @returns The icon's previous icon, if any.
*/
getPreviousSibling(current: Icon): IFocusableNode | null {
const block = current.getSourceBlock() as BlockSvg;
const icons = block.getIcons();
const currentIndex = icons.indexOf(current);
if (currentIndex >= 1) {
return icons[currentIndex - 1];
}

return null;
return navigateBlock(current, -1);
}

/**
Expand Down
Loading