{ "version": 3, "sources": ["pure-context-menu.js"], "sourcesContent": ["let globalListenerSet = false;\nlet baseOptions = {\n contextMenuClass: \"pure-context-menu\",\n dropdownClass: \"dropdown-menu\",\n dividerClass: \"dropdown-divider\",\n itemClass: \"dropdown-item\",\n zIndex: \"9999\",\n preventCloseOnClick: false,\n show: (event) => true,\n};\n\n/**\n * Easily manage context menus\n * Works out of the box with bootstrap css\n */\nclass PureContextMenu {\n _el;\n _items;\n _options;\n _currentEvent;\n\n /**\n * @param {HTMLElement} el\n * @param {object} items\n * @param {object} opts\n */\n constructor(el, items, opts) {\n this._items = items;\n this._el = el;\n\n this._options = Object.assign(baseOptions, opts);\n\n // bind the menu on context menu\n el.addEventListener(\"contextmenu\", this);\n\n // add also long press support, this helps with ios browsers\n // include https://cdn.jsdelivr.net/npm/long-press-event@2.4/dist/long-press-event.min.js in your pages\n el.addEventListener(\"long-press\", this);\n\n // close if the user clicks outside of the menu\n if (!globalListenerSet) {\n document.addEventListener(\"click\", this);\n globalListenerSet = true;\n }\n }\n\n /**\n * @link https://gist.github.com/WebReflection/ec9f6687842aa385477c4afca625bbf4#handling-events\n * @param {Event} event\n */\n handleEvent(event) {\n const type = event.type === \"long-press\" ? \"contextmenu\" : event.type;\n this[`on${type}`](event);\n }\n\n /**\n * @param {object} opts\n */\n static updateDefaultOptions(opts) {\n baseOptions = Object.assign(baseOptions, opts);\n }\n\n /**\n * @returns {object}\n */\n static getDefaultOptions() {\n return baseOptions;\n }\n\n /**\n * Create the menu\n * @returns {HTMLElement}\n */\n _buildContextMenu = () => {\n const contextMenu = document.createElement(\"ul\");\n contextMenu.style.minWidth = \"120px\";\n contextMenu.style.maxWidth = \"240px\";\n contextMenu.style.display = \"block\";\n contextMenu.classList.add(this._options.contextMenuClass);\n contextMenu.classList.add(this._options.dropdownClass);\n\n for (const item of this._items) {\n const child = document.createElement(\"li\");\n if (item === \"-\") {\n const divider = document.createElement(\"hr\");\n divider.classList.add(this._options.dividerClass);\n child.appendChild(divider);\n } else {\n const link = document.createElement(\"a\");\n link.innerText = item.label;\n link.style.cursor = \"pointer\";\n link.style.whiteSpace = \"normal\";\n link.classList.add(this._options.itemClass);\n child.appendChild(link);\n }\n\n contextMenu.appendChild(child);\n }\n return contextMenu;\n };\n\n /**\n * Normalize the context menu position so that it won't get out of bounds\n * @param {number} mouseX\n * @param {number} mouseY\n * @param {HTMLElement} contextMenu\n */\n _normalizePosition = (mouseX, mouseY, contextMenu) => {\n const scope = this._el;\n const contextStyles = window.getComputedStyle(contextMenu);\n // clientWidth exclude borders and we add 1px for good measure\n const offset = parseInt(contextStyles.borderWidth) + 1;\n\n // compute what is the mouse position relative to the container element (scope)\n const bounds = scope.getBoundingClientRect();\n\n let scopeX = mouseX;\n let scopeY = mouseY;\n\n if (![\"BODY\", \"HTML\"].includes(scope.tagName)) {\n scopeX -= bounds.left;\n scopeY -= bounds.top;\n }\n\n const menuWidth = parseInt(contextStyles.width);\n\n // check if the element will go out of bounds\n const outOfBoundsOnX = scopeX + menuWidth > scope.clientWidth;\n const outOfBoundsOnY = scopeY + contextMenu.clientHeight > scope.clientHeight;\n\n let normalizedX = mouseX;\n let normalizedY = mouseY;\n\n // normalize on X\n if (outOfBoundsOnX) {\n normalizedX = scope.clientWidth - menuWidth - offset;\n if (![\"BODY\", \"HTML\"].includes(scope.tagName)) {\n normalizedX += bounds.left;\n }\n }\n\n // normalize on Y\n if (outOfBoundsOnY) {\n normalizedY = scope.clientHeight - contextMenu.clientHeight - offset;\n if (![\"BODY\", \"HTML\"].includes(scope.tagName)) {\n normalizedY += bounds.top;\n }\n }\n\n return { normalizedX, normalizedY };\n };\n\n _removeExistingContextMenu = () => {\n document.querySelector(`.${this._options.contextMenuClass}`)?.remove();\n };\n\n _bindCallbacks = (contextMenu) => {\n this._items.forEach((menuItem, index) => {\n if (menuItem === \"-\") {\n return;\n }\n\n const htmlEl = contextMenu.children[index];\n\n // We also need to listen on touchstart to avoid \"double tap\" issue\n htmlEl.ontouchstart = htmlEl.onclick = () => {\n menuItem.callback(this._currentEvent);\n\n // do not close the menu if set\n const preventCloseOnClick = menuItem.preventCloseOnClick ?? this._options.preventCloseOnClick ?? false;\n if (!preventCloseOnClick) {\n this._removeExistingContextMenu();\n }\n };\n });\n };\n\n /**\n * @param {MouseEvent} event\n */\n oncontextmenu = (event) => {\n if (!this._options.show(event)) {\n return;\n }\n event.preventDefault();\n event.stopPropagation();\n\n // Store event for callbakcs\n this._currentEvent = event;\n\n // the current context menu should disappear when a new one is displayed\n this._removeExistingContextMenu();\n\n // build and show on ui\n const contextMenu = this._buildContextMenu();\n document.querySelector(\"body\").append(contextMenu);\n\n // set the position already so that width can be computed\n contextMenu.style.position = \"fixed\";\n contextMenu.style.zIndex = this._options.zIndex;\n\n // adjust the position according to mouse position\n const mouseX = event.detail.clientX ?? event.clientX;\n const mouseY = event.detail.clientY ?? event.clientY;\n const { normalizedX, normalizedY } = this._normalizePosition(mouseX, mouseY, contextMenu);\n contextMenu.style.top = `${normalizedY}px`;\n contextMenu.style.left = `${normalizedX}px`;\n\n // disable context menu for it\n contextMenu.oncontextmenu = (e) => e.preventDefault();\n\n // bind the callbacks on each option\n this._bindCallbacks(contextMenu);\n };\n\n /**\n * Used to determine if the user has clicked outside of the context menu and if so to close it\n * @param {MouseEvent} event\n */\n onclick = (event) => {\n const clickedTarget = event.target;\n if (clickedTarget.closest(`.${this._options.contextMenuClass}`)) {\n return;\n }\n this._removeExistingContextMenu();\n };\n\n /**\n * Remove all the event listeners that were registered for this feature\n */\n off() {\n this._removeExistingContextMenu();\n document.removeEventListener(\"click\", this);\n globalListenerSet = false;\n this._el.removeEventListener(\"contextmenu\", this);\n this._el.removeEventListener(\"long-press\", this);\n }\n}\n\nexport default PureContextMenu;\n"], "mappings": "AAAA,GAAI,GAAoB,GACpB,EAAc,CAChB,iBAAkB,oBAClB,cAAe,gBACf,aAAc,mBACd,UAAW,gBACX,OAAQ,OACR,oBAAqB,GACrB,KAAM,AAAC,GAAU,EACnB,EAMM,EAAN,KAAsB,CACpB,EACA,EACA,EACA,EAOA,YAAY,EAAI,EAAO,EAAM,CAC3B,OAAc,EACd,OAAW,EAEX,OAAgB,OAAO,OAAO,EAAa,CAAI,EAG/C,EAAG,iBAAiB,cAAe,IAAI,EAIvC,EAAG,iBAAiB,aAAc,IAAI,EAGjC,GACH,UAAS,iBAAiB,QAAS,IAAI,EACvC,EAAoB,GAExB,CAMA,YAAY,EAAO,CACjB,GAAM,GAAO,EAAM,OAAS,aAAe,cAAgB,EAAM,KACjE,KAAK,KAAK,KAAQ,CAAK,CACzB,CAKA,MAAO,sBAAqB,EAAM,CAChC,EAAc,OAAO,OAAO,EAAa,CAAI,CAC/C,CAKA,MAAO,oBAAoB,CACzB,MAAO,EACT,CAMA,EAAoB,IAAM,CACxB,GAAM,GAAc,SAAS,cAAc,IAAI,EAC/C,EAAY,MAAM,SAAW,QAC7B,EAAY,MAAM,SAAW,QAC7B,EAAY,MAAM,QAAU,QAC5B,EAAY,UAAU,IAAI,OAAc,gBAAgB,EACxD,EAAY,UAAU,IAAI,OAAc,aAAa,EAErD,OAAW,KAAQ,QAAa,CAC9B,GAAM,GAAQ,SAAS,cAAc,IAAI,EACzC,GAAI,IAAS,IAAK,CAChB,GAAM,GAAU,SAAS,cAAc,IAAI,EAC3C,EAAQ,UAAU,IAAI,OAAc,YAAY,EAChD,EAAM,YAAY,CAAO,CAC3B,KAAO,CACL,GAAM,GAAO,SAAS,cAAc,GAAG,EACvC,EAAK,UAAY,EAAK,MACtB,EAAK,MAAM,OAAS,UACpB,EAAK,MAAM,WAAa,SACxB,EAAK,UAAU,IAAI,OAAc,SAAS,EAC1C,EAAM,YAAY,CAAI,CACxB,CAEA,EAAY,YAAY,CAAK,CAC/B,CACA,MAAO,EACT,EAQA,EAAqB,CAAC,EAAQ,EAAQ,IAAgB,CACpD,GAAM,GAAQ,OACR,EAAgB,OAAO,iBAAiB,CAAW,EAEnD,EAAS,SAAS,EAAc,WAAW,EAAI,EAG/C,EAAS,EAAM,sBAAsB,EAEvC,EAAS,EACT,EAAS,EAEb,AAAK,CAAC,OAAQ,MAAM,EAAE,SAAS,EAAM,OAAO,GAC1C,IAAU,EAAO,KACjB,GAAU,EAAO,KAGnB,GAAM,GAAY,SAAS,EAAc,KAAK,EAGxC,EAAiB,EAAS,EAAY,EAAM,YAC5C,EAAiB,EAAS,EAAY,aAAe,EAAM,aAE7D,EAAc,EACd,EAAc,EAGlB,MAAI,IACF,GAAc,EAAM,YAAc,EAAY,EACzC,CAAC,OAAQ,MAAM,EAAE,SAAS,EAAM,OAAO,GAC1C,IAAe,EAAO,OAKtB,GACF,GAAc,EAAM,aAAe,EAAY,aAAe,EACzD,CAAC,OAAQ,MAAM,EAAE,SAAS,EAAM,OAAO,GAC1C,IAAe,EAAO,MAInB,CAAE,cAAa,aAAY,CACpC,EAEA,EAA6B,IAAM,CACjC,SAAS,cAAc,IAAI,OAAc,kBAAkB,GAAG,OAAO,CACvE,EAEA,EAAiB,AAAC,GAAgB,CAChC,OAAY,QAAQ,CAAC,EAAU,IAAU,CACvC,GAAI,IAAa,IACf,OAGF,GAAM,GAAS,EAAY,SAAS,GAGpC,EAAO,aAAe,EAAO,QAAU,IAAM,CAC3C,EAAS,SAAS,MAAkB,EAI/B,AADuB,GAAS,qBAAuB,OAAc,qBAAuB,KAE/F,OAAgC,CAEpC,CACF,CAAC,CACH,EAKA,cAAgB,AAAC,GAAU,CACzB,GAAI,CAAC,OAAc,KAAK,CAAK,EAC3B,OAEF,EAAM,eAAe,EACrB,EAAM,gBAAgB,EAGtB,OAAqB,EAGrB,OAAgC,EAGhC,GAAM,GAAc,OAAuB,EAC3C,SAAS,cAAc,MAAM,EAAE,OAAO,CAAW,EAGjD,EAAY,MAAM,SAAW,QAC7B,EAAY,MAAM,OAAS,OAAc,OAGzC,GAAM,GAAS,EAAM,OAAO,SAAW,EAAM,QACvC,EAAS,EAAM,OAAO,SAAW,EAAM,QACvC,CAAE,cAAa,eAAgB,OAAwB,EAAQ,EAAQ,CAAW,EACxF,EAAY,MAAM,IAAM,GAAG,MAC3B,EAAY,MAAM,KAAO,GAAG,MAG5B,EAAY,cAAgB,AAAC,GAAM,EAAE,eAAe,EAGpD,OAAoB,CAAW,CACjC,EAMA,QAAU,AAAC,GAAU,CAEnB,AAAI,AADkB,EAAM,OACV,QAAQ,IAAI,OAAc,kBAAkB,GAG9D,OAAgC,CAClC,EAKA,KAAM,CACJ,OAAgC,EAChC,SAAS,oBAAoB,QAAS,IAAI,EAC1C,EAAoB,GACpB,OAAS,oBAAoB,cAAe,IAAI,EAChD,OAAS,oBAAoB,aAAc,IAAI,CACjD,CACF,EAEO,EAAQ", "names": [] }