diff --git a/core/bubbles/mini_workspace_bubble.ts b/core/bubbles/mini_workspace_bubble.ts index f6ea60936..194cb41f3 100644 --- a/core/bubbles/mini_workspace_bubble.ts +++ b/core/bubbles/mini_workspace_bubble.ts @@ -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(); diff --git a/core/icons/mutator_icon.ts b/core/icons/mutator_icon.ts index 1842855fa..9055a91ea 100644 --- a/core/icons/mutator_icon.ts +++ b/core/icons/mutator_icon.ts @@ -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; } diff --git a/core/keyboard_nav/icon_navigation_policy.ts b/core/keyboard_nav/icon_navigation_policy.ts index 96908cbbd..70631ce81 100644 --- a/core/keyboard_nav/icon_navigation_policy.ts +++ b/core/keyboard_nav/icon_navigation_policy.ts @@ -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 { /** * 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; } diff --git a/core/keyboard_nav/workspace_navigation_policy.ts b/core/keyboard_nav/workspace_navigation_policy.ts index 12a7555b4..b671f8fe7 100644 --- a/core/keyboard_nav/workspace_navigation_policy.ts +++ b/core/keyboard_nav/workspace_navigation_policy.ts @@ -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; } /** diff --git a/core/navigator.ts b/core/navigator.ts index 92c921122..77bb64cd8 100644 --- a/core/navigator.ts +++ b/core/navigator.ts @@ -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; } diff --git a/core/workspace_svg.ts b/core/workspace_svg.ts index 9eb5ea545..552d37061 100644 --- a/core/workspace_svg.ts +++ b/core/workspace_svg.ts @@ -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 { - 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. */