web-dev-qa-db-ger.com

Öffnen und Schließen Angular Mattenmenü auf Schwebeflug

Diese Frage bezieht sich auf das this Github-Problem, bei dem der mat-menu nicht mit dem Mauszeiger umgeschaltet werden kann. Ich versuche im Grunde, ein Bootstrap-basiertes horizontales Navigationsmenü durch ein eckiges Materialmenü zu ersetzen. Das einzige, was mich davon abhält, ein Bootstrap-basiertes Menü zu replizieren, ist das Öffnen und Schließen von mat-menu beim Schweben. Wie in der obigen Ausgabe von Github erwähnt, gibt es einige Problemumgehungen, um das zu erreichen, was ich möchte, wie die Verwendung von mouseEnter

(mouseenter)="menuTrigger.openMenu()"

oder füge ein span inside Mat-Menü hinzu, um mat-menu zu binden close,

<mat-menu #menu="matMenu" overlapTrigger="false">
  <span (mouseleave)="menuTrigger.closeMenu()">
    <button mat-menu-item>Item 1</button>
    <button mat-menu-item>Item 2</button>
  </span>
</mat-menu>

aber keine der Lösungen scheint jedes kleine Szenario abzudecken,

z.B.

Wie in der obigen Github-Ausgabe erwähnt, gibt es in der ersten SO Lösung folgende Probleme.

  • Bewegen Sie den Mauszeiger auf die Schaltfläche und das Menü wird angezeigt. Wenn Sie jedoch auf die Schaltfläche klicken, wird das Menü ausgeblendet und angezeigt. IMHO ist es ein Fehler.
  • Um das Menü auszublenden, muss der Benutzer außerhalb des Menüs klicken. Im Idealfall wird das Menü ausgeblendet, wenn sich der Mauszeiger außerhalb befindet
    des Bereichs (der die Schaltfläche, das Menü und Untermenüs enthält)
    länger als 400 ms.

Und in der Span-Lösung, die versucht, eines der obigen Probleme zu lösen, aber nicht richtig funktioniert, z.

wenn Sie den Mauszeiger über MatMenuTrigger bewegen, wird der mat-menu wie erwartet geöffnet. Wenn ein Benutzer die Maus wegbewegt, ohne mat-menu einzugeben, wird der Code nicht automatisch geschlossen, was falsch ist.

Wenn Sie ebenfalls zu einem der Untermenüs der Ebene 2 wechseln, wird auch das Menü der Ebene 1 geschlossen, was nicht das ist, was ich möchte.

PS Wenn Sie die Maus von einem geöffneten Menü zum nächsten bewegen, wird das nächste Menü nicht geöffnet. Ich denke, dies könnte schwierig zu erreichen sein, wie erwähnt hier , aber ich denke, einige davon könnten erreichbar sein, oder?

Hier ist ein grundlegendes stackBlitz , das wiedergibt, was ich erlebe, jede Hilfe wird geschätzt.

5
Saif Ullah

Die erste Herausforderung besteht darin, dass mat-menu den Fokus der Schaltfläche stiehlt, wenn das CDK-Overlay aufgrund des z-index des Overlays generiert wird. Um dieses Problem zu lösen, müssen Sie den Z-Index in einem Stil für die Schaltfläche festlegen. 

  • Dadurch wird die rekursive Schleife angehalten, wenn Sie der Schaltfläche einen (mouseleave) hinzufügen. style="z-index:1050"

Als Nächstes müssen Sie den Status aller Eingabe- und Verlassereignisse für die Menüs levelone und levelTwo verfolgen und diesen Status in zwei Komponentenvariablen speichern.

enteredButton = false;
isMatMenuOpen = false;
isMatMenu2Open = false;

Als Nächstes erstellen Sie Menüeingabe- und menuLeave-Methoden für beide Menüebenen. Hinweis menuLeave(trigger) prüft, ob auf Ebene2 zugegriffen wird, und führt nichts aus, wenn es wahr ist.

Bitte beachten Sie:menu2Leave() verfügt über eine Logik, um die Navigation zu Ebene 1 zu ermöglichen, aber beide zu schließen, wenn Sie die andere Seite verlassen.

menuenter() {
    this.isMatMenuOpen = true;
    if (this.isMatMenu2Open) {
      this.isMatMenu2Open = false;
    }
  }

  menuLeave(trigger, button) {
    setTimeout(() => {
      if (!this.isMatMenu2Open && !this.enteredButton) {
        this.isMatMenuOpen = false;
        trigger.closeMenu();
        this.ren.removeClass(button['_elementRef'].nativeElement, 'cdk-focused');
        this.ren.removeClass(button['_elementRef'].nativeElement, 'cdk-program-focused');
      } else {
        this.isMatMenuOpen = false;
      }
    }, 80)
  }

  menu2enter() {
    this.isMatMenu2Open = true;
  }

  menu2Leave(trigger1, trigger2, button) {
    setTimeout(() => {
      if (this.isMatMenu2Open) {
        trigger1.closeMenu();
        this.isMatMenuOpen = false;
        this.isMatMenu2Open = false;
        this.enteredButton = false;
        this.ren.removeClass(button['_elementRef'].nativeElement, 'cdk-focused');
        this.ren.removeClass(button['_elementRef'].nativeElement, 'cdk-program-focused');
      } else {
        this.isMatMenu2Open = false;
        trigger2.closeMenu();
      }
    }, 100)
  }

  buttonEnter(trigger) {
    setTimeout(() => {
      if(this.prevButtonTrigger && this.prevButtonTrigger != trigger){
        this.prevButtonTrigger.closeMenu();
        this.prevButtonTrigger = trigger;
        trigger.openMenu();
      }
      else if (!this.isMatMenuOpen) {
        this.enteredButton = true;
        this.prevButtonTrigger = trigger
        trigger.openMenu()
      }
      else {
        this.enteredButton = true;
        this.prevButtonTrigger = trigger
      }
    })
  }

  buttonLeave(trigger, button) {
    setTimeout(() => {
      if (this.enteredButton && !this.isMatMenuOpen) {
        trigger.closeMenu();
        this.ren.removeClass(button['_elementRef'].nativeElement, 'cdk-focused');
        this.ren.removeClass(button['_elementRef'].nativeElement, 'cdk-program-focused');
      } if (!this.isMatMenuOpen) {
        trigger.closeMenu();
        this.ren.removeClass(button['_elementRef'].nativeElement, 'cdk-focused');
        this.ren.removeClass(button['_elementRef'].nativeElement, 'cdk-program-focused');
      } else {
        this.enteredButton = false;
      }
    }, 100)
  }

HTML

unten ist, wie man alles verdrahtet.

<ng-container *ngFor="let menuItem of modulesList">

    <ng-container *ngIf="!menuItem.children">
        <a class="nav-link">
            <span class="icon fa" [ngClass]="menuItem.icon"></span>
      <span class="text-holder">{{menuItem.label}}</span>
    </a>
  </ng-container>
  <ng-container *ngIf="menuItem.children.length > 0">
    <button #button mat-button [matMenuTriggerFor]="levelOne" #levelOneTrigger="matMenuTrigger" (mouseenter)="levelOneTrigger.openMenu()" (mouseleave)="buttonLeave(levelOneTrigger, button)" style="z-index:1050">
      <span class="icon fa" [ngClass]="menuItem.icon"></span>
      <span>{{menuItem.label}}
        <i class="fa fa-chevron-down"></i>
      </span>
    </button>

    <mat-menu #levelOne="matMenu" direction="down" yPosition="below">
      <span (mouseenter)="menuenter()" (mouseleave)="menuLeave(levelOneTrigger, button)">
      <ng-container *ngFor="let childL1 of menuItem.children">
        <li class="p-0" *ngIf="!childL1.children" mat-menu-item>
          <a class="nav-link">{{childL1.label}}
            <i *ngIf="childL1.icon" [ngClass]="childL1.icon"></i>
          </a>
        </li>
        <ng-container *ngIf="childL1.children && childL1.children.length > 0">
          <li mat-menu-item #levelTwoTrigger="matMenuTrigger" [matMenuTriggerFor]="levelTwo">
            <span class="icon fa" [ngClass]="childL1.icon"></span>
            <span>{{childL1.label}}</span>
          </li>

          <mat-menu #levelTwo="matMenu">
            <span (mouseenter)="menu2enter()" (mouseleave)="menu2Leave(levelOneTrigger,levelTwoTrigger, button)">
            <ng-container *ngFor="let childL2 of childL1.children">
              <li class="p-0" mat-menu-item>
                <a class="nav-link">{{childL2.label}}
                  <i *ngIf="childL2.icon" [ngClass]="childL2.icon"></i>
                </a>
              </li>
            </ng-container>
            </span>
          </mat-menu>
        </ng-container>
      </ng-container>
      </span>
    </mat-menu>
  </ng-container>

</ng-container>

Stackblitz

https://stackblitz.com/edit/mat-nested-menu-yclrmd?embed=1&file=app/nested-menu-example.html

8
Marshal

Diese Lösung kann als Alternative zur Einstellung von z-index: 1050 verwendet werden, wie von Marshal vorgeschlagen. Für andere Korrekturen sollten Sie die Antwort von Marshal überprüfen.

Sie können verwenden 

<button [matMenuTriggerFor]="menu" #trigger="matMenuTrigger" (mouseenter)="trigger.openMenu()" (mouseleave)="trigger.closeMenu()"></button>

Wenn Sie dies verwenden, wird eine kontinuierliche Flimmer-Schleife erzeugt, es gibt jedoch eine einfache Lösung.

Es muss nur eines beachtet werden, d. H.

wenn das Menü geöffnet wird

<div class="cdk-overlay-container"></div>

dieses div deckt den gesamten Bildschirm ab und wird normalerweise am Ende der gesamten HTML direkt vor dem Tag/body hinzugefügt. Alle Ihre Menüs werden in diesem Container generiert. (Klassenname kann sich in verschiedenen Versionen unterscheiden).

Fügen Sie dies einfach in Ihre CSS/Scss-Styles-Datei ein:

.cdk-overlay-container{
    left:200px;
    top:200px;
}
.cdk-overlay-connected-position-bounding-box{
    top:0 !important;

}

oder etwas, das dieses Element daran hindert, Ihre Schaltfläche zu überlappen.

Ich habe das selbst ausprobiert und hoffe, dass meine Antwort klar und präzise war.

Hier ist stackblitz Demo des gleichen, ich habe den stackblitz-Code in der Frage bearbeitet.

0
Sunil Kumar