(null)
+ // Initial guess; corrected (before paint) by the layout effect once we can measure the menu.
+ const [pos, setPos] = useState<{ top: number; left: number }>(() => ({
+ top: anchor.bottom + 6,
+ left: Math.max(MARGIN, Math.min(anchor.left, window.innerWidth - 240 - MARGIN)),
+ }))
+
+ // Position viewport-aware: flip above the anchor if it would overflow the bottom
+ // (the trigger often sits right above the composer), and clamp into the viewport.
+ useLayoutEffect(() => {
+ const el = ref.current
+ if (!el) return
+ const { height, width } = el.getBoundingClientRect()
+ const vh = window.innerHeight
+ const vw = window.innerWidth
+
+ let top = anchor.bottom + 6
+ if (height > 0 && top + height > vh - MARGIN) {
+ const flipped = anchor.top - height - 6
+ top = flipped >= MARGIN ? flipped : Math.max(MARGIN, vh - height - MARGIN)
+ }
+ let left = anchor.left
+ if (width > 0) left = Math.max(MARGIN, Math.min(left, vw - width - MARGIN))
+ setPos({ top, left })
+ }, [anchor])
+
useEffect(() => {
const onDown = (e: MouseEvent) => { if (!ref.current?.contains(e.target as Node)) onClose() }
const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose() }
@@ -31,8 +58,8 @@ export function OpenWithMenu({ items, anchor, onClose }: Props) {
{items.map((item) => (