mirror of
https://github.com/google/blockly.git
synced 2026-01-09 10:00:09 +01:00
refactor: Make focusable elements responsible for scrolling themselves into bounds. (#9288)
* refactor: Make focusable elements responsible for scrolling themselves into bounds. * chore: Add tests for scrolling focused elements into view. * fix: Removed inadvertent `.only`. * fix: Scroll parent block of connections into bounds on focus.
This commit is contained in:
@@ -1844,6 +1844,9 @@ export class BlockSvg
|
||||
/** See IFocusableNode.onNodeFocus. */
|
||||
onNodeFocus(): void {
|
||||
this.select();
|
||||
this.workspace.scrollBoundsIntoView(
|
||||
this.getBoundingRectangleWithoutChildren(),
|
||||
);
|
||||
}
|
||||
|
||||
/** See IFocusableNode.onNodeBlur. */
|
||||
|
||||
@@ -707,6 +707,10 @@ export abstract class Bubble implements IBubble, ISelectable, IFocusableNode {
|
||||
onNodeFocus(): void {
|
||||
this.select();
|
||||
this.bringToFront();
|
||||
const xy = this.getRelativeToSurfaceXY();
|
||||
const size = this.getSize();
|
||||
const bounds = new Rect(xy.y, xy.y + size.height, xy.x, xy.x + size.width);
|
||||
this.workspace.scrollBoundsIntoView(bounds);
|
||||
}
|
||||
|
||||
/** See IFocusableNode.onNodeBlur. */
|
||||
|
||||
@@ -87,7 +87,13 @@ export abstract class CommentBarButton implements IFocusableNode {
|
||||
}
|
||||
|
||||
/** Called when this button's focusable DOM element gains focus. */
|
||||
onNodeFocus() {}
|
||||
onNodeFocus() {
|
||||
const commentView = this.getCommentView();
|
||||
const xy = commentView.getRelativeToSurfaceXY();
|
||||
const size = commentView.getSize();
|
||||
const bounds = new Rect(xy.y, xy.y + size.height, xy.x, xy.x + size.width);
|
||||
commentView.workspace.scrollBoundsIntoView(bounds);
|
||||
}
|
||||
|
||||
/** Called when this button's focusable DOM element loses focus. */
|
||||
onNodeBlur() {}
|
||||
|
||||
@@ -10,8 +10,10 @@ import {IFocusableNode} from '../interfaces/i_focusable_node.js';
|
||||
import {IFocusableTree} from '../interfaces/i_focusable_tree.js';
|
||||
import * as touch from '../touch.js';
|
||||
import * as dom from '../utils/dom.js';
|
||||
import {Rect} from '../utils/rect.js';
|
||||
import {Size} from '../utils/size.js';
|
||||
import {Svg} from '../utils/svg.js';
|
||||
import * as svgMath from '../utils/svg_math.js';
|
||||
import {WorkspaceSvg} from '../workspace_svg.js';
|
||||
|
||||
/**
|
||||
@@ -188,7 +190,16 @@ export class CommentEditor implements IFocusableNode {
|
||||
getFocusableTree(): IFocusableTree {
|
||||
return this.workspace;
|
||||
}
|
||||
onNodeFocus(): void {}
|
||||
onNodeFocus(): void {
|
||||
const bbox = Rect.from(this.foreignObject.getBoundingClientRect());
|
||||
this.workspace.scrollBoundsIntoView(
|
||||
Rect.createFromPoint(
|
||||
svgMath.screenToWsCoordinates(this.workspace, bbox.getOrigin()),
|
||||
bbox.getWidth(),
|
||||
bbox.getHeight(),
|
||||
),
|
||||
);
|
||||
}
|
||||
onNodeBlur(): void {}
|
||||
canBeFocused(): boolean {
|
||||
if (this.id) return true;
|
||||
|
||||
@@ -347,6 +347,7 @@ export class RenderedWorkspaceComment
|
||||
this.select();
|
||||
// Ensure that the comment is always at the top when focused.
|
||||
this.workspace.getLayerManager()?.append(this, layers.BLOCK);
|
||||
this.workspace.scrollBoundsIntoView(this.getBoundingRectangle());
|
||||
}
|
||||
|
||||
/** See IFocusableNode.onNodeBlur. */
|
||||
|
||||
@@ -1380,7 +1380,12 @@ export abstract class Field<T = any>
|
||||
}
|
||||
|
||||
/** See IFocusableNode.onNodeFocus. */
|
||||
onNodeFocus(): void {}
|
||||
onNodeFocus(): void {
|
||||
const block = this.getSourceBlock() as BlockSvg;
|
||||
block.workspace.scrollBoundsIntoView(
|
||||
block.getBoundingRectangleWithoutChildren(),
|
||||
);
|
||||
}
|
||||
|
||||
/** See IFocusableNode.onNodeBlur. */
|
||||
onNodeBlur(): void {}
|
||||
|
||||
@@ -398,7 +398,11 @@ export class FlyoutButton
|
||||
}
|
||||
|
||||
/** See IFocusableNode.onNodeFocus. */
|
||||
onNodeFocus(): void {}
|
||||
onNodeFocus(): void {
|
||||
const xy = this.getPosition();
|
||||
const bounds = new Rect(xy.y, xy.y + this.height, xy.x, xy.x + this.width);
|
||||
this.workspace.scrollBoundsIntoView(bounds);
|
||||
}
|
||||
|
||||
/** See IFocusableNode.onNodeBlur. */
|
||||
onNodeBlur(): void {}
|
||||
|
||||
@@ -14,6 +14,7 @@ import * as tooltip from '../tooltip.js';
|
||||
import {Coordinate} from '../utils/coordinate.js';
|
||||
import * as dom from '../utils/dom.js';
|
||||
import * as idGenerator from '../utils/idgenerator.js';
|
||||
import {Rect} from '../utils/rect.js';
|
||||
import {Size} from '../utils/size.js';
|
||||
import {Svg} from '../utils/svg.js';
|
||||
import type {WorkspaceSvg} from '../workspace_svg.js';
|
||||
@@ -168,7 +169,16 @@ export abstract class Icon implements IIcon {
|
||||
}
|
||||
|
||||
/** See IFocusableNode.onNodeFocus. */
|
||||
onNodeFocus(): void {}
|
||||
onNodeFocus(): void {
|
||||
const blockBounds = (this.sourceBlock as BlockSvg).getBoundingRectangle();
|
||||
const bounds = new Rect(
|
||||
blockBounds.top + this.offsetInBlock.y,
|
||||
blockBounds.top + this.offsetInBlock.y + this.getSize().height,
|
||||
blockBounds.left + this.offsetInBlock.x,
|
||||
blockBounds.left + this.offsetInBlock.x + this.getSize().width,
|
||||
);
|
||||
(this.sourceBlock as BlockSvg).workspace.scrollBoundsIntoView(bounds);
|
||||
}
|
||||
|
||||
/** See IFocusableNode.onNodeBlur. */
|
||||
onNodeBlur(): void {}
|
||||
|
||||
@@ -14,14 +14,11 @@
|
||||
*/
|
||||
|
||||
import {BlockSvg} from '../block_svg.js';
|
||||
import {CommentBarButton} from '../comments/comment_bar_button.js';
|
||||
import {RenderedWorkspaceComment} from '../comments/rendered_workspace_comment.js';
|
||||
import {Field} from '../field.js';
|
||||
import {getFocusManager} from '../focus_manager.js';
|
||||
import type {IFocusableNode} from '../interfaces/i_focusable_node.js';
|
||||
import * as registry from '../registry.js';
|
||||
import {Rect} from '../utils/rect.js';
|
||||
import {WorkspaceSvg} from '../workspace_svg.js';
|
||||
import type {WorkspaceSvg} from '../workspace_svg.js';
|
||||
import {Marker} from './marker.js';
|
||||
|
||||
/**
|
||||
@@ -392,31 +389,6 @@ export class LineCursor extends Marker {
|
||||
*/
|
||||
setCurNode(newNode: IFocusableNode) {
|
||||
getFocusManager().focusNode(newNode);
|
||||
|
||||
// Try to scroll cursor into view.
|
||||
if (newNode instanceof BlockSvg) {
|
||||
newNode.workspace.scrollBoundsIntoView(
|
||||
newNode.getBoundingRectangleWithoutChildren(),
|
||||
);
|
||||
} else if (newNode instanceof Field) {
|
||||
const block = newNode.getSourceBlock() as BlockSvg;
|
||||
block.workspace.scrollBoundsIntoView(
|
||||
block.getBoundingRectangleWithoutChildren(),
|
||||
);
|
||||
} else if (newNode instanceof RenderedWorkspaceComment) {
|
||||
newNode.workspace.scrollBoundsIntoView(newNode.getBoundingRectangle());
|
||||
} else if (newNode instanceof CommentBarButton) {
|
||||
const commentView = newNode.getCommentView();
|
||||
const xy = commentView.getRelativeToSurfaceXY();
|
||||
const size = commentView.getSize();
|
||||
const bounds = new Rect(
|
||||
xy.y,
|
||||
xy.y + size.height,
|
||||
xy.x,
|
||||
xy.x + size.width,
|
||||
);
|
||||
commentView.workspace.scrollBoundsIntoView(bounds);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -644,6 +644,9 @@ export class RenderedConnection
|
||||
/** See IFocusableNode.onNodeFocus. */
|
||||
onNodeFocus(): void {
|
||||
this.highlight();
|
||||
this.getSourceBlock().workspace.scrollBoundsIntoView(
|
||||
this.getSourceBlock().getBoundingRectangleWithoutChildren(),
|
||||
);
|
||||
}
|
||||
|
||||
/** See IFocusableNode.onNodeBlur. */
|
||||
@@ -656,12 +659,12 @@ export class RenderedConnection
|
||||
return true;
|
||||
}
|
||||
|
||||
private findHighlightSvg(): SVGElement | null {
|
||||
private findHighlightSvg(): SVGPathElement | null {
|
||||
// This cast is valid as TypeScript's definition is wrong. See:
|
||||
// https://github.com/microsoft/TypeScript/issues/60996.
|
||||
return document.getElementById(this.id) as
|
||||
| unknown
|
||||
| null as SVGElement | null;
|
||||
| null as SVGPathElement | null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user