Fix: Revert focus prs (#8933)

* Revert "feat: Make toolbox and flyout focusable (#8920)"

This reverts commit 5bc83808bf.

* Revert "feat: Make WorkspaceSvg and BlockSvg focusable (#8916)"

This reverts commit d7680cf32e.
This commit is contained in:
RoboErikG
2025-04-25 15:03:32 -07:00
committed by GitHub
parent 8f59649956
commit c644fe36ef
13 changed files with 15 additions and 296 deletions

View File

@@ -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<BlockCopyData>,
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);
}
}
}

View File

@@ -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<IFocusableTree> {
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);
}
}

View File

@@ -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,
);

View File

@@ -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;

View File

@@ -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;

View File

@@ -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

View File

@@ -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,
);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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<IFocusableTree> {
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. */

View File

@@ -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

View File

@@ -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
* <g class="blocklyBubbleCanvas"></g>
* </g>
*/
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 <g> 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<IFocusableTree> {
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);
}
}
}
/**

View File

@@ -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());
});