Skip to content

Commit 14f422f

Browse files
authored
fix: forward shadow block context menu to parent (#10070)
1 parent d2ef90b commit 14f422f

2 files changed

Lines changed: 100 additions & 0 deletions

File tree

packages/blockly/core/block_svg.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -709,6 +709,18 @@ export class BlockSvg
709709
* @internal
710710
*/
711711
showContextMenu(e: Event) {
712+
// Forward to the nearest non-shadow ancestor and focus it for keyboard users.
713+
if (this.isShadow()) {
714+
let parent = this.getParent();
715+
while (parent && parent.isShadow()) {
716+
parent = parent.getParent();
717+
}
718+
if (parent) {
719+
getFocusManager().focusNode(parent);
720+
parent.showContextMenu(e);
721+
}
722+
return;
723+
}
712724
const menuOptions = this.generateContextMenu(e);
713725

714726
const location = this.calculateContextMenuLocation(e);

packages/blockly/tests/mocha/contextmenu_test.js

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
import {callbackFactory} from '../../build/src/core/contextmenu.js';
88
import * as xmlUtils from '../../build/src/core/utils/xml.js';
99
import {assert} from '../../node_modules/chai/index.js';
10+
import {
11+
defineRowBlock,
12+
defineStackBlock,
13+
} from './test_helpers/block_definitions.js';
1014
import {
1115
sharedTestSetup,
1216
sharedTestTeardown,
@@ -91,4 +95,88 @@ suite('Context Menu', function () {
9195
assert.isNull(Blockly.ContextMenu.getMenu());
9296
});
9397
});
98+
99+
suite('Block showContextMenu', function () {
100+
setup(function () {
101+
defineRowBlock();
102+
defineStackBlock();
103+
Blockly.ContextMenuRegistry.registry.reset();
104+
Blockly.ContextMenuItems.registerDefaultOptions();
105+
this.pointerEvent = new PointerEvent('pointerdown');
106+
});
107+
108+
teardown(function () {
109+
if (Blockly.ContextMenu.getMenu()) {
110+
Blockly.ContextMenu.hide();
111+
}
112+
});
113+
114+
/**
115+
* Initializes and renders the given blocks.
116+
* @param {...Blockly.BlockSvg} blocks The blocks to initialize.
117+
*/
118+
function initAndRender(...blocks) {
119+
for (const block of blocks) {
120+
block.initSvg();
121+
block.render();
122+
}
123+
}
124+
125+
/**
126+
* Asserts that the given block's context menu is shown and it has focus.
127+
* @param {!Blockly.BlockSvg} block The block that should own the menu.
128+
*/
129+
function assertBlockContextMenu(block) {
130+
assert.strictEqual(Blockly.getFocusManager().getFocusedNode(), block);
131+
assert.instanceOf(
132+
Blockly.ContextMenu.getMenu(),
133+
Blockly.Menu,
134+
'Context menu should be shown',
135+
);
136+
assert.strictEqual(Blockly.ContextMenu.getCurrentBlock(), block);
137+
}
138+
139+
test('value shadow forwards context menu to parent and focuses parent', function () {
140+
const parent = this.workspace.newBlock('row_block');
141+
parent.getInput('INPUT').connection.setShadowState({type: 'row_block'});
142+
const shadow = parent.getInput('INPUT').connection.targetBlock();
143+
assert.isTrue(shadow.isShadow());
144+
initAndRender(parent, shadow);
145+
146+
Blockly.getFocusManager().focusNode(shadow);
147+
shadow.showContextMenu(this.pointerEvent);
148+
149+
assertBlockContextMenu(parent);
150+
});
151+
152+
test('nested shadows forward to the first non-shadow ancestor', function () {
153+
const parent = this.workspace.newBlock('row_block');
154+
parent.getInput('INPUT').connection.setShadowState({type: 'row_block'});
155+
const middleShadow = parent.getInput('INPUT').connection.targetBlock();
156+
middleShadow
157+
.getInput('INPUT')
158+
.connection.setShadowState({type: 'row_block'});
159+
const childShadow = middleShadow
160+
.getInput('INPUT')
161+
.connection.targetBlock();
162+
assert.isTrue(middleShadow.isShadow());
163+
assert.isTrue(childShadow.isShadow());
164+
initAndRender(parent, middleShadow, childShadow);
165+
166+
Blockly.getFocusManager().focusNode(childShadow);
167+
childShadow.showContextMenu(this.pointerEvent);
168+
169+
assertBlockContextMenu(parent);
170+
});
171+
172+
test('non-shadow block shows its own context menu', function () {
173+
const block = this.workspace.newBlock('stack_block');
174+
initAndRender(block);
175+
Blockly.getFocusManager().focusNode(block);
176+
177+
block.showContextMenu(this.pointerEvent);
178+
179+
assertBlockContextMenu(block);
180+
});
181+
});
94182
});

0 commit comments

Comments
 (0)