From 73d73cb11e61f4e75d80fafcf9f81aa81893df4c Mon Sep 17 00:00:00 2001
From: Muthu Kumar <muthukumar@thefeathers.in>
Date: Thu, 10 Apr 2025 02:48:24 +0530
Subject: [PATCH] fix: Draggable+Flippable interactions. blur, ESC to stop

---
 src/components/Flippable.tsx          | 14 +++++++++-----
 src/draggable.attempts/6/Draggable.ts | 23 +++++++++++++++--------
 2 files changed, 24 insertions(+), 13 deletions(-)

diff --git a/src/components/Flippable.tsx b/src/components/Flippable.tsx
index ffaf433..8376dc6 100644
--- a/src/components/Flippable.tsx
+++ b/src/components/Flippable.tsx
@@ -19,11 +19,11 @@ export const Flippable: React.FC<FlippableProps> = ({
 	const mouseDownTime = useRef<number>(0);
 	const DRAG_THRESHOLD = 250; // milliseconds
 
-	const handleMouseDown = () => {
+	const down = () => {
 		mouseDownTime.current = Date.now();
 	};
 
-	const handleClick = () => {
+	const up = () => {
 		if (Date.now() - mouseDownTime.current < DRAG_THRESHOLD) {
 			setIsFlipped(prev => !prev);
 
@@ -45,9 +45,8 @@ export const Flippable: React.FC<FlippableProps> = ({
 	return (
 		<div
 			ref={ref}
-			onClick={handleClick}
-			onMouseDown={handleMouseDown}
-			onTouchStart={handleMouseDown}
+			onPointerUp={up}
+			onPointerDown={down}
 			className={cx(
 				css`
 					position: relative;
@@ -66,8 +65,13 @@ export const Flippable: React.FC<FlippableProps> = ({
 						-webkit-backface-visibility: hidden; /* Safari */
 					}
 
+					.card-front {
+						pointer-events: ${isFlipped ? "none" : "auto"};
+					}
+
 					.card-back {
 						rotate: y 180deg;
+						pointer-events: ${isFlipped ? "auto" : "none"};
 					}
 				`,
 				className,
diff --git a/src/draggable.attempts/6/Draggable.ts b/src/draggable.attempts/6/Draggable.ts
index cd3715a..0d6268d 100644
--- a/src/draggable.attempts/6/Draggable.ts
+++ b/src/draggable.attempts/6/Draggable.ts
@@ -88,7 +88,6 @@ export function makeDraggable(card: HTMLElement, opts: DraggableOpts = {}) {
 		state.dragging = true;
 		activePointerId = e.pointerId;
 		card.style.cursor = "grabbing";
-		card.setPointerCapture(e.pointerId);
 		velocity = { x: 0, y: 0 };
 
 		const dx = e.pageX - center.x;
@@ -148,13 +147,17 @@ export function makeDraggable(card: HTMLElement, opts: DraggableOpts = {}) {
 		};
 	};
 
-	const up = (e: PointerEvent) => {
-		if (e.pointerId === activePointerId) {
-			state.dragging = false;
-			activePointerId = null;
-			card.style.cursor = "grab";
-			// Momentum is handled in the render loop
-		}
+	const cancel = () => {
+		state.dragging = false;
+		activePointerId = null;
+		card.style.cursor = "grab";
+		// Momentum is handled in the render loop
+	};
+
+	const up = (e: PointerEvent | FocusEvent | KeyboardEvent) => {
+		if ("pointerId" in e && e.pointerId === activePointerId) return cancel();
+		else if ("key" in e && e.key === "Escape") return cancel();
+		else return cancel();
 	};
 
 	// Debounced Resize Handler using the Reset-Reflow-Recalculate-Reapply strategy
@@ -244,6 +247,8 @@ export function makeDraggable(card: HTMLElement, opts: DraggableOpts = {}) {
 	window.addEventListener("pointermove", move, { passive: true });
 	window.addEventListener("pointerup", up, { passive: true });
 	window.addEventListener("pointercancel", up, { passive: true });
+	window.addEventListener("blur", up, { passive: true });
+	window.addEventListener("keydown", up, { passive: true });
 	window.addEventListener("resize", handleResize);
 
 	render();
@@ -254,6 +259,8 @@ export function makeDraggable(card: HTMLElement, opts: DraggableOpts = {}) {
 		window.removeEventListener("pointermove", move);
 		window.removeEventListener("pointerup", up);
 		window.removeEventListener("pointercancel", up);
+		window.removeEventListener("blur", up);
+		window.removeEventListener("keydown", up);
 		window.removeEventListener("resize", handleResize);
 		card.style.cursor = "";
 		card.style.touchAction = "";