Files
shoptime/com.twin.app.shoptime/src/utils/spotlight-utils.js
2025-06-19 08:59:10 +09:00

240 lines
8.4 KiB
JavaScript

// spotlight-utils.js
import {getTargetByContainer} from '@enact/spotlight/src/target';
import {getTargetBySelector} from '@enact/spotlight/src/target';
import {getContainerConfig} from '@enact/spotlight/src/container';
import {isContainer, getContainerId} from '@enact/spotlight/src/container';
import {getContainersForNode} from '@enact/spotlight/src/container';
import {isNavigable} from '@enact/spotlight/src/container';
import {setLastContainer} from '@enact/spotlight/src/container';
import Spotlight from '@enact/spotlight';
// lodash 없이 last 함수 직접 구현
const last = (array) => {
return array && array.length > 0 ? array[array.length - 1] : undefined;
};
// focusElement 함수는 spotlight 내부 함수이므로, 직접 구현하거나 spotlight의 focus를 사용
const focusElement = (target, containerIds) => {
console.log("focusElement called with:", target, containerIds);
if (target && typeof target.focus === 'function') {
try {
target.focus();
return true;
} catch (e) {
console.error("Focus failed:", e);
return false;
}
}
return false;
};
/**
* Predicts which element would be focused by Spotlight.focus() without actually
* changing the focus.
*
* @param {String|Node} [elem] The spotlight ID or selector for either a spottable
* component or a spotlight container, or spottable node. If not supplied, the default
* container's target will be used.
* @returns {Node|null} The DOM element that would be focused, or null if no
* navigable target is found.
*/
export const getPredictedFocus = (elem) => {
let target = elem;
if (!elem) {
target = getTargetByContainer();
} else if (typeof elem === 'string') {
if (getContainerConfig(elem)) {
// String is a container ID
target = getTargetByContainer(elem);
} else if (/^[\w\d-]+$/.test(elem)) {
// Support component IDs consisting of alphanumeric, dash, or underscore
target = getTargetBySelector(`[data-spotlight-id=${elem}]`);
} else {
// Treat as a CSS selector
target = getTargetBySelector(elem);
}
} else if (isContainer(elem)) {
// elem is a container element
target = getTargetByContainer(getContainerId(elem));
}
// Check navigability without attempting to focus
const nextContainerIds = getContainersForNode(target);
const nextContainerId = last(nextContainerIds);
if (isNavigable(target, nextContainerId, true)) {
return target;
}
return null;
};
// 메인 focus 함수
export const focus = (elem) => {
console.log("focus test", elem);
var target = elem;
var wasContainerId = false;
if (!elem) {
// elem이 없으면 기본 컨테이너의 타겟을 가져옴
target = getTargetByContainer();
} else if (typeof elem === 'string') {
if (getContainerConfig(elem)) {
// 문자열이 컨테이너 ID인 경우
target = getTargetByContainer(elem);
wasContainerId = true;
} else if (/^[\w\d-]+$/.test(elem)) {
// 알파벳, 숫자, 대시, 언더스코어로 구성된 컴포넌트 ID 지원
target = getTargetBySelector("[data-spotlight-id=".concat(elem, "]"));
} else {
// CSS 셀렉터로 처리
target = getTargetBySelector(elem);
}
} else if (isContainer(elem)) {
// elem이 컨테이너 요소인 경우
target = getTargetByContainer(getContainerId(elem));
}
// 타겟 노드의 컨테이너들을 가져옴
var nextContainerIds = getContainersForNode(target);
var nextContainerId = last(nextContainerIds); // 마지막 컨테이너 ID
if (isNavigable(target, nextContainerId, true)) {
// 네비게이션 가능한 경우 포커스 설정
var focused = focusElement(target, nextContainerIds);
if (!focused && wasContainerId) {
// 포커스 실패했지만 컨테이너 ID였던 경우 마지막 컨테이너로 설정
setLastContainer(elem);
}
return focused;
} else if (wasContainerId) {
// 제공된 컨테이너 내에서 spottable 타겟을 찾지 못한 경우
// 내용이 변경될 때 자체적으로 포커스할 수 있도록 활성 컨테이너로 설정
setLastContainer(elem);
}
return false;
};
// spotlight-utils.js에 추가할 함수
/**
* spotlightId로 직접 포커스를 설정하는 함수
*
* @param {String} spotlightId - data-spotlight-id 속성값
* @param {Boolean} force - 강제 포커스 여부 (기본값: false)
* @returns {Boolean} 포커스 성공 여부
*/
export const focusById = (spotlightId, force = false) => {
// spotlightId 유효성 검사
if (!spotlightId || typeof spotlightId !== 'string') {
console.error('[focusById] spotlightId는 반드시 문자열이어야 합니다.');
return false;
}
try {
// data-spotlight-id 속성을 가진 요소 직접 검색
const targetElement = document.querySelector(`[data-spotlight-id="${spotlightId}"]`);
if (!targetElement) {
console.warn(`[focusById] spotlightId "${spotlightId}"를 가진 요소를 찾을 수 없습니다.`);
return false;
}
// 요소가 현재 보이고 활성화되어 있는지 확인
if (!isElementVisible(targetElement)) {
console.warn(`[focusById] 요소 "${spotlightId}"가 보이지 않거나 비활성화되어 있습니다.`);
if (!force) return false;
}
// Spotlight의 isSpottable로 포커스 가능 여부 확인
if (typeof Spotlight !== 'undefined' && Spotlight.isSpottable) {
if (!Spotlight.isSpottable(targetElement) && !force) {
console.warn(`[focusById] 요소 "${spotlightId}"가 현재 spottable하지 않습니다.`);
return false;
}
}
// 직접 DOM 포커스 시도
if (force) {
// 강제 모드: DOM focus() 직접 호출
console.log(`[focusById] 강제 포커스 모드: "${spotlightId}"`);
targetElement.focus();
return true;
} else {
// 일반 모드: Spotlight 시스템 사용
console.log(`[focusById] Spotlight 포커스: "${spotlightId}"`);
// Spotlight.focus() 사용 (선택자 형태로 전달)
const focusResult = focus(`[data-spotlight-id="${spotlightId}"]`);
if (!focusResult) {
// Spotlight 포커스 실패 시 직접 포커스 시도
console.log(`[focusById] Spotlight 포커스 실패, 직접 포커스 시도: "${spotlightId}"`);
targetElement.focus();
return document.activeElement === targetElement;
}
return focusResult;
}
} catch (error) {
console.error(`[focusById] 포커스 설정 중 오류 발생: "${spotlightId}"`, error);
return false;
}
};
/**
* 요소가 보이고 포커스 가능한 상태인지 확인하는 헬퍼 함수
*
* @param {Element} element - 확인할 DOM 요소
* @returns {Boolean} 요소의 가시성 및 활성화 상태
*/
const isElementVisible = (element) => {
if (!element) return false;
// 요소가 DOM에 연결되어 있는지 확인
if (!element.isConnected) return false;
// disabled 속성 확인
if (element.disabled) return false;
// display: none 또는 visibility: hidden 확인
const style = window.getComputedStyle(element);
if (style.display === 'none' || style.visibility === 'hidden') return false;
// opacity가 0인지 확인
if (parseFloat(style.opacity) === 0) return false;
// 요소의 크기가 0인지 확인
const rect = element.getBoundingClientRect();
if (rect.width === 0 && rect.height === 0) return false;
return true;
};
/**
* 현재 포커스된 요소의 spotlightId를 반환하는 헬퍼 함수
*
* @returns {String|null} 현재 포커스된 요소의 spotlightId 또는 null
*/
export const getCurrentSpotlightId = () => {
const current = document.activeElement;
if (current && current.hasAttribute('data-spotlight-id')) {
return current.getAttribute('data-spotlight-id');
}
return null;
};
/**
* 특정 spotlightId를 가진 요소가 현재 포커스되어 있는지 확인하는 함수
*
* @param {String} spotlightId - 확인할 spotlightId
* @returns {Boolean} 해당 요소가 현재 포커스되어 있는지 여부
*/
export const isCurrentlyFocused = (spotlightId) => {
return getCurrentSpotlightId() === spotlightId;
};