@asup/context-menu 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -22,41 +22,26 @@ npm install @asup/context-menu
22
22
  ## Usage
23
23
 
24
24
  Context menu provider, takes a list of available actions and renders a context menu on appropriate click.
25
+ Sub menus can be added within each item.
26
+ Wrap around the elements that need to have the menu.
25
27
 
26
28
  ```
27
- import { ContextMenuProvider, iMenuItem, MenuContext } from '@asup/context-menu';
28
-
29
- ... inside REACT component
30
-
31
- <ContextMenuProvider>
32
-
33
- <SomeChild
34
-
35
- const menuContext = useContext(MenuContext);
36
- const showMenu = useCallback((e) => {
37
- e.preventDefault();
38
- e.stopPropagation();
39
- const menuItems: iMenuItem[] = [
40
- { label: 'Item 1', action: item1Function },
41
- { label: 'Item 2', action: item2Function },
42
- ...
43
- ];
44
- menuContext.set && menuContext.set({
45
- visible: true,
46
- y: e.pageY,
47
- x: e.pageX,
48
- menuItems: menuItems,
49
- });
29
+ import { ContextMenuProvider, iMenuItem } from '@asup/context-menu';
30
+
31
+ <ContextMenuHandler
32
+ menuItems={[
33
+ { label: 'Item 1', action: item1Function },
34
+ { label: 'Item 2', action: item2Function, group: [
35
+ { label: 'Subitem 2.1', action: item21Function }
36
+ ...
37
+ ]
50
38
  },
51
- [...]);
52
-
53
- return (
54
- <div onContextMenu={showMenu}>
55
- </div>
56
- );
57
- >
58
-
39
+ { label: 'Item 3', action: item3Function, disabeld: true },
40
+ ...
41
+ ]}
42
+ >
43
+ <Chilren
44
+ where the context menu is applied...
45
+ />
59
46
  </ContextMenuProvider>
60
47
  ```
61
-
62
- Add an `onContextMenu` action to an element inside the `ContextMenuProvider`, and create a corresponding function that loads the menuItems array and then sets it to visible.
package/dist/cjs/main.css CHANGED
@@ -1,12 +1,3 @@
1
- .context-menu-handler {
2
- height: -webkit-fit-content;
3
- height: -moz-fit-content;
4
- height: fit-content;
5
- width: -webkit-fit-content;
6
- width: -moz-fit-content;
7
- width: fit-content;
8
- }
9
-
10
1
  .context-menu {
11
2
  visibility: hidden;
12
3
  opacity: 1;
@@ -25,27 +16,49 @@
25
16
  color: #111418;
26
17
  cursor: pointer;
27
18
  min-width: 80px;
19
+ height: 21px;
20
+ white-space: nowrap;
21
+ flex-direction: row;
22
+ justify-content: space-between;
28
23
  padding: 0 4px;
24
+ display: flex;
25
+ position: relative;
26
+ }
27
+
28
+ .context-menu-item.disabled {
29
+ cursor: not-allowed;
30
+ background-color: rgba(0, 0, 0, .2);
29
31
  }
30
32
 
31
33
  .context-menu-item:first-child {
32
- padding-top: 2px;
34
+ padding-top: 4px;
33
35
  }
34
36
 
35
37
  .context-menu-item:last-child {
36
- padding-bottom: 2px;
38
+ padding-bottom: 4px;
37
39
  }
38
40
 
39
- .context-menu-item:hover {
41
+ .context-menu-item:not(.disabled):hover {
40
42
  background-color: #fbe9e6;
41
43
  }
42
44
 
43
45
  .context-menu-item:hover:first-child {
44
- border-radius: 6px 6px 0 0;
46
+ border-top-left-radius: 6px;
47
+ border-top-right-radius: 6px;
45
48
  }
46
49
 
47
50
  .context-menu-item:hover:last-child {
48
- border-radius: 0 0 6px 6px;
51
+ border-bottom-right-radius: 6px;
52
+ border-bottom-left-radius: 6px;
53
+ }
54
+
55
+ .context-menu-item .caret-holder {
56
+ align-self: flex-end;
57
+ }
58
+
59
+ .context-menu-item .caret-holder .sub-menu {
60
+ z-index: 1;
61
+ position: relative;
49
62
  }
50
63
 
51
64
  /*# sourceMappingURL=main.css.map */
@@ -1 +1 @@
1
- {"mappings":"AAAA;;;;;;;;;AAKA;;;;;;;;;;AAWA;;;;AAIA;;;;;;;AAOA;;;;AAIA;;;;AAIA;;;;AAIA;;;;AAIA","sources":["src/components/ContextMenu.scss"],"sourcesContent":[".context-menu-handler {\n height: fit-content;\n width: fit-content;\n}\n\n.context-menu {\n position: absolute;\n visibility: hidden;\n border: 1px solid;\n border-color: rgb(17, 20, 24);\n border-radius: 8px;\n opacity: 1;\n background-color: rgb(251, 253, 246);\n z-index: 10000;\n}\n\n.context-menu.visible {\n visibility: inherit;\n}\n\n.context-menu-item {\n color: rgb(17, 20, 24);\n cursor: pointer;\n padding: 0 4px;\n min-width: 80px;\n}\n\n.context-menu-item:first-child {\n padding-top: 2px;\n}\n\n.context-menu-item:last-child {\n padding-bottom: 2px;\n}\n\n.context-menu-item:hover {\n background-color: rgb(251, 233, 230);\n}\n\n.context-menu-item:hover:first-child {\n border-radius: 6px 6px 0 0;\n}\n\n.context-menu-item:hover:last-child {\n border-radius: 0 0 6px 6px;\n}\n"],"names":[],"version":3,"file":"main.css.map"}
1
+ {"mappings":"AAAA;;;;;;;;;;AAWA;;;;AAIA;;;;;;;;;;;;;AAaA;;;;;AAKA;;;;AAIA;;;;AAIA;;;;AAIA;;;;;AAKA;;;;;AAKA;;;;AAIA","sources":["src/components/ContextMenu.scss"],"sourcesContent":[".context-menu {\n position: absolute;\n visibility: hidden;\n border: 1px solid;\n border-color: rgb(17, 20, 24);\n border-radius: 8px;\n opacity: 1;\n background-color: rgb(251, 253, 246);\n z-index: 10000;\n}\n\n.context-menu.visible {\n visibility: inherit;\n}\n\n.context-menu-item {\n color: rgb(17, 20, 24);\n cursor: pointer;\n padding: 0 4px;\n min-width: 80px;\n height: 21px;\n position: relative;\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n white-space: nowrap;\n}\n\n.context-menu-item.disabled {\n background-color: rgba(0, 0, 0, 0.2);\n cursor: not-allowed;\n}\n\n.context-menu-item:first-child {\n padding-top: 4px;\n}\n\n.context-menu-item:last-child {\n padding-bottom: 4px;\n}\n\n.context-menu-item:not(.disabled):hover {\n background-color: rgb(251, 233, 230);\n}\n\n.context-menu-item:hover:first-child {\n border-top-left-radius: 6px;\n border-top-right-radius: 6px;\n}\n\n.context-menu-item:hover:last-child {\n border-bottom-left-radius: 6px;\n border-bottom-right-radius: 6px;\n}\n\n.context-menu-item .caret-holder {\n align-self: flex-end;\n}\n\n.context-menu-item .caret-holder .sub-menu {\n z-index: 1;\n position: relative;\n}\n"],"names":[],"version":3,"file":"main.css.map"}
package/dist/cjs/main.js CHANGED
@@ -35,24 +35,75 @@ $parcel$export($a68bd8a6c0fd98c2$exports, "ContextMenuHandler", function () { re
35
35
 
36
36
 
37
37
 
38
+
39
+
40
+
41
+
42
+ var $2275c4645c0c5e94$export$a9c522a6d7beb830 = function(param) {
43
+ var entries = param.entries, toClose = param.toClose;
44
+ var _useState = (0, ($parcel$interopDefault($gTuX4$swchelperslib_sliced_to_arrayjs)))((0, $gTuX4$react.useState)(false), 2), visible = _useState[0], setVisible = _useState[1];
45
+ return /*#__PURE__*/ (0, $gTuX4$reactjsxruntime.jsxs)("span", {
46
+ className: "caret-holder",
47
+ onMouseEnter: function() {
48
+ setVisible(true);
49
+ },
50
+ onMouseLeave: function() {
51
+ setVisible(false);
52
+ },
53
+ children: [
54
+ /*#__PURE__*/ (0, $gTuX4$reactjsxruntime.jsx)("svg", {
55
+ xmlns: "http://www.w3.org/2000/svg",
56
+ width: "16",
57
+ height: "16",
58
+ fill: "currentColor",
59
+ viewBox: "0 0 16 16",
60
+ children: /*#__PURE__*/ (0, $gTuX4$reactjsxruntime.jsx)("path", {
61
+ d: "m12.14 8.753-5.482 4.796c-.646.566-1.658.106-1.658-.753V3.204a1 1 0 0 1 1.659-.753l5.48 4.796a1 1 0 0 1 0 1.506z"
62
+ })
63
+ }),
64
+ /*#__PURE__*/ (0, $gTuX4$reactjsxruntime.jsx)("div", {
65
+ className: "sub-menu",
66
+ children: /*#__PURE__*/ (0, $gTuX4$reactjsxruntime.jsx)((0, $5150b66b01c99189$export$8dc6765e8be191c7), {
67
+ visible: visible,
68
+ entries: entries,
69
+ xPos: 14,
70
+ yPos: -21,
71
+ toClose: toClose
72
+ })
73
+ })
74
+ ]
75
+ });
76
+ };
77
+
78
+
38
79
  var $5150b66b01c99189$export$8dc6765e8be191c7 = /*#__PURE__*/ (0, ($parcel$interopDefault($gTuX4$react))).forwardRef(function(param, ref) {
39
- var entries = param.entries, xPos = param.xPos, yPos = param.yPos, toClose = param.toClose;
80
+ var visible = param.visible, entries = param.entries, xPos = param.xPos, yPos = param.yPos, toClose = param.toClose;
40
81
  $5150b66b01c99189$export$8dc6765e8be191c7.displayName = "ContextMenu";
41
82
  return /*#__PURE__*/ (0, $gTuX4$reactjsxruntime.jsx)("div", {
42
83
  ref: ref,
43
- className: "context-menu visible",
84
+ className: "context-menu".concat(visible ? " visible" : ""),
44
85
  style: {
45
86
  top: "".concat(yPos, "px"),
46
87
  left: "".concat(xPos, "px")
47
88
  },
48
89
  children: entries.map(function(e, i) {
49
- return /*#__PURE__*/ (0, $gTuX4$reactjsxruntime.jsx)("div", {
50
- className: "context-menu-item",
51
- onClick: function() {
52
- e.action && e.action();
53
- toClose();
90
+ return /*#__PURE__*/ (0, $gTuX4$reactjsxruntime.jsxs)("div", {
91
+ className: "context-menu-item".concat(e.disabled ? " disabled" : ""),
92
+ onClick: function(ev) {
93
+ ev.preventDefault();
94
+ ev.stopPropagation();
95
+ e.action && !e.disabled && e.action();
96
+ !e.disabled && toClose();
54
97
  },
55
- children: e.label
98
+ children: [
99
+ /*#__PURE__*/ (0, $gTuX4$reactjsxruntime.jsx)("span", {
100
+ children: e.label
101
+ }),
102
+ e.group && /*#__PURE__*/ (0, $gTuX4$reactjsxruntime.jsx)((0, $2275c4645c0c5e94$export$a9c522a6d7beb830), {
103
+ toClose: toClose,
104
+ entries: e.group
105
+ })
106
+ ]
56
107
  }, i);
57
108
  })
58
109
  });
@@ -61,7 +112,10 @@ var $5150b66b01c99189$export$8dc6765e8be191c7 = /*#__PURE__*/ (0, ($parcel$inter
61
112
 
62
113
 
63
114
  var $3c568ee547c732c3$export$ed4f9641643dc7e4 = function(param) {
64
- var children = param.children, menuItems = param.menuItems;
115
+ var children = param.children, menuItems = param.menuItems, _param_style = param.style, style = _param_style === void 0 ? {
116
+ height: "fit-content",
117
+ width: "fit-content"
118
+ } : _param_style;
65
119
  // Menu resources
66
120
  var divRef = (0, $gTuX4$react.useRef)(null);
67
121
  var menuRef = (0, $gTuX4$react.useRef)(null);
@@ -97,6 +151,7 @@ var $3c568ee547c732c3$export$ed4f9641643dc7e4 = function(param) {
97
151
  /*#__PURE__*/ (0, $gTuX4$reactjsxruntime.jsx)("div", {
98
152
  onContextMenu: showMenu,
99
153
  className: "context-menu-handler",
154
+ style: style,
100
155
  children: children
101
156
  }),
102
157
  menuVisible && /*#__PURE__*/ (0, $gTuX4$reactdom.createPortal)(/*#__PURE__*/ (0, $gTuX4$reactjsxruntime.jsx)("div", {
@@ -107,6 +162,7 @@ var $3c568ee547c732c3$export$ed4f9641643dc7e4 = function(param) {
107
162
  },
108
163
  ref: divRef,
109
164
  children: /*#__PURE__*/ (0, $gTuX4$reactjsxruntime.jsx)((0, $5150b66b01c99189$export$8dc6765e8be191c7), {
165
+ visible: true,
110
166
  ref: menuRef,
111
167
  entries: menuItems,
112
168
  xPos: menuXPos,
@@ -1 +1 @@
1
- {"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AEAA;;;;ACAA;;AAUO,IAAM,0DAAc,CAAA,GAAA,sCAAI,EAAE,UAAU,CACzC,gBAAmC,KAAqB;QAArD,gBAAA,SAAS,aAAA,MAAM,aAAA,MAAM,gBAAA;IACtB,0CAAY,WAAW,GAAG;IAE1B,qBACE,gCAAC;QACC,KAAK;QACL,WAAU;QACV,OAAO;YACL,KAAK,AAAC,GAAO,OAAL,MAAK;YACb,MAAM,AAAC,GAAO,OAAL,MAAK;QAChB;kBAEC,QAAQ,GAAG,CAAC,SAAC,GAAG;iCACf,gCAAC;gBAEC,WAAU;gBACV,SAAS,WAAM;oBACb,EAAE,MAAM,IAAI,EAAE,MAAM;oBACpB;gBACF;0BAEC,EAAE,KAAK;eAPH;;;AAYf;;;;AD/BK,IAAM,4CAAqB,gBAMf;QALjB,iBAAA,UACA,kBAAA;IAKA,iBAAiB;IACjB,IAAM,SAAS,CAAA,GAAA,mBAAK,EAAyB,IAAI;IACjD,IAAM,UAAU,CAAA,GAAA,mBAAK,EAAyB,IAAI;IAClD,IAAgC,kFAAA,CAAA,GAAA,qBAAO,EAAU,QAA1C,WAAyB,cAAf,cAAe;IAChC,IAAgC,mFAAA,CAAA,GAAA,qBAAO,EAAU,QAA1C,WAAyB,eAAf,cAAe;IAChC,IAAsC,mFAAA,CAAA,GAAA,qBAAO,EAAW,KAAK,OAAtD,cAA+B,eAAlB,iBAAkB;IAEtC,sCAAsC;IACtC,IAAM,WAAW,SAAC,GAAkC;QAClD,EAAE,cAAc;QAChB,EAAE,eAAe;QACjB,eAAe,IAAI;QACnB,YAAY,EAAE,KAAK;QACnB,YAAY,EAAE,KAAK;IACrB;IAEA,4BAA4B;IAC5B,IAAM,cAAc,CAAA,GAAA,wBAAW,AAAD,EAAE,SAAC,GAA6B;YAGxB;QAFpC,IACE,QAAQ,OAAO,IACd,CAAA,AAAC,EAAE,MAAM,YAAY,WAAW,EAAC,CAAA,mBAAA,QAAQ,OAAO,cAAf,8BAAA,KAAA,IAAA,iBAAiB,SAAS,EAAE,MAAM,MAClE,CAAE,CAAA,EAAE,MAAM,YAAY,OAAM,CAAC,GAE/B,eAAe,KAAK;IAExB,GAAG,EAAE;IAEL,oCAAoC;IACpC,CAAA,GAAA,sBAAS,AAAD,EAAE,WAAM;QACd,IAAI,aAAa,SAAS,gBAAgB,CAAC,aAAa;aACnD,SAAS,mBAAmB,CAAC,aAAa;QAC/C,OAAO,WAAM;YACX,SAAS,mBAAmB,CAAC,aAAa;QAC5C;IACF,GAAG;QAAC;QAAa;KAAY;IAE7B,qBACE;;0BACE,gCAAC;gBACC,eAAe;gBACf,WAAU;0BAET;;YAEF,6BACC,CAAA,GAAA,4BAAY,AAAD,gBACT,gCAAC;gBACC,OAAO;oBAAE,UAAU;oBAAY,KAAK;oBAAG,MAAM;gBAAE;gBAC/C,KAAK;0BAEL,cAAA,gCAAC,CAAA,GAAA,yCAAW,AAAD;oBACT,KAAK;oBACL,SAAS;oBACT,MAAM;oBACN,MAAM;oBACN,SAAS;+BAAM,eAAe,KAAK;;;gBAGvC,SAAS,IAAI;;;AAIvB;;AD3EA;;ADAA","sources":["src/main.ts","src/components/index.ts","src/components/ContextMenuHandler.tsx","src/components/ContextMenu.tsx"],"sourcesContent":["export * from './components';\n","import { ContextMenuHandler } from './ContextMenuHandler';\nimport { iMenuItem } from './interface';\n\nexport { ContextMenuHandler };\nexport type { iMenuItem };\n","import { MouseEvent, useCallback, useEffect, useRef, useState } from 'react';\nimport { createPortal } from 'react-dom';\nimport { ContextMenu } from './ContextMenu';\nimport { iMenuItem } from './interface';\nimport './ContextMenu.scss';\n\nexport const ContextMenuHandler = ({\n children,\n menuItems,\n}: {\n children: JSX.Element[] | JSX.Element;\n menuItems: iMenuItem[];\n}): JSX.Element => {\n // Menu resources\n const divRef = useRef<HTMLDivElement | null>(null);\n const menuRef = useRef<HTMLDivElement | null>(null);\n const [menuXPos, setMenuXPos] = useState<number>(0);\n const [menuYPos, setMenuYPos] = useState<number>(0);\n const [menuVisible, setMenuVisible] = useState<boolean>(false);\n\n // Show menu when context is requested\n const showMenu = (e: MouseEvent<HTMLDivElement>) => {\n e.preventDefault();\n e.stopPropagation();\n setMenuVisible(true);\n setMenuXPos(e.pageX);\n setMenuYPos(e.pageY);\n };\n\n // Handle click off the menu\n const handleClick = useCallback((e: globalThis.MouseEvent) => {\n if (\n menuRef.current &&\n ((e.target instanceof Element && !menuRef.current?.contains(e.target)) ||\n !(e.target instanceof Element))\n ) {\n setMenuVisible(false);\n }\n }, []);\n\n // Update the document click handler\n useEffect(() => {\n if (menuVisible) document.addEventListener('mousedown', handleClick);\n else document.removeEventListener('mousedown', handleClick);\n return () => {\n document.removeEventListener('mousedown', handleClick);\n };\n }, [handleClick, menuVisible]);\n\n return (\n <>\n <div\n onContextMenu={showMenu}\n className='context-menu-handler'\n >\n {children}\n </div>\n {menuVisible &&\n createPortal(\n <div\n style={{ position: 'absolute', top: 0, left: 0 }}\n ref={divRef}\n >\n <ContextMenu\n ref={menuRef}\n entries={menuItems}\n xPos={menuXPos}\n yPos={menuYPos}\n toClose={() => setMenuVisible(false)}\n />\n </div>,\n document.body,\n )}\n </>\n );\n};\n","import React, { useContext } from 'react';\nimport { iMenuItem } from './interface';\n\nexport interface contextMenuProps {\n entries: iMenuItem[];\n xPos: number;\n yPos: number;\n toClose: () => void;\n}\n\nexport const ContextMenu = React.forwardRef<HTMLDivElement, contextMenuProps>(\n ({ entries, xPos, yPos, toClose }, ref): JSX.Element => {\n ContextMenu.displayName = 'ContextMenu';\n\n return (\n <div\n ref={ref}\n className='context-menu visible'\n style={{\n top: `${yPos}px`,\n left: `${xPos}px`,\n }}\n >\n {entries.map((e, i) => (\n <div\n key={i}\n className='context-menu-item'\n onClick={() => {\n e.action && e.action();\n toClose();\n }}\n >\n {e.label}\n </div>\n ))}\n </div>\n );\n },\n);\n"],"names":[],"version":3,"file":"main.js.map"}
1
+ {"mappings":"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","sources":["src/main.ts","src/components/index.ts","src/components/ContextMenuHandler.tsx","src/components/ContextMenu.tsx","src/components/SubMenu.tsx"],"sourcesContent":["export * from './components';\n","import { ContextMenuHandler } from './ContextMenuHandler';\nimport { iMenuItem } from './interface';\n\nexport { ContextMenuHandler };\nexport type { iMenuItem };\n","import { MouseEvent, useCallback, useEffect, useRef, useState } from 'react';\nimport { createPortal } from 'react-dom';\nimport { ContextMenu } from './ContextMenu';\nimport { iMenuItem } from './interface';\nimport './ContextMenu.scss';\n\nexport const ContextMenuHandler = ({\n children,\n menuItems,\n style = {\n height: 'fit-content',\n width: 'fit-content',\n },\n}: {\n children: JSX.Element[] | JSX.Element;\n menuItems: iMenuItem[];\n style?: React.CSSProperties;\n}): JSX.Element => {\n // Menu resources\n const divRef = useRef<HTMLDivElement | null>(null);\n const menuRef = useRef<HTMLDivElement | null>(null);\n const [menuXPos, setMenuXPos] = useState<number>(0);\n const [menuYPos, setMenuYPos] = useState<number>(0);\n const [menuVisible, setMenuVisible] = useState<boolean>(false);\n\n // Show menu when context is requested\n const showMenu = (e: MouseEvent<HTMLDivElement>) => {\n e.preventDefault();\n e.stopPropagation();\n setMenuVisible(true);\n setMenuXPos(e.pageX);\n setMenuYPos(e.pageY);\n };\n\n // Handle click off the menu\n const handleClick = useCallback((e: globalThis.MouseEvent) => {\n if (\n menuRef.current &&\n ((e.target instanceof Element && !menuRef.current?.contains(e.target)) ||\n !(e.target instanceof Element))\n ) {\n setMenuVisible(false);\n }\n }, []);\n\n // Update the document click handler\n useEffect(() => {\n if (menuVisible) document.addEventListener('mousedown', handleClick);\n else document.removeEventListener('mousedown', handleClick);\n return () => {\n document.removeEventListener('mousedown', handleClick);\n };\n }, [handleClick, menuVisible]);\n\n return (\n <>\n <div\n onContextMenu={showMenu}\n className='context-menu-handler'\n style={style}\n >\n {children}\n </div>\n {menuVisible &&\n createPortal(\n <div\n style={{ position: 'absolute', top: 0, left: 0 }}\n ref={divRef}\n >\n <ContextMenu\n visible={true}\n ref={menuRef}\n entries={menuItems}\n xPos={menuXPos}\n yPos={menuYPos}\n toClose={() => setMenuVisible(false)}\n />\n </div>,\n document.body,\n )}\n </>\n );\n};\n","import React from 'react';\nimport { iMenuItem } from './interface';\nimport { SubMenu } from './SubMenu';\n\nexport interface contextMenuProps {\n visible: boolean;\n entries: iMenuItem[];\n xPos: number;\n yPos: number;\n toClose: () => void;\n}\n\nexport const ContextMenu = React.forwardRef<HTMLDivElement, contextMenuProps>(\n ({ visible, entries, xPos, yPos, toClose }, ref): JSX.Element => {\n ContextMenu.displayName = 'ContextMenu';\n\n return (\n <div\n ref={ref}\n className={`context-menu${visible ? ' visible' : ''}`}\n style={{\n top: `${yPos}px`,\n left: `${xPos}px`,\n }}\n >\n {entries.map((e, i) => (\n <div\n key={i}\n className={`context-menu-item${e.disabled ? ' disabled' : ''}`}\n onClick={(ev) => {\n ev.preventDefault();\n ev.stopPropagation();\n e.action && !e.disabled && e.action();\n !e.disabled && toClose();\n }}\n >\n <span>{e.label}</span>\n {e.group && (\n <SubMenu\n toClose={toClose}\n entries={e.group}\n />\n )}\n </div>\n ))}\n </div>\n );\n },\n);\n","import { useState } from 'react';\nimport { ContextMenu } from './ContextMenu';\nimport { iMenuItem } from './interface';\n\nexport interface subMenuProps {\n entries: iMenuItem[];\n toClose: () => void;\n}\n\nexport const SubMenu = ({ entries, toClose }: subMenuProps): JSX.Element => {\n const [visible, setVisible] = useState<boolean>(false);\n\n return (\n <span\n className='caret-holder'\n onMouseEnter={() => {\n setVisible(true);\n }}\n onMouseLeave={() => {\n setVisible(false);\n }}\n >\n <svg\n xmlns='http://www.w3.org/2000/svg'\n width='16'\n height='16'\n fill='currentColor'\n viewBox='0 0 16 16'\n >\n <path d='m12.14 8.753-5.482 4.796c-.646.566-1.658.106-1.658-.753V3.204a1 1 0 0 1 1.659-.753l5.48 4.796a1 1 0 0 1 0 1.506z' />\n </svg>\n <div className='sub-menu'>\n <ContextMenu\n visible={visible}\n entries={entries}\n xPos={14}\n yPos={-21}\n toClose={toClose}\n />\n </div>\n </span>\n );\n};\n"],"names":[],"version":3,"file":"main.js.map"}
@@ -1,10 +1,13 @@
1
1
  export interface iMenuItem {
2
2
  label: string;
3
+ disabled?: boolean;
3
4
  action?: () => void;
5
+ group?: iMenuItem[];
4
6
  }
5
- export const ContextMenuHandler: ({ children, menuItems, }: {
7
+ export const ContextMenuHandler: ({ children, menuItems, style, }: {
6
8
  children: JSX.Element[] | JSX.Element;
7
9
  menuItems: iMenuItem[];
10
+ style?: import("react").CSSProperties | undefined;
8
11
  }) => JSX.Element;
9
12
 
10
13
  //# sourceMappingURL=context-menu.d.ts.map
@@ -1 +1 @@
1
- {"mappings":"AAAA;IACE,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;CACrB;AEGD,OAAO,MAAM;cAID,WAAW,EAAE,GAAG,WAAW;eAC1B,SAAS,EAAE;MACpB,WA+DH,CAAC","sources":["src/src/components/interface.ts","src/src/components/ContextMenu.tsx","src/src/components/ContextMenuHandler.tsx","src/src/components/index.ts","src/src/main.ts","src/main.ts"],"sourcesContent":[null,null,null,null,null,"export * from './components';\n"],"names":[],"version":3,"file":"context-menu.d.ts.map"}
1
+ {"mappings":"AAAA;IACE,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,KAAK,CAAC,EAAE,SAAS,EAAE,CAAC;CACrB;AGCD,OAAO,MAAM;cAQD,WAAW,EAAE,GAAG,WAAW;eAC1B,SAAS,EAAE;;MAEpB,WAiEH,CAAC","sources":["src/src/components/interface.ts","src/src/components/SubMenu.tsx","src/src/components/ContextMenu.tsx","src/src/components/ContextMenuHandler.tsx","src/src/components/index.ts","src/src/main.ts","src/main.ts"],"sourcesContent":[null,null,null,null,null,null,"export * from './components';\n"],"names":[],"version":3,"file":"context-menu.d.ts.map"}
package/dist/main.css CHANGED
@@ -1,12 +1,3 @@
1
- .context-menu-handler {
2
- height: -webkit-fit-content;
3
- height: -moz-fit-content;
4
- height: fit-content;
5
- width: -webkit-fit-content;
6
- width: -moz-fit-content;
7
- width: fit-content;
8
- }
9
-
10
1
  .context-menu {
11
2
  visibility: hidden;
12
3
  opacity: 1;
@@ -25,27 +16,49 @@
25
16
  color: #111418;
26
17
  cursor: pointer;
27
18
  min-width: 80px;
19
+ height: 21px;
20
+ white-space: nowrap;
21
+ flex-direction: row;
22
+ justify-content: space-between;
28
23
  padding: 0 4px;
24
+ display: flex;
25
+ position: relative;
26
+ }
27
+
28
+ .context-menu-item.disabled {
29
+ cursor: not-allowed;
30
+ background-color: rgba(0, 0, 0, .2);
29
31
  }
30
32
 
31
33
  .context-menu-item:first-child {
32
- padding-top: 2px;
34
+ padding-top: 4px;
33
35
  }
34
36
 
35
37
  .context-menu-item:last-child {
36
- padding-bottom: 2px;
38
+ padding-bottom: 4px;
37
39
  }
38
40
 
39
- .context-menu-item:hover {
41
+ .context-menu-item:not(.disabled):hover {
40
42
  background-color: #fbe9e6;
41
43
  }
42
44
 
43
45
  .context-menu-item:hover:first-child {
44
- border-radius: 6px 6px 0 0;
46
+ border-top-left-radius: 6px;
47
+ border-top-right-radius: 6px;
45
48
  }
46
49
 
47
50
  .context-menu-item:hover:last-child {
48
- border-radius: 0 0 6px 6px;
51
+ border-bottom-right-radius: 6px;
52
+ border-bottom-left-radius: 6px;
53
+ }
54
+
55
+ .context-menu-item .caret-holder {
56
+ align-self: flex-end;
57
+ }
58
+
59
+ .context-menu-item .caret-holder .sub-menu {
60
+ z-index: 1;
61
+ position: relative;
49
62
  }
50
63
 
51
64
  /*# sourceMappingURL=main.css.map */
package/dist/main.css.map CHANGED
@@ -1 +1 @@
1
- {"mappings":"AAAA;;;;;;;;;AAKA;;;;;;;;;;AAWA;;;;AAIA;;;;;;;AAOA;;;;AAIA;;;;AAIA;;;;AAIA;;;;AAIA","sources":["src/components/ContextMenu.scss"],"sourcesContent":[".context-menu-handler {\n height: fit-content;\n width: fit-content;\n}\n\n.context-menu {\n position: absolute;\n visibility: hidden;\n border: 1px solid;\n border-color: rgb(17, 20, 24);\n border-radius: 8px;\n opacity: 1;\n background-color: rgb(251, 253, 246);\n z-index: 10000;\n}\n\n.context-menu.visible {\n visibility: inherit;\n}\n\n.context-menu-item {\n color: rgb(17, 20, 24);\n cursor: pointer;\n padding: 0 4px;\n min-width: 80px;\n}\n\n.context-menu-item:first-child {\n padding-top: 2px;\n}\n\n.context-menu-item:last-child {\n padding-bottom: 2px;\n}\n\n.context-menu-item:hover {\n background-color: rgb(251, 233, 230);\n}\n\n.context-menu-item:hover:first-child {\n border-radius: 6px 6px 0 0;\n}\n\n.context-menu-item:hover:last-child {\n border-radius: 0 0 6px 6px;\n}\n"],"names":[],"version":3,"file":"main.css.map"}
1
+ {"mappings":"AAAA;;;;;;;;;;AAWA;;;;AAIA;;;;;;;;;;;;;AAaA;;;;;AAKA;;;;AAIA;;;;AAIA;;;;AAIA;;;;;AAKA;;;;;AAKA;;;;AAIA","sources":["src/components/ContextMenu.scss"],"sourcesContent":[".context-menu {\n position: absolute;\n visibility: hidden;\n border: 1px solid;\n border-color: rgb(17, 20, 24);\n border-radius: 8px;\n opacity: 1;\n background-color: rgb(251, 253, 246);\n z-index: 10000;\n}\n\n.context-menu.visible {\n visibility: inherit;\n}\n\n.context-menu-item {\n color: rgb(17, 20, 24);\n cursor: pointer;\n padding: 0 4px;\n min-width: 80px;\n height: 21px;\n position: relative;\n display: flex;\n flex-direction: row;\n justify-content: space-between;\n white-space: nowrap;\n}\n\n.context-menu-item.disabled {\n background-color: rgba(0, 0, 0, 0.2);\n cursor: not-allowed;\n}\n\n.context-menu-item:first-child {\n padding-top: 4px;\n}\n\n.context-menu-item:last-child {\n padding-bottom: 4px;\n}\n\n.context-menu-item:not(.disabled):hover {\n background-color: rgb(251, 233, 230);\n}\n\n.context-menu-item:hover:first-child {\n border-top-left-radius: 6px;\n border-top-right-radius: 6px;\n}\n\n.context-menu-item:hover:last-child {\n border-bottom-left-radius: 6px;\n border-bottom-right-radius: 6px;\n}\n\n.context-menu-item .caret-holder {\n align-self: flex-end;\n}\n\n.context-menu-item .caret-holder .sub-menu {\n z-index: 1;\n position: relative;\n}\n"],"names":[],"version":3,"file":"main.css.map"}
package/dist/main.js CHANGED
@@ -14,29 +14,81 @@ $parcel$export($b65191f6d0a0a991$exports, "ContextMenuHandler", function () { re
14
14
 
15
15
 
16
16
 
17
- const $567ed433af94513f$export$8dc6765e8be191c7 = /*#__PURE__*/ (0, $duWW8$react).forwardRef(({ entries: entries , xPos: xPos , yPos: yPos , toClose: toClose }, ref)=>{
17
+
18
+
19
+
20
+ const $885a1576aecbe1c6$export$a9c522a6d7beb830 = ({ entries: entries , toClose: toClose })=>{
21
+ const [visible, setVisible] = (0, $duWW8$useState)(false);
22
+ return /*#__PURE__*/ (0, $duWW8$jsxs)("span", {
23
+ className: "caret-holder",
24
+ onMouseEnter: ()=>{
25
+ setVisible(true);
26
+ },
27
+ onMouseLeave: ()=>{
28
+ setVisible(false);
29
+ },
30
+ children: [
31
+ /*#__PURE__*/ (0, $duWW8$jsx)("svg", {
32
+ xmlns: "http://www.w3.org/2000/svg",
33
+ width: "16",
34
+ height: "16",
35
+ fill: "currentColor",
36
+ viewBox: "0 0 16 16",
37
+ children: /*#__PURE__*/ (0, $duWW8$jsx)("path", {
38
+ d: "m12.14 8.753-5.482 4.796c-.646.566-1.658.106-1.658-.753V3.204a1 1 0 0 1 1.659-.753l5.48 4.796a1 1 0 0 1 0 1.506z"
39
+ })
40
+ }),
41
+ /*#__PURE__*/ (0, $duWW8$jsx)("div", {
42
+ className: "sub-menu",
43
+ children: /*#__PURE__*/ (0, $duWW8$jsx)((0, $567ed433af94513f$export$8dc6765e8be191c7), {
44
+ visible: visible,
45
+ entries: entries,
46
+ xPos: 14,
47
+ yPos: -21,
48
+ toClose: toClose
49
+ })
50
+ })
51
+ ]
52
+ });
53
+ };
54
+
55
+
56
+ const $567ed433af94513f$export$8dc6765e8be191c7 = /*#__PURE__*/ (0, $duWW8$react).forwardRef(({ visible: visible , entries: entries , xPos: xPos , yPos: yPos , toClose: toClose }, ref)=>{
18
57
  $567ed433af94513f$export$8dc6765e8be191c7.displayName = "ContextMenu";
19
58
  return /*#__PURE__*/ (0, $duWW8$jsx)("div", {
20
59
  ref: ref,
21
- className: "context-menu visible",
60
+ className: `context-menu${visible ? " visible" : ""}`,
22
61
  style: {
23
62
  top: `${yPos}px`,
24
63
  left: `${xPos}px`
25
64
  },
26
- children: entries.map((e, i)=>/*#__PURE__*/ (0, $duWW8$jsx)("div", {
27
- className: "context-menu-item",
28
- onClick: ()=>{
29
- e.action && e.action();
30
- toClose();
65
+ children: entries.map((e, i)=>/*#__PURE__*/ (0, $duWW8$jsxs)("div", {
66
+ className: `context-menu-item${e.disabled ? " disabled" : ""}`,
67
+ onClick: (ev)=>{
68
+ ev.preventDefault();
69
+ ev.stopPropagation();
70
+ e.action && !e.disabled && e.action();
71
+ !e.disabled && toClose();
31
72
  },
32
- children: e.label
73
+ children: [
74
+ /*#__PURE__*/ (0, $duWW8$jsx)("span", {
75
+ children: e.label
76
+ }),
77
+ e.group && /*#__PURE__*/ (0, $duWW8$jsx)((0, $885a1576aecbe1c6$export$a9c522a6d7beb830), {
78
+ toClose: toClose,
79
+ entries: e.group
80
+ })
81
+ ]
33
82
  }, i))
34
83
  });
35
84
  });
36
85
 
37
86
 
38
87
 
39
- const $1e1c1e9e0b943830$export$ed4f9641643dc7e4 = ({ children: children , menuItems: menuItems })=>{
88
+ const $1e1c1e9e0b943830$export$ed4f9641643dc7e4 = ({ children: children , menuItems: menuItems , style: style = {
89
+ height: "fit-content",
90
+ width: "fit-content"
91
+ } })=>{
40
92
  // Menu resources
41
93
  const divRef = (0, $duWW8$useRef)(null);
42
94
  const menuRef = (0, $duWW8$useRef)(null);
@@ -72,6 +124,7 @@ const $1e1c1e9e0b943830$export$ed4f9641643dc7e4 = ({ children: children , menuIt
72
124
  /*#__PURE__*/ (0, $duWW8$jsx)("div", {
73
125
  onContextMenu: showMenu,
74
126
  className: "context-menu-handler",
127
+ style: style,
75
128
  children: children
76
129
  }),
77
130
  menuVisible && /*#__PURE__*/ (0, $duWW8$createPortal)(/*#__PURE__*/ (0, $duWW8$jsx)("div", {
@@ -82,6 +135,7 @@ const $1e1c1e9e0b943830$export$ed4f9641643dc7e4 = ({ children: children , menuIt
82
135
  },
83
136
  ref: divRef,
84
137
  children: /*#__PURE__*/ (0, $duWW8$jsx)((0, $567ed433af94513f$export$8dc6765e8be191c7), {
138
+ visible: true,
85
139
  ref: menuRef,
86
140
  entries: menuItems,
87
141
  xPos: menuXPos,
package/dist/main.js.map CHANGED
@@ -1 +1 @@
1
- {"mappings":";;;;;;;;;;;AEAA;;;ACAA;;AAUO,MAAM,0DAAc,CAAA,GAAA,YAAK,AAAD,EAAE,UAAU,CACzC,CAAC,WAAE,QAAO,QAAE,KAAI,QAAE,KAAI,WAAE,QAAO,EAAE,EAAE,MAAqB;IACtD,0CAAY,WAAW,GAAG;IAE1B,qBACE,gBAAC;QACC,KAAK;QACL,WAAU;QACV,OAAO;YACL,KAAK,CAAC,EAAE,KAAK,EAAE,CAAC;YAChB,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC;QACnB;kBAEC,QAAQ,GAAG,CAAC,CAAC,GAAG,kBACf,gBAAC;gBAEC,WAAU;gBACV,SAAS,IAAM;oBACb,EAAE,MAAM,IAAI,EAAE,MAAM;oBACpB;gBACF;0BAEC,EAAE,KAAK;eAPH;;AAYf;;;;AD/BK,MAAM,4CAAqB,CAAC,YACjC,SAAQ,aACR,UAAS,EAIV,GAAkB;IACjB,iBAAiB;IACjB,MAAM,SAAS,CAAA,GAAA,aAAK,EAAyB,IAAI;IACjD,MAAM,UAAU,CAAA,GAAA,aAAK,EAAyB,IAAI;IAClD,MAAM,CAAC,UAAU,YAAY,GAAG,CAAA,GAAA,eAAQ,AAAD,EAAU;IACjD,MAAM,CAAC,UAAU,YAAY,GAAG,CAAA,GAAA,eAAQ,AAAD,EAAU;IACjD,MAAM,CAAC,aAAa,eAAe,GAAG,CAAA,GAAA,eAAO,EAAW,KAAK;IAE7D,sCAAsC;IACtC,MAAM,WAAW,CAAC,IAAkC;QAClD,EAAE,cAAc;QAChB,EAAE,eAAe;QACjB,eAAe,IAAI;QACnB,YAAY,EAAE,KAAK;QACnB,YAAY,EAAE,KAAK;IACrB;IAEA,4BAA4B;IAC5B,MAAM,cAAc,CAAA,GAAA,kBAAW,AAAD,EAAE,CAAC,IAA6B;YAGxB;QAFpC,IACE,QAAQ,OAAO,IACd,CAAA,AAAC,EAAE,MAAM,YAAY,WAAW,EAAC,CAAA,mBAAA,QAAQ,OAAO,cAAf,8BAAA,KAAA,IAAA,iBAAiB,SAAS,EAAE,MAAM,MAClE,CAAE,CAAA,EAAE,MAAM,YAAY,OAAM,CAAC,GAE/B,eAAe,KAAK;IAExB,GAAG,EAAE;IAEL,oCAAoC;IACpC,CAAA,GAAA,gBAAS,AAAD,EAAE,IAAM;QACd,IAAI,aAAa,SAAS,gBAAgB,CAAC,aAAa;aACnD,SAAS,mBAAmB,CAAC,aAAa;QAC/C,OAAO,IAAM;YACX,SAAS,mBAAmB,CAAC,aAAa;QAC5C;IACF,GAAG;QAAC;QAAa;KAAY;IAE7B,qBACE;;0BACE,gBAAC;gBACC,eAAe;gBACf,WAAU;0BAET;;YAEF,6BACC,CAAA,GAAA,mBAAY,AAAD,gBACT,gBAAC;gBACC,OAAO;oBAAE,UAAU;oBAAY,KAAK;oBAAG,MAAM;gBAAE;gBAC/C,KAAK;0BAEL,cAAA,gBAAC,CAAA,GAAA,yCAAW,AAAD;oBACT,KAAK;oBACL,SAAS;oBACT,MAAM;oBACN,MAAM;oBACN,SAAS,IAAM,eAAe,KAAK;;gBAGvC,SAAS,IAAI;;;AAIvB;;AD3EA;;ADAA","sources":["src/main.ts","src/components/index.ts","src/components/ContextMenuHandler.tsx","src/components/ContextMenu.tsx"],"sourcesContent":["export * from './components';\n","import { ContextMenuHandler } from './ContextMenuHandler';\nimport { iMenuItem } from './interface';\n\nexport { ContextMenuHandler };\nexport type { iMenuItem };\n","import { MouseEvent, useCallback, useEffect, useRef, useState } from 'react';\nimport { createPortal } from 'react-dom';\nimport { ContextMenu } from './ContextMenu';\nimport { iMenuItem } from './interface';\nimport './ContextMenu.scss';\n\nexport const ContextMenuHandler = ({\n children,\n menuItems,\n}: {\n children: JSX.Element[] | JSX.Element;\n menuItems: iMenuItem[];\n}): JSX.Element => {\n // Menu resources\n const divRef = useRef<HTMLDivElement | null>(null);\n const menuRef = useRef<HTMLDivElement | null>(null);\n const [menuXPos, setMenuXPos] = useState<number>(0);\n const [menuYPos, setMenuYPos] = useState<number>(0);\n const [menuVisible, setMenuVisible] = useState<boolean>(false);\n\n // Show menu when context is requested\n const showMenu = (e: MouseEvent<HTMLDivElement>) => {\n e.preventDefault();\n e.stopPropagation();\n setMenuVisible(true);\n setMenuXPos(e.pageX);\n setMenuYPos(e.pageY);\n };\n\n // Handle click off the menu\n const handleClick = useCallback((e: globalThis.MouseEvent) => {\n if (\n menuRef.current &&\n ((e.target instanceof Element && !menuRef.current?.contains(e.target)) ||\n !(e.target instanceof Element))\n ) {\n setMenuVisible(false);\n }\n }, []);\n\n // Update the document click handler\n useEffect(() => {\n if (menuVisible) document.addEventListener('mousedown', handleClick);\n else document.removeEventListener('mousedown', handleClick);\n return () => {\n document.removeEventListener('mousedown', handleClick);\n };\n }, [handleClick, menuVisible]);\n\n return (\n <>\n <div\n onContextMenu={showMenu}\n className='context-menu-handler'\n >\n {children}\n </div>\n {menuVisible &&\n createPortal(\n <div\n style={{ position: 'absolute', top: 0, left: 0 }}\n ref={divRef}\n >\n <ContextMenu\n ref={menuRef}\n entries={menuItems}\n xPos={menuXPos}\n yPos={menuYPos}\n toClose={() => setMenuVisible(false)}\n />\n </div>,\n document.body,\n )}\n </>\n );\n};\n","import React, { useContext } from 'react';\nimport { iMenuItem } from './interface';\n\nexport interface contextMenuProps {\n entries: iMenuItem[];\n xPos: number;\n yPos: number;\n toClose: () => void;\n}\n\nexport const ContextMenu = React.forwardRef<HTMLDivElement, contextMenuProps>(\n ({ entries, xPos, yPos, toClose }, ref): JSX.Element => {\n ContextMenu.displayName = 'ContextMenu';\n\n return (\n <div\n ref={ref}\n className='context-menu visible'\n style={{\n top: `${yPos}px`,\n left: `${xPos}px`,\n }}\n >\n {entries.map((e, i) => (\n <div\n key={i}\n className='context-menu-item'\n onClick={() => {\n e.action && e.action();\n toClose();\n }}\n >\n {e.label}\n </div>\n ))}\n </div>\n );\n },\n);\n"],"names":[],"version":3,"file":"main.js.map"}
1
+ {"mappings":"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","sources":["src/main.ts","src/components/index.ts","src/components/ContextMenuHandler.tsx","src/components/ContextMenu.tsx","src/components/SubMenu.tsx"],"sourcesContent":["export * from './components';\n","import { ContextMenuHandler } from './ContextMenuHandler';\nimport { iMenuItem } from './interface';\n\nexport { ContextMenuHandler };\nexport type { iMenuItem };\n","import { MouseEvent, useCallback, useEffect, useRef, useState } from 'react';\nimport { createPortal } from 'react-dom';\nimport { ContextMenu } from './ContextMenu';\nimport { iMenuItem } from './interface';\nimport './ContextMenu.scss';\n\nexport const ContextMenuHandler = ({\n children,\n menuItems,\n style = {\n height: 'fit-content',\n width: 'fit-content',\n },\n}: {\n children: JSX.Element[] | JSX.Element;\n menuItems: iMenuItem[];\n style?: React.CSSProperties;\n}): JSX.Element => {\n // Menu resources\n const divRef = useRef<HTMLDivElement | null>(null);\n const menuRef = useRef<HTMLDivElement | null>(null);\n const [menuXPos, setMenuXPos] = useState<number>(0);\n const [menuYPos, setMenuYPos] = useState<number>(0);\n const [menuVisible, setMenuVisible] = useState<boolean>(false);\n\n // Show menu when context is requested\n const showMenu = (e: MouseEvent<HTMLDivElement>) => {\n e.preventDefault();\n e.stopPropagation();\n setMenuVisible(true);\n setMenuXPos(e.pageX);\n setMenuYPos(e.pageY);\n };\n\n // Handle click off the menu\n const handleClick = useCallback((e: globalThis.MouseEvent) => {\n if (\n menuRef.current &&\n ((e.target instanceof Element && !menuRef.current?.contains(e.target)) ||\n !(e.target instanceof Element))\n ) {\n setMenuVisible(false);\n }\n }, []);\n\n // Update the document click handler\n useEffect(() => {\n if (menuVisible) document.addEventListener('mousedown', handleClick);\n else document.removeEventListener('mousedown', handleClick);\n return () => {\n document.removeEventListener('mousedown', handleClick);\n };\n }, [handleClick, menuVisible]);\n\n return (\n <>\n <div\n onContextMenu={showMenu}\n className='context-menu-handler'\n style={style}\n >\n {children}\n </div>\n {menuVisible &&\n createPortal(\n <div\n style={{ position: 'absolute', top: 0, left: 0 }}\n ref={divRef}\n >\n <ContextMenu\n visible={true}\n ref={menuRef}\n entries={menuItems}\n xPos={menuXPos}\n yPos={menuYPos}\n toClose={() => setMenuVisible(false)}\n />\n </div>,\n document.body,\n )}\n </>\n );\n};\n","import React from 'react';\nimport { iMenuItem } from './interface';\nimport { SubMenu } from './SubMenu';\n\nexport interface contextMenuProps {\n visible: boolean;\n entries: iMenuItem[];\n xPos: number;\n yPos: number;\n toClose: () => void;\n}\n\nexport const ContextMenu = React.forwardRef<HTMLDivElement, contextMenuProps>(\n ({ visible, entries, xPos, yPos, toClose }, ref): JSX.Element => {\n ContextMenu.displayName = 'ContextMenu';\n\n return (\n <div\n ref={ref}\n className={`context-menu${visible ? ' visible' : ''}`}\n style={{\n top: `${yPos}px`,\n left: `${xPos}px`,\n }}\n >\n {entries.map((e, i) => (\n <div\n key={i}\n className={`context-menu-item${e.disabled ? ' disabled' : ''}`}\n onClick={(ev) => {\n ev.preventDefault();\n ev.stopPropagation();\n e.action && !e.disabled && e.action();\n !e.disabled && toClose();\n }}\n >\n <span>{e.label}</span>\n {e.group && (\n <SubMenu\n toClose={toClose}\n entries={e.group}\n />\n )}\n </div>\n ))}\n </div>\n );\n },\n);\n","import { useState } from 'react';\nimport { ContextMenu } from './ContextMenu';\nimport { iMenuItem } from './interface';\n\nexport interface subMenuProps {\n entries: iMenuItem[];\n toClose: () => void;\n}\n\nexport const SubMenu = ({ entries, toClose }: subMenuProps): JSX.Element => {\n const [visible, setVisible] = useState<boolean>(false);\n\n return (\n <span\n className='caret-holder'\n onMouseEnter={() => {\n setVisible(true);\n }}\n onMouseLeave={() => {\n setVisible(false);\n }}\n >\n <svg\n xmlns='http://www.w3.org/2000/svg'\n width='16'\n height='16'\n fill='currentColor'\n viewBox='0 0 16 16'\n >\n <path d='m12.14 8.753-5.482 4.796c-.646.566-1.658.106-1.658-.753V3.204a1 1 0 0 1 1.659-.753l5.48 4.796a1 1 0 0 1 0 1.506z' />\n </svg>\n <div className='sub-menu'>\n <ContextMenu\n visible={visible}\n entries={entries}\n xPos={14}\n yPos={-21}\n toClose={toClose}\n />\n </div>\n </span>\n );\n};\n"],"names":[],"version":3,"file":"main.js.map"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asup/context-menu",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "REACT Typescript Context menu component",
5
5
  "author": "Paul Thomas <@PaulDThomas>",
6
6
  "private": false,
@@ -12,8 +12,7 @@
12
12
  "keywords": [
13
13
  "react",
14
14
  "typescript",
15
- "simple",
16
- "treeview"
15
+ "context menu"
17
16
  ],
18
17
  "bugs": {
19
18
  "url": "https://github.com/PaulDThomas/context-menu/issues"
@@ -41,6 +40,9 @@
41
40
  "@types/node": "^18.11.9",
42
41
  "@types/react": "^18.0.25",
43
42
  "@types/react-dom": "^18.0.8",
43
+ "@typescript-eslint/eslint-plugin": "^5.49.0",
44
+ "@typescript-eslint/parser": "^5.49.0",
45
+ "eslint": "^8.32.0",
44
46
  "eslint-config-prettier": "^8.5.0",
45
47
  "eslint-plugin-prettier": "^4.2.1",
46
48
  "eslint-plugin-react": "^7.31.10",