240 lines
8.4 KiB
JavaScript
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;
|
|
}; |