Skip to content

Commit f02fe71

Browse files
committed
Fix focusing on first context menu item
Ensure menu positioning is complete. Make focus take place after next repaint.
1 parent b9f04df commit f02fe71

File tree

1 file changed

+24
-10
lines changed

1 file changed

+24
-10
lines changed

frontend/src/app/shared/components/op-context-menu/op-context-menu.service.ts

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export class OPContextMenuService {
2121

2222
// Allow temporarily disabling the close handler
2323
private isOpening = false;
24+
private openSeq = 0;
2425

2526
constructor(
2627
readonly FocusHelper:FocusHelperService,
@@ -83,20 +84,33 @@ export class OPContextMenuService {
8384
*/
8485
public show(menu:OpContextMenuHandler, event:Event, component:ComponentType<unknown> = OPContextMenuComponent):void {
8586
this.close();
87+
this.isOpening = true;
88+
89+
// eslint-disable-next-line no-plusplus
90+
const seq = ++this.openSeq;
8691

8792
// Create a portal for the given component class and render it
88-
this.isOpening = true;
8993
const portal = new ComponentPortal(component, null, this.injectorFor(menu.locals));
9094
this.bodyPortalHost.attach(portal);
9195
this.portalHostElement.style.display = 'block';
9296
this.active = menu;
9397

94-
setTimeout(() => {
95-
this.reposition(event);
96-
// Focus on the first element
97-
this.active?.onOpen(this.activeMenu);
98-
this.isOpening = false;
99-
});
98+
void this.reposition(event)
99+
.then(() => {
100+
// Defer onOpen to the next frame to ensure DOM/styles are applied
101+
if (this.active && this.openSeq === seq) {
102+
requestAnimationFrame(() => {
103+
if (this.active && this.openSeq === seq) {
104+
this.active.onOpen(this.activeMenu);
105+
}
106+
});
107+
}
108+
})
109+
.finally(() => {
110+
if (this.openSeq === seq) {
111+
this.isOpening = false;
112+
}
113+
});
100114
}
101115

102116
public isActive(menu:OpContextMenuHandler):boolean {
@@ -118,12 +132,12 @@ export class OPContextMenuService {
118132
this.active = null;
119133
}
120134

121-
public reposition(event:Event):void {
135+
public reposition(event:Event):Promise<void> {
122136
if (!this.active) {
123-
return;
137+
return Promise.resolve();
124138
}
125139

126-
this.active.computePosition(this.activeMenu, event)
140+
return this.active.computePosition(this.activeMenu, event)
127141
.then(({ x, y }) => {
128142
Object.assign(this.activeMenu.style, {
129143
left: `${x}px`,

0 commit comments

Comments
 (0)