diff --git a/README.md b/README.md
index f43cc81..e27a76b 100644
--- a/README.md
+++ b/README.md
@@ -75,6 +75,43 @@ public isMenuItemType1(item: any): boolean {
}
```
+## Sub-menus
+
+You can specify sub-menus like this:
+
+```html
+
+ Right Click: {{item?.name}}
+
+
+
+ Say...
+
+
+
+ ...hi!
+
+
+ ...hola!
+
+
+ ...salut!
+
+
+
+
+ Bye, {{item?.name}}
+
+
+ Input something:
+
+
+```
+
+Notes:
+1. The sub `` can not be placed inside the `` that references it.
+2. Sub-menus may be nested as deeply as you wish.
+
## Upgrade from angular2-contextmenu 0.x
1. Change `package.json` to reference `ngx-contextmenu` instead of `angular2-contextmenu`
@@ -253,6 +290,10 @@ You can optionally set focus on the context menu whenever it opens. This enable
export class AppModule {}
```
+### Keyboard navigation
+
+If you have `autoFocus` enabled, you can use keyboard shortcuts to navigate the context menu. `tab` - focus next menu item, `shift-tab` - focus previous menu item, `enter` - execute menu item or open sub menu, `esc` - close current context menu.
+
## Disable Context Menu
If you need to disable the context menu, you can pass a `boolean` to the `[disabled]` input:
diff --git a/src/demo/app.component.html b/src/demo/app.component.html
index 388ff74..f70f24b 100644
--- a/src/demo/app.component.html
+++ b/src/demo/app.component.html
@@ -64,12 +64,48 @@ Enabled and Visible as Functions
-
- Say hi!
+
+ Say...
-
+
+
+ ...hi!
+
+
+
+ ...hi!
+
+
+ ...hola!
+
+
+ ...salut!
+
+
+
+ ...hola!
+
+
+ ...salut!
+
+
+
Bye, {{item?.name}}
+
+
+ ...bye!
+
+
+ ...ciao!
+
+
+ ...au revoir!
+
+
+
+ Simple
+
Input something:
diff --git a/src/lib/contextMenu.component.ts b/src/lib/contextMenu.component.ts
index 4339a55..9642a02 100644
--- a/src/lib/contextMenu.component.ts
+++ b/src/lib/contextMenu.component.ts
@@ -1,3 +1,4 @@
+import { ContextMenuContentComponent } from './contextMenuContent.component';
import { ContextMenuItemDirective } from './contextMenu.item.directive';
import { CONTEXT_MENU_OPTIONS, IContextMenuOptions } from './contextMenu.options';
import { ContextMenuService, IContextMenuClickEvent } from './contextMenu.service';
@@ -5,9 +6,11 @@ import { ContextMenuInjectorService } from './contextMenuInjector.service';
import {
ChangeDetectorRef,
Component,
+ ComponentRef,
ContentChildren,
ElementRef,
EventEmitter,
+ HostListener,
Inject,
Input,
OnDestroy,
@@ -35,6 +38,7 @@ export interface MouseLocation {
template: ``,
})
export class ContextMenuComponent implements OnDestroy {
+ @Input() public autoFocus = false;
@Input() public useBootstrap4 = false;
@Input() public disabled = false;
@Output() public close: EventEmitter = new EventEmitter();
@@ -42,10 +46,9 @@ export class ContextMenuComponent implements OnDestroy {
@ContentChildren(ContextMenuItemDirective) public menuItems: QueryList;
@ViewChild('menu') public menuElement: ElementRef;
public visibleMenuItems: ContextMenuItemDirective[] = [];
+ public contextMenuContent: ContextMenuContentComponent;
public links: ILinkConfig[] = [];
- public isShown = false;
- public isOpening = false;
public item: any;
public event: MouseEvent;
private mouseLocation: MouseLocation = { left: '0px', top: '0px' };
@@ -59,9 +62,18 @@ export class ContextMenuComponent implements OnDestroy {
private contextMenuInjector: ContextMenuInjectorService,
) {
if (options) {
+ this.autoFocus = options.autoFocus;
this.useBootstrap4 = options.useBootstrap4;
}
this.subscription.add(_contextMenuService.show.subscribe(menuEvent => this.onMenuEvent(menuEvent)));
+ this.subscription.add(_contextMenuService.triggerClose.subscribe(contextMenuContent => {
+ if (!contextMenuContent) {
+ this.contextMenuInjector.destroyAll();
+ } else {
+ this.destroySubMenus(contextMenuContent);
+ this.contextMenuInjector.destroy(contextMenuContent);
+ }
+ }));
this.subscription.add(_contextMenuService.close.subscribe(event => this.close.emit(event)));
}
@@ -75,23 +87,43 @@ export class ContextMenuComponent implements OnDestroy {
return;
}
const { contextMenu, event, item } = menuEvent;
- this.contextMenuInjector.destroyAll();
+ if (!menuEvent.parentContextMenu) {
+ this.contextMenuInjector.destroyAll();
+ } else {
+ this.destroySubMenus(menuEvent.parentContextMenu);
+ }
+
if (contextMenu && contextMenu !== this) {
return;
}
this.event = event;
this.item = item;
- this.setVisibleMenuItems();
setTimeout(() => {
- this.contextMenuInjector.create({
+ this.setVisibleMenuItems();
+ this.contextMenuContent = this.contextMenuInjector.create({
menuItems: this.visibleMenuItems,
item: this.item,
event: this.event,
+ parentContextMenu: menuEvent.parentContextMenu,
});
this.open.next(menuEvent);
});
}
+ public destroySubMenus(parent: ContextMenuContentComponent): void {
+ const cmContents: ComponentRef[] = this.contextMenuInjector.getByType(this.contextMenuInjector.type);
+ cmContents.filter(content => content.instance.parentContextMenu === parent)
+ .forEach(comp => {
+ this.destroySubMenus(comp.instance);
+ this.contextMenuInjector.destroy(comp);
+ });
+ }
+
+ @HostListener('window:keydown.Escape')
+ public destroyLeafMenu(): void {
+ this._contextMenuService.destroyLeafMenu();
+ }
+
public isMenuItemVisible(menuItem: ContextMenuItemDirective): boolean {
return this.evaluateIfFunction(menuItem.visible);
}
diff --git a/src/lib/contextMenu.item.directive.ts b/src/lib/contextMenu.item.directive.ts
index 05c2da7..ed8dd9a 100644
--- a/src/lib/contextMenu.item.directive.ts
+++ b/src/lib/contextMenu.item.directive.ts
@@ -1,3 +1,4 @@
+import { ContextMenuComponent } from './contextMenu.component';
import { Directive, Input, Output, EventEmitter, TemplateRef } from '@angular/core';
@Directive({
@@ -6,9 +7,10 @@ import { Directive, Input, Output, EventEmitter, TemplateRef } from '@angular/co
/* tslint:enable:directive-selector-type */
})
export class ContextMenuItemDirective {
- @Input() public divider: boolean = false;
- @Input() public passive: boolean = false;
+ @Input() public subMenu: ContextMenuComponent;
+ @Input() public divider = false;
@Input() public enabled: boolean | ((item: any) => boolean) = true;
+ @Input() public passive = false;
@Input() public visible: boolean | ((item: any) => boolean) = true;
@Output() public execute: EventEmitter<{ event: Event, item: any }> = new EventEmitter<{ event: Event, item: any }>();
diff --git a/src/lib/contextMenu.service.ts b/src/lib/contextMenu.service.ts
index 80679ac..e518f9e 100644
--- a/src/lib/contextMenu.service.ts
+++ b/src/lib/contextMenu.service.ts
@@ -1,15 +1,40 @@
import { ContextMenuComponent } from './';
-import { Injectable } from '@angular/core';
+import { ContextMenuContentComponent } from './contextMenuContent.component';
+import { ContextMenuInjectorService } from './contextMenuInjector.service';
+import { ComponentRef, Injectable } from '@angular/core';
import { Subject } from 'rxjs/Subject';
export interface IContextMenuClickEvent {
contextMenu?: ContextMenuComponent;
event: MouseEvent;
+ parentContextMenu?: ContextMenuContentComponent;
item: any;
}
@Injectable()
export class ContextMenuService {
+ public isDestroyingLeafMenu = false;
+
public show: Subject = new Subject();
+ public triggerClose: Subject = new Subject();
public close: Subject = new Subject();
+
+ constructor(private contextMenuInjector: ContextMenuInjectorService) {}
+
+ public destroyLeafMenu(): void {
+ if (this.isDestroyingLeafMenu) {
+ return;
+ }
+ this.isDestroyingLeafMenu = true;
+ setTimeout(() => {
+ const cmContents: ComponentRef[] = this.contextMenuInjector.getByType(this.contextMenuInjector.type);
+ if (cmContents.length > 1) {
+ cmContents[cmContents.length - 2].instance.focus();
+ }
+ if (cmContents.length > 0) {
+ this.contextMenuInjector.destroy(cmContents[cmContents.length - 1]);
+ }
+ this.isDestroyingLeafMenu = false;
+ });
+ }
}
diff --git a/src/lib/contextMenuContent.component.ts b/src/lib/contextMenuContent.component.ts
index eb6bf36..951db9d 100644
--- a/src/lib/contextMenuContent.component.ts
+++ b/src/lib/contextMenuContent.component.ts
@@ -1,5 +1,3 @@
-import { Subscription } from 'rxjs/Subscription';
-import { ContextMenuInjectorService } from './contextMenuInjector.service';
import { ContextMenuItemDirective } from './contextMenu.item.directive';
import { CONTEXT_MENU_OPTIONS, IContextMenuOptions } from './contextMenu.options';
import { ContextMenuService } from './contextMenu.service';
@@ -8,16 +6,15 @@ import {
ChangeDetectorRef,
Component,
ElementRef,
- EventEmitter,
HostListener,
Inject,
Input,
Optional,
- Output,
Renderer,
ViewChild
} from '@angular/core';
-import { OnInit, OnDestroy } from '@angular/core';
+import { OnDestroy, OnInit } from '@angular/core';
+import { Subscription } from 'rxjs/Subscription';
export interface ILinkConfig {
click: (item: any, $event?: MouseEvent) => void;
@@ -41,17 +38,21 @@ export interface MouseLocation {
font-weight: normal;
line-height: @line-height-base;
white-space: nowrap;
- }`
+ }`,
+ `.hasSubMenu:after {
+ content: "\u25B6";
+ float: right;
+ }`,
],
template:
- `