From 6935180f789af2fc4389899f86acd4de04057b5e Mon Sep 17 00:00:00 2001 From: Ben Henning Date: Fri, 21 Feb 2025 00:28:38 +0000 Subject: [PATCH 1/6] feat: Make toolbox & flyout properly focusable. The toolbox already had a tab stop, but it wouldn't become properly enabled for keyboard navigation. Now it is properly enabled and works when focused both via tab navigation and with the 'T' shortcut. --- src/constants.ts | 1 + src/index.ts | 16 +++++++-- src/navigation.ts | 24 +++++++++++-- src/navigation_controller.ts | 67 +++++++++++++++++++++++++++++------- src/toolbox_monkey_patch.js | 12 +++++++ 5 files changed, 104 insertions(+), 16 deletions(-) create mode 100644 src/toolbox_monkey_patch.js diff --git a/src/constants.ts b/src/constants.ts index f6d2f2d7..1ddbf775 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -14,6 +14,7 @@ * The different parts of Blockly that the user navigates between. */ export enum STATE { + NOWHERE = 'nowhere', WORKSPACE = 'workspace', FLYOUT = 'flyout', TOOLBOX = 'toolbox', diff --git a/src/index.ts b/src/index.ts index c47cc9f5..7fc196db 100644 --- a/src/index.ts +++ b/src/index.ts @@ -82,14 +82,26 @@ export class KeyboardNavigation { workspace.getParentSvg().setAttribute('tabindex', '-1'); this.focusListener = () => { - this.navigationController.setHasFocus(workspace, true); + this.navigationController.updateWorkspaceFocus(workspace, true); }; this.blurListener = () => { - this.navigationController.setHasFocus(workspace, false); + this.navigationController.updateWorkspaceFocus(workspace, false); }; workspace.getSvgGroup().addEventListener('focus', this.focusListener); workspace.getSvgGroup().addEventListener('blur', this.blurListener); + + const toolbox = workspace.getToolbox(); + if (toolbox != null && toolbox instanceof Blockly.Toolbox) { + const contentsDiv = toolbox.HtmlDiv?.querySelector('.blocklyToolboxContents'); + contentsDiv?.addEventListener('focus', () => { + this.navigationController.updateToolboxOrFlyoutFocus(workspace, true); + }); + contentsDiv?.addEventListener('blur', () => { + this.navigationController.updateToolboxOrFlyoutFocus(workspace, false); + }); + } + // Temporary workaround for #136. // TODO(#136): fix in core. workspace.getParentSvg().addEventListener('focus', this.focusListener); diff --git a/src/navigation.ts b/src/navigation.ts index d8b932d5..9795c05d 100644 --- a/src/navigation.ts +++ b/src/navigation.ts @@ -527,7 +527,7 @@ export class Navigation { */ focusWorkspace( workspace: Blockly.WorkspaceSvg, - keepCursorPosition: boolean = false, + keepCursorPosition = false, ) { workspace.hideChaff(); const reset = !!workspace.getToolbox(); @@ -537,6 +537,26 @@ export class Navigation { this.setCursorOnWorkspaceFocus(workspace, keepCursorPosition); } + /** + * Blurs (de-focuses) the workspace's toolbox or flyout, and hides the flyout + * if it's currently visible. + * + * @param workspace The workspace containing the toolbox or flyout. + */ + blurToolboxAndFlyout(workspace: Blockly.WorkspaceSvg) { + workspace.hideChaff(); + const reset = !!workspace.getToolbox(); + + this.resetFlyout(workspace, reset); + switch (this.getState(workspace)) { + case Constants.STATE.FLYOUT: + case Constants.STATE.TOOLBOX: + // Clear state since neither the flyout nor toolbox are focused anymore. + this.setState(workspace, Constants.STATE.NOWHERE); + break; + } + } + /** * Sets the cursor location when focusing the workspace. * Tries the following, in order, stopping after the first success: @@ -544,7 +564,7 @@ export class Navigation { * - Resume editing by returning the cursor to its previous location, if any. * - Move the cursor to the top connection point on on the first top block. * - Move the cursor to the default location on the workspace. - * + * * @param workspace The main Blockly workspace. * @param keepPosition Whether to retain the cursor's previous position. */ diff --git a/src/navigation_controller.ts b/src/navigation_controller.ts index 0c4affe0..0a38c781 100644 --- a/src/navigation_controller.ts +++ b/src/navigation_controller.ts @@ -11,6 +11,7 @@ */ import './gesture_monkey_patch'; +import './toolbox_monkey_patch'; import * as Blockly from 'blockly/core'; import { @@ -44,6 +45,16 @@ interface Scope { connection?: Connection; } +/** Represents the current focus mode of the navigation controller. */ +enum NAVIGATION_FOCUS_MODE { + /** Indicates that no interactive elements of Blockly currently have focus. */ + NONE = 'none', + /** Indicates either the toolbox or a flyout has focus. */ + TOOLBOX_OR_FLYOUT = 'toolbox_or_flyout', + /** Indicates that the main workspace currently has focus. */ + WORKSPACE = 'workspace', +} + /** * Class for registering shortcuts for keyboard navigation. */ @@ -57,7 +68,7 @@ export class NavigationController { announcer: Announcer = new Announcer(); shortcutDialog: ShortcutDialog = new ShortcutDialog(); - hasNavigationFocus: boolean = false; + navigationFocus: NAVIGATION_FOCUS_MODE = NAVIGATION_FOCUS_MODE.NONE; /** * Original Toolbox.prototype.onShortcut method, saved by @@ -148,33 +159,65 @@ export class NavigationController { } /** - * Sets whether the navigation controller has focus. This will enable keyboard - * navigation if focus is now gained. Additionally, the cursor may be reset if - * it hasn't already been positioned in the workspace. + * Sets whether the navigation controller has toolbox or flyout focus. This + * will enable keyboard navigation in the toolbox or flyout. * - * @param workspace the workspace that now has input focus. + * @param workspace the workspace that now has toolbox/flyout input focus. * @param isFocused whether the environment has browser focus. */ - setHasFocus(workspace: WorkspaceSvg, isFocused: boolean) { - this.hasNavigationFocus = isFocused; + updateToolboxOrFlyoutFocus(workspace: WorkspaceSvg, isFocused: boolean) { if (isFocused) { - this.navigation.focusWorkspace(workspace, true); + if (!workspace.getToolbox()) { + this.navigation.focusFlyout(workspace); + } else { + this.navigation.focusToolbox(workspace); + } + this.navigationFocus = NAVIGATION_FOCUS_MODE.TOOLBOX_OR_FLYOUT; + } else { + this.navigation.blurToolboxAndFlyout(workspace); + this.navigationFocus = NAVIGATION_FOCUS_MODE.NONE; } } + /** + * Sets whether the navigation controller has workspace focus. This will + * enable keyboard navigation within the workspace. Additionally, the cursor + * may be reset if it hasn't already been positioned in the workspace. + * + * @param workspace the workspace that now has workspace input focus. + * @param isFocused whether the environment has browser focus. + */ + updateWorkspaceFocus(workspace: WorkspaceSvg, isFocused: boolean) { + if (isFocused) { + this.navigation.focusWorkspace(workspace, true); + this.navigationFocus = NAVIGATION_FOCUS_MODE.WORKSPACE; + } else this.navigationFocus = NAVIGATION_FOCUS_MODE.NONE; + } + /** * Determines whether keyboard navigation should be allowed based on the * current state of the workspace. * - * A return value of 'true' generally indicates that the workspace both has - * enabled keyboard navigation and is currently in a state (e.g. focus) that - * can support keyboard navigation. + * A return value of 'true' generally indicates that either the workspace or + * toolbox/flyout both has enabled keyboard navigation and is currently in a + * state (e.g. focus) that can support keyboard navigation. * * @param workspace the workspace in which keyboard navigation may be allowed. * @returns whether keyboard navigation is currently allowed. */ private canCurrentlyNavigate(workspace: WorkspaceSvg) { - return workspace.keyboardAccessibilityMode && this.hasNavigationFocus; + return this.canCurrentlyNavigateInToolboxOrFlyout(workspace) || + this.canCurrentlyNavigateInWorkspace(workspace); + } + + private canCurrentlyNavigateInToolboxOrFlyout(workspace: WorkspaceSvg) { + return workspace.keyboardAccessibilityMode && + this.navigationFocus == NAVIGATION_FOCUS_MODE.TOOLBOX_OR_FLYOUT; + } + + private canCurrentlyNavigateInWorkspace(workspace: WorkspaceSvg) { + return workspace.keyboardAccessibilityMode && + this.navigationFocus == NAVIGATION_FOCUS_MODE.WORKSPACE; } /** diff --git a/src/toolbox_monkey_patch.js b/src/toolbox_monkey_patch.js new file mode 100644 index 00000000..2649da51 --- /dev/null +++ b/src/toolbox_monkey_patch.js @@ -0,0 +1,12 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as Blockly from 'blockly/core'; + +Blockly.Toolbox.prototype.onKeyDown_ = function () { + // Do nothing since keyboard functionality should be entirely handled by the + // keyboard navigation plugin. +}; From d05c7414f80b97fe4980ce6ccfe85ffa9dbed97f Mon Sep 17 00:00:00 2001 From: Ben Henning Date: Tue, 25 Feb 2025 00:22:22 +0000 Subject: [PATCH 2/6] chore: address reviewer comments --- src/index.ts | 28 ++++++++++++++++++++------ src/navigation.ts | 12 +++++++---- src/navigation_controller.ts | 39 ++++++++++++++++++------------------ 3 files changed, 50 insertions(+), 29 deletions(-) diff --git a/src/index.ts b/src/index.ts index 7fc196db..62d2e5c2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -29,6 +29,12 @@ export class KeyboardNavigation { /** Event handler run when the workspace loses focus. */ private blurListener: () => void; + /** Event handler run when the toolbox gains focus. */ + private toolboxFocusListener: () => void; + + /** Event handler run when the toolbox loses focus. */ + private toolboxBlurListener: () => void; + /** Keyboard navigation controller instance for the workspace. */ private navigationController: NavigationController; @@ -91,15 +97,18 @@ export class KeyboardNavigation { workspace.getSvgGroup().addEventListener('focus', this.focusListener); workspace.getSvgGroup().addEventListener('blur', this.blurListener); + this.toolboxFocusListener = () => { + this.navigationController.updateToolboxFocus(workspace, true); + }; + this.toolboxBlurListener = () => { + this.navigationController.updateToolboxFocus(workspace, false); + }; + const toolbox = workspace.getToolbox(); if (toolbox != null && toolbox instanceof Blockly.Toolbox) { const contentsDiv = toolbox.HtmlDiv?.querySelector('.blocklyToolboxContents'); - contentsDiv?.addEventListener('focus', () => { - this.navigationController.updateToolboxOrFlyoutFocus(workspace, true); - }); - contentsDiv?.addEventListener('blur', () => { - this.navigationController.updateToolboxOrFlyoutFocus(workspace, false); - }); + contentsDiv?.addEventListener('focus', this.toolboxFocusListener); + contentsDiv?.addEventListener('blur', this.toolboxBlurListener); } // Temporary workaround for #136. @@ -126,6 +135,13 @@ export class KeyboardNavigation { .getSvgGroup() .removeEventListener('focus', this.focusListener); + const toolbox = this.workspace.getToolbox(); + if (toolbox != null && toolbox instanceof Blockly.Toolbox) { + const contentsDiv = toolbox.HtmlDiv?.querySelector('.blocklyToolboxContents'); + contentsDiv?.removeEventListener('focus', this.toolboxFocusListener); + contentsDiv?.removeEventListener('blur', this.toolboxBlurListener); + } + if (this.workspaceParentTabIndex) { this.workspace .getParentSvg() diff --git a/src/navigation.ts b/src/navigation.ts index 9795c05d..02482cc4 100644 --- a/src/navigation.ts +++ b/src/navigation.ts @@ -538,12 +538,16 @@ export class Navigation { } /** - * Blurs (de-focuses) the workspace's toolbox or flyout, and hides the flyout - * if it's currently visible. + * Blurs (de-focuses) the workspace's toolbox, and hides the flyout if it's + * currently visible. * - * @param workspace The workspace containing the toolbox or flyout. + * Note that it's up to callers to ensure that this function is only called + * when appropriate (i.e. when the workspace actually has a toolbox that's + * currently focused). + * + * @param workspace The workspace containing the toolbox. */ - blurToolboxAndFlyout(workspace: Blockly.WorkspaceSvg) { + blurToolbox(workspace: Blockly.WorkspaceSvg) { workspace.hideChaff(); const reset = !!workspace.getToolbox(); diff --git a/src/navigation_controller.ts b/src/navigation_controller.ts index 0a38c781..a7d612fb 100644 --- a/src/navigation_controller.ts +++ b/src/navigation_controller.ts @@ -49,8 +49,8 @@ interface Scope { enum NAVIGATION_FOCUS_MODE { /** Indicates that no interactive elements of Blockly currently have focus. */ NONE = 'none', - /** Indicates either the toolbox or a flyout has focus. */ - TOOLBOX_OR_FLYOUT = 'toolbox_or_flyout', + /** Indicates that the toolbox currently has focus. */ + TOOLBOX = 'toolbox', /** Indicates that the main workspace currently has focus. */ WORKSPACE = 'workspace', } @@ -159,22 +159,21 @@ export class NavigationController { } /** - * Sets whether the navigation controller has toolbox or flyout focus. This - * will enable keyboard navigation in the toolbox or flyout. + * Sets whether the navigation controller has toolbox focus and will enable + * keyboard navigation in the toolbox. * - * @param workspace the workspace that now has toolbox/flyout input focus. + * If the workspace doesn't have a toolbox, this function is a no-op. + * + * @param workspace the workspace that now has toolbox input focus. * @param isFocused whether the environment has browser focus. */ - updateToolboxOrFlyoutFocus(workspace: WorkspaceSvg, isFocused: boolean) { + updateToolboxFocus(workspace: WorkspaceSvg, isFocused: boolean) { + if (!workspace.getToolbox()) return; if (isFocused) { - if (!workspace.getToolbox()) { - this.navigation.focusFlyout(workspace); - } else { - this.navigation.focusToolbox(workspace); - } - this.navigationFocus = NAVIGATION_FOCUS_MODE.TOOLBOX_OR_FLYOUT; + this.navigation.focusToolbox(workspace); + this.navigationFocus = NAVIGATION_FOCUS_MODE.TOOLBOX; } else { - this.navigation.blurToolboxAndFlyout(workspace); + this.navigation.blurToolbox(workspace); this.navigationFocus = NAVIGATION_FOCUS_MODE.NONE; } } @@ -191,7 +190,9 @@ export class NavigationController { if (isFocused) { this.navigation.focusWorkspace(workspace, true); this.navigationFocus = NAVIGATION_FOCUS_MODE.WORKSPACE; - } else this.navigationFocus = NAVIGATION_FOCUS_MODE.NONE; + } else { + this.navigationFocus = NAVIGATION_FOCUS_MODE.NONE; + } } /** @@ -199,20 +200,20 @@ export class NavigationController { * current state of the workspace. * * A return value of 'true' generally indicates that either the workspace or - * toolbox/flyout both has enabled keyboard navigation and is currently in a - * state (e.g. focus) that can support keyboard navigation. + * toolbox both has enabled keyboard navigation and is currently in a state + * (e.g. focus) that can support keyboard navigation. * * @param workspace the workspace in which keyboard navigation may be allowed. * @returns whether keyboard navigation is currently allowed. */ private canCurrentlyNavigate(workspace: WorkspaceSvg) { - return this.canCurrentlyNavigateInToolboxOrFlyout(workspace) || + return this.canCurrentlyNavigateInToolbox(workspace) || this.canCurrentlyNavigateInWorkspace(workspace); } - private canCurrentlyNavigateInToolboxOrFlyout(workspace: WorkspaceSvg) { + private canCurrentlyNavigateInToolbox(workspace: WorkspaceSvg) { return workspace.keyboardAccessibilityMode && - this.navigationFocus == NAVIGATION_FOCUS_MODE.TOOLBOX_OR_FLYOUT; + this.navigationFocus == NAVIGATION_FOCUS_MODE.TOOLBOX; } private canCurrentlyNavigateInWorkspace(workspace: WorkspaceSvg) { From dabb9d9296f607591ae75bbedc7bf002c635d4b2 Mon Sep 17 00:00:00 2001 From: Ben Henning Date: Tue, 25 Feb 2025 00:58:24 +0000 Subject: [PATCH 3/6] feat: add subcategory to demo toolbox categories This helps to verify subcategory behavior (which has been observed as not completely working currently). --- test/toolboxCategories.js | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/test/toolboxCategories.js b/test/toolboxCategories.js index 9969a497..fe7eb8f7 100644 --- a/test/toolboxCategories.js +++ b/test/toolboxCategories.js @@ -794,5 +794,38 @@ export default { contents: p5CategoryContents, categorystyle: 'logic_category', }, + { + 'kind': 'category', + 'name': 'Misc', + 'contents': [ + { + kind: 'label', + text: 'This is a label', + }, + { + 'kind': 'category', + 'name': 'A subcategory', + 'contents': [ + { + kind: 'label', + text: 'This is another label', + }, + { + kind: 'block', + type: 'colour_random', + }, + ], + }, + { + 'kind': 'button', + 'text': 'This is a button', + 'callbackKey': 'unimplemented', + }, + { + kind: 'block', + type: 'colour_random', + }, + ], + }, ], }; From 1a8d972d16450208c96e81854c8437387225af71 Mon Sep 17 00:00:00 2001 From: Ben Henning Date: Thu, 6 Mar 2025 00:12:19 +0000 Subject: [PATCH 4/6] Address reviewer comment for monkey patch install. --- src/navigation_controller.ts | 1 - src/toolbox_monkey_patch.d.ts | 14 ++++++++++++++ src/toolbox_monkey_patch.js | 19 ++++++++++++++++--- test/index.ts | 2 ++ 4 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 src/toolbox_monkey_patch.d.ts diff --git a/src/navigation_controller.ts b/src/navigation_controller.ts index fd2f3418..1dcdca4e 100644 --- a/src/navigation_controller.ts +++ b/src/navigation_controller.ts @@ -11,7 +11,6 @@ */ import './gesture_monkey_patch'; -import './toolbox_monkey_patch'; import * as Blockly from 'blockly/core'; import { diff --git a/src/toolbox_monkey_patch.d.ts b/src/toolbox_monkey_patch.d.ts new file mode 100644 index 00000000..e8e4408b --- /dev/null +++ b/src/toolbox_monkey_patch.d.ts @@ -0,0 +1,14 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * Installs a Toolbox-specific monkey-patch. Note that this must be installed + * before any Toolboxes are registered for key bindings. + */ +export declare function install(): void; + +/** Uninstalls the Toolbox-specific monkey-patch. */ +export declare function uninstall(): void; diff --git a/src/toolbox_monkey_patch.js b/src/toolbox_monkey_patch.js index 2649da51..ec591f28 100644 --- a/src/toolbox_monkey_patch.js +++ b/src/toolbox_monkey_patch.js @@ -6,7 +6,20 @@ import * as Blockly from 'blockly/core'; -Blockly.Toolbox.prototype.onKeyDown_ = function () { - // Do nothing since keyboard functionality should be entirely handled by the - // keyboard navigation plugin. +let oldOnKeyDownHandler = null; + +export function install() { + oldOnKeyDownHandler = Blockly.Toolbox.prototype.onKeyDown_; + Blockly.Toolbox.prototype.onKeyDown_ = function () { + // Do nothing since keyboard functionality should be entirely handled by the + // keyboard navigation plugin. + }; +}; + +export function uninstall() { + if (!oldOnKeyDownHandler) { + throw new Error("Trying to dispose non-inited monkey patch."); + } + Blockly.Toolbox.prototype.onKeyDown_ = oldOnKeyDownHandler; + oldOnKeyDownHandler = null; }; diff --git a/test/index.ts b/test/index.ts index fdb71d61..d17d93f7 100644 --- a/test/index.ts +++ b/test/index.ts @@ -22,6 +22,7 @@ import {javascriptGenerator} from 'blockly/javascript'; // @ts-expect-error No types in js file import {load} from './loadTestBlocks'; import {runCode, registerRunCodeShortcut} from './runCode'; +import * as ToolboxMonkeyPatch from '../src/toolbox_monkey_patch'; /** * Parse query params for inject and navigation options and update @@ -83,6 +84,7 @@ function createWorkspace(): Blockly.WorkspaceSvg { renderer, }; const blocklyDiv = document.getElementById('blocklyDiv')!; + ToolboxMonkeyPatch.install(); const workspace = Blockly.inject(blocklyDiv, injectOptions); const navigationOptions = { From 6ec8ceb33d98adacfc795dcc5e1499ac2ad5e6e9 Mon Sep 17 00:00:00 2001 From: Ben Henning Date: Tue, 11 Mar 2025 23:47:19 +0000 Subject: [PATCH 5/6] Revert monkeypatch installation changes. --- src/navigation_controller.ts | 1 + src/toolbox_monkey_patch.d.ts | 14 -------------- src/toolbox_monkey_patch.js | 19 +++---------------- test/index.ts | 2 -- 4 files changed, 4 insertions(+), 32 deletions(-) delete mode 100644 src/toolbox_monkey_patch.d.ts diff --git a/src/navigation_controller.ts b/src/navigation_controller.ts index c2a9c9ca..c653a2df 100644 --- a/src/navigation_controller.ts +++ b/src/navigation_controller.ts @@ -11,6 +11,7 @@ */ import './gesture_monkey_patch'; +import './toolbox_monkey_patch'; import * as Blockly from 'blockly/core'; import { diff --git a/src/toolbox_monkey_patch.d.ts b/src/toolbox_monkey_patch.d.ts deleted file mode 100644 index e8e4408b..00000000 --- a/src/toolbox_monkey_patch.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * @license - * Copyright 2025 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * Installs a Toolbox-specific monkey-patch. Note that this must be installed - * before any Toolboxes are registered for key bindings. - */ -export declare function install(): void; - -/** Uninstalls the Toolbox-specific monkey-patch. */ -export declare function uninstall(): void; diff --git a/src/toolbox_monkey_patch.js b/src/toolbox_monkey_patch.js index ec591f28..2649da51 100644 --- a/src/toolbox_monkey_patch.js +++ b/src/toolbox_monkey_patch.js @@ -6,20 +6,7 @@ import * as Blockly from 'blockly/core'; -let oldOnKeyDownHandler = null; - -export function install() { - oldOnKeyDownHandler = Blockly.Toolbox.prototype.onKeyDown_; - Blockly.Toolbox.prototype.onKeyDown_ = function () { - // Do nothing since keyboard functionality should be entirely handled by the - // keyboard navigation plugin. - }; -}; - -export function uninstall() { - if (!oldOnKeyDownHandler) { - throw new Error("Trying to dispose non-inited monkey patch."); - } - Blockly.Toolbox.prototype.onKeyDown_ = oldOnKeyDownHandler; - oldOnKeyDownHandler = null; +Blockly.Toolbox.prototype.onKeyDown_ = function () { + // Do nothing since keyboard functionality should be entirely handled by the + // keyboard navigation plugin. }; diff --git a/test/index.ts b/test/index.ts index d17d93f7..fdb71d61 100644 --- a/test/index.ts +++ b/test/index.ts @@ -22,7 +22,6 @@ import {javascriptGenerator} from 'blockly/javascript'; // @ts-expect-error No types in js file import {load} from './loadTestBlocks'; import {runCode, registerRunCodeShortcut} from './runCode'; -import * as ToolboxMonkeyPatch from '../src/toolbox_monkey_patch'; /** * Parse query params for inject and navigation options and update @@ -84,7 +83,6 @@ function createWorkspace(): Blockly.WorkspaceSvg { renderer, }; const blocklyDiv = document.getElementById('blocklyDiv')!; - ToolboxMonkeyPatch.install(); const workspace = Blockly.inject(blocklyDiv, injectOptions); const navigationOptions = { From 2f41d8f9c88a6f2dcb70439797ca2d86d479e5ec Mon Sep 17 00:00:00 2001 From: Ben Henning Date: Wed, 12 Mar 2025 00:00:22 +0000 Subject: [PATCH 6/6] Add fix for null current node. It seems possible now to call hide() earlier than previously expected (i.e. when there's a cursor but no current node, such as in the case of focusing the toolbox before the workspace as this PR now makes possible). --- src/line_cursor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/line_cursor.ts b/src/line_cursor.ts index 2ff67811..d5558a5e 100644 --- a/src/line_cursor.ts +++ b/src/line_cursor.ts @@ -658,7 +658,7 @@ export class LineCursor extends Marker { // If there's a block currently selected, remove the selection since the // cursor should now be hidden. const curNode = this.getCurNode(); - if (curNode.getType() === ASTNode.types.BLOCK) { + if (curNode && curNode.getType() === ASTNode.types.BLOCK) { const block = curNode.getLocation() as Blockly.BlockSvg; if (!block.isShadow()) { Blockly.common.setSelected(null);