mirror of
https://github.com/google/blockly.git
synced 2026-01-04 23:50:12 +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 {IFocusableNode} from './interfaces/i_focusable_node.js';
|
||||||
import type {IFocusableTree} from './interfaces/i_focusable_tree.js';
|
import type {IFocusableTree} from './interfaces/i_focusable_tree.js';
|
||||||
import {IIcon} from './interfaces/i_icon.js';
|
import {IIcon} from './interfaces/i_icon.js';
|
||||||
import type {INavigable} from './interfaces/i_navigable.js';
|
|
||||||
import * as internalConstants from './internal_constants.js';
|
import * as internalConstants from './internal_constants.js';
|
||||||
import {Msg} from './msg.js';
|
import {Msg} from './msg.js';
|
||||||
import * as renderManagement from './render_management.js';
|
import * as renderManagement from './render_management.js';
|
||||||
@@ -77,8 +76,7 @@ export class BlockSvg
|
|||||||
ICopyable<BlockCopyData>,
|
ICopyable<BlockCopyData>,
|
||||||
IDraggable,
|
IDraggable,
|
||||||
IDeletable,
|
IDeletable,
|
||||||
IFocusableNode,
|
IFocusableNode
|
||||||
INavigable<BlockSvg>
|
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Constant for identifying rows that are to be rendered inline.
|
* Constant for identifying rows that are to be rendered inline.
|
||||||
@@ -1796,16 +1794,4 @@ export class BlockSvg
|
|||||||
canBeFocused(): boolean {
|
canBeFocused(): boolean {
|
||||||
return true;
|
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 {IVariableMap} from './interfaces/i_variable_map.js';
|
||||||
import {IVariableModel, IVariableState} from './interfaces/i_variable_model.js';
|
import {IVariableModel, IVariableState} from './interfaces/i_variable_model.js';
|
||||||
import * as internalConstants from './internal_constants.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 {Marker} from './keyboard_nav/marker.js';
|
||||||
import type {LayerManager} from './layer_manager.js';
|
import type {LayerManager} from './layer_manager.js';
|
||||||
import * as layers from './layers.js';
|
import * as layers from './layers.js';
|
||||||
@@ -429,7 +429,7 @@ Names.prototype.populateProcedures = function (
|
|||||||
};
|
};
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
export * from './interfaces/i_navigable.js';
|
export * from './flyout_navigator.js';
|
||||||
export * from './interfaces/i_navigation_policy.js';
|
export * from './interfaces/i_navigation_policy.js';
|
||||||
export * from './keyboard_nav/block_navigation_policy.js';
|
export * from './keyboard_nav/block_navigation_policy.js';
|
||||||
export * from './keyboard_nav/connection_navigation_policy.js';
|
export * from './keyboard_nav/connection_navigation_policy.js';
|
||||||
@@ -457,7 +457,6 @@ export {
|
|||||||
ContextMenuItems,
|
ContextMenuItems,
|
||||||
ContextMenuRegistry,
|
ContextMenuRegistry,
|
||||||
Css,
|
Css,
|
||||||
CursorOptions,
|
|
||||||
DeleteArea,
|
DeleteArea,
|
||||||
DragTarget,
|
DragTarget,
|
||||||
Events,
|
Events,
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import type {Input} from './inputs/input.js';
|
|||||||
import type {IFocusableNode} from './interfaces/i_focusable_node.js';
|
import type {IFocusableNode} from './interfaces/i_focusable_node.js';
|
||||||
import type {IFocusableTree} from './interfaces/i_focusable_tree.js';
|
import type {IFocusableTree} from './interfaces/i_focusable_tree.js';
|
||||||
import type {IKeyboardAccessible} from './interfaces/i_keyboard_accessible.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 type {IRegistrable} from './interfaces/i_registrable.js';
|
||||||
import {ISerializable} from './interfaces/i_serializable.js';
|
import {ISerializable} from './interfaces/i_serializable.js';
|
||||||
import type {ConstantProvider} from './renderers/common/constants.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.
|
* @typeParam T - The value stored on the field.
|
||||||
*/
|
*/
|
||||||
export abstract class Field<T = any>
|
export abstract class Field<T = any>
|
||||||
implements
|
implements IKeyboardAccessible, IRegistrable, ISerializable, IFocusableNode
|
||||||
IKeyboardAccessible,
|
|
||||||
IRegistrable,
|
|
||||||
ISerializable,
|
|
||||||
IFocusableNode,
|
|
||||||
INavigable<Field<T>>
|
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* To overwrite the default value which is set in **Field**, directly update
|
* 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.`,
|
`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.
|
// 'override' the static fromJson method.
|
||||||
return new this(options.checked, undefined, options);
|
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);
|
fieldRegistry.register('field_checkbox', FieldCheckbox);
|
||||||
|
|||||||
@@ -796,18 +796,6 @@ export class FieldDropdown extends Field<string> {
|
|||||||
throw TypeError('Found invalid FieldDropdown options.');
|
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,
|
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);
|
fieldRegistry.register('field_image', FieldImage);
|
||||||
|
|||||||
@@ -126,18 +126,6 @@ export class FieldLabel extends Field<string> {
|
|||||||
// the static fromJson method.
|
// the static fromJson method.
|
||||||
return new this(text, undefined, options);
|
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);
|
fieldRegistry.register('field_label', FieldLabel);
|
||||||
|
|||||||
@@ -341,18 +341,6 @@ export class FieldNumber extends FieldInput<number> {
|
|||||||
options,
|
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);
|
fieldRegistry.register('field_number', FieldNumber);
|
||||||
|
|||||||
@@ -89,18 +89,6 @@ export class FieldTextInput extends FieldInput<string> {
|
|||||||
// override the static fromJson method.
|
// override the static fromJson method.
|
||||||
return new this(text, undefined, options);
|
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);
|
fieldRegistry.register('field_input', FieldTextInput);
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {EventType} from './events/type.js';
|
|||||||
import * as eventUtils from './events/utils.js';
|
import * as eventUtils from './events/utils.js';
|
||||||
import {FlyoutItem} from './flyout_item.js';
|
import {FlyoutItem} from './flyout_item.js';
|
||||||
import {FlyoutMetricsManager} from './flyout_metrics_manager.js';
|
import {FlyoutMetricsManager} from './flyout_metrics_manager.js';
|
||||||
|
import {FlyoutNavigator} from './flyout_navigator.js';
|
||||||
import {FlyoutSeparator, SeparatorAxis} from './flyout_separator.js';
|
import {FlyoutSeparator, SeparatorAxis} from './flyout_separator.js';
|
||||||
import {getFocusManager} from './focus_manager.js';
|
import {getFocusManager} from './focus_manager.js';
|
||||||
import {IAutoHideable} from './interfaces/i_autohideable.js';
|
import {IAutoHideable} from './interfaces/i_autohideable.js';
|
||||||
@@ -243,6 +244,7 @@ export abstract class Flyout
|
|||||||
this.workspace_.internalIsFlyout = true;
|
this.workspace_.internalIsFlyout = true;
|
||||||
// Keep the workspace visibility consistent with the flyout's visibility.
|
// Keep the workspace visibility consistent with the flyout's visibility.
|
||||||
this.workspace_.setVisible(this.visible);
|
this.workspace_.setVisible(this.visible);
|
||||||
|
this.workspace_.setNavigator(new FlyoutNavigator(this));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The unique id for this component that is used to register with the
|
* 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 {IBoundedElement} from './interfaces/i_bounded_element.js';
|
||||||
import type {IFocusableNode} from './interfaces/i_focusable_node.js';
|
import type {IFocusableNode} from './interfaces/i_focusable_node.js';
|
||||||
import type {IFocusableTree} from './interfaces/i_focusable_tree.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 type {IRenderedElement} from './interfaces/i_rendered_element.js';
|
||||||
import {idGenerator} from './utils.js';
|
import {idGenerator} from './utils.js';
|
||||||
import {Coordinate} from './utils/coordinate.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.
|
* Class for a button or label in the flyout.
|
||||||
*/
|
*/
|
||||||
export class FlyoutButton
|
export class FlyoutButton
|
||||||
implements
|
implements IBoundedElement, IRenderedElement, IFocusableNode
|
||||||
IBoundedElement,
|
|
||||||
IRenderedElement,
|
|
||||||
IFocusableNode,
|
|
||||||
INavigable<FlyoutButton>
|
|
||||||
{
|
{
|
||||||
/** The horizontal margin around the text in the button. */
|
/** The horizontal margin around the text in the button. */
|
||||||
static TEXT_MARGIN_X = 5;
|
static TEXT_MARGIN_X = 5;
|
||||||
@@ -412,18 +407,6 @@ export class FlyoutButton
|
|||||||
canBeFocused(): boolean {
|
canBeFocused(): boolean {
|
||||||
return true;
|
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. */
|
/** 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 {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.
|
* Representation of an item displayed in a flyout.
|
||||||
*/
|
*/
|
||||||
@@ -12,7 +13,7 @@ export class FlyoutItem {
|
|||||||
* flyout inflater that created this object.
|
* flyout inflater that created this object.
|
||||||
*/
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
private element: IBoundedElement & INavigable<any>,
|
private element: IBoundedElement & IFocusableNode,
|
||||||
private type: string,
|
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 {IBoundedElement} from './interfaces/i_bounded_element.js';
|
||||||
import type {IFocusableNode} from './interfaces/i_focusable_node.js';
|
import type {IFocusableNode} from './interfaces/i_focusable_node.js';
|
||||||
import type {IFocusableTree} from './interfaces/i_focusable_tree.js';
|
import type {IFocusableTree} from './interfaces/i_focusable_tree.js';
|
||||||
import type {INavigable} from './interfaces/i_navigable.js';
|
|
||||||
import {Rect} from './utils/rect.js';
|
import {Rect} from './utils/rect.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Representation of a gap between elements in a flyout.
|
* Representation of a gap between elements in a flyout.
|
||||||
*/
|
*/
|
||||||
export class FlyoutSeparator
|
export class FlyoutSeparator implements IBoundedElement, IFocusableNode {
|
||||||
implements IBoundedElement, INavigable<FlyoutSeparator>, IFocusableNode
|
|
||||||
{
|
|
||||||
private x = 0;
|
private x = 0;
|
||||||
private y = 0;
|
private y = 0;
|
||||||
|
|
||||||
@@ -66,18 +63,6 @@ export class FlyoutSeparator
|
|||||||
return false;
|
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. */
|
/** See IFocusableNode.getFocusableElement. */
|
||||||
getFocusableElement(): HTMLElement | SVGElement {
|
getFocusableElement(): HTMLElement | SVGElement {
|
||||||
throw new Error('Cannot be focused');
|
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
|
* 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.
|
* 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.
|
* @param current The element which the user is navigating into.
|
||||||
* @returns The current element's first child, or null if it has none.
|
* @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.
|
* 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.
|
* @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.
|
* @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.
|
* 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
|
* @returns The next peer element of the current element, or null if there is
|
||||||
* none.
|
* none.
|
||||||
*/
|
*/
|
||||||
getNextSibling(current: T): INavigable<any> | null;
|
getNextSibling(current: T): IFocusableNode | null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the peer element preceding the given element, if any.
|
* 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
|
* @returns The previous peer element of the current element, or null if
|
||||||
* there is none.
|
* 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
|
* 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.
|
* @returns True if this element should be included in keyboard navigation.
|
||||||
*/
|
*/
|
||||||
isNavigable(current: T): boolean;
|
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 {BlockSvg} from '../block_svg.js';
|
||||||
import type {Field} from '../field.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 type {INavigationPolicy} from '../interfaces/i_navigation_policy.js';
|
||||||
import {WorkspaceSvg} from '../workspace_svg.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.
|
* @param current The block to return the first child of.
|
||||||
* @returns The first field or input of the given block, if any.
|
* @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 input of current.inputList) {
|
||||||
for (const field of input.fieldRow) {
|
for (const field of input.fieldRow) {
|
||||||
return field;
|
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
|
* @returns The top block of the given block's stack, or the connection to
|
||||||
* which it is attached.
|
* which it is attached.
|
||||||
*/
|
*/
|
||||||
getParent(current: BlockSvg): INavigable<unknown> | null {
|
getParent(current: BlockSvg): IFocusableNode | null {
|
||||||
if (current.previousConnection?.targetBlock()) {
|
if (current.previousConnection?.targetBlock()) {
|
||||||
const surroundParent = current.getSurroundParent();
|
const surroundParent = current.getSurroundParent();
|
||||||
if (surroundParent) return surroundParent;
|
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
|
* @returns The first block of the next stack if the given block is a terminal
|
||||||
* block, or its next connection.
|
* block, or its next connection.
|
||||||
*/
|
*/
|
||||||
getNextSibling(current: BlockSvg): INavigable<unknown> | null {
|
getNextSibling(current: BlockSvg): IFocusableNode | null {
|
||||||
if (current.nextConnection?.targetBlock()) {
|
if (current.nextConnection?.targetBlock()) {
|
||||||
return 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
|
* @returns The block's previous/output connection, or the last
|
||||||
* connection/block of the previous block stack if it is a root block.
|
* 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()) {
|
if (current.previousConnection?.targetBlock()) {
|
||||||
return current.previousConnection?.targetBlock();
|
return current.previousConnection?.targetBlock();
|
||||||
}
|
}
|
||||||
@@ -127,7 +127,7 @@ export class BlockNavigationPolicy implements INavigationPolicy<BlockSvg> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const currentIndex = siblings.indexOf(current);
|
const currentIndex = siblings.indexOf(current);
|
||||||
let result: INavigable<any> | null = null;
|
let result: IFocusableNode | null = null;
|
||||||
if (currentIndex >= 1) {
|
if (currentIndex >= 1) {
|
||||||
result = siblings[currentIndex - 1];
|
result = siblings[currentIndex - 1];
|
||||||
} else if (currentIndex === 0 && navigatingCrossStacks) {
|
} else if (currentIndex === 0 && navigatingCrossStacks) {
|
||||||
@@ -152,4 +152,14 @@ export class BlockNavigationPolicy implements INavigationPolicy<BlockSvg> {
|
|||||||
isNavigable(current: BlockSvg): boolean {
|
isNavigable(current: BlockSvg): boolean {
|
||||||
return current.canBeFocused();
|
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 type {BlockSvg} from '../block_svg.js';
|
||||||
import {ConnectionType} from '../connection_type.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 {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.
|
* 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.
|
* @param current The connection to return the first child of.
|
||||||
* @returns The connection's first child element, or null if not none.
|
* @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()) {
|
if (current.getParentInput()) {
|
||||||
return current.targetConnection;
|
return current.targetConnection;
|
||||||
}
|
}
|
||||||
@@ -36,7 +36,7 @@ export class ConnectionNavigationPolicy
|
|||||||
* @param current The connection to return the parent of.
|
* @param current The connection to return the parent of.
|
||||||
* @returns The given connection's parent connection or block.
|
* @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) {
|
if (current.type === ConnectionType.OUTPUT_VALUE) {
|
||||||
return current.targetConnection ?? current.getSourceBlock();
|
return current.targetConnection ?? current.getSourceBlock();
|
||||||
} else if (current.getParentInput()) {
|
} else if (current.getParentInput()) {
|
||||||
@@ -56,7 +56,7 @@ export class ConnectionNavigationPolicy
|
|||||||
* @param current The connection to navigate from.
|
* @param current The connection to navigate from.
|
||||||
* @returns The field, input connection or block following this connection.
|
* @returns The field, input connection or block following this connection.
|
||||||
*/
|
*/
|
||||||
getNextSibling(current: RenderedConnection): INavigable<unknown> | null {
|
getNextSibling(current: RenderedConnection): IFocusableNode | null {
|
||||||
if (current.getParentInput()) {
|
if (current.getParentInput()) {
|
||||||
const parentInput = current.getParentInput();
|
const parentInput = current.getParentInput();
|
||||||
const block = parentInput?.getSourceBlock();
|
const block = parentInput?.getSourceBlock();
|
||||||
@@ -101,7 +101,7 @@ export class ConnectionNavigationPolicy
|
|||||||
* @param current The connection to navigate from.
|
* @param current The connection to navigate from.
|
||||||
* @returns The field, input connection or block preceding this connection.
|
* @returns The field, input connection or block preceding this connection.
|
||||||
*/
|
*/
|
||||||
getPreviousSibling(current: RenderedConnection): INavigable<unknown> | null {
|
getPreviousSibling(current: RenderedConnection): IFocusableNode | null {
|
||||||
if (current.getParentInput()) {
|
if (current.getParentInput()) {
|
||||||
const parentInput = current.getParentInput();
|
const parentInput = current.getParentInput();
|
||||||
const block = parentInput?.getSourceBlock();
|
const block = parentInput?.getSourceBlock();
|
||||||
@@ -176,4 +176,14 @@ export class ConnectionNavigationPolicy
|
|||||||
isNavigable(current: RenderedConnection): boolean {
|
isNavigable(current: RenderedConnection): boolean {
|
||||||
return current.canBeFocused();
|
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 {BlockSvg} from '../block_svg.js';
|
||||||
import type {Field} from '../field.js';
|
import {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 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.
|
* @param _current The field to navigate from.
|
||||||
* @returns Null.
|
* @returns Null.
|
||||||
*/
|
*/
|
||||||
getFirstChild(_current: Field<any>): INavigable<unknown> | null {
|
getFirstChild(_current: Field<any>): IFocusableNode | null {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ export class FieldNavigationPolicy implements INavigationPolicy<Field<any>> {
|
|||||||
* @param current The field to navigate from.
|
* @param current The field to navigate from.
|
||||||
* @returns The given field's parent block.
|
* @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;
|
return current.getSourceBlock() as BlockSvg;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ export class FieldNavigationPolicy implements INavigationPolicy<Field<any>> {
|
|||||||
* @param current The field to navigate from.
|
* @param current The field to navigate from.
|
||||||
* @returns The next field or input in the given field's block.
|
* @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 input = current.getParentInput();
|
||||||
const block = current.getSourceBlock();
|
const block = current.getSourceBlock();
|
||||||
if (!block) return null;
|
if (!block) return null;
|
||||||
@@ -64,7 +64,7 @@ export class FieldNavigationPolicy implements INavigationPolicy<Field<any>> {
|
|||||||
* @param current The field to navigate from.
|
* @param current The field to navigate from.
|
||||||
* @returns The preceding field or input in the given field's block.
|
* @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 parentInput = current.getParentInput();
|
||||||
const block = current.getSourceBlock();
|
const block = current.getSourceBlock();
|
||||||
if (!block) return null;
|
if (!block) return null;
|
||||||
@@ -106,4 +106,14 @@ export class FieldNavigationPolicy implements INavigationPolicy<Field<any>> {
|
|||||||
current.getParentInput().isVisible()
|
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
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type {FlyoutButton} from '../flyout_button.js';
|
import {FlyoutButton} from '../flyout_button.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 {INavigationPolicy} from '../interfaces/i_navigation_policy.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -20,7 +20,7 @@ export class FlyoutButtonNavigationPolicy
|
|||||||
* @param _current The FlyoutButton instance to navigate from.
|
* @param _current The FlyoutButton instance to navigate from.
|
||||||
* @returns Null.
|
* @returns Null.
|
||||||
*/
|
*/
|
||||||
getFirstChild(_current: FlyoutButton): INavigable<unknown> | null {
|
getFirstChild(_current: FlyoutButton): IFocusableNode | null {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ export class FlyoutButtonNavigationPolicy
|
|||||||
* @param current The FlyoutButton instance to navigate from.
|
* @param current The FlyoutButton instance to navigate from.
|
||||||
* @returns The given flyout button's parent workspace.
|
* @returns The given flyout button's parent workspace.
|
||||||
*/
|
*/
|
||||||
getParent(current: FlyoutButton): INavigable<unknown> | null {
|
getParent(current: FlyoutButton): IFocusableNode | null {
|
||||||
return current.getWorkspace();
|
return current.getWorkspace();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ export class FlyoutButtonNavigationPolicy
|
|||||||
* @param _current The FlyoutButton instance to navigate from.
|
* @param _current The FlyoutButton instance to navigate from.
|
||||||
* @returns Null.
|
* @returns Null.
|
||||||
*/
|
*/
|
||||||
getNextSibling(_current: FlyoutButton): INavigable<unknown> | null {
|
getNextSibling(_current: FlyoutButton): IFocusableNode | null {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ export class FlyoutButtonNavigationPolicy
|
|||||||
* @param _current The FlyoutButton instance to navigate from.
|
* @param _current The FlyoutButton instance to navigate from.
|
||||||
* @returns Null.
|
* @returns Null.
|
||||||
*/
|
*/
|
||||||
getPreviousSibling(_current: FlyoutButton): INavigable<unknown> | null {
|
getPreviousSibling(_current: FlyoutButton): IFocusableNode | null {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,4 +63,14 @@ export class FlyoutButtonNavigationPolicy
|
|||||||
isNavigable(current: FlyoutButton): boolean {
|
isNavigable(current: FlyoutButton): boolean {
|
||||||
return current.canBeFocused();
|
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 {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';
|
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.
|
* @param _current The flyout item to navigate from.
|
||||||
* @returns Null to prevent navigating into flyout items.
|
* @returns Null to prevent navigating into flyout items.
|
||||||
*/
|
*/
|
||||||
getFirstChild(_current: T): INavigable<unknown> | null {
|
getFirstChild(_current: T): IFocusableNode | null {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ export class FlyoutNavigationPolicy<T> implements INavigationPolicy<T> {
|
|||||||
* @param current The flyout item to navigate from.
|
* @param current The flyout item to navigate from.
|
||||||
* @returns The parent of the given flyout item.
|
* @returns The parent of the given flyout item.
|
||||||
*/
|
*/
|
||||||
getParent(current: T): INavigable<unknown> | null {
|
getParent(current: T): IFocusableNode | null {
|
||||||
return this.policy.getParent(current);
|
return this.policy.getParent(current);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ export class FlyoutNavigationPolicy<T> implements INavigationPolicy<T> {
|
|||||||
* @param current The flyout item to navigate from.
|
* @param current The flyout item to navigate from.
|
||||||
* @returns The flyout item following the given one.
|
* @returns The flyout item following the given one.
|
||||||
*/
|
*/
|
||||||
getNextSibling(current: T): INavigable<unknown> | null {
|
getNextSibling(current: T): IFocusableNode | null {
|
||||||
const flyoutContents = this.flyout.getContents();
|
const flyoutContents = this.flyout.getContents();
|
||||||
if (!flyoutContents) return null;
|
if (!flyoutContents) return null;
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ export class FlyoutNavigationPolicy<T> implements INavigationPolicy<T> {
|
|||||||
* @param current The flyout item to navigate from.
|
* @param current The flyout item to navigate from.
|
||||||
* @returns The flyout item preceding the given one.
|
* @returns The flyout item preceding the given one.
|
||||||
*/
|
*/
|
||||||
getPreviousSibling(current: T): INavigable<unknown> | null {
|
getPreviousSibling(current: T): IFocusableNode | null {
|
||||||
const flyoutContents = this.flyout.getContents();
|
const flyoutContents = this.flyout.getContents();
|
||||||
if (!flyoutContents) return null;
|
if (!flyoutContents) return null;
|
||||||
|
|
||||||
@@ -98,4 +98,14 @@ export class FlyoutNavigationPolicy<T> implements INavigationPolicy<T> {
|
|||||||
isNavigable(current: T): boolean {
|
isNavigable(current: T): boolean {
|
||||||
return this.policy.isNavigable(current);
|
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
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type {FlyoutSeparator} from '../flyout_separator.js';
|
import {FlyoutSeparator} from '../flyout_separator.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 {INavigationPolicy} from '../interfaces/i_navigation_policy.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -15,19 +15,19 @@ import type {INavigationPolicy} from '../interfaces/i_navigation_policy.js';
|
|||||||
export class FlyoutSeparatorNavigationPolicy
|
export class FlyoutSeparatorNavigationPolicy
|
||||||
implements INavigationPolicy<FlyoutSeparator>
|
implements INavigationPolicy<FlyoutSeparator>
|
||||||
{
|
{
|
||||||
getFirstChild(_current: FlyoutSeparator): INavigable<unknown> | null {
|
getFirstChild(_current: FlyoutSeparator): IFocusableNode | null {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
getParent(_current: FlyoutSeparator): INavigable<unknown> | null {
|
getParent(_current: FlyoutSeparator): IFocusableNode | null {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
getNextSibling(_current: FlyoutSeparator): INavigable<unknown> | null {
|
getNextSibling(_current: FlyoutSeparator): IFocusableNode | null {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
getPreviousSibling(_current: FlyoutSeparator): INavigable<unknown> | null {
|
getPreviousSibling(_current: FlyoutSeparator): IFocusableNode | null {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,4 +40,14 @@ export class FlyoutSeparatorNavigationPolicy
|
|||||||
isNavigable(_current: FlyoutSeparator): boolean {
|
isNavigable(_current: FlyoutSeparator): boolean {
|
||||||
return false;
|
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 {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 {getFocusManager} from '../focus_manager.js';
|
||||||
|
import type {IFocusableNode} from '../interfaces/i_focusable_node.js';
|
||||||
import {isFocusableNode} 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 * 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 {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 {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.
|
* Class for a line cursor.
|
||||||
@@ -58,76 +27,14 @@ const defaultOptions: CursorOptions = {
|
|||||||
export class LineCursor extends Marker {
|
export class LineCursor extends Marker {
|
||||||
override type = 'cursor';
|
override type = 'cursor';
|
||||||
|
|
||||||
/** Options for this line cursor. */
|
|
||||||
private readonly options: CursorOptions;
|
|
||||||
|
|
||||||
/** Locations to try moving the cursor to after a deletion. */
|
/** Locations to try moving the cursor to after a deletion. */
|
||||||
private potentialNodes: INavigable<any>[] | null = null;
|
private potentialNodes: IFocusableNode[] | null = null;
|
||||||
|
|
||||||
/** Whether the renderer is zelos-style. */
|
|
||||||
private isZelos = false;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param workspace The workspace this cursor belongs to.
|
* @param workspace The workspace this cursor belongs to.
|
||||||
* @param options Cursor options.
|
|
||||||
*/
|
*/
|
||||||
constructor(
|
constructor(protected readonly workspace: WorkspaceSvg) {
|
||||||
protected readonly workspace: WorkspaceSvg,
|
|
||||||
options?: Partial<CursorOptions>,
|
|
||||||
) {
|
|
||||||
super();
|
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
|
* @returns The next node, or null if the current node is
|
||||||
* not set or there is no next value.
|
* not set or there is no next value.
|
||||||
*/
|
*/
|
||||||
next(): INavigable<any> | null {
|
next(): IFocusableNode | null {
|
||||||
const curNode = this.getCurNode();
|
const curNode = this.getCurNode();
|
||||||
if (!curNode) {
|
if (!curNode) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const newNode = this.getNextNode(
|
const newNode = this.getNextNode(
|
||||||
curNode,
|
curNode,
|
||||||
(candidate: INavigable<any> | null) => {
|
(candidate: IFocusableNode | null) => {
|
||||||
return (
|
return (
|
||||||
candidate instanceof BlockSvg &&
|
candidate instanceof BlockSvg &&
|
||||||
!candidate.outputConnection?.targetBlock()
|
!candidate.outputConnection?.targetBlock()
|
||||||
@@ -166,7 +73,7 @@ export class LineCursor extends Marker {
|
|||||||
* @returns The next node, or null if the current node is
|
* @returns The next node, or null if the current node is
|
||||||
* not set or there is no next value.
|
* not set or there is no next value.
|
||||||
*/
|
*/
|
||||||
in(): INavigable<any> | null {
|
in(): IFocusableNode | null {
|
||||||
const curNode = this.getCurNode();
|
const curNode = this.getCurNode();
|
||||||
if (!curNode) {
|
if (!curNode) {
|
||||||
return null;
|
return null;
|
||||||
@@ -186,14 +93,14 @@ export class LineCursor extends Marker {
|
|||||||
* @returns The previous node, or null if the current node
|
* @returns The previous node, or null if the current node
|
||||||
* is not set or there is no previous value.
|
* is not set or there is no previous value.
|
||||||
*/
|
*/
|
||||||
prev(): INavigable<any> | null {
|
prev(): IFocusableNode | null {
|
||||||
const curNode = this.getCurNode();
|
const curNode = this.getCurNode();
|
||||||
if (!curNode) {
|
if (!curNode) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const newNode = this.getPreviousNode(
|
const newNode = this.getPreviousNode(
|
||||||
curNode,
|
curNode,
|
||||||
(candidate: INavigable<any> | null) => {
|
(candidate: IFocusableNode | null) => {
|
||||||
return (
|
return (
|
||||||
candidate instanceof BlockSvg &&
|
candidate instanceof BlockSvg &&
|
||||||
!candidate.outputConnection?.targetBlock()
|
!candidate.outputConnection?.targetBlock()
|
||||||
@@ -215,7 +122,7 @@ export class LineCursor extends Marker {
|
|||||||
* @returns The previous node, or null if the current node
|
* @returns The previous node, or null if the current node
|
||||||
* is not set or there is no previous value.
|
* is not set or there is no previous value.
|
||||||
*/
|
*/
|
||||||
out(): INavigable<any> | null {
|
out(): IFocusableNode | null {
|
||||||
const curNode = this.getCurNode();
|
const curNode = this.getCurNode();
|
||||||
if (!curNode) {
|
if (!curNode) {
|
||||||
return null;
|
return null;
|
||||||
@@ -241,7 +148,7 @@ export class LineCursor extends Marker {
|
|||||||
const inNode = this.getNextNode(curNode, () => true, true);
|
const inNode = this.getNextNode(curNode, () => true, true);
|
||||||
const nextNode = this.getNextNode(
|
const nextNode = this.getNextNode(
|
||||||
curNode,
|
curNode,
|
||||||
(candidate: INavigable<any> | null) => {
|
(candidate: IFocusableNode | null) => {
|
||||||
return (
|
return (
|
||||||
candidate instanceof BlockSvg &&
|
candidate instanceof BlockSvg &&
|
||||||
!candidate.outputConnection?.targetBlock()
|
!candidate.outputConnection?.targetBlock()
|
||||||
@@ -265,10 +172,10 @@ export class LineCursor extends Marker {
|
|||||||
* @returns The next node in the traversal.
|
* @returns The next node in the traversal.
|
||||||
*/
|
*/
|
||||||
private getNextNodeImpl(
|
private getNextNodeImpl(
|
||||||
node: INavigable<any> | null,
|
node: IFocusableNode | null,
|
||||||
isValid: (p1: INavigable<any> | null) => boolean,
|
isValid: (p1: IFocusableNode | null) => boolean,
|
||||||
visitedNodes: Set<INavigable<any>> = new Set<INavigable<any>>(),
|
visitedNodes: Set<IFocusableNode> = new Set<IFocusableNode>(),
|
||||||
): INavigable<any> | null {
|
): IFocusableNode | null {
|
||||||
if (!node || visitedNodes.has(node)) return null;
|
if (!node || visitedNodes.has(node)) return null;
|
||||||
let newNode =
|
let newNode =
|
||||||
this.workspace.getNavigator().getFirstChild(node) ||
|
this.workspace.getNavigator().getFirstChild(node) ||
|
||||||
@@ -301,10 +208,10 @@ export class LineCursor extends Marker {
|
|||||||
* @returns The next node in the traversal.
|
* @returns The next node in the traversal.
|
||||||
*/
|
*/
|
||||||
getNextNode(
|
getNextNode(
|
||||||
node: INavigable<any> | null,
|
node: IFocusableNode | null,
|
||||||
isValid: (p1: INavigable<any> | null) => boolean,
|
isValid: (p1: IFocusableNode | null) => boolean,
|
||||||
loop: boolean,
|
loop: boolean,
|
||||||
): INavigable<any> | null {
|
): IFocusableNode | null {
|
||||||
if (!node || (!loop && this.getLastNode() === node)) return null;
|
if (!node || (!loop && this.getLastNode() === node)) return null;
|
||||||
|
|
||||||
return this.getNextNodeImpl(node, isValid);
|
return this.getNextNodeImpl(node, isValid);
|
||||||
@@ -323,10 +230,10 @@ export class LineCursor extends Marker {
|
|||||||
* exists.
|
* exists.
|
||||||
*/
|
*/
|
||||||
private getPreviousNodeImpl(
|
private getPreviousNodeImpl(
|
||||||
node: INavigable<any> | null,
|
node: IFocusableNode | null,
|
||||||
isValid: (p1: INavigable<any> | null) => boolean,
|
isValid: (p1: IFocusableNode | null) => boolean,
|
||||||
visitedNodes: Set<INavigable<any>> = new Set<INavigable<any>>(),
|
visitedNodes: Set<IFocusableNode> = new Set<IFocusableNode>(),
|
||||||
): INavigable<any> | null {
|
): IFocusableNode | null {
|
||||||
if (!node || visitedNodes.has(node)) return null;
|
if (!node || visitedNodes.has(node)) return null;
|
||||||
|
|
||||||
const newNode =
|
const newNode =
|
||||||
@@ -355,10 +262,10 @@ export class LineCursor extends Marker {
|
|||||||
* exists.
|
* exists.
|
||||||
*/
|
*/
|
||||||
getPreviousNode(
|
getPreviousNode(
|
||||||
node: INavigable<any> | null,
|
node: IFocusableNode | null,
|
||||||
isValid: (p1: INavigable<any> | null) => boolean,
|
isValid: (p1: IFocusableNode | null) => boolean,
|
||||||
loop: boolean,
|
loop: boolean,
|
||||||
): INavigable<any> | null {
|
): IFocusableNode | null {
|
||||||
if (!node || (!loop && this.getFirstNode() === node)) return null;
|
if (!node || (!loop && this.getFirstNode() === node)) return null;
|
||||||
|
|
||||||
return this.getPreviousNodeImpl(node, isValid);
|
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
|
* @returns The right most child of the given node, or the node if no child
|
||||||
* exists.
|
* exists.
|
||||||
*/
|
*/
|
||||||
getRightMostChild(
|
private getRightMostChild(
|
||||||
node: INavigable<any> | null,
|
node: IFocusableNode | null,
|
||||||
stopIfFound: INavigable<any>,
|
stopIfFound: IFocusableNode,
|
||||||
): INavigable<any> | null {
|
): IFocusableNode | null {
|
||||||
if (!node) return node;
|
if (!node) return node;
|
||||||
let newNode = this.workspace.getNavigator().getFirstChild(node);
|
let newNode = this.workspace.getNavigator().getFirstChild(node);
|
||||||
if (!newNode || newNode === stopIfFound) return node;
|
if (!newNode || newNode === stopIfFound) return node;
|
||||||
for (
|
for (
|
||||||
let nextNode: INavigable<any> | null = newNode;
|
let nextNode: IFocusableNode | null = newNode;
|
||||||
nextNode;
|
nextNode;
|
||||||
nextNode = this.workspace.getNavigator().getNextSibling(newNode)
|
nextNode = this.workspace.getNavigator().getNextSibling(newNode)
|
||||||
) {
|
) {
|
||||||
@@ -414,7 +321,7 @@ export class LineCursor extends Marker {
|
|||||||
preDelete(deletedBlock: BlockSvg) {
|
preDelete(deletedBlock: BlockSvg) {
|
||||||
const curNode = this.getCurNode();
|
const curNode = this.getCurNode();
|
||||||
|
|
||||||
const nodes: INavigable<any>[] = curNode ? [curNode] : [];
|
const nodes: IFocusableNode[] = curNode ? [curNode] : [];
|
||||||
// The connection to which the deleted block is attached.
|
// The connection to which the deleted block is attached.
|
||||||
const parentConnection =
|
const parentConnection =
|
||||||
deletedBlock.previousConnection?.targetConnection ??
|
deletedBlock.previousConnection?.targetConnection ??
|
||||||
@@ -466,7 +373,7 @@ export class LineCursor extends Marker {
|
|||||||
*
|
*
|
||||||
* @returns The current field, connection, or block the cursor is on.
|
* @returns The current field, connection, or block the cursor is on.
|
||||||
*/
|
*/
|
||||||
override getCurNode(): INavigable<any> | null {
|
override getCurNode(): IFocusableNode | null {
|
||||||
this.updateCurNodeFromFocus();
|
this.updateCurNodeFromFocus();
|
||||||
return super.getCurNode();
|
return super.getCurNode();
|
||||||
}
|
}
|
||||||
@@ -479,7 +386,7 @@ export class LineCursor extends Marker {
|
|||||||
*
|
*
|
||||||
* @param newNode The new location of the cursor.
|
* @param newNode The new location of the cursor.
|
||||||
*/
|
*/
|
||||||
override setCurNode(newNode: INavigable<any> | null) {
|
override setCurNode(newNode: IFocusableNode | null) {
|
||||||
super.setCurNode(newNode);
|
super.setCurNode(newNode);
|
||||||
|
|
||||||
if (isFocusableNode(newNode)) {
|
if (isFocusableNode(newNode)) {
|
||||||
@@ -513,7 +420,7 @@ export class LineCursor extends Marker {
|
|||||||
*
|
*
|
||||||
* @returns The first navigable node on the workspace, or null.
|
* @returns The first navigable node on the workspace, or null.
|
||||||
*/
|
*/
|
||||||
getFirstNode(): INavigable<any> | null {
|
getFirstNode(): IFocusableNode | null {
|
||||||
return this.workspace.getNavigator().getFirstChild(this.workspace);
|
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.
|
* @returns The last navigable node on the workspace, or null.
|
||||||
*/
|
*/
|
||||||
getLastNode(): INavigable<any> | null {
|
getLastNode(): IFocusableNode | null {
|
||||||
const first = this.getFirstNode();
|
const first = this.getFirstNode();
|
||||||
return this.getPreviousNode(first, () => true, true);
|
return this.getPreviousNode(first, () => true, true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
import {BlockSvg} from '../block_svg.js';
|
import {BlockSvg} from '../block_svg.js';
|
||||||
import {Field} from '../field.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';
|
import {RenderedConnection} from '../rendered_connection.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -26,7 +26,7 @@ export class Marker {
|
|||||||
colour: string | null = null;
|
colour: string | null = null;
|
||||||
|
|
||||||
/** The current location of the marker. */
|
/** The current location of the marker. */
|
||||||
protected curNode: INavigable<any> | null = null;
|
protected curNode: IFocusableNode | null = null;
|
||||||
|
|
||||||
/** The type of the marker. */
|
/** The type of the marker. */
|
||||||
type = 'marker';
|
type = 'marker';
|
||||||
@@ -36,7 +36,7 @@ export class Marker {
|
|||||||
*
|
*
|
||||||
* @returns The current field, connection, or block the marker is on.
|
* @returns The current field, connection, or block the marker is on.
|
||||||
*/
|
*/
|
||||||
getCurNode(): INavigable<any> | null {
|
getCurNode(): IFocusableNode | null {
|
||||||
return this.curNode;
|
return this.curNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ export class Marker {
|
|||||||
*
|
*
|
||||||
* @param newNode The new location of the marker, or null to remove it.
|
* @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;
|
this.curNode = newNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ export class Marker {
|
|||||||
*
|
*
|
||||||
* @returns The parent block of the node if any, otherwise null.
|
* @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) {
|
if (node instanceof BlockSvg) {
|
||||||
return node;
|
return node;
|
||||||
} else if (node instanceof Field) {
|
} else if (node instanceof Field) {
|
||||||
|
|||||||
@@ -4,9 +4,9 @@
|
|||||||
* SPDX-License-Identifier: Apache-2.0
|
* 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 {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.
|
* 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.
|
* @param current The workspace to return the first child of.
|
||||||
* @returns The top block of the first block stack, if any.
|
* @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);
|
const blocks = current.getTopBlocks(true);
|
||||||
return blocks.length ? blocks[0] : null;
|
return blocks.length ? blocks[0] : null;
|
||||||
}
|
}
|
||||||
@@ -31,7 +31,7 @@ export class WorkspaceNavigationPolicy
|
|||||||
* @param _current The workspace to return the parent of.
|
* @param _current The workspace to return the parent of.
|
||||||
* @returns Null.
|
* @returns Null.
|
||||||
*/
|
*/
|
||||||
getParent(_current: WorkspaceSvg): INavigable<unknown> | null {
|
getParent(_current: WorkspaceSvg): IFocusableNode | null {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ export class WorkspaceNavigationPolicy
|
|||||||
* @param _current The workspace to return the next sibling of.
|
* @param _current The workspace to return the next sibling of.
|
||||||
* @returns Null.
|
* @returns Null.
|
||||||
*/
|
*/
|
||||||
getNextSibling(_current: WorkspaceSvg): INavigable<unknown> | null {
|
getNextSibling(_current: WorkspaceSvg): IFocusableNode | null {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ export class WorkspaceNavigationPolicy
|
|||||||
* @param _current The workspace to return the previous sibling of.
|
* @param _current The workspace to return the previous sibling of.
|
||||||
* @returns Null.
|
* @returns Null.
|
||||||
*/
|
*/
|
||||||
getPreviousSibling(_current: WorkspaceSvg): INavigable<unknown> | null {
|
getPreviousSibling(_current: WorkspaceSvg): IFocusableNode | null {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,4 +64,14 @@ export class WorkspaceNavigationPolicy
|
|||||||
isNavigable(current: WorkspaceSvg): boolean {
|
isNavigable(current: WorkspaceSvg): boolean {
|
||||||
return current.canBeFocused();
|
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
|
* 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 {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
|
* 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
|
* Map from classes to a corresponding ruleset to handle navigation from
|
||||||
* instances of that class.
|
* 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
|
* @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>>(
|
addNavigationPolicy(policy: INavigationPolicy<any>) {
|
||||||
key: new (...args: any) => T,
|
this.rules.push(policy);
|
||||||
policy: INavigationPolicy<T>,
|
|
||||||
) {
|
|
||||||
this.rules.set(key, policy);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the navigation ruleset associated with the given object instance's
|
* Returns the navigation ruleset associated with the given object instance's
|
||||||
* class.
|
* 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
|
* @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.
|
* undefined if no ruleset has been registered for the object's class.
|
||||||
*/
|
*/
|
||||||
private get<T extends INavigable<T>>(
|
private get(
|
||||||
key: T,
|
current: IFocusableNode,
|
||||||
): INavigationPolicy<T> | undefined {
|
): INavigationPolicy<typeof current> | undefined {
|
||||||
return this.rules.get(key.getClass());
|
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.
|
* @param current The object to retrieve the first child of.
|
||||||
* @returns The first child node of the given object, if any.
|
* @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);
|
const result = this.get(current)?.getFirstChild(current);
|
||||||
if (!result) return null;
|
if (!result) return null;
|
||||||
// If the child isn't navigable, don't traverse into it; check its peers.
|
// 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.
|
* @param current The object to retrieve the parent of.
|
||||||
* @returns The parent node of the given object, if any.
|
* @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);
|
const result = this.get(current)?.getParent(current);
|
||||||
if (!result) return null;
|
if (!result) return null;
|
||||||
if (!this.get(result)?.isNavigable(result)) return this.getParent(result);
|
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.
|
* @param current The object to retrieve the next sibling node of.
|
||||||
* @returns The next sibling node of the given object, if any.
|
* @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);
|
const result = this.get(current)?.getNextSibling(current);
|
||||||
if (!result) return null;
|
if (!result) return null;
|
||||||
if (!this.get(result)?.isNavigable(result)) {
|
if (!this.get(result)?.isNavigable(result)) {
|
||||||
@@ -99,9 +103,7 @@ export class Navigator {
|
|||||||
* @param current The object to retrieve the previous sibling node of.
|
* @param current The object to retrieve the previous sibling node of.
|
||||||
* @returns The previous sibling node of the given object, if any.
|
* @returns The previous sibling node of the given object, if any.
|
||||||
*/
|
*/
|
||||||
getPreviousSibling<T extends INavigable<T>>(
|
getPreviousSibling(current: IFocusableNode): IFocusableNode | null {
|
||||||
current: T,
|
|
||||||
): INavigable<any> | null {
|
|
||||||
const result = this.get(current)?.getPreviousSibling(current);
|
const result = this.get(current)?.getPreviousSibling(current);
|
||||||
if (!result) return null;
|
if (!result) return null;
|
||||||
if (!this.get(result)?.isNavigable(result)) {
|
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 {IFocusableNode} from './interfaces/i_focusable_node.js';
|
||||||
import type {IFocusableTree} from './interfaces/i_focusable_tree.js';
|
import type {IFocusableTree} from './interfaces/i_focusable_tree.js';
|
||||||
import {hasBubble} from './interfaces/i_has_bubble.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 * as internalConstants from './internal_constants.js';
|
||||||
import {Coordinate} from './utils/coordinate.js';
|
import {Coordinate} from './utils/coordinate.js';
|
||||||
import * as svgMath from './utils/svg_math.js';
|
import * as svgMath from './utils/svg_math.js';
|
||||||
@@ -38,7 +37,7 @@ const BUMP_RANDOMNESS = 10;
|
|||||||
*/
|
*/
|
||||||
export class RenderedConnection
|
export class RenderedConnection
|
||||||
extends Connection
|
extends Connection
|
||||||
implements IContextMenu, IFocusableNode, INavigable<RenderedConnection>
|
implements IContextMenu, IFocusableNode
|
||||||
{
|
{
|
||||||
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
|
// TODO(b/109816955): remove '!', see go/strict-prop-init-fix.
|
||||||
sourceBlock_!: BlockSvg;
|
sourceBlock_!: BlockSvg;
|
||||||
@@ -664,15 +663,6 @@ export class RenderedConnection
|
|||||||
| unknown
|
| unknown
|
||||||
| null as SVGElement | null;
|
| null as SVGElement | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns this connection's class for keyboard navigation.
|
|
||||||
*
|
|
||||||
* @returns RenderedConnection.
|
|
||||||
*/
|
|
||||||
getClass() {
|
|
||||||
return RenderedConnection;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace RenderedConnection {
|
export namespace RenderedConnection {
|
||||||
|
|||||||
@@ -53,7 +53,6 @@ import {
|
|||||||
import type {IFocusableTree} from './interfaces/i_focusable_tree.js';
|
import type {IFocusableTree} from './interfaces/i_focusable_tree.js';
|
||||||
import {hasBubble} from './interfaces/i_has_bubble.js';
|
import {hasBubble} from './interfaces/i_has_bubble.js';
|
||||||
import type {IMetricsManager} from './interfaces/i_metrics_manager.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 {IToolbox} from './interfaces/i_toolbox.js';
|
||||||
import type {LineCursor} from './keyboard_nav/line_cursor.js';
|
import type {LineCursor} from './keyboard_nav/line_cursor.js';
|
||||||
import type {Marker} from './keyboard_nav/marker.js';
|
import type {Marker} from './keyboard_nav/marker.js';
|
||||||
@@ -100,11 +99,7 @@ const ZOOM_TO_FIT_MARGIN = 20;
|
|||||||
*/
|
*/
|
||||||
export class WorkspaceSvg
|
export class WorkspaceSvg
|
||||||
extends Workspace
|
extends Workspace
|
||||||
implements
|
implements IContextMenu, IFocusableNode, IFocusableTree
|
||||||
IContextMenu,
|
|
||||||
IFocusableNode,
|
|
||||||
IFocusableTree,
|
|
||||||
INavigable<WorkspaceSvg>
|
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* A wrapper function called when a resize event occurs.
|
* 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
|
* Returns an object responsible for coordinating movement of focus between
|
||||||
* items on this workspace in response to keyboard navigation commands.
|
* items on this workspace in response to keyboard navigation commands.
|
||||||
@@ -2841,6 +2827,16 @@ export class WorkspaceSvg
|
|||||||
getNavigator(): Navigator {
|
getNavigator(): Navigator {
|
||||||
return this.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};
|
this.value_ = {cells};
|
||||||
}
|
}
|
||||||
|
|
||||||
getClass() {
|
|
||||||
return FieldMitosis;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fieldRegistry.register('field_mitosis', FieldMitosis);
|
fieldRegistry.register('field_mitosis', FieldMitosis);
|
||||||
|
|||||||
Reference in New Issue
Block a user