| ... | ... | @@ -13,7 +13,15 @@ import { |
| 13 | 13 | } from "lucide-react"; |
| 14 | 14 | import { ProjectFavicon } from "./ProjectFavicon"; |
| 15 | 15 | import { autoAnimate } from "@formkit/auto-animate"; |
| 16 | | import { useCallback, useEffect, useMemo, useRef, useState, type MouseEvent } from "react"; |
| 16 | import { |
| 17 | useCallback, |
| 18 | useEffect, |
| 19 | useMemo, |
| 20 | useRef, |
| 21 | useState, |
| 22 | type MouseEvent, |
| 23 | type PointerEvent, |
| 24 | } from "react"; |
| 17 | 25 | import { |
| 18 | 26 | DndContext, |
| 19 | 27 | type DragCancelEvent, |
| ... | ... | @@ -93,6 +101,7 @@ import { useThreadSelectionStore } from "../threadSelectionStore"; |
| 93 | 101 | import { isNonEmpty as isNonEmptyString } from "effect/String"; |
| 94 | 102 | import { |
| 95 | 103 | getVisibleThreadsForProject, |
| 104 | isContextMenuPointerDown, |
| 96 | 105 | resolveProjectStatusIndicator, |
| 97 | 106 | resolveSidebarNewThreadEnvMode, |
| 98 | 107 | resolveThreadRowClassName, |
| ... | ... | @@ -348,6 +357,7 @@ export default function Sidebar() { |
| 348 | 357 | const renamingInputRef = useRef<HTMLInputElement | null>(null); |
| 349 | 358 | const dragInProgressRef = useRef(false); |
| 350 | 359 | const suppressProjectClickAfterDragRef = useRef(false); |
| 360 | const suppressProjectClickForContextMenuRef = useRef(false); |
| 351 | 361 | const [desktopUpdateState, setDesktopUpdateState] = useState<DesktopUpdateState | null>(null); |
| 352 | 362 | const selectedThreadIds = useThreadSelectionStore((s) => s.selectedThreadIds); |
| 353 | 363 | const toggleThreadSelection = useThreadSelectionStore((s) => s.toggleThread); |
| ... | ... | @@ -941,9 +951,24 @@ export default function Sidebar() { |
| 941 | 951 | animatedThreadListsRef.current.add(node); |
| 942 | 952 | }, []); |
| 943 | 953 | |
| 944 | | const handleProjectTitlePointerDownCapture = useCallback(() => { |
| 945 | | suppressProjectClickAfterDragRef.current = false; |
| 946 | | }, []); |
| 954 | const handleProjectTitlePointerDownCapture = useCallback( |
| 955 | (event: PointerEvent<HTMLButtonElement>) => { |
| 956 | suppressProjectClickForContextMenuRef.current = false; |
| 957 | if ( |
| 958 | isContextMenuPointerDown({ |
| 959 | button: event.button, |
| 960 | ctrlKey: event.ctrlKey, |
| 961 | isMac: isMacPlatform(navigator.platform), |
| 962 | }) |
| 963 | ) { |
| 964 | // Keep context-menu gestures from arming the sortable drag sensor. |
| 965 | event.stopPropagation(); |
| 966 | } |
| 967 | |
| 968 | suppressProjectClickAfterDragRef.current = false; |
| 969 | }, |
| 970 | [], |
| 971 | ); |
| 947 | 972 | |
| 948 | 973 | const visibleThreads = useMemo( |
| 949 | 974 | () => threads.filter((thread) => thread.archivedAt === null), |
| ... | ... | @@ -1242,6 +1267,7 @@ export default function Sidebar() { |
| 1242 | 1267 | onKeyDown={(event) => handleProjectTitleKeyDown(event, project.id)} |
| 1243 | 1268 | onContextMenu={(event) => { |
| 1244 | 1269 | event.preventDefault(); |
| 1270 | suppressProjectClickForContextMenuRef.current = true; |
| 1245 | 1271 | void handleProjectContextMenu(project.id, { |
| 1246 | 1272 | x: event.clientX, |
| 1247 | 1273 | y: event.clientY, |
| ... | ... | @@ -1351,6 +1377,12 @@ export default function Sidebar() { |
| 1351 | 1377 | |
| 1352 | 1378 | const handleProjectTitleClick = useCallback( |
| 1353 | 1379 | (event: React.MouseEvent<HTMLButtonElement>, projectId: ProjectId) => { |
| 1380 | if (suppressProjectClickForContextMenuRef.current) { |
| 1381 | suppressProjectClickForContextMenuRef.current = false; |
| 1382 | event.preventDefault(); |
| 1383 | event.stopPropagation(); |
| 1384 | return; |
| 1385 | } |
| 1354 | 1386 | if (dragInProgressRef.current) { |
| 1355 | 1387 | event.preventDefault(); |
| 1356 | 1388 | event.stopPropagation(); |