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 dragRef = useRef<HTMLDivElement>(null);
|
||||||
const dragStart = useRef<{ mx: number; my: number; bx: number; by: number } | null>(null);
|
const dragStart = useRef<{ mx: number; my: number; bx: number; by: number } | null>(null);
|
||||||
const dragMoved = useRef(false);
|
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
|
// Dragging handlers
|
||||||
const onPointerDown = useCallback((e: React.PointerEvent) => {
|
const onPointerDown = useCallback((e: React.PointerEvent) => {
|
||||||
|
|
@ -389,13 +393,27 @@ export function SasquatchGuide({ message, onDismiss }: SasquatchGuideProps) {
|
||||||
}, [setDragPos]);
|
}, [setDragPos]);
|
||||||
|
|
||||||
const onPointerUp = useCallback((e: React.PointerEvent) => {
|
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;
|
const wasDrag = dragMoved.current;
|
||||||
dragStart.current = null;
|
dragStart.current = null;
|
||||||
dragMoved.current = false;
|
dragMoved.current = false;
|
||||||
setTimeout(() => setIsDragging(false), 50);
|
setTimeout(() => setIsDragging(false), 50);
|
||||||
if (!wasDrag) {
|
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);
|
setIsMinimized(false);
|
||||||
setIsVisible(true);
|
setIsVisible(true);
|
||||||
}
|
}
|
||||||
|
|
@ -739,7 +757,7 @@ Instructions:
|
||||||
<div className="flex gap-1 flex-shrink-0">
|
<div className="flex gap-1 flex-shrink-0">
|
||||||
{/* Minimize — shrinks to draggable bubble */}
|
{/* Minimize — shrinks to draggable bubble */}
|
||||||
<button
|
<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"
|
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"
|
aria-label="Minimize to bubble"
|
||||||
title="Minimize"
|
title="Minimize"
|
||||||
|
|
@ -748,7 +766,7 @@ Instructions:
|
||||||
</button>
|
</button>
|
||||||
{/* Close — hides the panel entirely */}
|
{/* Close — hides the panel entirely */}
|
||||||
<button
|
<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"
|
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"
|
aria-label="Close panel"
|
||||||
title="Close"
|
title="Close"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue