TelegramPWA/src/hooks/useContextMenuHandlers.ts
2021-08-16 16:58:51 +03:00

144 lines
4.5 KiB
TypeScript

import { RefObject } from 'react';
import { useState, useEffect, useCallback } from '../lib/teact/teact';
import { IAnchorPosition } from '../types';
import { IS_TOUCH_ENV, IS_SINGLE_COLUMN_LAYOUT } from '../util/environment';
const LONG_TAP_DURATION_MS = 150;
const SELECTION_ANIMATION_DURATION_MS = 200;
let contextMenuCounter = 0;
function checkIsDisabledForMobile() {
return IS_SINGLE_COLUMN_LAYOUT
&& window.document.body.classList.contains('enable-symbol-menu-transforms');
}
export default (
elementRef: RefObject<HTMLElement>,
isMenuDisabled?: boolean,
shouldDisableOnLink?: boolean,
shouldDisableOnLongTap?: boolean,
) => {
const [isContextMenuOpen, setIsContextMenuOpen] = useState(false);
const [contextMenuPosition, setContextMenuPosition] = useState<IAnchorPosition | undefined>(undefined);
const handleBeforeContextMenu = useCallback((e: React.MouseEvent) => {
if (!isMenuDisabled && e.button === 2) {
document.body.classList.add('no-selection');
}
}, [isMenuDisabled]);
const handleContextMenu = useCallback((e: React.MouseEvent) => {
document.body.classList.remove('no-selection');
if (isMenuDisabled || (shouldDisableOnLink && (e.target as HTMLElement).matches('a.text-entity-link[href]'))) {
return;
}
e.preventDefault();
if (contextMenuPosition) {
return;
}
document.body.classList.remove('no-selection');
if (contextMenuCounter === 0) {
document.body.classList.add('has-context-menu');
}
contextMenuCounter++;
setIsContextMenuOpen(true);
setContextMenuPosition({ x: e.clientX, y: e.clientY });
}, [isMenuDisabled, shouldDisableOnLink, contextMenuPosition]);
const handleContextMenuClose = useCallback(() => {
setIsContextMenuOpen(false);
}, []);
const handleContextMenuHide = useCallback(() => {
setContextMenuPosition(undefined);
document.body.classList.remove('no-selection');
setTimeout(() => {
contextMenuCounter--;
if (contextMenuCounter === 0) {
document.body.classList.remove('has-context-menu');
}
}, SELECTION_ANIMATION_DURATION_MS);
}, []);
// Support context menu on touch-devices
useEffect(() => {
if (isMenuDisabled || !IS_TOUCH_ENV || shouldDisableOnLongTap) {
return undefined;
}
const element = elementRef.current;
if (!element) {
return undefined;
}
let timer: number | undefined;
const clearLongPressTimer = () => {
if (timer) {
clearTimeout(timer);
timer = undefined;
}
};
const emulateContextMenuEvent = (originalEvent: TouchEvent) => {
clearLongPressTimer();
const { clientX, clientY, target } = originalEvent.touches[0];
if (contextMenuPosition || (shouldDisableOnLink && (target as HTMLElement).matches('a.text-entity-link[href]'))) {
return;
}
// temporarily intercept and clear the next click
element.addEventListener('touchend', function cancelClickOnce(e) {
element.removeEventListener('touchend', cancelClickOnce, true);
e.stopImmediatePropagation();
e.preventDefault();
e.stopPropagation();
}, true);
document.body.classList.add('no-selection');
setIsContextMenuOpen(true);
setContextMenuPosition({ x: clientX, y: clientY });
};
const startLongPressTimer = (e: TouchEvent) => {
if (isMenuDisabled || checkIsDisabledForMobile()) {
return;
}
clearLongPressTimer();
timer = window.setTimeout(() => emulateContextMenuEvent(e), LONG_TAP_DURATION_MS);
};
// @perf Consider event delegation
element.addEventListener('touchstart', startLongPressTimer, { passive: true });
element.addEventListener('touchcancel', clearLongPressTimer, true);
element.addEventListener('touchend', clearLongPressTimer, true);
element.addEventListener('touchmove', clearLongPressTimer, { passive: true });
return () => {
clearLongPressTimer();
element.removeEventListener('touchstart', startLongPressTimer);
element.removeEventListener('touchcancel', clearLongPressTimer, true);
element.removeEventListener('touchend', clearLongPressTimer, true);
element.removeEventListener('touchmove', clearLongPressTimer);
};
}, [contextMenuPosition, isMenuDisabled, shouldDisableOnLongTap, elementRef, shouldDisableOnLink]);
return {
isContextMenuOpen,
contextMenuPosition,
handleBeforeContextMenu,
handleContextMenu,
handleContextMenuClose,
handleContextMenuHide,
};
};