Make Sasquatch bubble open reliably on first tap (mobile)
The "1st tap does nothing, 2nd/3rd works" bug came from relying solely on pointer-capture tap detection, which is flaky on the first interaction after mount in mobile WebViews — and the earlier e.preventDefault() on pointerup was suppressing the click we could otherwise use. Fix: add a plain onClick as a reliable open path (the most dependable "tapped" signal in a WebView), extract an idempotent openPanel() used by both pointerup and click, and drop the preventDefault so the click isn't swallowed. The justOpenedRef lock still blocks the ghost-click that would re-close it. Double firing is harmless since openPanel just sets visible=true. Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
This commit is contained in:
parent
ebbd0fd125
commit
834f33d53e
1 changed files with 25 additions and 14 deletions
|
|
@ -392,14 +392,15 @@ export function SasquatchGuide({ message, onDismiss }: SasquatchGuideProps) {
|
|||
setDragPos({ x: newX, y: newY });
|
||||
}, [setDragPos]);
|
||||
|
||||
// Single source of truth for opening the panel. Sets the "just opened" lock
|
||||
// so a stray follow-up event from the same tap can't immediately close it.
|
||||
const openPanel = useCallback(() => {
|
||||
justOpenedRef.current = Date.now();
|
||||
setIsMinimized(false);
|
||||
setIsVisible(true);
|
||||
}, [setIsMinimized]);
|
||||
|
||||
const onPointerUp = useCallback((e: React.PointerEvent) => {
|
||||
// Suppress the synthesized mouse "click" that follows a touch pointerup.
|
||||
// Without this, on mobile WebViews the ghost click lands on whatever element
|
||||
// is now under the finger (the panel unmounts the bubble the instant we
|
||||
// open) and can immediately re-close the panel — the "opens then closes"
|
||||
// bug.
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
try {
|
||||
if (dragRef.current) dragRef.current.releasePointerCapture(e.pointerId);
|
||||
} catch {
|
||||
|
|
@ -409,15 +410,24 @@ export function SasquatchGuide({ message, onDismiss }: SasquatchGuideProps) {
|
|||
dragStart.current = null;
|
||||
dragMoved.current = false;
|
||||
setTimeout(() => setIsDragging(false), 50);
|
||||
// If this pointer sequence was a genuine tap (no real drag), open here.
|
||||
// A parallel onClick handler also opens as a reliable fallback for mobile
|
||||
// WebViews where the pointer-capture tap can be flaky on first interaction;
|
||||
// openPanel is idempotent so double-firing is harmless.
|
||||
if (!wasDrag) {
|
||||
// Always open the panel — even on pages without a context message.
|
||||
// Set a short "just opened" lock so a stray follow-up event from the same
|
||||
// tap can't immediately minimize/close it.
|
||||
justOpenedRef.current = Date.now();
|
||||
setIsMinimized(false);
|
||||
setIsVisible(true);
|
||||
openPanel();
|
||||
}
|
||||
}, [setIsMinimized]);
|
||||
}, [openPanel]);
|
||||
|
||||
// Reliable open path: plain click. On mobile WebViews the pointer-capture tap
|
||||
// detection can miss the very first interaction after mount (the "1st tap does
|
||||
// nothing, 2nd works" bug). A normal onClick is the most dependable "I tapped
|
||||
// this" signal, so we use it as a fallback — but ignore it if the gesture was
|
||||
// actually a drag.
|
||||
const onBubbleClick = useCallback(() => {
|
||||
if (dragMoved.current) return;
|
||||
openPanel();
|
||||
}, [openPanel]);
|
||||
|
||||
const bubbleStyle = useMemo(() => {
|
||||
if (dragPos) {
|
||||
|
|
@ -682,6 +692,7 @@ Instructions:
|
|||
onPointerDown={onPointerDown}
|
||||
onPointerMove={onPointerMove}
|
||||
onPointerUp={onPointerUp}
|
||||
onClick={onBubbleClick}
|
||||
title="Press and drag to move · Tap to open"
|
||||
>
|
||||
<div
|
||||
|
|
|
|||
Loading…
Reference in a new issue