Skip to content

Commit c6730ab

Browse files
authored
fix: Fix bug that caused inadvertent scrolling when the WidgetDiv was shown. (#9291)
* fix: Fix bug that caused inadvertent scrolling when the `WidgetDiv` was shown. * chore: Add test to verify that displaying the context menu does not scroll the page. * chore: Clarify comments. * fix: Remove errant `.only`. * chore: Add test to verify that actively focusing a node does not scroll the page. * fix: Remove inadvertent `.only`.
1 parent 3e26b00 commit c6730ab

3 files changed

Lines changed: 48 additions & 2 deletions

File tree

core/focus_manager.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,8 @@ export class FocusManager {
309309
* Note that this may update the specified node's element's tabindex to ensure
310310
* that it can be properly read out by screenreaders while focused.
311311
*
312+
* The focused node will not be automatically scrolled into view.
313+
*
312314
* @param focusableNode The node that should receive active focus.
313315
*/
314316
focusNode(focusableNode: IFocusableNode): void {
@@ -423,6 +425,8 @@ export class FocusManager {
423425
* the returned lambda is called. Additionally, only 1 ephemeral focus context
424426
* can be active at any given time (attempting to activate more than one
425427
* simultaneously will result in an error being thrown).
428+
*
429+
* This method does not scroll the ephemerally focused element into view.
426430
*/
427431
takeEphemeralFocus(
428432
focusableElement: HTMLElement | SVGElement,
@@ -439,7 +443,7 @@ export class FocusManager {
439443
if (this.focusedNode) {
440444
this.passivelyFocusNode(this.focusedNode, null);
441445
}
442-
focusableElement.focus();
446+
focusableElement.focus({preventScroll: true});
443447

444448
let hasFinishedEphemeralFocus = false;
445449
return () => {
@@ -574,7 +578,7 @@ export class FocusManager {
574578
}
575579

576580
this.setNodeToVisualActiveFocus(node);
577-
elem.focus();
581+
elem.focus({preventScroll: true});
578582
}
579583

580584
/**

core/interfaces/i_focusable_node.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ export interface IFocusableNode {
5959
* they should avoid the following:
6060
* - Creating or removing DOM elements (including via the renderer or drawer).
6161
* - Affecting focus via DOM focus() calls or the FocusManager.
62+
*
63+
* Implementations may consider scrolling themselves into view here; that is
64+
* not handled by the focus manager.
6265
*/
6366
onNodeFocus(): void;
6467

tests/browser/test/basic_playground_test.mjs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,45 @@ suite('Right Clicking on Blocks', function () {
101101
await contextMenuSelect(this.browser, this.block, 'Remove Comment');
102102
chai.assert.isNull(await getCommentText(this.browser, this.block.id));
103103
});
104+
105+
test('does not scroll the page when node is ephemerally focused', async function () {
106+
const initialScroll = await this.browser.execute(() => {
107+
return window.scrollY;
108+
});
109+
// This left-right-left sequence was necessary to reproduce unintended
110+
// scrolling; regardless of the number of clicks/context menu activations,
111+
// the page should not scroll.
112+
this.block.click({button: 2});
113+
this.block.click({button: 0});
114+
this.block.click({button: 2});
115+
await this.browser.pause(250);
116+
const finalScroll = await this.browser.execute(() => {
117+
return window.scrollY;
118+
});
119+
120+
chai.assert.equal(initialScroll, finalScroll);
121+
});
122+
123+
test('does not scroll the page when node is actively focused', async function () {
124+
await this.browser.setWindowSize(500, 300);
125+
await this.browser.setViewport({width: 500, height: 300});
126+
const initialScroll = await this.browser.execute((blockId) => {
127+
window.scrollTo(0, document.body.scrollHeight);
128+
return window.scrollY;
129+
}, this.block.id);
130+
await this.browser.execute(() => {
131+
Blockly.getFocusManager().focusNode(
132+
Blockly.getMainWorkspace().getToolbox(),
133+
);
134+
});
135+
const finalScroll = await this.browser.execute(() => {
136+
return window.scrollY;
137+
});
138+
139+
chai.assert.equal(initialScroll, finalScroll);
140+
await this.browser.setWindowSize(800, 600);
141+
await this.browser.setViewport({width: 800, height: 600});
142+
});
104143
});
105144

106145
suite('Disabling', function () {

0 commit comments

Comments
 (0)