135 lines
6.6 KiB
Plaintext
135 lines
6.6 KiB
Plaintext
var $g3RPq$react = require("react");
|
|
var $g3RPq$reactariainteractions = require("@react-aria/interactions");
|
|
var $g3RPq$reactariautils = require("@react-aria/utils");
|
|
|
|
|
|
function $parcel$export(e, n, v, s) {
|
|
Object.defineProperty(e, n, {get: v, set: s, enumerable: true, configurable: true});
|
|
}
|
|
|
|
$parcel$export(module.exports, "useSafelyMouseToSubmenu", () => $62347d8c4183e713$export$85ec83e04c95f50a);
|
|
|
|
|
|
|
|
const $62347d8c4183e713$var$ALLOWED_INVALID_MOVEMENTS = 2;
|
|
const $62347d8c4183e713$var$THROTTLE_TIME = 50;
|
|
const $62347d8c4183e713$var$TIMEOUT_TIME = 1000;
|
|
const $62347d8c4183e713$var$ANGLE_PADDING = Math.PI / 12; // 15°
|
|
function $62347d8c4183e713$export$85ec83e04c95f50a(options) {
|
|
let { menuRef: menuRef, submenuRef: submenuRef, isOpen: isOpen, isDisabled: isDisabled } = options;
|
|
let prevPointerPos = (0, $g3RPq$react.useRef)(undefined);
|
|
let submenuRect = (0, $g3RPq$react.useRef)(undefined);
|
|
let lastProcessedTime = (0, $g3RPq$react.useRef)(0);
|
|
let timeout = (0, $g3RPq$react.useRef)(undefined);
|
|
let autoCloseTimeout = (0, $g3RPq$react.useRef)(undefined);
|
|
let submenuSide = (0, $g3RPq$react.useRef)(undefined);
|
|
let movementsTowardsSubmenuCount = (0, $g3RPq$react.useRef)(2);
|
|
let [preventPointerEvents, setPreventPointerEvents] = (0, $g3RPq$react.useState)(false);
|
|
let updateSubmenuRect = ()=>{
|
|
if (submenuRef.current) {
|
|
submenuRect.current = submenuRef.current.getBoundingClientRect();
|
|
submenuSide.current = undefined;
|
|
}
|
|
};
|
|
(0, $g3RPq$reactariautils.useResizeObserver)({
|
|
ref: submenuRef,
|
|
onResize: updateSubmenuRect
|
|
});
|
|
let reset = ()=>{
|
|
setPreventPointerEvents(false);
|
|
movementsTowardsSubmenuCount.current = $62347d8c4183e713$var$ALLOWED_INVALID_MOVEMENTS;
|
|
prevPointerPos.current = undefined;
|
|
};
|
|
let modality = (0, $g3RPq$reactariainteractions.useInteractionModality)();
|
|
(0, $g3RPq$react.useEffect)(()=>{
|
|
if (preventPointerEvents && menuRef.current) menuRef.current.style.pointerEvents = 'none';
|
|
else menuRef.current.style.pointerEvents = '';
|
|
}, [
|
|
menuRef,
|
|
preventPointerEvents
|
|
]);
|
|
(0, $g3RPq$react.useEffect)(()=>{
|
|
let submenu = submenuRef.current;
|
|
let menu = menuRef.current;
|
|
if (isDisabled || !submenu || !isOpen || modality !== 'pointer' || !menu) {
|
|
reset();
|
|
return;
|
|
}
|
|
submenuRect.current = submenu.getBoundingClientRect();
|
|
let onPointerMove = (e)=>{
|
|
if (e.pointerType === 'touch' || e.pointerType === 'pen') return;
|
|
let currentTime = Date.now();
|
|
// Throttle
|
|
if (currentTime - lastProcessedTime.current < $62347d8c4183e713$var$THROTTLE_TIME) return;
|
|
clearTimeout(timeout.current);
|
|
clearTimeout(autoCloseTimeout.current);
|
|
let { clientX: mouseX, clientY: mouseY } = e;
|
|
if (!prevPointerPos.current) {
|
|
prevPointerPos.current = {
|
|
x: mouseX,
|
|
y: mouseY
|
|
};
|
|
return;
|
|
}
|
|
if (!submenuRect.current) return;
|
|
if (!submenuSide.current) submenuSide.current = mouseX > submenuRect.current.right ? 'left' : 'right';
|
|
// Pointer is outside of parent menu
|
|
if (mouseX < menu.getBoundingClientRect().left || mouseX > menu.getBoundingClientRect().right || mouseY < menu.getBoundingClientRect().top || mouseY > menu.getBoundingClientRect().bottom) {
|
|
reset();
|
|
return;
|
|
}
|
|
/* Check if pointer is moving towards submenu.
|
|
Uses the 2-argument arctangent (https://en.wikipedia.org/wiki/Atan2) to calculate:
|
|
- angle between previous pointer and top of submenu
|
|
- angle between previous pointer and bottom of submenu
|
|
- angle between previous pointer and current pointer (delta)
|
|
If the pointer delta angle value is between the top and bottom angle values, we know the pointer is moving towards the submenu.
|
|
*/ let prevMouseX = prevPointerPos.current.x;
|
|
let prevMouseY = prevPointerPos.current.y;
|
|
let toSubmenuX = submenuSide.current === 'right' ? submenuRect.current.left - prevMouseX : prevMouseX - submenuRect.current.right;
|
|
let angleTop = Math.atan2(prevMouseY - submenuRect.current.top, toSubmenuX) + $62347d8c4183e713$var$ANGLE_PADDING;
|
|
let angleBottom = Math.atan2(prevMouseY - submenuRect.current.bottom, toSubmenuX) - $62347d8c4183e713$var$ANGLE_PADDING;
|
|
let anglePointer = Math.atan2(prevMouseY - mouseY, submenuSide.current === 'left' ? -(mouseX - prevMouseX) : mouseX - prevMouseX);
|
|
let isMovingTowardsSubmenu = anglePointer < angleTop && anglePointer > angleBottom;
|
|
movementsTowardsSubmenuCount.current = isMovingTowardsSubmenu ? Math.min(movementsTowardsSubmenuCount.current + 1, $62347d8c4183e713$var$ALLOWED_INVALID_MOVEMENTS) : Math.max(movementsTowardsSubmenuCount.current - 1, 0);
|
|
if (movementsTowardsSubmenuCount.current >= $62347d8c4183e713$var$ALLOWED_INVALID_MOVEMENTS) setPreventPointerEvents(true);
|
|
else setPreventPointerEvents(false);
|
|
lastProcessedTime.current = currentTime;
|
|
prevPointerPos.current = {
|
|
x: mouseX,
|
|
y: mouseY
|
|
};
|
|
// If the pointer is moving towards the submenu, start a timeout to close if no other movements are made after 500ms.
|
|
if (isMovingTowardsSubmenu) timeout.current = setTimeout(()=>{
|
|
reset();
|
|
autoCloseTimeout.current = setTimeout(()=>{
|
|
// Fire a pointerover event to trigger the menu to close.
|
|
// Wait until pointer-events:none is no longer applied
|
|
let target = document.elementFromPoint(mouseX, mouseY);
|
|
if (target && menu.contains(target)) target.dispatchEvent(new PointerEvent('pointerover', {
|
|
bubbles: true,
|
|
cancelable: true
|
|
}));
|
|
}, 100);
|
|
}, $62347d8c4183e713$var$TIMEOUT_TIME);
|
|
};
|
|
window.addEventListener('pointermove', onPointerMove);
|
|
return ()=>{
|
|
window.removeEventListener('pointermove', onPointerMove);
|
|
clearTimeout(timeout.current);
|
|
clearTimeout(autoCloseTimeout.current);
|
|
movementsTowardsSubmenuCount.current = $62347d8c4183e713$var$ALLOWED_INVALID_MOVEMENTS;
|
|
};
|
|
}, [
|
|
isDisabled,
|
|
isOpen,
|
|
menuRef,
|
|
modality,
|
|
setPreventPointerEvents,
|
|
submenuRef
|
|
]);
|
|
}
|
|
|
|
|
|
//# sourceMappingURL=useSafelyMouseToSubmenu.main.js.map
|