mirror of
https://github.com/google/blockly.git
synced 2025-12-15 13:50:08 +01:00
refactor: Remove INavigable in favor of IFocusableNode. (#9037)
* refactor: Remove INavigable in favor of IFocusableNode. * chore: Fix JSDoc. * chore: Address review feedback.
This commit is contained in:
@@ -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<BlockCopyData>,
|
||||
IDraggable,
|
||||
IDeletable,
|
||||
IFocusableNode,
|
||||
INavigable<BlockSvg>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<T = any> = (newValue: T) => T | null | undefined;
|
||||
* @typeParam T - The value stored on the field.
|
||||
*/
|
||||
export abstract class Field<T = any>
|
||||
implements
|
||||
IKeyboardAccessible,
|
||||
IRegistrable,
|
||||
ISerializable,
|
||||
IFocusableNode,
|
||||
INavigable<Field<T>>
|
||||
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<T = any>
|
||||
`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<T>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -228,18 +228,6 @@ export class FieldCheckbox extends Field<CheckboxBool> {
|
||||
// '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);
|
||||
|
||||
@@ -796,18 +796,6 @@ export class FieldDropdown extends Field<string> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -272,18 +272,6 @@ export class FieldImage extends Field<string> {
|
||||
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);
|
||||
|
||||
@@ -126,18 +126,6 @@ export class FieldLabel extends Field<string> {
|
||||
// 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);
|
||||
|
||||
@@ -341,18 +341,6 @@ export class FieldNumber extends FieldInput<number> {
|
||||
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);
|
||||
|
||||
@@ -89,18 +89,6 @@ export class FieldTextInput extends FieldInput<string> {
|
||||
// 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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<FlyoutButton>
|
||||
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. */
|
||||
|
||||
@@ -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<any>,
|
||||
private element: IBoundedElement & IFocusableNode,
|
||||
private type: string,
|
||||
) {}
|
||||
|
||||
|
||||
24
core/flyout_navigator.ts
Normal file
24
core/flyout_navigator.ts
Normal file
@@ -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),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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<FlyoutSeparator>, 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');
|
||||
|
||||
@@ -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<T> extends IFocusableNode {
|
||||
/**
|
||||
* Returns the class of this instance.
|
||||
*
|
||||
* @returns This object's class.
|
||||
*/
|
||||
getClass(): new (...args: any) => T;
|
||||
}
|
||||
@@ -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<T> {
|
||||
* @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<any> | null;
|
||||
getFirstChild(current: T): IFocusableNode | null;
|
||||
|
||||
/**
|
||||
* Returns the parent element of the given element, if any.
|
||||
@@ -24,7 +24,7 @@ export interface INavigationPolicy<T> {
|
||||
* @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<any> | null;
|
||||
getParent(current: T): IFocusableNode | null;
|
||||
|
||||
/**
|
||||
* Returns the peer element following the given element, if any.
|
||||
@@ -33,7 +33,7 @@ export interface INavigationPolicy<T> {
|
||||
* @returns The next peer element of the current element, or null if there is
|
||||
* none.
|
||||
*/
|
||||
getNextSibling(current: T): INavigable<any> | null;
|
||||
getNextSibling(current: T): IFocusableNode | null;
|
||||
|
||||
/**
|
||||
* Returns the peer element preceding the given element, if any.
|
||||
@@ -42,7 +42,7 @@ export interface INavigationPolicy<T> {
|
||||
* @returns The previous peer element of the current element, or null if
|
||||
* there is none.
|
||||
*/
|
||||
getPreviousSibling(current: T): INavigable<any> | 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<T> {
|
||||
* @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;
|
||||
}
|
||||
|
||||
@@ -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<BlockSvg> {
|
||||
* @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<unknown> | 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<BlockSvg> {
|
||||
* @returns The top block of the given block's stack, or the connection to
|
||||
* which it is attached.
|
||||
*/
|
||||
getParent(current: BlockSvg): INavigable<unknown> | 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<BlockSvg> {
|
||||
* @returns The first block of the next stack if the given block is a terminal
|
||||
* block, or its next connection.
|
||||
*/
|
||||
getNextSibling(current: BlockSvg): INavigable<unknown> | null {
|
||||
getNextSibling(current: BlockSvg): IFocusableNode | null {
|
||||
if (current.nextConnection?.targetBlock()) {
|
||||
return current.nextConnection?.targetBlock();
|
||||
}
|
||||
@@ -101,7 +101,7 @@ export class BlockNavigationPolicy implements INavigationPolicy<BlockSvg> {
|
||||
* @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<unknown> | null {
|
||||
getPreviousSibling(current: BlockSvg): IFocusableNode | null {
|
||||
if (current.previousConnection?.targetBlock()) {
|
||||
return current.previousConnection?.targetBlock();
|
||||
}
|
||||
@@ -127,7 +127,7 @@ export class BlockNavigationPolicy implements INavigationPolicy<BlockSvg> {
|
||||
}
|
||||
|
||||
const currentIndex = siblings.indexOf(current);
|
||||
let result: INavigable<any> | 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<BlockSvg> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<unknown> | 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<unknown> | 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<unknown> | 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<unknown> | 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Field<any>> {
|
||||
* @param _current The field to navigate from.
|
||||
* @returns Null.
|
||||
*/
|
||||
getFirstChild(_current: Field<any>): INavigable<unknown> | null {
|
||||
getFirstChild(_current: Field<any>): IFocusableNode | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ export class FieldNavigationPolicy implements INavigationPolicy<Field<any>> {
|
||||
* @param current The field to navigate from.
|
||||
* @returns The given field's parent block.
|
||||
*/
|
||||
getParent(current: Field<any>): INavigable<unknown> | null {
|
||||
getParent(current: Field<any>): IFocusableNode | null {
|
||||
return current.getSourceBlock() as BlockSvg;
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ export class FieldNavigationPolicy implements INavigationPolicy<Field<any>> {
|
||||
* @param current The field to navigate from.
|
||||
* @returns The next field or input in the given field's block.
|
||||
*/
|
||||
getNextSibling(current: Field<any>): INavigable<unknown> | null {
|
||||
getNextSibling(current: Field<any>): IFocusableNode | null {
|
||||
const input = current.getParentInput();
|
||||
const block = current.getSourceBlock();
|
||||
if (!block) return null;
|
||||
@@ -64,7 +64,7 @@ export class FieldNavigationPolicy implements INavigationPolicy<Field<any>> {
|
||||
* @param current The field to navigate from.
|
||||
* @returns The preceding field or input in the given field's block.
|
||||
*/
|
||||
getPreviousSibling(current: Field<any>): INavigable<unknown> | null {
|
||||
getPreviousSibling(current: Field<any>): IFocusableNode | null {
|
||||
const parentInput = current.getParentInput();
|
||||
const block = current.getSourceBlock();
|
||||
if (!block) return null;
|
||||
@@ -106,4 +106,14 @@ export class FieldNavigationPolicy implements INavigationPolicy<Field<any>> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<unknown> | 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<unknown> | 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<unknown> | 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<unknown> | 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<T> implements INavigationPolicy<T> {
|
||||
* @param _current The flyout item to navigate from.
|
||||
* @returns Null to prevent navigating into flyout items.
|
||||
*/
|
||||
getFirstChild(_current: T): INavigable<unknown> | null {
|
||||
getFirstChild(_current: T): IFocusableNode | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ export class FlyoutNavigationPolicy<T> implements INavigationPolicy<T> {
|
||||
* @param current The flyout item to navigate from.
|
||||
* @returns The parent of the given flyout item.
|
||||
*/
|
||||
getParent(current: T): INavigable<unknown> | null {
|
||||
getParent(current: T): IFocusableNode | null {
|
||||
return this.policy.getParent(current);
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ export class FlyoutNavigationPolicy<T> implements INavigationPolicy<T> {
|
||||
* @param current The flyout item to navigate from.
|
||||
* @returns The flyout item following the given one.
|
||||
*/
|
||||
getNextSibling(current: T): INavigable<unknown> | null {
|
||||
getNextSibling(current: T): IFocusableNode | null {
|
||||
const flyoutContents = this.flyout.getContents();
|
||||
if (!flyoutContents) return null;
|
||||
|
||||
@@ -72,7 +72,7 @@ export class FlyoutNavigationPolicy<T> implements INavigationPolicy<T> {
|
||||
* @param current The flyout item to navigate from.
|
||||
* @returns The flyout item preceding the given one.
|
||||
*/
|
||||
getPreviousSibling(current: T): INavigable<unknown> | null {
|
||||
getPreviousSibling(current: T): IFocusableNode | null {
|
||||
const flyoutContents = this.flyout.getContents();
|
||||
if (!flyoutContents) return null;
|
||||
|
||||
@@ -98,4 +98,14 @@ export class FlyoutNavigationPolicy<T> implements INavigationPolicy<T> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<FlyoutSeparator>
|
||||
{
|
||||
getFirstChild(_current: FlyoutSeparator): INavigable<unknown> | null {
|
||||
getFirstChild(_current: FlyoutSeparator): IFocusableNode | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
getParent(_current: FlyoutSeparator): INavigable<unknown> | null {
|
||||
getParent(_current: FlyoutSeparator): IFocusableNode | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
getNextSibling(_current: FlyoutSeparator): INavigable<unknown> | null {
|
||||
getNextSibling(_current: FlyoutSeparator): IFocusableNode | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
getPreviousSibling(_current: FlyoutSeparator): INavigable<unknown> | 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<any>[] | 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<CursorOptions>,
|
||||
) {
|
||||
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<any> | null {
|
||||
next(): IFocusableNode | null {
|
||||
const curNode = this.getCurNode();
|
||||
if (!curNode) {
|
||||
return null;
|
||||
}
|
||||
const newNode = this.getNextNode(
|
||||
curNode,
|
||||
(candidate: INavigable<any> | 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<any> | 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<any> | null {
|
||||
prev(): IFocusableNode | null {
|
||||
const curNode = this.getCurNode();
|
||||
if (!curNode) {
|
||||
return null;
|
||||
}
|
||||
const newNode = this.getPreviousNode(
|
||||
curNode,
|
||||
(candidate: INavigable<any> | 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<any> | 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<any> | 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<any> | null,
|
||||
isValid: (p1: INavigable<any> | null) => boolean,
|
||||
visitedNodes: Set<INavigable<any>> = new Set<INavigable<any>>(),
|
||||
): INavigable<any> | null {
|
||||
node: IFocusableNode | null,
|
||||
isValid: (p1: IFocusableNode | null) => boolean,
|
||||
visitedNodes: Set<IFocusableNode> = new Set<IFocusableNode>(),
|
||||
): 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<any> | null,
|
||||
isValid: (p1: INavigable<any> | null) => boolean,
|
||||
node: IFocusableNode | null,
|
||||
isValid: (p1: IFocusableNode | null) => boolean,
|
||||
loop: boolean,
|
||||
): INavigable<any> | 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<any> | null,
|
||||
isValid: (p1: INavigable<any> | null) => boolean,
|
||||
visitedNodes: Set<INavigable<any>> = new Set<INavigable<any>>(),
|
||||
): INavigable<any> | null {
|
||||
node: IFocusableNode | null,
|
||||
isValid: (p1: IFocusableNode | null) => boolean,
|
||||
visitedNodes: Set<IFocusableNode> = new Set<IFocusableNode>(),
|
||||
): IFocusableNode | null {
|
||||
if (!node || visitedNodes.has(node)) return null;
|
||||
|
||||
const newNode =
|
||||
@@ -355,10 +262,10 @@ export class LineCursor extends Marker {
|
||||
* exists.
|
||||
*/
|
||||
getPreviousNode(
|
||||
node: INavigable<any> | null,
|
||||
isValid: (p1: INavigable<any> | null) => boolean,
|
||||
node: IFocusableNode | null,
|
||||
isValid: (p1: IFocusableNode | null) => boolean,
|
||||
loop: boolean,
|
||||
): INavigable<any> | 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<any> | null,
|
||||
stopIfFound: INavigable<any>,
|
||||
): INavigable<any> | 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<any> | 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<any>[] = 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<any> | 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<any> | 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<any> | 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<any> | null {
|
||||
getLastNode(): IFocusableNode | null {
|
||||
const first = this.getFirstNode();
|
||||
return this.getPreviousNode(first, () => true, true);
|
||||
}
|
||||
|
||||
@@ -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<any> | 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<any> | 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<any> | 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<any> | null): BlockSvg | null {
|
||||
getSourceBlockFromNode(node: IFocusableNode | null): BlockSvg | null {
|
||||
if (node instanceof BlockSvg) {
|
||||
return node;
|
||||
} else if (node instanceof Field) {
|
||||
|
||||
@@ -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<unknown> | 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<unknown> | 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<unknown> | 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<unknown> | 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<T> = Map<new (...args: any) => T, INavigationPolicy<T>>;
|
||||
type RuleList<T> = INavigationPolicy<T>[];
|
||||
|
||||
/**
|
||||
* 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<any> = new Map();
|
||||
protected rules: RuleList<any> = [
|
||||
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<T extends INavigable<T>>(
|
||||
key: new (...args: any) => T,
|
||||
policy: INavigationPolicy<T>,
|
||||
) {
|
||||
this.rules.set(key, policy);
|
||||
addNavigationPolicy(policy: INavigationPolicy<any>) {
|
||||
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<T extends INavigable<T>>(
|
||||
key: T,
|
||||
): INavigationPolicy<T> | undefined {
|
||||
return this.rules.get(key.getClass());
|
||||
private get(
|
||||
current: IFocusableNode,
|
||||
): INavigationPolicy<typeof current> | 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<T extends INavigable<T>>(current: T): INavigable<any> | 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<T extends INavigable<T>>(current: T): INavigable<any> | 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<T extends INavigable<T>>(current: T): INavigable<any> | 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<T extends INavigable<T>>(
|
||||
current: T,
|
||||
): INavigable<any> | null {
|
||||
getPreviousSibling(current: IFocusableNode): IFocusableNode | null {
|
||||
const result = this.get(current)?.getPreviousSibling(current);
|
||||
if (!result) return null;
|
||||
if (!this.get(result)?.isNavigable(result)) {
|
||||
|
||||
@@ -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<RenderedConnection>
|
||||
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 {
|
||||
|
||||
@@ -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<WorkspaceSvg>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -61,10 +61,6 @@ class FieldMitosis extends Field<CellGroup> {
|
||||
});
|
||||
this.value_ = {cells};
|
||||
}
|
||||
|
||||
getClass() {
|
||||
return FieldMitosis;
|
||||
}
|
||||
}
|
||||
|
||||
fieldRegistry.register('field_mitosis', FieldMitosis);
|
||||
|
||||
Reference in New Issue
Block a user