Skip to content

Commit 3233dce

Browse files
vursenclaude
andauthored
refactor: make context-menu opened property sync (#11650)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent c4d835d commit 3233dce

5 files changed

Lines changed: 33 additions & 20 deletions

File tree

packages/context-menu/src/vaadin-context-menu-mixin.js

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export const ContextMenuMixin = (superClass) =>
3636
value: false,
3737
notify: true,
3838
readOnly: true,
39+
sync: true,
3940
},
4041

4142
/**
@@ -135,7 +136,7 @@ export const ContextMenuMixin = (superClass) =>
135136
this._boundOpen = this.open.bind(this);
136137
this._boundClose = this.close.bind(this);
137138
this._boundPreventDefault = this._preventDefault.bind(this);
138-
this._boundOnGlobalContextMenu = this._onGlobalContextMenu.bind(this);
139+
this.__onGlobalContextMenu = this.__onGlobalContextMenu.bind(this);
139140
}
140141

141142
/** @protected */
@@ -144,6 +145,7 @@ export const ContextMenuMixin = (superClass) =>
144145

145146
this.__boundOnScroll = this.__onScroll.bind(this);
146147
window.addEventListener('scroll', this.__boundOnScroll, true);
148+
document.documentElement.addEventListener('contextmenu', this.__onGlobalContextMenu, true);
147149

148150
// Restore opened state if overlay was opened when disconnecting
149151
if (this.__restoreOpened) {
@@ -156,10 +158,16 @@ export const ContextMenuMixin = (superClass) =>
156158
super.disconnectedCallback();
157159

158160
window.removeEventListener('scroll', this.__boundOnScroll, true);
161+
document.documentElement.removeEventListener('contextmenu', this.__onGlobalContextMenu, true);
159162

160-
// Close overlay and memorize opened state
163+
// Memorize opened state and defer close so that DOM moves (disconnect
164+
// followed by reconnect within the same task) do not close the overlay.
161165
this.__restoreOpened = this.opened;
162-
this.close();
166+
setTimeout(() => {
167+
if (!this.isConnected) {
168+
this.close();
169+
}
170+
});
163171
}
164172

165173
/** @protected */
@@ -290,13 +298,7 @@ export const ContextMenuMixin = (superClass) =>
290298
}
291299

292300
/** @private */
293-
_openedChanged(opened, oldOpened) {
294-
if (opened) {
295-
document.documentElement.addEventListener('contextmenu', this._boundOnGlobalContextMenu, true);
296-
} else if (oldOpened) {
297-
document.documentElement.removeEventListener('contextmenu', this._boundOnGlobalContextMenu, true);
298-
}
299-
301+
_openedChanged(opened) {
300302
this.__setListenOnUserSelect(opened);
301303
}
302304

@@ -630,7 +632,10 @@ export const ContextMenuMixin = (superClass) =>
630632
}
631633

632634
/** @private */
633-
_onGlobalContextMenu(e) {
635+
__onGlobalContextMenu(e) {
636+
if (!this.opened) {
637+
return;
638+
}
634639
if (!e.shiftKey) {
635640
const isTouchDevice = isAndroid || isIOS;
636641
if (!isTouchDevice) {

packages/context-menu/src/vaadin-contextmenu-items-mixin.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -374,9 +374,7 @@ export const ItemsMixin = (superClass) =>
374374

375375
// Listen to the forwarded event from sub-menu.
376376
this.addEventListener('close-all-menus', () => {
377-
// Call `close()` on the overlay to close synchronously,
378-
// as we can't have `sync: true` on `opened` property.
379-
this._overlayElement.close();
377+
this.close();
380378
});
381379

382380
// Listen to the forwarded event from sub-menu.

packages/context-menu/test/context.test.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { expect } from '@vaadin/chai-plugins';
2-
import { fire, fixtureSync, nextRender } from '@vaadin/testing-helpers';
2+
import { aTimeout, fire, fixtureSync, nextRender } from '@vaadin/testing-helpers';
33
import sinon from 'sinon';
44
import '../src/vaadin-context-menu.js';
55
import '@vaadin/item/src/vaadin-item.js';
@@ -121,25 +121,27 @@ describe('context', () => {
121121
expect(menu.opened).to.be.true;
122122
});
123123

124-
it('should be closed after detached', () => {
124+
it('should be closed after detached', async () => {
125125
fire(target, 'contextmenu');
126126
expect(menu.opened).to.be.true;
127127

128128
const spy = sinon.spy(menu, 'close');
129129

130130
menu.parentNode.removeChild(menu);
131+
await aTimeout(0);
131132
expect(spy.calledOnce).to.be.true;
132133
expect(menu.opened).to.be.false;
133134
});
134135

135-
it('should not close when moved within the DOM', () => {
136+
it('should not close when moved within the DOM', async () => {
136137
fire(target, 'contextmenu');
137138
expect(menu.opened).to.be.true;
138139

139140
const newParent = document.createElement('div');
140141
document.body.appendChild(newParent);
141142

142143
newParent.appendChild(menu);
144+
await aTimeout(0);
143145
expect(menu.opened).to.be.true;
144146
});
145147
});

packages/menu-bar/src/vaadin-menu-bar-mixin.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -707,7 +707,7 @@ export const MenuBarMixin = (superClass) =>
707707

708708
if (wasExpanded && button.item && button.item.children) {
709709
this.__openSubMenu(button, true, { keepFocus: true });
710-
} else {
710+
} else if (!this._subMenu.opened) {
711711
this._tooltipController.open({ trigger: 'focus' });
712712
}
713713
}
@@ -761,7 +761,7 @@ export const MenuBarMixin = (superClass) =>
761761
this._setTabindex(btn, btn === target);
762762
});
763763

764-
if (isKeyboardActive()) {
764+
if (!this._subMenu.opened && isKeyboardActive()) {
765765
this._tooltipController.open({ trigger: 'focus' });
766766
}
767767
} else {

packages/menu-bar/src/vaadin-menu-bar-submenu.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,13 +102,21 @@ class MenuBarSubmenu extends ContextMenuMixin(ThemePropertyMixin(PolylitMixin(Li
102102
}
103103

104104
/**
105-
* Overriding the observer to not add global "contextmenu" listener.
105+
* Overriding the observer to not toggle user-select on the host.
106106
* @override
107107
*/
108108
_openedChanged() {
109109
// Do nothing
110110
}
111111

112+
/**
113+
* Overriding the handler to not react to global "contextmenu" events.
114+
* @override
115+
*/
116+
__onGlobalContextMenu() {
117+
// Do nothing
118+
}
119+
112120
/**
113121
* Overriding the public method to reset expanded button state.
114122
*/

0 commit comments

Comments
 (0)