144 lines
4.5 KiB
TypeScript
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,
|
|
};
|
|
};
|