MCPcopy
hub / github.com/coder/mux / useContextMenuPosition

Function useContextMenuPosition

src/browser/hooks/useContextMenuPosition.ts:41–200  ·  view source on GitHub ↗
(
  options?: UseContextMenuPositionOptions
)

Source from the content-addressed store, hash-verified

39 * AgentListItem (draft + regular) and ChatPane's transcript right-click menu.
40 */
41export function useContextMenuPosition(
42 options?: UseContextMenuPositionOptions
43): UseContextMenuPositionReturn {
44 const [isOpen, setIsOpen] = useState(false);
45 const [position, setPosition] = useState<ContextMenuPosition | null>(null);
46
47 // Long-press refs (only used when longPress option is enabled)
48 const longPressTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
49 const touchStartPosRef = useRef<ContextMenuPosition | null>(null);
50 const longPressTriggeredRef = useRef(false);
51
52 // When opening at cursor coordinates, render once with the anchor mounted first,
53 // then flip open in a layout effect. This avoids a one-frame "flash" at an
54 // incorrect fallback anchor before Popover finishes resolving the new anchor.
55 const pendingPositionOpenRef = useRef(false);
56 // Keep canOpen guard fresh so delayed callbacks (like long-press timers)
57 // use the latest availability instead of stale render-time closures.
58 const canOpenRef = useRef(options?.canOpen);
59 canOpenRef.current = options?.canOpen;
60 const canOpenMenu = useCallback(() => {
61 const guard = canOpenRef.current;
62 return guard ? guard() : true;
63 }, []);
64
65 // Clean up timer on unmount
66 useEffect(() => {
67 return () => {
68 if (longPressTimerRef.current) {
69 clearTimeout(longPressTimerRef.current);
70 }
71 };
72 }, []);
73
74 const openAtPosition = useCallback((nextPosition: ContextMenuPosition) => {
75 pendingPositionOpenRef.current = true;
76 setIsOpen(false);
77 setPosition(nextPosition);
78 }, []);
79
80 const close = useCallback(() => {
81 pendingPositionOpenRef.current = false;
82 setIsOpen(false);
83 setPosition(null);
84 }, []);
85
86 const getContextMenuPositionFromEvent = useCallback(
87 (e: React.MouseEvent): ContextMenuPosition => {
88 // Keyboard-triggered activations (click/contextmenu) can report client
89 // coordinates as 0,0. Anchor those opens to the trigger element so menus
90 // appear next to the control.
91 const isKeyboardActivation =
92 (e.type === "click" && e.detail === 0) || (e.clientX === 0 && e.clientY === 0);
93
94 if (!isKeyboardActivation) {
95 return { x: e.clientX, y: e.clientY };
96 }
97
98 const rect = e.currentTarget.getBoundingClientRect();

Callers 6

ProjectSidebarInnerFunction · 0.90
SectionHeaderFunction · 0.90
DraftAgentListItemInnerFunction · 0.90
useTranscriptContextMenuFunction · 0.90
ChatInputInnerFunction · 0.90

Calls 4

guardFunction · 0.85
setIsOpenFunction · 0.85
absMethod · 0.80
getBoundingClientRectMethod · 0.65

Tested by

no test coverage detected