[251026] fix: App.js 전역 SpotlightFocus FOCUS_DEBUG에 따라서 로그기능

🕐 커밋 시간: 2025. 10. 26. 14:59:46

📊 변경 통계:
  • 총 파일: 4개
  • 추가: +191줄
  • 삭제: -7줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/App/App.js
  ~ com.twin.app.shoptime/src/utils/Config.js
  ~ com.twin.app.shoptime/src/views/SearchPanel/SearchPanel.new.v2.jsx
  ~ com.twin.app.shoptime/src/views/SearchPanel/SearchResults.new.v2.jsx

🔧 주요 변경 내용:
  • 핵심 비즈니스 로직 개선
  • 공통 유틸리티 함수 최적화
  • 중간 규모 기능 개선
This commit is contained in:
2025-10-26 14:59:47 +09:00
parent 893c5c16e6
commit c624c60d98
4 changed files with 191 additions and 7 deletions

View File

@@ -267,10 +267,62 @@ console.warn = function (...args) {
};
const originFocus = Spotlight.focus;
const originMove = Spotlight.move;
const originSilentlyFocus = Spotlight.silentlyFocus;
let lastLoggedSpotlightId = null;
let lastLoggedBlurSpotlightId = null;
let focusLoggingSuppressed = 0;
const resolveSpotlightIdFromNode = (node) => {
if (!node) return undefined;
if (node.dataset && node.dataset.spotlightId) {
return node.dataset.spotlightId;
}
if (typeof node.getAttribute === 'function') {
const idFromAttr = node.getAttribute('data-spotlight-id');
if (idFromAttr) {
return idFromAttr;
}
}
if (node.id) {
return node.id;
}
return undefined;
};
const logFocusTransition = (previousNode, currentNode) => {
if (!Config.FOCUS_DEBUG) {
return;
}
const previousId = resolveSpotlightIdFromNode(previousNode);
const currentId = resolveSpotlightIdFromNode(currentNode);
if (previousId && previousId !== currentId) {
console.log(`[SpotlightFocus] blur - ${previousId}`);
lastLoggedBlurSpotlightId = previousId;
}
if (currentId && currentId !== lastLoggedSpotlightId) {
console.log(`[SpotlightFocus] focus - ${currentId}`);
lastLoggedSpotlightId = currentId;
}
};
Spotlight.focus = function (elem, containerOption) {
const previousNode = Spotlight.getCurrent();
const ret = originFocus.apply(this, [elem, containerOption]); // this 바인딩을 유지하여 originFocus 호출
const current = Spotlight.getCurrent();
if ((ret === true || current !== previousNode) && focusLoggingSuppressed === 0) {
logFocusTransition(previousNode, current);
}
if (ret === true) {
const current = Spotlight.getCurrent();
const floatLayerNode = document.getElementById('floatLayer');
const tabLayoutNode = document.getElementById(SpotlightIds.TAB_LAYOUT);
//팝업이 존재할 경우
@@ -300,6 +352,74 @@ Spotlight.focus = function (elem, containerOption) {
return ret;
};
Spotlight.move = function (...args) {
if (!originMove) {
return false;
}
const previousNode = Spotlight.getCurrent();
focusLoggingSuppressed += 1;
let ret;
try {
ret = originMove.apply(this, args);
} finally {
focusLoggingSuppressed = Math.max(0, focusLoggingSuppressed - 1);
}
const current = Spotlight.getCurrent();
if (current !== previousNode) {
logFocusTransition(previousNode, current);
}
return ret;
};
Spotlight.silentlyFocus = function (...args) {
if (!originSilentlyFocus) {
return false;
}
const previousNode = Spotlight.getCurrent();
focusLoggingSuppressed += 1;
let ret;
try {
ret = originSilentlyFocus.apply(this, args);
} finally {
focusLoggingSuppressed = Math.max(0, focusLoggingSuppressed - 1);
}
const current = Spotlight.getCurrent();
if (current !== previousNode) {
logFocusTransition(previousNode, current);
}
return ret;
};
const resolveSpotlightIdFromEvent = (event) => {
if (!event) return undefined;
const { detail, target } = event;
if (detail) {
if (detail.spotlightId) {
return detail.spotlightId;
}
if (detail.id) {
return detail.id;
}
if (detail.target && detail.target.dataset && detail.target.dataset.spotlightId) {
return detail.target.dataset.spotlightId;
}
}
if (target && target.dataset && target.dataset.spotlightId) {
return target.dataset.spotlightId;
}
return undefined;
};
function AppBase(props) {
const dispatch = useDispatch();
const httpHeader = useSelector((state) => state.common.httpHeader);
@@ -317,6 +437,53 @@ function AppBase(props) {
// const termsFlag = useSelector((state) => state.common.termsFlag);
const termsData = useSelector((state) => state.home.termsData);
useEffect(() => {
if (!Config.FOCUS_DEBUG) {
return undefined;
}
const handleFocusLog = (event) => {
const spotlightId = resolveSpotlightIdFromEvent(event);
if (!spotlightId || spotlightId === lastLoggedSpotlightId) {
return;
}
console.log(`[SpotlightFocus] focus - ${spotlightId}`);
lastLoggedSpotlightId = spotlightId;
};
const handleBlurLog = (event) => {
const spotlightId = resolveSpotlightIdFromEvent(event);
if (!spotlightId || spotlightId === lastLoggedBlurSpotlightId) {
return;
}
console.log(`[SpotlightFocus] blur - ${spotlightId}`);
lastLoggedBlurSpotlightId = spotlightId;
};
const hasSpotlightListener = typeof Spotlight.addEventListener === 'function';
if (hasSpotlightListener) {
Spotlight.addEventListener('focus', handleFocusLog);
Spotlight.addEventListener('blur', handleBlurLog);
return () => {
Spotlight.removeEventListener('focus', handleFocusLog);
Spotlight.removeEventListener('blur', handleBlurLog);
};
}
if (typeof document !== 'undefined') {
document.addEventListener('spotlightfocus', handleFocusLog);
document.addEventListener('spotlightblur', handleBlurLog);
return () => {
document.removeEventListener('spotlightfocus', handleFocusLog);
document.removeEventListener('spotlightblur', handleBlurLog);
};
}
return undefined;
}, [Config.FOCUS_DEBUG]);
useEffect(() => {
// Chromium68 호환성을 위해 Optional Chaining 제거
if (termsData && termsData.data && termsData.data.terms) {

View File

@@ -616,3 +616,6 @@ export const LOG_MESSAGE_ID = {
CHECKOUT_CLICK: 'AL_CHECKOUT_CLICK',
BUY_NOW: 'AL_BUY_NOW',
};
// Spotlight 포커스 디버깅 플래그 (true 시 포커스 이동 로그 활성화)
export const FOCUS_DEBUG = true;

View File

@@ -1749,8 +1749,10 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
} // INITIAL, VOICE_RESULT & SEARCH_RESULT 모드에서 TInputSimple 내부 포커스 활성화
onKeyDown={handleKeydown}
spotlightId={SPOTLIGHT_IDS.SEARCH_INPUT_BOX}
// [251026] ADD: Spotlight 포커스 네비게이션 - data-spotlight-down 속성 사용
data-spotlight-down={currentMode === SEARCH_PANEL_MODES.VOICE_RESULT ? 'howAboutThese-seeMore' : undefined}
// 🎯 [포커스 중첩 해결] SearchResultsContainer로 포커스 전달
// SearchResultsContainer가 Spotlight 컨테이너이므로, 포커스가 들어오면
// enterTo: 'last-focused' 설정에 의해 자동으로 HowAboutThese.small의 SEE MORE로 이동
data-spotlight-down={currentMode === SEARCH_PANEL_MODES.VOICE_RESULT ? 'search-results-container' : undefined}
// 🎯 HowAboutThese 포커스 관리 - 포커스가 검색 입력 영역으로 감지
onSpotlightUp={handleSearchInputFocus}
onSpotlightLeft={handleSearchInputFocus}
@@ -1768,8 +1770,8 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
// onFocus={onFocusMic}
onKeyDown={handleMicKeyDown}
spotlightId={SPOTLIGHT_IDS.MICROPHONE_BUTTON}
// [251026] ADD: Spotlight 포커스 네비게이션 - data-spotlight-down 속성 사용
data-spotlight-down={currentMode === SEARCH_PANEL_MODES.VOICE_RESULT ? 'howAboutThese-seeMore' : undefined}
// 🎯 [포커스 중첩 해결] SearchResultsContainer로 포커스 전달
data-spotlight-down={currentMode === SEARCH_PANEL_MODES.VOICE_RESULT ? 'search-results-container' : undefined}
// 🎯 HowAboutThese 포커스 관리 - 포커스가 마이크 버튼으로 감지
onSpotlightUp={handleSearchInputFocus}
onSpotlightLeft={handleSearchInputFocus}

View File

@@ -3,6 +3,7 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import classNames from 'classnames';
import Spotlight from '@enact/spotlight';
import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator';
import Spottable from '@enact/spotlight/Spottable';
import downBtnImg from '../../../assets/images/btn/search_btn_down_arrow.png';
@@ -215,6 +216,13 @@ const SearchResultsNew = ({
// Spottable 컴포넌트 캐싱하여 메모리 누수 방지
const SpottableDiv = useMemo(() => Spottable('div'), []);
// 🎯 [포커스 중첩 해결] SearchResultsNew를 Spotlight 컨테이너로 감싸기
// 이렇게 하면 외부에서 들어온 포커스를 자식 요소(HowAboutThese.small)로 자동 전달
const SearchResultsContainer = useMemo(
() => SpotlightContainerDecorator({ enterTo: 'last-focused' }, 'div'),
[]
);
// ❌ [251026] DEPRECATED: 탭 키다운 핸들러 - Spotlight 속성으로 대체됨
// Spotlight의 data-spotlight-up 속성으로 처리하도록 변경
// 기존 코드 보존 (향후 필요시 참고용)
@@ -353,7 +361,11 @@ const SearchResultsNew = ({
// 이제는 SearchPanel에서 통합 포커스 로직으로 관리됨
return (
<div className={css.searchBox}>
<SearchResultsContainer
className={css.searchBox}
spotlightId="search-results-container"
data-wheel-point="true"
>
{/* HowAboutThese Small 버전 - relativeQueries가 존재할 때만 표시 */}
{relativeQueries && relativeQueries.length > 0 && (
<div>
@@ -445,7 +457,7 @@ const SearchResultsNew = ({
<CustomImage className={css.btnImg} src={upBtnImg} alt="Up arrow" />
</SpottableDiv>
</div>
</div>
</SearchResultsContainer>
);
};