--- /dev/null
+import React, { useState, useEffect, useRef } from 'react';
+
+import cons from '../../../client/state/cons';
+import navigation from '../../../client/state/navigation';
+
+import ContextMenu from './ContextMenu';
+
+let key = null;
+function ReusableContextMenu() {
+ const [data, setData] = useState(null);
+ const openerRef = useRef(null);
+
+ const closeMenu = () => {
+ key = null;
+ if (data) openerRef.current.click();
+ };
+
+ useEffect(() => {
+ if (data) {
+ const { cords } = data;
+ openerRef.current.style.transform = `translate(${cords.x}px, ${cords.y}px)`;
+ openerRef.current.style.width = `${cords.width}px`;
+ openerRef.current.style.height = `${cords.height}px`;
+ openerRef.current.click();
+ }
+ const handleContextMenuOpen = (placement, cords, render) => {
+ if (key) {
+ closeMenu();
+ return;
+ }
+ setData({ placement, cords, render });
+ };
+ navigation.on(cons.events.navigation.REUSABLE_CONTEXT_MENU_OPENED, handleContextMenuOpen);
+ return () => {
+ navigation.removeListener(
+ cons.events.navigation.REUSABLE_CONTEXT_MENU_OPENED,
+ handleContextMenuOpen,
+ );
+ };
+ }, [data]);
+
+ const handleAfterToggle = (isVisible) => {
+ if (isVisible) {
+ key = Math.random();
+ return;
+ }
+ if (setData) setData(null);
+
+ if (key === null) return;
+ const copyKey = key;
+ setTimeout(() => {
+ if (key === copyKey) key = null;
+ }, 200);
+ };
+
+ return (
+ <ContextMenu
+ afterToggle={handleAfterToggle}
+ placement={data?.placement || 'right'}
+ content={data?.render(closeMenu) ?? ''}
+ render={(toggleMenu) => (
+ <input
+ ref={openerRef}
+ onClick={toggleMenu}
+ type="button"
+ style={{
+ width: '32px',
+ height: '32px',
+ backgroundColor: 'transparent',
+ position: 'fixed',
+ top: 0,
+ left: 0,
+ padding: 0,
+ border: 'none',
+ visibility: 'hidden',
+ appearance: 'none',
+ }}
+ />
+ )}
+ />
+ );
+}
+
+export default ReusableContextMenu;
);
}
-export function getEventCords(ev) {
- const boxInfo = ev.target.getBoundingClientRect();
+/**
+ * @param {Event} ev
+ * @param {string} [targetSelector] element selector for Element.matches([selector])
+ */
+export function getEventCords(ev, targetSelector) {
+ let boxInfo;
+
+ const path = ev.nativeEvent.composedPath();
+ const target = targetSelector
+ ? path.find((element) => element.matches?.(targetSelector))
+ : null;
+ if (target) {
+ boxInfo = target.getBoundingClientRect();
+ } else {
+ boxInfo = ev.target.getBoundingClientRect();
+ }
+
return {
x: boxInfo.x,
y: boxInfo.y,
+ width: boxInfo.width,
+ height: boxInfo.height,
detail: ev.detail,
};
}