Skip to content
58 changes: 42 additions & 16 deletions src/actions/arrow_navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {ASTNode, ShortcutRegistry, utils as BlocklyUtils} from 'blockly/core';

import type {Field, Toolbox, WorkspaceSvg} from 'blockly/core';

import * as Blockly from 'blockly/core';
import * as Constants from '../constants';
import type {Navigation} from '../navigation';

Expand Down Expand Up @@ -47,6 +48,7 @@ export class ArrowNavigation {
* Adds all arrow key navigation shortcuts to the registry.
*/
install() {
console.log('@@@@@ install arrow keys');
const shortcuts: {
[name: string]: ShortcutRegistry.KeyboardShortcut;
} = {
Expand All @@ -73,12 +75,15 @@ export class ArrowNavigation {
}
return isHandled;
case Constants.STATE.TOOLBOX:
isHandled =
toolbox && typeof toolbox.onShortcut === 'function'
? toolbox.onShortcut(shortcut)
: false;
if (!isHandled) {
this.navigation.focusFlyout(workspace);
isHandled = toolbox.selectChild();
// isHandled =
// toolbox && typeof toolbox.onShortcut === 'function'
// ? toolbox.onShortcut(shortcut)
// : false;
const flyout = toolbox.getFlyout();
if (!isHandled && flyout) {
Blockly.getFocusManager().focusTree(flyout.getWorkspace());
// this.navigation.focusFlyout(workspace);
}
return true;
default:
Expand Down Expand Up @@ -111,12 +116,15 @@ export class ArrowNavigation {
}
return isHandled;
case Constants.STATE.FLYOUT:
this.navigation.focusToolbox(workspace);
Blockly.getFocusManager().focusTree(toolbox);
// this.navigation.focusToolbox(workspace);
return true;
case Constants.STATE.TOOLBOX:
return toolbox && typeof toolbox.onShortcut === 'function'
? toolbox.onShortcut(shortcut)
: false;
isHandled = toolbox.selectParent();
return isHandled;
// return toolbox && typeof toolbox.onShortcut === 'function'
// ? toolbox.onShortcut(shortcut)
// : false;
default:
return false;
}
Expand Down Expand Up @@ -157,9 +165,20 @@ export class ArrowNavigation {
}
return isHandled;
case Constants.STATE.TOOLBOX:
return toolbox && typeof toolbox.onShortcut === 'function'
? toolbox.onShortcut(shortcut)
: false;
// TODO: Move this into cursor?
if (!toolbox.getSelectedItem()) {
const firstItem = toolbox.getToolboxItems().find((item) => item.isSelectable()) ?? null;
toolbox.setSelectedItem(firstItem);
isHandled = true;
} else isHandled = toolbox.selectNext();
const selectedItem = toolbox.getSelectedItem();
if (selectedItem) {
Blockly.getFocusManager().focusNode(selectedItem);
}
return isHandled;
// return toolbox && typeof toolbox.onShortcut === 'function'
// ? toolbox.onShortcut(shortcut)
// : false;
default:
return false;
}
Expand Down Expand Up @@ -205,9 +224,16 @@ export class ArrowNavigation {
}
return isHandled;
case Constants.STATE.TOOLBOX:
return toolbox && typeof toolbox.onShortcut === 'function'
? toolbox.onShortcut(shortcut)
: false;
// TODO: Move this into cursor?
isHandled = toolbox.selectPrevious();
const selectedItem = toolbox.getSelectedItem();
if (selectedItem) {
Blockly.getFocusManager().focusNode(selectedItem);
}
return isHandled;
// return toolbox && typeof toolbox.onShortcut === 'function'
// ? toolbox.onShortcut(shortcut)
// : false;
default:
return false;
}
Expand Down
7 changes: 6 additions & 1 deletion src/actions/exit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import {ShortcutRegistry, utils as BlocklyUtils} from 'blockly/core';

import * as Blockly from 'blockly/core';
import * as Constants from '../constants';
import type {Navigation} from '../navigation';

Expand All @@ -29,7 +30,11 @@ export class ExitAction {
switch (this.navigation.getState(workspace)) {
case Constants.STATE.FLYOUT:
case Constants.STATE.TOOLBOX:
this.navigation.focusWorkspace(workspace);
Blockly.getFocusManager().focusTree(workspace);
if (!Blockly.Gesture.inProgress()) {
workspace.hideChaff();
}
// this.navigation.focusWorkspace(workspace);
return true;
default:
return false;
Expand Down
58 changes: 29 additions & 29 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ export class KeyboardNavigation {
* These fields are used to preserve the workspace's initial state to restore
* it when/if keyboard navigation is disabled.
*/
private injectionDivTabIndex: string | null;
private workspaceParentTabIndex: string | null;
// private injectionDivTabIndex: string | null;
// private workspaceParentTabIndex: string | null;
private originalTheme: Blockly.Theme;

/**
Expand Down Expand Up @@ -98,15 +98,15 @@ export class KeyboardNavigation {
workspace.addChangeListener(enableBlocksOnDrag);

// Ensure that only the root SVG G (group) has a tab index.
this.injectionDivTabIndex = workspace
.getInjectionDiv()
.getAttribute('tabindex');
workspace.getInjectionDiv().removeAttribute('tabindex');
this.workspaceParentTabIndex = workspace
.getParentSvg()
.getAttribute('tabindex');
// We add a focus listener below so use -1 so it doesn't become focusable.
workspace.getParentSvg().setAttribute('tabindex', '-1');
// this.injectionDivTabIndex = workspace
// .getInjectionDiv()
// .getAttribute('tabindex');
// workspace.getInjectionDiv().removeAttribute('tabindex');
// this.workspaceParentTabIndex = workspace
// .getParentSvg()
// .getAttribute('tabindex');
// // We add a focus listener below so use -1 so it doesn't become focusable.
// workspace.getParentSvg().setAttribute('tabindex', '-1');

// Move the flyout for logical tab order.
const flyoutElement = getFlyoutElement(workspace);
Expand Down Expand Up @@ -157,8 +157,8 @@ export class KeyboardNavigation {
this.navigationController.handleBlurWorkspace(workspace);
};

workspace.getSvgGroup().addEventListener('focus', this.focusListener);
workspace.getSvgGroup().addEventListener('blur', this.blurListener);
workspace.getSvgGroup().addEventListener('focusin', this.focusListener);
workspace.getSvgGroup().addEventListener('focusout', this.blurListener);

this.widgetDropDownDivFocusOutListener = (e: Event) => {
this.navigationController.handleFocusOutWidgetDropdownDiv(
Expand Down Expand Up @@ -240,7 +240,7 @@ export class KeyboardNavigation {
// Remove the event listener that enables blocks on drag
this.workspace.removeChangeListener(enableBlocksOnDrag);

this.workspace.getSvgGroup().removeEventListener('blur', this.blurListener);
this.workspace.getSvgGroup().removeEventListener('focusout', this.blurListener);
this.workspace
.getSvgGroup()
.removeEventListener('focus', this.focusListener);
Expand All @@ -265,21 +265,21 @@ export class KeyboardNavigation {
flyoutElement?.removeEventListener('focus', this.flyoutFocusListener);
flyoutElement?.removeEventListener('blur', this.flyoutBlurListener);

if (this.workspaceParentTabIndex) {
this.workspace
.getParentSvg()
.setAttribute('tabindex', this.workspaceParentTabIndex);
} else {
this.workspace.getParentSvg().removeAttribute('tabindex');
}

if (this.injectionDivTabIndex) {
this.workspace
.getInjectionDiv()
.setAttribute('tabindex', this.injectionDivTabIndex);
} else {
this.workspace.getInjectionDiv().removeAttribute('tabindex');
}
// if (this.workspaceParentTabIndex) {
// this.workspace
// .getParentSvg()
// .setAttribute('tabindex', this.workspaceParentTabIndex);
// } else {
// this.workspace.getParentSvg().removeAttribute('tabindex');
// }

// if (this.injectionDivTabIndex) {
// this.workspace
// .getInjectionDiv()
// .setAttribute('tabindex', this.injectionDivTabIndex);
// } else {
// this.workspace.getInjectionDiv().removeAttribute('tabindex');
// }

this.workspace.setTheme(this.originalTheme);

Expand Down
28 changes: 20 additions & 8 deletions src/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,18 @@ export class Navigation {
* @returns The state of the given workspace.
*/
getState(workspace: Blockly.WorkspaceSvg): Constants.STATE {
return this.workspaceStates[workspace.id];
const focusedTree = Blockly.getFocusManager().getFocusedTree();
if (focusedTree instanceof Blockly.WorkspaceSvg) {
// TODO: Is the instanceof Flyout below ever needed now? Probably not since Flyout shouldn't actually be a real IFocusableTree...
if (focusedTree.isFlyout) {
return Constants.STATE.FLYOUT;
} else return Constants.STATE.WORKSPACE;
} else if (focusedTree instanceof Blockly.Toolbox) {
return Constants.STATE.TOOLBOX;
} else if (focusedTree instanceof Blockly.Flyout) {
return Constants.STATE.FLYOUT;
} else return Constants.STATE.NOWHERE;
// return this.workspaceStates[workspace.id];
}

/**
Expand Down Expand Up @@ -381,7 +392,7 @@ export class Navigation {
* @param workspace The workspace to focus.
*/
focusWorkspace(workspace: Blockly.WorkspaceSvg) {
getWorkspaceElement(workspace).focus();
// getWorkspaceElement(workspace).focus();
}

/**
Expand Down Expand Up @@ -432,7 +443,7 @@ export class Navigation {
if (cursor && (ignorePopUpDivs || !popUpDivsShowing)) {
const curNode = cursor.getCurNode();
if (curNode) {
this.passiveFocusIndicator.show(curNode);
// this.passiveFocusIndicator.show(curNode);
}
// It's initially null so this is a valid state despite the types.
cursor.setCurNode(null);
Expand Down Expand Up @@ -460,9 +471,9 @@ export class Navigation {
// https://github.com/google/blockly-samples/issues/2498
return;
}
if (relatedTarget !== getWorkspaceElement(workspace)) {
this.handleBlurWorkspace(workspace, true);
}
// if (relatedTarget !== getWorkspaceElement(workspace)) {
// this.handleBlurWorkspace(workspace, true);
// }
}

/**
Expand All @@ -471,7 +482,7 @@ export class Navigation {
* @param workspace The workspace with the toolbox.
*/
focusToolbox(workspace: Blockly.WorkspaceSvg) {
getToolboxElement(workspace)?.focus();
// getToolboxElement(workspace)?.focus();
}

/**
Expand Down Expand Up @@ -519,7 +530,7 @@ export class Navigation {
* @param workspace The workspace with the flyout.
*/
focusFlyout(workspace: Blockly.WorkspaceSvg) {
getFlyoutElement(workspace)?.focus();
// getFlyoutElement(workspace)?.focus();
}

/**
Expand Down Expand Up @@ -1141,6 +1152,7 @@ export class Navigation {
* @returns whether keyboard navigation is currently allowed.
*/
canCurrentlyNavigate(workspace: Blockly.WorkspaceSvg) {
console.log('@@@@@@@ current state:', this.getState(workspace));
return (
workspace.keyboardAccessibilityMode &&
this.getState(workspace) !== Constants.STATE.NOWHERE
Expand Down
47 changes: 30 additions & 17 deletions src/navigation_controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,16 +132,12 @@ export class NavigationController {
}
switch (shortcut.name) {
case Constants.SHORTCUT_NAMES.UP:
// @ts-expect-error private method
return this.selectPrevious();
case Constants.SHORTCUT_NAMES.LEFT:
// @ts-expect-error private method
return this.selectParent();
case Constants.SHORTCUT_NAMES.DOWN:
// @ts-expect-error private method
return this.selectNext();
case Constants.SHORTCUT_NAMES.RIGHT:
// @ts-expect-error private method
return this.selectChild();
default:
return false;
Expand Down Expand Up @@ -172,46 +168,46 @@ export class NavigationController {
}

focusWorkspace(workspace: WorkspaceSvg) {
this.navigation.focusWorkspace(workspace);
// this.navigation.focusWorkspace(workspace);
}

handleFocusWorkspace(workspace: Blockly.WorkspaceSvg) {
this.navigation.handleFocusWorkspace(workspace);
// this.navigation.handleFocusWorkspace(workspace);
}

handleBlurWorkspace(workspace: Blockly.WorkspaceSvg) {
this.navigation.handleBlurWorkspace(workspace);
// this.navigation.handleBlurWorkspace(workspace);
}

handleFocusOutWidgetDropdownDiv(
workspace: Blockly.WorkspaceSvg,
relatedTarget: EventTarget | null,
) {
this.navigation.handleFocusOutWidgetDropdownDiv(workspace, relatedTarget);
// this.navigation.handleFocusOutWidgetDropdownDiv(workspace, relatedTarget);
}

focusToolbox(workspace: Blockly.WorkspaceSvg) {
this.navigation.focusToolbox(workspace);
// this.navigation.focusToolbox(workspace);
}

handleFocusToolbox(workspace: Blockly.WorkspaceSvg) {
this.navigation.handleFocusToolbox(workspace);
// this.navigation.handleFocusToolbox(workspace);
}

handleBlurToolbox(workspace: Blockly.WorkspaceSvg, closeFlyout: boolean) {
this.navigation.handleBlurToolbox(workspace, closeFlyout);
// this.navigation.handleBlurToolbox(workspace, closeFlyout);
}

focusFlyout(workspace: Blockly.WorkspaceSvg) {
this.navigation.focusFlyout(workspace);
// this.navigation.focusFlyout(workspace);
}

handleFocusFlyout(workspace: Blockly.WorkspaceSvg) {
this.navigation.handleFocusFlyout(workspace);
// this.navigation.handleFocusFlyout(workspace);
}

handleBlurFlyout(workspace: Blockly.WorkspaceSvg, closeFlyout: boolean) {
this.navigation.handleBlurFlyout(workspace, closeFlyout);
// this.navigation.handleBlurFlyout(workspace, closeFlyout);
}

/**
Expand Down Expand Up @@ -248,10 +244,27 @@ export class NavigationController {
callback: (workspace) => {
switch (this.navigation.getState(workspace)) {
case Constants.STATE.WORKSPACE:
if (!workspace.getToolbox()) {
this.navigation.focusFlyout(workspace);
const toolbox = workspace.getToolbox();
if (!toolbox) {
Blockly.getFocusManager().focusTree(workspace);
// this.navigation.focusFlyout(workspace);
} else {
this.navigation.focusToolbox(workspace);
// The toolbox receiving focus should ensure it has at least its first item selected
// if there was no previous focus yet.
Blockly.getFocusManager().focusTree(toolbox);
// Ensure that the first item is selected.
// TODO: Retain this across contexts?
// if (!toolbox.getSelectedItem() && toolbox instanceof Blockly.Toolbox) {
// // Find the first item that is selectable.
// const toolboxItems = toolbox.getToolboxItems();
// for (let i = 0, toolboxItem; (toolboxItem = toolboxItems[i]); i++) {
// if (toolboxItem.isSelectable()) {
// toolbox.selectItemByPosition(i);
// break;
// }
// }
// }
// this.navigation.focusToolbox(workspace);
}
return true;
default:
Expand Down
Loading