slickgrid
Version:
A lightning fast JavaScript grid/spreadsheet
276 lines (274 loc) • 21.7 kB
JavaScript
"use strict";
(() => {
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: !0, configurable: !0, writable: !0, value }) : obj[key] = value;
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key != "symbol" ? key + "" : key, value);
// src/plugins/slick.contextmenu.ts
var BindingEventService = Slick.BindingEventService, SlickEvent = Slick.Event, SlickEventData = Slick.EventData, EventHandler = Slick.EventHandler, Utils = Slick.Utils, SlickContextMenu = class {
constructor(optionProperties) {
// --
// public API
__publicField(this, "pluginName", "ContextMenu");
__publicField(this, "onAfterMenuShow", new SlickEvent("onAfterMenuShow"));
__publicField(this, "onBeforeMenuShow", new SlickEvent("onBeforeMenuShow"));
__publicField(this, "onBeforeMenuClose", new SlickEvent("onBeforeMenuClose"));
__publicField(this, "onCommand", new SlickEvent("onCommand"));
__publicField(this, "onOptionSelected", new SlickEvent("onOptionSelected"));
// --
// protected props
__publicField(this, "_bindingEventService", new BindingEventService());
__publicField(this, "_contextMenuProperties");
__publicField(this, "_currentCell", -1);
__publicField(this, "_currentRow", -1);
__publicField(this, "_grid");
__publicField(this, "_gridOptions");
__publicField(this, "_gridUid", "");
__publicField(this, "_handler", new EventHandler());
__publicField(this, "_commandTitleElm");
__publicField(this, "_optionTitleElm");
__publicField(this, "_lastMenuTypeClicked", "");
__publicField(this, "_menuElm");
__publicField(this, "_subMenuParentId", "");
__publicField(this, "_defaults", {
autoAdjustDrop: !0,
// dropup/dropdown
autoAlignSide: !0,
// left/right
autoAdjustDropOffset: -4,
autoAlignSideOffset: 0,
hideMenuOnScroll: !1,
maxHeight: "none",
width: "auto",
optionShownOverColumnIds: [],
commandShownOverColumnIds: [],
subMenuOpenByEvent: "mouseover"
});
this._contextMenuProperties = Utils.extend({}, this._defaults, optionProperties);
}
init(grid) {
this._grid = grid, this._gridOptions = grid.getOptions(), this._gridUid = grid.getUID() || "", Utils.addSlickEventPubSubWhenDefined(grid.getPubSubService(), this), this._handler.subscribe(this._grid.onContextMenu, this.handleOnContextMenu.bind(this)), this._contextMenuProperties.hideMenuOnScroll && this._handler.subscribe(this._grid.onScroll, this.destroyMenu.bind(this));
}
setOptions(newOptions) {
this._contextMenuProperties = Utils.extend({}, this._contextMenuProperties, newOptions), newOptions.commandShownOverColumnIds && (this._contextMenuProperties.commandShownOverColumnIds = newOptions.commandShownOverColumnIds), newOptions.optionShownOverColumnIds && (this._contextMenuProperties.optionShownOverColumnIds = newOptions.optionShownOverColumnIds);
}
destroy() {
var _a;
this.onAfterMenuShow.unsubscribe(), this.onBeforeMenuShow.unsubscribe(), this.onBeforeMenuClose.unsubscribe(), this.onCommand.unsubscribe(), this.onOptionSelected.unsubscribe(), this._handler.unsubscribeAll(), this._bindingEventService.unbindAll(), (_a = this._menuElm) == null || _a.remove(), this._commandTitleElm = null, this._optionTitleElm = null, this._menuElm = null;
}
createParentMenu(evt) {
var _a, _b, _c, _d, _e, _f;
let e = evt instanceof SlickEventData ? evt.getNativeEvent() : evt, targetEvent = (_b = (_a = e.touches) == null ? void 0 : _a[0]) != null ? _b : e, cell = this._grid.getCellFromEvent(e);
this._currentCell = (_c = cell == null ? void 0 : cell.cell) != null ? _c : 0, this._currentRow = (_d = cell == null ? void 0 : cell.row) != null ? _d : 0;
let columnDef = this._grid.getColumns()[this._currentCell], isColumnOptionAllowed = this.checkIsColumnAllowed((_e = this._contextMenuProperties.optionShownOverColumnIds) != null ? _e : [], columnDef.id), isColumnCommandAllowed = this.checkIsColumnAllowed((_f = this._contextMenuProperties.commandShownOverColumnIds) != null ? _f : [], columnDef.id), commandItems = this._contextMenuProperties.commandItems || [], optionItems = this._contextMenuProperties.optionItems || [];
if (!(!columnDef || !isColumnCommandAllowed && !isColumnOptionAllowed || !commandItems.length && !optionItems.length) && (this.destroyMenu(e), this.onBeforeMenuShow.notify({
cell: this._currentCell,
row: this._currentRow,
grid: this._grid
}, e, this).getReturnValue() !== !1 && (this._menuElm = this.createMenu(commandItems, optionItems), this._menuElm.style.top = `${targetEvent.pageY}px`, this._menuElm.style.left = `${targetEvent.pageX}px`, this._menuElm.style.display = "block", document.body.appendChild(this._menuElm), this.onAfterMenuShow.notify({
cell: this._currentCell,
row: this._currentRow,
grid: this._grid
}, e, this).getReturnValue() !== !1)))
return this._menuElm;
}
createMenu(commandItems, optionItems, level = 0, item) {
var _a, _b, _c, _d;
let columnDef = this._grid.getColumns()[this._currentCell], dataContext = this._grid.getDataItem(this._currentRow), isColumnOptionAllowed = this.checkIsColumnAllowed((_a = this._contextMenuProperties.optionShownOverColumnIds) != null ? _a : [], columnDef.id), isColumnCommandAllowed = this.checkIsColumnAllowed((_b = this._contextMenuProperties.commandShownOverColumnIds) != null ? _b : [], columnDef.id), maxHeight = isNaN(this._contextMenuProperties.maxHeight) ? this._contextMenuProperties.maxHeight : `${(_c = this._contextMenuProperties.maxHeight) != null ? _c : 0}px`, width = isNaN(this._contextMenuProperties.width) ? this._contextMenuProperties.width : `${(_d = this._contextMenuProperties.maxWidth) != null ? _d : 0}px`, subMenuCommand = item == null ? void 0 : item.command, subMenuId = level === 1 && subMenuCommand ? subMenuCommand.replaceAll(" ", "") : "";
subMenuId && (this._subMenuParentId = subMenuId), level > 1 && (subMenuId = this._subMenuParentId);
let menuClasses = `slick-context-menu slick-menu-level-${level} ${this._gridUid}`, bodyMenuElm = document.body.querySelector(`.slick-context-menu.slick-menu-level-${level}${this.getGridUidSelector()}`);
if (bodyMenuElm) {
if (bodyMenuElm.dataset.subMenuParent === subMenuId)
return bodyMenuElm;
this.destroySubMenus();
}
let menuElm = document.createElement("div");
menuElm.className = menuClasses, level > 0 && (menuElm.classList.add("slick-submenu"), subMenuId && (menuElm.dataset.subMenuParent = subMenuId)), menuElm.ariaLabel = level > 1 ? "SubMenu" : "Context Menu", menuElm.role = "menu", width && (menuElm.style.width = width), maxHeight && (menuElm.style.maxHeight = maxHeight), menuElm.style.display = "none";
let closeButtonElm = null;
if (level === 0) {
closeButtonElm = document.createElement("button"), closeButtonElm.type = "button", closeButtonElm.className = "close", closeButtonElm.dataset.dismiss = "slick-context-menu", closeButtonElm.ariaLabel = "Close";
let spanCloseElm = document.createElement("span");
spanCloseElm.className = "close", spanCloseElm.ariaHidden = "true", spanCloseElm.textContent = "\xD7", closeButtonElm.appendChild(spanCloseElm);
}
if (!this._contextMenuProperties.hideOptionSection && isColumnOptionAllowed && optionItems.length > 0) {
let optionMenuElm = document.createElement("div");
optionMenuElm.className = "slick-context-menu-option-list", optionMenuElm.role = "menu", item && level > 0 && this.addSubMenuTitleWhenExists(item, optionMenuElm), closeButtonElm && !this._contextMenuProperties.hideCloseButton && (this._bindingEventService.bind(closeButtonElm, "click", this.handleCloseButtonClicked.bind(this)), menuElm.appendChild(closeButtonElm)), menuElm.appendChild(optionMenuElm), this.populateCommandOrOptionItems(
"option",
this._contextMenuProperties,
optionMenuElm,
optionItems,
{ cell: this._currentCell, row: this._currentRow, column: columnDef, dataContext, grid: this._grid, level }
);
}
if (!this._contextMenuProperties.hideCommandSection && isColumnCommandAllowed && commandItems.length > 0) {
let commandMenuElm = document.createElement("div");
commandMenuElm.className = "slick-context-menu-command-list", commandMenuElm.role = "menu", item && level > 0 && this.addSubMenuTitleWhenExists(item, commandMenuElm), closeButtonElm && !this._contextMenuProperties.hideCloseButton && (!isColumnOptionAllowed || optionItems.length === 0 || this._contextMenuProperties.hideOptionSection) && (this._bindingEventService.bind(closeButtonElm, "click", this.handleCloseButtonClicked.bind(this)), menuElm.appendChild(closeButtonElm)), menuElm.appendChild(commandMenuElm), this.populateCommandOrOptionItems(
"command",
this._contextMenuProperties,
commandMenuElm,
commandItems,
{ cell: this._currentCell, row: this._currentRow, column: columnDef, dataContext, grid: this._grid, level }
);
}
return level++, menuElm;
}
addSubMenuTitleWhenExists(item, commandOrOptionMenu) {
if (item !== "divider" && (item != null && item.subMenuTitle)) {
let subMenuTitleElm = document.createElement("div");
subMenuTitleElm.className = "slick-menu-title", subMenuTitleElm.textContent = item.subMenuTitle;
let subMenuTitleClass = item.subMenuTitleCssClass;
subMenuTitleClass && subMenuTitleElm.classList.add(...Utils.classNameToList(subMenuTitleClass)), commandOrOptionMenu.appendChild(subMenuTitleElm);
}
}
handleCloseButtonClicked(e) {
e.defaultPrevented || this.destroyMenu(e);
}
destroyMenu(e, args) {
var _a, _b, _c;
if (this._menuElm = this._menuElm || document.querySelector(`.slick-context-menu${this.getGridUidSelector()}`), (_a = this._menuElm) != null && _a.remove) {
if (this.onBeforeMenuClose.notify({
cell: (_b = args == null ? void 0 : args.cell) != null ? _b : 0,
row: (_c = args == null ? void 0 : args.row) != null ? _c : 0,
grid: this._grid
}, e, this).getReturnValue() === !1)
return;
this._menuElm.remove(), this._menuElm = null;
}
this.destroySubMenus();
}
/** Destroy all parent menus and any sub-menus */
destroyAllMenus() {
this.destroySubMenus(), this._bindingEventService.unbindAll("parent-menu"), document.querySelectorAll(`.slick-context-menu${this.getGridUidSelector()}`).forEach((subElm) => subElm.remove());
}
/** Close and destroy all previously opened sub-menus */
destroySubMenus() {
this._bindingEventService.unbindAll("sub-menu"), document.querySelectorAll(`.slick-context-menu.slick-submenu${this.getGridUidSelector()}`).forEach((subElm) => subElm.remove());
}
checkIsColumnAllowed(columnIds, columnId) {
let isAllowedColumn = !1;
if ((columnIds == null ? void 0 : columnIds.length) > 0)
for (let o = 0, ln = columnIds.length; o < ln; o++)
columnIds[o] === columnId && (isAllowedColumn = !0);
else
isAllowedColumn = !0;
return isAllowedColumn;
}
getGridUidSelector() {
let gridUid = this._grid.getUID() || "";
return gridUid ? `.${gridUid}` : "";
}
handleOnContextMenu(evt, args) {
this.destroyAllMenus();
let e = evt instanceof SlickEventData ? evt.getNativeEvent() : evt;
e.preventDefault();
let cell = this._grid.getCellFromEvent(e);
if (cell) {
let columnDef = this._grid.getColumns()[cell.cell], dataContext = this._grid.getDataItem(cell.row);
if (args = args || {}, args.cell = cell.cell, args.row = cell.row, args.column = columnDef, args.dataContext = dataContext, args.grid = this._grid, !this.runOverrideFunctionWhenExists(this._contextMenuProperties.menuUsabilityOverride, args))
return;
this._menuElm = this.createParentMenu(e), this._menuElm && (this.repositionMenu(e, this._menuElm), this._menuElm.style.display = "block"), this._bindingEventService.bind(document.body, "mousedown", this.handleBodyMouseDown.bind(this));
}
}
/** When users click outside the Cell Menu, we will typically close the Cell Menu (and any sub-menus) */
handleBodyMouseDown(e) {
var _a;
let isMenuClicked = !1;
(_a = this._menuElm) != null && _a.contains(e.target) && (isMenuClicked = !0), isMenuClicked || document.querySelectorAll(`.slick-context-menu.slick-submenu${this.getGridUidSelector()}`).forEach((subElm) => {
subElm.contains(e.target) && (isMenuClicked = !0);
}), this._menuElm !== e.target && !isMenuClicked && !e.defaultPrevented && this.destroyMenu(e, { cell: this._currentCell, row: this._currentRow });
}
/** Construct the Command Items section. */
populateCommandOrOptionItems(itemType, contextMenu, commandOrOptionMenuElm, commandOrOptionItems, args) {
if (!args || !commandOrOptionItems || !contextMenu)
return;
let level = (args == null ? void 0 : args.level) || 0, isSubMenu = level > 0;
contextMenu != null && contextMenu[`${itemType}Title`] && !isSubMenu && (this[`_${itemType}TitleElm`] = document.createElement("div"), this[`_${itemType}TitleElm`].className = "slick-menu-title", this[`_${itemType}TitleElm`].textContent = contextMenu[`${itemType}Title`], commandOrOptionMenuElm.appendChild(this[`_${itemType}TitleElm`]));
for (let i = 0, ln = commandOrOptionItems.length; i < ln; i++) {
let addClickListener = !0, item = commandOrOptionItems[i], isItemVisible = this.runOverrideFunctionWhenExists(item.itemVisibilityOverride, args), isItemUsable = this.runOverrideFunctionWhenExists(item.itemUsabilityOverride, args);
if (!isItemVisible)
continue;
Object.prototype.hasOwnProperty.call(item, "itemUsabilityOverride") && (item.disabled = !isItemUsable);
let liElm = document.createElement("div");
liElm.className = "slick-context-menu-item", liElm.role = "menuitem", (item.divider || item === "divider") && (liElm.classList.add("slick-context-menu-item-divider"), addClickListener = !1), (item.disabled || !isItemUsable) && liElm.classList.add("slick-context-menu-item-disabled"), item.hidden && liElm.classList.add("slick-context-menu-item-hidden"), item.cssClass && liElm.classList.add(...Utils.classNameToList(item.cssClass)), item.tooltip && (liElm.title = item.tooltip || "");
let iconElm = document.createElement("div");
iconElm.className = "slick-context-menu-icon", liElm.appendChild(iconElm), item.iconCssClass && iconElm.classList.add(...Utils.classNameToList(item.iconCssClass)), item.iconImage && (iconElm.style.backgroundImage = `url(${item.iconImage})`);
let textElm = document.createElement("span");
if (textElm.className = "slick-context-menu-content", textElm.textContent = item.title || "", liElm.appendChild(textElm), item.textCssClass && textElm.classList.add(...Utils.classNameToList(item.textCssClass)), commandOrOptionMenuElm.appendChild(liElm), addClickListener) {
let eventGroup = isSubMenu ? "sub-menu" : "parent-menu";
this._bindingEventService.bind(liElm, "click", this.handleMenuItemClick.bind(this, item, itemType, level), void 0, eventGroup);
}
if (this._contextMenuProperties.subMenuOpenByEvent === "mouseover" && this._bindingEventService.bind(liElm, "mouseover", (e) => {
item.commandItems || item.optionItems ? (this.repositionSubMenu(item, itemType, level, e), this._lastMenuTypeClicked = itemType) : isSubMenu || this.destroySubMenus();
}), item.commandItems || item.optionItems) {
let chevronElm = document.createElement("span");
chevronElm.className = "sub-item-chevron", this._contextMenuProperties.subItemChevronClass ? chevronElm.classList.add(...Utils.classNameToList(this._contextMenuProperties.subItemChevronClass)) : chevronElm.textContent = "\u2B9E", liElm.classList.add("slick-submenu-item"), liElm.appendChild(chevronElm);
continue;
}
}
}
handleMenuItemClick(item, type, level = 0, e) {
if ((item == null ? void 0 : item[type]) !== void 0 && item !== "divider" && !item.disabled && !item.divider && this._currentCell !== void 0 && this._currentRow !== void 0) {
if (type === "option" && !this._grid.getEditorLock().commitCurrentEdit())
return;
let optionOrCommand = item[type] !== void 0 ? item[type] : "", row = this._currentRow, cell = this._currentCell, columnDef = this._grid.getColumns()[cell], dataContext = this._grid.getDataItem(row), cellValue;
if (Object.prototype.hasOwnProperty.call(dataContext, columnDef == null ? void 0 : columnDef.field) && (cellValue = dataContext[columnDef.field]), optionOrCommand !== void 0 && !item[`${type}Items`]) {
let callbackArgs = {
cell,
row,
grid: this._grid,
[type]: optionOrCommand,
item,
column: columnDef,
dataContext,
value: cellValue
};
this[type === "command" ? "onCommand" : "onOptionSelected"].notify(callbackArgs, e, this), typeof item.action == "function" && item.action.call(this, e, callbackArgs), e.defaultPrevented || this.destroyMenu(e, { cell, row });
} else item.commandItems || item.optionItems ? this.repositionSubMenu(item, type, level, e) : this.destroySubMenus();
this._lastMenuTypeClicked = type;
}
}
repositionSubMenu(item, type, level, e) {
(e.target.classList.contains("slick-cell") || this._lastMenuTypeClicked !== type) && this.destroySubMenus();
let subMenuElm = this.createMenu((item == null ? void 0 : item.commandItems) || [], (item == null ? void 0 : item.optionItems) || [], level + 1, item);
subMenuElm.style.display = "block", document.body.appendChild(subMenuElm), this.repositionMenu(e, subMenuElm);
}
/**
* Reposition the menu drop (up/down) and the side (left/right)
* @param {*} event
*/
repositionMenu(e, menuElm) {
var _a, _b, _c, _d;
let isSubMenu = menuElm.classList.contains("slick-submenu"), targetEvent = (_b = (_a = e.touches) == null ? void 0 : _a[0]) != null ? _b : e, parentElm = isSubMenu ? e.target.closest(".slick-context-menu-item") : e.target.closest(".slick-cell");
if (menuElm && parentElm) {
let parentOffset = Utils.offset(parentElm), menuOffsetLeft = isSubMenu && parentElm ? (_c = parentOffset == null ? void 0 : parentOffset.left) != null ? _c : 0 : targetEvent.pageX, menuOffsetTop = parentElm ? (_d = parentOffset == null ? void 0 : parentOffset.top) != null ? _d : 0 : targetEvent.pageY, menuHeight = (menuElm == null ? void 0 : menuElm.offsetHeight) || 0, menuWidth = Number((menuElm == null ? void 0 : menuElm.offsetWidth) || this._contextMenuProperties.width || 0), rowHeight = this._gridOptions.rowHeight, dropOffset = Number(this._contextMenuProperties.autoAdjustDropOffset || 0), sideOffset = Number(this._contextMenuProperties.autoAlignSideOffset || 0);
if (this._contextMenuProperties.autoAdjustDrop) {
let spaceBottom = Utils.calculateAvailableSpace(parentElm).bottom, spaceTop = Utils.calculateAvailableSpace(parentElm).top, spaceBottomRemaining = spaceBottom + dropOffset - rowHeight, spaceTopRemaining = spaceTop - dropOffset + rowHeight;
(spaceBottomRemaining < menuHeight && spaceTopRemaining > spaceBottomRemaining ? "top" : "bottom") === "top" ? (menuElm.classList.remove("dropdown"), menuElm.classList.add("dropup"), isSubMenu ? menuOffsetTop -= menuHeight - dropOffset - parentElm.clientHeight : menuOffsetTop -= menuHeight - dropOffset) : (menuElm.classList.remove("dropup"), menuElm.classList.add("dropdown"), isSubMenu ? menuOffsetTop += dropOffset : menuOffsetTop += rowHeight + dropOffset);
}
if (this._contextMenuProperties.autoAlignSide) {
let gridPos = this._grid.getGridPosition(), subMenuPosCalc = menuOffsetLeft + Number(menuWidth);
isSubMenu && (subMenuPosCalc += parentElm.clientWidth);
let browserWidth = document.documentElement.clientWidth;
(subMenuPosCalc >= gridPos.width || subMenuPosCalc >= browserWidth ? "left" : "right") === "left" ? (menuElm.classList.remove("dropright"), menuElm.classList.add("dropleft"), menuOffsetLeft -= menuWidth - sideOffset) : (menuElm.classList.remove("dropleft"), menuElm.classList.add("dropright"), isSubMenu ? menuOffsetLeft += sideOffset + parentElm.offsetWidth : menuOffsetLeft += sideOffset);
}
menuElm.style.top = `${menuOffsetTop}px`, menuElm.style.left = `${menuOffsetLeft}px`;
}
}
/**
* Method that user can pass to override the default behavior.
* In order word, user can choose or an item is (usable/visible/enable) by providing his own logic.
* @param overrideFn: override function callback
* @param args: multiple arguments provided to the override (cell, row, columnDef, dataContext, grid)
*/
runOverrideFunctionWhenExists(overrideFn, args) {
return typeof overrideFn == "function" ? overrideFn.call(this, args) : !0;
}
};
window.Slick && Utils.extend(!0, window, {
Slick: {
Plugins: {
ContextMenu: SlickContextMenu
}
}
});
})();
//# sourceMappingURL=slick.contextmenu.js.map