[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:
@@ -267,10 +267,62 @@ console.warn = function (...args) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const originFocus = Spotlight.focus;
|
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) {
|
Spotlight.focus = function (elem, containerOption) {
|
||||||
|
const previousNode = Spotlight.getCurrent();
|
||||||
|
|
||||||
const ret = originFocus.apply(this, [elem, containerOption]); // this 바인딩을 유지하여 originFocus 호출
|
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) {
|
if (ret === true) {
|
||||||
const current = Spotlight.getCurrent();
|
|
||||||
const floatLayerNode = document.getElementById('floatLayer');
|
const floatLayerNode = document.getElementById('floatLayer');
|
||||||
const tabLayoutNode = document.getElementById(SpotlightIds.TAB_LAYOUT);
|
const tabLayoutNode = document.getElementById(SpotlightIds.TAB_LAYOUT);
|
||||||
//팝업이 존재할 경우
|
//팝업이 존재할 경우
|
||||||
@@ -300,6 +352,74 @@ Spotlight.focus = function (elem, containerOption) {
|
|||||||
return ret;
|
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) {
|
function AppBase(props) {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const httpHeader = useSelector((state) => state.common.httpHeader);
|
const httpHeader = useSelector((state) => state.common.httpHeader);
|
||||||
@@ -317,6 +437,53 @@ function AppBase(props) {
|
|||||||
// const termsFlag = useSelector((state) => state.common.termsFlag);
|
// const termsFlag = useSelector((state) => state.common.termsFlag);
|
||||||
const termsData = useSelector((state) => state.home.termsData);
|
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(() => {
|
useEffect(() => {
|
||||||
// Chromium68 호환성을 위해 Optional Chaining 제거
|
// Chromium68 호환성을 위해 Optional Chaining 제거
|
||||||
if (termsData && termsData.data && termsData.data.terms) {
|
if (termsData && termsData.data && termsData.data.terms) {
|
||||||
|
|||||||
@@ -616,3 +616,6 @@ export const LOG_MESSAGE_ID = {
|
|||||||
CHECKOUT_CLICK: 'AL_CHECKOUT_CLICK',
|
CHECKOUT_CLICK: 'AL_CHECKOUT_CLICK',
|
||||||
BUY_NOW: 'AL_BUY_NOW',
|
BUY_NOW: 'AL_BUY_NOW',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Spotlight 포커스 디버깅 플래그 (true 시 포커스 이동 로그 활성화)
|
||||||
|
export const FOCUS_DEBUG = true;
|
||||||
|
|||||||
@@ -1749,8 +1749,10 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
} // ✅ INITIAL, VOICE_RESULT & SEARCH_RESULT 모드에서 TInputSimple 내부 포커스 활성화
|
} // ✅ INITIAL, VOICE_RESULT & SEARCH_RESULT 모드에서 TInputSimple 내부 포커스 활성화
|
||||||
onKeyDown={handleKeydown}
|
onKeyDown={handleKeydown}
|
||||||
spotlightId={SPOTLIGHT_IDS.SEARCH_INPUT_BOX}
|
spotlightId={SPOTLIGHT_IDS.SEARCH_INPUT_BOX}
|
||||||
// ✅ [251026] ADD: Spotlight 포커스 네비게이션 - data-spotlight-down 속성 사용
|
// 🎯 [포커스 중첩 해결] SearchResultsContainer로 포커스 전달
|
||||||
data-spotlight-down={currentMode === SEARCH_PANEL_MODES.VOICE_RESULT ? 'howAboutThese-seeMore' : undefined}
|
// SearchResultsContainer가 Spotlight 컨테이너이므로, 포커스가 들어오면
|
||||||
|
// enterTo: 'last-focused' 설정에 의해 자동으로 HowAboutThese.small의 SEE MORE로 이동
|
||||||
|
data-spotlight-down={currentMode === SEARCH_PANEL_MODES.VOICE_RESULT ? 'search-results-container' : undefined}
|
||||||
// 🎯 HowAboutThese 포커스 관리 - 포커스가 검색 입력 영역으로 올 때 감지
|
// 🎯 HowAboutThese 포커스 관리 - 포커스가 검색 입력 영역으로 올 때 감지
|
||||||
onSpotlightUp={handleSearchInputFocus}
|
onSpotlightUp={handleSearchInputFocus}
|
||||||
onSpotlightLeft={handleSearchInputFocus}
|
onSpotlightLeft={handleSearchInputFocus}
|
||||||
@@ -1768,8 +1770,8 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId }) {
|
|||||||
// onFocus={onFocusMic}
|
// onFocus={onFocusMic}
|
||||||
onKeyDown={handleMicKeyDown}
|
onKeyDown={handleMicKeyDown}
|
||||||
spotlightId={SPOTLIGHT_IDS.MICROPHONE_BUTTON}
|
spotlightId={SPOTLIGHT_IDS.MICROPHONE_BUTTON}
|
||||||
// ✅ [251026] ADD: Spotlight 포커스 네비게이션 - data-spotlight-down 속성 사용
|
// 🎯 [포커스 중첩 해결] SearchResultsContainer로 포커스 전달
|
||||||
data-spotlight-down={currentMode === SEARCH_PANEL_MODES.VOICE_RESULT ? 'howAboutThese-seeMore' : undefined}
|
data-spotlight-down={currentMode === SEARCH_PANEL_MODES.VOICE_RESULT ? 'search-results-container' : undefined}
|
||||||
// 🎯 HowAboutThese 포커스 관리 - 포커스가 마이크 버튼으로 올 때 감지
|
// 🎯 HowAboutThese 포커스 관리 - 포커스가 마이크 버튼으로 올 때 감지
|
||||||
onSpotlightUp={handleSearchInputFocus}
|
onSpotlightUp={handleSearchInputFocus}
|
||||||
onSpotlightLeft={handleSearchInputFocus}
|
onSpotlightLeft={handleSearchInputFocus}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import Spotlight from '@enact/spotlight';
|
import Spotlight from '@enact/spotlight';
|
||||||
|
import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator';
|
||||||
import Spottable from '@enact/spotlight/Spottable';
|
import Spottable from '@enact/spotlight/Spottable';
|
||||||
|
|
||||||
import downBtnImg from '../../../assets/images/btn/search_btn_down_arrow.png';
|
import downBtnImg from '../../../assets/images/btn/search_btn_down_arrow.png';
|
||||||
@@ -215,6 +216,13 @@ const SearchResultsNew = ({
|
|||||||
// Spottable 컴포넌트 캐싱하여 메모리 누수 방지
|
// Spottable 컴포넌트 캐싱하여 메모리 누수 방지
|
||||||
const SpottableDiv = useMemo(() => Spottable('div'), []);
|
const SpottableDiv = useMemo(() => Spottable('div'), []);
|
||||||
|
|
||||||
|
// 🎯 [포커스 중첩 해결] SearchResultsNew를 Spotlight 컨테이너로 감싸기
|
||||||
|
// 이렇게 하면 외부에서 들어온 포커스를 자식 요소(HowAboutThese.small)로 자동 전달
|
||||||
|
const SearchResultsContainer = useMemo(
|
||||||
|
() => SpotlightContainerDecorator({ enterTo: 'last-focused' }, 'div'),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
// ❌ [251026] DEPRECATED: 탭 키다운 핸들러 - Spotlight 속성으로 대체됨
|
// ❌ [251026] DEPRECATED: 탭 키다운 핸들러 - Spotlight 속성으로 대체됨
|
||||||
// Spotlight의 data-spotlight-up 속성으로 처리하도록 변경
|
// Spotlight의 data-spotlight-up 속성으로 처리하도록 변경
|
||||||
// 기존 코드 보존 (향후 필요시 참고용)
|
// 기존 코드 보존 (향후 필요시 참고용)
|
||||||
@@ -353,7 +361,11 @@ const SearchResultsNew = ({
|
|||||||
// 이제는 SearchPanel에서 통합 포커스 로직으로 관리됨
|
// 이제는 SearchPanel에서 통합 포커스 로직으로 관리됨
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={css.searchBox}>
|
<SearchResultsContainer
|
||||||
|
className={css.searchBox}
|
||||||
|
spotlightId="search-results-container"
|
||||||
|
data-wheel-point="true"
|
||||||
|
>
|
||||||
{/* HowAboutThese Small 버전 - relativeQueries가 존재할 때만 표시 */}
|
{/* HowAboutThese Small 버전 - relativeQueries가 존재할 때만 표시 */}
|
||||||
{relativeQueries && relativeQueries.length > 0 && (
|
{relativeQueries && relativeQueries.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
@@ -445,7 +457,7 @@ const SearchResultsNew = ({
|
|||||||
<CustomImage className={css.btnImg} src={upBtnImg} alt="Up arrow" />
|
<CustomImage className={css.btnImg} src={upBtnImg} alt="Up arrow" />
|
||||||
</SpottableDiv>
|
</SpottableDiv>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</SearchResultsContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user