-
Notifications
You must be signed in to change notification settings - Fork 6.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Right-click context menu #5007
Comments
As a temporary workaround, until material2 adds this feature, it's currently possible to simulate a context menu by putting a hidden menu trigger next to the item you want to right-click, like so:
This workaround is functional, but not perfect—so I'm looking forward to when material2 adds built-in support for context menus. |
@dschnelldavis we did something similar to use mdMenu as a contextual menu. But have you encountered problems with the overlay backdrop? I explain my case. We have a map with different markers and we show mdMenu on right clicking these markers. But, between each right click, if you don't close the menu, the backdrop intercept the right click and display the browser contextual menu instead. Have you manage this situation? |
It would be great if it could support dynamic menus with a variable number of submenus, like what was suggested in #4995. Different elements I right click on may produce slightly different menu options and submenus. I'm not sure how to create dynamic submenus since I think I would need dynamic template reference variables on those submenus. |
I've created a temporary contextmenu of For instance. |
@jelbourn Would it be acceptable to have the menu items become navigable through keyboard arrow keys? That's how context menus work in chrome macOS, or would we rather have it work with the tab key. I am currently making a context menu by utilizing the |
@jelbourn any progress on this ? |
@heyanctil Until they expose the overlay I added a littlebit of a hack that has been working well:
The CDK destroys the element when it's closed which destroys the listener... |
Hey @jelbourn, has there been any progress on integrating this into Angular Material? |
Nope- context menu isn't super high on our priority list |
Ideally, when a context menu is up, and the user right-clicks on a different element which has a context menu, the expected behavior is that the first context menu will be closed and the second context menu, triggered by the right-click, be opened. Please consider. |
I needed this too. Here is how I've implemented it, inspired by the solution of @dschnelldavis. I've added precise positioning of the context menu and reference to the contextual data: <mat-list>
<mat-list-item *ngFor="let item of items" (contextmenu)="onContextMenu($event, item)">
{{ item.name }}
</mat-list-item>
</mat-list>
<div style="visibility: hidden; position: fixed"
[style.left]="contextMenuPosition.x"
[style.top]="contextMenuPosition.y"
[matMenuTriggerFor]="contextMenu">
</div>
<mat-menu #contextMenu="matMenu">
<ng-template matMenuContent let-item="item">
<button mat-menu-item (click)="onContextMenuAction1(item)">Action 1</button>
<button mat-menu-item (click)="onContextMenuAction2(item)">Action 2</button>
</ng-template>
</mat-menu> import { Component, ViewChild } from '@angular/core';
import { MatMenuTrigger } from '@angular/material';
@Component({
selector: 'context-menu-example',
templateUrl: 'context-menu-example.html'
})
export class ContextMenuExample {
items = [
{id: 1, name: 'Item 1'},
{id: 2, name: 'Item 2'},
{id: 3, name: 'Item 3'}
];
@ViewChild(MatMenuTrigger)
contextMenu: MatMenuTrigger;
contextMenuPosition = { x: '0px', y: '0px' };
onContextMenu(event: MouseEvent, item: Item) {
event.preventDefault();
this.contextMenuPosition.x = event.clientX + 'px';
this.contextMenuPosition.y = event.clientY + 'px';
this.contextMenu.menuData = { 'item': item };
this.contextMenu.menu.focusFirstItem('mouse');
this.contextMenu.openMenu();
}
onContextMenuAction1(item: Item) {
alert(`Click on Action 1 for ${item.name}`);
}
onContextMenuAction2(item: Item) {
alert(`Click on Action 2 for ${item.name}`);
}
}
export interface Item {
id: number;
name: string;
} Here is a working example on StackBlitz. (code edited based on #5007 (comment) and #5007 (comment)) |
@simonbland Do you know why your solution does not work properly with a material-table ? |
Hi @hgndgn, Here is another working example, but with a table instead of list, also on StackBlitz. This is the same implementation, except that the table was replaced with a list and this is working fine. (code edited based on #5007 (comment) and #5007 (comment)) |
Thank you @simonbland it works now.
inside the last Thank you again! |
@irowbin do you have a stackblitz example of this? This is really great! |
@irowbin This is awesome! Can you share it on stackblitz ? |
@codestitch @TauanMatos sorry that the source code from the image above is not available at the moment.😢 To popup the context-menu you write few css rules for the Take a look at these links to get an idea which is written in vanilla js. Not the Angular or Material Design. |
@irowbin Thx XD |
…to a point (angular#14616) Allows for the connected overlay's origin to be set to a point on the page, rather than a DOM element. This allows people to easily implement right click context menus. Relates to angular#5007.
…to a point (angular#14616) Allows for the connected overlay's origin to be set to a point on the page, rather than a DOM element. This allows people to easily implement right click context menus. Relates to angular#5007.
@s2-abdo can you give us an example about how to use the new implementation? |
Amanzing. Can't wait to see matMenuTrigger taking advantage from it. |
@wizdmio Thanks! Your solution works as a charm aside to be very clean. |
@simonbland Why put the trigger inside *ngFor and have it duplicated?
Putting it only once like this accomplishes the same result in a more efficient way:
<mat-list>
<mat-list-item *ngFor="let item of items" (contextmenu)="onContextMenu($event, item)">
{{ item.name }}
</mat-list-item>
</mat-list>
<div style="visibility: hidden; position: fixed;"
[style.left]="contextMenuPosition.x"
[style.top]="contextMenuPosition.y"
[matMenuTriggerFor]="contextMenu">
</div>
<mat-menu #contextMenu="matMenu">
<ng-template matMenuContent let-item="item">
<button mat-menu-item (click)="onContextMenuAction1(item)">Action 1</button>
<button mat-menu-item (click)="onContextMenuAction2(item)">Action 2</button>
</ng-template>
</mat-menu> |
Thank you @philip-firstorder! I agree with all your points. I remember I was not totally happy with putting the trigger inside *ngFor, but simply didn't realise at the time I wrote this code that I've update the original example on StackBlitz with your enhancement. Cheers! |
@simonbland Very nice, you could also change the code in your original comment, so it matches the stackblitz |
@philip-firstorder Done, thanks! |
@simonbland Great and simple solution, thanks for posting it. EDIT: I found a solution. I had to update onContextMenu(event: MouseEvent, item: Item) {
event.preventDefault();
this.contextMenuPosition.x = event.clientX + 'px';
this.contextMenuPosition.y = event.clientY + 'px';
this.contextMenu.menuData = { item };
this.contextMenu._openedBy = 'mouse';
this.contextMenu.openMenu();
} You need to tell the context menu trigger that it's opened by a mouse or it highlights the first item for keyboard selection (defaults to 'program' instead of 'mouse'). Note you could also create a |
Hi @camargo, Thank you for the improvement and for the explanations why the first item is highlighted 👍 To fix this, I've found that we can merge the two alternative solutions you proposed, and instead of: this.contextMenu._openedBy = 'mouse'; We can write this: this.contextMenu.menu.focusFirstItem('mouse'); This doesn't involve calling the private I've updated the examples on StackBlitz: |
@simonbland Is this sort of thing possible with your implementation? In your examples right clicking anywhere while a context menu is open shows the browser context menu.
I found an example that has this functionality, but it doesn't use the material context menu (I would prefer material over cdk). |
Thank you , @simonbland and @camargo, Very useful indeed. |
ah …. @simonbland and @camargo, …. is it correct that this technique is relying on the HTML contextmenu attribute that is no longer supported on browsers ?? Edit: Ok Looked into this more and I got this wrong. I now understand, this is Not correct. It Is the HTML attribute 'contextmenu' that is being made obsolete. And In the example code here;
(contextmenu) is the HTML oncontextmenu Event (not the attribute) and the key learning for a newbie like myself is: Angular not using the 'on' in event names has to be considered when looking up the mechanics of code examples like these. So … Back to where I started... Thanks for the great solution for a context menu in Angular material. reference: https://www.w3schools.com/jsref/event_oncontextmenu.asp |
Hi @kreinerjm, You are right. Thank you for pointing this out. I've quickly tried to workaround this issue, but didn't find a solution using the CDK. If someone finds a nice solution for this, I will update the code examples. I've used this context menu implementation for an Electron application, where the browser context menu is disabled, so this problem don't appear. |
Hi @SimonGAndrews, Good to know! |
Thank for sharing your reasoning. It looks correct to me. However, I would say that (contextmenu) is the equivalent for the HTML oncontextmenu Event. Angular does not necessarily implement it like that. In fact, I don't see DOM onevent handlers in Angular generated code. Here is some more documentation on this topic, for those who are interested: |
FWIW I created a version of this that does not require adding a export function onContextMenu(
event: MouseEvent,
trigger: MatMenuTrigger,
data: any,
) {
event.preventDefault();
// @ts-ignore
const triggerElement: HTMLElement = trigger._element.nativeElement;
triggerElement.style.setProperty('left', `${event.clientX}px`);
triggerElement.style.setProperty('position', 'fixed');
triggerElement.style.setProperty('top', `${event.clientY}px`);
triggerElement.style.setProperty('visibility', 'hidden');
trigger.menuData = { data };
trigger.menu.focusFirstItem('mouse');
trigger.openMenu();
} <button (click)="onContextMenu($event, contextMenuTrigger, {})">
Open Context Menu
</button>
<div #contextMenuTrigger="matMenuTrigger" [matMenuTriggerFor]="contextMenu">
<mat-menu #contextMenu="matMenu">
<ng-template matMenuContent let-data="data">
<button mat-menu-item>
<mat-icon>delete_forever</mat-icon>
<span>Delete</span>
</button>
</ng-template>
</mat-menu>
</div> |
Context menus are now supported by the CdkMenu (https://material.angular.io/cdk/menu/overview#context-menus). We should re-implement the MatMenu to be based on the CdkMenu and it will get this feature for free. |
@mmalerba CdkMenu doesn't support passing a context to the template like MatMenu does, fyi. I just ran into that trying to replace a discontinued context menu library, so I'm stuck with working around MatMenu's shortcomings in the meantime. Luckily I don't need positioning, just a different triggering event. |
@jneuhaus20 could you open a feature request for cdk menu for this? That will make it easier to track (and mark as a good community contribution) |
This is actually now possible with |
This would effectively be md-menu but triggered by right-click instead of a specific element on the page.
Would need some investigation for a11y.
The text was updated successfully, but these errors were encountered: