mirror of
https://github.com/google/blockly.git
synced 2026-01-10 10:27:08 +01:00
feat: Add support for keyboard navigation into mutator workspaces. (#9151)
* feat: Add support for keyboard navigation into mutators. * fix: Prevent mutator bubbles from jumping wildly during keyboard nav.
This commit is contained in:
@@ -153,7 +153,11 @@ export class MiniWorkspaceBubble extends Bubble {
|
||||
* are dealt with by resizing the workspace to show them.
|
||||
*/
|
||||
private bumpBlocksIntoBounds() {
|
||||
if (this.miniWorkspace.isDragging()) return;
|
||||
if (
|
||||
this.miniWorkspace.isDragging() &&
|
||||
!this.miniWorkspace.keyboardMoveInProgress
|
||||
)
|
||||
return;
|
||||
|
||||
const MARGIN = 20;
|
||||
|
||||
@@ -185,7 +189,15 @@ export class MiniWorkspaceBubble extends Bubble {
|
||||
* mini workspace.
|
||||
*/
|
||||
private updateBubbleSize() {
|
||||
if (this.miniWorkspace.isDragging()) return;
|
||||
if (
|
||||
this.miniWorkspace.isDragging() &&
|
||||
!this.miniWorkspace.keyboardMoveInProgress
|
||||
)
|
||||
return;
|
||||
|
||||
// Disable autolayout if a keyboard move is in progress to prevent the
|
||||
// mutator bubble from jumping around.
|
||||
this.autoLayout &&= !this.miniWorkspace.keyboardMoveInProgress;
|
||||
|
||||
const currSize = this.getSize();
|
||||
const newSize = this.calculateWorkspaceSize();
|
||||
|
||||
@@ -14,7 +14,6 @@ import {BlockChange} from '../events/events_block_change.js';
|
||||
import {isBlockChange, isBlockCreate} from '../events/predicates.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 * as renderManagement from '../render_management.js';
|
||||
import {Coordinate} from '../utils/coordinate.js';
|
||||
@@ -205,7 +204,7 @@ export class MutatorIcon extends Icon implements IHasBubble {
|
||||
}
|
||||
|
||||
/** See IHasBubble.getBubble. */
|
||||
getBubble(): IBubble | null {
|
||||
getBubble(): MiniWorkspaceBubble | null {
|
||||
return this.miniWorkspaceBubble;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
*/
|
||||
|
||||
import {BlockSvg} from '../block_svg.js';
|
||||
import {getFocusManager} from '../focus_manager.js';
|
||||
import {Icon} from '../icons/icon.js';
|
||||
import {MutatorIcon} from '../icons/mutator_icon.js';
|
||||
import type {IFocusableNode} from '../interfaces/i_focusable_node.js';
|
||||
import type {INavigationPolicy} from '../interfaces/i_navigation_policy.js';
|
||||
import {navigateBlock} from './block_navigation_policy.js';
|
||||
@@ -17,10 +19,18 @@ export class IconNavigationPolicy implements INavigationPolicy<Icon> {
|
||||
/**
|
||||
* Returns the first child of the given icon.
|
||||
*
|
||||
* @param _current The icon to return the first child of.
|
||||
* @param current The icon to return the first child of.
|
||||
* @returns Null.
|
||||
*/
|
||||
getFirstChild(_current: Icon): IFocusableNode | null {
|
||||
getFirstChild(current: Icon): IFocusableNode | null {
|
||||
if (
|
||||
current instanceof MutatorIcon &&
|
||||
current.bubbleIsVisible() &&
|
||||
getFocusManager().getFocusedNode() === current
|
||||
) {
|
||||
return current.getBubble()?.getWorkspace() ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ export class WorkspaceNavigationPolicy
|
||||
* @returns True if the given workspace can be focused.
|
||||
*/
|
||||
isNavigable(current: WorkspaceSvg): boolean {
|
||||
return current.canBeFocused();
|
||||
return current.canBeFocused() && !current.isMutator;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -64,9 +64,8 @@ export class Navigator {
|
||||
getFirstChild(current: IFocusableNode): IFocusableNode | null {
|
||||
const result = this.get(current)?.getFirstChild(current);
|
||||
if (!result) return null;
|
||||
// If the child isn't navigable, don't traverse into it; check its peers.
|
||||
if (!this.get(result)?.isNavigable(result)) {
|
||||
return this.getNextSibling(result);
|
||||
return this.getFirstChild(result) || this.getNextSibling(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ import type {FlyoutButton} from './flyout_button.js';
|
||||
import {getFocusManager} from './focus_manager.js';
|
||||
import {Gesture} from './gesture.js';
|
||||
import {Grid} from './grid.js';
|
||||
import {MutatorIcon} from './icons/mutator_icon.js';
|
||||
import {isAutoHideable} from './interfaces/i_autohideable.js';
|
||||
import type {IBoundedElement} from './interfaces/i_bounded_element.js';
|
||||
import {IContextMenu} from './interfaces/i_contextmenu.js';
|
||||
@@ -2680,7 +2681,7 @@ export class WorkspaceSvg
|
||||
|
||||
/** See IFocusableNode.getFocusableTree. */
|
||||
getFocusableTree(): IFocusableTree {
|
||||
return this;
|
||||
return (this.isMutator && this.options.parentWorkspace) || this;
|
||||
}
|
||||
|
||||
/** See IFocusableNode.onNodeFocus. */
|
||||
@@ -2710,7 +2711,22 @@ export class WorkspaceSvg
|
||||
|
||||
/** See IFocusableTree.getNestedTrees. */
|
||||
getNestedTrees(): Array<IFocusableTree> {
|
||||
return [];
|
||||
const nestedWorkspaces = this.getAllBlocks()
|
||||
.map((block) => block.getIcons())
|
||||
.flat()
|
||||
.filter(
|
||||
(icon): icon is MutatorIcon =>
|
||||
icon instanceof MutatorIcon && icon.bubbleIsVisible(),
|
||||
)
|
||||
.map((icon) => icon.getBubble()?.getWorkspace())
|
||||
.filter((workspace) => !!workspace);
|
||||
|
||||
const ownFlyout = this.getFlyout(true);
|
||||
if (ownFlyout) {
|
||||
nestedWorkspaces.push(ownFlyout.getWorkspace());
|
||||
}
|
||||
|
||||
return nestedWorkspaces;
|
||||
}
|
||||
|
||||
/** See IFocusableTree.lookUpFocusableNode. */
|
||||
|
||||
Reference in New Issue
Block a user