diff --git a/packages/blockly/core/component_manager.ts b/packages/blockly/core/component_manager.ts index 8363d6fb4a0..5a8ad2b4df2 100644 --- a/packages/blockly/core/component_manager.ts +++ b/packages/blockly/core/component_manager.ts @@ -15,6 +15,7 @@ import type {IAutoHideable} from './interfaces/i_autohideable.js'; import type {IComponent} from './interfaces/i_component.js'; import type {IDeleteArea} from './interfaces/i_delete_area.js'; import type {IDragTarget} from './interfaces/i_drag_target.js'; +import type {IFocusableNode} from './interfaces/i_focusable_node.js'; import type {IPositionable} from './interfaces/i_positionable.js'; import * as arrayUtils from './utils/array.js'; @@ -23,6 +24,7 @@ class Capability<_T> { static DRAG_TARGET = new Capability('drag_target'); static DELETE_AREA = new Capability('delete_area'); static AUTOHIDEABLE = new Capability('autohideable'); + static FOCUSABLE = new Capability('focusable'); private readonly name: string; /** @param name The name of the component capability. */ constructor(name: string) { diff --git a/packages/blockly/core/trashcan.ts b/packages/blockly/core/trashcan.ts index cde76d4a827..6d8b7ac7cbd 100644 --- a/packages/blockly/core/trashcan.ts +++ b/packages/blockly/core/trashcan.ts @@ -22,6 +22,7 @@ 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 {IComponent} from './interfaces/i_component'; 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'; @@ -49,7 +50,7 @@ import type {WorkspaceSvg} from './workspace_svg.js'; */ export class Trashcan extends DeleteArea - implements IAutoHideable, IPositionable, IFocusableNode + implements IAutoHideable, IPositionable, IFocusableNode, IComponent { /** * The id for this component that is used to register with the @@ -269,6 +270,7 @@ export class Trashcan ComponentManager.Capability.DELETE_AREA, ComponentManager.Capability.DRAG_TARGET, ComponentManager.Capability.POSITIONABLE, + ComponentManager.Capability.FOCUSABLE, ], }); this.initialized = true; @@ -659,16 +661,6 @@ export class Trashcan performAction() { this.click(); } - - /** - * Retrieves the globally unique ID of this Trashcan instance. Used for focus - * management. - * - * @internal - */ - getGloballyUniqueId() { - return this.uniqueId; - } } /** Width of both the trash can and lid images. */ diff --git a/packages/blockly/core/workspace_svg.ts b/packages/blockly/core/workspace_svg.ts index 77572bb960c..029b4f8e247 100644 --- a/packages/blockly/core/workspace_svg.ts +++ b/packages/blockly/core/workspace_svg.ts @@ -49,6 +49,7 @@ import * as hints from './hints.js'; import {MutatorIcon} from './icons/mutator_icon.js'; import {isAutoHideable} from './interfaces/i_autohideable.js'; import type {IBoundedElement} from './interfaces/i_bounded_element.js'; +import type {IComponent} from './interfaces/i_component.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'; @@ -2961,13 +2962,15 @@ export class WorkspaceSvg } } - if (this.trashcan?.getGloballyUniqueId() === id) { - return this.trashcan; + const focusableComponents = this.getComponentManager().getComponents< + IFocusableNode & IComponent + >(ComponentManager.Capability.FOCUSABLE, false); + for (const component of focusableComponents) { + if (component.getFocusableElement().getAttribute('id') === id) { + return component; + } } - const zoomControl = this.zoomControls_?.getControlWithId(id); - if (zoomControl) return zoomControl; - return null; } diff --git a/packages/blockly/core/zoom_controls.ts b/packages/blockly/core/zoom_controls.ts index 8ff2cf3bc11..be1196ad733 100644 --- a/packages/blockly/core/zoom_controls.ts +++ b/packages/blockly/core/zoom_controls.ts @@ -19,6 +19,7 @@ import {ComponentManager} from './component_manager.js'; import * as Css from './css.js'; import {EventType} from './events/type.js'; import * as eventUtils from './events/utils.js'; +import type {IComponent} from './interfaces/i_component.js'; import {IFocusableNode} from './interfaces/i_focusable_node.js'; import type {IPositionable} from './interfaces/i_positionable.js'; import type {UiMetrics} from './metrics_manager.js'; @@ -39,9 +40,9 @@ import type {WorkspaceSvg} from './workspace_svg.js'; * * @internal */ -abstract class ZoomControl implements IFocusableNode { +abstract class ZoomControl implements IFocusableNode, IComponent { private pointerDownHandler: browserEvents.Data; - private id: string; + id: string; constructor( protected workspace: WorkspaceSvg, @@ -60,10 +61,6 @@ abstract class ZoomControl implements IFocusableNode { this.group.id = this.id; } - getId() { - return this.id; - } - /** * Handles a mouse down event on the zoom in or zoom out buttons on the * workspace. @@ -385,6 +382,21 @@ export class ZoomControls implements IPositionable { this.svgGroup, ); } + + for (const control of [ + this.zoomOutControl, + this.zoomInControl, + this.zoomResetControl, + ]) { + if (!control) continue; + + this.workspace.getComponentManager().addComponent({ + component: control, + weight: ComponentManager.ComponentWeight.ZOOM_CONTROLS_WEIGHT, + capabilities: [ComponentManager.Capability.FOCUSABLE], + }); + } + return this.svgGroup; } @@ -508,24 +520,6 @@ export class ZoomControls implements IPositionable { 'translate(' + this.left + ',' + this.top + ')', ); } - - /** - * Returns the individual zoom control, if any, with the given ID. Used for - * focus management. - * - * @internal - */ - getControlWithId(id: string) { - for (const control of [ - this.zoomInControl, - this.zoomOutControl, - this.zoomResetControl, - ]) { - if (control?.getId() === id) { - return control; - } - } - } } /** CSS for zoom controls. See css.js for use. */