이 글을 쓰게 된 이유는
정보가 없어서이다.
내가 이 라이브러리를 이해하기 위해서 공식문서도 보고 했지만,
구체적인 설명은 별로 없었고, 실제 라이브러리 코드를 뜯어보거나
import해서 디버깅을 해봤을 때 정리했던 내용들 위주로 적었다.
enact란 lg에서 만든 라이브러리로, webOS 환경인 TV나 기타 기기에서 사용하기 위해 만들어진 라이브러리이다.
그중에서 spotlight라는 라이브러리에 대해 알아보겠다.
!https://img.shields.io/npm/v/@enact/spotlight.svg?style=flat-square
5-방향 탐색 및 초점 제어를 위한 확장 가능한 라이브러리입니다.
키보드 또는 텔레비전 리모트 컨트롤을 사용하여 애플리케이션을 탐색할 수 있도록 하는 확장 가능한 유틸리티입니다. UP, DOWN, LEFT, RIGHT, RETURN 키로부터의 입력을 처리하며, Spotlight는 마우스가 있는 컴퓨터와 비슷한 탐색 경험을 제공합니다.
각각 메서드들 위주로 정리하였다.
SpotlightContainerDecorator
포커스를 관리하는 컴포넌트입니다. 해당 컴포넌트로 감싸진 자식 컴포넌트들은 포커스 가능한 요소로 처리됩니다. 또한, 포커스 이동과 관련된 다양한 설정 및 옵션을 제공합니다.
const SCD = SpotlightContainerDecorator(
{
enterTo: "last-focused",
},
"header"
);
위의 코드에서 enterTo
와 header
는 SCD (SpotlightContainerDecorator) 컴포넌트의 속성을 설정하는 부분입니다.
enterTo
: 이 속성은 사용자가 입력 키를 눌렀을 때 포커스가 이동하는 방식을 지정합니다. "last-focused"
로 설정되어 있으므로, 사용자가 키보드의 입력 키를 누르면 포커스는 마지막으로 초점이 맞춰진 요소로 이동합니다. 다른 옵션으로는 "first"
(첫 번째 요소로 이동) 또는 특정 요소의 ID를 지정할 수도 있습니다.header
: 이 속성은 SCD 컴포넌트의 헤더를 설정합니다. 헤더는 컨테이너의 상단에 표시되는 컴포넌트이며, 일반적으로 컨테이너의 제목 또는 설명을 나타내는 데 사용됩니다. "header"
라는 문자열이 지정되어 있으므로, 컨테이너의 헤더에는 "header"라는 텍스트 또는 해당 문자열에 대한 컴포넌트가 사용될 것입니다.<SCD
spotlightId={"LeftMenuContainer"}
className={classnames("LeftMenuContainer gnb", expand && "expand")}
spotlightRestrict="self-only"
>
각 속성의 의미는 다음과 같다
spotlightId
컴포넌트에 Spotlight 포커스를 적용하기 위한 식별자를 지정합니다. Spotlight는 사용자 인터페이스에서 특정 요소에 초점을 맞추고 상호작용을 용이하게 하는 데 사용되는 기술입니다. spotlightId
를 통해 해당 컴포넌트를 선택적으로 포커스할 수 있습니다.
spotlightRestrict
Spotlight 포커스의 제한 범위를 지정합니다. "self-only" 값은 컴포넌트 자체에 대해서만 포커스를 제한함을 의미합니다. 이를 통해 해당 컴포넌트의 하위 요소들이 포커스를 받지 않도록 할 수 있습니다.
이러한 속성들은 컴포넌트의 동작과 외관을 조정하고 사용자 인터페이스를 관리하는 데 사용됩니다.
SpotlightRootDecorator
최상위 컴포넌트에서 포커스를 처리하기 위해 사용됩니다. 일반적으로 앱의 최상위 레벨 컴포넌트에 적용되며, 앱 전체의 포커스 이동을 관리합니다. SpotlightRootDecorator는 여러 개의 SpotlightContainerDecorator를 포함할 수 있습니다.
Spottable
일반 컴포넌트를 포커스 가능한 요소로 만들어주는 Higher-Order-Component(HOC)입니다. Spottable은 기본적인 포커스 기능을 제공하며, 사용자의 입력에 따라 포커스를 이동시키고 이벤트를 처리할 수 있도록 합니다.
이러한 Spotlight 관련 개념과 컴포넌트들을 활용하여 Enact 애플리케이션에서 포커스 관리 및 사용자 입력 처리를 구현할 수 있습니다. Spotlight은 Enact에서 TV 기반 애플리케이션을 개발할 때 편리하게 사용되는 기능입니다.
ex)
const SpottableEleButton = Spottable("button");
<SpottableEleButton
spotlightId={"LeftMenuContainer_modeChange"}
type="button"
onSpotlightRight={(e) => {//오른쪽 이벤트 다룸
}}
onFocus={(e) => {//포커스 받는 상황에서 쓰는 메소드
}}
onBlur={(e) => {// 포커스를 벗어나면 받는 메소드
}}
onKeyUp={(e) => {// 키보드를 눌렀을 때, 다룸
}}
onSpotlightDown={(e) => {// 키보드 다운 이벤트를 다룸
}}
>
spotlightId
위와 같이 focus id 설정이 가능하게 해준다.
onSpotlightRight
4방향에 대해서도 입력을 받을 수 있다.
키보드 오른쪽을 주었을 때, 해당 메서드가 작동한다.
left, down, up도 존재한다.
pointer.js 파일은 Spotlight 라이브러리의 일부로서, 포인터 이벤트를 처리하는데 사용됩니다. 이 파일은 다음과 같은 주요 기능을 제공합니다:
setPointerMode(pointerMode): 현재 포인터 모드를 설정하는 함수입니다. 이 함수는 true 또는 false 값을 인자로 받아, 포인터 모드를 활성화하거나 비활성화합니다.
getPointerMode(): 현재 포인터 모드를 반환하는 함수입니다. 이 함수는 포인터 모드가 활성화되어 있으면 true를, 그렇지 않으면 false를 반환합니다.
hasPointerMoved(x, y): 현재 포인터 위치가 제공된 좌표와 일치하는지 확인하는 함수입니다. 이 함수는 포인터의 x, y 좌표를 인자로 받아, 포인터가 이동했는지 여부를 반환합니다.
updatePointerPosition(x, y): 캐시된 포인터 위치를 업데이트하는 함수입니다. 이 함수는 포인터의 x, y 좌표를 인자로 받아, 포인터 위치를 업데이트하고 포인터 모드를 활성화합니다.
getLastPointerPosition(): 마지막으로 알려진 포인터 위치를 반환하는 함수입니다. 이 함수는 포인터의 x, y 좌표를 객체 형태로 반환합니다.
notifyPointerMove(current, target, x, y): 포인터 위치 변경을 Spotlight에 알리는 함수입니다. 이 함수는 현재 포커스된 요소, 포인터 아래의 요소, 그리고 포인터의 x, y 좌표를 인자로 받아, 포인터 위치 변경이 포커스 변경을 초래하는지 여부를 반환합니다.
notifyKeyDown(keyCode, callback): 키 이벤트를 포인터 모듈에 알리는 함수입니다. 이 함수는 키 코드와 콜백 함수를 인자로 받아, 포인터를 숨기거나 보여주는 키 이벤트에 따라 적절한 동작을 수행합니다.
이 파일은 포인터 이벤트를 처리하고, 포인터 위치를 추적하며, 포인터 모드를 관리하는 등의 역할을 합니다. 이를 통해 사용자가 마우스나 터치스크린 등의 포인터 입력 장치를 사용하여 인터페이스를 탐색할 수 있게 돕습니다.
focus: function (elem, containerOption = {}) {
// 제공된 요소를 대상 요소로 초기화합니다.
let target = elem;
let wasContainerId = false;
let currentContainerNode = null;
// 요소가 제공되지 않은 경우, 컨테이너를 통해 대상을 가져옵니다.
if (!elem) {
target = getTargetByContainer();
}
// 제공된 요소가 문자열인 경우, 이는 컨테이너 ID 또는 CSS 선택자일 수 있습니다.
else if (typeof elem === 'string') {
// 문자열이 컨테이너 ID인 경우, 해당 컨테이너 내의 대상을 가져옵니다.
if (getContainerConfig(elem)) {
target = getTargetByContainer(elem, containerOption.enterTo);
wasContainerId = true;
currentContainerNode = getContainerNode(elem);
}
// 문자열이 알파벳, 숫자, 대시, 밑줄로 구성된 컴포넌트 ID인 경우, 해당 선택자를 통해 대상을 가져옵니다.
else if (/^[\w\d-]+$/.test(elem)) {
target = getTargetBySelector(`[data-spotlight-id=${elem}]`);
}
// 그 외의 경우, 문자열을 CSS 선택자로 간주하고 해당 선택자를 통해 대상을 가져옵니다.
else {
target = getTargetBySelector(elem);
}
}
// 제공된 요소가 컨테이너인 경우, 해당 컨테이너 내의 대상을 가져옵니다.
else if (isContainer(elem)) {
target = getTargetByContainer(getContainerId(elem), containerOption.enterTo);
currentContainerNode = elem;
}
// 대상 요소의 컨테이너 ID를 가져옵니다.
const nextContainerIds = getContainersForNode(target);
const nextContainerId = last(nextContainerIds);
// 대상 요소가 탐색 가능한 경우, 해당 요소에 포커스를 맞춥니다.
if (isNavigable(target, nextContainerId, true)) {
const focused = focusElement(target, nextContainerIds);
// 포커스가 맞춰지지 않은 경우, 마지막 컨테이너를 설정합니다.
if (!focused && wasContainerId) {
setLastContainer(elem);
}
return focused;
}
// 컨테이너 ID가 제공되었으나 탐색 가능한 대상을 찾지 못한 경우, 마지막 컨테이너를 설정합니다.
else if (wasContainerId) {
setLastContainer(elem);
}
// 외부 컨테이너로 이동하는 옵션이 설정되어 있고, 현재 컨테이너 노드가 있는 경우, 외부 컨테이너로 이동합니다.
if (containerOption.toOuterContainer && currentContainerNode) {
const outerContainer = getContainersForNode(currentContainerNode.parentElement).pop();
if (outerContainer) {
return this.focus(outerContainer, containerOption);
}
}
// 포커스를 맞추지 못한 경우, false를 반환합니다.
return false;
}
/**
* 지정된 방향으로 다음 탐색 가능한 컨트롤로 포커스를 이동합니다. 선택적으로, 시작점으로 사용할 요소의 선택자를 제공할 수 있습니다.
*
* @param {String} direction 이동할 방향, 'left', 'right', 'up', 'down' 중 하나입니다.
* @param {String|undefined} selector 제공된 경우, 이동할 시작 요소입니다. 제공되지 않은 경우, 현재 포커스된 항목이 사용됩니다.
* @returns {Boolean} 포커스가 성공적으로 이동하면 `true`, 그렇지 않으면 `false`를 반환합니다.
* @public
*/
move: function (direction, selector) {
// 방향을 소문자로 변환합니다.
direction = direction.toLowerCase();
// 방향이 'up', 'down', 'left', 'right' 중 하나가 아닌 경우, false를 반환합니다.
if (direction !== 'up' && direction !== 'down' && direction !== 'left' && direction !== 'right') {
return false;
}
// 시작 요소를 선택자를 통해 파싱하거나, 현재 포커스된 요소를 사용합니다.
const elem = selector ? parseSelector(selector)[0] : getCurrent();
// 시작 요소가 없는 경우, false를 반환합니다.
if (!elem) {
return false;
}
// 시작 요소의 컨테이너 ID를 가져옵니다.
const containerIds = getContainersForNode(elem);
// 컨테이너 ID가 없는 경우, false를 반환합니다.
if (!containerIds.length) {
return false;
}
// 다음 요소로 포커스를 이동합니다.
return spotNext(direction, elem, containerIds);
},
아래에는 메소드의 의미만 적어서 정리하였다.
initialize(containerDefaults: object): void
containerDefaults
: 기본 컨테이너 설정을 나타내는 객체를 전달해야 합니다.terminate(): void
set(containerIdOrConfig: string | object, config?: object): void
containerIdOrConfig
: 컨테이너 ID 또는 설정 객체를 전달합니다.config
: 컨테이너의 설정 객체를 전달합니다. 선택적 매개변수입니다.add(containerIdOrConfig: string | object, config?: object): string
containerIdOrConfig
: 컨테이너 ID 또는 설정 객체를 전달합니다.config
: 컨테이너의 설정 객체를 전달합니다. 선택적 매개변수입니다.remove(containerId: string): boolean
containerId
: 제거할 컨테이너의 ID를 전달합니다.true
를 반환합니다.disableSelector(containerId: string): boolean
containerId
: 선택기를 비활성화할 컨테이너의 ID를 전달합니다.true
를 반환합니다.enableSelector(containerId: string): boolean
containerId
: 선택기를 활성화할 컨테이너의 ID를 전달합니다.true
를 반환합니다.pause(): void
resume(): void
focus(elem?: string | React.ReactNode, containerOption?: object): boolean
elem
: 포커스를 설정할 엘리먼트를 전달합니다. 선택적 매개변수입니다.containerOption
: 컨테이너 설정 객체를 전달합니다. 선택적 매개변수입니다.elem
은 해당 컨테이너의 마지막으로 포커스된 엘리먼트로 설정됩니다. 성공적으로 포커스를 설정하면 true
를 반환합니다.move(direction: string, selector: string | void): boolean
direction
: 포커스를 이동할 방향을 전달합니다.selector
: 선택적으로 시작점으로 사용할 엘리먼트 선택자를 전달합니다.true
를 반환합니다.setDefaultContainer(containerId?: string): void
containerId
: 기본 컨테이너의 ID를 전달합니다. 선택적 매개변수입니다.getActiveContainer(): string
setActiveContainer(containerId?: string): void
containerId
: 활성화할 컨테이너의 ID를 전달합니다. 선택적 매개변수입니다. 현재 컨테이너가 'self-only'로 제한되어 있고 containerId
가 현재 컨테이너에 포함되어 있지 않으면 활성 컨테이너가 업데이트되지 않습니다.getPointerMode(): boolean
true
를 반환합니다.setPointerMode(pointerMode: boolean): void
pointerMode
: 포인터 모드를 설정하려면 true
를, 해제하려면 false
를 전달합니다.isMuted(elem: object): boolean
elem
: 음소거 모드를 확인할 엘리먼트 객체를 전달합니다.isPaused(): boolean
isPaused
메서드는 현재 Spotlight가 일시 중지되어 있는지 확인하는 메서드입니다.true
를 반환하고, 그렇지 않은 경우 false
를 반환합니다.pause()
메서드를 호출하여 일시 중지 상태로 만들 수 있으며, resume()
메서드를 호출하여 다시 시작할 수 있습니다.isSpottable(elem: object): boolean
isSpottable
메서드는 주어진 엘리먼트가 포커스 가능한지 확인하는 메서드입니다.elem
: 포커스 가능 여부를 확인하고자 하는 엘리먼트 객체를 전달합니다.true
를 반환하고, 그렇지 않은 경우 false
를 반환합니다.getCurrent(): React.ReactNode
getCurrent
메서드는 현재 포커스가 있는 컨트롤을 반환하는 메서드입니다.React.ReactNode
형태로 반환됩니다.null
을 반환합니다.getSpottableDescendants(containerId: string): React.ReactNode[]
getSpottableDescendants
메서드는 지정된 컨테이너에 속한 포커스 가능한 자식 요소들을 리스트로 반환하는 메서드입니다.containerId
: 포커스 가능한 자식 요소들을 확인하고자 하는 컨테이너의 ID를 전달합니다.React.ReactNode[]
형태로 반환됩니다.import Spotlight from '@enact/Spotlight';
// Spotlight가 일시 중지되어 있는지 확인
const isPaused = Spotlight.isPaused();
// elem 엘리먼트가 포커스 가능한지 확인
const isElemSpottable = Spotlight.isSpottable(elem);
// 현재 포커스가 있는 컨트롤을 가져옴
const currentControl = Spotlight.getCurrent();
// 'containerId' 컨테이너 내의 포커스 가능한 자식 요소들의 리스트를 가져옴
const spottableDescendants = Spotlight.getSpottableDescendants('containerId');
실제 적용해서 구현되는 화면이 궁금하네요…! 고생하셨습니다