import { FocusDetails, getCurrentFocusKey, init, SpatialNavigation, } from "@noriginmedia/norigin-spatial-navigation"; import { RefObject, useEffect } from "react"; init({ shouldFocusDOMNode: false, throttle: 200, }); let addFocusable = SpatialNavigation.addFocusable.bind(SpatialNavigation); let removeFocusable = SpatialNavigation.removeFocusable.bind(SpatialNavigation); let setFocus = SpatialNavigation.setFocus.bind(SpatialNavigation); type SaveFocusType = "session" | "local"; type HistorySourceType = "settings" | 'details' | 'launch' | 'game-list'; const historySourceMap = new Map(); export function SaveSource (id: HistorySourceType, url?: string) { const finalUrl = url ?? location.hash.replace("#", ''); if (finalUrl) { historySourceMap.set(id, finalUrl); } } export function HasSource (id: HistorySourceType) { return historySourceMap.has(id); } export function PopSource (id: HistorySourceType) { if (!historySourceMap.has(id)) { return undefined; } const source = historySourceMap.get(id); historySourceMap.delete(id); return source; } export function GetFocusedElement (focusKey: string) { return (SpatialNavigation as any).focusableComponents[focusKey]?.node as HTMLElement; } export function GetFocusedTree (leaf: string): string[] { const tree: string[] = []; let component = (SpatialNavigation as any).focusableComponents[leaf]; while (component) { tree.push(component.focusKey); if (component.parentFocusKey && !tree.includes(component.parentFocusKey)) { component = (SpatialNavigation as any).focusableComponents[component.parentFocusKey]; } else { break; } } return tree; } export function dispatchFocusedEvent (event: Event, override?: Element | Window) { const focusedElement = GetFocusedElement(getCurrentFocusKey()); const finalTarget = override ?? focusedElement ?? window; return finalTarget.dispatchEvent(event); } export interface FocusEventMap { 'focuschanged': Event; } export function useFocusEventListener (eventName: K, handler: (event: FocusEventMap[K]) => void, element?: RefObject): void { useEffect(() => { const finalElement = element ? element.current : window; finalElement?.addEventListener(eventName, handler); return () => finalElement?.removeEventListener(eventName, handler); }, [eventName, handler, element?.current]); } SpatialNavigation.setFocus = (newFocusKey, focusDetails) => { setFocus(newFocusKey, focusDetails); dispatchFocusedEvent(new CustomEvent('focuschanged', { bubbles: true, detail: focusDetails })); }; SpatialNavigation.addFocusable = (toAdd) => { addFocusable(toAdd); const component: { lastFocusedChildKey?: string; preferredChildFocusKey?: string; node: HTMLElement; focusKey: string; } = (SpatialNavigation as any).focusableComponents[toAdd.focusKey]; if (component.node?.hasAttribute("save-child-focus")) { const storageKey = `${component.focusKey}-last-child-focus`; const saveChildFocus = component.node.getAttribute( "save-child-focus", ) as SaveFocusType; if (saveChildFocus === "session" && sessionStorage.getItem(storageKey)) { SpatialNavigation.saveLastFocusedChildKey( component as any, sessionStorage.getItem(storageKey)!, ); } else if (saveChildFocus === "local" && localStorage.getItem(storageKey)) { SpatialNavigation.saveLastFocusedChildKey( component as any, localStorage.getItem(storageKey)!, ); } } }; // Override remove callback to insert custom functionality like saving to storage SpatialNavigation.removeFocusable = ({ focusKey }) => { const component: { lastFocusedChildKey?: string; node: HTMLElement; focusKey: string; } = (SpatialNavigation as any).focusableComponents[focusKey]; if (component) { if (component.node?.hasAttribute("save-child-focus")) { const saveChildFocus = component.node.getAttribute( "save-child-focus", ) as SaveFocusType; const storageKey = `${component.focusKey}-last-child-focus`; if (saveChildFocus === "session") { if (component.lastFocusedChildKey) { sessionStorage.setItem(storageKey, component.lastFocusedChildKey); } else { //sessionStorage.removeItem(storageKey); } } else if (saveChildFocus === "local") { if (component.lastFocusedChildKey) { localStorage.setItem(storageKey, component.lastFocusedChildKey); } else { //localStorage.removeItem(storageKey); } } } removeFocusable(component); } }; SpatialNavigation.saveLastFocusedChildKey = (component, focusKey) => { component.lastFocusedChildKey = focusKey; };