Files
auto-trade/features/trade/hooks/useTradeSearchPanel.ts
2026-02-12 10:24:03 +09:00

119 lines
3.8 KiB
TypeScript

import {
type FocusEvent,
type KeyboardEvent,
useCallback,
useEffect,
useRef,
useState,
} from "react";
import type { KisRuntimeCredentials } from "@/features/settings/store/use-kis-runtime-store";
interface UseTradeSearchPanelParams {
canSearch: boolean;
keyword: string;
verifiedCredentials: KisRuntimeCredentials | null;
search: (query: string, credentials: KisRuntimeCredentials | null) => void;
clearSearch: () => void;
}
/**
* @description 트레이드 검색 패널(열림/닫힘/자동검색/포커스 이탈) UI 상태를 관리합니다.
* @see features/trade/components/TradeContainer.tsx TradeContainer에서 검색 관련 상태 조합을 단순화하기 위해 사용합니다.
* @see features/trade/components/search/TradeSearchSection.tsx 검색 UI 이벤트 핸들러를 전달합니다.
*/
export function useTradeSearchPanel({
canSearch,
keyword,
verifiedCredentials,
search,
clearSearch,
}: UseTradeSearchPanelParams) {
// [Ref] 종목 선택 직후 자동 검색을 1회 건너뛰기 위한 플래그
const skipNextAutoSearchRef = useRef(false);
// [Ref] 검색 패널 루트 (포커스 아웃 감지 범위)
const searchShellRef = useRef<HTMLDivElement | null>(null);
// [State] 검색 패널 열림 상태
const [isSearchPanelOpen, setIsSearchPanelOpen] = useState(false);
/**
* @description 다음 자동 검색 사이클 1회를 건너뛰도록 표시합니다.
* @see features/trade/components/TradeContainer.tsx handleSelectStock 종목 선택 직후 중복 검색 방지에 사용합니다.
*/
const markSkipNextAutoSearch = useCallback(() => {
skipNextAutoSearchRef.current = true;
}, []);
const closeSearchPanel = useCallback(() => {
setIsSearchPanelOpen(false);
}, []);
const openSearchPanel = useCallback(() => {
if (!canSearch) return;
setIsSearchPanelOpen(true);
}, [canSearch]);
/**
* @description 검색 박스에서 포커스가 완전히 벗어나면 드롭다운을 닫습니다.
* @see features/trade/components/search/TradeSearchSection.tsx onBlurCapture 이벤트로 연결됩니다.
*/
const handleSearchShellBlur = useCallback(
(event: FocusEvent<HTMLDivElement>) => {
const nextTarget = event.relatedTarget as Node | null;
if (nextTarget && searchShellRef.current?.contains(nextTarget)) return;
closeSearchPanel();
},
[closeSearchPanel],
);
/**
* @description ESC 키 입력 시 검색 드롭다운을 닫고 포커스를 해제합니다.
* @see features/trade/components/search/TradeSearchSection.tsx onKeyDownCapture 이벤트로 연결됩니다.
*/
const handleSearchShellKeyDown = useCallback(
(event: KeyboardEvent<HTMLDivElement>) => {
if (event.key !== "Escape") return;
closeSearchPanel();
(event.target as HTMLElement | null)?.blur?.();
},
[closeSearchPanel],
);
useEffect(() => {
// [Step 1] 종목 선택 직후 1회 자동 검색 스킵 처리
if (skipNextAutoSearchRef.current) {
skipNextAutoSearchRef.current = false;
return;
}
// [Step 2] 인증 불가 상태면 검색 결과를 즉시 정리
if (!canSearch) {
clearSearch();
return;
}
const trimmed = keyword.trim();
// [Step 3] 입력값이 비어 있으면 검색 상태 초기화
if (!trimmed) {
clearSearch();
return;
}
// [Step 4] 입력 디바운스 후 검색 실행
const timer = window.setTimeout(() => {
search(trimmed, verifiedCredentials);
}, 220);
return () => window.clearTimeout(timer);
}, [canSearch, keyword, verifiedCredentials, search, clearSearch]);
return {
searchShellRef,
isSearchPanelOpen: canSearch && isSearchPanelOpen,
markSkipNextAutoSearch,
openSearchPanel,
closeSearchPanel,
handleSearchShellBlur,
handleSearchShellKeyDown,
};
}