매매 시그널 디테일 팝업 기능 적용
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
import { useLayoutEffect, type RefObject } from 'react';
|
||||
|
||||
/** 스크롤 컨테이너 안에서 target 요소가 뷰포트 중앙(세로)에 오도록 scrollTop 조정 */
|
||||
export function useCenterElementInScrollContainer(
|
||||
scrollRef: RefObject<HTMLElement | null>,
|
||||
targetRef: RefObject<HTMLElement | null>,
|
||||
enabled: boolean,
|
||||
deps: unknown[] = [],
|
||||
): void {
|
||||
useLayoutEffect(() => {
|
||||
if (!enabled) return;
|
||||
const scroll = scrollRef.current;
|
||||
const target = targetRef.current;
|
||||
if (!scroll || !target) return;
|
||||
|
||||
const center = () => {
|
||||
const scrollRect = scroll.getBoundingClientRect();
|
||||
const targetRect = target.getBoundingClientRect();
|
||||
const targetMidInContent =
|
||||
scroll.scrollTop + (targetRect.top - scrollRect.top) + targetRect.height / 2;
|
||||
const targetTop = targetMidInContent - scroll.clientHeight / 2;
|
||||
scroll.scrollTop = Math.max(0, Math.min(
|
||||
targetTop,
|
||||
scroll.scrollHeight - scroll.clientHeight,
|
||||
));
|
||||
};
|
||||
|
||||
center();
|
||||
requestAnimationFrame(center);
|
||||
|
||||
const ro = new ResizeObserver(() => center());
|
||||
ro.observe(scroll);
|
||||
ro.observe(target);
|
||||
return () => ro.disconnect();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [enabled, scrollRef, targetRef, ...deps]);
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import type { ChartManager } from '../utils/ChartManager';
|
||||
|
||||
type LogicalRange = { from: number; to: number };
|
||||
|
||||
/** 분리된 TradingChart 인스턴스 — 가로 시간축(visible logical range) 동기화 */
|
||||
export function useLinkedChartLogicalRange(chartCount = 2) {
|
||||
const managersRef = useRef<(ChartManager | null)[]>(Array(chartCount).fill(null));
|
||||
const unsubsRef = useRef<Array<Array<() => void>>>(
|
||||
Array.from({ length: chartCount }, () => []),
|
||||
);
|
||||
const isSyncingRef = useRef(false);
|
||||
|
||||
const clearUnsubs = useCallback((index: number) => {
|
||||
for (const u of unsubsRef.current[index] ?? []) {
|
||||
try { u(); } catch { /* ok */ }
|
||||
}
|
||||
unsubsRef.current[index] = [];
|
||||
}, []);
|
||||
|
||||
const syncPeers = useCallback((sourceIndex: number, range: LogicalRange) => {
|
||||
if (isSyncingRef.current) return;
|
||||
isSyncingRef.current = true;
|
||||
for (let i = 0; i < managersRef.current.length; i++) {
|
||||
if (i === sourceIndex) continue;
|
||||
const peer = managersRef.current[i];
|
||||
if (!peer?.hasMainSeries() || peer.getRawBarsLength() < 2) continue;
|
||||
peer.applyVisibleLogicalRange(range.from, range.to);
|
||||
}
|
||||
requestAnimationFrame(() => { isSyncingRef.current = false; });
|
||||
}, []);
|
||||
|
||||
const attachRangeSync = useCallback((index: number, mgr: ChartManager) => {
|
||||
clearUnsubs(index);
|
||||
|
||||
const onRange = (r: LogicalRange | null) => {
|
||||
if (!r || isSyncingRef.current) return;
|
||||
syncPeers(index, r);
|
||||
};
|
||||
|
||||
unsubsRef.current[index].push(mgr.subscribeVisibleLogicalRange(onRange));
|
||||
|
||||
// 커스텀 패닝 등 — LWC logical 이벤트가 누락될 수 있어 viewport 알림에도 연결
|
||||
unsubsRef.current[index].push(mgr.subscribeViewport(() => {
|
||||
if (isSyncingRef.current) return;
|
||||
const r = mgr.getVisibleLogicalRange();
|
||||
if (r) syncPeers(index, r);
|
||||
}));
|
||||
}, [clearUnsubs, syncPeers]);
|
||||
|
||||
const registerManager = useCallback((index: number, mgr: ChartManager | null) => {
|
||||
if (index < 0 || index >= managersRef.current.length) return;
|
||||
|
||||
clearUnsubs(index);
|
||||
managersRef.current[index] = mgr;
|
||||
if (!mgr) return;
|
||||
|
||||
attachRangeSync(index, mgr);
|
||||
|
||||
// 후행 차트 마운트 시 — 이미 준비된 peer 시간축에 맞춤
|
||||
for (let i = 0; i < managersRef.current.length; i++) {
|
||||
if (i === index) continue;
|
||||
const peer = managersRef.current[i];
|
||||
if (!peer?.hasMainSeries() || peer.getRawBarsLength() < 2) continue;
|
||||
const peerRange = peer.getVisibleLogicalRange();
|
||||
if (!peerRange) continue;
|
||||
isSyncingRef.current = true;
|
||||
mgr.applyVisibleLogicalRange(peerRange.from, peerRange.to);
|
||||
requestAnimationFrame(() => { isSyncingRef.current = false; });
|
||||
return;
|
||||
}
|
||||
}, [attachRangeSync, clearUnsubs]);
|
||||
|
||||
useEffect(() => () => {
|
||||
for (let i = 0; i < unsubsRef.current.length; i++) {
|
||||
clearUnsubs(i);
|
||||
}
|
||||
}, [clearUnsubs]);
|
||||
|
||||
return { registerManager };
|
||||
}
|
||||
Reference in New Issue
Block a user