From 3f4e796671bf845c3bdac1bf4a0b21a3330de477 Mon Sep 17 00:00:00 2001 From: Johannes Korrek Date: Tue, 28 Sep 2021 09:45:33 +0200 Subject: [PATCH 1/3] implement #27 - adds an api for more direct usage --- README.md | 20 +++++++++++-- docs/ctxmenu.js | 39 ++++++++++++++++-------- docs/ctxmenu.min.js | 2 +- docs/demo.js | 23 +++++++++++++++ docs/index.html | 4 ++- index.d.ts | 11 +++++++ index.js | 39 ++++++++++++++++-------- src/ctxmenu.ts | 62 +++++++++++++++++++++++++++------------ standalone/ctxmenu.d.ts | 11 +++++++ standalone/ctxmenu.js | 39 ++++++++++++++++-------- standalone/ctxmenu.min.js | 2 +- test/demo.ts | 27 ++++++++++++++++- 12 files changed, 218 insertions(+), 61 deletions(-) diff --git a/README.md b/README.md index f5f95cc..ebe7823 100644 --- a/README.md +++ b/README.md @@ -165,11 +165,13 @@ This is a divider item which draws a horizontal line. This library exports a singleton object `ctxmenu`. In the standalone version the singleton is a global variable (`window.ctxmenu`). -It has the following three APIs: +It has the following five APIs: [attach](#ctxmenuattach)\ [update](#ctxmenuupdate)\ -[delete](#ctxmenudelete) +[delete](#ctxmenudelete)\ +[show](#ctxmenushow)\ +[hide](#ctxmenuhide) ### `ctxmenu.attach` ```typescript @@ -199,6 +201,20 @@ ctxmenu.delete(target: string) ``` The delete method is used to delete a context menu and only takes the `target` selector string. +### `ctxmenu.show` +```typescript +ctxmenu.show(ctxmenu: Array, e: MouseEvent) +``` +The `show` method can be used to show a context menu without using the `attach` method to set up a contextmenu for specific elements first. + +This may be useful when integrating with other libraries or frameworks that already provide a contextmenu handler or when trying to show a context menu on a different user interaction (for example showing a context menu when left-clicking a button). + +### `ctxmenu.hide` +```typescript +ctxmenu.hide() +``` +The `hide` method can be used to hide any open context menu. + ## Customize ctxmenu.js uses the following css classes which you might want to overwrite: diff --git a/docs/ctxmenu.js b/docs/ctxmenu.js index b93960c..e6f1f90 100644 --- a/docs/ctxmenu.js +++ b/docs/ctxmenu.js @@ -115,13 +115,13 @@ return; } - _this.closeMenu(); + _this.hide(); }); window.addEventListener("resize", function () { - return _this.closeMenu(); + return _this.hide(); }); window.addEventListener("scroll", function () { - return _this.closeMenu(); + return _this.hide(); }); ContextMenu.addStylesToDom(); } @@ -149,11 +149,12 @@ var handler = function handler(e) { e.stopImmediatePropagation(); - _this2.closeMenu(); + _this2.hide(); var newMenu = beforeRender(_toConsumableArray(ctxMenu), e); - _this2.menu = _this2.generateDOM(newMenu, e); - document.body.appendChild(_this2.menu); + + _this2.openMenu(newMenu, e); + e.preventDefault(); }; @@ -194,8 +195,16 @@ delete this.cache[target]; } }, { - key: "closeMenu", - value: function closeMenu() { + key: "show", + value: function show(ctxMenu, e) { + e.stopImmediatePropagation(); + this.hide(); + this.openMenu(_toConsumableArray(ctxMenu), e); + e.preventDefault(); + } + }, { + key: "hide", + value: function hide() { var menu = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.menu; var _a; @@ -211,6 +220,12 @@ (_a = menu.parentElement) === null || _a === void 0 ? void 0 : _a.removeChild(menu); } } + }, { + key: "openMenu", + value: function openMenu(ctxMenu, e) { + this.menu = this.generateDOM(_toConsumableArray(ctxMenu), e); + document.body.appendChild(this.menu); + } }, { key: "debounce", value: function debounce(target, action) { @@ -244,7 +259,7 @@ var subMenu = (_a = li.parentElement) === null || _a === void 0 ? void 0 : _a.querySelector("ul"); if (subMenu && subMenu.parentElement !== li) { - _this3.closeMenu(subMenu); + _this3.hide(subMenu); } }); @@ -269,14 +284,14 @@ li.addEventListener("click", function (e) { item.action(e); - _this3.closeMenu(); + _this3.hide(); }); } else if (ContextMenu.itemIsAnchor(item)) { var a = document.createElement("a"); elem ? a.append(elem) : a.innerHTML = html ? html : text; a.onclick = function () { - return _this3.closeMenu(); + return _this3.hide(); }; a.href = ContextMenu.getProp(item.href); @@ -384,7 +399,7 @@ var subMenu = (_a = listElement.parentElement) === null || _a === void 0 ? void 0 : _a.querySelector("li > ul"); if (subMenu && subMenu.parentElement !== listElement) { - this.closeMenu(subMenu); + this.hide(subMenu); } listElement.appendChild(this.generateDOM(ctxMenu, listElement)); diff --git a/docs/ctxmenu.min.js b/docs/ctxmenu.min.js index a6f5882..468db18 100644 --- a/docs/ctxmenu.min.js +++ b/docs/ctxmenu.min.js @@ -1 +1 @@ -"use strict";(function(){function b(d,e,g){function a(j,i){if(!e[j]){if(!d[j]){var f="function"==typeof require&&require;if(!i&&f)return f(j,!0);if(h)return h(j,!0);var c=new Error("Cannot find module '"+j+"'");throw c.code="MODULE_NOT_FOUND",c}var k=e[j]={exports:{}};d[j][0].call(k.exports,function(b){var c=d[j][1][b];return a(c||b)},k,k.exports,b,d,e,g)}return e[j].exports}for(var h="function"==typeof require&&require,c=0;ca.length)&&(b=a.length);for(var c=0,d=Array(b);c".concat(b.getProp(c.text),""),i=b.getProp(c.element);if(i?f.append(i):f.innerHTML=g?g:h,f.title=b.getProp(c.tooltip)||"",c.style&&f.setAttribute("style",b.getProp(c.style)),!b.itemIsInteractive(c))f.setAttribute("style","font-weight: bold; margin-left: -5px;"+f.getAttribute("style"));else if(!!b.getProp(c.disabled))f.classList.add("disabled"),b.itemIsSubMenu(c)&&f.classList.add("submenu");else if(f.classList.add("interactive"),b.itemIsAction(c))f.addEventListener("click",function(a){c.action(a),d.closeMenu()});else if(b.itemIsAnchor(c)){var j=document.createElement("a");i?j.append(i):j.innerHTML=g?g:h,j.onclick=function(){return d.closeMenu()},j.href=b.getProp(c.href),c.hasOwnProperty("download")&&(j.download=b.getProp(c.download)),c.hasOwnProperty("target")&&(j.target=b.getProp(c.target)),f.childNodes.forEach(function(a){return a.remove()}),f.append(j)}else b.itemIsSubMenu(c)&&(0===b.getProp(c.subMenu).length?f.classList.add("disabled"):(f.classList.add("submenu"),d.debounce(f,function(a){var e=f.querySelector("ul");e||d.openSubMenu(a,b.getProp(c.subMenu),f)})));b.getProp(c.icon)&&(f.classList.add("icon"),f.innerHTML+=""))}e.appendChild(f)}),e.style.position="fixed",e.className="ctxmenu";var f=b.getBounding(e),g={x:0,y:0};if(c instanceof Element){var h=c.getBoundingClientRect();g={x:"r"===this.hdir?h.left+h.width:h.left-f.width,y:h.top+("d"===this.vdir?4:-12)};var i=this.getPosition(f,g);g.x!==i.x&&(this.hdir="r"===this.hdir?"l":"r",g.x="r"===this.hdir?h.left+h.width:h.left-f.width),g.y!==i.y&&(this.vdir="u"===this.vdir?"d":"u",g.y=i.y),g=this.getPosition(f,g)}else g=this.getPosition(f,{x:c.clientX,y:c.clientY});return e.style.left=g.x+"px",e.style.top=g.y+"px",e.addEventListener("contextmenu",function(a){a.stopPropagation(),a.preventDefault()}),e.addEventListener("click",function(a){var b=a.target instanceof Element&&a.target.parentElement;b&&"interactive"!==b.className&&a.stopPropagation()}),e}},{key:"openSubMenu",value:function(a,b,c){var d,e=null===(d=c.parentElement)||void 0===d?void 0:d.querySelector("li > ul");e&&e.parentElement!==c&&this.closeMenu(e),c.appendChild(this.generateDOM(b,c))}},{key:"getPosition",value:function(a,b){return{x:"r"===this.hdir?b.x+a.width>window.innerWidth?window.innerWidth-a.width:b.x:0>b.x?0:b.x,y:"d"===this.vdir?b.y+a.height>window.innerHeight?window.innerHeight-a.height:b.y:0>b.y?0:b.y}}}],[{key:"getInstance",value:function(){return b.instance||(b.instance=new b),b.instance}},{key:"getBounding",value:function(a){var b=a.cloneNode(!0);b.style.visibility="hidden",document.body.appendChild(b);var c=b.getBoundingClientRect();return document.body.removeChild(b),c}},{key:"getProp",value:function(a){return"function"==typeof a?a():a}},{key:"itemIsInteractive",value:function(a){return this.itemIsAction(a)||this.itemIsAnchor(a)||this.itemIsSubMenu(a)||this.itemIsCustom(a)}},{key:"itemIsAction",value:function(a){return a.hasOwnProperty("action")}},{key:"itemIsAnchor",value:function(a){return a.hasOwnProperty("href")}},{key:"itemIsDivider",value:function(a){return a.hasOwnProperty("isDivider")}},{key:"itemIsSubMenu",value:function(a){return a.hasOwnProperty("subMenu")}},{key:"itemIsCustom",value:function(a){return a.hasOwnProperty("html")||a.hasOwnProperty("element")}},{key:"addStylesToDom",value:function(){var a=function(){var b=Object.entries({".ctxmenu":{border:"1px solid #999",padding:"2px 0",boxShadow:"3px 3px 3px #aaa",background:"#fff",margin:"0",fontSize:"15px",fontFamily:"Verdana, sans-serif",zIndex:"9999"},".ctxmenu li":{margin:"1px 0",display:"block",position:"relative",userSelect:"none",webkitUserSelect:"none"},".ctxmenu li span":{display:"block",padding:"2px 20px",cursor:"default"},".ctxmenu li a":{color:"inherit",textDecoration:"none"},".ctxmenu li.icon":{paddingLeft:"15px"},".ctxmenu img.icon":{position:"absolute",width:"18px",left:"10px",top:"2px"},".ctxmenu li.disabled":{color:"#ccc"},".ctxmenu li.divider":{borderBottom:"1px solid #aaa",margin:"5px 0"},".ctxmenu li.interactive:hover":{background:"rgba(0,0,0,0.1)"},".ctxmenu li.submenu::after":{content:"''",position:"absolute",display:"block",top:"0",bottom:"0",right:"0.4em",margin:"auto",borderRight:"1px solid #000",borderTop:"1px solid #000",transform:"rotate(45deg)",width:"0.3rem",height:"0.3rem",marginRight:"0.1rem"},".ctxmenu li.submenu.disabled::after":{borderColor:"#ccc"}}).map(function(a){return"".concat(a[0]," { ").concat(Object.assign(document.createElement("p").style,a[1]).cssText," }")}),c=document.head.insertBefore(document.createElement("style"),document.head.childNodes[0]);b.forEach(function(a){var b;return null===(b=c.sheet)||void 0===b?void 0:b.insertRule(a)}),a=function(){}};"loading"===document.readyState?document.addEventListener("readystatechange",function(){"loading"!==document.readyState&&a()}):a()}}]),b}();c.ctxmenu=m.getInstance()},{}],2:[function(a,b,c){"use strict";Object.defineProperty(c,"__esModule",{value:!0});var d=a("./ctxmenu");window.ctxmenu=d.ctxmenu},{"./ctxmenu":1}]},{},[2]); +"use strict";(function(){function b(d,e,g){function a(j,i){if(!e[j]){if(!d[j]){var f="function"==typeof require&&require;if(!i&&f)return f(j,!0);if(h)return h(j,!0);var c=new Error("Cannot find module '"+j+"'");throw c.code="MODULE_NOT_FOUND",c}var k=e[j]={exports:{}};d[j][0].call(k.exports,function(b){var c=d[j][1][b];return a(c||b)},k,k.exports,b,d,e,g)}return e[j].exports}for(var h="function"==typeof require&&require,c=0;ca.length)&&(b=a.length);for(var c=0,d=Array(b);c".concat(b.getProp(c.text),""),i=b.getProp(c.element);if(i?f.append(i):f.innerHTML=g?g:h,f.title=b.getProp(c.tooltip)||"",c.style&&f.setAttribute("style",b.getProp(c.style)),!b.itemIsInteractive(c))f.setAttribute("style","font-weight: bold; margin-left: -5px;"+f.getAttribute("style"));else if(!!b.getProp(c.disabled))f.classList.add("disabled"),b.itemIsSubMenu(c)&&f.classList.add("submenu");else if(f.classList.add("interactive"),b.itemIsAction(c))f.addEventListener("click",function(a){c.action(a),d.hide()});else if(b.itemIsAnchor(c)){var j=document.createElement("a");i?j.append(i):j.innerHTML=g?g:h,j.onclick=function(){return d.hide()},j.href=b.getProp(c.href),c.hasOwnProperty("download")&&(j.download=b.getProp(c.download)),c.hasOwnProperty("target")&&(j.target=b.getProp(c.target)),f.childNodes.forEach(function(a){return a.remove()}),f.append(j)}else b.itemIsSubMenu(c)&&(0===b.getProp(c.subMenu).length?f.classList.add("disabled"):(f.classList.add("submenu"),d.debounce(f,function(a){var e=f.querySelector("ul");e||d.openSubMenu(a,b.getProp(c.subMenu),f)})));b.getProp(c.icon)&&(f.classList.add("icon"),f.innerHTML+=""))}e.appendChild(f)}),e.style.position="fixed",e.className="ctxmenu";var f=b.getBounding(e),g={x:0,y:0};if(c instanceof Element){var h=c.getBoundingClientRect();g={x:"r"===this.hdir?h.left+h.width:h.left-f.width,y:h.top+("d"===this.vdir?4:-12)};var i=this.getPosition(f,g);g.x!==i.x&&(this.hdir="r"===this.hdir?"l":"r",g.x="r"===this.hdir?h.left+h.width:h.left-f.width),g.y!==i.y&&(this.vdir="u"===this.vdir?"d":"u",g.y=i.y),g=this.getPosition(f,g)}else g=this.getPosition(f,{x:c.clientX,y:c.clientY});return e.style.left=g.x+"px",e.style.top=g.y+"px",e.addEventListener("contextmenu",function(a){a.stopPropagation(),a.preventDefault()}),e.addEventListener("click",function(a){var b=a.target instanceof Element&&a.target.parentElement;b&&"interactive"!==b.className&&a.stopPropagation()}),e}},{key:"openSubMenu",value:function(a,b,c){var d,e=null===(d=c.parentElement)||void 0===d?void 0:d.querySelector("li > ul");e&&e.parentElement!==c&&this.hide(e),c.appendChild(this.generateDOM(b,c))}},{key:"getPosition",value:function(a,b){return{x:"r"===this.hdir?b.x+a.width>window.innerWidth?window.innerWidth-a.width:b.x:0>b.x?0:b.x,y:"d"===this.vdir?b.y+a.height>window.innerHeight?window.innerHeight-a.height:b.y:0>b.y?0:b.y}}}],[{key:"getInstance",value:function(){return b.instance||(b.instance=new b),b.instance}},{key:"getBounding",value:function(a){var b=a.cloneNode(!0);b.style.visibility="hidden",document.body.appendChild(b);var c=b.getBoundingClientRect();return document.body.removeChild(b),c}},{key:"getProp",value:function(a){return"function"==typeof a?a():a}},{key:"itemIsInteractive",value:function(a){return this.itemIsAction(a)||this.itemIsAnchor(a)||this.itemIsSubMenu(a)||this.itemIsCustom(a)}},{key:"itemIsAction",value:function(a){return a.hasOwnProperty("action")}},{key:"itemIsAnchor",value:function(a){return a.hasOwnProperty("href")}},{key:"itemIsDivider",value:function(a){return a.hasOwnProperty("isDivider")}},{key:"itemIsSubMenu",value:function(a){return a.hasOwnProperty("subMenu")}},{key:"itemIsCustom",value:function(a){return a.hasOwnProperty("html")||a.hasOwnProperty("element")}},{key:"addStylesToDom",value:function(){var a=function(){var b=Object.entries({".ctxmenu":{border:"1px solid #999",padding:"2px 0",boxShadow:"3px 3px 3px #aaa",background:"#fff",margin:"0",fontSize:"15px",fontFamily:"Verdana, sans-serif",zIndex:"9999"},".ctxmenu li":{margin:"1px 0",display:"block",position:"relative",userSelect:"none",webkitUserSelect:"none"},".ctxmenu li span":{display:"block",padding:"2px 20px",cursor:"default"},".ctxmenu li a":{color:"inherit",textDecoration:"none"},".ctxmenu li.icon":{paddingLeft:"15px"},".ctxmenu img.icon":{position:"absolute",width:"18px",left:"10px",top:"2px"},".ctxmenu li.disabled":{color:"#ccc"},".ctxmenu li.divider":{borderBottom:"1px solid #aaa",margin:"5px 0"},".ctxmenu li.interactive:hover":{background:"rgba(0,0,0,0.1)"},".ctxmenu li.submenu::after":{content:"''",position:"absolute",display:"block",top:"0",bottom:"0",right:"0.4em",margin:"auto",borderRight:"1px solid #000",borderTop:"1px solid #000",transform:"rotate(45deg)",width:"0.3rem",height:"0.3rem",marginRight:"0.1rem"},".ctxmenu li.submenu.disabled::after":{borderColor:"#ccc"}}).map(function(a){return"".concat(a[0]," { ").concat(Object.assign(document.createElement("p").style,a[1]).cssText," }")}),c=document.head.insertBefore(document.createElement("style"),document.head.childNodes[0]);b.forEach(function(a){var b;return null===(b=c.sheet)||void 0===b?void 0:b.insertRule(a)}),a=function(){}};"loading"===document.readyState?document.addEventListener("readystatechange",function(){"loading"!==document.readyState&&a()}):a()}}]),b}();c.ctxmenu=m.getInstance()},{}],2:[function(a,b,c){"use strict";Object.defineProperty(c,"__esModule",{value:!0});var d=a("./ctxmenu");window.ctxmenu=d.ctxmenu},{"./ctxmenu":1}]},{},[2]); diff --git a/docs/demo.js b/docs/demo.js index 66636d7..832e4fe 100644 --- a/docs/demo.js +++ b/docs/demo.js @@ -185,3 +185,26 @@ function toggleDarkMode() { toggle.innerHTML = "Back to normal!"; } } +function showButtonContextMenu(e) { + ctxmenu.show([ + { + text: "Downloads", + subMenu: [ + { + text: "ctxmenu.js", + href: "ctxmenu.js", + download: "" + }, + { + text: "ctxmenu.min.js", + href: "ctxmenu.min.js", + download: "" + } + ] + }, + { + text: "Documentation (github)", + href: "https://www.github.com/nkappler/ctxmenu" + } + ], e); +} diff --git a/docs/index.html b/docs/index.html index 520ac46..cea76ab 100644 --- a/docs/index.html +++ b/docs/index.html @@ -168,6 +168,8 @@

ctxmenu.js

run any arbitrary javascript function
  • Fully customizable! Fancy dark mode?
  • +
  • Show a context menu "on demand" for any mouse event +
  • Mobile support
  • @@ -176,4 +178,4 @@

    ctxmenu.js

    - \ No newline at end of file + diff --git a/index.d.ts b/index.d.ts index 290f5c8..f6025f4 100644 --- a/index.d.ts +++ b/index.d.ts @@ -91,6 +91,17 @@ declare module "ctxmenu" { * @param target A selector string to define the target node (eg `'body'`, or `'#someID'`) */ delete(target: string): void; + /** + * Create & show a context menu without attaching it to a specific element, based on the passed event. + * This might be useful, when integrating with other libraries / components that already provide a contextmenu handler. + * @param ctxMenu An array of objects defining the menu layout. + * @param e The original contextmenu event, used to position the tooltip + */ + show(ctxMenu: CTXMenu, e: MouseEvent): void; + /** + * Close any contextmenu that might be open at the moment + */ + hide(): void; } export const ctxmenu: CTXMenuSingleton; } diff --git a/index.js b/index.js index 4a3cd86..e311e0f 100644 --- a/index.js +++ b/index.js @@ -40,13 +40,13 @@ var ContextMenu = /*#__PURE__*/function () { return; } - _this.closeMenu(); + _this.hide(); }); window.addEventListener("resize", function () { - return _this.closeMenu(); + return _this.hide(); }); window.addEventListener("scroll", function () { - return _this.closeMenu(); + return _this.hide(); }); ContextMenu.addStylesToDom(); } @@ -74,11 +74,12 @@ var ContextMenu = /*#__PURE__*/function () { var handler = function handler(e) { e.stopImmediatePropagation(); - _this2.closeMenu(); + _this2.hide(); var newMenu = beforeRender(_toConsumableArray(ctxMenu), e); - _this2.menu = _this2.generateDOM(newMenu, e); - document.body.appendChild(_this2.menu); + + _this2.openMenu(newMenu, e); + e.preventDefault(); }; @@ -119,8 +120,16 @@ var ContextMenu = /*#__PURE__*/function () { delete this.cache[target]; } }, { - key: "closeMenu", - value: function closeMenu() { + key: "show", + value: function show(ctxMenu, e) { + e.stopImmediatePropagation(); + this.hide(); + this.openMenu(_toConsumableArray(ctxMenu), e); + e.preventDefault(); + } + }, { + key: "hide", + value: function hide() { var menu = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.menu; var _a; @@ -136,6 +145,12 @@ var ContextMenu = /*#__PURE__*/function () { (_a = menu.parentElement) === null || _a === void 0 ? void 0 : _a.removeChild(menu); } } + }, { + key: "openMenu", + value: function openMenu(ctxMenu, e) { + this.menu = this.generateDOM(_toConsumableArray(ctxMenu), e); + document.body.appendChild(this.menu); + } }, { key: "debounce", value: function debounce(target, action) { @@ -169,7 +184,7 @@ var ContextMenu = /*#__PURE__*/function () { var subMenu = (_a = li.parentElement) === null || _a === void 0 ? void 0 : _a.querySelector("ul"); if (subMenu && subMenu.parentElement !== li) { - _this3.closeMenu(subMenu); + _this3.hide(subMenu); } }); @@ -194,14 +209,14 @@ var ContextMenu = /*#__PURE__*/function () { li.addEventListener("click", function (e) { item.action(e); - _this3.closeMenu(); + _this3.hide(); }); } else if (ContextMenu.itemIsAnchor(item)) { var a = document.createElement("a"); elem ? a.append(elem) : a.innerHTML = html ? html : text; a.onclick = function () { - return _this3.closeMenu(); + return _this3.hide(); }; a.href = ContextMenu.getProp(item.href); @@ -309,7 +324,7 @@ var ContextMenu = /*#__PURE__*/function () { var subMenu = (_a = listElement.parentElement) === null || _a === void 0 ? void 0 : _a.querySelector("li > ul"); if (subMenu && subMenu.parentElement !== listElement) { - this.closeMenu(subMenu); + this.hide(subMenu); } listElement.appendChild(this.generateDOM(ctxMenu, listElement)); diff --git a/src/ctxmenu.ts b/src/ctxmenu.ts index 7fd4999..77fa822 100644 --- a/src/ctxmenu.ts +++ b/src/ctxmenu.ts @@ -101,6 +101,16 @@ export interface CTXMenuSingleton { * @param target A selector string to define the target node (eg `'body'`, or `'#someID'`) */ delete(target: string): void; + /** + * Create & show a context menu without attaching it to a specific element, based on the passed mouse event. + * @param ctxMenu An array of objects defining the menu layout. + * @param e The original mouse event, used to position the tooltip + */ + show(ctxMenu: CTXMenu, e: MouseEvent): void; + /** + * Close any contextmenu that might be open at the moment + */ + hide(): void; } type CTXHandler = Exclude; @@ -130,10 +140,10 @@ class ContextMenu implements CTXMenuSingleton { if (item && item.className === "interactive") { return; } - this.closeMenu(); + this.hide(); }); - window.addEventListener("resize", () => this.closeMenu()); - window.addEventListener("scroll", () => this.closeMenu()); + window.addEventListener("resize", () => this.hide()); + window.addEventListener("scroll", () => this.hide()); ContextMenu.addStylesToDom(); } @@ -157,11 +167,10 @@ class ContextMenu implements CTXMenuSingleton { const handler: CTXHandler = e => { e.stopImmediatePropagation(); //close any open menu - this.closeMenu(); + this.hide(); const newMenu = beforeRender([...ctxMenu], e); - this.menu = this.generateDOM(newMenu, e); - document.body.appendChild(this.menu); + this.openMenu(newMenu, e); e.preventDefault(); }; @@ -196,18 +205,33 @@ class ContextMenu implements CTXMenuSingleton { t.removeEventListener("contextmenu", o.handler); delete this.cache[target]; } + + public show(ctxMenu: CTXMenu, e: MouseEvent) { + e.stopImmediatePropagation(); + //close any open menu + this.hide(); - private closeMenu(menu: Element | undefined = this.menu) { - //reset directions - this.hdir = "r"; - this.vdir = "d"; + this.openMenu([...ctxMenu], e); - if (menu) { - if (menu === this.menu) { - delete this.menu; - } - menu.parentElement?.removeChild(menu); + e.preventDefault(); + } + + public hide(menu: Element | undefined = this.menu) { + //reset directions + this.hdir = "r"; + this.vdir = "d"; + + if (menu) { + if (menu === this.menu) { + delete this.menu; } + menu.parentElement?.removeChild(menu); + } + } + + private openMenu(ctxMenu: CTXMenu, e: MouseEvent) { + this.menu = this.generateDOM([...ctxMenu], e); + document.body.appendChild(this.menu); } /** @@ -240,7 +264,7 @@ class ContextMenu implements CTXMenuSingleton { this.debounce(li, () => { const subMenu = li.parentElement?.querySelector("ul"); if (subMenu && subMenu.parentElement !== li) { - this.closeMenu(subMenu); + this.hide(subMenu); } }); @@ -262,7 +286,7 @@ class ContextMenu implements CTXMenuSingleton { if (ContextMenu.itemIsAction(item)) { li.addEventListener("click", (e) => { item.action(e); - this.closeMenu(); + this.hide(); } ); } @@ -271,7 +295,7 @@ class ContextMenu implements CTXMenuSingleton { elem ? a.append(elem) : a.innerHTML = html ? html : text; - a.onclick = () => this.closeMenu(); + a.onclick = () => this.hide(); a.href = ContextMenu.getProp(item.href); if (item.hasOwnProperty("download")) { a.download = ContextMenu.getProp(item.download!) } if (item.hasOwnProperty("target")) { a.target = ContextMenu.getProp(item.target!) } @@ -354,7 +378,7 @@ class ContextMenu implements CTXMenuSingleton { // check if other submenus on this level are open and close them const subMenu = listElement.parentElement?.querySelector("li > ul"); if (subMenu && subMenu.parentElement !== listElement) { - this.closeMenu(subMenu); + this.hide(subMenu); } listElement.appendChild(this.generateDOM(ctxMenu, listElement)); } diff --git a/standalone/ctxmenu.d.ts b/standalone/ctxmenu.d.ts index 469a3f0..5e06dea 100644 --- a/standalone/ctxmenu.d.ts +++ b/standalone/ctxmenu.d.ts @@ -91,6 +91,17 @@ declare module "ctxmenu" { * @param target A selector string to define the target node (eg `'body'`, or `'#someID'`) */ delete(target: string): void; + /** + * Create & show a context menu without attaching it to a specific element, based on the passed event. + * This might be useful, when integrating with other libraries / components that already provide a contextmenu handler. + * @param ctxMenu An array of objects defining the menu layout. + * @param e The original contextmenu event, used to position the tooltip + */ + show(ctxMenu: CTXMenu, e: MouseEvent): void; + /** + * Close any contextmenu that might be open at the moment + */ + hide(): void; } export const ctxmenu: CTXMenuSingleton; } diff --git a/standalone/ctxmenu.js b/standalone/ctxmenu.js index 2f9d3f1..c408056 100644 --- a/standalone/ctxmenu.js +++ b/standalone/ctxmenu.js @@ -41,13 +41,13 @@ var ContextMenu = /*#__PURE__*/function () { return; } - _this.closeMenu(); + _this.hide(); }); window.addEventListener("resize", function () { - return _this.closeMenu(); + return _this.hide(); }); window.addEventListener("scroll", function () { - return _this.closeMenu(); + return _this.hide(); }); ContextMenu.addStylesToDom(); } @@ -75,11 +75,12 @@ var ContextMenu = /*#__PURE__*/function () { var handler = function handler(e) { e.stopImmediatePropagation(); - _this2.closeMenu(); + _this2.hide(); var newMenu = beforeRender(_toConsumableArray(ctxMenu), e); - _this2.menu = _this2.generateDOM(newMenu, e); - document.body.appendChild(_this2.menu); + + _this2.openMenu(newMenu, e); + e.preventDefault(); }; @@ -120,8 +121,16 @@ var ContextMenu = /*#__PURE__*/function () { delete this.cache[target]; } }, { - key: "closeMenu", - value: function closeMenu() { + key: "show", + value: function show(ctxMenu, e) { + e.stopImmediatePropagation(); + this.hide(); + this.openMenu(_toConsumableArray(ctxMenu), e); + e.preventDefault(); + } + }, { + key: "hide", + value: function hide() { var menu = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.menu; var _a; @@ -137,6 +146,12 @@ var ContextMenu = /*#__PURE__*/function () { (_a = menu.parentElement) === null || _a === void 0 ? void 0 : _a.removeChild(menu); } } + }, { + key: "openMenu", + value: function openMenu(ctxMenu, e) { + this.menu = this.generateDOM(_toConsumableArray(ctxMenu), e); + document.body.appendChild(this.menu); + } }, { key: "debounce", value: function debounce(target, action) { @@ -170,7 +185,7 @@ var ContextMenu = /*#__PURE__*/function () { var subMenu = (_a = li.parentElement) === null || _a === void 0 ? void 0 : _a.querySelector("ul"); if (subMenu && subMenu.parentElement !== li) { - _this3.closeMenu(subMenu); + _this3.hide(subMenu); } }); @@ -195,14 +210,14 @@ var ContextMenu = /*#__PURE__*/function () { li.addEventListener("click", function (e) { item.action(e); - _this3.closeMenu(); + _this3.hide(); }); } else if (ContextMenu.itemIsAnchor(item)) { var a = document.createElement("a"); elem ? a.append(elem) : a.innerHTML = html ? html : text; a.onclick = function () { - return _this3.closeMenu(); + return _this3.hide(); }; a.href = ContextMenu.getProp(item.href); @@ -310,7 +325,7 @@ var ContextMenu = /*#__PURE__*/function () { var subMenu = (_a = listElement.parentElement) === null || _a === void 0 ? void 0 : _a.querySelector("li > ul"); if (subMenu && subMenu.parentElement !== listElement) { - this.closeMenu(subMenu); + this.hide(subMenu); } listElement.appendChild(this.generateDOM(ctxMenu, listElement)); diff --git a/standalone/ctxmenu.min.js b/standalone/ctxmenu.min.js index a6f5882..468db18 100644 --- a/standalone/ctxmenu.min.js +++ b/standalone/ctxmenu.min.js @@ -1 +1 @@ -"use strict";(function(){function b(d,e,g){function a(j,i){if(!e[j]){if(!d[j]){var f="function"==typeof require&&require;if(!i&&f)return f(j,!0);if(h)return h(j,!0);var c=new Error("Cannot find module '"+j+"'");throw c.code="MODULE_NOT_FOUND",c}var k=e[j]={exports:{}};d[j][0].call(k.exports,function(b){var c=d[j][1][b];return a(c||b)},k,k.exports,b,d,e,g)}return e[j].exports}for(var h="function"==typeof require&&require,c=0;ca.length)&&(b=a.length);for(var c=0,d=Array(b);c".concat(b.getProp(c.text),""),i=b.getProp(c.element);if(i?f.append(i):f.innerHTML=g?g:h,f.title=b.getProp(c.tooltip)||"",c.style&&f.setAttribute("style",b.getProp(c.style)),!b.itemIsInteractive(c))f.setAttribute("style","font-weight: bold; margin-left: -5px;"+f.getAttribute("style"));else if(!!b.getProp(c.disabled))f.classList.add("disabled"),b.itemIsSubMenu(c)&&f.classList.add("submenu");else if(f.classList.add("interactive"),b.itemIsAction(c))f.addEventListener("click",function(a){c.action(a),d.closeMenu()});else if(b.itemIsAnchor(c)){var j=document.createElement("a");i?j.append(i):j.innerHTML=g?g:h,j.onclick=function(){return d.closeMenu()},j.href=b.getProp(c.href),c.hasOwnProperty("download")&&(j.download=b.getProp(c.download)),c.hasOwnProperty("target")&&(j.target=b.getProp(c.target)),f.childNodes.forEach(function(a){return a.remove()}),f.append(j)}else b.itemIsSubMenu(c)&&(0===b.getProp(c.subMenu).length?f.classList.add("disabled"):(f.classList.add("submenu"),d.debounce(f,function(a){var e=f.querySelector("ul");e||d.openSubMenu(a,b.getProp(c.subMenu),f)})));b.getProp(c.icon)&&(f.classList.add("icon"),f.innerHTML+=""))}e.appendChild(f)}),e.style.position="fixed",e.className="ctxmenu";var f=b.getBounding(e),g={x:0,y:0};if(c instanceof Element){var h=c.getBoundingClientRect();g={x:"r"===this.hdir?h.left+h.width:h.left-f.width,y:h.top+("d"===this.vdir?4:-12)};var i=this.getPosition(f,g);g.x!==i.x&&(this.hdir="r"===this.hdir?"l":"r",g.x="r"===this.hdir?h.left+h.width:h.left-f.width),g.y!==i.y&&(this.vdir="u"===this.vdir?"d":"u",g.y=i.y),g=this.getPosition(f,g)}else g=this.getPosition(f,{x:c.clientX,y:c.clientY});return e.style.left=g.x+"px",e.style.top=g.y+"px",e.addEventListener("contextmenu",function(a){a.stopPropagation(),a.preventDefault()}),e.addEventListener("click",function(a){var b=a.target instanceof Element&&a.target.parentElement;b&&"interactive"!==b.className&&a.stopPropagation()}),e}},{key:"openSubMenu",value:function(a,b,c){var d,e=null===(d=c.parentElement)||void 0===d?void 0:d.querySelector("li > ul");e&&e.parentElement!==c&&this.closeMenu(e),c.appendChild(this.generateDOM(b,c))}},{key:"getPosition",value:function(a,b){return{x:"r"===this.hdir?b.x+a.width>window.innerWidth?window.innerWidth-a.width:b.x:0>b.x?0:b.x,y:"d"===this.vdir?b.y+a.height>window.innerHeight?window.innerHeight-a.height:b.y:0>b.y?0:b.y}}}],[{key:"getInstance",value:function(){return b.instance||(b.instance=new b),b.instance}},{key:"getBounding",value:function(a){var b=a.cloneNode(!0);b.style.visibility="hidden",document.body.appendChild(b);var c=b.getBoundingClientRect();return document.body.removeChild(b),c}},{key:"getProp",value:function(a){return"function"==typeof a?a():a}},{key:"itemIsInteractive",value:function(a){return this.itemIsAction(a)||this.itemIsAnchor(a)||this.itemIsSubMenu(a)||this.itemIsCustom(a)}},{key:"itemIsAction",value:function(a){return a.hasOwnProperty("action")}},{key:"itemIsAnchor",value:function(a){return a.hasOwnProperty("href")}},{key:"itemIsDivider",value:function(a){return a.hasOwnProperty("isDivider")}},{key:"itemIsSubMenu",value:function(a){return a.hasOwnProperty("subMenu")}},{key:"itemIsCustom",value:function(a){return a.hasOwnProperty("html")||a.hasOwnProperty("element")}},{key:"addStylesToDom",value:function(){var a=function(){var b=Object.entries({".ctxmenu":{border:"1px solid #999",padding:"2px 0",boxShadow:"3px 3px 3px #aaa",background:"#fff",margin:"0",fontSize:"15px",fontFamily:"Verdana, sans-serif",zIndex:"9999"},".ctxmenu li":{margin:"1px 0",display:"block",position:"relative",userSelect:"none",webkitUserSelect:"none"},".ctxmenu li span":{display:"block",padding:"2px 20px",cursor:"default"},".ctxmenu li a":{color:"inherit",textDecoration:"none"},".ctxmenu li.icon":{paddingLeft:"15px"},".ctxmenu img.icon":{position:"absolute",width:"18px",left:"10px",top:"2px"},".ctxmenu li.disabled":{color:"#ccc"},".ctxmenu li.divider":{borderBottom:"1px solid #aaa",margin:"5px 0"},".ctxmenu li.interactive:hover":{background:"rgba(0,0,0,0.1)"},".ctxmenu li.submenu::after":{content:"''",position:"absolute",display:"block",top:"0",bottom:"0",right:"0.4em",margin:"auto",borderRight:"1px solid #000",borderTop:"1px solid #000",transform:"rotate(45deg)",width:"0.3rem",height:"0.3rem",marginRight:"0.1rem"},".ctxmenu li.submenu.disabled::after":{borderColor:"#ccc"}}).map(function(a){return"".concat(a[0]," { ").concat(Object.assign(document.createElement("p").style,a[1]).cssText," }")}),c=document.head.insertBefore(document.createElement("style"),document.head.childNodes[0]);b.forEach(function(a){var b;return null===(b=c.sheet)||void 0===b?void 0:b.insertRule(a)}),a=function(){}};"loading"===document.readyState?document.addEventListener("readystatechange",function(){"loading"!==document.readyState&&a()}):a()}}]),b}();c.ctxmenu=m.getInstance()},{}],2:[function(a,b,c){"use strict";Object.defineProperty(c,"__esModule",{value:!0});var d=a("./ctxmenu");window.ctxmenu=d.ctxmenu},{"./ctxmenu":1}]},{},[2]); +"use strict";(function(){function b(d,e,g){function a(j,i){if(!e[j]){if(!d[j]){var f="function"==typeof require&&require;if(!i&&f)return f(j,!0);if(h)return h(j,!0);var c=new Error("Cannot find module '"+j+"'");throw c.code="MODULE_NOT_FOUND",c}var k=e[j]={exports:{}};d[j][0].call(k.exports,function(b){var c=d[j][1][b];return a(c||b)},k,k.exports,b,d,e,g)}return e[j].exports}for(var h="function"==typeof require&&require,c=0;ca.length)&&(b=a.length);for(var c=0,d=Array(b);c".concat(b.getProp(c.text),""),i=b.getProp(c.element);if(i?f.append(i):f.innerHTML=g?g:h,f.title=b.getProp(c.tooltip)||"",c.style&&f.setAttribute("style",b.getProp(c.style)),!b.itemIsInteractive(c))f.setAttribute("style","font-weight: bold; margin-left: -5px;"+f.getAttribute("style"));else if(!!b.getProp(c.disabled))f.classList.add("disabled"),b.itemIsSubMenu(c)&&f.classList.add("submenu");else if(f.classList.add("interactive"),b.itemIsAction(c))f.addEventListener("click",function(a){c.action(a),d.hide()});else if(b.itemIsAnchor(c)){var j=document.createElement("a");i?j.append(i):j.innerHTML=g?g:h,j.onclick=function(){return d.hide()},j.href=b.getProp(c.href),c.hasOwnProperty("download")&&(j.download=b.getProp(c.download)),c.hasOwnProperty("target")&&(j.target=b.getProp(c.target)),f.childNodes.forEach(function(a){return a.remove()}),f.append(j)}else b.itemIsSubMenu(c)&&(0===b.getProp(c.subMenu).length?f.classList.add("disabled"):(f.classList.add("submenu"),d.debounce(f,function(a){var e=f.querySelector("ul");e||d.openSubMenu(a,b.getProp(c.subMenu),f)})));b.getProp(c.icon)&&(f.classList.add("icon"),f.innerHTML+=""))}e.appendChild(f)}),e.style.position="fixed",e.className="ctxmenu";var f=b.getBounding(e),g={x:0,y:0};if(c instanceof Element){var h=c.getBoundingClientRect();g={x:"r"===this.hdir?h.left+h.width:h.left-f.width,y:h.top+("d"===this.vdir?4:-12)};var i=this.getPosition(f,g);g.x!==i.x&&(this.hdir="r"===this.hdir?"l":"r",g.x="r"===this.hdir?h.left+h.width:h.left-f.width),g.y!==i.y&&(this.vdir="u"===this.vdir?"d":"u",g.y=i.y),g=this.getPosition(f,g)}else g=this.getPosition(f,{x:c.clientX,y:c.clientY});return e.style.left=g.x+"px",e.style.top=g.y+"px",e.addEventListener("contextmenu",function(a){a.stopPropagation(),a.preventDefault()}),e.addEventListener("click",function(a){var b=a.target instanceof Element&&a.target.parentElement;b&&"interactive"!==b.className&&a.stopPropagation()}),e}},{key:"openSubMenu",value:function(a,b,c){var d,e=null===(d=c.parentElement)||void 0===d?void 0:d.querySelector("li > ul");e&&e.parentElement!==c&&this.hide(e),c.appendChild(this.generateDOM(b,c))}},{key:"getPosition",value:function(a,b){return{x:"r"===this.hdir?b.x+a.width>window.innerWidth?window.innerWidth-a.width:b.x:0>b.x?0:b.x,y:"d"===this.vdir?b.y+a.height>window.innerHeight?window.innerHeight-a.height:b.y:0>b.y?0:b.y}}}],[{key:"getInstance",value:function(){return b.instance||(b.instance=new b),b.instance}},{key:"getBounding",value:function(a){var b=a.cloneNode(!0);b.style.visibility="hidden",document.body.appendChild(b);var c=b.getBoundingClientRect();return document.body.removeChild(b),c}},{key:"getProp",value:function(a){return"function"==typeof a?a():a}},{key:"itemIsInteractive",value:function(a){return this.itemIsAction(a)||this.itemIsAnchor(a)||this.itemIsSubMenu(a)||this.itemIsCustom(a)}},{key:"itemIsAction",value:function(a){return a.hasOwnProperty("action")}},{key:"itemIsAnchor",value:function(a){return a.hasOwnProperty("href")}},{key:"itemIsDivider",value:function(a){return a.hasOwnProperty("isDivider")}},{key:"itemIsSubMenu",value:function(a){return a.hasOwnProperty("subMenu")}},{key:"itemIsCustom",value:function(a){return a.hasOwnProperty("html")||a.hasOwnProperty("element")}},{key:"addStylesToDom",value:function(){var a=function(){var b=Object.entries({".ctxmenu":{border:"1px solid #999",padding:"2px 0",boxShadow:"3px 3px 3px #aaa",background:"#fff",margin:"0",fontSize:"15px",fontFamily:"Verdana, sans-serif",zIndex:"9999"},".ctxmenu li":{margin:"1px 0",display:"block",position:"relative",userSelect:"none",webkitUserSelect:"none"},".ctxmenu li span":{display:"block",padding:"2px 20px",cursor:"default"},".ctxmenu li a":{color:"inherit",textDecoration:"none"},".ctxmenu li.icon":{paddingLeft:"15px"},".ctxmenu img.icon":{position:"absolute",width:"18px",left:"10px",top:"2px"},".ctxmenu li.disabled":{color:"#ccc"},".ctxmenu li.divider":{borderBottom:"1px solid #aaa",margin:"5px 0"},".ctxmenu li.interactive:hover":{background:"rgba(0,0,0,0.1)"},".ctxmenu li.submenu::after":{content:"''",position:"absolute",display:"block",top:"0",bottom:"0",right:"0.4em",margin:"auto",borderRight:"1px solid #000",borderTop:"1px solid #000",transform:"rotate(45deg)",width:"0.3rem",height:"0.3rem",marginRight:"0.1rem"},".ctxmenu li.submenu.disabled::after":{borderColor:"#ccc"}}).map(function(a){return"".concat(a[0]," { ").concat(Object.assign(document.createElement("p").style,a[1]).cssText," }")}),c=document.head.insertBefore(document.createElement("style"),document.head.childNodes[0]);b.forEach(function(a){var b;return null===(b=c.sheet)||void 0===b?void 0:b.insertRule(a)}),a=function(){}};"loading"===document.readyState?document.addEventListener("readystatechange",function(){"loading"!==document.readyState&&a()}):a()}}]),b}();c.ctxmenu=m.getInstance()},{}],2:[function(a,b,c){"use strict";Object.defineProperty(c,"__esModule",{value:!0});var d=a("./ctxmenu");window.ctxmenu=d.ctxmenu},{"./ctxmenu":1}]},{},[2]); diff --git a/test/demo.ts b/test/demo.ts index 12aa959..6444b37 100644 --- a/test/demo.ts +++ b/test/demo.ts @@ -189,4 +189,29 @@ function toggleDarkMode() { document.head.appendChild(link); toggle.innerHTML = "Back to normal!"; } -} \ No newline at end of file +} + + +function showButtonContextMenu(e: MouseEvent){ + ctxmenu.show( [ + { + text: "Downloads", + subMenu: [ + { + text: "ctxmenu.js", + href: "ctxmenu.js", + download: "" + }, + { + text: "ctxmenu.min.js", + href: "ctxmenu.min.js", + download: "" + } + ] + }, + { + text: "Documentation (github)", + href: "https://www.github.com/nkappler/ctxmenu" + } + ], e); +} From 8b6bab7da30679d97493d0c3796a9ff33b3c18d1 Mon Sep 17 00:00:00 2001 From: Johannes Korrek Date: Mon, 4 Oct 2021 16:02:43 +0200 Subject: [PATCH 2/3] implement #27 - adds an api for more direct usage - always use show method instead of private openMenu method --- docs/ctxmenu.js | 15 +++------------ docs/ctxmenu.min.js | 2 +- index.d.ts | 5 ++--- index.js | 15 +++------------ src/ctxmenu.ts | 16 +++------------- standalone/ctxmenu.d.ts | 5 ++--- standalone/ctxmenu.js | 15 +++------------ standalone/ctxmenu.min.js | 2 +- 8 files changed, 18 insertions(+), 57 deletions(-) diff --git a/docs/ctxmenu.js b/docs/ctxmenu.js index e6f1f90..cb4aa13 100644 --- a/docs/ctxmenu.js +++ b/docs/ctxmenu.js @@ -147,13 +147,9 @@ } var handler = function handler(e) { - e.stopImmediatePropagation(); - - _this2.hide(); - var newMenu = beforeRender(_toConsumableArray(ctxMenu), e); - _this2.openMenu(newMenu, e); + _this2.show(newMenu, e); e.preventDefault(); }; @@ -199,7 +195,8 @@ value: function show(ctxMenu, e) { e.stopImmediatePropagation(); this.hide(); - this.openMenu(_toConsumableArray(ctxMenu), e); + this.menu = this.generateDOM(_toConsumableArray(ctxMenu), e); + document.body.appendChild(this.menu); e.preventDefault(); } }, { @@ -220,12 +217,6 @@ (_a = menu.parentElement) === null || _a === void 0 ? void 0 : _a.removeChild(menu); } } - }, { - key: "openMenu", - value: function openMenu(ctxMenu, e) { - this.menu = this.generateDOM(_toConsumableArray(ctxMenu), e); - document.body.appendChild(this.menu); - } }, { key: "debounce", value: function debounce(target, action) { diff --git a/docs/ctxmenu.min.js b/docs/ctxmenu.min.js index 468db18..33c8c0b 100644 --- a/docs/ctxmenu.min.js +++ b/docs/ctxmenu.min.js @@ -1 +1 @@ -"use strict";(function(){function b(d,e,g){function a(j,i){if(!e[j]){if(!d[j]){var f="function"==typeof require&&require;if(!i&&f)return f(j,!0);if(h)return h(j,!0);var c=new Error("Cannot find module '"+j+"'");throw c.code="MODULE_NOT_FOUND",c}var k=e[j]={exports:{}};d[j][0].call(k.exports,function(b){var c=d[j][1][b];return a(c||b)},k,k.exports,b,d,e,g)}return e[j].exports}for(var h="function"==typeof require&&require,c=0;ca.length)&&(b=a.length);for(var c=0,d=Array(b);c".concat(b.getProp(c.text),""),i=b.getProp(c.element);if(i?f.append(i):f.innerHTML=g?g:h,f.title=b.getProp(c.tooltip)||"",c.style&&f.setAttribute("style",b.getProp(c.style)),!b.itemIsInteractive(c))f.setAttribute("style","font-weight: bold; margin-left: -5px;"+f.getAttribute("style"));else if(!!b.getProp(c.disabled))f.classList.add("disabled"),b.itemIsSubMenu(c)&&f.classList.add("submenu");else if(f.classList.add("interactive"),b.itemIsAction(c))f.addEventListener("click",function(a){c.action(a),d.hide()});else if(b.itemIsAnchor(c)){var j=document.createElement("a");i?j.append(i):j.innerHTML=g?g:h,j.onclick=function(){return d.hide()},j.href=b.getProp(c.href),c.hasOwnProperty("download")&&(j.download=b.getProp(c.download)),c.hasOwnProperty("target")&&(j.target=b.getProp(c.target)),f.childNodes.forEach(function(a){return a.remove()}),f.append(j)}else b.itemIsSubMenu(c)&&(0===b.getProp(c.subMenu).length?f.classList.add("disabled"):(f.classList.add("submenu"),d.debounce(f,function(a){var e=f.querySelector("ul");e||d.openSubMenu(a,b.getProp(c.subMenu),f)})));b.getProp(c.icon)&&(f.classList.add("icon"),f.innerHTML+=""))}e.appendChild(f)}),e.style.position="fixed",e.className="ctxmenu";var f=b.getBounding(e),g={x:0,y:0};if(c instanceof Element){var h=c.getBoundingClientRect();g={x:"r"===this.hdir?h.left+h.width:h.left-f.width,y:h.top+("d"===this.vdir?4:-12)};var i=this.getPosition(f,g);g.x!==i.x&&(this.hdir="r"===this.hdir?"l":"r",g.x="r"===this.hdir?h.left+h.width:h.left-f.width),g.y!==i.y&&(this.vdir="u"===this.vdir?"d":"u",g.y=i.y),g=this.getPosition(f,g)}else g=this.getPosition(f,{x:c.clientX,y:c.clientY});return e.style.left=g.x+"px",e.style.top=g.y+"px",e.addEventListener("contextmenu",function(a){a.stopPropagation(),a.preventDefault()}),e.addEventListener("click",function(a){var b=a.target instanceof Element&&a.target.parentElement;b&&"interactive"!==b.className&&a.stopPropagation()}),e}},{key:"openSubMenu",value:function(a,b,c){var d,e=null===(d=c.parentElement)||void 0===d?void 0:d.querySelector("li > ul");e&&e.parentElement!==c&&this.hide(e),c.appendChild(this.generateDOM(b,c))}},{key:"getPosition",value:function(a,b){return{x:"r"===this.hdir?b.x+a.width>window.innerWidth?window.innerWidth-a.width:b.x:0>b.x?0:b.x,y:"d"===this.vdir?b.y+a.height>window.innerHeight?window.innerHeight-a.height:b.y:0>b.y?0:b.y}}}],[{key:"getInstance",value:function(){return b.instance||(b.instance=new b),b.instance}},{key:"getBounding",value:function(a){var b=a.cloneNode(!0);b.style.visibility="hidden",document.body.appendChild(b);var c=b.getBoundingClientRect();return document.body.removeChild(b),c}},{key:"getProp",value:function(a){return"function"==typeof a?a():a}},{key:"itemIsInteractive",value:function(a){return this.itemIsAction(a)||this.itemIsAnchor(a)||this.itemIsSubMenu(a)||this.itemIsCustom(a)}},{key:"itemIsAction",value:function(a){return a.hasOwnProperty("action")}},{key:"itemIsAnchor",value:function(a){return a.hasOwnProperty("href")}},{key:"itemIsDivider",value:function(a){return a.hasOwnProperty("isDivider")}},{key:"itemIsSubMenu",value:function(a){return a.hasOwnProperty("subMenu")}},{key:"itemIsCustom",value:function(a){return a.hasOwnProperty("html")||a.hasOwnProperty("element")}},{key:"addStylesToDom",value:function(){var a=function(){var b=Object.entries({".ctxmenu":{border:"1px solid #999",padding:"2px 0",boxShadow:"3px 3px 3px #aaa",background:"#fff",margin:"0",fontSize:"15px",fontFamily:"Verdana, sans-serif",zIndex:"9999"},".ctxmenu li":{margin:"1px 0",display:"block",position:"relative",userSelect:"none",webkitUserSelect:"none"},".ctxmenu li span":{display:"block",padding:"2px 20px",cursor:"default"},".ctxmenu li a":{color:"inherit",textDecoration:"none"},".ctxmenu li.icon":{paddingLeft:"15px"},".ctxmenu img.icon":{position:"absolute",width:"18px",left:"10px",top:"2px"},".ctxmenu li.disabled":{color:"#ccc"},".ctxmenu li.divider":{borderBottom:"1px solid #aaa",margin:"5px 0"},".ctxmenu li.interactive:hover":{background:"rgba(0,0,0,0.1)"},".ctxmenu li.submenu::after":{content:"''",position:"absolute",display:"block",top:"0",bottom:"0",right:"0.4em",margin:"auto",borderRight:"1px solid #000",borderTop:"1px solid #000",transform:"rotate(45deg)",width:"0.3rem",height:"0.3rem",marginRight:"0.1rem"},".ctxmenu li.submenu.disabled::after":{borderColor:"#ccc"}}).map(function(a){return"".concat(a[0]," { ").concat(Object.assign(document.createElement("p").style,a[1]).cssText," }")}),c=document.head.insertBefore(document.createElement("style"),document.head.childNodes[0]);b.forEach(function(a){var b;return null===(b=c.sheet)||void 0===b?void 0:b.insertRule(a)}),a=function(){}};"loading"===document.readyState?document.addEventListener("readystatechange",function(){"loading"!==document.readyState&&a()}):a()}}]),b}();c.ctxmenu=m.getInstance()},{}],2:[function(a,b,c){"use strict";Object.defineProperty(c,"__esModule",{value:!0});var d=a("./ctxmenu");window.ctxmenu=d.ctxmenu},{"./ctxmenu":1}]},{},[2]); +"use strict";(function(){function b(d,e,g){function a(j,i){if(!e[j]){if(!d[j]){var f="function"==typeof require&&require;if(!i&&f)return f(j,!0);if(h)return h(j,!0);var c=new Error("Cannot find module '"+j+"'");throw c.code="MODULE_NOT_FOUND",c}var k=e[j]={exports:{}};d[j][0].call(k.exports,function(b){var c=d[j][1][b];return a(c||b)},k,k.exports,b,d,e,g)}return e[j].exports}for(var h="function"==typeof require&&require,c=0;ca.length)&&(b=a.length);for(var c=0,d=Array(b);c".concat(b.getProp(c.text),""),i=b.getProp(c.element);if(i?f.append(i):f.innerHTML=g?g:h,f.title=b.getProp(c.tooltip)||"",c.style&&f.setAttribute("style",b.getProp(c.style)),!b.itemIsInteractive(c))f.setAttribute("style","font-weight: bold; margin-left: -5px;"+f.getAttribute("style"));else if(!!b.getProp(c.disabled))f.classList.add("disabled"),b.itemIsSubMenu(c)&&f.classList.add("submenu");else if(f.classList.add("interactive"),b.itemIsAction(c))f.addEventListener("click",function(a){c.action(a),d.hide()});else if(b.itemIsAnchor(c)){var j=document.createElement("a");i?j.append(i):j.innerHTML=g?g:h,j.onclick=function(){return d.hide()},j.href=b.getProp(c.href),c.hasOwnProperty("download")&&(j.download=b.getProp(c.download)),c.hasOwnProperty("target")&&(j.target=b.getProp(c.target)),f.childNodes.forEach(function(a){return a.remove()}),f.append(j)}else b.itemIsSubMenu(c)&&(0===b.getProp(c.subMenu).length?f.classList.add("disabled"):(f.classList.add("submenu"),d.debounce(f,function(a){var e=f.querySelector("ul");e||d.openSubMenu(a,b.getProp(c.subMenu),f)})));b.getProp(c.icon)&&(f.classList.add("icon"),f.innerHTML+=""))}e.appendChild(f)}),e.style.position="fixed",e.className="ctxmenu";var f=b.getBounding(e),g={x:0,y:0};if(c instanceof Element){var h=c.getBoundingClientRect();g={x:"r"===this.hdir?h.left+h.width:h.left-f.width,y:h.top+("d"===this.vdir?4:-12)};var i=this.getPosition(f,g);g.x!==i.x&&(this.hdir="r"===this.hdir?"l":"r",g.x="r"===this.hdir?h.left+h.width:h.left-f.width),g.y!==i.y&&(this.vdir="u"===this.vdir?"d":"u",g.y=i.y),g=this.getPosition(f,g)}else g=this.getPosition(f,{x:c.clientX,y:c.clientY});return e.style.left=g.x+"px",e.style.top=g.y+"px",e.addEventListener("contextmenu",function(a){a.stopPropagation(),a.preventDefault()}),e.addEventListener("click",function(a){var b=a.target instanceof Element&&a.target.parentElement;b&&"interactive"!==b.className&&a.stopPropagation()}),e}},{key:"openSubMenu",value:function(a,b,c){var d,e=null===(d=c.parentElement)||void 0===d?void 0:d.querySelector("li > ul");e&&e.parentElement!==c&&this.hide(e),c.appendChild(this.generateDOM(b,c))}},{key:"getPosition",value:function(a,b){return{x:"r"===this.hdir?b.x+a.width>window.innerWidth?window.innerWidth-a.width:b.x:0>b.x?0:b.x,y:"d"===this.vdir?b.y+a.height>window.innerHeight?window.innerHeight-a.height:b.y:0>b.y?0:b.y}}}],[{key:"getInstance",value:function(){return b.instance||(b.instance=new b),b.instance}},{key:"getBounding",value:function(a){var b=a.cloneNode(!0);b.style.visibility="hidden",document.body.appendChild(b);var c=b.getBoundingClientRect();return document.body.removeChild(b),c}},{key:"getProp",value:function(a){return"function"==typeof a?a():a}},{key:"itemIsInteractive",value:function(a){return this.itemIsAction(a)||this.itemIsAnchor(a)||this.itemIsSubMenu(a)||this.itemIsCustom(a)}},{key:"itemIsAction",value:function(a){return a.hasOwnProperty("action")}},{key:"itemIsAnchor",value:function(a){return a.hasOwnProperty("href")}},{key:"itemIsDivider",value:function(a){return a.hasOwnProperty("isDivider")}},{key:"itemIsSubMenu",value:function(a){return a.hasOwnProperty("subMenu")}},{key:"itemIsCustom",value:function(a){return a.hasOwnProperty("html")||a.hasOwnProperty("element")}},{key:"addStylesToDom",value:function(){var a=function(){var b=Object.entries({".ctxmenu":{border:"1px solid #999",padding:"2px 0",boxShadow:"3px 3px 3px #aaa",background:"#fff",margin:"0",fontSize:"15px",fontFamily:"Verdana, sans-serif",zIndex:"9999"},".ctxmenu li":{margin:"1px 0",display:"block",position:"relative",userSelect:"none",webkitUserSelect:"none"},".ctxmenu li span":{display:"block",padding:"2px 20px",cursor:"default"},".ctxmenu li a":{color:"inherit",textDecoration:"none"},".ctxmenu li.icon":{paddingLeft:"15px"},".ctxmenu img.icon":{position:"absolute",width:"18px",left:"10px",top:"2px"},".ctxmenu li.disabled":{color:"#ccc"},".ctxmenu li.divider":{borderBottom:"1px solid #aaa",margin:"5px 0"},".ctxmenu li.interactive:hover":{background:"rgba(0,0,0,0.1)"},".ctxmenu li.submenu::after":{content:"''",position:"absolute",display:"block",top:"0",bottom:"0",right:"0.4em",margin:"auto",borderRight:"1px solid #000",borderTop:"1px solid #000",transform:"rotate(45deg)",width:"0.3rem",height:"0.3rem",marginRight:"0.1rem"},".ctxmenu li.submenu.disabled::after":{borderColor:"#ccc"}}).map(function(a){return"".concat(a[0]," { ").concat(Object.assign(document.createElement("p").style,a[1]).cssText," }")}),c=document.head.insertBefore(document.createElement("style"),document.head.childNodes[0]);b.forEach(function(a){var b;return null===(b=c.sheet)||void 0===b?void 0:b.insertRule(a)}),a=function(){}};"loading"===document.readyState?document.addEventListener("readystatechange",function(){"loading"!==document.readyState&&a()}):a()}}]),b}();c.ctxmenu=m.getInstance()},{}],2:[function(a,b,c){"use strict";Object.defineProperty(c,"__esModule",{value:!0});var d=a("./ctxmenu");window.ctxmenu=d.ctxmenu},{"./ctxmenu":1}]},{},[2]); diff --git a/index.d.ts b/index.d.ts index f6025f4..6f905de 100644 --- a/index.d.ts +++ b/index.d.ts @@ -92,10 +92,9 @@ declare module "ctxmenu" { */ delete(target: string): void; /** - * Create & show a context menu without attaching it to a specific element, based on the passed event. - * This might be useful, when integrating with other libraries / components that already provide a contextmenu handler. + * Create & show a context menu without attaching it to a specific element, based on the passed mouse event. * @param ctxMenu An array of objects defining the menu layout. - * @param e The original contextmenu event, used to position the tooltip + * @param e The original mouse event, used to position the tooltip */ show(ctxMenu: CTXMenu, e: MouseEvent): void; /** diff --git a/index.js b/index.js index e311e0f..ed35265 100644 --- a/index.js +++ b/index.js @@ -72,13 +72,9 @@ var ContextMenu = /*#__PURE__*/function () { } var handler = function handler(e) { - e.stopImmediatePropagation(); - - _this2.hide(); - var newMenu = beforeRender(_toConsumableArray(ctxMenu), e); - _this2.openMenu(newMenu, e); + _this2.show(newMenu, e); e.preventDefault(); }; @@ -124,7 +120,8 @@ var ContextMenu = /*#__PURE__*/function () { value: function show(ctxMenu, e) { e.stopImmediatePropagation(); this.hide(); - this.openMenu(_toConsumableArray(ctxMenu), e); + this.menu = this.generateDOM(_toConsumableArray(ctxMenu), e); + document.body.appendChild(this.menu); e.preventDefault(); } }, { @@ -145,12 +142,6 @@ var ContextMenu = /*#__PURE__*/function () { (_a = menu.parentElement) === null || _a === void 0 ? void 0 : _a.removeChild(menu); } } - }, { - key: "openMenu", - value: function openMenu(ctxMenu, e) { - this.menu = this.generateDOM(_toConsumableArray(ctxMenu), e); - document.body.appendChild(this.menu); - } }, { key: "debounce", value: function debounce(target, action) { diff --git a/src/ctxmenu.ts b/src/ctxmenu.ts index 77fa822..6573b9c 100644 --- a/src/ctxmenu.ts +++ b/src/ctxmenu.ts @@ -165,14 +165,8 @@ class ContextMenu implements CTXMenuSingleton { return; } const handler: CTXHandler = e => { - e.stopImmediatePropagation(); - //close any open menu - this.hide(); - const newMenu = beforeRender([...ctxMenu], e); - this.openMenu(newMenu, e); - - e.preventDefault(); + this.show(newMenu, e); }; this.cache[target] = { @@ -211,7 +205,8 @@ class ContextMenu implements CTXMenuSingleton { //close any open menu this.hide(); - this.openMenu([...ctxMenu], e); + this.menu = this.generateDOM([...ctxMenu], e); + document.body.appendChild(this.menu); e.preventDefault(); } @@ -229,11 +224,6 @@ class ContextMenu implements CTXMenuSingleton { } } - private openMenu(ctxMenu: CTXMenu, e: MouseEvent) { - this.menu = this.generateDOM([...ctxMenu], e); - document.body.appendChild(this.menu); - } - /** * assigns an eventhandler to a list item, that gets triggered after a short timeout, * but only if the cursor is still targeting that list item after the timeout. when diff --git a/standalone/ctxmenu.d.ts b/standalone/ctxmenu.d.ts index 5e06dea..9f654ac 100644 --- a/standalone/ctxmenu.d.ts +++ b/standalone/ctxmenu.d.ts @@ -92,10 +92,9 @@ declare module "ctxmenu" { */ delete(target: string): void; /** - * Create & show a context menu without attaching it to a specific element, based on the passed event. - * This might be useful, when integrating with other libraries / components that already provide a contextmenu handler. + * Create & show a context menu without attaching it to a specific element, based on the passed mouse event. * @param ctxMenu An array of objects defining the menu layout. - * @param e The original contextmenu event, used to position the tooltip + * @param e The original mouse event, used to position the tooltip */ show(ctxMenu: CTXMenu, e: MouseEvent): void; /** diff --git a/standalone/ctxmenu.js b/standalone/ctxmenu.js index c408056..8fa552d 100644 --- a/standalone/ctxmenu.js +++ b/standalone/ctxmenu.js @@ -73,13 +73,9 @@ var ContextMenu = /*#__PURE__*/function () { } var handler = function handler(e) { - e.stopImmediatePropagation(); - - _this2.hide(); - var newMenu = beforeRender(_toConsumableArray(ctxMenu), e); - _this2.openMenu(newMenu, e); + _this2.show(newMenu, e); e.preventDefault(); }; @@ -125,7 +121,8 @@ var ContextMenu = /*#__PURE__*/function () { value: function show(ctxMenu, e) { e.stopImmediatePropagation(); this.hide(); - this.openMenu(_toConsumableArray(ctxMenu), e); + this.menu = this.generateDOM(_toConsumableArray(ctxMenu), e); + document.body.appendChild(this.menu); e.preventDefault(); } }, { @@ -146,12 +143,6 @@ var ContextMenu = /*#__PURE__*/function () { (_a = menu.parentElement) === null || _a === void 0 ? void 0 : _a.removeChild(menu); } } - }, { - key: "openMenu", - value: function openMenu(ctxMenu, e) { - this.menu = this.generateDOM(_toConsumableArray(ctxMenu), e); - document.body.appendChild(this.menu); - } }, { key: "debounce", value: function debounce(target, action) { diff --git a/standalone/ctxmenu.min.js b/standalone/ctxmenu.min.js index 468db18..33c8c0b 100644 --- a/standalone/ctxmenu.min.js +++ b/standalone/ctxmenu.min.js @@ -1 +1 @@ -"use strict";(function(){function b(d,e,g){function a(j,i){if(!e[j]){if(!d[j]){var f="function"==typeof require&&require;if(!i&&f)return f(j,!0);if(h)return h(j,!0);var c=new Error("Cannot find module '"+j+"'");throw c.code="MODULE_NOT_FOUND",c}var k=e[j]={exports:{}};d[j][0].call(k.exports,function(b){var c=d[j][1][b];return a(c||b)},k,k.exports,b,d,e,g)}return e[j].exports}for(var h="function"==typeof require&&require,c=0;ca.length)&&(b=a.length);for(var c=0,d=Array(b);c".concat(b.getProp(c.text),""),i=b.getProp(c.element);if(i?f.append(i):f.innerHTML=g?g:h,f.title=b.getProp(c.tooltip)||"",c.style&&f.setAttribute("style",b.getProp(c.style)),!b.itemIsInteractive(c))f.setAttribute("style","font-weight: bold; margin-left: -5px;"+f.getAttribute("style"));else if(!!b.getProp(c.disabled))f.classList.add("disabled"),b.itemIsSubMenu(c)&&f.classList.add("submenu");else if(f.classList.add("interactive"),b.itemIsAction(c))f.addEventListener("click",function(a){c.action(a),d.hide()});else if(b.itemIsAnchor(c)){var j=document.createElement("a");i?j.append(i):j.innerHTML=g?g:h,j.onclick=function(){return d.hide()},j.href=b.getProp(c.href),c.hasOwnProperty("download")&&(j.download=b.getProp(c.download)),c.hasOwnProperty("target")&&(j.target=b.getProp(c.target)),f.childNodes.forEach(function(a){return a.remove()}),f.append(j)}else b.itemIsSubMenu(c)&&(0===b.getProp(c.subMenu).length?f.classList.add("disabled"):(f.classList.add("submenu"),d.debounce(f,function(a){var e=f.querySelector("ul");e||d.openSubMenu(a,b.getProp(c.subMenu),f)})));b.getProp(c.icon)&&(f.classList.add("icon"),f.innerHTML+=""))}e.appendChild(f)}),e.style.position="fixed",e.className="ctxmenu";var f=b.getBounding(e),g={x:0,y:0};if(c instanceof Element){var h=c.getBoundingClientRect();g={x:"r"===this.hdir?h.left+h.width:h.left-f.width,y:h.top+("d"===this.vdir?4:-12)};var i=this.getPosition(f,g);g.x!==i.x&&(this.hdir="r"===this.hdir?"l":"r",g.x="r"===this.hdir?h.left+h.width:h.left-f.width),g.y!==i.y&&(this.vdir="u"===this.vdir?"d":"u",g.y=i.y),g=this.getPosition(f,g)}else g=this.getPosition(f,{x:c.clientX,y:c.clientY});return e.style.left=g.x+"px",e.style.top=g.y+"px",e.addEventListener("contextmenu",function(a){a.stopPropagation(),a.preventDefault()}),e.addEventListener("click",function(a){var b=a.target instanceof Element&&a.target.parentElement;b&&"interactive"!==b.className&&a.stopPropagation()}),e}},{key:"openSubMenu",value:function(a,b,c){var d,e=null===(d=c.parentElement)||void 0===d?void 0:d.querySelector("li > ul");e&&e.parentElement!==c&&this.hide(e),c.appendChild(this.generateDOM(b,c))}},{key:"getPosition",value:function(a,b){return{x:"r"===this.hdir?b.x+a.width>window.innerWidth?window.innerWidth-a.width:b.x:0>b.x?0:b.x,y:"d"===this.vdir?b.y+a.height>window.innerHeight?window.innerHeight-a.height:b.y:0>b.y?0:b.y}}}],[{key:"getInstance",value:function(){return b.instance||(b.instance=new b),b.instance}},{key:"getBounding",value:function(a){var b=a.cloneNode(!0);b.style.visibility="hidden",document.body.appendChild(b);var c=b.getBoundingClientRect();return document.body.removeChild(b),c}},{key:"getProp",value:function(a){return"function"==typeof a?a():a}},{key:"itemIsInteractive",value:function(a){return this.itemIsAction(a)||this.itemIsAnchor(a)||this.itemIsSubMenu(a)||this.itemIsCustom(a)}},{key:"itemIsAction",value:function(a){return a.hasOwnProperty("action")}},{key:"itemIsAnchor",value:function(a){return a.hasOwnProperty("href")}},{key:"itemIsDivider",value:function(a){return a.hasOwnProperty("isDivider")}},{key:"itemIsSubMenu",value:function(a){return a.hasOwnProperty("subMenu")}},{key:"itemIsCustom",value:function(a){return a.hasOwnProperty("html")||a.hasOwnProperty("element")}},{key:"addStylesToDom",value:function(){var a=function(){var b=Object.entries({".ctxmenu":{border:"1px solid #999",padding:"2px 0",boxShadow:"3px 3px 3px #aaa",background:"#fff",margin:"0",fontSize:"15px",fontFamily:"Verdana, sans-serif",zIndex:"9999"},".ctxmenu li":{margin:"1px 0",display:"block",position:"relative",userSelect:"none",webkitUserSelect:"none"},".ctxmenu li span":{display:"block",padding:"2px 20px",cursor:"default"},".ctxmenu li a":{color:"inherit",textDecoration:"none"},".ctxmenu li.icon":{paddingLeft:"15px"},".ctxmenu img.icon":{position:"absolute",width:"18px",left:"10px",top:"2px"},".ctxmenu li.disabled":{color:"#ccc"},".ctxmenu li.divider":{borderBottom:"1px solid #aaa",margin:"5px 0"},".ctxmenu li.interactive:hover":{background:"rgba(0,0,0,0.1)"},".ctxmenu li.submenu::after":{content:"''",position:"absolute",display:"block",top:"0",bottom:"0",right:"0.4em",margin:"auto",borderRight:"1px solid #000",borderTop:"1px solid #000",transform:"rotate(45deg)",width:"0.3rem",height:"0.3rem",marginRight:"0.1rem"},".ctxmenu li.submenu.disabled::after":{borderColor:"#ccc"}}).map(function(a){return"".concat(a[0]," { ").concat(Object.assign(document.createElement("p").style,a[1]).cssText," }")}),c=document.head.insertBefore(document.createElement("style"),document.head.childNodes[0]);b.forEach(function(a){var b;return null===(b=c.sheet)||void 0===b?void 0:b.insertRule(a)}),a=function(){}};"loading"===document.readyState?document.addEventListener("readystatechange",function(){"loading"!==document.readyState&&a()}):a()}}]),b}();c.ctxmenu=m.getInstance()},{}],2:[function(a,b,c){"use strict";Object.defineProperty(c,"__esModule",{value:!0});var d=a("./ctxmenu");window.ctxmenu=d.ctxmenu},{"./ctxmenu":1}]},{},[2]); +"use strict";(function(){function b(d,e,g){function a(j,i){if(!e[j]){if(!d[j]){var f="function"==typeof require&&require;if(!i&&f)return f(j,!0);if(h)return h(j,!0);var c=new Error("Cannot find module '"+j+"'");throw c.code="MODULE_NOT_FOUND",c}var k=e[j]={exports:{}};d[j][0].call(k.exports,function(b){var c=d[j][1][b];return a(c||b)},k,k.exports,b,d,e,g)}return e[j].exports}for(var h="function"==typeof require&&require,c=0;ca.length)&&(b=a.length);for(var c=0,d=Array(b);c".concat(b.getProp(c.text),""),i=b.getProp(c.element);if(i?f.append(i):f.innerHTML=g?g:h,f.title=b.getProp(c.tooltip)||"",c.style&&f.setAttribute("style",b.getProp(c.style)),!b.itemIsInteractive(c))f.setAttribute("style","font-weight: bold; margin-left: -5px;"+f.getAttribute("style"));else if(!!b.getProp(c.disabled))f.classList.add("disabled"),b.itemIsSubMenu(c)&&f.classList.add("submenu");else if(f.classList.add("interactive"),b.itemIsAction(c))f.addEventListener("click",function(a){c.action(a),d.hide()});else if(b.itemIsAnchor(c)){var j=document.createElement("a");i?j.append(i):j.innerHTML=g?g:h,j.onclick=function(){return d.hide()},j.href=b.getProp(c.href),c.hasOwnProperty("download")&&(j.download=b.getProp(c.download)),c.hasOwnProperty("target")&&(j.target=b.getProp(c.target)),f.childNodes.forEach(function(a){return a.remove()}),f.append(j)}else b.itemIsSubMenu(c)&&(0===b.getProp(c.subMenu).length?f.classList.add("disabled"):(f.classList.add("submenu"),d.debounce(f,function(a){var e=f.querySelector("ul");e||d.openSubMenu(a,b.getProp(c.subMenu),f)})));b.getProp(c.icon)&&(f.classList.add("icon"),f.innerHTML+=""))}e.appendChild(f)}),e.style.position="fixed",e.className="ctxmenu";var f=b.getBounding(e),g={x:0,y:0};if(c instanceof Element){var h=c.getBoundingClientRect();g={x:"r"===this.hdir?h.left+h.width:h.left-f.width,y:h.top+("d"===this.vdir?4:-12)};var i=this.getPosition(f,g);g.x!==i.x&&(this.hdir="r"===this.hdir?"l":"r",g.x="r"===this.hdir?h.left+h.width:h.left-f.width),g.y!==i.y&&(this.vdir="u"===this.vdir?"d":"u",g.y=i.y),g=this.getPosition(f,g)}else g=this.getPosition(f,{x:c.clientX,y:c.clientY});return e.style.left=g.x+"px",e.style.top=g.y+"px",e.addEventListener("contextmenu",function(a){a.stopPropagation(),a.preventDefault()}),e.addEventListener("click",function(a){var b=a.target instanceof Element&&a.target.parentElement;b&&"interactive"!==b.className&&a.stopPropagation()}),e}},{key:"openSubMenu",value:function(a,b,c){var d,e=null===(d=c.parentElement)||void 0===d?void 0:d.querySelector("li > ul");e&&e.parentElement!==c&&this.hide(e),c.appendChild(this.generateDOM(b,c))}},{key:"getPosition",value:function(a,b){return{x:"r"===this.hdir?b.x+a.width>window.innerWidth?window.innerWidth-a.width:b.x:0>b.x?0:b.x,y:"d"===this.vdir?b.y+a.height>window.innerHeight?window.innerHeight-a.height:b.y:0>b.y?0:b.y}}}],[{key:"getInstance",value:function(){return b.instance||(b.instance=new b),b.instance}},{key:"getBounding",value:function(a){var b=a.cloneNode(!0);b.style.visibility="hidden",document.body.appendChild(b);var c=b.getBoundingClientRect();return document.body.removeChild(b),c}},{key:"getProp",value:function(a){return"function"==typeof a?a():a}},{key:"itemIsInteractive",value:function(a){return this.itemIsAction(a)||this.itemIsAnchor(a)||this.itemIsSubMenu(a)||this.itemIsCustom(a)}},{key:"itemIsAction",value:function(a){return a.hasOwnProperty("action")}},{key:"itemIsAnchor",value:function(a){return a.hasOwnProperty("href")}},{key:"itemIsDivider",value:function(a){return a.hasOwnProperty("isDivider")}},{key:"itemIsSubMenu",value:function(a){return a.hasOwnProperty("subMenu")}},{key:"itemIsCustom",value:function(a){return a.hasOwnProperty("html")||a.hasOwnProperty("element")}},{key:"addStylesToDom",value:function(){var a=function(){var b=Object.entries({".ctxmenu":{border:"1px solid #999",padding:"2px 0",boxShadow:"3px 3px 3px #aaa",background:"#fff",margin:"0",fontSize:"15px",fontFamily:"Verdana, sans-serif",zIndex:"9999"},".ctxmenu li":{margin:"1px 0",display:"block",position:"relative",userSelect:"none",webkitUserSelect:"none"},".ctxmenu li span":{display:"block",padding:"2px 20px",cursor:"default"},".ctxmenu li a":{color:"inherit",textDecoration:"none"},".ctxmenu li.icon":{paddingLeft:"15px"},".ctxmenu img.icon":{position:"absolute",width:"18px",left:"10px",top:"2px"},".ctxmenu li.disabled":{color:"#ccc"},".ctxmenu li.divider":{borderBottom:"1px solid #aaa",margin:"5px 0"},".ctxmenu li.interactive:hover":{background:"rgba(0,0,0,0.1)"},".ctxmenu li.submenu::after":{content:"''",position:"absolute",display:"block",top:"0",bottom:"0",right:"0.4em",margin:"auto",borderRight:"1px solid #000",borderTop:"1px solid #000",transform:"rotate(45deg)",width:"0.3rem",height:"0.3rem",marginRight:"0.1rem"},".ctxmenu li.submenu.disabled::after":{borderColor:"#ccc"}}).map(function(a){return"".concat(a[0]," { ").concat(Object.assign(document.createElement("p").style,a[1]).cssText," }")}),c=document.head.insertBefore(document.createElement("style"),document.head.childNodes[0]);b.forEach(function(a){var b;return null===(b=c.sheet)||void 0===b?void 0:b.insertRule(a)}),a=function(){}};"loading"===document.readyState?document.addEventListener("readystatechange",function(){"loading"!==document.readyState&&a()}):a()}}]),b}();c.ctxmenu=m.getInstance()},{}],2:[function(a,b,c){"use strict";Object.defineProperty(c,"__esModule",{value:!0});var d=a("./ctxmenu");window.ctxmenu=d.ctxmenu},{"./ctxmenu":1}]},{},[2]); From 25911cbba2197d661fbba607073aff7b53b8aa44 Mon Sep 17 00:00:00 2001 From: Johannes Korrek Date: Mon, 4 Oct 2021 16:29:08 +0200 Subject: [PATCH 3/3] implement #27 - adds an api for more direct usage - show method should also work with a HTMLElement instead of a MouseEvent --- README.md | 5 +++- docs/ctxmenu.js | 16 ++++++++----- docs/ctxmenu.min.js | 2 +- docs/demo.js | 49 +++++++++++++++++++++----------------- docs/index.html | 16 +++++++++++-- index.d.ts | 4 ++-- index.js | 16 ++++++++----- src/ctxmenu.ts | 20 +++++++++------- standalone/ctxmenu.d.ts | 4 ++-- standalone/ctxmenu.js | 16 ++++++++----- standalone/ctxmenu.min.js | 2 +- test/demo.ts | 50 ++++++++++++++++++++++----------------- 12 files changed, 120 insertions(+), 80 deletions(-) diff --git a/README.md b/README.md index ebe7823..710060d 100644 --- a/README.md +++ b/README.md @@ -203,12 +203,15 @@ The delete method is used to delete a context menu and only takes the `target` s ### `ctxmenu.show` ```typescript -ctxmenu.show(ctxmenu: Array, e: MouseEvent) +ctxmenu.show(ctxmenu: Array, e: MouseEvent | HTMLElement) ``` The `show` method can be used to show a context menu without using the `attach` method to set up a contextmenu for specific elements first. This may be useful when integrating with other libraries or frameworks that already provide a contextmenu handler or when trying to show a context menu on a different user interaction (for example showing a context menu when left-clicking a button). +When passing in an element as reference to open the context menu, you still need to stop the propagation of click events +in your custom code, otherwise the context menu may be directly closed. + ### `ctxmenu.hide` ```typescript ctxmenu.hide() diff --git a/docs/ctxmenu.js b/docs/ctxmenu.js index cb4aa13..fd800ea 100644 --- a/docs/ctxmenu.js +++ b/docs/ctxmenu.js @@ -150,8 +150,6 @@ var newMenu = beforeRender(_toConsumableArray(ctxMenu), e); _this2.show(newMenu, e); - - e.preventDefault(); }; this.cache[target] = { @@ -192,12 +190,18 @@ } }, { key: "show", - value: function show(ctxMenu, e) { - e.stopImmediatePropagation(); + value: function show(ctxMenu, eventOrElement) { + if (eventOrElement instanceof MouseEvent) { + eventOrElement.stopImmediatePropagation(); + } + this.hide(); - this.menu = this.generateDOM(_toConsumableArray(ctxMenu), e); + this.menu = this.generateDOM(_toConsumableArray(ctxMenu), eventOrElement); document.body.appendChild(this.menu); - e.preventDefault(); + + if (eventOrElement instanceof MouseEvent) { + eventOrElement.preventDefault(); + } } }, { key: "hide", diff --git a/docs/ctxmenu.min.js b/docs/ctxmenu.min.js index 33c8c0b..02e1fee 100644 --- a/docs/ctxmenu.min.js +++ b/docs/ctxmenu.min.js @@ -1 +1 @@ -"use strict";(function(){function b(d,e,g){function a(j,i){if(!e[j]){if(!d[j]){var f="function"==typeof require&&require;if(!i&&f)return f(j,!0);if(h)return h(j,!0);var c=new Error("Cannot find module '"+j+"'");throw c.code="MODULE_NOT_FOUND",c}var k=e[j]={exports:{}};d[j][0].call(k.exports,function(b){var c=d[j][1][b];return a(c||b)},k,k.exports,b,d,e,g)}return e[j].exports}for(var h="function"==typeof require&&require,c=0;ca.length)&&(b=a.length);for(var c=0,d=Array(b);c".concat(b.getProp(c.text),""),i=b.getProp(c.element);if(i?f.append(i):f.innerHTML=g?g:h,f.title=b.getProp(c.tooltip)||"",c.style&&f.setAttribute("style",b.getProp(c.style)),!b.itemIsInteractive(c))f.setAttribute("style","font-weight: bold; margin-left: -5px;"+f.getAttribute("style"));else if(!!b.getProp(c.disabled))f.classList.add("disabled"),b.itemIsSubMenu(c)&&f.classList.add("submenu");else if(f.classList.add("interactive"),b.itemIsAction(c))f.addEventListener("click",function(a){c.action(a),d.hide()});else if(b.itemIsAnchor(c)){var j=document.createElement("a");i?j.append(i):j.innerHTML=g?g:h,j.onclick=function(){return d.hide()},j.href=b.getProp(c.href),c.hasOwnProperty("download")&&(j.download=b.getProp(c.download)),c.hasOwnProperty("target")&&(j.target=b.getProp(c.target)),f.childNodes.forEach(function(a){return a.remove()}),f.append(j)}else b.itemIsSubMenu(c)&&(0===b.getProp(c.subMenu).length?f.classList.add("disabled"):(f.classList.add("submenu"),d.debounce(f,function(a){var e=f.querySelector("ul");e||d.openSubMenu(a,b.getProp(c.subMenu),f)})));b.getProp(c.icon)&&(f.classList.add("icon"),f.innerHTML+=""))}e.appendChild(f)}),e.style.position="fixed",e.className="ctxmenu";var f=b.getBounding(e),g={x:0,y:0};if(c instanceof Element){var h=c.getBoundingClientRect();g={x:"r"===this.hdir?h.left+h.width:h.left-f.width,y:h.top+("d"===this.vdir?4:-12)};var i=this.getPosition(f,g);g.x!==i.x&&(this.hdir="r"===this.hdir?"l":"r",g.x="r"===this.hdir?h.left+h.width:h.left-f.width),g.y!==i.y&&(this.vdir="u"===this.vdir?"d":"u",g.y=i.y),g=this.getPosition(f,g)}else g=this.getPosition(f,{x:c.clientX,y:c.clientY});return e.style.left=g.x+"px",e.style.top=g.y+"px",e.addEventListener("contextmenu",function(a){a.stopPropagation(),a.preventDefault()}),e.addEventListener("click",function(a){var b=a.target instanceof Element&&a.target.parentElement;b&&"interactive"!==b.className&&a.stopPropagation()}),e}},{key:"openSubMenu",value:function(a,b,c){var d,e=null===(d=c.parentElement)||void 0===d?void 0:d.querySelector("li > ul");e&&e.parentElement!==c&&this.hide(e),c.appendChild(this.generateDOM(b,c))}},{key:"getPosition",value:function(a,b){return{x:"r"===this.hdir?b.x+a.width>window.innerWidth?window.innerWidth-a.width:b.x:0>b.x?0:b.x,y:"d"===this.vdir?b.y+a.height>window.innerHeight?window.innerHeight-a.height:b.y:0>b.y?0:b.y}}}],[{key:"getInstance",value:function(){return b.instance||(b.instance=new b),b.instance}},{key:"getBounding",value:function(a){var b=a.cloneNode(!0);b.style.visibility="hidden",document.body.appendChild(b);var c=b.getBoundingClientRect();return document.body.removeChild(b),c}},{key:"getProp",value:function(a){return"function"==typeof a?a():a}},{key:"itemIsInteractive",value:function(a){return this.itemIsAction(a)||this.itemIsAnchor(a)||this.itemIsSubMenu(a)||this.itemIsCustom(a)}},{key:"itemIsAction",value:function(a){return a.hasOwnProperty("action")}},{key:"itemIsAnchor",value:function(a){return a.hasOwnProperty("href")}},{key:"itemIsDivider",value:function(a){return a.hasOwnProperty("isDivider")}},{key:"itemIsSubMenu",value:function(a){return a.hasOwnProperty("subMenu")}},{key:"itemIsCustom",value:function(a){return a.hasOwnProperty("html")||a.hasOwnProperty("element")}},{key:"addStylesToDom",value:function(){var a=function(){var b=Object.entries({".ctxmenu":{border:"1px solid #999",padding:"2px 0",boxShadow:"3px 3px 3px #aaa",background:"#fff",margin:"0",fontSize:"15px",fontFamily:"Verdana, sans-serif",zIndex:"9999"},".ctxmenu li":{margin:"1px 0",display:"block",position:"relative",userSelect:"none",webkitUserSelect:"none"},".ctxmenu li span":{display:"block",padding:"2px 20px",cursor:"default"},".ctxmenu li a":{color:"inherit",textDecoration:"none"},".ctxmenu li.icon":{paddingLeft:"15px"},".ctxmenu img.icon":{position:"absolute",width:"18px",left:"10px",top:"2px"},".ctxmenu li.disabled":{color:"#ccc"},".ctxmenu li.divider":{borderBottom:"1px solid #aaa",margin:"5px 0"},".ctxmenu li.interactive:hover":{background:"rgba(0,0,0,0.1)"},".ctxmenu li.submenu::after":{content:"''",position:"absolute",display:"block",top:"0",bottom:"0",right:"0.4em",margin:"auto",borderRight:"1px solid #000",borderTop:"1px solid #000",transform:"rotate(45deg)",width:"0.3rem",height:"0.3rem",marginRight:"0.1rem"},".ctxmenu li.submenu.disabled::after":{borderColor:"#ccc"}}).map(function(a){return"".concat(a[0]," { ").concat(Object.assign(document.createElement("p").style,a[1]).cssText," }")}),c=document.head.insertBefore(document.createElement("style"),document.head.childNodes[0]);b.forEach(function(a){var b;return null===(b=c.sheet)||void 0===b?void 0:b.insertRule(a)}),a=function(){}};"loading"===document.readyState?document.addEventListener("readystatechange",function(){"loading"!==document.readyState&&a()}):a()}}]),b}();c.ctxmenu=m.getInstance()},{}],2:[function(a,b,c){"use strict";Object.defineProperty(c,"__esModule",{value:!0});var d=a("./ctxmenu");window.ctxmenu=d.ctxmenu},{"./ctxmenu":1}]},{},[2]); +"use strict";(function(){function b(d,e,g){function a(j,i){if(!e[j]){if(!d[j]){var f="function"==typeof require&&require;if(!i&&f)return f(j,!0);if(h)return h(j,!0);var c=new Error("Cannot find module '"+j+"'");throw c.code="MODULE_NOT_FOUND",c}var k=e[j]={exports:{}};d[j][0].call(k.exports,function(b){var c=d[j][1][b];return a(c||b)},k,k.exports,b,d,e,g)}return e[j].exports}for(var h="function"==typeof require&&require,c=0;ca.length)&&(b=a.length);for(var c=0,d=Array(b);c".concat(b.getProp(c.text),""),i=b.getProp(c.element);if(i?f.append(i):f.innerHTML=g?g:h,f.title=b.getProp(c.tooltip)||"",c.style&&f.setAttribute("style",b.getProp(c.style)),!b.itemIsInteractive(c))f.setAttribute("style","font-weight: bold; margin-left: -5px;"+f.getAttribute("style"));else if(!!b.getProp(c.disabled))f.classList.add("disabled"),b.itemIsSubMenu(c)&&f.classList.add("submenu");else if(f.classList.add("interactive"),b.itemIsAction(c))f.addEventListener("click",function(a){c.action(a),d.hide()});else if(b.itemIsAnchor(c)){var j=document.createElement("a");i?j.append(i):j.innerHTML=g?g:h,j.onclick=function(){return d.hide()},j.href=b.getProp(c.href),c.hasOwnProperty("download")&&(j.download=b.getProp(c.download)),c.hasOwnProperty("target")&&(j.target=b.getProp(c.target)),f.childNodes.forEach(function(a){return a.remove()}),f.append(j)}else b.itemIsSubMenu(c)&&(0===b.getProp(c.subMenu).length?f.classList.add("disabled"):(f.classList.add("submenu"),d.debounce(f,function(a){var e=f.querySelector("ul");e||d.openSubMenu(a,b.getProp(c.subMenu),f)})));b.getProp(c.icon)&&(f.classList.add("icon"),f.innerHTML+=""))}e.appendChild(f)}),e.style.position="fixed",e.className="ctxmenu";var f=b.getBounding(e),g={x:0,y:0};if(c instanceof Element){var h=c.getBoundingClientRect();g={x:"r"===this.hdir?h.left+h.width:h.left-f.width,y:h.top+("d"===this.vdir?4:-12)};var i=this.getPosition(f,g);g.x!==i.x&&(this.hdir="r"===this.hdir?"l":"r",g.x="r"===this.hdir?h.left+h.width:h.left-f.width),g.y!==i.y&&(this.vdir="u"===this.vdir?"d":"u",g.y=i.y),g=this.getPosition(f,g)}else g=this.getPosition(f,{x:c.clientX,y:c.clientY});return e.style.left=g.x+"px",e.style.top=g.y+"px",e.addEventListener("contextmenu",function(a){a.stopPropagation(),a.preventDefault()}),e.addEventListener("click",function(a){var b=a.target instanceof Element&&a.target.parentElement;b&&"interactive"!==b.className&&a.stopPropagation()}),e}},{key:"openSubMenu",value:function(a,b,c){var d,e=null===(d=c.parentElement)||void 0===d?void 0:d.querySelector("li > ul");e&&e.parentElement!==c&&this.hide(e),c.appendChild(this.generateDOM(b,c))}},{key:"getPosition",value:function(a,b){return{x:"r"===this.hdir?b.x+a.width>window.innerWidth?window.innerWidth-a.width:b.x:0>b.x?0:b.x,y:"d"===this.vdir?b.y+a.height>window.innerHeight?window.innerHeight-a.height:b.y:0>b.y?0:b.y}}}],[{key:"getInstance",value:function(){return b.instance||(b.instance=new b),b.instance}},{key:"getBounding",value:function(a){var b=a.cloneNode(!0);b.style.visibility="hidden",document.body.appendChild(b);var c=b.getBoundingClientRect();return document.body.removeChild(b),c}},{key:"getProp",value:function(a){return"function"==typeof a?a():a}},{key:"itemIsInteractive",value:function(a){return this.itemIsAction(a)||this.itemIsAnchor(a)||this.itemIsSubMenu(a)||this.itemIsCustom(a)}},{key:"itemIsAction",value:function(a){return a.hasOwnProperty("action")}},{key:"itemIsAnchor",value:function(a){return a.hasOwnProperty("href")}},{key:"itemIsDivider",value:function(a){return a.hasOwnProperty("isDivider")}},{key:"itemIsSubMenu",value:function(a){return a.hasOwnProperty("subMenu")}},{key:"itemIsCustom",value:function(a){return a.hasOwnProperty("html")||a.hasOwnProperty("element")}},{key:"addStylesToDom",value:function(){var a=function(){var b=Object.entries({".ctxmenu":{border:"1px solid #999",padding:"2px 0",boxShadow:"3px 3px 3px #aaa",background:"#fff",margin:"0",fontSize:"15px",fontFamily:"Verdana, sans-serif",zIndex:"9999"},".ctxmenu li":{margin:"1px 0",display:"block",position:"relative",userSelect:"none",webkitUserSelect:"none"},".ctxmenu li span":{display:"block",padding:"2px 20px",cursor:"default"},".ctxmenu li a":{color:"inherit",textDecoration:"none"},".ctxmenu li.icon":{paddingLeft:"15px"},".ctxmenu img.icon":{position:"absolute",width:"18px",left:"10px",top:"2px"},".ctxmenu li.disabled":{color:"#ccc"},".ctxmenu li.divider":{borderBottom:"1px solid #aaa",margin:"5px 0"},".ctxmenu li.interactive:hover":{background:"rgba(0,0,0,0.1)"},".ctxmenu li.submenu::after":{content:"''",position:"absolute",display:"block",top:"0",bottom:"0",right:"0.4em",margin:"auto",borderRight:"1px solid #000",borderTop:"1px solid #000",transform:"rotate(45deg)",width:"0.3rem",height:"0.3rem",marginRight:"0.1rem"},".ctxmenu li.submenu.disabled::after":{borderColor:"#ccc"}}).map(function(a){return"".concat(a[0]," { ").concat(Object.assign(document.createElement("p").style,a[1]).cssText," }")}),c=document.head.insertBefore(document.createElement("style"),document.head.childNodes[0]);b.forEach(function(a){var b;return null===(b=c.sheet)||void 0===b?void 0:b.insertRule(a)}),a=function(){}};"loading"===document.readyState?document.addEventListener("readystatechange",function(){"loading"!==document.readyState&&a()}):a()}}]),b}();c.ctxmenu=m.getInstance()},{}],2:[function(a,b,c){"use strict";Object.defineProperty(c,"__esModule",{value:!0});var d=a("./ctxmenu");window.ctxmenu=d.ctxmenu},{"./ctxmenu":1}]},{},[2]); diff --git a/docs/demo.js b/docs/demo.js index 832e4fe..575dc02 100644 --- a/docs/demo.js +++ b/docs/demo.js @@ -185,26 +185,31 @@ function toggleDarkMode() { toggle.innerHTML = "Back to normal!"; } } -function showButtonContextMenu(e) { - ctxmenu.show([ - { - text: "Downloads", - subMenu: [ - { - text: "ctxmenu.js", - href: "ctxmenu.js", - download: "" - }, - { - text: "ctxmenu.min.js", - href: "ctxmenu.min.js", - download: "" - } - ] - }, - { - text: "Documentation (github)", - href: "https://www.github.com/nkappler/ctxmenu" - } - ], e); +var menuExample = [ + { + text: "Downloads", + subMenu: [ + { + text: "ctxmenu.js", + href: "ctxmenu.js", + download: "" + }, + { + text: "ctxmenu.min.js", + href: "ctxmenu.min.js", + download: "" + } + ] + }, + { + text: "Documentation (github)", + href: "https://www.github.com/nkappler/ctxmenu" + } +]; +function showContextMenuForEvent(e) { + ctxmenu.show(menuExample, e); +} +function showContextMenuForElement(element, e) { + e.stopPropagation(); + ctxmenu.show(menuExample, element); } diff --git a/docs/index.html b/docs/index.html index cea76ab..c1c0c54 100644 --- a/docs/index.html +++ b/docs/index.html @@ -91,6 +91,12 @@ height: 149px; } + .clickable-div { + background-color: #ccc; + display: inline; + cursor: pointer; + } + /* If the screen size is 600px wide or less, set the font-size of
    to 30px */ @media screen and (max-width: 600px) { h1 { @@ -168,8 +174,14 @@

    ctxmenu.js

    run any arbitrary javascript function
  • Fully customizable! Fancy dark mode?
  • -
  • Show a context menu "on demand" for any mouse event -
  • +
  • Show a context menu "on demand" for +
      +
    • any mouse event +
    • +
    • any element +
      Click me!
    • +
    +
  • Mobile support
  • diff --git a/index.d.ts b/index.d.ts index 6f905de..6efc677 100644 --- a/index.d.ts +++ b/index.d.ts @@ -94,9 +94,9 @@ declare module "ctxmenu" { /** * Create & show a context menu without attaching it to a specific element, based on the passed mouse event. * @param ctxMenu An array of objects defining the menu layout. - * @param e The original mouse event, used to position the tooltip + * @param e Either a MouseEvent or an HTMLElement, defining where the context menu should be opened. */ - show(ctxMenu: CTXMenu, e: MouseEvent): void; + show(ctxMenu: CTXMenu, e: MouseEvent | HTMLElement): void; /** * Close any contextmenu that might be open at the moment */ diff --git a/index.js b/index.js index ed35265..a0c2c3c 100644 --- a/index.js +++ b/index.js @@ -75,8 +75,6 @@ var ContextMenu = /*#__PURE__*/function () { var newMenu = beforeRender(_toConsumableArray(ctxMenu), e); _this2.show(newMenu, e); - - e.preventDefault(); }; this.cache[target] = { @@ -117,12 +115,18 @@ var ContextMenu = /*#__PURE__*/function () { } }, { key: "show", - value: function show(ctxMenu, e) { - e.stopImmediatePropagation(); + value: function show(ctxMenu, eventOrElement) { + if (eventOrElement instanceof MouseEvent) { + eventOrElement.stopImmediatePropagation(); + } + this.hide(); - this.menu = this.generateDOM(_toConsumableArray(ctxMenu), e); + this.menu = this.generateDOM(_toConsumableArray(ctxMenu), eventOrElement); document.body.appendChild(this.menu); - e.preventDefault(); + + if (eventOrElement instanceof MouseEvent) { + eventOrElement.preventDefault(); + } } }, { key: "hide", diff --git a/src/ctxmenu.ts b/src/ctxmenu.ts index 6573b9c..bcbe852 100644 --- a/src/ctxmenu.ts +++ b/src/ctxmenu.ts @@ -104,9 +104,9 @@ export interface CTXMenuSingleton { /** * Create & show a context menu without attaching it to a specific element, based on the passed mouse event. * @param ctxMenu An array of objects defining the menu layout. - * @param e The original mouse event, used to position the tooltip + * @param e Either a MouseEvent or an HTMLElement, defining where the context menu should be opened. */ - show(ctxMenu: CTXMenu, e: MouseEvent): void; + show(ctxMenu: CTXMenu, e: MouseEvent | HTMLElement): void; /** * Close any contextmenu that might be open at the moment */ @@ -200,15 +200,19 @@ class ContextMenu implements CTXMenuSingleton { delete this.cache[target]; } - public show(ctxMenu: CTXMenu, e: MouseEvent) { - e.stopImmediatePropagation(); + public show(ctxMenu: CTXMenu, eventOrElement: HTMLElement | MouseEvent) { + if(eventOrElement instanceof MouseEvent) { + eventOrElement.stopImmediatePropagation(); + } //close any open menu this.hide(); - this.menu = this.generateDOM([...ctxMenu], e); + this.menu = this.generateDOM([...ctxMenu], eventOrElement); document.body.appendChild(this.menu); - e.preventDefault(); + if(eventOrElement instanceof MouseEvent) { + eventOrElement.preventDefault(); + } } public hide(menu: Element | undefined = this.menu) { @@ -239,9 +243,7 @@ class ContextMenu implements CTXMenuSingleton { target.addEventListener("mouseleave", () => clearTimeout(timeout)); } - private generateDOM(ctxMenu: CTXMenu, event: MouseEvent): HTMLUListElement; - private generateDOM(ctxMenu: CTXMenu, parentElement: HTMLLIElement): HTMLUListElement; - private generateDOM(ctxMenu: CTXMenu, parentOrEvent: HTMLLIElement | MouseEvent): HTMLUListElement { + private generateDOM(ctxMenu: CTXMenu, parentOrEvent: HTMLElement | MouseEvent): HTMLUListElement { //This has grown pretty messy and could use a rework const container = document.createElement("ul"); diff --git a/standalone/ctxmenu.d.ts b/standalone/ctxmenu.d.ts index 9f654ac..31dbf31 100644 --- a/standalone/ctxmenu.d.ts +++ b/standalone/ctxmenu.d.ts @@ -94,9 +94,9 @@ declare module "ctxmenu" { /** * Create & show a context menu without attaching it to a specific element, based on the passed mouse event. * @param ctxMenu An array of objects defining the menu layout. - * @param e The original mouse event, used to position the tooltip + * @param e Either a MouseEvent or an HTMLElement, defining where the context menu should be opened. */ - show(ctxMenu: CTXMenu, e: MouseEvent): void; + show(ctxMenu: CTXMenu, e: MouseEvent | HTMLElement): void; /** * Close any contextmenu that might be open at the moment */ diff --git a/standalone/ctxmenu.js b/standalone/ctxmenu.js index 8fa552d..24c5268 100644 --- a/standalone/ctxmenu.js +++ b/standalone/ctxmenu.js @@ -76,8 +76,6 @@ var ContextMenu = /*#__PURE__*/function () { var newMenu = beforeRender(_toConsumableArray(ctxMenu), e); _this2.show(newMenu, e); - - e.preventDefault(); }; this.cache[target] = { @@ -118,12 +116,18 @@ var ContextMenu = /*#__PURE__*/function () { } }, { key: "show", - value: function show(ctxMenu, e) { - e.stopImmediatePropagation(); + value: function show(ctxMenu, eventOrElement) { + if (eventOrElement instanceof MouseEvent) { + eventOrElement.stopImmediatePropagation(); + } + this.hide(); - this.menu = this.generateDOM(_toConsumableArray(ctxMenu), e); + this.menu = this.generateDOM(_toConsumableArray(ctxMenu), eventOrElement); document.body.appendChild(this.menu); - e.preventDefault(); + + if (eventOrElement instanceof MouseEvent) { + eventOrElement.preventDefault(); + } } }, { key: "hide", diff --git a/standalone/ctxmenu.min.js b/standalone/ctxmenu.min.js index 33c8c0b..02e1fee 100644 --- a/standalone/ctxmenu.min.js +++ b/standalone/ctxmenu.min.js @@ -1 +1 @@ -"use strict";(function(){function b(d,e,g){function a(j,i){if(!e[j]){if(!d[j]){var f="function"==typeof require&&require;if(!i&&f)return f(j,!0);if(h)return h(j,!0);var c=new Error("Cannot find module '"+j+"'");throw c.code="MODULE_NOT_FOUND",c}var k=e[j]={exports:{}};d[j][0].call(k.exports,function(b){var c=d[j][1][b];return a(c||b)},k,k.exports,b,d,e,g)}return e[j].exports}for(var h="function"==typeof require&&require,c=0;ca.length)&&(b=a.length);for(var c=0,d=Array(b);c".concat(b.getProp(c.text),""),i=b.getProp(c.element);if(i?f.append(i):f.innerHTML=g?g:h,f.title=b.getProp(c.tooltip)||"",c.style&&f.setAttribute("style",b.getProp(c.style)),!b.itemIsInteractive(c))f.setAttribute("style","font-weight: bold; margin-left: -5px;"+f.getAttribute("style"));else if(!!b.getProp(c.disabled))f.classList.add("disabled"),b.itemIsSubMenu(c)&&f.classList.add("submenu");else if(f.classList.add("interactive"),b.itemIsAction(c))f.addEventListener("click",function(a){c.action(a),d.hide()});else if(b.itemIsAnchor(c)){var j=document.createElement("a");i?j.append(i):j.innerHTML=g?g:h,j.onclick=function(){return d.hide()},j.href=b.getProp(c.href),c.hasOwnProperty("download")&&(j.download=b.getProp(c.download)),c.hasOwnProperty("target")&&(j.target=b.getProp(c.target)),f.childNodes.forEach(function(a){return a.remove()}),f.append(j)}else b.itemIsSubMenu(c)&&(0===b.getProp(c.subMenu).length?f.classList.add("disabled"):(f.classList.add("submenu"),d.debounce(f,function(a){var e=f.querySelector("ul");e||d.openSubMenu(a,b.getProp(c.subMenu),f)})));b.getProp(c.icon)&&(f.classList.add("icon"),f.innerHTML+=""))}e.appendChild(f)}),e.style.position="fixed",e.className="ctxmenu";var f=b.getBounding(e),g={x:0,y:0};if(c instanceof Element){var h=c.getBoundingClientRect();g={x:"r"===this.hdir?h.left+h.width:h.left-f.width,y:h.top+("d"===this.vdir?4:-12)};var i=this.getPosition(f,g);g.x!==i.x&&(this.hdir="r"===this.hdir?"l":"r",g.x="r"===this.hdir?h.left+h.width:h.left-f.width),g.y!==i.y&&(this.vdir="u"===this.vdir?"d":"u",g.y=i.y),g=this.getPosition(f,g)}else g=this.getPosition(f,{x:c.clientX,y:c.clientY});return e.style.left=g.x+"px",e.style.top=g.y+"px",e.addEventListener("contextmenu",function(a){a.stopPropagation(),a.preventDefault()}),e.addEventListener("click",function(a){var b=a.target instanceof Element&&a.target.parentElement;b&&"interactive"!==b.className&&a.stopPropagation()}),e}},{key:"openSubMenu",value:function(a,b,c){var d,e=null===(d=c.parentElement)||void 0===d?void 0:d.querySelector("li > ul");e&&e.parentElement!==c&&this.hide(e),c.appendChild(this.generateDOM(b,c))}},{key:"getPosition",value:function(a,b){return{x:"r"===this.hdir?b.x+a.width>window.innerWidth?window.innerWidth-a.width:b.x:0>b.x?0:b.x,y:"d"===this.vdir?b.y+a.height>window.innerHeight?window.innerHeight-a.height:b.y:0>b.y?0:b.y}}}],[{key:"getInstance",value:function(){return b.instance||(b.instance=new b),b.instance}},{key:"getBounding",value:function(a){var b=a.cloneNode(!0);b.style.visibility="hidden",document.body.appendChild(b);var c=b.getBoundingClientRect();return document.body.removeChild(b),c}},{key:"getProp",value:function(a){return"function"==typeof a?a():a}},{key:"itemIsInteractive",value:function(a){return this.itemIsAction(a)||this.itemIsAnchor(a)||this.itemIsSubMenu(a)||this.itemIsCustom(a)}},{key:"itemIsAction",value:function(a){return a.hasOwnProperty("action")}},{key:"itemIsAnchor",value:function(a){return a.hasOwnProperty("href")}},{key:"itemIsDivider",value:function(a){return a.hasOwnProperty("isDivider")}},{key:"itemIsSubMenu",value:function(a){return a.hasOwnProperty("subMenu")}},{key:"itemIsCustom",value:function(a){return a.hasOwnProperty("html")||a.hasOwnProperty("element")}},{key:"addStylesToDom",value:function(){var a=function(){var b=Object.entries({".ctxmenu":{border:"1px solid #999",padding:"2px 0",boxShadow:"3px 3px 3px #aaa",background:"#fff",margin:"0",fontSize:"15px",fontFamily:"Verdana, sans-serif",zIndex:"9999"},".ctxmenu li":{margin:"1px 0",display:"block",position:"relative",userSelect:"none",webkitUserSelect:"none"},".ctxmenu li span":{display:"block",padding:"2px 20px",cursor:"default"},".ctxmenu li a":{color:"inherit",textDecoration:"none"},".ctxmenu li.icon":{paddingLeft:"15px"},".ctxmenu img.icon":{position:"absolute",width:"18px",left:"10px",top:"2px"},".ctxmenu li.disabled":{color:"#ccc"},".ctxmenu li.divider":{borderBottom:"1px solid #aaa",margin:"5px 0"},".ctxmenu li.interactive:hover":{background:"rgba(0,0,0,0.1)"},".ctxmenu li.submenu::after":{content:"''",position:"absolute",display:"block",top:"0",bottom:"0",right:"0.4em",margin:"auto",borderRight:"1px solid #000",borderTop:"1px solid #000",transform:"rotate(45deg)",width:"0.3rem",height:"0.3rem",marginRight:"0.1rem"},".ctxmenu li.submenu.disabled::after":{borderColor:"#ccc"}}).map(function(a){return"".concat(a[0]," { ").concat(Object.assign(document.createElement("p").style,a[1]).cssText," }")}),c=document.head.insertBefore(document.createElement("style"),document.head.childNodes[0]);b.forEach(function(a){var b;return null===(b=c.sheet)||void 0===b?void 0:b.insertRule(a)}),a=function(){}};"loading"===document.readyState?document.addEventListener("readystatechange",function(){"loading"!==document.readyState&&a()}):a()}}]),b}();c.ctxmenu=m.getInstance()},{}],2:[function(a,b,c){"use strict";Object.defineProperty(c,"__esModule",{value:!0});var d=a("./ctxmenu");window.ctxmenu=d.ctxmenu},{"./ctxmenu":1}]},{},[2]); +"use strict";(function(){function b(d,e,g){function a(j,i){if(!e[j]){if(!d[j]){var f="function"==typeof require&&require;if(!i&&f)return f(j,!0);if(h)return h(j,!0);var c=new Error("Cannot find module '"+j+"'");throw c.code="MODULE_NOT_FOUND",c}var k=e[j]={exports:{}};d[j][0].call(k.exports,function(b){var c=d[j][1][b];return a(c||b)},k,k.exports,b,d,e,g)}return e[j].exports}for(var h="function"==typeof require&&require,c=0;ca.length)&&(b=a.length);for(var c=0,d=Array(b);c".concat(b.getProp(c.text),""),i=b.getProp(c.element);if(i?f.append(i):f.innerHTML=g?g:h,f.title=b.getProp(c.tooltip)||"",c.style&&f.setAttribute("style",b.getProp(c.style)),!b.itemIsInteractive(c))f.setAttribute("style","font-weight: bold; margin-left: -5px;"+f.getAttribute("style"));else if(!!b.getProp(c.disabled))f.classList.add("disabled"),b.itemIsSubMenu(c)&&f.classList.add("submenu");else if(f.classList.add("interactive"),b.itemIsAction(c))f.addEventListener("click",function(a){c.action(a),d.hide()});else if(b.itemIsAnchor(c)){var j=document.createElement("a");i?j.append(i):j.innerHTML=g?g:h,j.onclick=function(){return d.hide()},j.href=b.getProp(c.href),c.hasOwnProperty("download")&&(j.download=b.getProp(c.download)),c.hasOwnProperty("target")&&(j.target=b.getProp(c.target)),f.childNodes.forEach(function(a){return a.remove()}),f.append(j)}else b.itemIsSubMenu(c)&&(0===b.getProp(c.subMenu).length?f.classList.add("disabled"):(f.classList.add("submenu"),d.debounce(f,function(a){var e=f.querySelector("ul");e||d.openSubMenu(a,b.getProp(c.subMenu),f)})));b.getProp(c.icon)&&(f.classList.add("icon"),f.innerHTML+=""))}e.appendChild(f)}),e.style.position="fixed",e.className="ctxmenu";var f=b.getBounding(e),g={x:0,y:0};if(c instanceof Element){var h=c.getBoundingClientRect();g={x:"r"===this.hdir?h.left+h.width:h.left-f.width,y:h.top+("d"===this.vdir?4:-12)};var i=this.getPosition(f,g);g.x!==i.x&&(this.hdir="r"===this.hdir?"l":"r",g.x="r"===this.hdir?h.left+h.width:h.left-f.width),g.y!==i.y&&(this.vdir="u"===this.vdir?"d":"u",g.y=i.y),g=this.getPosition(f,g)}else g=this.getPosition(f,{x:c.clientX,y:c.clientY});return e.style.left=g.x+"px",e.style.top=g.y+"px",e.addEventListener("contextmenu",function(a){a.stopPropagation(),a.preventDefault()}),e.addEventListener("click",function(a){var b=a.target instanceof Element&&a.target.parentElement;b&&"interactive"!==b.className&&a.stopPropagation()}),e}},{key:"openSubMenu",value:function(a,b,c){var d,e=null===(d=c.parentElement)||void 0===d?void 0:d.querySelector("li > ul");e&&e.parentElement!==c&&this.hide(e),c.appendChild(this.generateDOM(b,c))}},{key:"getPosition",value:function(a,b){return{x:"r"===this.hdir?b.x+a.width>window.innerWidth?window.innerWidth-a.width:b.x:0>b.x?0:b.x,y:"d"===this.vdir?b.y+a.height>window.innerHeight?window.innerHeight-a.height:b.y:0>b.y?0:b.y}}}],[{key:"getInstance",value:function(){return b.instance||(b.instance=new b),b.instance}},{key:"getBounding",value:function(a){var b=a.cloneNode(!0);b.style.visibility="hidden",document.body.appendChild(b);var c=b.getBoundingClientRect();return document.body.removeChild(b),c}},{key:"getProp",value:function(a){return"function"==typeof a?a():a}},{key:"itemIsInteractive",value:function(a){return this.itemIsAction(a)||this.itemIsAnchor(a)||this.itemIsSubMenu(a)||this.itemIsCustom(a)}},{key:"itemIsAction",value:function(a){return a.hasOwnProperty("action")}},{key:"itemIsAnchor",value:function(a){return a.hasOwnProperty("href")}},{key:"itemIsDivider",value:function(a){return a.hasOwnProperty("isDivider")}},{key:"itemIsSubMenu",value:function(a){return a.hasOwnProperty("subMenu")}},{key:"itemIsCustom",value:function(a){return a.hasOwnProperty("html")||a.hasOwnProperty("element")}},{key:"addStylesToDom",value:function(){var a=function(){var b=Object.entries({".ctxmenu":{border:"1px solid #999",padding:"2px 0",boxShadow:"3px 3px 3px #aaa",background:"#fff",margin:"0",fontSize:"15px",fontFamily:"Verdana, sans-serif",zIndex:"9999"},".ctxmenu li":{margin:"1px 0",display:"block",position:"relative",userSelect:"none",webkitUserSelect:"none"},".ctxmenu li span":{display:"block",padding:"2px 20px",cursor:"default"},".ctxmenu li a":{color:"inherit",textDecoration:"none"},".ctxmenu li.icon":{paddingLeft:"15px"},".ctxmenu img.icon":{position:"absolute",width:"18px",left:"10px",top:"2px"},".ctxmenu li.disabled":{color:"#ccc"},".ctxmenu li.divider":{borderBottom:"1px solid #aaa",margin:"5px 0"},".ctxmenu li.interactive:hover":{background:"rgba(0,0,0,0.1)"},".ctxmenu li.submenu::after":{content:"''",position:"absolute",display:"block",top:"0",bottom:"0",right:"0.4em",margin:"auto",borderRight:"1px solid #000",borderTop:"1px solid #000",transform:"rotate(45deg)",width:"0.3rem",height:"0.3rem",marginRight:"0.1rem"},".ctxmenu li.submenu.disabled::after":{borderColor:"#ccc"}}).map(function(a){return"".concat(a[0]," { ").concat(Object.assign(document.createElement("p").style,a[1]).cssText," }")}),c=document.head.insertBefore(document.createElement("style"),document.head.childNodes[0]);b.forEach(function(a){var b;return null===(b=c.sheet)||void 0===b?void 0:b.insertRule(a)}),a=function(){}};"loading"===document.readyState?document.addEventListener("readystatechange",function(){"loading"!==document.readyState&&a()}):a()}}]),b}();c.ctxmenu=m.getInstance()},{}],2:[function(a,b,c){"use strict";Object.defineProperty(c,"__esModule",{value:!0});var d=a("./ctxmenu");window.ctxmenu=d.ctxmenu},{"./ctxmenu":1}]},{},[2]); diff --git a/test/demo.ts b/test/demo.ts index 6444b37..ed577f9 100644 --- a/test/demo.ts +++ b/test/demo.ts @@ -192,26 +192,32 @@ function toggleDarkMode() { } -function showButtonContextMenu(e: MouseEvent){ - ctxmenu.show( [ - { - text: "Downloads", - subMenu: [ - { - text: "ctxmenu.js", - href: "ctxmenu.js", - download: "" - }, - { - text: "ctxmenu.min.js", - href: "ctxmenu.min.js", - download: "" - } - ] - }, - { - text: "Documentation (github)", - href: "https://www.github.com/nkappler/ctxmenu" - } - ], e); +const menuExample = [ + { + text: "Downloads", + subMenu: [ + { + text: "ctxmenu.js", + href: "ctxmenu.js", + download: "" + }, + { + text: "ctxmenu.min.js", + href: "ctxmenu.min.js", + download: "" + } + ] + }, + { + text: "Documentation (github)", + href: "https://www.github.com/nkappler/ctxmenu" + } +]; + +function showContextMenuForEvent(e: MouseEvent){ + ctxmenu.show( menuExample, e); +} +function showContextMenuForElement(element: HTMLElement, e: MouseEvent){ + e.stopPropagation(); + ctxmenu.show( menuExample, element); }