web-dev-qa-db-fra.com

Comment ouvrir et fermer un menu mat angulaire en vol stationnaire

Cette question fait référence à ce problème -/Github, avec mat-menu qui ne peut pas être basculé avec le survol de la souris, j'essaie fondamentalement de remplacer un menu de navigation horizontale basé sur bootstrap par un menu de matériau angulaire. La seule chose qui m'empêche de reproduire le menu basé sur bootstrap est d'ouvrir et de fermer mat-menu en survol .. ... Comme mentionné dans le précédent problème de Github, il existe quelques solutions pour atteindre ce que je veux, comme utiliser, mouseEnter

(mouseenter)="menuTrigger.openMenu()"

ou en ajoutant un span à l'intérieur du menu Mat afin de lier mat-menu 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>

mais aucune des solutions ne semble couvrir tous les petits scénarios,

par exemple.

Comme mentionné dans le problème Github ci-dessus, la première solution SO contient les problèmes suivants.

  • Passez le curseur de la souris sur le bouton et le menu apparaîtra. Mais si vous cliquez sur le bouton, il va cacher et afficher le menu. A MON HUMBLE AVIS c'est un bug.
  • Pour masquer le menu, l'utilisateur doit cliquer en dehors du menu. Idéalement, le menu serait masqué si le curseur de la souris était à l'extérieur
    de la zone (qui comprend le bouton, le menu et les sous-menus)
    plus long que 400ms.

Et dans la solution span qui tente de résoudre l’un des problèmes ci-dessus, mais ne fonctionne pas correctement, par ex.

survoler MatMenuTrigger ouvre le mat-menu comme prévu, mais si un utilisateur éloigne la souris sans entrer mat-menu, il ne se ferme pas automatiquement, ce qui est faux.

Passer également à l’un des sous-menus de deux niveaux ferme également le menu de niveau un, ce qui n’est pas ce que je veux,

P.S déplacer la souris d'un menu ouvert à un autre frère n'ouvre pas le suivant. Je suppose que cela pourrait être difficile à réaliser comme mentionné ici , mais je pense que certaines d’entre elles pourraient être réalisables non?

Voici une base stackBlitz qui reproduit ce que je vis, toute aide est appréciée.

5
Saif Ullah

Le premier défi est que mat-menu vole le focus du bouton lorsque la superposition du CDK est générée en raison du z-index de la superposition ... pour résoudre ce problème, vous devez définir le z-index dans un style pour le bouton ... 

  • Cela arrêtera la boucle récursive lorsque vous ajoutez un (mouseleave) au bouton. style="z-index:1050"

Ensuite, vous devez suivre l'état de tous les événements d'entrée et de sortie pour les menus levelone et levelTwo et stocker cet état dans deux variables de composant.

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

Suivant les méthodes create menu et menuLeave pour les deux niveaux de menu .. notice menuLeave(trigger) vérifie si le niveau 2 est utilisé et ne fait rien si la valeur est true.

Remarque:menu2Leave() dispose d'une logique pour permettre la navigation vers le niveau un, mais ferme les deux si vous quittez l'autre côté ... en supprimant également la mise au point des boutons lorsque vous quittez les niveaux.

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

vous trouverez ci-dessous comment câbler le tout.

<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

Cette solution peut être utilisée comme alternative au réglage de z-index: 1050 comme suggéré par Marshal. Pour d'autres solutions, vous devriez vérifier la réponse de Marshal.

Vous pouvez utiliser 

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

Cette opération créera une boucle de scintillement continue, mais il existe une solution simple.

Il ne faut s'occuper que d'une chose, à savoir:

quand le menu s'ouvre

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

cette div couvre la totalité de l'écran, généralement ajoutée à la fin du code HTML complet juste avant la balise/body. Tous vos menus sont générés dans ce conteneur. (le nom de la classe peut différer selon les versions).

Ajoutez simplement ceci dans votre fichier de styles css/scss:

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

}

ou tout ce qui empêche cet élément de chevaucher votre bouton.

J'ai essayé cela moi-même, en espérant que ma réponse soit claire et précise.

Voici stackblitz demo du même, j'ai édité le code de stackblitz dans la question.

0
Sunil Kumar