preact-context-menu
Version:
A context menu in Preact
128 lines • 5.39 kB
JavaScript
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
import { h, createContext } from "preact";
import { useState, useRef, useEffect, useCallback } from "preact/hooks";
import { createPortal } from "preact/compat";
export var menuOffset = 8;
export var contextMenus = new Map();
export var MenuContext = createContext(undefined);
var menuContainer;
if (typeof window !== 'undefined') {
menuContainer = document.createElement("div");
menuContainer.classList.add("preact-context-menu");
menuContainer.setAttribute("style", "overflow: hidden; pointer-events: none;");
document.body.appendChild(menuContainer);
}
export var ContextMenuWithData = function (props) {
var id = props.id, children = props.children, className = props.className, shouldOpen = props.shouldOpen, onClose = props.onClose, style = props.style, divProps = __rest(props, ["id", "children", "className", "shouldOpen", "onClose", "style"]);
var _a = useState(false), render = _a[0], setRender = _a[1];
var _b = useState(false), wasOpen = _b[0], setWasOpen = _b[1];
var _c = useState(undefined), placement = _c[0], setPlacement = _c[1];
var _d = useState(undefined), eventCoords = _d[0], setEventCoords = _d[1];
var _e = useState(undefined), data = _e[0], setData = _e[1];
var ref = useRef(null);
var trigger = useCallback(function (coords, data) {
if (shouldOpen !== undefined && !shouldOpen(data))
return;
setEventCoords(coords);
setRender(true);
setData(data);
}, [shouldOpen]);
var closeMenu = useCallback(function (data) {
setRender(false);
setPlacement(undefined);
setData(undefined);
if (props.onClose !== undefined)
props.onClose(data);
}, [props.onClose]);
var onClickAway = useCallback(function (event) {
if (ref.current && !ref.current.contains(event.target)) {
closeMenu(undefined);
}
}, []);
useEffect(function () {
if (contextMenus.has(id))
throw new Error("There is another ContextMenu element with the ID " + id);
contextMenus.set(id, trigger);
return function () {
contextMenus.delete(id);
};
}, [id]);
useEffect(function () {
if (render && typeof window !== 'undefined') {
var div = ref.current;
if (div === null)
return;
var coords = eventCoords || { x: 0, y: 0 };
var x = coords.x + menuOffset;
var y = coords.y + menuOffset;
var width = window.innerWidth;
var height = window.innerHeight;
if (x + div.offsetWidth > width - 8) {
x = coords.x - div.offsetWidth;
}
if (y + div.offsetHeight > height - 8) {
y = height - div.offsetHeight - 8;
}
setPlacement({ x: x, y: y });
document.addEventListener('mousedown', onClickAway);
return function () { return document.removeEventListener('mousedown', onClickAway); };
}
return undefined;
}, [render, eventCoords]);
if (render) {
var finalStyle = style || {};
finalStyle.position = "fixed";
if (placement !== undefined) {
finalStyle.top = placement.y;
finalStyle.left = placement.x;
finalStyle.pointerEvents = "initial";
}
else {
finalStyle.opacity = 0;
finalStyle.pointerEvents = "none";
}
var out = children(data);
if (!wasOpen && (!out || out.length === 0)) {
setRender(false);
setPlacement(undefined);
setData(undefined);
return null;
}
setWasOpen(true);
return createPortal(h(MenuContext.Provider, { value: function (data) { return closeMenu(data); } },
h("div", __assign({ ref: ref, id: id }, divProps, {
// This is to stop the browser context menu from opening
onContextMenu: function (event) { return event.preventDefault(); }, className: className !== undefined ? "context-menu " + className : "context-menu", style: finalStyle }), out)), menuContainer);
}
else {
setWasOpen(false);
return null;
}
};
var ContextMenu = function (props) {
var children = props.children, rest = __rest(props, ["children"]);
return (h(ContextMenuWithData, __assign({}, rest), function (_) { return children; }));
};
export default ContextMenu;
//# sourceMappingURL=menu.js.map