// 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; };