UNPKG

@perfectmemory/ngx-contextmenu

Version:

A context menu component for Angular

957 lines (943 loc) 37.9 kB
import * as i1 from '@angular/cdk/overlay'; import { OverlayModule, FullscreenOverlayContainer, OverlayContainer } from '@angular/cdk/overlay'; import * as i2 from '@angular/common'; import { CommonModule } from '@angular/common'; import * as i0 from '@angular/core'; import { EventEmitter, Output, Input, Optional, Directive, Injectable, DOCUMENT, HostListener, HostBinding, ViewChildren, Inject, ChangeDetectionStrategy, Component, ElementRef, ContentChildren, ViewEncapsulation, NgModule } from '@angular/core'; import { FocusKeyManager } from '@angular/cdk/a11y'; import { Subscription } from 'rxjs'; import { ComponentPortal } from '@angular/cdk/portal'; const evaluateIfFunction = (value, item) => { if (value instanceof Function) { return value(item); } return !!value; }; class ContextMenuItemDirective { /** * Is this menu item disabled */ set disabled(disabled) { this.#disabled = disabled; } get disabled() { return (this.passive || this.divider || evaluateIfFunction(this.#disabled, this.value)); } #disabled; constructor(template) { this.template = template; /** * True to make this menu item a divider */ this.divider = false; /** * Is this menu item passive (for title) */ this.passive = false; /** * Is this menu item visible */ this.visible = true; /** * Emits event and item */ this.execute = new EventEmitter(); this.#disabled = false; } /** * @internal */ triggerExecute(event, value) { if (evaluateIfFunction(this.#disabled, value)) { return; } this.execute.emit({ event, value }); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: ContextMenuItemDirective, deps: [{ token: i0.TemplateRef, optional: true }], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.0.3", type: ContextMenuItemDirective, isStandalone: false, selector: "[contextMenuItem]", inputs: { subMenu: "subMenu", divider: "divider", disabled: "disabled", passive: "passive", visible: "visible" }, outputs: { execute: "execute" }, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: ContextMenuItemDirective, decorators: [{ type: Directive, args: [{ selector: '[contextMenuItem]', standalone: false, }] }], ctorParameters: () => [{ type: i0.TemplateRef, decorators: [{ type: Optional }] }], propDecorators: { subMenu: [{ type: Input }], divider: [{ type: Input }], disabled: [{ type: Input }], passive: [{ type: Input }], visible: [{ type: Input }], execute: [{ type: Output }] } }); class ContextMenuContentItemDirective { get nativeElement() { return this.elementRef.nativeElement; } constructor(elementRef) { this.elementRef = elementRef; } /** * @implements FocusableOption */ focus(origin) { this.elementRef.nativeElement.focus(); } /** * @implements FocusableOption */ get disabled() { return evaluateIfFunction(this.contextMenuContentItem?.disabled, this.contextMenuContentItem?.value); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: ContextMenuContentItemDirective, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.0.3", type: ContextMenuContentItemDirective, isStandalone: false, selector: "[contextMenuContentItem]", inputs: { contextMenuContentItem: "contextMenuContentItem" }, host: { classAttribute: "ngx-context-menu-item" }, exportAs: ["contextMenuContentItem"], ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: ContextMenuContentItemDirective, decorators: [{ type: Directive, args: [{ selector: '[contextMenuContentItem]', exportAs: 'contextMenuContentItem', host: { class: 'ngx-context-menu-item', }, standalone: false, }] }], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { contextMenuContentItem: [{ type: Input }] } }); class ContextMenuOverlaysService { constructor() { /** * Emits when all context menus are closed */ this.allClosed = new EventEmitter(); this.stack = []; } /** * Add an item to the stack */ push(value) { this.stack.push(value); } /** * Clear the whole stack */ closeAll() { this.stack.forEach((item) => this.dispose(item)); this.stack = []; this.allClosed.emit(); } isEmpty() { return this.stack.length === 0; } dispose(overlayRef) { overlayRef.detach(); overlayRef.dispose(); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: ContextMenuOverlaysService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: ContextMenuOverlaysService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: ContextMenuOverlaysService, decorators: [{ type: Injectable, args: [{ providedIn: 'root', }] }] }); /** * For testing purpose only */ const TESTING_WRAPPER = { FocusKeyManager, }; class ContextMenuContentComponent { // TODO: should be private but issue in spec with NullInjectorError: No provider for ElementRef! constructor(elementRef, document, contextMenuOverlaysService) { this.elementRef = elementRef; this.document = document; this.contextMenuOverlaysService = contextMenuOverlaysService; /** * The list of `IContextMenuItemDirective` that represent each menu items */ this.menuDirectives = []; /** * A CSS class to apply a theme to the the menu */ this.menuClass = ''; /** * Emit when a menu item is selected */ this.execute = new EventEmitter(); /** * Emit when all menus is closed */ this.close = new EventEmitter(); /** * Accessibility * * @internal */ this.role = 'menu'; /** * Accessibility * * @internal */ this.ariaOrientation = 'vertical'; this.subscription = new Subscription(); } /** * @internal */ ngAfterViewInit() { this.setupDirectives(); this.activeElement = this.document.activeElement; this.elementRef.nativeElement.focus(); } /** * @internal */ ngOnDestroy() { this.activeElement?.focus(); this.subscription.unsubscribe(); this.focusKeyManager?.destroy(); } /** * @internal */ onKeyArrowDownOrUp(event) { this.focusKeyManager?.onKeydown(event); } /** * @internal */ onKeyArrowRight(event) { this.openCloseActiveItemSubMenu(this.dir !== 'rtl', event); } /** * @internal */ onKeyArrowLeft(event) { this.openCloseActiveItemSubMenu(this.dir === 'rtl', event); } /** * @internal */ onKeyEnterOrSpace(event) { if (!this.focusKeyManager?.activeItem) { return; } this.onMenuItemSelect(this.focusKeyManager.activeItem, event); } /** * @internal */ onClickOrRightClick(event) { if (event.type === 'click' && event.button === 2) { return; } if (this.elementRef.nativeElement.contains(event.target)) { return; } this.contextMenuOverlaysService.closeAll(); } /** * @internal */ hideSubMenus() { this.menuDirectives.forEach((menuDirective) => { menuDirective.subMenu?.hide(); }); } /** * @internal */ stopEvent(event) { event.stopPropagation(); } /** * @internal */ isMenuItemDisabled(menuItem) { return evaluateIfFunction(menuItem.disabled, this.value); } /** * @internal */ isMenuItemVisible(menuItem) { return evaluateIfFunction(menuItem.contextMenuContentItem?.visible, this.value); } /** * @internal */ openSubMenu(subMenu, event) { if (!subMenu) { return; } if (this.focusKeyManager?.activeItemIndex === null) { return; } const anchorContextMenuContentItem = this.contextMenuContentItems?.toArray()[this.focusKeyManager?.activeItemIndex ?? 0]; const anchorElement = anchorContextMenuContentItem && anchorContextMenuContentItem.nativeElement; if (anchorElement && event instanceof KeyboardEvent) { subMenu.show({ anchoredTo: 'element', anchorElement, value: this.value, parentContextMenu: this, }); return; } if (subMenu.isOpen) { return; } if (event.currentTarget) { subMenu.show({ anchoredTo: 'element', anchorElement: event.currentTarget, value: this.value, parentContextMenu: this, }); return; } subMenu.show({ anchoredTo: 'position', x: event.clientX, y: event.clientY, value: this.value, }); } /** * @internal */ onMenuItemSelect(menuContentItem, event) { this.cancelEvent(event); this.openSubMenu(menuContentItem.contextMenuContentItem?.subMenu, event); if (!menuContentItem.contextMenuContentItem?.subMenu) { this.triggerExecute(menuContentItem, event); } } triggerExecute(menuItem, event) { menuItem.contextMenuContentItem?.triggerExecute(event, this.value); } setupDirectives() { this.menuDirectives.forEach((menuDirective) => { menuDirective.value = this.value; this.subscription.add(menuDirective.execute.subscribe((event) => { this.execute.emit({ ...event, menuDirective }); })); }); this.focusKeyManager = new TESTING_WRAPPER.FocusKeyManager(this.contextMenuContentItems).withWrap(); } openCloseActiveItemSubMenu(open, event) { if (open) { this.openActiveItemSubMenu(event); return; } this.closeActiveItemSubMenu(event); } openActiveItemSubMenu(event) { if (this.focusKeyManager?.activeItemIndex === null) { return; } this.cancelEvent(event); if (this.focusKeyManager?.activeItem) { this.openSubMenu(this.focusKeyManager.activeItem?.contextMenuContentItem?.subMenu, event); } } closeActiveItemSubMenu(event) { this.hideSubMenus(); if (!this.focusKeyManager?.activeItemIndex) { return; } this.close.emit(); this.cancelEvent(event); } cancelEvent(event) { if (!event || !event.target) { return; } const target = event.target; if (['INPUT', 'TEXTAREA', 'SELECT'].includes(target.tagName) || target.isContentEditable) { return; } event.preventDefault(); event.stopPropagation(); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: ContextMenuContentComponent, deps: [{ token: i0.ElementRef }, { token: DOCUMENT }, { token: ContextMenuOverlaysService }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.0.3", type: ContextMenuContentComponent, isStandalone: false, selector: "context-menu-content", inputs: { menuDirectives: "menuDirectives", value: "value", dir: "dir", parentContextMenu: "parentContextMenu", menuClass: "menuClass" }, outputs: { execute: "execute", close: "close" }, host: { attributes: { "tabindex": "0", "role": "dialog" }, listeners: { "keydown.ArrowDown": "onKeyArrowDownOrUp($event)", "keydown.ArrowUp": "onKeyArrowDownOrUp($event)", "keydown.ArrowRight": "onKeyArrowRight($event)", "keydown.ArrowLeft": "onKeyArrowLeft($event)", "window:keydown.Enter": "onKeyEnterOrSpace($event)", "window:keydown.Space": "onKeyEnterOrSpace($event)", "document:click": "onClickOrRightClick($event)" }, properties: { "attr.dir": "this.dir", "class": "this.menuClass", "attr.role": "this.role", "aria-orientation": "this.ariaOrientation" }, classAttribute: "ngx-contextmenu" }, viewQueries: [{ propertyName: "contextMenuContentItems", predicate: ContextMenuContentItemDirective, descendants: true, read: ContextMenuContentItemDirective }], ngImport: i0, template: "@for (menuDirective of menuDirectives; track menuDirective; let i = $index) {\n@if (menuDirective.divider) {\n<hr\n [contextMenuContentItem]=\"menuDirective\"\n role=\"separator\"\n (mouseenter)=\"hideSubMenus()\"\n/>\n} @if (!menuDirective.divider && !menuDirective.passive) {\n<button\n #buttonContextMenuContentItem=\"contextMenuContentItem\"\n [contextMenuContentItem]=\"menuDirective\"\n type=\"button\"\n role=\"menuitem\"\n [attr.aria-haspopup]=\"!!menuDirective.subMenu ? 'menu' : null\"\n [class.ngx-contextmenu--parent-menu]=\"!!menuDirective.subMenu\"\n [attr.disabled]=\"isMenuItemDisabled(menuDirective) ? 'disabled' : null\"\n (click)=\"onMenuItemSelect(buttonContextMenuContentItem, $event)\"\n (mouseenter)=\"\n hideSubMenus();\n openSubMenu(\n buttonContextMenuContentItem.contextMenuContentItem?.subMenu,\n $event\n )\n \"\n>\n <ng-template\n [ngTemplateOutlet]=\"menuDirective.template\"\n [ngTemplateOutletContext]=\"{ $implicit: value }\"\n ></ng-template>\n</button>\n} @if (!menuDirective.divider && menuDirective.passive) {\n<span\n [contextMenuContentItem]=\"menuDirective\"\n class=\"ngx-contextmenu-item--passive\"\n role=\"menuitem\"\n [attr.aria-disabled]=\"isMenuItemDisabled(menuDirective)\"\n (click)=\"stopEvent($event)\"\n (contextmenu)=\"stopEvent($event)\"\n [class.disabled]=\"isMenuItemDisabled(menuDirective)\"\n (mouseenter)=\"isMenuItemDisabled(menuDirective) ? null : hideSubMenus()\"\n>\n <ng-template\n [ngTemplateOutlet]=\"menuDirective.template\"\n [ngTemplateOutletContext]=\"{ $implicit: value }\"\n ></ng-template>\n</span>\n} }\n", dependencies: [{ kind: "directive", type: i2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: ContextMenuContentItemDirective, selector: "[contextMenuContentItem]", inputs: ["contextMenuContentItem"], exportAs: ["contextMenuContentItem"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: ContextMenuContentComponent, decorators: [{ type: Component, args: [{ selector: 'context-menu-content', changeDetection: ChangeDetectionStrategy.OnPush, host: { tabindex: '0', role: 'dialog', class: 'ngx-contextmenu', }, standalone: false, template: "@for (menuDirective of menuDirectives; track menuDirective; let i = $index) {\n@if (menuDirective.divider) {\n<hr\n [contextMenuContentItem]=\"menuDirective\"\n role=\"separator\"\n (mouseenter)=\"hideSubMenus()\"\n/>\n} @if (!menuDirective.divider && !menuDirective.passive) {\n<button\n #buttonContextMenuContentItem=\"contextMenuContentItem\"\n [contextMenuContentItem]=\"menuDirective\"\n type=\"button\"\n role=\"menuitem\"\n [attr.aria-haspopup]=\"!!menuDirective.subMenu ? 'menu' : null\"\n [class.ngx-contextmenu--parent-menu]=\"!!menuDirective.subMenu\"\n [attr.disabled]=\"isMenuItemDisabled(menuDirective) ? 'disabled' : null\"\n (click)=\"onMenuItemSelect(buttonContextMenuContentItem, $event)\"\n (mouseenter)=\"\n hideSubMenus();\n openSubMenu(\n buttonContextMenuContentItem.contextMenuContentItem?.subMenu,\n $event\n )\n \"\n>\n <ng-template\n [ngTemplateOutlet]=\"menuDirective.template\"\n [ngTemplateOutletContext]=\"{ $implicit: value }\"\n ></ng-template>\n</button>\n} @if (!menuDirective.divider && menuDirective.passive) {\n<span\n [contextMenuContentItem]=\"menuDirective\"\n class=\"ngx-contextmenu-item--passive\"\n role=\"menuitem\"\n [attr.aria-disabled]=\"isMenuItemDisabled(menuDirective)\"\n (click)=\"stopEvent($event)\"\n (contextmenu)=\"stopEvent($event)\"\n [class.disabled]=\"isMenuItemDisabled(menuDirective)\"\n (mouseenter)=\"isMenuItemDisabled(menuDirective) ? null : hideSubMenus()\"\n>\n <ng-template\n [ngTemplateOutlet]=\"menuDirective.template\"\n [ngTemplateOutletContext]=\"{ $implicit: value }\"\n ></ng-template>\n</span>\n} }\n" }] }], ctorParameters: () => [{ type: i0.ElementRef }, { type: Document, decorators: [{ type: Inject, args: [DOCUMENT] }] }, { type: ContextMenuOverlaysService }], propDecorators: { menuDirectives: [{ type: Input }], value: [{ type: Input }], dir: [{ type: Input }, { type: HostBinding, args: ['attr.dir'] }], parentContextMenu: [{ type: Input }], menuClass: [{ type: HostBinding, args: ['class'] }, { type: Input }], execute: [{ type: Output }], close: [{ type: Output }], contextMenuContentItems: [{ type: ViewChildren, args: [ContextMenuContentItemDirective, { read: ContextMenuContentItemDirective, }] }], role: [{ type: HostBinding, args: ['attr.role'] }], ariaOrientation: [{ type: HostBinding, args: ['aria-orientation'] }], onKeyArrowDownOrUp: [{ type: HostListener, args: ['keydown.ArrowDown', ['$event']] }, { type: HostListener, args: ['keydown.ArrowUp', ['$event']] }], onKeyArrowRight: [{ type: HostListener, args: ['keydown.ArrowRight', ['$event']] }], onKeyArrowLeft: [{ type: HostListener, args: ['keydown.ArrowLeft', ['$event']] }], onKeyEnterOrSpace: [{ type: HostListener, args: ['window:keydown.Enter', ['$event']] }, { type: HostListener, args: ['window:keydown.Space', ['$event']] }], onClickOrRightClick: [{ type: HostListener, args: ['document:click', ['$event']] }] } }); const getPositionsToXY = (dir = 'ltr') => { if (dir === 'ltr') { return [ { originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', }, { originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom', }, { originX: 'end', originY: 'top', overlayX: 'start', overlayY: 'top', }, { originX: 'start', originY: 'top', overlayX: 'end', overlayY: 'top', }, { originX: 'end', originY: 'center', overlayX: 'start', overlayY: 'center', }, { originX: 'start', originY: 'center', overlayX: 'end', overlayY: 'center', }, ]; } return [ { originX: 'end', originY: 'bottom', overlayX: 'end', overlayY: 'top', }, { originX: 'end', originY: 'top', overlayX: 'end', overlayY: 'bottom', }, { originX: 'start', originY: 'top', overlayX: 'end', overlayY: 'top', }, { originX: 'end', originY: 'top', overlayX: 'start', overlayY: 'top', }, { originX: 'start', originY: 'center', overlayX: 'end', overlayY: 'center', }, { originX: 'end', originY: 'center', overlayX: 'start', overlayY: 'center', }, ]; }; const getPositionsToAnchorElement = (dir = 'ltr') => { if (dir === 'ltr') { return [ { originX: 'end', originY: 'top', overlayX: 'start', overlayY: 'top', }, { originX: 'start', originY: 'top', overlayX: 'end', overlayY: 'top', }, { originX: 'end', originY: 'bottom', overlayX: 'start', overlayY: 'bottom', }, { originX: 'start', originY: 'bottom', overlayX: 'end', overlayY: 'bottom', }, ]; } else { return [ { originX: 'start', originY: 'top', overlayX: 'end', overlayY: 'top', }, { originX: 'end', originY: 'top', overlayX: 'start', overlayY: 'top', }, { originX: 'start', originY: 'bottom', overlayX: 'end', overlayY: 'bottom', }, { originX: 'end', originY: 'bottom', overlayX: 'start', overlayY: 'bottom', }, ]; } }; class ContextMenuComponent { /** * Returns true if the context menu is opened, false otherwise */ get isOpen() { return this.#isOpen; } #isOpen; constructor(overlay, scrollStrategy, contextMenuOverlaysService) { this.overlay = overlay; this.scrollStrategy = scrollStrategy; this.contextMenuOverlaysService = contextMenuOverlaysService; /** * A CSS class to apply to the context menu overlay, ideal for theming and custom styling */ this.menuClass = ''; /** * Disable the whole context menu */ this.disabled = false; /** * Emit when the menu is opened */ this.open = new EventEmitter(); /** * Emit when the menu is closed */ this.close = new EventEmitter(); /** * @internal */ this.visibleMenuItems = []; this.subscriptions = new Subscription(); this.#isOpen = false; } /** * @internal */ ngOnInit() { const subscription = this.contextMenuOverlaysService.allClosed.subscribe(() => { this.#isOpen = false; }); this.subscriptions.add(subscription); } /** * @internal */ ngOnDestroy() { this.subscriptions.unsubscribe(); } /** * @internal */ show(event) { if (this.disabled) { return; } this.value = event.value; this.setVisibleMenuItems(); this.openContextMenu({ ...event, menuItemDirectives: this.visibleMenuItems, menuClass: this.menuClass, dir: this.dir, }); this.open.next(event); } /** * @internal */ hide() { this.contextMenuContentComponent?.menuDirectives.forEach((menuDirective) => { menuDirective.subMenu?.hide(); }); this.overlayRef?.detach(); this.overlayRef?.dispose(); this.#isOpen = false; } /** * @internal */ openContextMenu(context) { let positionStrategy; if (context.anchoredTo === 'position') { positionStrategy = this.overlay .position() .flexibleConnectedTo({ x: context.x, y: context.y, }) .withPositions(getPositionsToXY(context.dir)); } else { const { anchorElement, parentContextMenu } = context; positionStrategy = this.overlay .position() .flexibleConnectedTo(new ElementRef(anchorElement)) .withPositions(getPositionsToAnchorElement(parentContextMenu.dir)); } this.overlayRef = this.overlay.create({ positionStrategy, panelClass: 'ngx-contextmenu-overlay', scrollStrategy: this.scrollStrategy.close(), }); this.contextMenuOverlaysService.push(this.overlayRef); this.attachContextMenu(context); this.#isOpen = true; } attachContextMenu(context) { const { value, menuItemDirectives } = context; const contextMenuContentRef = this.overlayRef?.attach(new ComponentPortal(ContextMenuContentComponent)); const contextMenuContentComponent = contextMenuContentRef?.instance; if (!contextMenuContentComponent) { return; } this.contextMenuContentComponent = contextMenuContentComponent; contextMenuContentComponent.value = value; contextMenuContentComponent.menuDirectives = menuItemDirectives; contextMenuContentComponent.menuClass = this.getMenuClass(context); contextMenuContentComponent.dir = this.getDir(context); const closeSubscription = contextMenuContentComponent.close.subscribe(() => { this.overlayRef?.detach(); this.overlayRef?.dispose(); }); const executeSubscription = contextMenuContentComponent.execute.subscribe(() => { this.contextMenuOverlaysService.closeAll(); }); contextMenuContentRef.onDestroy(() => { this.close.emit(); closeSubscription.unsubscribe(); executeSubscription.unsubscribe(); }); contextMenuContentRef.changeDetectorRef.detectChanges(); } getMenuClass(event) { return (event.menuClass || (event.anchoredTo === 'element' && event?.parentContextMenu?.menuClass) || ''); } getDir(event) { return (event.dir || (event.anchoredTo === 'element' && event?.parentContextMenu?.dir) || undefined); } isMenuItemVisible(menuItem) { return evaluateIfFunction(menuItem.visible, this.value); } setVisibleMenuItems() { this.visibleMenuItems = this.menuItems?.filter((menuItem) => this.isMenuItemVisible(menuItem)) ?? []; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: ContextMenuComponent, deps: [{ token: i1.Overlay }, { token: i1.ScrollStrategyOptions }, { token: ContextMenuOverlaysService }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.0.3", type: ContextMenuComponent, isStandalone: false, selector: "context-menu", inputs: { menuClass: "menuClass", disabled: "disabled", dir: "dir" }, outputs: { open: "open", close: "close" }, queries: [{ propertyName: "menuItems", predicate: ContextMenuItemDirective }], ngImport: i0, template: '', isInline: true, encapsulation: i0.ViewEncapsulation.None }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: ContextMenuComponent, decorators: [{ type: Component, args: [{ encapsulation: ViewEncapsulation.None, selector: 'context-menu', template: '', standalone: false, }] }], ctorParameters: () => [{ type: i1.Overlay }, { type: i1.ScrollStrategyOptions }, { type: ContextMenuOverlaysService }], propDecorators: { menuClass: [{ type: Input }], disabled: [{ type: Input }], dir: [{ type: Input }], open: [{ type: Output }], close: [{ type: Output }], menuItems: [{ type: ContentChildren, args: [ContextMenuItemDirective] }] } }); class ContextMenuDirective { /** * Return true if the context menu is opened, false otherwise */ get isOpen() { return this.contextMenu?.isOpen ?? false; } constructor(elementRef, contextMenuOverlaysService) { this.elementRef = elementRef; this.contextMenuOverlaysService = contextMenuOverlaysService; /** * The directive must have a tabindex for being accessible */ this.tabindex = '0'; } /** * @internal */ onContextMenu(event) { if (!this.canOpen()) { return; } this.closeAll(); this.contextMenu?.show({ anchoredTo: 'position', x: event.clientX, y: event.clientY, value: this.contextMenuValue, }); event.preventDefault(); event.stopPropagation(); } /** * @internal */ onKeyArrowEscape() { this.close(); } /** * Programmatically open the context menu */ open(event) { if (!this.canOpen()) { return; } if (event instanceof MouseEvent) { this.onContextMenu(event); return; } const { x, y, height } = this.elementRef.nativeElement.getBoundingClientRect(); this.contextMenu?.show({ anchoredTo: 'position', x, y: y + height, value: this.contextMenuValue, }); } /** * Programmatically close the context menu */ close() { this.contextMenu?.hide(); } closeAll() { this.contextMenuOverlaysService.closeAll(); } canOpen() { return (this.contextMenu && !this.contextMenu.disabled) ?? false; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: ContextMenuDirective, deps: [{ token: i0.ElementRef }, { token: ContextMenuOverlaysService }], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.0.3", type: ContextMenuDirective, isStandalone: false, selector: "[contextMenu]", inputs: { contextMenuValue: "contextMenuValue", contextMenu: "contextMenu", tabindex: "tabindex" }, host: { attributes: { "role": "button", "attr.aria-haspopup": "menu" }, listeners: { "contextmenu": "onContextMenu($event)", "window:contextmenu": "onKeyArrowEscape()", "window:keydown.Escape": "onKeyArrowEscape()" }, properties: { "attr.tabindex": "this.tabindex", "attr.aria-expanded": "this.isOpen" } }, exportAs: ["ngxContextMenu"], ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: ContextMenuDirective, decorators: [{ type: Directive, args: [{ selector: '[contextMenu]', exportAs: 'ngxContextMenu', host: { role: 'button', 'attr.aria-haspopup': 'menu', }, standalone: false, }] }], ctorParameters: () => [{ type: i0.ElementRef }, { type: ContextMenuOverlaysService }], propDecorators: { contextMenuValue: [{ type: Input }], contextMenu: [{ type: Input }], tabindex: [{ type: Input }, { type: HostBinding, args: ['attr.tabindex'] }], isOpen: [{ type: HostBinding, args: ['attr.aria-expanded'] }], onContextMenu: [{ type: HostListener, args: ['contextmenu', ['$event']] }], onKeyArrowEscape: [{ type: HostListener, args: ['window:contextmenu'] }, { type: HostListener, args: ['window:keydown.Escape'] }] } }); class ContextMenuModule { static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: ContextMenuModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); } static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.0.3", ngImport: i0, type: ContextMenuModule, declarations: [ContextMenuComponent, ContextMenuContentComponent, ContextMenuContentItemDirective, ContextMenuDirective, ContextMenuItemDirective], imports: [CommonModule, OverlayModule], exports: [ContextMenuDirective, ContextMenuComponent, ContextMenuItemDirective] }); } static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: ContextMenuModule, providers: [ { provide: OverlayContainer, useClass: FullscreenOverlayContainer }, ], imports: [CommonModule, OverlayModule] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: ContextMenuModule, decorators: [{ type: NgModule, args: [{ declarations: [ ContextMenuComponent, ContextMenuContentComponent, ContextMenuContentItemDirective, ContextMenuDirective, ContextMenuItemDirective, ], providers: [ { provide: OverlayContainer, useClass: FullscreenOverlayContainer }, ], exports: [ ContextMenuDirective, ContextMenuComponent, ContextMenuItemDirective, ], imports: [CommonModule, OverlayModule], }] }] }); /** * Programmatically open a ContextMenuComponent to a X/Y position */ class ContextMenuService { constructor(contextMenuOverlaysService) { this.contextMenuOverlaysService = contextMenuOverlaysService; } /** * Show the given `ContextMenuComponent` at a specified X/Y position */ show(contextMenu, options = { x: 0, y: 0 }) { contextMenu.show({ anchoredTo: 'position', value: options.value, x: options.x, y: options.y, }); } /** * Close all open `ContextMenuComponent` */ closeAll() { this.contextMenuOverlaysService.closeAll(); } /** * Return true if any `ContextMenuComponent` is open */ hasOpenMenu() { return !this.contextMenuOverlaysService.isEmpty(); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: ContextMenuService, deps: [{ token: ContextMenuOverlaysService }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: ContextMenuService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.3", ngImport: i0, type: ContextMenuService, decorators: [{ type: Injectable, args: [{ providedIn: 'root', }] }], ctorParameters: () => [{ type: ContextMenuOverlaysService }] }); /* * Public API Surface of @perfectmemory/ngx-contextmenu */ /** * Generated bundle index. Do not edit. */ export { ContextMenuComponent, ContextMenuDirective, ContextMenuItemDirective, ContextMenuModule, ContextMenuService }; //# sourceMappingURL=perfectmemory-ngx-contextmenu.mjs.map