'use client'; import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react'; import type { MenuNode } from '@/lib/api/types/menu'; const HOVER_LEAVE_MS = 280; type FlyoutState = { flyoutRoot: MenuNode | null; l1AnchorRect: DOMRect | null; openFlyout: (node: MenuNode, anchorEl: HTMLDivElement) => void; scheduleCloseFlyout: () => void; clearCloseTimer: () => void; closeFlyoutNow: () => void; toggleFlyoutClick: (node: MenuNode, wrapper: HTMLDivElement | null) => void; l1AnchorElRef: React.RefObject; railScrollRef: React.RefObject; }; /** 从 ClassicCollapsedSidebar 提取的 flyout 状态管理逻辑 */ export function useFlyoutState(pathname: string): FlyoutState { const [flyoutRoot, setFlyoutRoot] = useState(null); const [l1AnchorRect, setL1AnchorRect] = useState(null); const l1AnchorElRef = useRef(null); const railScrollRef = useRef(null); const closeTimerRef = useRef | null>(null); const clearCloseTimer = useCallback(() => { if (closeTimerRef.current) { clearTimeout(closeTimerRef.current); closeTimerRef.current = null; } }, []); const closeFlyout = useCallback(() => { setFlyoutRoot(null); setL1AnchorRect(null); l1AnchorElRef.current = null; }, []); const scheduleCloseFlyout = useCallback(() => { clearCloseTimer(); closeTimerRef.current = setTimeout(() => { closeFlyout(); closeTimerRef.current = null; }, HOVER_LEAVE_MS); }, [clearCloseTimer, closeFlyout]); const closeFlyoutNow = useCallback(() => { clearCloseTimer(); closeFlyout(); }, [clearCloseTimer, closeFlyout]); const syncL1Anchor = useCallback(() => { const el = l1AnchorElRef.current; if (el) setL1AnchorRect(el.getBoundingClientRect()); }, []); useLayoutEffect(() => { if (flyoutRoot) syncL1Anchor(); }, [flyoutRoot, syncL1Anchor]); useEffect(() => { if (!flyoutRoot) return; const onResize = () => syncL1Anchor(); window.addEventListener('resize', onResize); const rs = railScrollRef.current; rs?.addEventListener('scroll', onResize, { passive: true }); return () => { window.removeEventListener('resize', onResize); rs?.removeEventListener('scroll', onResize); }; }, [flyoutRoot, syncL1Anchor]); useEffect(() => { closeFlyout(); }, [pathname, closeFlyout]); useEffect(() => { const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') closeFlyout(); }; window.addEventListener('keydown', onKey); return () => window.removeEventListener('keydown', onKey); }, [closeFlyout]); useEffect(() => clearCloseTimer, [clearCloseTimer]); const openFlyout = useCallback( (node: MenuNode, anchorEl: HTMLDivElement) => { clearCloseTimer(); l1AnchorElRef.current = anchorEl; setL1AnchorRect(anchorEl.getBoundingClientRect()); setFlyoutRoot(node); }, [clearCloseTimer], ); const toggleFlyoutClick = useCallback( (node: MenuNode, wrapper: HTMLDivElement | null) => { setFlyoutRoot((prev) => { if (prev?.id === node.id) { setL1AnchorRect(null); l1AnchorElRef.current = null; return null; } if (wrapper) { l1AnchorElRef.current = wrapper; setL1AnchorRect(wrapper.getBoundingClientRect()); return node; } return prev; }); }, [], ); return { flyoutRoot, l1AnchorRect, openFlyout, scheduleCloseFlyout, clearCloseTimer, closeFlyoutNow, toggleFlyoutClick, l1AnchorElRef, railScrollRef, }; }