ng2-right-click-menu
Version:
Right click context menu for Angular
777 lines (770 loc) • 24.2 kB
JavaScript
import { Directive, TemplateRef, Optional, Input, Output, EventEmitter, Injectable, ElementRef, Component, ViewEncapsulation, ContentChildren, ViewChildren, ViewChild, ViewContainerRef, QueryList, NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Overlay, OverlayModule } from '@angular/cdk/overlay';
import { TemplatePortal } from '@angular/cdk/portal';
import { __spread } from 'tslib';
import { fromEvent, merge } from 'rxjs';
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
var MenuItemContext = /** @class */ (function () {
function MenuItemContext() {
this.$implicit = {};
}
return MenuItemContext;
}());
var ShContextMenuItemDirective = /** @class */ (function () {
function ShContextMenuItemDirective(template) {
this.template = template;
this.closeOnClick = true;
this.click = new EventEmitter();
this.context = new MenuItemContext();
}
/**
* @return {?}
*/
ShContextMenuItemDirective.prototype.setNotActive = /**
* @return {?}
*/
function () {
this._active = false;
if (this.subMenu) {
this.subMenu.setNotActive();
}
};
/**
* @return {?}
*/
ShContextMenuItemDirective.prototype.setActive = /**
* @return {?}
*/
function () {
this._active = true;
};
ShContextMenuItemDirective.decorators = [
{ type: Directive, args: [{
selector: '[shContextMenuItem]'
},] }
];
/** @nocollapse */
ShContextMenuItemDirective.ctorParameters = function () { return [
{ type: TemplateRef, decorators: [{ type: Optional }] }
]; };
ShContextMenuItemDirective.propDecorators = {
subMenu: [{ type: Input }],
divider: [{ type: Input }],
visible: [{ type: Input }],
disabled: [{ type: Input }],
closeOnClick: [{ type: Input }],
click: [{ type: Output }]
};
return ShContextMenuItemDirective;
}());
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
var ShContextMenuService = /** @class */ (function () {
function ShContextMenuService(overlay) {
this.overlay = overlay;
this.activeOverlays = [];
}
/**
* @param {?} ctxEvent
* @return {?}
*/
ShContextMenuService.prototype.openMenu = /**
* @param {?} ctxEvent
* @return {?}
*/
function (ctxEvent) {
this.closeCurrentOverlays();
var menu = ctxEvent.menu, mouseEvent = ctxEvent.mouseEvent, data = ctxEvent.data;
this.activeMenu = menu;
this.anchorElement = this.createAnchorElement();
/** @type {?} */
var scrollStrategy = this.buildScrollStrategy();
/** @type {?} */
var positionStrategy = this.buildPositionStrategy(this.anchorElement, mouseEvent);
this.attachContextToItems(menu, data);
/** @type {?} */
var overlayRef = this.createAndAttachOverlay(positionStrategy, scrollStrategy, menu, true);
this.attachOverlayRef(menu, overlayRef);
this.registerDetachEvents(overlayRef);
};
/**
* @param {?} ctxEvent
* @return {?}
*/
ShContextMenuService.prototype.openSubMenu = /**
* @param {?} ctxEvent
* @return {?}
*/
function (ctxEvent) {
var menu = ctxEvent.menu, mouseEvent = ctxEvent.mouseEvent, targetElement = ctxEvent.targetElement, data = ctxEvent.data, parentMenu = ctxEvent.parentMenu;
mouseEvent.preventDefault();
mouseEvent.stopPropagation();
/** @type {?} */
var scrollStrategy = this.buildScrollStrategy();
/** @type {?} */
var positionStrategy = this.buildPositionStrategyForSubMenu(targetElement);
/** @type {?} */
var overlayRef = this.createAndAttachOverlay(positionStrategy, scrollStrategy, menu, false);
this.attachContextToItems(menu, data);
this.attachThisContext(menu, parentMenu);
this.attachOverlayRef(menu, overlayRef);
};
/**
* @return {?}
*/
ShContextMenuService.prototype.destroy = /**
* @return {?}
*/
function () {
this.closeCurrentOverlays();
this.subs.unsubscribe();
};
/**
* @return {?}
*/
ShContextMenuService.prototype.ngOnDestroy = /**
* @return {?}
*/
function () {
this.destroy();
};
/**
* @param {?} menu
* @return {?}
*/
ShContextMenuService.prototype.closeSubMenus = /**
* @param {?} menu
* @return {?}
*/
function (menu) {
var _this = this;
/** @type {?} */
var itemsWithSubMenus = menu.menuItems.filter((/**
* @param {?} i
* @return {?}
*/
function (i) { return !!i.subMenu && !!i.subMenu.overlayRef; }));
if (itemsWithSubMenus.length) {
itemsWithSubMenus.forEach((/**
* @param {?} sm
* @return {?}
*/
function (sm) { return _this.closeSubMenus(sm.subMenu); }));
/** @type {?} */
var overlayRefs = itemsWithSubMenus.map((/**
* @param {?} i
* @return {?}
*/
function (i) { return i.subMenu.overlayRef; }));
overlayRefs.forEach((/**
* @param {?} r
* @return {?}
*/
function (r) { return r.dispose(); }));
}
};
/**
* @private
* @param {?} overlayRef
* @return {?}
*/
ShContextMenuService.prototype.registerDetachEvents = /**
* @private
* @param {?} overlayRef
* @return {?}
*/
function (overlayRef) {
this.subs = overlayRef
.backdropClick()
.subscribe(this.closeCurrentOverlays.bind(this));
this.subs.add(overlayRef.detachments().subscribe(this.closeCurrentOverlays.bind(this)));
};
/**
* @private
* @param {?} positionStrategy
* @param {?} scrollStrategy
* @param {?} menu
* @param {?=} hasBackdrop
* @return {?}
*/
ShContextMenuService.prototype.createAndAttachOverlay = /**
* @private
* @param {?} positionStrategy
* @param {?} scrollStrategy
* @param {?} menu
* @param {?=} hasBackdrop
* @return {?}
*/
function (positionStrategy, scrollStrategy, menu, hasBackdrop) {
if (hasBackdrop === void 0) { hasBackdrop = true; }
/** @type {?} */
var overlayRef = this.overlay.create({
positionStrategy: positionStrategy,
scrollStrategy: scrollStrategy,
hasBackdrop: hasBackdrop,
backdropClass: 'sh-backdrop'
});
/*
TODO: try passing the TemplatePortal context (data)
and then injecting it to the *ngTemplateOutlet in the component template
*/
/** @type {?} */
var menuPortal = new TemplatePortal(menu.menuTemplate, menu.menuContainer);
overlayRef.attach(menuPortal);
this.activeOverlays.push(overlayRef);
return overlayRef;
};
/**
* @private
* @return {?}
*/
ShContextMenuService.prototype.buildScrollStrategy = /**
* @private
* @return {?}
*/
function () {
return this.overlay.scrollStrategies.reposition({ autoClose: true });
};
/**
* @private
* @param {?} ele
* @param {?} event
* @return {?}
*/
ShContextMenuService.prototype.buildPositionStrategy = /**
* @private
* @param {?} ele
* @param {?} event
* @return {?}
*/
function (ele, event) {
var x = event.x, y = event.y;
return this.overlay
.position()
.flexibleConnectedTo(ele)
.withDefaultOffsetX(x)
.withDefaultOffsetY(y)
.withPositions(this.buildPositions())
.withFlexibleDimensions(false)
.withPush(true);
};
/**
* @private
* @param {?} elm
* @return {?}
*/
ShContextMenuService.prototype.buildPositionStrategyForSubMenu = /**
* @private
* @param {?} elm
* @return {?}
*/
function (elm) {
return this.overlay
.position()
.flexibleConnectedTo(elm)
.withPositions(this.buildSubMenuPositions())
.withFlexibleDimensions(false)
.withPush(true);
};
/**
* @private
* @return {?}
*/
ShContextMenuService.prototype.closeCurrentOverlays = /**
* @private
* @return {?}
*/
function () {
if (this.anchorElement) {
this.anchorElement.remove();
}
this.activeOverlays.forEach((/**
* @param {?} o
* @return {?}
*/
function (o) {
o.detach();
o.dispose();
}));
this.activeOverlays = [];
// TODO: create close subject and emit.
// subscribe in component
if (this.activeMenu) {
this.activeMenu.close();
}
};
/**
* @private
* @param {?} menu
* @param {?} data
* @return {?}
*/
ShContextMenuService.prototype.attachContextToItems = /**
* @private
* @param {?} menu
* @param {?} data
* @return {?}
*/
function (menu, data) {
menu.menuItems.forEach((/**
* @param {?} i
* @return {?}
*/
function (i) { return (i.context.$implicit = data); }));
};
/**
* @private
* @param {?} menu
* @param {?} parentMenu
* @return {?}
*/
ShContextMenuService.prototype.attachThisContext = /**
* @private
* @param {?} menu
* @param {?} parentMenu
* @return {?}
*/
function (menu, parentMenu) {
menu.thisContext = parentMenu.thisContext;
};
/**
* @private
* @param {?} menu
* @param {?} overlayRef
* @return {?}
*/
ShContextMenuService.prototype.attachOverlayRef = /**
* @private
* @param {?} menu
* @param {?} overlayRef
* @return {?}
*/
function (menu, overlayRef) {
menu.overlayRef = overlayRef;
};
/**
* @private
* @return {?}
*/
ShContextMenuService.prototype.createAnchorElement = /**
* @private
* @return {?}
*/
function () {
/** @type {?} */
var div = document.createElement('div');
div.style.position = 'absolute';
div.style.top = '0';
div.style.bottom = '0';
div.style.left = '0';
div.style.right = '0';
document.body.appendChild(div);
return div;
};
/**
* @private
* @return {?}
*/
ShContextMenuService.prototype.buildSubMenuPositions = /**
* @private
* @return {?}
*/
function () {
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'
}
];
};
/**
* @private
* @return {?}
*/
ShContextMenuService.prototype.buildPositions = /**
* @private
* @return {?}
*/
function () {
return [
{
originX: 'start',
originY: 'top',
overlayX: 'start',
overlayY: 'top'
},
{
originX: 'start',
originY: 'top',
overlayX: 'end',
overlayY: 'top'
},
{
originX: 'start',
originY: 'top',
overlayX: 'start',
overlayY: 'bottom'
},
{
originX: 'start',
originY: 'top',
overlayX: 'end',
overlayY: 'bottom'
}
];
};
ShContextMenuService.decorators = [
{ type: Injectable }
];
/** @nocollapse */
ShContextMenuService.ctorParameters = function () { return [
{ type: Overlay }
]; };
return ShContextMenuService;
}());
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
var ShContextMenuComponent = /** @class */ (function () {
function ShContextMenuComponent(ctxService) {
this.ctxService = ctxService;
this.contentChildrenItems = new QueryList();
this.viewChildrenItems = new QueryList();
}
Object.defineProperty(ShContextMenuComponent.prototype, "menuItems", {
get: /**
* @return {?}
*/
function () {
// when using the ShContextMenuComponent as menu, the ContentChildren is the source
if (this.contentChildrenItems.length) {
return this.contentChildrenItems;
}
// when using a custom component as menu the ViewChildren is the source
return this.viewChildrenItems;
},
enumerable: true,
configurable: true
});
/**
* @param {?} $event
* @param {?} item
* @param {?} elm
* @return {?}
*/
ShContextMenuComponent.prototype.onEnter = /**
* @param {?} $event
* @param {?} item
* @param {?} elm
* @return {?}
*/
function ($event, item, elm) {
this.ctxService.closeSubMenus(this);
this.setNotActive();
if (!item.subMenu) {
return;
}
this.setActive(item);
this.ctxService.openSubMenu({
data: item.context.$implicit,
targetElement: new ElementRef(elm),
menu: item.subMenu,
mouseEvent: $event,
parentMenu: this
});
};
/**
* @private
* @param {?} item
* @return {?}
*/
ShContextMenuComponent.prototype.setActive = /**
* @private
* @param {?} item
* @return {?}
*/
function (item) {
item.setActive();
this.subActive = true;
};
/**
* @param {?} event
* @param {?} item
* @return {?}
*/
ShContextMenuComponent.prototype.onClick = /**
* @param {?} event
* @param {?} item
* @return {?}
*/
function (event, item) {
// TODO: move click handling to service
if (item.divider) {
return;
}
if (!item.subMenu && item.closeOnClick) {
this.ctxService.destroy();
item.click.emit({
data: item.context.$implicit,
event: event
});
}
};
/**
* @private
* @param {?} fn
* @param {?} fallbackContext
* @param {?} data
* @param {?} event
* @return {?}
*/
ShContextMenuComponent.prototype.callWithContext = /**
* @private
* @param {?} fn
* @param {?} fallbackContext
* @param {?} data
* @param {?} event
* @return {?}
*/
function (fn, fallbackContext, data, event) {
return fn.call(this.thisContext ? this.thisContext : fallbackContext, {
data: data,
event: event
});
};
/**
* @return {?}
*/
ShContextMenuComponent.prototype.close = /**
* @return {?}
*/
function () {
this.setNotActive();
this.menuContainer.detach();
if (this.overlayRef) {
this.overlayRef.detach();
}
};
/**
* @return {?}
*/
ShContextMenuComponent.prototype.ngOnDestroy = /**
* @return {?}
*/
function () {
this.close();
};
/**
* @return {?}
*/
ShContextMenuComponent.prototype.setNotActive = /**
* @return {?}
*/
function () {
this.subActive = false;
this.menuItems.forEach((/**
* @param {?} i
* @return {?}
*/
function (i) { return i.setNotActive(); }));
};
/**
* @param {?} item
* @return {?}
*/
ShContextMenuComponent.prototype.isVisible = /**
* @param {?} item
* @return {?}
*/
function (item) {
if (!item.visible) {
return true;
}
return this.callWithContext(item.visible, this, item.context.$implicit, null);
};
ShContextMenuComponent.decorators = [
{ type: Component, args: [{
selector: 'sh-context-menu',
encapsulation: ViewEncapsulation.None,
template: "\n\t\t<ng-container #menuContainer></ng-container>\n\t\t<ng-template #menuTemplate>\n\t\t\t<div class=\"sh-context-menu\">\n\t\t\t\t<div\n\t\t\t\t\t*ngFor=\"let menuItem of menuItems\"\n\t\t\t\t\t#itemElement\n\t\t\t\t\t[ngClass]=\"{\n\t\t\t\t\t\t'sh-sub-anchor': menuItem.subMenu,\n\t\t\t\t\t\t'sh-context-menu--item__divider': menuItem.divider,\n\t\t\t\t\t\t'sh-context-menu--item__sub-active': subActive && menuItem.active\n\t\t\t\t\t}\"\n\t\t\t\t\tclass=\"sh-context-menu--item\"\n\t\t\t\t\t(mouseenter)=\"onEnter($event, menuItem, itemElement)\"\n\t\t\t\t\t(click)=\"onClick($event, menuItem)\"\n\t\t\t\t>\n\t\t\t\t\t<ng-container *ngIf=\"!menuItem.divider || !isVisible(menuItem)\">\n\t\t\t\t\t\t<ng-content\n\t\t\t\t\t\t\t*ngTemplateOutlet=\"menuItem.template; context: menuItem.context\"\n\t\t\t\t\t\t></ng-content>\n\t\t\t\t\t</ng-container>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</ng-template>\n\t",
styles: [".sh-backdrop{background-color:transparent}.sh-context-menu{background:#ececec;min-width:150px;border:1px solid rgba(0,0,0,.2);border-radius:3px;box-shadow:0 0 10px 2px rgba(0,0,0,.1);color:#000;padding:5px 0;margin:0}.sh-context-menu--item{padding:5px 10px 5px 15px;-webkit-transition:.15s;transition:.15s}.sh-context-menu--item:hover,.sh-context-menu--item__sub-active{background-color:#4b8bec;color:#fff;cursor:pointer}.sh-context-menu--item.sh-context-menu--item__divider:hover{background-color:#ececec;color:#000;cursor:default}.sh-context-menu--item__divider{height:1px;margin:1px 1px 8px;overflow:hidden;border-bottom:1px solid #d0d0d0}.sh-context-menu--item.sh-sub-anchor{position:relative;min-width:160px}.sh-sub-anchor:after{content:'';top:50%;right:6px;-webkit-transform:translateY(-50%);transform:translateY(-50%);position:absolute;border-top:6px solid transparent;border-bottom:6px solid transparent;border-left:8px solid #000}"]
}] }
];
/** @nocollapse */
ShContextMenuComponent.ctorParameters = function () { return [
{ type: ShContextMenuService }
]; };
ShContextMenuComponent.propDecorators = {
thisContext: [{ type: Input, args: ['this',] }],
contentChildrenItems: [{ type: ContentChildren, args: [ShContextMenuItemDirective, {
read: ShContextMenuItemDirective
},] }],
viewChildrenItems: [{ type: ViewChildren, args: [ShContextMenuItemDirective, {
read: ShContextMenuItemDirective
},] }],
menuTemplate: [{ type: ViewChild, args: ['menuTemplate', { read: TemplateRef, static: true },] }],
menuContainer: [{ type: ViewChild, args: ['menuContainer', { read: ViewContainerRef, static: true },] }]
};
return ShContextMenuComponent;
}());
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
var ShAttachMenuDirective = /** @class */ (function () {
function ShAttachMenuDirective(ctxService, elm) {
this.ctxService = ctxService;
this.elm = elm;
this.open = new EventEmitter();
}
/**
* @return {?}
*/
ShAttachMenuDirective.prototype.ngOnInit = /**
* @return {?}
*/
function () {
this.setupEvents();
};
/**
* @private
* @return {?}
*/
ShAttachMenuDirective.prototype.setupEvents = /**
* @private
* @return {?}
*/
function () {
var _this = this;
/** @type {?} */
var observables = [];
if (!this.triggers) {
observables.push(fromEvent(this.elm.nativeElement, 'contextmenu'));
}
else {
this.triggers.forEach((/**
* @param {?} t
* @return {?}
*/
function (t) {
observables.push(fromEvent(_this.elm.nativeElement, t));
}));
}
this.sub = merge.apply(void 0, __spread(observables)).subscribe(this.openMenu.bind(this));
};
/**
* @param {?} event
* @return {?}
*/
ShAttachMenuDirective.prototype.openMenu = /**
* @param {?} event
* @return {?}
*/
function (event) {
event.preventDefault();
event.stopPropagation();
/** @type {?} */
var preventOpen = false;
this.open.emit({
data: this.data,
mouseEvent: event,
preventOpen: (/**
* @return {?}
*/
function () {
preventOpen = true;
})
});
if (preventOpen)
return;
this.ctxService.openMenu({
menu: this.menu,
mouseEvent: event,
targetElement: this.elm,
data: this.data
});
};
/**
* @return {?}
*/
ShAttachMenuDirective.prototype.ngOnDestroy = /**
* @return {?}
*/
function () {
if (this.sub) {
this.sub.unsubscribe();
}
};
ShAttachMenuDirective.decorators = [
{ type: Directive, args: [{
selector: '[shAttachMenu]'
},] }
];
/** @nocollapse */
ShAttachMenuDirective.ctorParameters = function () { return [
{ type: ShContextMenuService },
{ type: ElementRef }
]; };
ShAttachMenuDirective.propDecorators = {
menu: [{ type: Input, args: ['shAttachMenu',] }],
triggers: [{ type: Input, args: ['shMenuTriggers',] }],
data: [{ type: Input, args: ['shMenuData',] }],
open: [{ type: Output }]
};
return ShAttachMenuDirective;
}());
/**
* @fileoverview added by tsickle
* @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
*/
var ShContextMenuModule = /** @class */ (function () {
function ShContextMenuModule() {
}
ShContextMenuModule.decorators = [
{ type: NgModule, args: [{
declarations: [
ShAttachMenuDirective,
ShContextMenuComponent,
ShContextMenuItemDirective
],
exports: [
ShAttachMenuDirective,
ShContextMenuComponent,
ShContextMenuItemDirective
],
providers: [ShContextMenuService],
imports: [CommonModule, OverlayModule],
entryComponents: [ShContextMenuComponent]
},] }
];
return ShContextMenuModule;
}());
export { ShAttachMenuDirective, ShContextMenuComponent, ShContextMenuItemDirective, ShContextMenuModule, ShContextMenuService, MenuItemContext as ɵa };
//# sourceMappingURL=ng2-right-click-menu.js.map