diff --git a/core/block_svg.ts b/core/block_svg.ts index 26b8c4fb5..ca25560fd 100644 --- a/core/block_svg.ts +++ b/core/block_svg.ts @@ -47,7 +47,6 @@ 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 type {INavigable} from './interfaces/i_navigable.js'; import * as internalConstants from './internal_constants.js'; import {Msg} from './msg.js'; import * as renderManagement from './render_management.js'; @@ -77,8 +76,7 @@ export class BlockSvg ICopyable, IDraggable, IDeletable, - IFocusableNode, - INavigable + IFocusableNode { /** * Constant for identifying rows that are to be rendered inline. @@ -1796,16 +1794,4 @@ export class BlockSvg canBeFocused(): boolean { return true; } - - /** - * Returns this block's class. - * - * Used by keyboard navigation to look up the rules for navigating from this - * block. - * - * @returns This block's class. - */ - getClass() { - return BlockSvg; - } } diff --git a/core/blockly.ts b/core/blockly.ts index f7b44b2ef..14383a947 100644 --- a/core/blockly.ts +++ b/core/blockly.ts @@ -171,7 +171,7 @@ import { import {IVariableMap} from './interfaces/i_variable_map.js'; import {IVariableModel, IVariableState} from './interfaces/i_variable_model.js'; import * as internalConstants from './internal_constants.js'; -import {CursorOptions, LineCursor} from './keyboard_nav/line_cursor.js'; +import {LineCursor} from './keyboard_nav/line_cursor.js'; import {Marker} from './keyboard_nav/marker.js'; import type {LayerManager} from './layer_manager.js'; import * as layers from './layers.js'; @@ -429,7 +429,7 @@ Names.prototype.populateProcedures = function ( }; // clang-format on -export * from './interfaces/i_navigable.js'; +export * from './flyout_navigator.js'; export * from './interfaces/i_navigation_policy.js'; export * from './keyboard_nav/block_navigation_policy.js'; export * from './keyboard_nav/connection_navigation_policy.js'; @@ -457,7 +457,6 @@ export { ContextMenuItems, ContextMenuRegistry, Css, - CursorOptions, DeleteArea, DragTarget, Events, diff --git a/core/field.ts b/core/field.ts index 5e9092f88..f7e01527e 100644 --- a/core/field.ts +++ b/core/field.ts @@ -26,7 +26,6 @@ import type {Input} from './inputs/input.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 {INavigable} from './interfaces/i_navigable.js'; import type {IRegistrable} from './interfaces/i_registrable.js'; import {ISerializable} from './interfaces/i_serializable.js'; import type {ConstantProvider} from './renderers/common/constants.js'; @@ -68,12 +67,7 @@ export type FieldValidator = (newValue: T) => T | null | undefined; * @typeParam T - The value stored on the field. */ export abstract class Field - implements - IKeyboardAccessible, - IRegistrable, - ISerializable, - IFocusableNode, - INavigable> + implements IKeyboardAccessible, IRegistrable, ISerializable, IFocusableNode { /** * To overwrite the default value which is set in **Field**, directly update @@ -1410,16 +1404,6 @@ export abstract class Field `Attempted to instantiate a field from the registry that hasn't defined a 'fromJson' method.`, ); } - - /** - * Returns this field's class. - * - * Used by keyboard navigation to look up the rules for navigating from this - * field. Must be implemented by subclasses. - * - * @returns This field's class. - */ - abstract getClass(): new (...args: any) => Field; } /** diff --git a/core/field_checkbox.ts b/core/field_checkbox.ts index 85c176bc3..55ed42cbf 100644 --- a/core/field_checkbox.ts +++ b/core/field_checkbox.ts @@ -228,18 +228,6 @@ export class FieldCheckbox extends Field { // 'override' the static fromJson method. return new this(options.checked, undefined, options); } - - /** - * Returns this field's class. - * - * Used by keyboard navigation to look up the rules for navigating from this - * field. - * - * @returns This field's class. - */ - getClass() { - return FieldCheckbox; - } } fieldRegistry.register('field_checkbox', FieldCheckbox); diff --git a/core/field_dropdown.ts b/core/field_dropdown.ts index 38c5727f2..0b787bd77 100644 --- a/core/field_dropdown.ts +++ b/core/field_dropdown.ts @@ -796,18 +796,6 @@ export class FieldDropdown extends Field { throw TypeError('Found invalid FieldDropdown options.'); } } - - /** - * Returns this field's class. - * - * Used by keyboard navigation to look up the rules for navigating from this - * field. - * - * @returns This field's class. - */ - getClass() { - return FieldDropdown; - } } /** diff --git a/core/field_image.ts b/core/field_image.ts index 761294a8c..6dfe2530e 100644 --- a/core/field_image.ts +++ b/core/field_image.ts @@ -272,18 +272,6 @@ export class FieldImage extends Field { options, ); } - - /** - * Returns this field's class. - * - * Used by keyboard navigation to look up the rules for navigating from this - * field. - * - * @returns This field's class. - */ - getClass() { - return FieldImage; - } } fieldRegistry.register('field_image', FieldImage); diff --git a/core/field_label.ts b/core/field_label.ts index 6b8437d2b..236154cc7 100644 --- a/core/field_label.ts +++ b/core/field_label.ts @@ -126,18 +126,6 @@ export class FieldLabel extends Field { // the static fromJson method. return new this(text, undefined, options); } - - /** - * Returns this field's class. - * - * Used by keyboard navigation to look up the rules for navigating from this - * field. - * - * @returns This field's class. - */ - getClass() { - return FieldLabel; - } } fieldRegistry.register('field_label', FieldLabel); diff --git a/core/field_number.ts b/core/field_number.ts index 6ecda1011..7e3659175 100644 --- a/core/field_number.ts +++ b/core/field_number.ts @@ -341,18 +341,6 @@ export class FieldNumber extends FieldInput { options, ); } - - /** - * Returns this field's class. - * - * Used by keyboard navigation to look up the rules for navigating from this - * field. - * - * @returns This field's class. - */ - getClass() { - return FieldNumber; - } } fieldRegistry.register('field_number', FieldNumber); diff --git a/core/field_textinput.ts b/core/field_textinput.ts index 6ec2331d0..2b896ad47 100644 --- a/core/field_textinput.ts +++ b/core/field_textinput.ts @@ -89,18 +89,6 @@ export class FieldTextInput extends FieldInput { // override the static fromJson method. return new this(text, undefined, options); } - - /** - * Returns this field's class. - * - * Used by keyboard navigation to look up the rules for navigating from this - * field. - * - * @returns This field's class. - */ - getClass() { - return FieldTextInput; - } } fieldRegistry.register('field_input', FieldTextInput); diff --git a/core/flyout_base.ts b/core/flyout_base.ts index 2af280820..9f94ec309 100644 --- a/core/flyout_base.ts +++ b/core/flyout_base.ts @@ -20,6 +20,7 @@ import {EventType} from './events/type.js'; import * as eventUtils from './events/utils.js'; import {FlyoutItem} from './flyout_item.js'; import {FlyoutMetricsManager} from './flyout_metrics_manager.js'; +import {FlyoutNavigator} from './flyout_navigator.js'; import {FlyoutSeparator, SeparatorAxis} from './flyout_separator.js'; import {getFocusManager} from './focus_manager.js'; import {IAutoHideable} from './interfaces/i_autohideable.js'; @@ -243,6 +244,7 @@ export abstract class Flyout this.workspace_.internalIsFlyout = true; // Keep the workspace visibility consistent with the flyout's visibility. this.workspace_.setVisible(this.visible); + this.workspace_.setNavigator(new FlyoutNavigator(this)); /** * The unique id for this component that is used to register with the diff --git a/core/flyout_button.ts b/core/flyout_button.ts index 605a13585..823b57be7 100644 --- a/core/flyout_button.ts +++ b/core/flyout_button.ts @@ -16,7 +16,6 @@ import * as Css from './css.js'; import type {IBoundedElement} from './interfaces/i_bounded_element.js'; import type {IFocusableNode} from './interfaces/i_focusable_node.js'; import type {IFocusableTree} from './interfaces/i_focusable_tree.js'; -import type {INavigable} from './interfaces/i_navigable.js'; import type {IRenderedElement} from './interfaces/i_rendered_element.js'; import {idGenerator} from './utils.js'; import {Coordinate} from './utils/coordinate.js'; @@ -32,11 +31,7 @@ import type {WorkspaceSvg} from './workspace_svg.js'; * Class for a button or label in the flyout. */ export class FlyoutButton - implements - IBoundedElement, - IRenderedElement, - IFocusableNode, - INavigable + implements IBoundedElement, IRenderedElement, IFocusableNode { /** The horizontal margin around the text in the button. */ static TEXT_MARGIN_X = 5; @@ -412,18 +407,6 @@ export class FlyoutButton canBeFocused(): boolean { return true; } - - /** - * Returns this button's class. - * - * Used by keyboard navigation to look up the rules for navigating from this - * button. - * - * @returns This button's class. - */ - getClass() { - return FlyoutButton; - } } /** CSS for buttons and labels. See css.js for use. */ diff --git a/core/flyout_item.ts b/core/flyout_item.ts index ab973cb97..26be0ed12 100644 --- a/core/flyout_item.ts +++ b/core/flyout_item.ts @@ -1,5 +1,6 @@ import type {IBoundedElement} from './interfaces/i_bounded_element.js'; -import type {INavigable} from './interfaces/i_navigable.js'; +import type {IFocusableNode} from './interfaces/i_focusable_node.js'; + /** * Representation of an item displayed in a flyout. */ @@ -12,7 +13,7 @@ export class FlyoutItem { * flyout inflater that created this object. */ constructor( - private element: IBoundedElement & INavigable, + private element: IBoundedElement & IFocusableNode, private type: string, ) {} diff --git a/core/flyout_navigator.ts b/core/flyout_navigator.ts new file mode 100644 index 000000000..a102ce817 --- /dev/null +++ b/core/flyout_navigator.ts @@ -0,0 +1,24 @@ +/** + * @license + * Copyright 2025 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import type {IFlyout} from './interfaces/i_flyout.js'; +import {FlyoutButtonNavigationPolicy} from './keyboard_nav/flyout_button_navigation_policy.js'; +import {FlyoutNavigationPolicy} from './keyboard_nav/flyout_navigation_policy.js'; +import {FlyoutSeparatorNavigationPolicy} from './keyboard_nav/flyout_separator_navigation_policy.js'; +import {Navigator} from './navigator.js'; + +export class FlyoutNavigator extends Navigator { + constructor(flyout: IFlyout) { + super(); + this.rules.push( + new FlyoutButtonNavigationPolicy(), + new FlyoutSeparatorNavigationPolicy(), + ); + this.rules = this.rules.map( + (rule) => new FlyoutNavigationPolicy(rule, flyout), + ); + } +} diff --git a/core/flyout_separator.ts b/core/flyout_separator.ts index 73698b3dd..e9ace428e 100644 --- a/core/flyout_separator.ts +++ b/core/flyout_separator.ts @@ -7,15 +7,12 @@ import type {IBoundedElement} from './interfaces/i_bounded_element.js'; import type {IFocusableNode} from './interfaces/i_focusable_node.js'; import type {IFocusableTree} from './interfaces/i_focusable_tree.js'; -import type {INavigable} from './interfaces/i_navigable.js'; import {Rect} from './utils/rect.js'; /** * Representation of a gap between elements in a flyout. */ -export class FlyoutSeparator - implements IBoundedElement, INavigable, IFocusableNode -{ +export class FlyoutSeparator implements IBoundedElement, IFocusableNode { private x = 0; private y = 0; @@ -66,18 +63,6 @@ export class FlyoutSeparator return false; } - /** - * Returns this separator's class. - * - * Used by keyboard navigation to look up the rules for navigating from this - * separator. - * - * @returns This separator's class. - */ - getClass() { - return FlyoutSeparator; - } - /** See IFocusableNode.getFocusableElement. */ getFocusableElement(): HTMLElement | SVGElement { throw new Error('Cannot be focused'); diff --git a/core/interfaces/i_navigable.ts b/core/interfaces/i_navigable.ts deleted file mode 100644 index dcb8ad50f..000000000 --- a/core/interfaces/i_navigable.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * @license - * Copyright 2025 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -import type {IFocusableNode} from './i_focusable_node.js'; - -/** - * Represents a UI element which can be navigated to using the keyboard. - */ -export interface INavigable extends IFocusableNode { - /** - * Returns the class of this instance. - * - * @returns This object's class. - */ - getClass(): new (...args: any) => T; -} diff --git a/core/interfaces/i_navigation_policy.ts b/core/interfaces/i_navigation_policy.ts index d2b694c89..8e1ce6c10 100644 --- a/core/interfaces/i_navigation_policy.ts +++ b/core/interfaces/i_navigation_policy.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import type {INavigable} from './i_navigable.js'; +import type {IFocusableNode} from './i_focusable_node.js'; /** * A set of rules that specify where keyboard navigation should proceed. @@ -16,7 +16,7 @@ export interface INavigationPolicy { * @param current The element which the user is navigating into. * @returns The current element's first child, or null if it has none. */ - getFirstChild(current: T): INavigable | null; + getFirstChild(current: T): IFocusableNode | null; /** * Returns the parent element of the given element, if any. @@ -24,7 +24,7 @@ export interface INavigationPolicy { * @param current The element which the user is navigating out of. * @returns The parent element of the current element, or null if it has none. */ - getParent(current: T): INavigable | null; + getParent(current: T): IFocusableNode | null; /** * Returns the peer element following the given element, if any. @@ -33,7 +33,7 @@ export interface INavigationPolicy { * @returns The next peer element of the current element, or null if there is * none. */ - getNextSibling(current: T): INavigable | null; + getNextSibling(current: T): IFocusableNode | null; /** * Returns the peer element preceding the given element, if any. @@ -42,7 +42,7 @@ export interface INavigationPolicy { * @returns The previous peer element of the current element, or null if * there is none. */ - getPreviousSibling(current: T): INavigable | null; + getPreviousSibling(current: T): IFocusableNode | null; /** * Returns whether or not the given instance should be reachable via keyboard @@ -57,4 +57,13 @@ export interface INavigationPolicy { * @returns True if this element should be included in keyboard navigation. */ isNavigable(current: T): boolean; + + /** + * Returns whether or not this navigation policy corresponds to the type of + * the given object. + * + * @param current An instance to check whether this policy applies to. + * @returns True if the given object is of a type handled by this policy. + */ + isApplicable(current: any): current is T; } diff --git a/core/keyboard_nav/block_navigation_policy.ts b/core/keyboard_nav/block_navigation_policy.ts index 55c7086ec..74f970d99 100644 --- a/core/keyboard_nav/block_navigation_policy.ts +++ b/core/keyboard_nav/block_navigation_policy.ts @@ -6,7 +6,7 @@ import {BlockSvg} from '../block_svg.js'; import type {Field} from '../field.js'; -import type {INavigable} from '../interfaces/i_navigable.js'; +import type {IFocusableNode} from '../interfaces/i_focusable_node.js'; import type {INavigationPolicy} from '../interfaces/i_navigation_policy.js'; import {WorkspaceSvg} from '../workspace_svg.js'; @@ -20,7 +20,7 @@ export class BlockNavigationPolicy implements INavigationPolicy { * @param current The block to return the first child of. * @returns The first field or input of the given block, if any. */ - getFirstChild(current: BlockSvg): INavigable | null { + getFirstChild(current: BlockSvg): IFocusableNode | null { for (const input of current.inputList) { for (const field of input.fieldRow) { return field; @@ -39,7 +39,7 @@ export class BlockNavigationPolicy implements INavigationPolicy { * @returns The top block of the given block's stack, or the connection to * which it is attached. */ - getParent(current: BlockSvg): INavigable | null { + getParent(current: BlockSvg): IFocusableNode | null { if (current.previousConnection?.targetBlock()) { const surroundParent = current.getSurroundParent(); if (surroundParent) return surroundParent; @@ -57,7 +57,7 @@ export class BlockNavigationPolicy implements INavigationPolicy { * @returns The first block of the next stack if the given block is a terminal * block, or its next connection. */ - getNextSibling(current: BlockSvg): INavigable | null { + getNextSibling(current: BlockSvg): IFocusableNode | null { if (current.nextConnection?.targetBlock()) { return current.nextConnection?.targetBlock(); } @@ -101,7 +101,7 @@ export class BlockNavigationPolicy implements INavigationPolicy { * @returns The block's previous/output connection, or the last * connection/block of the previous block stack if it is a root block. */ - getPreviousSibling(current: BlockSvg): INavigable | null { + getPreviousSibling(current: BlockSvg): IFocusableNode | null { if (current.previousConnection?.targetBlock()) { return current.previousConnection?.targetBlock(); } @@ -127,7 +127,7 @@ export class BlockNavigationPolicy implements INavigationPolicy { } const currentIndex = siblings.indexOf(current); - let result: INavigable | null = null; + let result: IFocusableNode | null = null; if (currentIndex >= 1) { result = siblings[currentIndex - 1]; } else if (currentIndex === 0 && navigatingCrossStacks) { @@ -152,4 +152,14 @@ export class BlockNavigationPolicy implements INavigationPolicy { isNavigable(current: BlockSvg): boolean { return current.canBeFocused(); } + + /** + * Returns whether the given object can be navigated from by this policy. + * + * @param current The object to check if this policy applies to. + * @returns True if the object is a BlockSvg. + */ + isApplicable(current: any): current is BlockSvg { + return current instanceof BlockSvg; + } } diff --git a/core/keyboard_nav/connection_navigation_policy.ts b/core/keyboard_nav/connection_navigation_policy.ts index 8323a73e1..9c3eafc56 100644 --- a/core/keyboard_nav/connection_navigation_policy.ts +++ b/core/keyboard_nav/connection_navigation_policy.ts @@ -6,9 +6,9 @@ import type {BlockSvg} from '../block_svg.js'; import {ConnectionType} from '../connection_type.js'; -import type {INavigable} from '../interfaces/i_navigable.js'; +import type {IFocusableNode} from '../interfaces/i_focusable_node.js'; import type {INavigationPolicy} from '../interfaces/i_navigation_policy.js'; -import type {RenderedConnection} from '../rendered_connection.js'; +import {RenderedConnection} from '../rendered_connection.js'; /** * Set of rules controlling keyboard navigation from a connection. @@ -22,7 +22,7 @@ export class ConnectionNavigationPolicy * @param current The connection to return the first child of. * @returns The connection's first child element, or null if not none. */ - getFirstChild(current: RenderedConnection): INavigable | null { + getFirstChild(current: RenderedConnection): IFocusableNode | null { if (current.getParentInput()) { return current.targetConnection; } @@ -36,7 +36,7 @@ export class ConnectionNavigationPolicy * @param current The connection to return the parent of. * @returns The given connection's parent connection or block. */ - getParent(current: RenderedConnection): INavigable | null { + getParent(current: RenderedConnection): IFocusableNode | null { if (current.type === ConnectionType.OUTPUT_VALUE) { return current.targetConnection ?? current.getSourceBlock(); } else if (current.getParentInput()) { @@ -56,7 +56,7 @@ export class ConnectionNavigationPolicy * @param current The connection to navigate from. * @returns The field, input connection or block following this connection. */ - getNextSibling(current: RenderedConnection): INavigable | null { + getNextSibling(current: RenderedConnection): IFocusableNode | null { if (current.getParentInput()) { const parentInput = current.getParentInput(); const block = parentInput?.getSourceBlock(); @@ -101,7 +101,7 @@ export class ConnectionNavigationPolicy * @param current The connection to navigate from. * @returns The field, input connection or block preceding this connection. */ - getPreviousSibling(current: RenderedConnection): INavigable | null { + getPreviousSibling(current: RenderedConnection): IFocusableNode | null { if (current.getParentInput()) { const parentInput = current.getParentInput(); const block = parentInput?.getSourceBlock(); @@ -176,4 +176,14 @@ export class ConnectionNavigationPolicy isNavigable(current: RenderedConnection): boolean { return current.canBeFocused(); } + + /** + * Returns whether the given object can be navigated from by this policy. + * + * @param current The object to check if this policy applies to. + * @returns True if the object is a RenderedConnection. + */ + isApplicable(current: any): current is RenderedConnection { + return current instanceof RenderedConnection; + } } diff --git a/core/keyboard_nav/field_navigation_policy.ts b/core/keyboard_nav/field_navigation_policy.ts index a4a48df50..9139b64ae 100644 --- a/core/keyboard_nav/field_navigation_policy.ts +++ b/core/keyboard_nav/field_navigation_policy.ts @@ -5,8 +5,8 @@ */ import type {BlockSvg} from '../block_svg.js'; -import type {Field} from '../field.js'; -import type {INavigable} from '../interfaces/i_navigable.js'; +import {Field} from '../field.js'; +import type {IFocusableNode} from '../interfaces/i_focusable_node.js'; import type {INavigationPolicy} from '../interfaces/i_navigation_policy.js'; /** @@ -19,7 +19,7 @@ export class FieldNavigationPolicy implements INavigationPolicy> { * @param _current The field to navigate from. * @returns Null. */ - getFirstChild(_current: Field): INavigable | null { + getFirstChild(_current: Field): IFocusableNode | null { return null; } @@ -29,7 +29,7 @@ export class FieldNavigationPolicy implements INavigationPolicy> { * @param current The field to navigate from. * @returns The given field's parent block. */ - getParent(current: Field): INavigable | null { + getParent(current: Field): IFocusableNode | null { return current.getSourceBlock() as BlockSvg; } @@ -39,7 +39,7 @@ export class FieldNavigationPolicy implements INavigationPolicy> { * @param current The field to navigate from. * @returns The next field or input in the given field's block. */ - getNextSibling(current: Field): INavigable | null { + getNextSibling(current: Field): IFocusableNode | null { const input = current.getParentInput(); const block = current.getSourceBlock(); if (!block) return null; @@ -64,7 +64,7 @@ export class FieldNavigationPolicy implements INavigationPolicy> { * @param current The field to navigate from. * @returns The preceding field or input in the given field's block. */ - getPreviousSibling(current: Field): INavigable | null { + getPreviousSibling(current: Field): IFocusableNode | null { const parentInput = current.getParentInput(); const block = current.getSourceBlock(); if (!block) return null; @@ -106,4 +106,14 @@ export class FieldNavigationPolicy implements INavigationPolicy> { current.getParentInput().isVisible() ); } + + /** + * Returns whether the given object can be navigated from by this policy. + * + * @param current The object to check if this policy applies to. + * @returns True if the object is a Field. + */ + isApplicable(current: any): current is Field { + return current instanceof Field; + } } diff --git a/core/keyboard_nav/flyout_button_navigation_policy.ts b/core/keyboard_nav/flyout_button_navigation_policy.ts index 771a327b5..6c39c3061 100644 --- a/core/keyboard_nav/flyout_button_navigation_policy.ts +++ b/core/keyboard_nav/flyout_button_navigation_policy.ts @@ -4,8 +4,8 @@ * SPDX-License-Identifier: Apache-2.0 */ -import type {FlyoutButton} from '../flyout_button.js'; -import type {INavigable} from '../interfaces/i_navigable.js'; +import {FlyoutButton} from '../flyout_button.js'; +import type {IFocusableNode} from '../interfaces/i_focusable_node.js'; import type {INavigationPolicy} from '../interfaces/i_navigation_policy.js'; /** @@ -20,7 +20,7 @@ export class FlyoutButtonNavigationPolicy * @param _current The FlyoutButton instance to navigate from. * @returns Null. */ - getFirstChild(_current: FlyoutButton): INavigable | null { + getFirstChild(_current: FlyoutButton): IFocusableNode | null { return null; } @@ -30,7 +30,7 @@ export class FlyoutButtonNavigationPolicy * @param current The FlyoutButton instance to navigate from. * @returns The given flyout button's parent workspace. */ - getParent(current: FlyoutButton): INavigable | null { + getParent(current: FlyoutButton): IFocusableNode | null { return current.getWorkspace(); } @@ -40,7 +40,7 @@ export class FlyoutButtonNavigationPolicy * @param _current The FlyoutButton instance to navigate from. * @returns Null. */ - getNextSibling(_current: FlyoutButton): INavigable | null { + getNextSibling(_current: FlyoutButton): IFocusableNode | null { return null; } @@ -50,7 +50,7 @@ export class FlyoutButtonNavigationPolicy * @param _current The FlyoutButton instance to navigate from. * @returns Null. */ - getPreviousSibling(_current: FlyoutButton): INavigable | null { + getPreviousSibling(_current: FlyoutButton): IFocusableNode | null { return null; } @@ -63,4 +63,14 @@ export class FlyoutButtonNavigationPolicy isNavigable(current: FlyoutButton): boolean { return current.canBeFocused(); } + + /** + * Returns whether the given object can be navigated from by this policy. + * + * @param current The object to check if this policy applies to. + * @returns True if the object is a FlyoutButton. + */ + isApplicable(current: any): current is FlyoutButton { + return current instanceof FlyoutButton; + } } diff --git a/core/keyboard_nav/flyout_navigation_policy.ts b/core/keyboard_nav/flyout_navigation_policy.ts index 1abdbfb6c..6552c27b4 100644 --- a/core/keyboard_nav/flyout_navigation_policy.ts +++ b/core/keyboard_nav/flyout_navigation_policy.ts @@ -5,7 +5,7 @@ */ import type {IFlyout} from '../interfaces/i_flyout.js'; -import type {INavigable} from '../interfaces/i_navigable.js'; +import type {IFocusableNode} from '../interfaces/i_focusable_node.js'; import type {INavigationPolicy} from '../interfaces/i_navigation_policy.js'; /** @@ -29,7 +29,7 @@ export class FlyoutNavigationPolicy implements INavigationPolicy { * @param _current The flyout item to navigate from. * @returns Null to prevent navigating into flyout items. */ - getFirstChild(_current: T): INavigable | null { + getFirstChild(_current: T): IFocusableNode | null { return null; } @@ -39,7 +39,7 @@ export class FlyoutNavigationPolicy implements INavigationPolicy { * @param current The flyout item to navigate from. * @returns The parent of the given flyout item. */ - getParent(current: T): INavigable | null { + getParent(current: T): IFocusableNode | null { return this.policy.getParent(current); } @@ -49,7 +49,7 @@ export class FlyoutNavigationPolicy implements INavigationPolicy { * @param current The flyout item to navigate from. * @returns The flyout item following the given one. */ - getNextSibling(current: T): INavigable | null { + getNextSibling(current: T): IFocusableNode | null { const flyoutContents = this.flyout.getContents(); if (!flyoutContents) return null; @@ -72,7 +72,7 @@ export class FlyoutNavigationPolicy implements INavigationPolicy { * @param current The flyout item to navigate from. * @returns The flyout item preceding the given one. */ - getPreviousSibling(current: T): INavigable | null { + getPreviousSibling(current: T): IFocusableNode | null { const flyoutContents = this.flyout.getContents(); if (!flyoutContents) return null; @@ -98,4 +98,14 @@ export class FlyoutNavigationPolicy implements INavigationPolicy { isNavigable(current: T): boolean { return this.policy.isNavigable(current); } + + /** + * Returns whether the given object can be navigated from by this policy. + * + * @param current The object to check if this policy applies to. + * @returns True if the object is a BlockSvg. + */ + isApplicable(current: any): current is T { + return this.policy.isApplicable(current); + } } diff --git a/core/keyboard_nav/flyout_separator_navigation_policy.ts b/core/keyboard_nav/flyout_separator_navigation_policy.ts index 2e69af37a..eb7ca4eb7 100644 --- a/core/keyboard_nav/flyout_separator_navigation_policy.ts +++ b/core/keyboard_nav/flyout_separator_navigation_policy.ts @@ -4,8 +4,8 @@ * SPDX-License-Identifier: Apache-2.0 */ -import type {FlyoutSeparator} from '../flyout_separator.js'; -import type {INavigable} from '../interfaces/i_navigable.js'; +import {FlyoutSeparator} from '../flyout_separator.js'; +import type {IFocusableNode} from '../interfaces/i_focusable_node.js'; import type {INavigationPolicy} from '../interfaces/i_navigation_policy.js'; /** @@ -15,19 +15,19 @@ import type {INavigationPolicy} from '../interfaces/i_navigation_policy.js'; export class FlyoutSeparatorNavigationPolicy implements INavigationPolicy { - getFirstChild(_current: FlyoutSeparator): INavigable | null { + getFirstChild(_current: FlyoutSeparator): IFocusableNode | null { return null; } - getParent(_current: FlyoutSeparator): INavigable | null { + getParent(_current: FlyoutSeparator): IFocusableNode | null { return null; } - getNextSibling(_current: FlyoutSeparator): INavigable | null { + getNextSibling(_current: FlyoutSeparator): IFocusableNode | null { return null; } - getPreviousSibling(_current: FlyoutSeparator): INavigable | null { + getPreviousSibling(_current: FlyoutSeparator): IFocusableNode | null { return null; } @@ -40,4 +40,14 @@ export class FlyoutSeparatorNavigationPolicy isNavigable(_current: FlyoutSeparator): boolean { return false; } + + /** + * Returns whether the given object can be navigated from by this policy. + * + * @param current The object to check if this policy applies to. + * @returns True if the object is a FlyoutSeparator. + */ + isApplicable(current: any): current is FlyoutSeparator { + return current instanceof FlyoutSeparator; + } } diff --git a/core/keyboard_nav/line_cursor.ts b/core/keyboard_nav/line_cursor.ts index 845fab951..71c61b4f3 100644 --- a/core/keyboard_nav/line_cursor.ts +++ b/core/keyboard_nav/line_cursor.ts @@ -14,43 +14,12 @@ */ import {BlockSvg} from '../block_svg.js'; -import {FieldCheckbox} from '../field_checkbox.js'; -import {FieldDropdown} from '../field_dropdown.js'; -import {FieldImage} from '../field_image.js'; -import {FieldLabel} from '../field_label.js'; -import {FieldNumber} from '../field_number.js'; -import {FieldTextInput} from '../field_textinput.js'; -import {FlyoutButton} from '../flyout_button.js'; -import {FlyoutSeparator} from '../flyout_separator.js'; import {getFocusManager} from '../focus_manager.js'; +import type {IFocusableNode} from '../interfaces/i_focusable_node.js'; import {isFocusableNode} from '../interfaces/i_focusable_node.js'; -import type {INavigable} from '../interfaces/i_navigable.js'; import * as registry from '../registry.js'; -import {RenderedConnection} from '../rendered_connection.js'; -import {Renderer} from '../renderers/zelos/renderer.js'; import {WorkspaceSvg} from '../workspace_svg.js'; -import {BlockNavigationPolicy} from './block_navigation_policy.js'; -import {ConnectionNavigationPolicy} from './connection_navigation_policy.js'; -import {FieldNavigationPolicy} from './field_navigation_policy.js'; -import {FlyoutButtonNavigationPolicy} from './flyout_button_navigation_policy.js'; -import {FlyoutNavigationPolicy} from './flyout_navigation_policy.js'; -import {FlyoutSeparatorNavigationPolicy} from './flyout_separator_navigation_policy.js'; import {Marker} from './marker.js'; -import {WorkspaceNavigationPolicy} from './workspace_navigation_policy.js'; - -/** Options object for LineCursor instances. */ -export interface CursorOptions { - /** - * Can the cursor visit all stack connections (next/previous), or - * (if false) only unconnected next connections? - */ - stackConnections: boolean; -} - -/** Default options for LineCursor instances. */ -const defaultOptions: CursorOptions = { - stackConnections: true, -}; /** * Class for a line cursor. @@ -58,76 +27,14 @@ const defaultOptions: CursorOptions = { export class LineCursor extends Marker { override type = 'cursor'; - /** Options for this line cursor. */ - private readonly options: CursorOptions; - /** Locations to try moving the cursor to after a deletion. */ - private potentialNodes: INavigable[] | null = null; - - /** Whether the renderer is zelos-style. */ - private isZelos = false; + private potentialNodes: IFocusableNode[] | null = null; /** * @param workspace The workspace this cursor belongs to. - * @param options Cursor options. */ - constructor( - protected readonly workspace: WorkspaceSvg, - options?: Partial, - ) { + constructor(protected readonly workspace: WorkspaceSvg) { super(); - // Regularise options and apply defaults. - this.options = {...defaultOptions, ...options}; - - this.isZelos = workspace.getRenderer() instanceof Renderer; - - this.registerNavigationPolicies(); - } - - /** - * Registers default navigation policies for Blockly's built-in types with - * this cursor's workspace. - */ - protected registerNavigationPolicies() { - const navigator = this.workspace.getNavigator(); - - const blockPolicy = new BlockNavigationPolicy(); - if (this.workspace.isFlyout) { - const flyout = this.workspace.targetWorkspace?.getFlyout(); - if (flyout) { - navigator.set( - BlockSvg, - new FlyoutNavigationPolicy(blockPolicy, flyout), - ); - - const buttonPolicy = new FlyoutButtonNavigationPolicy(); - navigator.set( - FlyoutButton, - new FlyoutNavigationPolicy(buttonPolicy, flyout), - ); - - navigator.set( - FlyoutSeparator, - new FlyoutNavigationPolicy( - new FlyoutSeparatorNavigationPolicy(), - flyout, - ), - ); - } - } else { - navigator.set(BlockSvg, blockPolicy); - } - - navigator.set(RenderedConnection, new ConnectionNavigationPolicy()); - navigator.set(WorkspaceSvg, new WorkspaceNavigationPolicy()); - - const fieldPolicy = new FieldNavigationPolicy(); - navigator.set(FieldCheckbox, fieldPolicy); - navigator.set(FieldDropdown, fieldPolicy); - navigator.set(FieldImage, fieldPolicy); - navigator.set(FieldLabel, fieldPolicy); - navigator.set(FieldNumber, fieldPolicy); - navigator.set(FieldTextInput, fieldPolicy); } /** @@ -137,14 +44,14 @@ export class LineCursor extends Marker { * @returns The next node, or null if the current node is * not set or there is no next value. */ - next(): INavigable | null { + next(): IFocusableNode | null { const curNode = this.getCurNode(); if (!curNode) { return null; } const newNode = this.getNextNode( curNode, - (candidate: INavigable | null) => { + (candidate: IFocusableNode | null) => { return ( candidate instanceof BlockSvg && !candidate.outputConnection?.targetBlock() @@ -166,7 +73,7 @@ export class LineCursor extends Marker { * @returns The next node, or null if the current node is * not set or there is no next value. */ - in(): INavigable | null { + in(): IFocusableNode | null { const curNode = this.getCurNode(); if (!curNode) { return null; @@ -186,14 +93,14 @@ export class LineCursor extends Marker { * @returns The previous node, or null if the current node * is not set or there is no previous value. */ - prev(): INavigable | null { + prev(): IFocusableNode | null { const curNode = this.getCurNode(); if (!curNode) { return null; } const newNode = this.getPreviousNode( curNode, - (candidate: INavigable | null) => { + (candidate: IFocusableNode | null) => { return ( candidate instanceof BlockSvg && !candidate.outputConnection?.targetBlock() @@ -215,7 +122,7 @@ export class LineCursor extends Marker { * @returns The previous node, or null if the current node * is not set or there is no previous value. */ - out(): INavigable | null { + out(): IFocusableNode | null { const curNode = this.getCurNode(); if (!curNode) { return null; @@ -241,7 +148,7 @@ export class LineCursor extends Marker { const inNode = this.getNextNode(curNode, () => true, true); const nextNode = this.getNextNode( curNode, - (candidate: INavigable | null) => { + (candidate: IFocusableNode | null) => { return ( candidate instanceof BlockSvg && !candidate.outputConnection?.targetBlock() @@ -265,10 +172,10 @@ export class LineCursor extends Marker { * @returns The next node in the traversal. */ private getNextNodeImpl( - node: INavigable | null, - isValid: (p1: INavigable | null) => boolean, - visitedNodes: Set> = new Set>(), - ): INavigable | null { + node: IFocusableNode | null, + isValid: (p1: IFocusableNode | null) => boolean, + visitedNodes: Set = new Set(), + ): IFocusableNode | null { if (!node || visitedNodes.has(node)) return null; let newNode = this.workspace.getNavigator().getFirstChild(node) || @@ -301,10 +208,10 @@ export class LineCursor extends Marker { * @returns The next node in the traversal. */ getNextNode( - node: INavigable | null, - isValid: (p1: INavigable | null) => boolean, + node: IFocusableNode | null, + isValid: (p1: IFocusableNode | null) => boolean, loop: boolean, - ): INavigable | null { + ): IFocusableNode | null { if (!node || (!loop && this.getLastNode() === node)) return null; return this.getNextNodeImpl(node, isValid); @@ -323,10 +230,10 @@ export class LineCursor extends Marker { * exists. */ private getPreviousNodeImpl( - node: INavigable | null, - isValid: (p1: INavigable | null) => boolean, - visitedNodes: Set> = new Set>(), - ): INavigable | null { + node: IFocusableNode | null, + isValid: (p1: IFocusableNode | null) => boolean, + visitedNodes: Set = new Set(), + ): IFocusableNode | null { if (!node || visitedNodes.has(node)) return null; const newNode = @@ -355,10 +262,10 @@ export class LineCursor extends Marker { * exists. */ getPreviousNode( - node: INavigable | null, - isValid: (p1: INavigable | null) => boolean, + node: IFocusableNode | null, + isValid: (p1: IFocusableNode | null) => boolean, loop: boolean, - ): INavigable | null { + ): IFocusableNode | null { if (!node || (!loop && this.getFirstNode() === node)) return null; return this.getPreviousNodeImpl(node, isValid); @@ -371,15 +278,15 @@ export class LineCursor extends Marker { * @returns The right most child of the given node, or the node if no child * exists. */ - getRightMostChild( - node: INavigable | null, - stopIfFound: INavigable, - ): INavigable | null { + private getRightMostChild( + node: IFocusableNode | null, + stopIfFound: IFocusableNode, + ): IFocusableNode | null { if (!node) return node; let newNode = this.workspace.getNavigator().getFirstChild(node); if (!newNode || newNode === stopIfFound) return node; for ( - let nextNode: INavigable | null = newNode; + let nextNode: IFocusableNode | null = newNode; nextNode; nextNode = this.workspace.getNavigator().getNextSibling(newNode) ) { @@ -414,7 +321,7 @@ export class LineCursor extends Marker { preDelete(deletedBlock: BlockSvg) { const curNode = this.getCurNode(); - const nodes: INavigable[] = curNode ? [curNode] : []; + const nodes: IFocusableNode[] = curNode ? [curNode] : []; // The connection to which the deleted block is attached. const parentConnection = deletedBlock.previousConnection?.targetConnection ?? @@ -466,7 +373,7 @@ export class LineCursor extends Marker { * * @returns The current field, connection, or block the cursor is on. */ - override getCurNode(): INavigable | null { + override getCurNode(): IFocusableNode | null { this.updateCurNodeFromFocus(); return super.getCurNode(); } @@ -479,7 +386,7 @@ export class LineCursor extends Marker { * * @param newNode The new location of the cursor. */ - override setCurNode(newNode: INavigable | null) { + override setCurNode(newNode: IFocusableNode | null) { super.setCurNode(newNode); if (isFocusableNode(newNode)) { @@ -513,7 +420,7 @@ export class LineCursor extends Marker { * * @returns The first navigable node on the workspace, or null. */ - getFirstNode(): INavigable | null { + getFirstNode(): IFocusableNode | null { return this.workspace.getNavigator().getFirstChild(this.workspace); } @@ -522,7 +429,7 @@ export class LineCursor extends Marker { * * @returns The last navigable node on the workspace, or null. */ - getLastNode(): INavigable | null { + getLastNode(): IFocusableNode | null { const first = this.getFirstNode(); return this.getPreviousNode(first, () => true, true); } diff --git a/core/keyboard_nav/marker.ts b/core/keyboard_nav/marker.ts index 931f068b3..5c2e7e946 100644 --- a/core/keyboard_nav/marker.ts +++ b/core/keyboard_nav/marker.ts @@ -14,7 +14,7 @@ import {BlockSvg} from '../block_svg.js'; import {Field} from '../field.js'; -import type {INavigable} from '../interfaces/i_navigable.js'; +import type {IFocusableNode} from '../interfaces/i_focusable_node.js'; import {RenderedConnection} from '../rendered_connection.js'; /** @@ -26,7 +26,7 @@ export class Marker { colour: string | null = null; /** The current location of the marker. */ - protected curNode: INavigable | null = null; + protected curNode: IFocusableNode | null = null; /** The type of the marker. */ type = 'marker'; @@ -36,7 +36,7 @@ export class Marker { * * @returns The current field, connection, or block the marker is on. */ - getCurNode(): INavigable | null { + getCurNode(): IFocusableNode | null { return this.curNode; } @@ -45,7 +45,7 @@ export class Marker { * * @param newNode The new location of the marker, or null to remove it. */ - setCurNode(newNode: INavigable | null) { + setCurNode(newNode: IFocusableNode | null) { this.curNode = newNode; } @@ -59,7 +59,7 @@ export class Marker { * * @returns The parent block of the node if any, otherwise null. */ - getSourceBlockFromNode(node: INavigable | null): BlockSvg | null { + getSourceBlockFromNode(node: IFocusableNode | null): BlockSvg | null { if (node instanceof BlockSvg) { return node; } else if (node instanceof Field) { diff --git a/core/keyboard_nav/workspace_navigation_policy.ts b/core/keyboard_nav/workspace_navigation_policy.ts index ed7dc5d02..12a7555b4 100644 --- a/core/keyboard_nav/workspace_navigation_policy.ts +++ b/core/keyboard_nav/workspace_navigation_policy.ts @@ -4,9 +4,9 @@ * SPDX-License-Identifier: Apache-2.0 */ -import type {INavigable} from '../interfaces/i_navigable.js'; +import type {IFocusableNode} from '../interfaces/i_focusable_node.js'; import type {INavigationPolicy} from '../interfaces/i_navigation_policy.js'; -import type {WorkspaceSvg} from '../workspace_svg.js'; +import {WorkspaceSvg} from '../workspace_svg.js'; /** * Set of rules controlling keyboard navigation from a workspace. @@ -20,7 +20,7 @@ export class WorkspaceNavigationPolicy * @param current The workspace to return the first child of. * @returns The top block of the first block stack, if any. */ - getFirstChild(current: WorkspaceSvg): INavigable | null { + getFirstChild(current: WorkspaceSvg): IFocusableNode | null { const blocks = current.getTopBlocks(true); return blocks.length ? blocks[0] : null; } @@ -31,7 +31,7 @@ export class WorkspaceNavigationPolicy * @param _current The workspace to return the parent of. * @returns Null. */ - getParent(_current: WorkspaceSvg): INavigable | null { + getParent(_current: WorkspaceSvg): IFocusableNode | null { return null; } @@ -41,7 +41,7 @@ export class WorkspaceNavigationPolicy * @param _current The workspace to return the next sibling of. * @returns Null. */ - getNextSibling(_current: WorkspaceSvg): INavigable | null { + getNextSibling(_current: WorkspaceSvg): IFocusableNode | null { return null; } @@ -51,7 +51,7 @@ export class WorkspaceNavigationPolicy * @param _current The workspace to return the previous sibling of. * @returns Null. */ - getPreviousSibling(_current: WorkspaceSvg): INavigable | null { + getPreviousSibling(_current: WorkspaceSvg): IFocusableNode | null { return null; } @@ -64,4 +64,14 @@ export class WorkspaceNavigationPolicy isNavigable(current: WorkspaceSvg): boolean { return current.canBeFocused(); } + + /** + * Returns whether the given object can be navigated from by this policy. + * + * @param current The object to check if this policy applies to. + * @returns True if the object is a WorkspaceSvg. + */ + isApplicable(current: any): current is WorkspaceSvg { + return current instanceof WorkspaceSvg; + } } diff --git a/core/navigator.ts b/core/navigator.ts index ffff2507c..7a1c2d4ea 100644 --- a/core/navigator.ts +++ b/core/navigator.ts @@ -4,10 +4,14 @@ * SPDX-License-Identifier: Apache-2.0 */ -import type {INavigable} from './interfaces/i_navigable.js'; +import type {IFocusableNode} from './interfaces/i_focusable_node.js'; import type {INavigationPolicy} from './interfaces/i_navigation_policy.js'; +import {BlockNavigationPolicy} from './keyboard_nav/block_navigation_policy.js'; +import {ConnectionNavigationPolicy} from './keyboard_nav/connection_navigation_policy.js'; +import {FieldNavigationPolicy} from './keyboard_nav/field_navigation_policy.js'; +import {WorkspaceNavigationPolicy} from './keyboard_nav/workspace_navigation_policy.js'; -type RuleMap = Map T, INavigationPolicy>; +type RuleList = INavigationPolicy[]; /** * Class responsible for determining where focus should move in response to @@ -18,35 +22,35 @@ export class Navigator { * Map from classes to a corresponding ruleset to handle navigation from * instances of that class. */ - private rules: RuleMap = new Map(); + protected rules: RuleList = [ + new BlockNavigationPolicy(), + new FieldNavigationPolicy(), + new ConnectionNavigationPolicy(), + new WorkspaceNavigationPolicy(), + ]; /** - * Associates a navigation ruleset with its corresponding class. + * Adds a navigation ruleset to this Navigator. * - * @param key The class whose object instances should have their navigation - * controlled by the associated policy. * @param policy A ruleset that determines where focus should move starting - * from an instance of the given class. + * from an instance of its managed class. */ - set>( - key: new (...args: any) => T, - policy: INavigationPolicy, - ) { - this.rules.set(key, policy); + addNavigationPolicy(policy: INavigationPolicy) { + this.rules.push(policy); } /** * Returns the navigation ruleset associated with the given object instance's * class. * - * @param key An object to retrieve a navigation ruleset for. + * @param current An object to retrieve a navigation ruleset for. * @returns The navigation ruleset of objects of the given object's class, or * undefined if no ruleset has been registered for the object's class. */ - private get>( - key: T, - ): INavigationPolicy | undefined { - return this.rules.get(key.getClass()); + private get( + current: IFocusableNode, + ): INavigationPolicy | undefined { + return this.rules.find((rule) => rule.isApplicable(current)); } /** @@ -55,7 +59,7 @@ export class Navigator { * @param current The object to retrieve the first child of. * @returns The first child node of the given object, if any. */ - getFirstChild>(current: T): INavigable | null { + getFirstChild(current: IFocusableNode): IFocusableNode | null { const result = this.get(current)?.getFirstChild(current); if (!result) return null; // If the child isn't navigable, don't traverse into it; check its peers. @@ -71,7 +75,7 @@ export class Navigator { * @param current The object to retrieve the parent of. * @returns The parent node of the given object, if any. */ - getParent>(current: T): INavigable | null { + getParent(current: IFocusableNode): IFocusableNode | null { const result = this.get(current)?.getParent(current); if (!result) return null; if (!this.get(result)?.isNavigable(result)) return this.getParent(result); @@ -84,7 +88,7 @@ export class Navigator { * @param current The object to retrieve the next sibling node of. * @returns The next sibling node of the given object, if any. */ - getNextSibling>(current: T): INavigable | null { + getNextSibling(current: IFocusableNode): IFocusableNode | null { const result = this.get(current)?.getNextSibling(current); if (!result) return null; if (!this.get(result)?.isNavigable(result)) { @@ -99,9 +103,7 @@ export class Navigator { * @param current The object to retrieve the previous sibling node of. * @returns The previous sibling node of the given object, if any. */ - getPreviousSibling>( - current: T, - ): INavigable | null { + getPreviousSibling(current: IFocusableNode): IFocusableNode | null { const result = this.get(current)?.getPreviousSibling(current); if (!result) return null; if (!this.get(result)?.isNavigable(result)) { diff --git a/core/rendered_connection.ts b/core/rendered_connection.ts index 4298afa3f..84905eecc 100644 --- a/core/rendered_connection.ts +++ b/core/rendered_connection.ts @@ -24,7 +24,6 @@ import {IContextMenu} from './interfaces/i_contextmenu.js'; import type {IFocusableNode} from './interfaces/i_focusable_node.js'; import type {IFocusableTree} from './interfaces/i_focusable_tree.js'; import {hasBubble} from './interfaces/i_has_bubble.js'; -import type {INavigable} from './interfaces/i_navigable.js'; import * as internalConstants from './internal_constants.js'; import {Coordinate} from './utils/coordinate.js'; import * as svgMath from './utils/svg_math.js'; @@ -38,7 +37,7 @@ const BUMP_RANDOMNESS = 10; */ export class RenderedConnection extends Connection - implements IContextMenu, IFocusableNode, INavigable + implements IContextMenu, IFocusableNode { // TODO(b/109816955): remove '!', see go/strict-prop-init-fix. sourceBlock_!: BlockSvg; @@ -664,15 +663,6 @@ export class RenderedConnection | unknown | null as SVGElement | null; } - - /** - * Returns this connection's class for keyboard navigation. - * - * @returns RenderedConnection. - */ - getClass() { - return RenderedConnection; - } } export namespace RenderedConnection { diff --git a/core/workspace_svg.ts b/core/workspace_svg.ts index 3095f0924..5caa56818 100644 --- a/core/workspace_svg.ts +++ b/core/workspace_svg.ts @@ -53,7 +53,6 @@ import { import type {IFocusableTree} from './interfaces/i_focusable_tree.js'; import {hasBubble} from './interfaces/i_has_bubble.js'; import type {IMetricsManager} from './interfaces/i_metrics_manager.js'; -import type {INavigable} from './interfaces/i_navigable.js'; import type {IToolbox} from './interfaces/i_toolbox.js'; import type {LineCursor} from './keyboard_nav/line_cursor.js'; import type {Marker} from './keyboard_nav/marker.js'; @@ -100,11 +99,7 @@ const ZOOM_TO_FIT_MARGIN = 20; */ export class WorkspaceSvg extends Workspace - implements - IContextMenu, - IFocusableNode, - IFocusableTree, - INavigable + implements IContextMenu, IFocusableNode, IFocusableTree { /** * A wrapper function called when a resize event occurs. @@ -2823,15 +2818,6 @@ export class WorkspaceSvg } } - /** - * Returns the class of this workspace. - * - * @returns WorkspaceSvg. - */ - getClass() { - return WorkspaceSvg; - } - /** * Returns an object responsible for coordinating movement of focus between * items on this workspace in response to keyboard navigation commands. @@ -2841,6 +2827,16 @@ export class WorkspaceSvg getNavigator(): Navigator { return this.navigator; } + + /** + * Sets the Navigator instance used by this workspace. + * + * @param newNavigator A Navigator object to coordinate movement between + * elements on the workspace. + */ + setNavigator(newNavigator: Navigator) { + this.navigator = newNavigator; + } } /** diff --git a/tests/typescript/src/field/different_user_input.ts b/tests/typescript/src/field/different_user_input.ts index 3083fe554..f91f25bbc 100644 --- a/tests/typescript/src/field/different_user_input.ts +++ b/tests/typescript/src/field/different_user_input.ts @@ -61,10 +61,6 @@ class FieldMitosis extends Field { }); this.value_ = {cells}; } - - getClass() { - return FieldMitosis; - } } fieldRegistry.register('field_mitosis', FieldMitosis);