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.
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 ...
(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
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.