diff --git a/core/block_svg.ts b/core/block_svg.ts index 8bc0b7af3f6..b8712b01914 100644 --- a/core/block_svg.ts +++ b/core/block_svg.ts @@ -44,8 +44,6 @@ import {IContextMenu} from './interfaces/i_contextmenu.js'; import type {ICopyable} from './interfaces/i_copyable.js'; import {IDeletable} from './interfaces/i_deletable.js'; import type {IDragStrategy, IDraggable} from './interfaces/i_draggable.js'; -import type {IFocusableNode} from './interfaces/i_focusable_node.js'; -import type {IFocusableTree} from './interfaces/i_focusable_tree.js'; import {IIcon} from './interfaces/i_icon.js'; import * as internalConstants from './internal_constants.js'; import {MarkerManager} from './marker_manager.js'; @@ -78,8 +76,7 @@ export class BlockSvg IContextMenu, ICopyable, IDraggable, - IDeletable, - IFocusableNode + IDeletable { /** * Constant for identifying rows that are to be rendered inline. @@ -213,7 +210,6 @@ export class BlockSvg // Expose this block's ID on its top-level SVG group. this.svgGroup.setAttribute('data-id', this.id); - svgPath.id = this.id; this.doInit_(); } @@ -1823,26 +1819,4 @@ export class BlockSvg ); } } - - /** See IFocusableNode.getFocusableElement. */ - getFocusableElement(): HTMLElement | SVGElement { - return this.pathObject.svgPath; - } - - /** See IFocusableNode.getFocusableTree. */ - getFocusableTree(): IFocusableTree { - return this.workspace; - } - - /** See IFocusableNode.onNodeFocus. */ - onNodeFocus(): void { - common.setSelected(this); - } - - /** See IFocusableNode.onNodeBlur. */ - onNodeBlur(): void { - if (common.getSelected() === this) { - common.setSelected(null); - } - } } diff --git a/core/flyout_base.ts b/core/flyout_base.ts index d24ea2758a0..e738470a606 100644 --- a/core/flyout_base.ts +++ b/core/flyout_base.ts @@ -21,12 +21,9 @@ import * as eventUtils from './events/utils.js'; import {FlyoutItem} from './flyout_item.js'; import {FlyoutMetricsManager} from './flyout_metrics_manager.js'; import {FlyoutSeparator, SeparatorAxis} from './flyout_separator.js'; -import {getFocusManager} from './focus_manager.js'; import {IAutoHideable} from './interfaces/i_autohideable.js'; import type {IFlyout} from './interfaces/i_flyout.js'; import type {IFlyoutInflater} from './interfaces/i_flyout_inflater.js'; -import {IFocusableNode} from './interfaces/i_focusable_node.js'; -import {IFocusableTree} from './interfaces/i_focusable_tree.js'; import type {Options} from './options.js'; import * as registry from './registry.js'; import * as renderManagement from './render_management.js'; @@ -46,7 +43,7 @@ import {WorkspaceSvg} from './workspace_svg.js'; */ export abstract class Flyout extends DeleteArea - implements IAutoHideable, IFlyout, IFocusableNode + implements IAutoHideable, IFlyout { /** * Position the flyout. @@ -306,7 +303,6 @@ export abstract class Flyout // hide/show code will set up proper visibility and size later. this.svgGroup_ = dom.createSvgElement(tagName, { 'class': 'blocklyFlyout', - 'tabindex': '0', }); this.svgGroup_.style.display = 'none'; this.svgBackground_ = dom.createSvgElement( @@ -321,9 +317,6 @@ export abstract class Flyout this.workspace_ .getThemeManager() .subscribe(this.svgBackground_, 'flyoutOpacity', 'fill-opacity'); - - getFocusManager().registerTree(this); - return this.svgGroup_; } @@ -405,7 +398,6 @@ export abstract class Flyout if (this.svgGroup_) { dom.removeNode(this.svgGroup_); } - getFocusManager().unregisterTree(this); } /** @@ -969,63 +961,4 @@ export abstract class Flyout return null; } - - /** See IFocusableNode.getFocusableElement. */ - getFocusableElement(): HTMLElement | SVGElement { - if (!this.svgGroup_) throw new Error('Flyout DOM is not yet created.'); - return this.svgGroup_; - } - - /** See IFocusableNode.getFocusableTree. */ - getFocusableTree(): IFocusableTree { - return this; - } - - /** See IFocusableNode.onNodeFocus. */ - onNodeFocus(): void {} - - /** See IFocusableNode.onNodeBlur. */ - onNodeBlur(): void {} - - /** See IFocusableTree.getRootFocusableNode. */ - getRootFocusableNode(): IFocusableNode { - return this; - } - - /** See IFocusableTree.getRestoredFocusableNode. */ - getRestoredFocusableNode( - _previousNode: IFocusableNode | null, - ): IFocusableNode | null { - return null; - } - - /** See IFocusableTree.getNestedTrees. */ - getNestedTrees(): Array { - return [this.workspace_]; - } - - /** See IFocusableTree.lookUpFocusableNode. */ - lookUpFocusableNode(_id: string): IFocusableNode | null { - // No focusable node needs to be returned since the flyout's subtree is a - // workspace that will manage its own focusable state. - return null; - } - - /** See IFocusableTree.onTreeFocus. */ - onTreeFocus( - _node: IFocusableNode, - _previousTree: IFocusableTree | null, - ): void {} - - /** See IFocusableTree.onTreeBlur. */ - onTreeBlur(nextTree: IFocusableTree | null): void { - const toolbox = this.targetWorkspace.getToolbox(); - // If focus is moving to either the toolbox or the flyout's workspace, do - // not close the flyout. For anything else, do close it since the flyout is - // no longer focused. - if (toolbox && nextTree === toolbox) return; - if (nextTree == this.workspace_) return; - if (toolbox) toolbox.clearSelection(); - this.autoHide(false); - } } diff --git a/core/inject.ts b/core/inject.ts index 34d9c1795f8..de78fbfae75 100644 --- a/core/inject.ts +++ b/core/inject.ts @@ -13,11 +13,13 @@ import * as common from './common.js'; import * as Css from './css.js'; import * as dropDownDiv from './dropdowndiv.js'; import {Grid} from './grid.js'; +import {Msg} from './msg.js'; import {Options} from './options.js'; import {ScrollbarPair} from './scrollbar_pair.js'; import {ShortcutRegistry} from './shortcut_registry.js'; import * as Tooltip from './tooltip.js'; import * as Touch from './touch.js'; +import * as aria from './utils/aria.js'; import * as dom from './utils/dom.js'; import {Svg} from './utils/svg.js'; import * as WidgetDiv from './widgetdiv.js'; @@ -54,6 +56,8 @@ export function inject( if (opt_options?.rtl) { dom.addClass(subContainer, 'blocklyRTL'); } + subContainer.tabIndex = 0; + aria.setState(subContainer, aria.State.LABEL, Msg['WORKSPACE_ARIA_LABEL']); containerElement!.appendChild(subContainer); const svg = createDom(subContainer, options); @@ -122,6 +126,7 @@ function createDom(container: HTMLElement, options: Options): SVGElement { 'xmlns:xlink': dom.XLINK_NS, 'version': '1.1', 'class': 'blocklySvg', + 'tabindex': '0', }, container, ); diff --git a/core/interfaces/i_flyout.ts b/core/interfaces/i_flyout.ts index 067cd5ef20d..42204775ece 100644 --- a/core/interfaces/i_flyout.ts +++ b/core/interfaces/i_flyout.ts @@ -12,13 +12,12 @@ import type {Coordinate} from '../utils/coordinate.js'; import type {Svg} from '../utils/svg.js'; import type {FlyoutDefinition} from '../utils/toolbox.js'; import type {WorkspaceSvg} from '../workspace_svg.js'; -import {IFocusableTree} from './i_focusable_tree.js'; import type {IRegistrable} from './i_registrable.js'; /** * Interface for a flyout. */ -export interface IFlyout extends IRegistrable, IFocusableTree { +export interface IFlyout extends IRegistrable { /** Whether the flyout is laid out horizontally or not. */ horizontalLayout: boolean; diff --git a/core/interfaces/i_toolbox.ts b/core/interfaces/i_toolbox.ts index f5d9c9fd7c6..1780af94d8a 100644 --- a/core/interfaces/i_toolbox.ts +++ b/core/interfaces/i_toolbox.ts @@ -9,14 +9,13 @@ import type {ToolboxInfo} from '../utils/toolbox.js'; import type {WorkspaceSvg} from '../workspace_svg.js'; import type {IFlyout} from './i_flyout.js'; -import type {IFocusableTree} from './i_focusable_tree.js'; import type {IRegistrable} from './i_registrable.js'; import type {IToolboxItem} from './i_toolbox_item.js'; /** * Interface for a toolbox. */ -export interface IToolbox extends IRegistrable, IFocusableTree { +export interface IToolbox extends IRegistrable { /** Initializes the toolbox. */ init(): void; diff --git a/core/interfaces/i_toolbox_item.ts b/core/interfaces/i_toolbox_item.ts index 661624fd7e8..e3c9864f0c0 100644 --- a/core/interfaces/i_toolbox_item.ts +++ b/core/interfaces/i_toolbox_item.ts @@ -6,12 +6,10 @@ // Former goog.module ID: Blockly.IToolboxItem -import type {IFocusableNode} from './i_focusable_node.js'; - /** * Interface for an item in the toolbox. */ -export interface IToolboxItem extends IFocusableNode { +export interface IToolboxItem { /** * Initializes the toolbox item. * This includes creating the DOM and updating the state of any items based diff --git a/core/renderers/common/path_object.ts b/core/renderers/common/path_object.ts index 72cf2a594ce..077f80bb741 100644 --- a/core/renderers/common/path_object.ts +++ b/core/renderers/common/path_object.ts @@ -62,7 +62,7 @@ export class PathObject implements IPathObject { /** The primary path of the block. */ this.svgPath = dom.createSvgElement( Svg.PATH, - {'class': 'blocklyPath', 'tabindex': '-1'}, + {'class': 'blocklyPath'}, this.svgRoot, ); diff --git a/core/toolbox/category.ts b/core/toolbox/category.ts index fc7d1aa03cf..d8ee8736ea6 100644 --- a/core/toolbox/category.ts +++ b/core/toolbox/category.ts @@ -225,8 +225,6 @@ export class ToolboxCategory */ protected createContainer_(): HTMLDivElement { const container = document.createElement('div'); - container.tabIndex = -1; - container.id = this.getId(); const className = this.cssConfig_['container']; if (className) { dom.addClass(container, className); diff --git a/core/toolbox/separator.ts b/core/toolbox/separator.ts index 44ae358cf53..31ccb7e42f3 100644 --- a/core/toolbox/separator.ts +++ b/core/toolbox/separator.ts @@ -54,8 +54,6 @@ export class ToolboxSeparator extends ToolboxItem { */ protected createDom_(): HTMLDivElement { const container = document.createElement('div'); - container.tabIndex = -1; - container.id = this.getId(); const className = this.cssConfig_['container']; if (className) { dom.addClass(container, className); diff --git a/core/toolbox/toolbox.ts b/core/toolbox/toolbox.ts index ceb756afbd6..b0fd82e97f2 100644 --- a/core/toolbox/toolbox.ts +++ b/core/toolbox/toolbox.ts @@ -22,14 +22,11 @@ import {DeleteArea} from '../delete_area.js'; import '../events/events_toolbox_item_select.js'; import {EventType} from '../events/type.js'; import * as eventUtils from '../events/utils.js'; -import {getFocusManager} from '../focus_manager.js'; import type {IAutoHideable} from '../interfaces/i_autohideable.js'; import type {ICollapsibleToolboxItem} from '../interfaces/i_collapsible_toolbox_item.js'; import {isDeletable} from '../interfaces/i_deletable.js'; import type {IDraggable} from '../interfaces/i_draggable.js'; import type {IFlyout} from '../interfaces/i_flyout.js'; -import type {IFocusableNode} from '../interfaces/i_focusable_node.js'; -import type {IFocusableTree} from '../interfaces/i_focusable_tree.js'; import type {IKeyboardAccessible} from '../interfaces/i_keyboard_accessible.js'; import type {ISelectableToolboxItem} from '../interfaces/i_selectable_toolbox_item.js'; import {isSelectableToolboxItem} from '../interfaces/i_selectable_toolbox_item.js'; @@ -54,12 +51,7 @@ import {CollapsibleToolboxCategory} from './collapsible_category.js'; */ export class Toolbox extends DeleteArea - implements - IAutoHideable, - IKeyboardAccessible, - IStyleable, - IToolbox, - IFocusableNode + implements IAutoHideable, IKeyboardAccessible, IStyleable, IToolbox { /** * The unique ID for this component that is used to register with the @@ -171,7 +163,6 @@ export class Toolbox ComponentManager.Capability.DRAG_TARGET, ], }); - getFocusManager().registerTree(this); } /** @@ -186,6 +177,7 @@ export class Toolbox const container = this.createContainer_(); this.contentsDiv_ = this.createContentsContainer_(); + this.contentsDiv_.tabIndex = 0; aria.setRole(this.contentsDiv_, aria.Role.TREE); container.appendChild(this.contentsDiv_); @@ -202,7 +194,6 @@ export class Toolbox */ protected createContainer_(): HTMLDivElement { const toolboxContainer = document.createElement('div'); - toolboxContainer.tabIndex = 0; toolboxContainer.setAttribute('layout', this.isHorizontal() ? 'h' : 'v'); dom.addClass(toolboxContainer, 'blocklyToolbox'); toolboxContainer.setAttribute('dir', this.RTL ? 'RTL' : 'LTR'); @@ -1086,71 +1077,7 @@ export class Toolbox this.workspace_.getThemeManager().unsubscribe(this.HtmlDiv); dom.removeNode(this.HtmlDiv); } - - getFocusManager().unregisterTree(this); - } - - /** See IFocusableNode.getFocusableElement. */ - getFocusableElement(): HTMLElement | SVGElement { - if (!this.HtmlDiv) throw Error('Toolbox DOM has not yet been created.'); - return this.HtmlDiv; - } - - /** See IFocusableNode.getFocusableTree. */ - getFocusableTree(): IFocusableTree { - return this; - } - - /** See IFocusableNode.onNodeFocus. */ - onNodeFocus(): void {} - - /** See IFocusableNode.onNodeBlur. */ - onNodeBlur(): void {} - - /** See IFocusableTree.getRootFocusableNode. */ - getRootFocusableNode(): IFocusableNode { - return this; - } - - /** See IFocusableTree.getRestoredFocusableNode. */ - getRestoredFocusableNode( - previousNode: IFocusableNode | null, - ): IFocusableNode | null { - // Always try to select the first selectable toolbox item rather than the - // root of the toolbox. - if (!previousNode || previousNode === this) { - return this.getToolboxItems().find((item) => item.isSelectable()) ?? null; - } - return null; } - - /** See IFocusableTree.getNestedTrees. */ - getNestedTrees(): Array { - return []; - } - - /** See IFocusableTree.lookUpFocusableNode. */ - lookUpFocusableNode(id: string): IFocusableNode | null { - return this.getToolboxItemById(id) as IFocusableNode; - } - - /** See IFocusableTree.onTreeFocus. */ - onTreeFocus( - node: IFocusableNode, - _previousTree: IFocusableTree | null, - ): void { - if (node !== this) { - // Only select the item if it isn't already selected so as to not toggle. - if (this.getSelectedItem() !== node) { - this.setSelectedItem(node as IToolboxItem); - } - } else { - this.clearSelection(); - } - } - - /** See IFocusableTree.onTreeBlur. */ - onTreeBlur(_nextTree: IFocusableTree | null): void {} } /** CSS for Toolbox. See css.js for use. */ diff --git a/core/toolbox/toolbox_item.ts b/core/toolbox/toolbox_item.ts index 0d46a5eadfd..ef9d979ab43 100644 --- a/core/toolbox/toolbox_item.ts +++ b/core/toolbox/toolbox_item.ts @@ -12,7 +12,6 @@ // Former goog.module ID: Blockly.ToolboxItem import type {ICollapsibleToolboxItem} from '../interfaces/i_collapsible_toolbox_item.js'; -import type {IFocusableTree} from '../interfaces/i_focusable_tree.js'; import type {IToolbox} from '../interfaces/i_toolbox.js'; import type {IToolboxItem} from '../interfaces/i_toolbox_item.js'; import * as idGenerator from '../utils/idgenerator.js'; @@ -149,28 +148,5 @@ export class ToolboxItem implements IToolboxItem { * @param _isVisible True if category should be visible. */ setVisible_(_isVisible: boolean) {} - - /** See IFocusableNode.getFocusableElement. */ - getFocusableElement(): HTMLElement | SVGElement { - const div = this.getDiv(); - if (!div) { - throw Error('Trying to access toolbox item before DOM is initialized.'); - } - if (!(div instanceof HTMLElement)) { - throw Error('Toolbox item div is unexpectedly not an HTML element.'); - } - return div as HTMLElement; - } - - /** See IFocusableNode.getFocusableTree. */ - getFocusableTree(): IFocusableTree { - return this.parentToolbox_; - } - - /** See IFocusableNode.onNodeFocus. */ - onNodeFocus(): void {} - - /** See IFocusableNode.onNodeBlur. */ - onNodeBlur(): void {} } // nop by default diff --git a/core/workspace_svg.ts b/core/workspace_svg.ts index 250d6cf43e2..91668b744d4 100644 --- a/core/workspace_svg.ts +++ b/core/workspace_svg.ts @@ -37,7 +37,6 @@ import {EventType} from './events/type.js'; import * as eventUtils from './events/utils.js'; import {Flyout} from './flyout_base.js'; import type {FlyoutButton} from './flyout_button.js'; -import {getFocusManager} from './focus_manager.js'; import {Gesture} from './gesture.js'; import {Grid} from './grid.js'; import type {IASTNodeLocationSvg} from './interfaces/i_ast_node_location_svg.js'; @@ -45,8 +44,6 @@ import type {IBoundedElement} from './interfaces/i_bounded_element.js'; import {IContextMenu} from './interfaces/i_contextmenu.js'; import type {IDragTarget} from './interfaces/i_drag_target.js'; import type {IFlyout} from './interfaces/i_flyout.js'; -import type {IFocusableNode} from './interfaces/i_focusable_node.js'; -import type {IFocusableTree} from './interfaces/i_focusable_tree.js'; import type {IMetricsManager} from './interfaces/i_metrics_manager.js'; import type {IToolbox} from './interfaces/i_toolbox.js'; import type { @@ -57,7 +54,6 @@ import type {LineCursor} from './keyboard_nav/line_cursor.js'; import type {Marker} from './keyboard_nav/marker.js'; import {LayerManager} from './layer_manager.js'; import {MarkerManager} from './marker_manager.js'; -import {Msg} from './msg.js'; import {Options} from './options.js'; import * as Procedures from './procedures.js'; import * as registry from './registry.js'; @@ -70,7 +66,6 @@ import {Classic} from './theme/classic.js'; import {ThemeManager} from './theme_manager.js'; import * as Tooltip from './tooltip.js'; import type {Trashcan} from './trashcan.js'; -import * as aria from './utils/aria.js'; import * as arrayUtils from './utils/array.js'; import {Coordinate} from './utils/coordinate.js'; import * as dom from './utils/dom.js'; @@ -98,7 +93,7 @@ const ZOOM_TO_FIT_MARGIN = 20; */ export class WorkspaceSvg extends Workspace - implements IASTNodeLocationSvg, IContextMenu, IFocusableNode, IFocusableTree + implements IASTNodeLocationSvg, IContextMenu { /** * A wrapper function called when a resize event occurs. @@ -769,19 +764,7 @@ export class WorkspaceSvg * * */ - this.svgGroup_ = dom.createSvgElement(Svg.G, { - 'class': 'blocklyWorkspace', - // Only the top-level workspace should be tabbable. - 'tabindex': injectionDiv ? '0' : '-1', - 'id': this.id, - }); - if (injectionDiv) { - aria.setState( - this.svgGroup_, - aria.State.LABEL, - Msg['WORKSPACE_ARIA_LABEL'], - ); - } + this.svgGroup_ = dom.createSvgElement(Svg.G, {'class': 'blocklyWorkspace'}); // Note that a alone does not receive mouse events--it must have a // valid target inside it. If no background class is specified, as in the @@ -857,9 +840,6 @@ export class WorkspaceSvg this.getTheme(), isParentWorkspace ? this.getInjectionDiv() : undefined, ); - - getFocusManager().registerTree(this); - return this.svgGroup_; } @@ -944,10 +924,6 @@ export class WorkspaceSvg document.body.removeEventListener('wheel', this.dummyWheelListener); this.dummyWheelListener = null; } - - if (getFocusManager().isRegistered(this)) { - getFocusManager().unregisterTree(this); - } } /** @@ -2642,67 +2618,6 @@ export class WorkspaceSvg deltaY *= scale; this.scroll(this.scrollX + deltaX, this.scrollY + deltaY); } - - /** See IFocusableNode.getFocusableElement. */ - getFocusableElement(): HTMLElement | SVGElement { - return this.svgGroup_; - } - - /** See IFocusableNode.getFocusableTree. */ - getFocusableTree(): IFocusableTree { - return this; - } - - /** See IFocusableNode.onNodeFocus. */ - onNodeFocus(): void {} - - /** See IFocusableNode.onNodeBlur. */ - onNodeBlur(): void {} - - /** See IFocusableTree.getRootFocusableNode. */ - getRootFocusableNode(): IFocusableNode { - return this; - } - - /** See IFocusableTree.getRestoredFocusableNode. */ - getRestoredFocusableNode( - previousNode: IFocusableNode | null, - ): IFocusableNode | null { - if (!previousNode) { - return this.getTopBlocks(true)[0] ?? null; - } else return null; - } - - /** See IFocusableTree.getNestedTrees. */ - getNestedTrees(): Array { - return []; - } - - /** See IFocusableTree.lookUpFocusableNode. */ - lookUpFocusableNode(id: string): IFocusableNode | null { - return this.getBlockById(id) as IFocusableNode; - } - - /** See IFocusableTree.onTreeFocus. */ - onTreeFocus( - _node: IFocusableNode, - _previousTree: IFocusableTree | null, - ): void {} - - /** See IFocusableTree.onTreeBlur. */ - onTreeBlur(nextTree: IFocusableTree | null): void { - // If the flyout loses focus, make sure to close it. - if (this.isFlyout && this.targetWorkspace) { - // Only hide the flyout if the flyout's workspace is losing focus and that - // focus isn't returning to the flyout itself or the toolbox. - const flyout = this.targetWorkspace.getFlyout(); - const toolbox = this.targetWorkspace.getToolbox(); - if (flyout && nextTree === flyout) return; - if (toolbox && nextTree === toolbox) return; - if (toolbox) toolbox.clearSelection(); - if (flyout && flyout instanceof Flyout) flyout.autoHide(false); - } - } } /** diff --git a/tests/mocha/toolbox_test.js b/tests/mocha/toolbox_test.js index f32319c6779..10bfd335223 100644 --- a/tests/mocha/toolbox_test.js +++ b/tests/mocha/toolbox_test.js @@ -54,7 +54,6 @@ suite('Toolbox', function () { const themeManagerSpy = sinon.spy(themeManager, 'subscribe'); const componentManager = this.toolbox.workspace_.getComponentManager(); sinon.stub(componentManager, 'addComponent'); - this.toolbox.dispose(); // Dispose of the old toolbox so that it can be reinited. this.toolbox.init(); sinon.assert.calledWith( themeManagerSpy, @@ -73,14 +72,12 @@ suite('Toolbox', function () { const renderSpy = sinon.spy(this.toolbox, 'render'); const componentManager = this.toolbox.workspace_.getComponentManager(); sinon.stub(componentManager, 'addComponent'); - this.toolbox.dispose(); // Dispose of the old toolbox so that it can be reinited. this.toolbox.init(); sinon.assert.calledOnce(renderSpy); }); test('Init called -> Flyout is initialized', function () { const componentManager = this.toolbox.workspace_.getComponentManager(); sinon.stub(componentManager, 'addComponent'); - this.toolbox.dispose(); // Dispose of the old toolbox so that it can be reinited. this.toolbox.init(); assert.isDefined(this.toolbox.getFlyout()); });