Fix Sasquatch panel opening then instantly closing on mobile
On touch devices, the bubble's pointerup opened the panel and unmounted the bubble; the synthesized "ghost click" from the same tap then landed on the panel's minimize/close button (which renders in roughly the same bottom-left spot), instantly re-closing it. Fix: preventDefault/stopPropagation on pointerup to suppress the ghost click, guard releasePointerCapture against unmount throws, and add a 400ms "just opened" lock so stray close/minimize events from the same tap are ignored. Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
This commit is contained in:
parent
5ec1c84ef0
commit
ebbd0fd125
1 changed files with 22 additions and 4 deletions
|
|
@ -365,6 +365,10 @@ export function SasquatchGuide({ message, onDismiss }: SasquatchGuideProps) {
|
|||
const dragRef = useRef<HTMLDivElement>(null);
|
||||
const dragStart = useRef<{ mx: number; my: number; bx: number; by: number } | null>(null);
|
||||
const dragMoved = useRef(false);
|
||||
// Timestamp of the last panel-open via tap. Used to ignore stray
|
||||
// close/minimize events that fire from the same tap on mobile.
|
||||
const justOpenedRef = useRef(0);
|
||||
const recentlyOpened = () => Date.now() - justOpenedRef.current < 400;
|
||||
|
||||
// Dragging handlers
|
||||
const onPointerDown = useCallback((e: React.PointerEvent) => {
|
||||
|
|
@ -389,13 +393,27 @@ export function SasquatchGuide({ message, onDismiss }: SasquatchGuideProps) {
|
|||
}, [setDragPos]);
|
||||
|
||||
const onPointerUp = useCallback((e: React.PointerEvent) => {
|
||||
if (dragRef.current) dragRef.current.releasePointerCapture(e.pointerId);
|
||||
// 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 {
|
||||
// releasePointerCapture can throw if the element is unmounting — ignore.
|
||||
}
|
||||
const wasDrag = dragMoved.current;
|
||||
dragStart.current = null;
|
||||
dragMoved.current = false;
|
||||
setTimeout(() => setIsDragging(false), 50);
|
||||
if (!wasDrag) {
|
||||
// Always open the panel — even on pages without a context message
|
||||
// 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);
|
||||
}
|
||||
|
|
@ -739,7 +757,7 @@ Instructions:
|
|||
<div className="flex gap-1 flex-shrink-0">
|
||||
{/* Minimize — shrinks to draggable bubble */}
|
||||
<button
|
||||
onClick={() => setIsMinimized(true)}
|
||||
onClick={() => { if (recentlyOpened()) return; setIsMinimized(true); }}
|
||||
className="w-6 h-6 flex items-center justify-center rounded text-[#ccff00]/40 hover:text-[#ccff00] hover:bg-[#ccff00]/10 transition-colors"
|
||||
aria-label="Minimize to bubble"
|
||||
title="Minimize"
|
||||
|
|
@ -748,7 +766,7 @@ Instructions:
|
|||
</button>
|
||||
{/* Close — hides the panel entirely */}
|
||||
<button
|
||||
onClick={() => { setIsVisible(false); if (onDismiss) onDismiss(); }}
|
||||
onClick={() => { if (recentlyOpened()) return; setIsVisible(false); if (onDismiss) onDismiss(); }}
|
||||
className="w-6 h-6 flex items-center justify-center rounded text-white/30 hover:text-white/80 hover:bg-white/10 transition-colors"
|
||||
aria-label="Close panel"
|
||||
title="Close"
|
||||
|
|
|
|||
Loading…
Reference in a new issue