/** * useConnectionResolver - 런타임 컴포넌트 연결 해석기 * * PopViewerWithModals에서 사용. * layout.dataFlow.connections를 읽고, 소스 컴포넌트의 __comp_output__ 이벤트를 * 타겟 컴포넌트의 __comp_input__ 이벤트로 자동 변환/중계한다. * * 이벤트 규칙: * 소스: __comp_output__${sourceComponentId}__${outputKey} * 타겟: __comp_input__${targetComponentId}__${inputKey} */ import { useEffect, useRef } from "react"; import { usePopEvent } from "./usePopEvent"; import type { PopDataConnection } from "@/components/pop/designer/types/pop-layout"; interface UseConnectionResolverOptions { screenId: string; connections: PopDataConnection[]; } export function useConnectionResolver({ screenId, connections, }: UseConnectionResolverOptions): void { const { publish, subscribe } = usePopEvent(screenId); // 연결 목록을 ref로 저장하여 콜백 안정성 확보 const connectionsRef = useRef(connections); connectionsRef.current = connections; useEffect(() => { if (!connections || connections.length === 0) return; const unsubscribers: (() => void)[] = []; // 소스별로 그룹핑하여 구독 생성 const sourceGroups = new Map(); for (const conn of connections) { const sourceEvent = `__comp_output__${conn.sourceComponent}__${conn.sourceOutput || conn.sourceField}`; const existing = sourceGroups.get(sourceEvent) || []; existing.push(conn); sourceGroups.set(sourceEvent, existing); } for (const [sourceEvent, conns] of sourceGroups) { const unsub = subscribe(sourceEvent, (payload: unknown) => { for (const conn of conns) { const targetEvent = `__comp_input__${conn.targetComponent}__${conn.targetInput || conn.targetField}`; // 항상 통일된 구조로 감싸서 전달: { value, filterConfig?, _connectionId } const enrichedPayload = { value: payload, filterConfig: conn.filterConfig, _connectionId: conn.id, }; publish(targetEvent, enrichedPayload); } }); unsubscribers.push(unsub); } return () => { for (const unsub of unsubscribers) { unsub(); } }; }, [screenId, connections, subscribe, publish]); }