diff --git a/com.twin.app.shoptime/src/views/SearchPanel/SearchPanel.new.v2.jsx b/com.twin.app.shoptime/src/views/SearchPanel/SearchPanel.new.v2.jsx index af239205..3852f1ef 100644 --- a/com.twin.app.shoptime/src/views/SearchPanel/SearchPanel.new.v2.jsx +++ b/com.twin.app.shoptime/src/views/SearchPanel/SearchPanel.new.v2.jsx @@ -939,16 +939,16 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) { return 'SEARCH_RESULT_LOADED'; } - // 새로운 음성 검색 결과 모드로 진입 (모드 변경으로 감지 - 일관성 유지) - // - currentMode가 VOICE_RESULT로 변경되고 - // - 이전에는 VOICE_RESULT가 아니었으면 + // 새로운 음성 검색 결과 모드로 진입 (모드 변경 또는 데이터 도착으로 감지) + // - currentMode가 VOICE_RESULT 이고, 새로운 ShopperHouse 데이터가 도착했으면 // - 🎯 중요: isOnTop이 변화하지 않았을 때만 (이미 SearchPanel이 열려있고 새로 검색한 경우) // DetailPanel 복귀(isOnTop 변화)는 위의 DETAIL_PANEL_RETURN에서 먼저 처리됨 if ( isOnTop === isOnTopRef.current && currentMode === SEARCH_PANEL_MODES.VOICE_RESULT && - currentModeRef.current !== SEARCH_PANEL_MODES.VOICE_RESULT && - shopperHouseData + shopperHouseData && + // 🎯 [개선] 모드 변경 OR 새로운 데이터 도착 감지 + (currentModeRef.current !== SEARCH_PANEL_MODES.VOICE_RESULT || shopperHouseDataRef.current !== shopperHouseData) ) { if (DEBUG_MODE) { console.log('[FOCUS] 🎯 Scenario: NEW_SEARCH_LOADED (Voice Result Mode)', { @@ -956,6 +956,8 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) { prevMode: currentModeRef.current, nextMode: currentMode, isOnTopChanged: isOnTop !== isOnTopRef.current, + modeChanged: currentModeRef.current !== SEARCH_PANEL_MODES.VOICE_RESULT, + dataChanged: shopperHouseDataRef.current !== shopperHouseData, }); } return 'NEW_SEARCH_LOADED'; @@ -968,6 +970,14 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) { hasShopperHouseData: !!shopperHouseData, }); } + // 🎯 [중요] 새로운 음성 검색 결과가 도착했으면 NEW_SEARCH_LOADED 우선 처리 + // 이렇게 하면 VOICE_OVERLAY_CLOSED 시나리오에서 TInput으로 가는 것을 방지 + if (shopperHouseData && currentMode === SEARCH_PANEL_MODES.VOICE_RESULT) { + if (DEBUG_MODE) { + console.log('[FOCUS] 🔄 VOICE_OVERLAY_CLOSED + new data → NEW_SEARCH_LOADED 우선 처리'); + } + return 'NEW_SEARCH_LOADED'; + } return 'VOICE_OVERLAY_CLOSED'; } @@ -1840,6 +1850,17 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) { unifiedFocusTimerRef.current = null; }, focusDelay); + // 🎯 [NEW_SEARCH_LOADED] 1초 후 다시 첫 번째 아이템으로 포커스 이동 + // TInputSimple과 Mic Icon의 포커스 충돌 해결을 위해 + if (scenario === 'NEW_SEARCH_LOADED' && targetId === 'searchItemContents0') { + setTimeout(() => { + if (DEBUG_MODE) { + console.log('[FOCUS] 🔄 NEW_SEARCH_LOADED: 1초 후 첫 번째 상품으로 다시 포커스 이동'); + } + Spotlight.focus('searchItemContents0'); + }, 500); // 0.5초 후 + } + // Cleanup: 컴포넌트 언마운트 또는 targetId 변경 시 타이머 정리 return () => { if (unifiedFocusTimerRef.current) { diff --git a/com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/VoiceInputOverlay.v2.jsx b/com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/VoiceInputOverlay.v2.jsx index 6776d575..c041be3e 100644 --- a/com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/VoiceInputOverlay.v2.jsx +++ b/com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/VoiceInputOverlay.v2.jsx @@ -260,6 +260,9 @@ const VoiceInputOverlay = ({ // 약간의 지연 후 닫기 (사용자가 결과를 인지할 수 있도록) closeTimerRef.current = setTimeout(() => { + // 음성 검색 결과가 도착했을 때는 포커스 복원을 하지 않음 + // SearchPanel의 포커스 로직이 첫 번째 상품으로 포커스를 이동시킴 + lastFocusedElement.current = null; onClose(); }, 500); }