@ctrl/ngx-rightclick
Version:
GitHub Buttons for Angular. Star, Like, Follow and more
439 lines (430 loc) • 15.2 kB
JavaScript
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