From b780e7dd959b18d4141069f1c8b853eb150550b5 Mon Sep 17 00:00:00 2001 From: Arno Date: Sat, 6 Jul 2019 23:49:19 +0200 Subject: [PATCH 1/4] Option to highlight the parent items of a submenu --- .../src/lib/contextMenu.component.ts | 192 +++++++++--------- .../src/lib/contextMenu.item.directive.ts | 8 + .../src/lib/contextMenu.options.ts | 1 + .../src/lib/contextMenuContent.component.ts | 11 + src/demo/app.module.ts | 1 + 5 files changed, 118 insertions(+), 95 deletions(-) diff --git a/projects/ngx-contextmenu/src/lib/contextMenu.component.ts b/projects/ngx-contextmenu/src/lib/contextMenu.component.ts index ed623dc..92f105b 100644 --- a/projects/ngx-contextmenu/src/lib/contextMenu.component.ts +++ b/projects/ngx-contextmenu/src/lib/contextMenu.component.ts @@ -1,18 +1,18 @@ import { - ChangeDetectorRef, - Component, - ContentChildren, - ElementRef, - EventEmitter, - HostListener, - Inject, - Input, - OnDestroy, - Optional, - Output, - QueryList, - ViewChild, - ViewEncapsulation, + ChangeDetectorRef, + Component, + ContentChildren, + ElementRef, + EventEmitter, + HostListener, + Inject, + Input, + OnDestroy, + Optional, + Output, + QueryList, + ViewChild, + ViewEncapsulation, } from '@angular/core'; import { Subscription } from 'rxjs'; import { first } from 'rxjs/operators'; @@ -23,102 +23,104 @@ import { ContextMenuService, IContextMenuClickEvent, CloseContextMenuEvent } fro import { CONTEXT_MENU_OPTIONS } from './contextMenu.tokens'; export interface ILinkConfig { - click: (item: any, $event?: MouseEvent) => void; - enabled?: (item: any) => boolean; - html: (item: any) => string; +click: (item: any, $event?: MouseEvent) => void; +enabled?: (item: any) => boolean; +html: (item: any) => string; } export interface MouseLocation { - left?: string; - marginLeft?: string; - marginTop?: string; - top?: string; +left?: string; +marginLeft?: string; +marginTop?: string; +top?: string; } @Component({ - encapsulation: ViewEncapsulation.None, - selector: 'context-menu', - styles: [` - .cdk-overlay-container { - position: fixed; - z-index: 1000; - pointer-events: none; - top: 0; - left: 0; - width: 100%; - height: 100%; - } - .ngx-contextmenu.cdk-overlay-pane { - position: absolute; - pointer-events: auto; - box-sizing: border-box; - } - `], - template: ` `, +encapsulation: ViewEncapsulation.None, +selector: 'context-menu', +styles: [` + .cdk-overlay-container { + position: fixed; + z-index: 1000; + pointer-events: none; + top: 0; + left: 0; + width: 100%; + height: 100%; + } + .ngx-contextmenu.cdk-overlay-pane { + position: absolute; + pointer-events: auto; + box-sizing: border-box; + } +`], +template: ` `, }) export class ContextMenuComponent implements OnDestroy { - @Input() public menuClass = ""; - @Input() public autoFocus = false; - @Input() public useBootstrap4 = false; - @Input() public disabled = false; - @Output() public close: EventEmitter = new EventEmitter(); - @Output() public open: EventEmitter = new EventEmitter(); - @ContentChildren(ContextMenuItemDirective) public menuItems: QueryList; - @ViewChild('menu') public menuElement: ElementRef; - public visibleMenuItems: ContextMenuItemDirective[] = []; +@Input() public menuClass = ""; +@Input() public autoFocus = false; +@Input() public useBootstrap4 = false; +@Input() public highlightParentItems = false; +@Input() public disabled = false; +@Output() public close: EventEmitter = new EventEmitter(); +@Output() public open: EventEmitter = new EventEmitter(); +@ContentChildren(ContextMenuItemDirective) public menuItems: QueryList; +@ViewChild('menu') public menuElement: ElementRef; +public visibleMenuItems: ContextMenuItemDirective[] = []; - public links: ILinkConfig[] = []; - public item: any; - public event: MouseEvent | KeyboardEvent; - private subscription: Subscription = new Subscription(); +public links: ILinkConfig[] = []; +public item: any; +public event: MouseEvent | KeyboardEvent; +private subscription: Subscription = new Subscription(); - constructor( - private _contextMenuService: ContextMenuService, - private changeDetector: ChangeDetectorRef, - private elementRef: ElementRef, - @Optional() - @Inject(CONTEXT_MENU_OPTIONS) private options: IContextMenuOptions, - ) { - if (options) { - this.autoFocus = options.autoFocus; - this.useBootstrap4 = options.useBootstrap4; - } - this.subscription.add(_contextMenuService.show.subscribe(menuEvent => { - this.onMenuEvent(menuEvent); - })); +constructor( + private _contextMenuService: ContextMenuService, + private changeDetector: ChangeDetectorRef, + private elementRef: ElementRef, + @Optional() + @Inject(CONTEXT_MENU_OPTIONS) private options: IContextMenuOptions, +) { + if (options) { + this.autoFocus = options.autoFocus; + this.useBootstrap4 = options.useBootstrap4; + this.highlightParentItems = options.highlightParentItems; } + this.subscription.add(_contextMenuService.show.subscribe(menuEvent => { + this.onMenuEvent(menuEvent); + })); +} - public ngOnDestroy(): void { - this.subscription.unsubscribe(); - } +public ngOnDestroy(): void { + this.subscription.unsubscribe(); +} - public onMenuEvent(menuEvent: IContextMenuClickEvent): void { - if (this.disabled) { - return; - } - const { contextMenu, event, item } = menuEvent; - if (contextMenu && contextMenu !== this) { - return; - } - this.event = event; - this.item = item; - this.setVisibleMenuItems(); - this._contextMenuService.openContextMenu({ ...menuEvent, menuItems: this.visibleMenuItems, menuClass: this.menuClass }); - this._contextMenuService.close.asObservable().pipe(first()).subscribe(closeEvent => this.close.emit(closeEvent)); - this.open.next(menuEvent); +public onMenuEvent(menuEvent: IContextMenuClickEvent): void { + if (this.disabled) { + return; } - - public isMenuItemVisible(menuItem: ContextMenuItemDirective): boolean { - return this.evaluateIfFunction(menuItem.visible); + const { contextMenu, event, item } = menuEvent; + if (contextMenu && contextMenu !== this) { + return; } + this.event = event; + this.item = item; + this.setVisibleMenuItems(); + this._contextMenuService.openContextMenu({ ...menuEvent, menuItems: this.visibleMenuItems, menuClass: this.menuClass }); + this._contextMenuService.close.asObservable().pipe(first()).subscribe(closeEvent => this.close.emit(closeEvent)); + this.open.next(menuEvent); +} - public setVisibleMenuItems(): void { - this.visibleMenuItems = this.menuItems.filter(menuItem => this.isMenuItemVisible(menuItem)); - } +public isMenuItemVisible(menuItem: ContextMenuItemDirective): boolean { + return this.evaluateIfFunction(menuItem.visible); +} - public evaluateIfFunction(value: any): any { - if (value instanceof Function) { - return value(this.item); - } - return value; +public setVisibleMenuItems(): void { + this.visibleMenuItems = this.menuItems.filter(menuItem => this.isMenuItemVisible(menuItem)); +} + +public evaluateIfFunction(value: any): any { + if (value instanceof Function) { + return value(this.item); } + return value; +} } diff --git a/projects/ngx-contextmenu/src/lib/contextMenu.item.directive.ts b/projects/ngx-contextmenu/src/lib/contextMenu.item.directive.ts index be2e947..9369780 100644 --- a/projects/ngx-contextmenu/src/lib/contextMenu.item.directive.ts +++ b/projects/ngx-contextmenu/src/lib/contextMenu.item.directive.ts @@ -16,6 +16,7 @@ export class ContextMenuItemDirective implements Highlightable { public currentItem: any; public isActive = false; + public isActiveParent = false; public get disabled() { return this.passive || this.divider || @@ -38,6 +39,13 @@ export class ContextMenuItemDirective implements Highlightable { this.isActive = false; } + public setActiveParentStyles(): void { + this.isActiveParent = true; + } + public setInActiveParentStyles(): void { + this.isActiveParent = false; + } + public triggerExecute(item: any, $event?: MouseEvent | KeyboardEvent): void { if (!this.evaluateIfFunction(this.enabled, item)) { return; diff --git a/projects/ngx-contextmenu/src/lib/contextMenu.options.ts b/projects/ngx-contextmenu/src/lib/contextMenu.options.ts index a607f69..e6e3ad7 100644 --- a/projects/ngx-contextmenu/src/lib/contextMenu.options.ts +++ b/projects/ngx-contextmenu/src/lib/contextMenu.options.ts @@ -1,4 +1,5 @@ export interface IContextMenuOptions { useBootstrap4?: boolean; autoFocus?: boolean; + highlightParentItems?: boolean; } diff --git a/projects/ngx-contextmenu/src/lib/contextMenuContent.component.ts b/projects/ngx-contextmenu/src/lib/contextMenuContent.component.ts index c244f01..752181c 100644 --- a/projects/ngx-contextmenu/src/lib/contextMenuContent.component.ts +++ b/projects/ngx-contextmenu/src/lib/contextMenuContent.component.ts @@ -42,6 +42,11 @@ const ARROW_LEFT_KEYCODE = 37; .hasSubMenu:before { content: "\u25B6"; float: right; + } + .activeParent { + text-decoration: none; + color: #262626; + background-color: #f5f5f5; }`, ], template: @@ -53,6 +58,7 @@ const ARROW_LEFT_KEYCODE = 37; [attr.role]="menuItem.divider ? 'separator' : undefined"> @@ -86,6 +92,7 @@ export class ContextMenuContentComponent implements OnInit, OnDestroy, AfterView public autoFocus = false; public useBootstrap4 = false; + public highlightParentItems = false; private _keyManager: ActiveDescendantKeyManager; private subscription: Subscription = new Subscription(); constructor( @@ -98,6 +105,7 @@ export class ContextMenuContentComponent implements OnInit, OnDestroy, AfterView if (options) { this.autoFocus = options.autoFocus; this.useBootstrap4 = options.useBootstrap4; + this.highlightParentItems = options.highlightParentItems; } } @@ -207,6 +215,9 @@ export class ContextMenuContentComponent implements OnInit, OnDestroy, AfterView public onOpenSubMenu(menuItem: ContextMenuItemDirective, event?: MouseEvent | KeyboardEvent): void { const anchorElementRef = this.menuItemElements.toArray()[this._keyManager.activeItemIndex]; const anchorElement = anchorElementRef && anchorElementRef.nativeElement; + if (this.highlightParentItems) { + this.menuItems.forEach(item => item.isActiveParent = (item === menuItem && menuItem.subMenu) ); + } this.openSubMenu.emit({ anchorElement, contextMenu: menuItem.subMenu, diff --git a/src/demo/app.module.ts b/src/demo/app.module.ts index 1eda11d..8910a60 100644 --- a/src/demo/app.module.ts +++ b/src/demo/app.module.ts @@ -18,6 +18,7 @@ import { ChildTwoComponent } from './child2.component'; ContextMenuModule.forRoot({ autoFocus: true, // useBootstrap4: true, + highlightParentItems: true }), RouterModule.forRoot([ { From d14da166ae53119d3400bbda2097c25843421c69 Mon Sep 17 00:00:00 2001 From: Arno Date: Sat, 6 Jul 2019 23:56:26 +0200 Subject: [PATCH 2/4] fixed spacing --- .../src/lib/contextMenu.component.ts | 194 +++++++++--------- 1 file changed, 97 insertions(+), 97 deletions(-) diff --git a/projects/ngx-contextmenu/src/lib/contextMenu.component.ts b/projects/ngx-contextmenu/src/lib/contextMenu.component.ts index 92f105b..5219897 100644 --- a/projects/ngx-contextmenu/src/lib/contextMenu.component.ts +++ b/projects/ngx-contextmenu/src/lib/contextMenu.component.ts @@ -1,18 +1,18 @@ import { - ChangeDetectorRef, - Component, - ContentChildren, - ElementRef, - EventEmitter, - HostListener, - Inject, - Input, - OnDestroy, - Optional, - Output, - QueryList, - ViewChild, - ViewEncapsulation, + ChangeDetectorRef, + Component, + ContentChildren, + ElementRef, + EventEmitter, + HostListener, + Inject, + Input, + OnDestroy, + Optional, + Output, + QueryList, + ViewChild, + ViewEncapsulation, } from '@angular/core'; import { Subscription } from 'rxjs'; import { first } from 'rxjs/operators'; @@ -23,104 +23,104 @@ import { ContextMenuService, IContextMenuClickEvent, CloseContextMenuEvent } fro import { CONTEXT_MENU_OPTIONS } from './contextMenu.tokens'; export interface ILinkConfig { -click: (item: any, $event?: MouseEvent) => void; -enabled?: (item: any) => boolean; -html: (item: any) => string; + click: (item: any, $event?: MouseEvent) => void; + enabled?: (item: any) => boolean; + html: (item: any) => string; } export interface MouseLocation { -left?: string; -marginLeft?: string; -marginTop?: string; -top?: string; + left?: string; + marginLeft?: string; + marginTop?: string; + top?: string; } @Component({ -encapsulation: ViewEncapsulation.None, -selector: 'context-menu', -styles: [` - .cdk-overlay-container { - position: fixed; - z-index: 1000; - pointer-events: none; - top: 0; - left: 0; - width: 100%; - height: 100%; - } - .ngx-contextmenu.cdk-overlay-pane { - position: absolute; - pointer-events: auto; - box-sizing: border-box; - } -`], -template: ` `, + encapsulation: ViewEncapsulation.None, + selector: 'context-menu', + styles: [` + .cdk-overlay-container { + position: fixed; + z-index: 1000; + pointer-events: none; + top: 0; + left: 0; + width: 100%; + height: 100%; + } + .ngx-contextmenu.cdk-overlay-pane { + position: absolute; + pointer-events: auto; + box-sizing: border-box; + } + `], + template: ` `, }) export class ContextMenuComponent implements OnDestroy { -@Input() public menuClass = ""; -@Input() public autoFocus = false; -@Input() public useBootstrap4 = false; -@Input() public highlightParentItems = false; -@Input() public disabled = false; -@Output() public close: EventEmitter = new EventEmitter(); -@Output() public open: EventEmitter = new EventEmitter(); -@ContentChildren(ContextMenuItemDirective) public menuItems: QueryList; -@ViewChild('menu') public menuElement: ElementRef; -public visibleMenuItems: ContextMenuItemDirective[] = []; + @Input() public menuClass = ""; + @Input() public autoFocus = false; + @Input() public useBootstrap4 = false; + @Input() public highlightParentItems = false; + @Input() public disabled = false; + @Output() public close: EventEmitter = new EventEmitter(); + @Output() public open: EventEmitter = new EventEmitter(); + @ContentChildren(ContextMenuItemDirective) public menuItems: QueryList; + @ViewChild('menu') public menuElement: ElementRef; + public visibleMenuItems: ContextMenuItemDirective[] = []; -public links: ILinkConfig[] = []; -public item: any; -public event: MouseEvent | KeyboardEvent; -private subscription: Subscription = new Subscription(); + public links: ILinkConfig[] = []; + public item: any; + public event: MouseEvent | KeyboardEvent; + private subscription: Subscription = new Subscription(); -constructor( - private _contextMenuService: ContextMenuService, - private changeDetector: ChangeDetectorRef, - private elementRef: ElementRef, - @Optional() - @Inject(CONTEXT_MENU_OPTIONS) private options: IContextMenuOptions, -) { - if (options) { - this.autoFocus = options.autoFocus; - this.useBootstrap4 = options.useBootstrap4; - this.highlightParentItems = options.highlightParentItems; + constructor( + private _contextMenuService: ContextMenuService, + private changeDetector: ChangeDetectorRef, + private elementRef: ElementRef, + @Optional() + @Inject(CONTEXT_MENU_OPTIONS) private options: IContextMenuOptions, + ) { + if (options) { + this.autoFocus = options.autoFocus; + this.useBootstrap4 = options.useBootstrap4; + this.highlightParentItems = options.highlightParentItems; + } + this.subscription.add(_contextMenuService.show.subscribe(menuEvent => { + this.onMenuEvent(menuEvent); + })); } - this.subscription.add(_contextMenuService.show.subscribe(menuEvent => { - this.onMenuEvent(menuEvent); - })); -} -public ngOnDestroy(): void { - this.subscription.unsubscribe(); -} - -public onMenuEvent(menuEvent: IContextMenuClickEvent): void { - if (this.disabled) { - return; + public ngOnDestroy(): void { + this.subscription.unsubscribe(); } - const { contextMenu, event, item } = menuEvent; - if (contextMenu && contextMenu !== this) { - return; + + public onMenuEvent(menuEvent: IContextMenuClickEvent): void { + if (this.disabled) { + return; + } + const { contextMenu, event, item } = menuEvent; + if (contextMenu && contextMenu !== this) { + return; + } + this.event = event; + this.item = item; + this.setVisibleMenuItems(); + this._contextMenuService.openContextMenu({ ...menuEvent, menuItems: this.visibleMenuItems, menuClass: this.menuClass }); + this._contextMenuService.close.asObservable().pipe(first()).subscribe(closeEvent => this.close.emit(closeEvent)); + this.open.next(menuEvent); } - this.event = event; - this.item = item; - this.setVisibleMenuItems(); - this._contextMenuService.openContextMenu({ ...menuEvent, menuItems: this.visibleMenuItems, menuClass: this.menuClass }); - this._contextMenuService.close.asObservable().pipe(first()).subscribe(closeEvent => this.close.emit(closeEvent)); - this.open.next(menuEvent); -} -public isMenuItemVisible(menuItem: ContextMenuItemDirective): boolean { - return this.evaluateIfFunction(menuItem.visible); -} + public isMenuItemVisible(menuItem: ContextMenuItemDirective): boolean { + return this.evaluateIfFunction(menuItem.visible); + } -public setVisibleMenuItems(): void { - this.visibleMenuItems = this.menuItems.filter(menuItem => this.isMenuItemVisible(menuItem)); -} + public setVisibleMenuItems(): void { + this.visibleMenuItems = this.menuItems.filter(menuItem => this.isMenuItemVisible(menuItem)); + } -public evaluateIfFunction(value: any): any { - if (value instanceof Function) { - return value(this.item); + public evaluateIfFunction(value: any): any { + if (value instanceof Function) { + return value(this.item); + } + return value; } - return value; -} } From 1fae08bda2c07081bfcf5eadc3b04278f13a63bc Mon Sep 17 00:00:00 2001 From: Arno Date: Thu, 10 Oct 2019 09:50:43 +0200 Subject: [PATCH 3/4] Package files and README updated to reflect new version --- README.md | 15 +++++++++++++++ package.json | 2 +- projects/ngx-contextmenu/package.json | 8 ++++---- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 676073e..05b1006 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,21 @@ # ngx-contextmenu A context menu built with Angular (6) inspired by [ui.bootstrap.contextMenu](https://github.com/Templarian/ui.bootstrap.contextMenu). Bootstrap classes are included in the markup, but there is no explicit dependency on Bootstrap. [Demo](https://isaacplmann.github.io/ngx-contextmenu/) [Stackblitz example](https://stackblitz.com/edit/ngx-contextmenu-example) +Originally created by [Isaac Mann](isaacplmann@gmail.com), just slightly edited by me. +Changes are: +- Option to enable trace of selection path + +When selecting a subMenu item the parent item will stay highlighted. + +Usage: +In your module import set the highlightParentItems option to true: + +```js + ContextMenuModule.forRoot({ useBootstrap4: true }), +``` + +ToDo: +The component seems to "remember" the last selected item, so when the menu is closed and reopend the last selected menu item is still highlighted. ## Installation diff --git a/package.json b/package.json index e4c6be2..de804c5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ngx-contextmenu-platform", - "version": "5.2.0", + "version": "5.2.1", "description": "An Angular component to show a context menu on an arbitrary component", "keywords": [ "angular2", diff --git a/projects/ngx-contextmenu/package.json b/projects/ngx-contextmenu/package.json index 8210a74..1533496 100644 --- a/projects/ngx-contextmenu/package.json +++ b/projects/ngx-contextmenu/package.json @@ -1,6 +1,6 @@ { - "name": "ngx-contextmenu", - "version": "5.2.0", + "name": "@avdbrink/ngx-contextmenu", + "version": "5.2.1", "description": "An Angular component to show a context menu on an arbitrary component", "keywords": [ "angular2", @@ -11,11 +11,11 @@ "ng2", "ng2-contextmenu" ], - "author": "Isaac Mann ", + "author": "Arno van den Brink ", "license": "MIT", "repository": { "type": "git", - "url": "git+ssh://git@github.com/isaacplmann/ngx-contextmenu.git" + "url": "git+ssh://git@github.com/avdbrink/ngx-contextmenu.git" }, "peerDependencies": { "@angular/cdk": ">=6.0.0 <9.0.0", From 67df93475e9e0e1ee5f7c557f1881a7706610add Mon Sep 17 00:00:00 2001 From: Arno Date: Thu, 10 Oct 2019 09:51:53 +0200 Subject: [PATCH 4/4] Typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 05b1006..43f2c81 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Usage: In your module import set the highlightParentItems option to true: ```js - ContextMenuModule.forRoot({ useBootstrap4: true }), + ContextMenuModule.forRoot({ highlightParentItems: true }), ``` ToDo: