使用垫子菜单作为上下文菜单:如何关闭打开的垫子菜单,并在另一个位置打开它

Using mat-menu as context menu: How to close opened mat-menu, and open it in another location

Angular: v8.2.8

我设法使用 mat-menu (angular material) 来模拟上下文菜单。在我的页面上右键单击,菜单出现在我的鼠标位置。

我想达到的效果:当我将鼠标移动到页面上的另一个位置时,我再次单击鼠标右键。我期待原始位置的菜单关闭并在新位置重新打开。

但实际上:当我右键单击时,没有任何反应,原始位置的菜单仍然打开。

事实上,我必须在原来的位置左键关闭打开的菜单,然后在新的鼠标位置右击打开菜单。

问题:知道如何实现我想要的吗?

@Component({
  selector: 'fl-home',
  template:`
 <mat-menu #contextmenu>
      <div >
           <button mat-menu-item>Clear all Paths</button>
       </div>
  </mat-menu>
  <div [matMenuTriggerFor]="contextmenu" [style.position]="'absolute'" [style.left.px]="menuX" [style.top.px]="menuY" ></div>
  
  <div class="container" (contextmenu)="onTriggerContextMenu($event);"> ....</div>
`})
export class HomeComponent  {

    menuX:number=0
    menuY:number=0

    @ViewChild(MatMenuTrigger,{static:false}) menu: MatMenuTrigger; 
    
   
    onTriggerContextMenu(event){
        
      event.preventDefault();
      this.menuX = event.x - 10;
      this.menuY = event.y - 10;
      this.menu.closeMenu() // close existing menu first.
      this.menu.openMenu()

    }
}

只需添加 hasBackdrop=false,如 <mat-menu ... [hasBackdrop]="false">。 发生的情况是,当我右键单击后打开菜单时,会出现一个不可见的背景覆盖层,覆盖整个页面。无论我再次尝试右键单击什么,上下文菜单事件都不会触发,因此 onTriggerContextMenu() 不会被调用。通过将 hasBackgrop 设置为 false,将不会出现此叠加层。

也许你解决了它,但对于其他人来说,这是我解决这个问题的方法: div 中的环绕菜单项,当鼠标离开菜单时(即 div)我将关闭它,因此您可以立即在另一个地方再次右键单击以再次显示上下文菜单....

<div (mouseleave)="mouseLeaveMenu()">
  ...menu items...
</div>
...
mouseLeaveMenu() {
  if (this.contextMenu.menuOpened) this.contextMenu.closeMenu();
}

我的解决方案

  ngOnInit(): void {
    this.windowClickSubscription = fromEvent(window, 'click').subscribe((_) => {
      if (this.contextMenu.menuOpen) {
        this.contextMenu.closeMenu();
      }
    });
  }
  ngOnDestroy(): void {
    this.windowClickSubscription && this.windowClickSubscription.unsubscribe();
    this.menuSubscription && this.menuSubscription.unsubscribe();
  }
  onContextMenu(event: MouseEvent){
    event.preventDefault();
    this.menuSubscription && this.menuSubscription.unsubscribe();
    this.menuSubscription = of(1)
      .pipe(
        tap(() => {
          if (this.contextMenu.menuOpen) {
            this.contextMenu.closeMenu();
          }
          this.contextMenuPosition.x = event.clientX;
          this.contextMenuPosition.y = event.clientY;
        }),
        // delay(this.contextMenu.menuOpen ? 200 : 0),
        delayWhen((_) => (this.contextMenu.menuOpen ? interval(200) : of(undefined))),
        tap(async() => {
          this.contextMenu.openMenu();
          let backdrop: HTMLElement = null;
          do {
            await this.delay(100);
            backdrop = document.querySelector(
              'div.cdk-overlay-backdrop.cdk-overlay-transparent-backdrop.cdk-overlay-backdrop-showing'
            ) as HTMLElement;
          } while (backdrop === null);
          backdrop.style.pointerEvents = 'none';
        })
      )
      .subscribe();
  }
  delay(delayInms: number) {
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve(2);
      }, delayInms);
    });
  }

https://stackblitz.com/edit/angular-ivy-udzyez

  1. 将属性 [hasBackdrop]="false" 添加到 mat-menu
  2. 更新 onTriggerContextMenu() 函数如下所示
  3. 终于;要关闭菜单,请在具有 (contextmenu)="..." 触发器的同一元素上正常单击,同时添加 (click)="themenu.closeMenu();"
    onTriggerContextMenu(event: MouseEvent) {
        this.themenu.closeMenu();
        event.preventDefault();
        this.menuX = event.clientX;
        this.menuY = event.clientY;
        this.themenu.menu.focusFirstItem('mouse');
        this.themenu.openMenu();
    }

如果您不想像大多数其他答案那样先关闭菜单,您可以手动设置菜单的位置:

// Assume you have the position in variables x and y
// Additionally you need to have the MatMenuTrigger (in this example in variable matMenuTrigger)

// this.cd.markForCheck() is important here if you just opened the menu with matMenuTrigger.openMenu()

const panelID = matMenuTrigger.menu.panelId;
const panelElement: HTMLElement = document.getElementById(panelID);

panelElement.style.position = "absolute";
panelElement.style.left = x + "px";
panelElement.style.top = y + "px";