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