round66: Natives Kontextmenue beim Lang-Druecken auf Icons unterdrueckt (verhinderte bisher das Drag-and-Drop)

This commit is contained in:
2026-07-26 20:06:26 +02:00
parent ff87f9812c
commit 8046120548

View File

@@ -30,6 +30,18 @@ import { useHomeLayout, type HomeSlot, type ItemRef } from "../hooks/useHomeLayo
* Maus/Touch/Stift), inklusive Lang-Drücken zum Start (350ms), damit * Maus/Touch/Stift), inklusive Lang-Drücken zum Start (350ms), damit
* normales Scrollen nicht versehentlich als Ziehen interpretiert wird. * normales Scrollen nicht versehentlich als Ziehen interpretiert wird.
* *
* Zusätzlich: mobile Browser fangen ein Lang-Drücken auf einem <img> (bzw.
* dem umgebenden Element) standardmäßig SELBST ab und zeigen ihr eigenes
* Kontextmenü ("Bild speichern"/"Bild kopieren" usw.), noch bevor unser
* eigener Drag-Timer greift (siehe Bugreport). Dagegen: draggable={false} +
* pointer-events-none auf den <img>-Tags (Touch-Events landen dadurch immer
* auf dem umgebenden <button>), -webkit-touch-callout:none (unterdrückt das
* iOS-Kontextmenü), onContextMenu-preventDefault (unterdrückt das
* Android-Pendant) sowie touch-action:manipulation an den Kacheln kombiniert
* mit einem gezielten preventDefault() in handlePointerMove NUR während
* eines aktiven Drags (normales Scrollen bleibt dadurch möglich, wenn gerade
* nicht gezogen wird).
*
* Signatur-Element: der Wallpaper-Hintergrund besteht aus den tatsächlichen, * Signatur-Element: der Wallpaper-Hintergrund besteht aus den tatsächlichen,
* in den eigenen Kategorien hinterlegten Farben (Admin -> Kategorien). * in den eigenen Kategorien hinterlegten Farben (Admin -> Kategorien).
*/ */
@@ -137,7 +149,10 @@ function TileIcon({ favicon, label, size = "normal", dimmed }: TileVisualProps)
<img <img
src={resolveIconSrc(favicon)} src={resolveIconSrc(favicon)}
alt="" alt=""
className={size === "small" ? "h-5 w-5 object-contain" : "h-8 w-8 object-contain"} draggable={false}
className={`pointer-events-none select-none [-webkit-touch-callout:none] ${
size === "small" ? "h-5 w-5" : "h-8 w-8"
} object-contain`}
onError={() => setFailed(true)} onError={() => setFailed(true)}
/> />
) : ( ) : (
@@ -164,7 +179,12 @@ function FolderTile({ icons, dimmed }: { icons: Array<{ favicon: string | null;
className="flex items-center justify-center overflow-hidden rounded-md bg-white/70 dark:bg-white/10" className="flex items-center justify-center overflow-hidden rounded-md bg-white/70 dark:bg-white/10"
> >
{item?.favicon ? ( {item?.favicon ? (
<img src={resolveIconSrc(item.favicon)} alt="" className="h-full w-full object-contain p-0.5" /> <img
src={resolveIconSrc(item.favicon)}
alt=""
draggable={false}
className="pointer-events-none h-full w-full select-none object-contain p-0.5 [-webkit-touch-callout:none]"
/>
) : item ? ( ) : item ? (
<span className="text-[8px] font-semibold text-black/50">{item.label.charAt(0).toUpperCase()}</span> <span className="text-[8px] font-semibold text-black/50">{item.label.charAt(0).toUpperCase()}</span>
) : null} ) : null}
@@ -304,6 +324,11 @@ export function MobileAndroidHome({
return; return;
} }
if (e.pointerId !== drag.pointerId) return; if (e.pointerId !== drag.pointerId) return;
// Solange aktiv gezogen wird, das native Scrollen unterdrücken (siehe
// touch-action: manipulation an den Kacheln - das erlaubt normales
// Scrollen, WENN nicht gezogen wird, verhindert aber, dass die Seite
// während des Ziehens selbst "wegscrollt").
e.preventDefault();
setDrag((d) => (d ? { ...d, x: e.clientX, y: e.clientY } : d)); setDrag((d) => (d ? { ...d, x: e.clientX, y: e.clientY } : d));
const el = document.elementFromPoint(e.clientX, e.clientY); const el = document.elementFromPoint(e.clientX, e.clientY);
@@ -401,7 +426,9 @@ export function MobileAndroidHome({
onPointerMove={handlePointerMove} onPointerMove={handlePointerMove}
onPointerUp={handlePointerUp} onPointerUp={handlePointerUp}
onPointerCancel={handlePointerCancel} onPointerCancel={handlePointerCancel}
onContextMenu={(e) => e.preventDefault()}
onClick={() => handleTileClick(() => setOpenFolderId(slot.id))} onClick={() => handleTileClick(() => setOpenFolderId(slot.id))}
style={{ touchAction: "manipulation", WebkitTouchCallout: "none", WebkitUserSelect: "none", userSelect: "none" }}
className={`flex flex-col items-center gap-1.5 ${isHoverTarget ? "scale-110" : ""} transition-transform`} className={`flex flex-col items-center gap-1.5 ${isHoverTarget ? "scale-110" : ""} transition-transform`}
> >
<FolderTile icons={icons} dimmed={isDragging} /> <FolderTile icons={icons} dimmed={isDragging} />
@@ -424,7 +451,9 @@ export function MobileAndroidHome({
onPointerMove={handlePointerMove} onPointerMove={handlePointerMove}
onPointerUp={handlePointerUp} onPointerUp={handlePointerUp}
onPointerCancel={handlePointerCancel} onPointerCancel={handlePointerCancel}
onContextMenu={(e) => e.preventDefault()}
onClick={() => handleTileClick(() => onOpen({ url: item.url, id: item.id, kind: item.type }))} onClick={() => handleTileClick(() => onOpen({ url: item.url, id: item.id, kind: item.type }))}
style={{ touchAction: "manipulation", WebkitTouchCallout: "none", WebkitUserSelect: "none", userSelect: "none" }}
className={`flex flex-col items-center gap-1.5 ${isHoverTarget ? "scale-110" : ""} transition-transform`} className={`flex flex-col items-center gap-1.5 ${isHoverTarget ? "scale-110" : ""} transition-transform`}
> >
<TileIcon favicon={item.favicon} label={item.displayName} dimmed={isDragging} /> <TileIcon favicon={item.favicon} label={item.displayName} dimmed={isDragging} />