Reference Source

src/plugins/lib/html/MenuEvent.js

import { os } from "../../../viewer/utils/os.js";

//failCallback will be called when the press is not long enough to considered a long press
export function addContextMenuListener(elem, callback, failCallback = () => {}) {
    if (!elem || !callback) return;

    let timeout = null;
    const longPressTimer = 500;
    const MOVE_THRESHOLD = 3;
    let startX, startY;

    const touchStartHandler = (event) => {
        event.preventDefault();
        
        if (timeout) {
            clearTimeout(timeout);
            timeout = null;
        }
        // If more than one finger touches the screen, cancel the timeout
        if (event.touches.length > 1) return;

        const touch = event.touches[0];
        startX = touch.clientX;
        startY = touch.clientY;

        timeout = setTimeout(() => {
            event.clientX = touch.clientX;
            event.clientY = touch.clientY;
            callback(event);
            clearTimeout(timeout);
            timeout = null;
        }, longPressTimer);
    };

    const globalTouchStartHandler = (event) => {
        // Cancel timeout if multiple touches are detected anywhere on the screen
        if (event.touches.length > 1 && timeout) {
            clearTimeout(timeout);
            timeout = null;
        }
    };

    const touchMoveHandler = (event) => {
        if (!timeout) return;
        const touch = event.touches[0];
        const deltaX = Math.abs(touch.clientX - startX);
        const deltaY = Math.abs(touch.clientY - startY);

        if (deltaX > MOVE_THRESHOLD || deltaY > MOVE_THRESHOLD) {
            clearTimeout(timeout);
            timeout = null;
        }
    };

    const touchEndHandler = (event) => {
        event.preventDefault();
        if (timeout) {
            failCallback(event);
            clearTimeout(timeout);
            timeout = null;
        }
    };

    const contextMenuHandler = (event) => {
        callback(event);
        event.preventDefault();
        event.stopPropagation();
    };
    
    if (os.isIphoneSafari()) {
        elem.addEventListener('touchstart', touchStartHandler);
        elem.addEventListener('touchmove', touchMoveHandler);
        elem.addEventListener('touchend', touchEndHandler);
        window.addEventListener('touchstart', globalTouchStartHandler);
    } else {
        elem.addEventListener('contextmenu', contextMenuHandler);
    }

    return function removeContextMenuListener() {
        if (os.isIphoneSafari()) {
            elem.removeEventListener('touchstart', touchStartHandler);
            elem.removeEventListener('touchmove', touchMoveHandler);
            elem.removeEventListener('touchend', touchEndHandler);
            window.removeEventListener('touchstart', globalTouchStartHandler);
        } else {
            elem.removeEventListener('contextmenu', contextMenuHandler);
        }
    };
}