mirror of
https://github.com/google/blockly.git
synced 2026-01-07 17:10:11 +01:00
feat: Add support for keyboard navigation to/from block comments. (#9227)
* refactor: Update `TextInputBubble` to use `CommentEditor` for text editing. * feat: Designate `Bubble` as implementing `IFocusableNode`. * feat: Dismiss focused bubbles on Escape. * feat: Add support for keyboard navigation to block comments. * fix: Scroll comment editors rather than zooming the workspace. * chore: Add param to docstring.
This commit is contained in:
@@ -9,7 +9,9 @@ import * as common from '../common.js';
|
||||
import {BubbleDragStrategy} from '../dragging/bubble_drag_strategy.js';
|
||||
import {getFocusManager} from '../focus_manager.js';
|
||||
import {IBubble} from '../interfaces/i_bubble.js';
|
||||
import type {IFocusableNode} from '../interfaces/i_focusable_node.js';
|
||||
import type {IFocusableTree} from '../interfaces/i_focusable_tree.js';
|
||||
import type {IHasBubble} from '../interfaces/i_has_bubble.js';
|
||||
import {ISelectable} from '../interfaces/i_selectable.js';
|
||||
import {ContainerRegion} from '../metrics_manager.js';
|
||||
import {Scrollbar} from '../scrollbar.js';
|
||||
@@ -27,7 +29,7 @@ import {WorkspaceSvg} from '../workspace_svg.js';
|
||||
* bubble, where it has a "tail" that points to the block, and a "head" that
|
||||
* displays arbitrary svg elements.
|
||||
*/
|
||||
export abstract class Bubble implements IBubble, ISelectable {
|
||||
export abstract class Bubble implements IBubble, ISelectable, IFocusableNode {
|
||||
/** The width of the border around the bubble. */
|
||||
static readonly BORDER_WIDTH = 6;
|
||||
|
||||
@@ -100,12 +102,14 @@ export abstract class Bubble implements IBubble, ISelectable {
|
||||
* element that's represented by this bubble (as a focusable node). This
|
||||
* element will have its ID overwritten. If not provided, the focusable
|
||||
* element of this node will default to the bubble's SVG root.
|
||||
* @param owner The object responsible for hosting/spawning this bubble.
|
||||
*/
|
||||
constructor(
|
||||
public readonly workspace: WorkspaceSvg,
|
||||
protected anchor: Coordinate,
|
||||
protected ownerRect?: Rect,
|
||||
overriddenFocusableElement?: SVGElement | HTMLElement,
|
||||
protected owner?: IHasBubble & IFocusableNode,
|
||||
) {
|
||||
this.id = idGenerator.getNextUniqueId();
|
||||
this.svgRoot = dom.createSvgElement(
|
||||
@@ -145,6 +149,13 @@ export abstract class Bubble implements IBubble, ISelectable {
|
||||
this,
|
||||
this.onMouseDown,
|
||||
);
|
||||
|
||||
browserEvents.conditionalBind(
|
||||
this.focusableElement,
|
||||
'keydown',
|
||||
this,
|
||||
this.onKeyDown,
|
||||
);
|
||||
}
|
||||
|
||||
/** Dispose of this bubble. */
|
||||
@@ -229,6 +240,19 @@ export abstract class Bubble implements IBubble, ISelectable {
|
||||
getFocusManager().focusNode(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles key events when this bubble is focused. By default, closes the
|
||||
* bubble on Escape.
|
||||
*
|
||||
* @param e The keyboard event to handle.
|
||||
*/
|
||||
protected onKeyDown(e: KeyboardEvent) {
|
||||
if (e.key === 'Escape' && this.owner) {
|
||||
this.owner.setBubbleVisible(false);
|
||||
getFocusManager().focusNode(this.owner);
|
||||
}
|
||||
}
|
||||
|
||||
/** Positions the bubble relative to its anchor. Does not render its tail. */
|
||||
protected positionRelativeToAnchor() {
|
||||
let left = this.anchor.x;
|
||||
@@ -694,4 +718,11 @@ export abstract class Bubble implements IBubble, ISelectable {
|
||||
canBeFocused(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the object that owns/hosts this bubble, if any.
|
||||
*/
|
||||
getOwner(): (IHasBubble & IFocusableNode) | undefined {
|
||||
return this.owner;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,11 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {CommentEditor} from '../comments/comment_editor.js';
|
||||
import * as Css from '../css.js';
|
||||
import {getFocusManager} from '../focus_manager.js';
|
||||
import type {IFocusableNode} from '../interfaces/i_focusable_node.js';
|
||||
import type {IHasBubble} from '../interfaces/i_has_bubble.js';
|
||||
import * as touch from '../touch.js';
|
||||
import {browserEvents} from '../utils.js';
|
||||
import {Coordinate} from '../utils/coordinate.js';
|
||||
@@ -21,12 +25,6 @@ import {Bubble} from './bubble.js';
|
||||
* Used by the comment icon.
|
||||
*/
|
||||
export class TextInputBubble extends Bubble {
|
||||
/** The root of the elements specific to the text element. */
|
||||
private inputRoot: SVGForeignObjectElement;
|
||||
|
||||
/** The text input area element. */
|
||||
private textArea: HTMLTextAreaElement;
|
||||
|
||||
/** The group containing the lines indicating the bubble is resizable. */
|
||||
private resizeGroup: SVGGElement;
|
||||
|
||||
@@ -42,18 +40,12 @@ export class TextInputBubble extends Bubble {
|
||||
*/
|
||||
private resizePointerMoveListener: browserEvents.Data | null = null;
|
||||
|
||||
/** Functions listening for changes to the text of this bubble. */
|
||||
private textChangeListeners: (() => void)[] = [];
|
||||
|
||||
/** Functions listening for changes to the size of this bubble. */
|
||||
private sizeChangeListeners: (() => void)[] = [];
|
||||
|
||||
/** Functions listening for changes to the location of this bubble. */
|
||||
private locationChangeListeners: (() => void)[] = [];
|
||||
|
||||
/** The text of this bubble. */
|
||||
private text = '';
|
||||
|
||||
/** The default size of this bubble, including borders. */
|
||||
private readonly DEFAULT_SIZE = new Size(
|
||||
160 + Bubble.DOUBLE_BORDER,
|
||||
@@ -68,46 +60,47 @@ export class TextInputBubble extends Bubble {
|
||||
|
||||
private editable = true;
|
||||
|
||||
/** View responsible for supporting text editing. */
|
||||
private editor: CommentEditor;
|
||||
|
||||
/**
|
||||
* @param workspace The workspace this bubble belongs to.
|
||||
* @param anchor The anchor location of the thing this bubble is attached to.
|
||||
* The tail of the bubble will point to this location.
|
||||
* @param ownerRect An optional rect we don't want the bubble to overlap with
|
||||
* when automatically positioning.
|
||||
* @param owner The object that owns/hosts this bubble.
|
||||
*/
|
||||
constructor(
|
||||
public readonly workspace: WorkspaceSvg,
|
||||
protected anchor: Coordinate,
|
||||
protected ownerRect?: Rect,
|
||||
protected owner?: IHasBubble & IFocusableNode,
|
||||
) {
|
||||
super(workspace, anchor, ownerRect, TextInputBubble.createTextArea());
|
||||
super(workspace, anchor, ownerRect, undefined, owner);
|
||||
dom.addClass(this.svgRoot, 'blocklyTextInputBubble');
|
||||
this.textArea = this.getFocusableElement() as HTMLTextAreaElement;
|
||||
this.inputRoot = this.createEditor(this.contentContainer, this.textArea);
|
||||
this.editor = new CommentEditor(workspace, this.id, () => {
|
||||
getFocusManager().focusNode(this);
|
||||
});
|
||||
this.contentContainer.appendChild(this.editor.getDom());
|
||||
this.resizeGroup = this.createResizeHandle(this.svgRoot, workspace);
|
||||
this.setSize(this.DEFAULT_SIZE, true);
|
||||
}
|
||||
|
||||
/** @returns the text of this bubble. */
|
||||
getText(): string {
|
||||
return this.text;
|
||||
return this.editor.getText();
|
||||
}
|
||||
|
||||
/** Sets the text of this bubble. Calls change listeners. */
|
||||
setText(text: string) {
|
||||
this.text = text;
|
||||
this.textArea.value = text;
|
||||
this.onTextChange();
|
||||
this.editor.setText(text);
|
||||
}
|
||||
|
||||
/** Sets whether or not the text in the bubble is editable. */
|
||||
setEditable(editable: boolean) {
|
||||
this.editable = editable;
|
||||
if (this.editable) {
|
||||
this.textArea.removeAttribute('readonly');
|
||||
} else {
|
||||
this.textArea.setAttribute('readonly', '');
|
||||
}
|
||||
this.editor.setEditable(editable);
|
||||
}
|
||||
|
||||
/** Returns whether or not the text in the bubble is editable. */
|
||||
@@ -117,7 +110,7 @@ export class TextInputBubble extends Bubble {
|
||||
|
||||
/** Adds a change listener to be notified when this bubble's text changes. */
|
||||
addTextChangeListener(listener: () => void) {
|
||||
this.textChangeListeners.push(listener);
|
||||
this.editor.addTextChangeListener(listener);
|
||||
}
|
||||
|
||||
/** Adds a change listener to be notified when this bubble's size changes. */
|
||||
@@ -130,58 +123,6 @@ export class TextInputBubble extends Bubble {
|
||||
this.locationChangeListeners.push(listener);
|
||||
}
|
||||
|
||||
/** Creates and returns the editable text area for this bubble's editor. */
|
||||
private static createTextArea(): HTMLTextAreaElement {
|
||||
const textArea = document.createElementNS(
|
||||
dom.HTML_NS,
|
||||
'textarea',
|
||||
) as HTMLTextAreaElement;
|
||||
textArea.className = 'blocklyTextarea blocklyText';
|
||||
return textArea;
|
||||
}
|
||||
|
||||
/** Creates and returns the UI container element for this bubble's editor. */
|
||||
private createEditor(
|
||||
container: SVGGElement,
|
||||
textArea: HTMLTextAreaElement,
|
||||
): SVGForeignObjectElement {
|
||||
const inputRoot = dom.createSvgElement(
|
||||
Svg.FOREIGNOBJECT,
|
||||
{
|
||||
'x': Bubble.BORDER_WIDTH,
|
||||
'y': Bubble.BORDER_WIDTH,
|
||||
},
|
||||
container,
|
||||
);
|
||||
|
||||
const body = document.createElementNS(dom.HTML_NS, 'body');
|
||||
body.setAttribute('xmlns', dom.HTML_NS);
|
||||
body.className = 'blocklyMinimalBody';
|
||||
|
||||
textArea.setAttribute('dir', this.workspace.RTL ? 'RTL' : 'LTR');
|
||||
body.appendChild(textArea);
|
||||
inputRoot.appendChild(body);
|
||||
|
||||
this.bindTextAreaEvents(textArea);
|
||||
|
||||
return inputRoot;
|
||||
}
|
||||
|
||||
/** Binds events to the text area element. */
|
||||
private bindTextAreaEvents(textArea: HTMLTextAreaElement) {
|
||||
// Don't zoom with mousewheel; let it scroll instead.
|
||||
browserEvents.conditionalBind(textArea, 'wheel', this, (e: Event) => {
|
||||
e.stopPropagation();
|
||||
});
|
||||
// Don't let the pointerdown event get to the workspace.
|
||||
browserEvents.conditionalBind(textArea, 'pointerdown', this, (e: Event) => {
|
||||
e.stopPropagation();
|
||||
touch.clearTouchIdentifier();
|
||||
});
|
||||
|
||||
browserEvents.conditionalBind(textArea, 'change', this, this.onTextChange);
|
||||
}
|
||||
|
||||
/** Creates the resize handler elements and binds events to them. */
|
||||
private createResizeHandle(
|
||||
container: SVGGElement,
|
||||
@@ -220,8 +161,12 @@ export class TextInputBubble extends Bubble {
|
||||
|
||||
const widthMinusBorder = size.width - Bubble.DOUBLE_BORDER;
|
||||
const heightMinusBorder = size.height - Bubble.DOUBLE_BORDER;
|
||||
this.inputRoot.setAttribute('width', `${widthMinusBorder}`);
|
||||
this.inputRoot.setAttribute('height', `${heightMinusBorder}`);
|
||||
this.editor.updateSize(
|
||||
new Size(widthMinusBorder, heightMinusBorder),
|
||||
new Size(0, 0),
|
||||
);
|
||||
this.editor.getDom().setAttribute('x', `${Bubble.DOUBLE_BORDER / 2}`);
|
||||
this.editor.getDom().setAttribute('y', `${Bubble.DOUBLE_BORDER / 2}`);
|
||||
|
||||
this.resizeGroup.setAttribute('y', `${heightMinusBorder}`);
|
||||
if (this.workspace.RTL) {
|
||||
@@ -312,14 +257,6 @@ export class TextInputBubble extends Bubble {
|
||||
this.onSizeChange();
|
||||
}
|
||||
|
||||
/** Handles a text change event for the text area. Calls event listeners. */
|
||||
private onTextChange() {
|
||||
this.text = this.textArea.value;
|
||||
for (const listener of this.textChangeListeners) {
|
||||
listener();
|
||||
}
|
||||
}
|
||||
|
||||
/** Handles a size change event for the text area. Calls event listeners. */
|
||||
private onSizeChange() {
|
||||
for (const listener of this.sizeChangeListeners) {
|
||||
@@ -333,6 +270,15 @@ export class TextInputBubble extends Bubble {
|
||||
listener();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the text editor component of this bubble.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
getEditor() {
|
||||
return this.editor;
|
||||
}
|
||||
}
|
||||
|
||||
Css.register(`
|
||||
|
||||
@@ -53,6 +53,7 @@ export class CommentEditor implements IFocusableNode {
|
||||
'textarea',
|
||||
) as HTMLTextAreaElement;
|
||||
this.textArea.setAttribute('tabindex', '-1');
|
||||
this.textArea.setAttribute('dir', this.workspace.RTL ? 'RTL' : 'LTR');
|
||||
dom.addClass(this.textArea, 'blocklyCommentText');
|
||||
dom.addClass(this.textArea, 'blocklyTextarea');
|
||||
dom.addClass(this.textArea, 'blocklyText');
|
||||
@@ -86,6 +87,11 @@ export class CommentEditor implements IFocusableNode {
|
||||
},
|
||||
);
|
||||
|
||||
// Don't zoom with mousewheel; let it scroll instead.
|
||||
browserEvents.conditionalBind(this.textArea, 'wheel', this, (e: Event) => {
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
// Register listener for keydown events that would finish editing.
|
||||
browserEvents.conditionalBind(
|
||||
this.textArea,
|
||||
|
||||
@@ -74,15 +74,6 @@ export class RenderedWorkspaceComment
|
||||
this,
|
||||
this.startGesture,
|
||||
);
|
||||
// Don't zoom with mousewheel; let it scroll instead.
|
||||
browserEvents.conditionalBind(
|
||||
this.view.getSvgRoot(),
|
||||
'wheel',
|
||||
this,
|
||||
(e: Event) => {
|
||||
e.stopPropagation();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,7 +11,6 @@ import type {BlockSvg} from '../block_svg.js';
|
||||
import {TextInputBubble} from '../bubbles/textinput_bubble.js';
|
||||
import {EventType} from '../events/type.js';
|
||||
import * as eventUtils from '../events/utils.js';
|
||||
import type {IBubble} from '../interfaces/i_bubble.js';
|
||||
import type {IHasBubble} from '../interfaces/i_has_bubble.js';
|
||||
import type {ISerializable} from '../interfaces/i_serializable.js';
|
||||
import * as renderManagement from '../render_management.js';
|
||||
@@ -62,7 +61,7 @@ export class CommentIcon extends Icon implements IHasBubble, ISerializable {
|
||||
/**
|
||||
* The visibility of the bubble for this comment.
|
||||
*
|
||||
* This is used to track what the visibile state /should/ be, not necessarily
|
||||
* This is used to track what the visible state /should/ be, not necessarily
|
||||
* what it currently /is/. E.g. sometimes this will be true, but the block
|
||||
* hasn't been rendered yet, so the bubble will not currently be visible.
|
||||
*/
|
||||
@@ -340,7 +339,7 @@ export class CommentIcon extends Icon implements IHasBubble, ISerializable {
|
||||
}
|
||||
|
||||
/** See IHasBubble.getBubble. */
|
||||
getBubble(): IBubble | null {
|
||||
getBubble(): TextInputBubble | null {
|
||||
return this.textInputBubble;
|
||||
}
|
||||
|
||||
@@ -365,6 +364,7 @@ export class CommentIcon extends Icon implements IHasBubble, ISerializable {
|
||||
this.sourceBlock.workspace as WorkspaceSvg,
|
||||
this.getAnchorLocation(),
|
||||
this.getBubbleOwnerRect(),
|
||||
this,
|
||||
);
|
||||
this.textInputBubble.setText(this.getText());
|
||||
this.textInputBubble.setSize(this.bubbleSize, true);
|
||||
|
||||
76
core/keyboard_nav/block_comment_navigation_policy.ts
Normal file
76
core/keyboard_nav/block_comment_navigation_policy.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {TextInputBubble} from '../bubbles/textinput_bubble.js';
|
||||
import type {IFocusableNode} from '../interfaces/i_focusable_node.js';
|
||||
import type {INavigationPolicy} from '../interfaces/i_navigation_policy.js';
|
||||
|
||||
/**
|
||||
* Set of rules controlling keyboard navigation from an TextInputBubble.
|
||||
*/
|
||||
export class BlockCommentNavigationPolicy
|
||||
implements INavigationPolicy<TextInputBubble>
|
||||
{
|
||||
/**
|
||||
* Returns the first child of the given block comment.
|
||||
*
|
||||
* @param current The block comment to return the first child of.
|
||||
* @returns The text editor of the given block comment bubble.
|
||||
*/
|
||||
getFirstChild(current: TextInputBubble): IFocusableNode | null {
|
||||
return current.getEditor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parent of the given block comment.
|
||||
*
|
||||
* @param current The block comment to return the parent of.
|
||||
* @returns The parent block of the given block comment.
|
||||
*/
|
||||
getParent(current: TextInputBubble): IFocusableNode | null {
|
||||
return current.getOwner() ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next peer node of the given block comment.
|
||||
*
|
||||
* @param _current The block comment to find the following element of.
|
||||
* @returns Null.
|
||||
*/
|
||||
getNextSibling(_current: TextInputBubble): IFocusableNode | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the previous peer node of the given block comment.
|
||||
*
|
||||
* @param _current The block comment to find the preceding element of.
|
||||
* @returns Null.
|
||||
*/
|
||||
getPreviousSibling(_current: TextInputBubble): IFocusableNode | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the given block comment can be navigated to.
|
||||
*
|
||||
* @param current The instance to check for navigability.
|
||||
* @returns True if the given block comment can be focused.
|
||||
*/
|
||||
isNavigable(current: TextInputBubble): 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 an TextInputBubble.
|
||||
*/
|
||||
isApplicable(current: any): current is TextInputBubble {
|
||||
return current instanceof TextInputBubble;
|
||||
}
|
||||
}
|
||||
54
core/keyboard_nav/comment_editor_navigation_policy.ts
Normal file
54
core/keyboard_nav/comment_editor_navigation_policy.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {CommentEditor} from '../comments/comment_editor.js';
|
||||
import type {IFocusableNode} from '../interfaces/i_focusable_node.js';
|
||||
import type {INavigationPolicy} from '../interfaces/i_navigation_policy.js';
|
||||
|
||||
/**
|
||||
* Set of rules controlling keyboard navigation from a comment editor.
|
||||
* This is a no-op placeholder (other than isNavigable/isApplicable) since
|
||||
* comment editors handle their own navigation when editing ends.
|
||||
*/
|
||||
export class CommentEditorNavigationPolicy
|
||||
implements INavigationPolicy<CommentEditor>
|
||||
{
|
||||
getFirstChild(_current: CommentEditor): IFocusableNode | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
getParent(_current: CommentEditor): IFocusableNode | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
getNextSibling(_current: CommentEditor): IFocusableNode | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
getPreviousSibling(_current: CommentEditor): IFocusableNode | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the given comment editor can be navigated to.
|
||||
*
|
||||
* @param current The instance to check for navigability.
|
||||
* @returns False.
|
||||
*/
|
||||
isNavigable(current: CommentEditor): 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 CommentEditor.
|
||||
*/
|
||||
isApplicable(current: any): current is CommentEditor {
|
||||
return current instanceof CommentEditor;
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
import {BlockSvg} from '../block_svg.js';
|
||||
import {getFocusManager} from '../focus_manager.js';
|
||||
import {CommentIcon} from '../icons/comment_icon.js';
|
||||
import {Icon} from '../icons/icon.js';
|
||||
import {MutatorIcon} from '../icons/mutator_icon.js';
|
||||
import type {IFocusableNode} from '../interfaces/i_focusable_node.js';
|
||||
@@ -29,6 +30,12 @@ export class IconNavigationPolicy implements INavigationPolicy<Icon> {
|
||||
getFocusManager().getFocusedNode() === current
|
||||
) {
|
||||
return current.getBubble()?.getWorkspace() ?? null;
|
||||
} else if (
|
||||
current instanceof CommentIcon &&
|
||||
current.bubbleIsVisible() &&
|
||||
getFocusManager().getFocusedNode() === current
|
||||
) {
|
||||
return current.getBubble()?.getEditor() ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -6,8 +6,10 @@
|
||||
|
||||
import type {IFocusableNode} from './interfaces/i_focusable_node.js';
|
||||
import type {INavigationPolicy} from './interfaces/i_navigation_policy.js';
|
||||
import {BlockCommentNavigationPolicy} from './keyboard_nav/block_comment_navigation_policy.js';
|
||||
import {BlockNavigationPolicy} from './keyboard_nav/block_navigation_policy.js';
|
||||
import {CommentBarButtonNavigationPolicy} from './keyboard_nav/comment_bar_button_navigation_policy.js';
|
||||
import {CommentEditorNavigationPolicy} from './keyboard_nav/comment_editor_navigation_policy.js';
|
||||
import {ConnectionNavigationPolicy} from './keyboard_nav/connection_navigation_policy.js';
|
||||
import {FieldNavigationPolicy} from './keyboard_nav/field_navigation_policy.js';
|
||||
import {IconNavigationPolicy} from './keyboard_nav/icon_navigation_policy.js';
|
||||
@@ -33,6 +35,8 @@ export class Navigator {
|
||||
new IconNavigationPolicy(),
|
||||
new WorkspaceCommentNavigationPolicy(),
|
||||
new CommentBarButtonNavigationPolicy(),
|
||||
new BlockCommentNavigationPolicy(),
|
||||
new CommentEditorNavigationPolicy(),
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -22,6 +22,7 @@ import type {Block} from './block.js';
|
||||
import type {BlockSvg} from './block_svg.js';
|
||||
import type {BlocklyOptions} from './blockly_options.js';
|
||||
import * as browserEvents from './browser_events.js';
|
||||
import {TextInputBubble} from './bubbles/textinput_bubble.js';
|
||||
import {COMMENT_COLLAPSE_BAR_BUTTON_FOCUS_IDENTIFIER} from './comments/collapse_comment_bar_button.js';
|
||||
import {COMMENT_EDITOR_FOCUS_IDENTIFIER} from './comments/comment_editor.js';
|
||||
import {COMMENT_DELETE_BAR_BUTTON_FOCUS_IDENTIFIER} from './comments/delete_comment_bar_button.js';
|
||||
@@ -2868,6 +2869,11 @@ export class WorkspaceSvg
|
||||
bubble.getFocusableElement().id === id
|
||||
) {
|
||||
return bubble;
|
||||
} else if (
|
||||
bubble instanceof TextInputBubble &&
|
||||
bubble.getEditor().getFocusableElement().id === id
|
||||
) {
|
||||
return bubble.getEditor();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user