UNPKG

@ctrl/ngx-rightclick

Version:

GitHub Buttons for Angular. Star, Like, Follow and more

439 lines (430 loc) 15.2 kB
import { ElementRef, ɵɵdefineInjectable, ɵɵinject, INJECTOR, Injectable, Injector, EventEmitter, Directive, Input, Output, HostListener, Component, NgModule } from '@angular/core'; import { Overlay, ScrollStrategyOptions, OverlayModule } from '@angular/cdk/overlay'; import { ComponentPortal, PortalModule } from '@angular/cdk/portal'; import { BehaviorSubject, Subject } from 'rxjs'; import { filter, take } from 'rxjs/operators'; class MenuPackage { constructor(menu, context) { this.menu = menu; this.context = context; } } class MenuInjector { constructor(activeContextMenu, parentInjector, context) { this.activeContextMenu = activeContextMenu; this.parentInjector = parentInjector; this.context = context; this.menuContext = new MenuPackage(activeContextMenu, context); } get(token, notFoundValue, flags) { if (token === MenuPackage) { return this.menuContext; } return this.parentInjector.get(token, notFoundValue, flags); } } class ContextMenuService { constructor(overlay, scrollStrategy, injector) { this.overlay = overlay; this.scrollStrategy = scrollStrategy; this.injector = injector; this.menus = []; this.id = 0; } /** * * @param $event triggering event * @param menuComponent the component to be shown * @param submenu is a menu within a menu * @param level if submenu, what level */ show($event, menuComponent, context, menuClose, menuAction, submenu = false, level) { let target; if (!submenu) { this.closeAll(); target = { getBoundingClientRect: () => ({ bottom: $event.clientY, height: 0, left: $event.clientX, right: $event.clientX, top: $event.clientY, width: 0, }), }; } else { // close other submenus this.closeAll(undefined, level); target = $event.target; } const el = new ElementRef(target); const positionStrategy = this.overlay .position() .flexibleConnectedTo(el) .withFlexibleDimensions(false); if (!submenu) { positionStrategy.withPositions([ { 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', }, ]); } else { positionStrategy.withPositions([ { 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', }, ]); } const t = { submenu, id: this.id++, isMenuHovered: new BehaviorSubject(false), isTriggerHovered: new BehaviorSubject(false), }; const menuInjector = new MenuInjector(t, this.injector, context); const componentPortal = new ComponentPortal(menuComponent, undefined, menuInjector); const overlayRef = this.overlay.create({ positionStrategy, panelClass: 'ngx-contextmenu', scrollStrategy: this.scrollStrategy.close(), }); const component = overlayRef.attach(componentPortal); const res = Object.assign(Object.assign({ overlayRef, component }, t), { menuClose, menuAction }); this.menus.push(res); return res; } getCurrentLevel() { return this.menus.length; } closeAll(context, idx = 0) { for (let index = idx; index < this.menus.length; index++) { const menu = this.menus[index]; this.destroyMenu(menu, context); } this.menus.splice(idx, this.menus.length); } destroyMenu(menu, context) { menu.component.instance._state = 'exit'; if (menu.component.instance.lazy) { menu.component.instance._animationDone .pipe(filter((event) => event.toState === 'exit'), take(1)) .subscribe(() => { menu.overlayRef.detach(); menu.overlayRef.dispose(); }); } else { menu.overlayRef.detach(); menu.overlayRef.dispose(); } if (context) { menu.menuAction.next(context); } menu.menuClose.next(); } close(menu, menuIndex, context) { this.destroyMenu(menu, context); this.menus.splice(menuIndex, 1); } checkOutsideClick($event) { for (const m of this.menus) { const clickedInside = m.component.location.nativeElement.contains($event.target); if (clickedInside) { $event.preventDefault(); $event.stopPropagation(); return; } } this.closeAll(); } closeSubMenu(id) { const menuIndex = this.menus.findIndex(n => n.id === id); if (menuIndex === -1 || menuIndex !== this.menus.length - 1) { return; } // make sure we can close the current menu const menu = this.menus[menuIndex]; if (menu.isMenuHovered.getValue() || menu.isTriggerHovered.getValue()) { return; } // close all menus up if possible for (let index = this.menus.length - 1; index >= 1; index--) { const m = this.menus[index]; if (!m.isMenuHovered.getValue() && !m.isTriggerHovered.getValue()) { this.close(m, index); } else { return; } } } } ContextMenuService.ɵprov = ɵɵdefineInjectable({ factory: function ContextMenuService_Factory() { return new ContextMenuService(ɵɵinject(Overlay), ɵɵinject(ScrollStrategyOptions), ɵɵinject(INJECTOR)); }, token: ContextMenuService, providedIn: "root" }); ContextMenuService.decorators = [ { type: Injectable, args: [{ providedIn: 'root' },] } ]; ContextMenuService.ctorParameters = () => [ { type: Overlay }, { type: ScrollStrategyOptions }, { type: Injector } ]; class ContextMenuTriggerDirective { constructor(contextMenuService) { this.contextMenuService = contextMenuService; this.holdToDisplay = 1000; this.menuAction = new EventEmitter(); this.menuClose = new EventEmitter(); this.beforeOpen = new EventEmitter(); this.visible = false; } handleMenu($event) { let preventOpen = false; this.beforeOpen.emit({ menuContext: this.menuContext, event: $event, preventOpen: () => { preventOpen = true; }, }); if (preventOpen) { return; } $event.preventDefault(); this.menu = this.contextMenuService.show($event, this.contextMenuTrigger, this.menuContext, this.menuClose, this.menuAction); this.visible = true; } handleMouseDown($event) { if (this.holdToDisplay >= 0) { $event.stopPropagation(); $event.clientY = $event.touches[0].clientY; $event.clientX = $event.touches[0].clientX; this.mouseDownTimeoutId = setTimeout(() => this.handleMenu($event), this.holdToDisplay); } } handleMouseUp() { clearTimeout(this.mouseDownTimeoutId); } ngOnInit() { this.sub = this.menuClose.subscribe(() => (this.visible = false)); } ngOnDestroy() { this.sub.unsubscribe(); } } ContextMenuTriggerDirective.decorators = [ { type: Directive, args: [{ selector: '[contextMenuTrigger]', exportAs: 'contextMenuTrigger', },] } ]; ContextMenuTriggerDirective.ctorParameters = () => [ { type: ContextMenuService } ]; ContextMenuTriggerDirective.propDecorators = { contextMenuTrigger: [{ type: Input }], menuContext: [{ type: Input }], holdToDisplay: [{ type: Input }], menuAction: [{ type: Output }], menuClose: [{ type: Output }], beforeOpen: [{ type: Output }], handleMenu: [{ type: HostListener, args: ['contextmenu', ['$event'],] }], handleMouseDown: [{ type: HostListener, args: ['touchstart', ['$event'],] }], handleMouseUp: [{ type: HostListener, args: ['touchend',] }] }; class ContextSubmenuTriggerDirective { constructor(contextMenuService) { this.contextMenuService = contextMenuService; this.hoverDelay = 500; this.openDelay = 200; this.menuAction = new EventEmitter(); this.menuClose = new EventEmitter(); this.visible = false; this.level = 1; // get current level setTimeout(() => (this.level = this.contextMenuService.getCurrentLevel())); } handleSubMenuClick($event) { $event.preventDefault(); $event.stopPropagation(); clearTimeout(this.opentimer); clearTimeout(this.closetimer); this.menu = this.contextMenuService.show($event, this.contextSubmenuTrigger, this.menuContext, this.menuClose, this.menuAction, true, this.level); this.visible = true; } handleSubMenuEnter($event) { if (this.menu) { this.menu.isTriggerHovered.next(true); } clearTimeout(this.closetimer); this.opentimer = setTimeout(() => { this.menu = this.contextMenuService.show($event, this.contextSubmenuTrigger, this.menuContext, this.menuClose, this.menuAction, true, this.level); this.visible = true; this.opentimer = null; }, this.openDelay); } /** * submenu hides after cursor has exited for a period of time */ handleSubMenuExit() { clearTimeout(this.opentimer); if (this.menu) { this.menu.isTriggerHovered.next(false); } this.closetimer = setTimeout(() => { if (this.menu) { this.menu.isTriggerHovered.next(false); this.contextMenuService.closeSubMenu(this.menu.id); this.menu = undefined; } this.visible = false; }, this.hoverDelay); } /** * if overwritten make sure to clear timeouts */ ngOnDestroy() { clearTimeout(this.opentimer); clearTimeout(this.closetimer); } } ContextSubmenuTriggerDirective.decorators = [ { type: Directive, args: [{ selector: '[contextSubmenuTrigger]' },] } ]; ContextSubmenuTriggerDirective.ctorParameters = () => [ { type: ContextMenuService } ]; ContextSubmenuTriggerDirective.propDecorators = { hoverDelay: [{ type: Input }], openDelay: [{ type: Input }], contextSubmenuTrigger: [{ type: Input }], menuContext: [{ type: Input }], menuAction: [{ type: Output }], menuClose: [{ type: Output }], handleSubMenuClick: [{ type: HostListener, args: ['click', ['$event'],] }], handleSubMenuEnter: [{ type: HostListener, args: ['mouseover', ['$event'],] }], handleSubMenuExit: [{ type: HostListener, args: ['mouseout',] }] }; class MenuComponent { constructor(menuPackage, contextMenuService) { this.menuPackage = menuPackage; this.contextMenuService = contextMenuService; /** State of the dialog animation. */ this._state = 'enter'; this._animationDone = new Subject(); /** set lazy to False if you do not have animations */ this.lazy = true; } handleMouseover() { if (!this.menuPackage.menu.submenu) { return; } this.menuPackage.menu.isMenuHovered.next(true); clearTimeout(this.closetimer); } handleMouseleave() { if (!this.menuPackage.menu.submenu) { return; } this.menuPackage.menu.isMenuHovered.next(false); this.closetimer = setTimeout(() => { this.contextMenuService.closeSubMenu(this.menuPackage.menu.id); }, 500); } handleWindowClick($event) { this.contextMenuService.checkOutsideClick($event); } /** Callback that is invoked when the menu animation completes. */ _onAnimationDone(event) { this._animationDone.next(event); } } MenuComponent.decorators = [ { type: Component, args: [{ selector: 'app-menu', template: `` },] } ]; MenuComponent.ctorParameters = () => [ { type: MenuPackage }, { type: ContextMenuService } ]; MenuComponent.propDecorators = { handleMouseover: [{ type: HostListener, args: ['mouseover',] }], handleMouseleave: [{ type: HostListener, args: ['mouseleave',] }], handleWindowClick: [{ type: HostListener, args: ['document:click', ['$event'],] }], _onAnimationDone: [{ type: HostListener, args: ['@menu.done', ['$event'],] }] }; class ContextMenuModule { } ContextMenuModule.decorators = [ { type: NgModule, args: [{ declarations: [ ContextMenuTriggerDirective, ContextSubmenuTriggerDirective, MenuComponent, ], exports: [ContextMenuTriggerDirective, ContextSubmenuTriggerDirective], imports: [PortalModule, OverlayModule], },] } ]; /** * Generated bundle index. Do not edit. */ export { ContextMenuModule, ContextMenuService, ContextMenuTriggerDirective, ContextSubmenuTriggerDirective, MenuComponent, MenuInjector, MenuPackage }; //# sourceMappingURL=ctrl-ngx-rightclick.js.map