🕐 커밋 시간: 2025. 11. 22. 16:46:50 📊 변경 통계: • 총 파일: 7개 • 추가: +184줄 • 삭제: -1320줄 📁 추가된 파일: + com.twin.app.shoptime/src/hooks/useDetailFocus/index.js + com.twin.app.shoptime/src/hooks/useDetailFocus/useDetailFocus.js 📝 수정된 파일: ~ com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx 🗑️ 삭제된 파일: - com.twin.app.shoptime/DEBUG_MODE_IMPLEMENTATION.md - com.twin.app.shoptime/MEDIAPANEL_CLEANUP_IMPROVEMENTS.md - com.twin.app.shoptime/TIMER_CLEANUP_SUMMARY.md - com.twin.app.shoptime/[251116]_video_state_management_design.md 🔧 함수 변경 내용: 📄 com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx (javascript): 🔄 Modified: extractProductMeta() ❌ Deleted: tryFocusUp() 📄 com.twin.app.shoptime/DEBUG_MODE_IMPLEMENTATION.md (md파일): ❌ Deleted: Before(), After() 📄 com.twin.app.shoptime/MEDIAPANEL_CLEANUP_IMPROVEMENTS.md (md파일): ❌ Deleted: useCallback(), showControls(), areControlsVisible(), toggleControls(), useLayoutEffect(), useEffect(), clearTimeout(), dispatch(), forEach(), getVideoNode(), addEventListener() 📄 com.twin.app.shoptime/TIMER_CLEANUP_SUMMARY.md (md파일): ❌ Deleted: clearTimeout(), clearAllVideoTimers(), pause(), useEffect(), setTimeout(), useCallback(), resume(), dispatch(), stopPropagation(), preventDefault(), disconnect(), updateProportionLoaded(), setInterval(), clearInterval(), useRef() 📄 com.twin.app.shoptime/[251116]_video_state_management_design.md (md파일): ❌ Deleted: curry(), dispatch(), useSelector() 🔧 주요 변경 내용: • 핵심 비즈니스 로직 개선 • 개발 문서 및 가이드 개선 Performance: 코드 최적화로 성능 개선 기대
121 lines
4.4 KiB
JavaScript
121 lines
4.4 KiB
JavaScript
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
import Spotlight from '@enact/spotlight';
|
|
|
|
/**
|
|
* useDetailFocus - 포커스 이동 보정용 Hook
|
|
*
|
|
* ProductAllSection의 복잡한 조건부 렌더링으로 인한 포커스 손실을 방지하기 위해
|
|
* arrow key에 따라 다음 포커스 항목을 queue 형태로 관리하고,
|
|
* 타이머로 포커스 이동을 수행합니다.
|
|
*
|
|
* useEffect의 의존성배열에 따라 타이머가 관리되고,
|
|
* 컴포넌트 unmount 시 자동으로 cleanup됩니다.
|
|
*
|
|
* @param {number} delayMs - 포커스 이동 지연 시간 (기본값: 250ms)
|
|
* @returns {Object} { enqueueFocus }
|
|
*
|
|
* @example
|
|
* const { enqueueFocus } = useDetailFocus(250);
|
|
*
|
|
* const handleArrowDown = (e) => {
|
|
* e.stopPropagation();
|
|
* enqueueFocus('next-button-id');
|
|
* };
|
|
*/
|
|
export default function useDetailFocus(delayMs = 500) {
|
|
const focusQueueRef = useRef([]);
|
|
const [queueTick, setQueueTick] = useState(0);
|
|
const timerRef = useRef(null);
|
|
|
|
/**
|
|
* 포커스 ID를 queue에 추가 (상태만 업데이트, 타이머는 useEffect에서 관리)
|
|
* @param {string} focusId - 포커스할 요소의 ID
|
|
*/
|
|
const enqueueFocus = useCallback(
|
|
(focusId) => {
|
|
if (!focusId) {
|
|
console.warn('[FocusDetail] ⚠️ focusId가 제공되지 않았습니다');
|
|
return;
|
|
}
|
|
|
|
// 큐에 추가하고 tick을 올려 useEffect를 트리거
|
|
focusQueueRef.current.push(focusId);
|
|
console.log(`[FocusDetail] 📋 Queue에 ID 추가: ${focusId} (${delayMs}ms 후 포커스)`);
|
|
setQueueTick((tick) => tick + 1);
|
|
},
|
|
[delayMs]
|
|
);
|
|
|
|
/**
|
|
* focusQueue 상태에 따라 타이머 관리
|
|
* focusQueue가 설정되면 타이머 시작
|
|
* 컴포넌트 unmount 시 useEffect cleanup에서 자동으로 타이머 정리
|
|
*/
|
|
useEffect(() => {
|
|
// queue에 아무것도 없으면 종료
|
|
if (!focusQueueRef.current.length) {
|
|
console.log(`[FocusDetail] 📭 Queue 비어있음 (포커스 보정 불필요)`);
|
|
return undefined;
|
|
}
|
|
|
|
// 기존 타이머가 있으면 취소
|
|
if (timerRef.current) {
|
|
console.log(
|
|
`[FocusDetail] ⏹️ 기존 타이머 취소 - 대기 중인 Queue: ${focusQueueRef.current.join(',')}`
|
|
);
|
|
clearTimeout(timerRef.current);
|
|
}
|
|
|
|
// 새로운 타이머 설정
|
|
const targetId = focusQueueRef.current[focusQueueRef.current.length - 1]; // 마지막 ID
|
|
console.log(`[FocusDetail] ⏱️ 타이머 시작 - ${delayMs}ms 후 포커스 이동: ${targetId}`);
|
|
timerRef.current = setTimeout(() => {
|
|
console.log(`[FocusDetail] ⏱️ 타이머 만료 - 포커스 이동 시도: ${targetId}`);
|
|
|
|
// 현재 포커스된 요소 확인
|
|
const currentElement = Spotlight.getCurrent();
|
|
const currentId = currentElement?.dataset?.spotlightId || currentElement?.id || 'unknown';
|
|
console.log(`[FocusDetail] 📌 현재 포커스 상태: ${currentId}, 타깃: ${targetId}`);
|
|
|
|
try {
|
|
const success = Spotlight.focus(targetId);
|
|
|
|
if (!success) {
|
|
const afterElement = Spotlight.getCurrent();
|
|
const afterId = afterElement?.dataset?.spotlightId || afterElement?.id || 'unknown';
|
|
console.warn(`[FocusDetail] ❌ 포커스 이동 실패: ${targetId} (현재: ${afterId})`);
|
|
console.warn(
|
|
`[FocusDetail] 📋 요소 존재 확인: ${document.querySelector(`[data-spotlight-id="${targetId}"]`) ? '✅ 존재' : '❌ 없음'}`
|
|
);
|
|
} else {
|
|
console.log(`[FocusDetail] ✅ 포커스 이동 성공: ${targetId}`);
|
|
}
|
|
} catch (error) {
|
|
console.error('[FocusDetail] 💥 포커스 이동 중 에러:', error);
|
|
} finally {
|
|
// 타이머 정리
|
|
console.log(`[FocusDetail] 🧹 타이머 정리 - 처리 완료: ${targetId}`);
|
|
timerRef.current = null;
|
|
// Queue 초기화
|
|
focusQueueRef.current = [];
|
|
}
|
|
}, delayMs);
|
|
|
|
// cleanup: 의존성배열 변경 또는 컴포넌트 unmount 시 타이머 정리
|
|
return () => {
|
|
if (timerRef.current) {
|
|
console.log(
|
|
`[FocusDetail] 🧹 useEffect cleanup - 대기 중인 타이머 취소 (Queue: ${focusQueueRef.current.join(',')})`
|
|
);
|
|
clearTimeout(timerRef.current);
|
|
timerRef.current = null;
|
|
}
|
|
};
|
|
}, [queueTick, delayMs]);
|
|
|
|
return {
|
|
enqueueFocus,
|
|
focusQueue: focusQueueRef.current, // 디버깅용
|
|
};
|
|
}
|