import { useEffect } from 'react' /** * Wischen vom linken Rand öffnet die Seitenleiste, Wischen nach links * schließt sie wieder. * * Bisher gab es dafür nur den Knopf oben links. Auf einem Telefon ist das die * Ecke, die man einhändig am schlechtesten erreicht – gerade auf einem * aufgeklappten Flip. * * Drei Bedingungen, damit die Geste nicht in die Quere kommt: * * * **Nur am Rand.** Sonst öffnete jedes Wischen über eine Kachel die * Leiste, und waagerecht scrollbare Bereiche (Reiterleisten, breite * Tabellen, der PDF-Betrachter im Zoom) wären nicht mehr zu bedienen. * * **Deutlich waagerecht.** Beim Blättern durch eine lange Liste wandert * der Finger fast nie exakt senkrecht; ohne diese Bedingung öffnete sich * die Leiste beim Scrollen. * * **Zügig.** Ein langsames Ziehen ist meistens etwas anderes – ein * Auswählen, ein Verschieben, ein Halten. * * **Auf Android hat das System das erste Wort:** Die Zurück-Geste belegt die * äußersten ~20 Pixel beider Ränder, und was dort beginnt, erreicht die Seite * gar nicht. Die Zone ist deshalb 44 Pixel breit – ein Wisch, der knapp hinter * dem Systembereich ansetzt, wird noch erkannt. */ /** Breite des Streifens am linken Rand, in dem eine Geste beginnen darf. */ const edgeZone = 44 /** So weit muss der Finger waagerecht wandern. */ const minDistance = 60 /** Länger gilt als „gezogen", nicht als „gewischt". */ const maxDuration = 800 interface Options { open: boolean onOpen: () => void onClose: () => void } export function useEdgeSwipe({ open, onOpen, onClose }: Options) { useEffect(() => { if (typeof matchMedia !== 'function' || !matchMedia('(pointer: coarse)').matches) return let startX = 0 let startY = 0 let startedAt = 0 let valid = false function onStart(e: TouchEvent) { valid = false if (e.touches.length !== 1) return // zwei Finger heißt zoomen const t = e.touches[0] // Nicht aus einer bildschirmfüllenden Ansicht heraus: Dort läge die // Leiste hinter dem Betrachter, und man wischt dort durch Seiten. const el = e.target instanceof Element ? e.target : null if (el?.closest('[data-fullscreen]')) return // Geschlossen: nur am Rand. Offen: überall dort, wo die Leiste und ihr // Schatten liegen – weiter rechts hat ein Wisch nichts mit ihr zu tun. if (!open && t.clientX > edgeZone) return if (open && t.clientX > 320) return startX = t.clientX startY = t.clientY startedAt = Date.now() valid = true } function onEnd(e: TouchEvent) { if (!valid) return valid = false const t = e.changedTouches[0] if (!t) return if (Date.now() - startedAt > maxDuration) return const dx = t.clientX - startX const dy = t.clientY - startY // Doppelt so weit zur Seite wie nach oben oder unten: Das trennt ein // Wischen sauber vom Blättern, ohne einen genauen Winkel zu verlangen. if (Math.abs(dx) < minDistance || Math.abs(dx) < Math.abs(dy) * 2) return if (!open && dx > 0) onOpen() else if (open && dx < 0) onClose() } // Passiv: Die Geste verhindert nichts, sie sieht nur zu. Ein nicht-passiver // Zuhörer würde das Scrollen der ganzen Anwendung ausbremsen. document.addEventListener('touchstart', onStart, { passive: true }) document.addEventListener('touchend', onEnd, { passive: true }) return () => { document.removeEventListener('touchstart', onStart) document.removeEventListener('touchend', onEnd) } }, [open, onOpen, onClose]) }