/** * Reactive Binding Engine * - 컴포넌트 간 동적 연동 관리 * - 이벤트 → 액션 실행 파이프라인 * - Phase A: 기본 구조만 구현 (실제 실행은 Phase B) */ "use client"; import { ReactiveBinding } from "@/lib/api/metaComponent"; export class ReactiveBindingEngine { private bindings: ReactiveBinding[] = []; private componentRegistry: Map = new Map(); private eventHandlers: Map void>> = new Map(); constructor(bindings: ReactiveBinding[] = []) { this.bindings = bindings.sort((a, b) => (a.priority || 100) - (b.priority || 100)); } /** * 컴포넌트 등록 * - 바인딩 대상 컴포넌트를 등록 */ registerComponent(componentId: string, component: any) { this.componentRegistry.set(componentId, component); console.log(`[ReactiveBinding] 컴포넌트 등록: ${componentId}`); } /** * 컴포넌트 등록 해제 */ unregisterComponent(componentId: string) { this.componentRegistry.delete(componentId); this.eventHandlers.delete(componentId); console.log(`[ReactiveBinding] 컴포넌트 등록 해제: ${componentId}`); } /** * 이벤트 발행 * - 특정 컴포넌트에서 이벤트 발생 시 호출 */ emit(sourceComponentId: string, event: string, data?: any) { console.log(`[ReactiveBinding] 이벤트 발행: ${sourceComponentId}.${event}`, data); // 해당 이벤트에 연결된 바인딩 찾기 const matchedBindings = this.bindings.filter( (binding) => binding.sourceComponentId === sourceComponentId && binding.sourceEvent === event ); if (matchedBindings.length === 0) { console.log(`[ReactiveBinding] 연결된 바인딩 없음`); return; } // 바인딩 실행 (priority 순서대로) matchedBindings.forEach((binding) => { this.executeBinding(binding, data); }); } /** * 바인딩 실행 * - 소스 컴포넌트 이벤트 → 타겟 컴포넌트 액션 */ private executeBinding(binding: ReactiveBinding, data: any) { console.log( `[ReactiveBinding] 바인딩 실행: ${binding.sourceComponentId}.${binding.sourceEvent} → ${binding.targetComponentId}.${binding.targetAction}` ); // 조건 검사 if (binding.conditionConfig && !this.evaluateCondition(binding.conditionConfig, data)) { console.log(`[ReactiveBinding] 조건 불만족, 실행 스킵`); return; } // 데이터 변환 let transformedData = data; if (binding.transformConfig) { transformedData = this.transformData(binding.transformConfig, data); } // 타겟 컴포넌트 찾기 const targetComponent = this.componentRegistry.get(binding.targetComponentId); if (!targetComponent) { console.warn( `[ReactiveBinding] 타겟 컴포넌트를 찾을 수 없음: ${binding.targetComponentId}` ); return; } // 액션 실행 this.executeAction(binding.targetAction, targetComponent, transformedData, binding.targetField); } /** * 조건 평가 */ private evaluateCondition(conditionConfig: any, data: any): boolean { // TODO: Phase B에서 구현 // 예: { type: "field_value", fieldId: "status", operator: "eq", value: "active" } return true; } /** * 데이터 변환 */ private transformData(transformConfig: any, data: any): any { // TODO: Phase B에서 구현 // 예: { type: "calculate", expression: "quantity * price" } return data; } /** * 액션 실행 */ private executeAction( action: string, targetComponent: any, data: any, targetField?: string | null ) { console.log(`[ReactiveBinding] 액션 실행: ${action}`, { targetField, data }); switch (action) { case "filter": // DataView 필터링 if (targetComponent.setFilter) { targetComponent.setFilter(data); } break; case "setValue": // Field 값 설정 if (targetComponent.setValue) { targetComponent.setValue(data); } break; case "show": // 컴포넌트 표시 if (targetComponent.show) { targetComponent.show(); } break; case "hide": // 컴포넌트 숨김 if (targetComponent.hide) { targetComponent.hide(); } break; case "enable": // 컴포넌트 활성화 if (targetComponent.enable) { targetComponent.enable(); } break; case "disable": // 컴포넌트 비활성화 if (targetComponent.disable) { targetComponent.disable(); } break; case "refresh": // 데이터 새로고침 if (targetComponent.refresh) { targetComponent.refresh(); } break; default: console.warn(`[ReactiveBinding] 지원하지 않는 액션: ${action}`); } } /** * 바인딩 추가 */ addBinding(binding: ReactiveBinding) { this.bindings.push(binding); this.bindings.sort((a, b) => (a.priority || 100) - (b.priority || 100)); } /** * 바인딩 제거 */ removeBinding(bindingId: number) { this.bindings = this.bindings.filter((b) => b.id !== bindingId); } /** * 모든 바인딩 조회 */ getBindings(): ReactiveBinding[] { return [...this.bindings]; } }