From dba53eb8075b028f11c364208de2fbc3e495fe6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20Gon=C3=A7alves=20Marchi?= Date: Tue, 17 Dec 2024 19:23:35 -0300 Subject: [PATCH] fix(ContextMenu): Fix context menu on iOS with long press (#937) --- src/lib/components/ui/ContextMenu.svelte | 84 ++++++++++++++++++++++-- 1 file changed, 78 insertions(+), 6 deletions(-) diff --git a/src/lib/components/ui/ContextMenu.svelte b/src/lib/components/ui/ContextMenu.svelte index 42b09ac61..3ac4a2742 100644 --- a/src/lib/components/ui/ContextMenu.svelte +++ b/src/lib/components/ui/ContextMenu.svelte @@ -10,7 +10,7 @@ import { clickoutside } from "@svelte-put/clickoutside" import { Appearance } from "$lib/enums" import type { ContextItem } from "$lib/types" - import { createEventDispatcher, onMount, tick } from "svelte" + import { createEventDispatcher, onDestroy, onMount, tick } from "svelte" import { log } from "$lib/utils/Logger" import type { PluginListenerHandle } from "@capacitor/core" import { isAndroidOriOS } from "$lib/utils/Mobile" @@ -18,12 +18,16 @@ let visible: boolean = false let coords: [number, number] = [0, 0] let context: HTMLElement + let slotContainer: HTMLElement export let items: ContextItem[] = [] export let hook: string = "" const dispatch = createEventDispatcher() function onClose(event: CustomEvent | MouseEvent) { + if (isLongPress) { + return + } visible = false dispatch("close", event) close_context = undefined @@ -34,9 +38,9 @@ const { width, height } = context.getBoundingClientRect() const offsetX = evt.pageX - const offsetY = evt.pageY - keyboardHeight / 2.5 + const offsetY = evt.pageY const screenWidth = evt.view!.innerWidth - const screenHeight = evt.view!.innerHeight + const screenHeight = evt.view!.innerHeight - keyboardHeight const overFlowX = screenWidth < width + offsetX const overFlowY = screenHeight < height + offsetY @@ -54,13 +58,18 @@ if (close_context !== undefined) { close_context() } - close_context = () => (visible = false) + close_context = () => { + if (!isLongPress) { + visible = false + } + } evt.preventDefault() - visible = true coords = [evt.clientX, evt.clientY] + visible = true await tick() coords = calculatePos(evt) } + let keyboardHeight = 0 onMount(() => { let mobileKeyboardListener01: PluginListenerHandle | undefined @@ -85,6 +94,46 @@ } }) + let touchTimer: number | undefined + let isLongPress: boolean = false + + function handleTouchStart(evt: TouchEvent) { + if (evt.touches.length === 1) { + isLongPress = false + let longPressElement = evt.target as HTMLElement + longPressElement.style.pointerEvents = "none" + touchTimer = window.setTimeout(() => { + const touch = evt.touches[0] + const mouseEvent = new MouseEvent("contextmenu", { + bubbles: true, + cancelable: true, + view: window, + clientX: touch.clientX, + clientY: touch.clientY, + }) + isLongPress = true + openContext(mouseEvent) + }, 500) + } + } + + function handleTouchEnd(evt: TouchEvent) { + clearTimeout(touchTimer) + let longPressElement = evt.target as HTMLElement + longPressElement.style.pointerEvents = "" + if (isLongPress) { + evt.preventDefault() + } + setTimeout(() => { + isLongPress = false + }, 100) + } + + function handleTouchMove(evt: TouchEvent) { + clearTimeout(touchTimer) + isLongPress = false + } + function handleItemClick(e: MouseEvent, item: ContextItem) { e.stopPropagation() log.info(`Clicked ${item.text}`) @@ -94,9 +143,23 @@ }) onClose(customEvent) } + + onMount(() => { + slotContainer.addEventListener("touchstart", handleTouchStart) + slotContainer.addEventListener("touchend", handleTouchEnd) + slotContainer.addEventListener("touchmove", handleTouchMove) + }) + + onDestroy(() => { + slotContainer.removeEventListener("touchstart", handleTouchStart) + slotContainer.removeEventListener("touchend", handleTouchEnd) + slotContainer.removeEventListener("touchmove", handleTouchMove) + }) - +
+ +
{#if visible}
@@ -115,6 +178,15 @@ {/if}