UNPKG

preact-spatial-navigation

Version:

A powerful Preact library for TV-style spatial navigation with LRUD algorithm, virtualized lists/grids, and smart TV support

1 lines 208 kB
{"version":3,"file":"index.mjs","sources":["../lib/utils/chrome38Compatibility.ts","../node_modules/preact/jsx-runtime/dist/jsxRuntime.module.js","../lib/context/ParentIdContext.tsx","../lib/spatial-navigation/helpers/isError.ts","../lib/spatial-navigation/SpatialNavigator.ts","../lib/context/SpatialNavigatorContext.tsx","../lib/hooks/useCreateSpatialNavigator.ts","../lib/utils/remoteControl.ts","../lib/context/DeviceTypeContext.tsx","../lib/hooks/useRemoteControl.ts","../lib/context/LockSpatialNavigationContext.tsx","../lib/context/IsRootActiveContext.tsx","../lib/components/SpatialNavigationRoot.tsx","../node_modules/preact/compat/dist/compat.module.js","../lib/context/DefaultFocusContext.tsx","../lib/context/ParentScrollContext.tsx","../lib/hooks/useUniqueId.ts","../lib/components/SpatialNavigationNode.tsx","../lib/components/SpatialNavigationView.tsx","../lib/hooks/useSpatialNavigatorFocusableAccessibilityProps.ts","../lib/components/SpatialNavigationFocusableView.tsx","../lib/components/SpatialNavigationScrollView.tsx","../lib/components/virtualizedList/helpers/updateVirtualNodeRegistration.ts","../lib/components/virtualizedList/hooks/useCachedValues.ts","../lib/components/virtualizedGrid/helpers/convertToGrid.ts","../lib/components/virtualizedList/helpers/getRange.ts","../lib/components/virtualizedList/helpers/getSizeInPxFromOneItemToAnother.ts","../lib/components/virtualizedList/helpers/computeTranslation.ts","../lib/components/virtualizedList/helpers/getLastItemIndex.ts","../lib/components/virtualizedList/helpers/createScrollOffsetArray.ts","../lib/components/virtualizedList/helpers/getNumberOfItemsVisibleOnScreen.ts","../lib/components/virtualizedList/helpers/getAdditionalNumberOfItemsRendered.ts","../lib/components/virtualizedList/VirtualizedList.tsx","../lib/components/virtualizedList/VirtualizedListWithSize.tsx","../lib/components/virtualizedList/SpatialNavigationVirtualizedListWithVirtualNodes.tsx","../lib/components/virtualizedList/SpatialNavigationVirtualizedListWithScroll.tsx","../lib/components/virtualizedList/SpatialNavigationVirtualizedList.tsx","../lib/components/virtualizedGrid/SpatialNavigationVirtualizedGrid.tsx","../lib/hooks/useFocusable.ts","../lib/utils/eventBus.ts","../lib/utils/helpers.ts","../lib/index.ts"],"sourcesContent":["/**\n * Chrome 38 Compatibility Utilities\n * Provides polyfills and workarounds for Chrome 38 specific issues\n */\n\n// Polyfill for Object.assign (not available in Chrome 38)\nif (!Object.assign) {\n Object.assign = function(target: any, ...sources: any[]) {\n if (target == null) {\n throw new TypeError('Cannot convert undefined or null to object');\n }\n \n const to = Object(target);\n \n for (let index = 0; index < sources.length; index++) {\n const nextSource = sources[index];\n \n if (nextSource != null) {\n for (const nextKey in nextSource) {\n if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {\n to[nextKey] = nextSource[nextKey];\n }\n }\n }\n }\n return to;\n };\n}\n\n// Polyfill for Array.from (not available in Chrome 38)\nif (!Array.from) {\n Array.from = function(arrayLike: any, mapFn?: any, thisArg?: any) {\n const C = this;\n const items = Object(arrayLike);\n \n if (arrayLike == null) {\n throw new TypeError('Array.from requires an array-like object - not null or undefined');\n }\n \n const mapFunction = mapFn ? mapFn : undefined;\n const T = thisArg;\n \n const len = parseInt(items.length) || 0;\n const A = typeof C === 'function' ? Object(new C(len)) : new Array(len);\n \n let k = 0;\n let kValue;\n \n while (k < len) {\n kValue = items[k];\n if (mapFunction) {\n A[k] = typeof T === 'undefined' ? mapFunction(kValue, k) : mapFunction.call(T, kValue, k);\n } else {\n A[k] = kValue;\n }\n k += 1;\n }\n A.length = len;\n return A;\n };\n}\n\n// Polyfill for Array.find (not available in Chrome 38)\nif (!Array.prototype.find) {\n Array.prototype.find = function(predicate: any, _thisArg?: any) {\n if (this == null) {\n throw new TypeError('Array.prototype.find called on null or undefined');\n }\n if (typeof predicate !== 'function') {\n throw new TypeError('predicate must be a function');\n }\n const list = Object(this);\n const length = parseInt(list.length) || 0;\n const context = arguments[1];\n let value;\n \n for (let i = 0; i < length; i++) {\n value = list[i];\n if (predicate.call(context, value, i, list)) {\n return value;\n }\n }\n return undefined;\n };\n}\n\n// Polyfill for Array.includes (not available in Chrome 38)\nif (!Array.prototype.includes) {\n Array.prototype.includes = function(searchElement: any, fromIndex?: number) {\n if (this == null) {\n throw new TypeError('Array.prototype.includes called on null or undefined');\n }\n const O = Object(this);\n const len = parseInt(O.length) || 0;\n if (len === 0) {\n return false;\n }\n const n = parseInt(String(fromIndex)) || 0;\n let k = n >= 0 ? n : Math.max(len + n, 0);\n \n function sameValueZero(x: any, y: any) {\n return x === y || (typeof x === 'number' && typeof y === 'number' && isNaN(x) && isNaN(y));\n }\n \n for (; k < len; k++) {\n if (sameValueZero(O[k], searchElement)) {\n return true;\n }\n }\n return false;\n };\n}\n\n// Polyfill for String.includes (not available in Chrome 38)\nif (!String.prototype.includes) {\n String.prototype.includes = function(search: string, start?: number) {\n const startIndex = typeof start === 'number' ? start : 0;\n if (startIndex + search.length > this.length) {\n return false;\n } else {\n return this.indexOf(search, startIndex) !== -1;\n }\n };\n}\n\n// Polyfill for String.startsWith (not available in Chrome 38)\nif (!String.prototype.startsWith) {\n String.prototype.startsWith = function(searchString: string, position?: number) {\n position = position || 0;\n return this.substr(position, searchString.length) === searchString;\n };\n}\n\n// Polyfill for String.endsWith (not available in Chrome 38)\nif (!String.prototype.endsWith) {\n String.prototype.endsWith = function(searchString: string, length?: number) {\n if (length === undefined || length > this.length) {\n length = this.length;\n }\n return this.substring(length - searchString.length, length) === searchString;\n };\n}\n\n// Polyfill for Number.isNaN (not available in Chrome 38)\nif (!Number.isNaN) {\n Number.isNaN = function(value: any) {\n return typeof value === 'number' && isNaN(value);\n };\n}\n\n// Polyfill for Number.isFinite (not available in Chrome 38)\nif (!Number.isFinite) {\n Number.isFinite = function(value: any) {\n return typeof value === 'number' && isFinite(value);\n };\n}\n\n// Polyfill for Math.trunc (not available in Chrome 38)\nif (!Math.trunc) {\n Math.trunc = function(x: number) {\n return x < 0 ? Math.ceil(x) : Math.floor(x);\n };\n}\n\n// Polyfill for Math.sign (not available in Chrome 38)\nif (!Math.sign) {\n Math.sign = function(x: number) {\n x = +x; // convert to a number\n if (x === 0 || isNaN(x)) {\n return x;\n }\n return x > 0 ? 1 : -1;\n };\n}\n\n// Console logging helper for Chrome 38 debugging (disabled for production)\nexport const chrome38Log = (_message: string, _data?: any) => {\n // No-op in production\n};\n\n// Feature detection for Chrome 38\nexport const isChrome38 = () => {\n if (typeof navigator === 'undefined') return false;\n const userAgent = navigator.userAgent;\n return userAgent.includes('Chrome/38') || userAgent.includes('Chrome/37') || userAgent.includes('Chrome/36');\n};\n\n// CSS property compatibility helper\nexport const getCompatibleCSSProperty = (property: string, value: string) => {\n const chrome38Prefixes: { [key: string]: string } = {\n 'display': '-webkit-box',\n 'flexDirection': 'WebkitBoxOrient',\n 'alignItems': 'WebkitBoxAlign',\n 'justifyContent': 'WebkitBoxPack',\n 'transition': 'WebkitTransition',\n 'transform': 'WebkitTransform',\n 'backfaceVisibility': 'WebkitBackfaceVisibility',\n 'overflowScrolling': 'WebkitOverflowScrolling',\n };\n \n const result: { [key: string]: string } = {};\n \n // Add the standard property\n result[property] = value;\n \n // Add Chrome 38 prefix if available\n if (chrome38Prefixes[property]) {\n result[chrome38Prefixes[property]] = value;\n }\n \n return result;\n};\n\n// Scroll behavior compatibility\nexport const getCompatibleScrollBehavior = () => {\n if (isChrome38()) {\n return {\n WebkitOverflowScrolling: 'touch',\n overflowScrolling: 'touch',\n };\n }\n return {};\n};\n","import{options as r,Fragment as e}from\"preact\";export{Fragment}from\"preact\";var t=/[\"&<]/;function n(r){if(0===r.length||!1===t.test(r))return r;for(var e=0,n=0,o=\"\",f=\"\";n<r.length;n++){switch(r.charCodeAt(n)){case 34:f=\"&quot;\";break;case 38:f=\"&amp;\";break;case 60:f=\"&lt;\";break;default:continue}n!==e&&(o+=r.slice(e,n)),o+=f,e=n+1}return n!==e&&(o+=r.slice(e,n)),o}var o=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i,f=0,i=Array.isArray;function u(e,t,n,o,i,u){t||(t={});var a,c,p=t;if(\"ref\"in p)for(c in p={},t)\"ref\"==c?a=t[c]:p[c]=t[c];var l={type:e,props:p,key:n,ref:a,__k:null,__:null,__b:0,__e:null,__c:null,constructor:void 0,__v:--f,__i:-1,__u:0,__source:i,__self:u};if(\"function\"==typeof e&&(a=e.defaultProps))for(c in a)void 0===p[c]&&(p[c]=a[c]);return r.vnode&&r.vnode(l),l}function a(r){var t=u(e,{tpl:r,exprs:[].slice.call(arguments,1)});return t.key=t.__v,t}var c={},p=/[A-Z]/g;function l(e,t){if(r.attr){var f=r.attr(e,t);if(\"string\"==typeof f)return f}if(t=function(r){return null!==r&&\"object\"==typeof r&&\"function\"==typeof r.valueOf?r.valueOf():r}(t),\"ref\"===e||\"key\"===e)return\"\";if(\"style\"===e&&\"object\"==typeof t){var i=\"\";for(var u in t){var a=t[u];if(null!=a&&\"\"!==a){var l=\"-\"==u[0]?u:c[u]||(c[u]=u.replace(p,\"-$&\").toLowerCase()),s=\";\";\"number\"!=typeof a||l.startsWith(\"--\")||o.test(l)||(s=\"px;\"),i=i+l+\":\"+a+s}}return e+'=\"'+n(i)+'\"'}return null==t||!1===t||\"function\"==typeof t||\"object\"==typeof t?\"\":!0===t?e:e+'=\"'+n(\"\"+t)+'\"'}function s(r){if(null==r||\"boolean\"==typeof r||\"function\"==typeof r)return null;if(\"object\"==typeof r){if(void 0===r.constructor)return r;if(i(r)){for(var e=0;e<r.length;e++)r[e]=s(r[e]);return r}}return n(\"\"+r)}export{u as jsx,l as jsxAttr,u as jsxDEV,s as jsxEscape,a as jsxTemplate,u as jsxs};\n//# sourceMappingURL=jsxRuntime.module.js.map\n","import { createContext } from 'preact';\nimport { useContext } from 'preact/hooks';\n\nexport const ParentIdContext = createContext<string>('root');\n\nexport const useParentId = () => {\n const parentId = useContext(ParentIdContext);\n return parentId;\n};\n\n","export const isError = (error: unknown): error is Error => {\n return error instanceof Error;\n};\n\n","import type { Direction } from '@bam.tech/lrud';\nimport { Lrud } from '@bam.tech/lrud';\nimport { isError } from './helpers/isError';\n\nexport type OnDirectionHandledWithoutMovement = (direction: Direction) => void;\ntype OnDirectionHandledWithoutMovementRef = { current: OnDirectionHandledWithoutMovement };\n\ntype SpatialNavigatorParams = {\n onDirectionHandledWithoutMovementRef: OnDirectionHandledWithoutMovementRef;\n};\n\nexport default class SpatialNavigator {\n private lrud: Lrud;\n private onDirectionHandledWithoutMovementRef: OnDirectionHandledWithoutMovementRef;\n\n constructor({\n onDirectionHandledWithoutMovementRef = { current: () => undefined },\n }: SpatialNavigatorParams) {\n this.lrud = new Lrud();\n this.onDirectionHandledWithoutMovementRef = onDirectionHandledWithoutMovementRef;\n }\n\n private registerMap: { [key: string]: Array<Parameters<Lrud['registerNode']>> } = {};\n\n public registerNode(...params: Parameters<Lrud['registerNode']>) {\n try {\n const parent = params[1] && params[1].parent;\n const id = params[0];\n\n // If no parent is given, we are talking about a root node. We want to register it.\n // If a parent is given, we need the node to exist. Otherwise, we'll pass and queue the node for later registration.\n if (parent === undefined || this.lrud.getNode(parent)) {\n this.lrud.registerNode(...params);\n\n // After we successfully register a node, we need to check whether it needs to grab the focus or not.\n this.handleQueuedFocus();\n\n // OK, we successfully registered an element.\n // Now, we check if some other elements were depending on us to be registered.\n // ...and we do it recursively.\n const potentialNodesToRegister = this.registerMap[id];\n if (!potentialNodesToRegister || potentialNodesToRegister.length === 0) return;\n\n potentialNodesToRegister.forEach((node) => {\n this.registerNode(...node);\n });\n delete this.registerMap[id];\n } else {\n // If the parent is not registered yet, we queue the node for later registration.\n if (!this.registerMap[parent]) {\n this.registerMap[parent] = [];\n }\n this.registerMap[parent].push(params);\n }\n } catch (e) {\n console.error(e);\n }\n }\n\n public unregisterNode(...params: Parameters<Lrud['unregisterNode']>) {\n this.lrud.unregisterNode(...params);\n }\n\n public async handleKeyDown(direction: Direction | null) {\n if (!direction) {\n return;\n }\n if (!this.hasRootNode) {\n console.warn('❌ SpatialNavigator: No root node');\n return;\n }\n if (!this.lrud.getRootNode()) {\n console.warn('❌ SpatialNavigator: LRUD root node not found');\n return;\n }\n\n // Handle Enter/Select separately\n if (direction === 'enter') {\n const currentNode = this.lrud.getCurrentFocusNode();\n if (currentNode && currentNode.onSelect) {\n currentNode.onSelect(currentNode);\n }\n return;\n }\n\n // Handle long enter\n if (direction === 'long_enter') {\n const currentNode = this.lrud.getCurrentFocusNode();\n if (currentNode && currentNode.onLongSelect) {\n currentNode.onLongSelect(currentNode);\n }\n return;\n }\n\n // Handle directional navigation\n if (direction) {\n const nodeBeforeMovement = this.lrud.getCurrentFocusNode();\n \n this.lrud.handleKeyEvent({ direction }, { forceFocus: true });\n const nodeAfterMovement = this.lrud.getCurrentFocusNode();\n\n if (nodeBeforeMovement === nodeAfterMovement) {\n this.onDirectionHandledWithoutMovementRef.current(direction);\n }\n }\n }\n\n public hasOneNodeFocused() {\n return this.lrud.getCurrentFocusNode() !== undefined;\n }\n\n /**\n * Sometimes we need to focus an element, but it is not registered yet.\n * That's where we put this waiting element.\n */\n private focusQueue: string | null = null;\n\n /**\n * In the case of virtualized lists, we have some race condition issues when trying\n * to imperatively assign focus.\n * Indeed, we need the list to scroll to the element and then focus it. But the element\n * needs to exist to be focused, so we need first to scroll then wait for the element to render\n * then focus it.\n */\n private virtualNodeFocusQueue: string | null = null;\n\n /**\n * To handle the default focus, we want to queue the element to be focused.\n * We queue it because it might not be registered yet when it asks for focus.\n *\n * We queue it only if there is no currently focused element already (or currently queued),\n * because multiple elements might try to take the focus (DefaultFocus is a context, so all its children\n * will try to grab it). We only want the first of these element to grab it.\n */\n public handleOrQueueDefaultFocus = (id: string) => {\n if (this.getCurrentFocusNode()) return;\n if (this.focusQueue) return;\n if (this.lrud.getNode(id)) {\n this.lrud.assignFocus(id);\n return;\n }\n this.focusQueue = id;\n };\n\n /**\n * Sometimes we want to queue focus an element, even if one is already focused.\n * That happens with an imperative focus for example. I can force a focus to an element,\n * even though another one is already focused.\n *\n * Still, I want to queue it, because the element might not be registered yet (example: in the case of virtualized lists)\n */\n public grabFocusDeferred = (id: string) => {\n try {\n if (this.lrud.getNode(id)) {\n this.lrud.assignFocus(id);\n return;\n }\n } catch (error) {\n // If the element exists but is not focusable, it is very likely that it will\n // have a focusable child soon. This is the case for imperative focus on virtualized lists.\n if (isError(error) && error.message === 'trying to assign focus to a non focusable node') {\n this.virtualNodeFocusQueue = id;\n }\n }\n };\n\n /**\n * This will focus the currently queued element if it exists.\n * Otherwise, it will do nothing.\n *\n * This function will eventually be called with the proper element\n * when the element is finally registered.\n */\n private handleQueuedFocus = () => {\n // Handle focus queue\n if (this.focusQueue && this.lrud.getNode(this.focusQueue)) {\n try {\n this.lrud.assignFocus(this.focusQueue);\n this.focusQueue = null;\n } catch (e) {\n // pass\n }\n }\n\n // Handle virtual nodes (for virtualized lists) focus queue\n if (this.virtualNodeFocusQueue) {\n const virtualNode = this.lrud.getNode(this.virtualNodeFocusQueue);\n if (virtualNode && virtualNode.children && virtualNode.children.length !== 0) {\n try {\n this.lrud.assignFocus(this.virtualNodeFocusQueue);\n this.virtualNodeFocusQueue = null;\n } catch (e) {\n // pass\n }\n }\n }\n };\n\n public grabFocus = (id: string) => {\n return this.lrud.assignFocus(id);\n };\n\n public getCurrentFocusNode = () => {\n return this.lrud.currentFocusNode;\n };\n\n private get hasRootNode(): boolean {\n try {\n this.lrud.getRootNode();\n return true;\n } catch (e) {\n console.warn('[Preact Spatial Navigation] No registered node on this page.');\n return false;\n }\n }\n}\n\n","import SpatialNavigator from '../spatial-navigation/SpatialNavigator';\nimport { createContext } from 'preact';\nimport { useContext } from 'preact/hooks';\n\nexport const SpatialNavigatorContext = createContext<SpatialNavigator | null>(null);\n\nexport const useSpatialNavigator = () => {\n const spatialNavigator = useContext(SpatialNavigatorContext);\n if (!spatialNavigator)\n throw new Error(\n 'No registered spatial navigator on this page. Use the <SpatialNavigationRoot /> component.',\n );\n return spatialNavigator;\n};\n\n// Alias for backward compatibility\nexport { useSpatialNavigator as useSpatialNavigatorContext };\n\n","import { useMemo } from 'preact/hooks';\nimport SpatialNavigator, { type OnDirectionHandledWithoutMovement } from '../spatial-navigation/SpatialNavigator';\n\ntype UseCreateSpatialNavigatorParams = {\n onDirectionHandledWithoutMovementRef: { current: OnDirectionHandledWithoutMovement };\n};\n\nexport const useCreateSpatialNavigator = ({\n onDirectionHandledWithoutMovementRef,\n}: UseCreateSpatialNavigatorParams): SpatialNavigator => {\n const spatialNavigator = useMemo(\n () =>\n new SpatialNavigator({\n onDirectionHandledWithoutMovementRef,\n }),\n // We only want to create the navigator once\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [],\n );\n\n return spatialNavigator;\n};\n\n","import type { Direction } from '@bam.tech/lrud';\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- can't know for sure what the subscriber will be...\ntype SubscriberType = any;\n\nexport interface RemoteControlConfiguration {\n remoteControlSubscriber: (lrudCallback: (direction: Direction | null) => void) => SubscriberType;\n remoteControlUnsubscriber: (subscriber: SubscriberType) => void;\n}\n\nexport let remoteControlSubscriber:\n | RemoteControlConfiguration['remoteControlSubscriber']\n | undefined = undefined;\nexport let remoteControlUnsubscriber:\n | RemoteControlConfiguration['remoteControlUnsubscriber']\n | undefined = undefined;\n\nexport const configureRemoteControl = (options: RemoteControlConfiguration) => {\n remoteControlSubscriber = options.remoteControlSubscriber;\n remoteControlUnsubscriber = options.remoteControlUnsubscriber;\n};\n\n/**\n * Common TV remote key codes\n * These can be used when implementing your own remote control subscriber\n */\nexport const TV_REMOTE_KEYS = {\n // Samsung Tizen\n SAMSUNG_UP: 38,\n SAMSUNG_DOWN: 40,\n SAMSUNG_LEFT: 37,\n SAMSUNG_RIGHT: 39,\n SAMSUNG_ENTER: 13,\n SAMSUNG_BACK: 10009,\n \n // LG webOS\n LG_UP: 38,\n LG_DOWN: 40,\n LG_LEFT: 37,\n LG_RIGHT: 39,\n LG_ENTER: 13,\n LG_BACK: 461,\n \n // Generic\n MEDIA_PLAY: 415,\n MEDIA_PAUSE: 19,\n MEDIA_STOP: 413,\n MEDIA_REWIND: 412,\n MEDIA_FAST_FORWARD: 417,\n};\n\n/**\n * Helper function to create a basic keyboard-based remote control subscriber\n * This is a convenience function for web development and testing\n * \n * @example\n * ```ts\n * import { configureRemoteControl, createKeyboardRemoteControl } from 'preact-spatial-navigation';\n * \n * const { subscriber, unsubscriber } = createKeyboardRemoteControl();\n * configureRemoteControl({\n * remoteControlSubscriber: subscriber,\n * remoteControlUnsubscriber: unsubscriber,\n * });\n * ```\n */\nexport const createKeyboardRemoteControl = () => {\n const subscriber = (callback: (direction: Direction | null) => void) => {\n const handleKeyDown = (event: KeyboardEvent) => {\n switch (event.key) {\n case 'ArrowUp':\n event.preventDefault();\n callback('up');\n break;\n case 'ArrowDown':\n event.preventDefault();\n callback('down');\n break;\n case 'ArrowLeft':\n event.preventDefault();\n callback('left');\n break;\n case 'ArrowRight':\n event.preventDefault();\n callback('right');\n break;\n case 'Enter':\n event.preventDefault();\n callback('enter');\n break;\n default:\n break;\n }\n };\n\n if (typeof window !== 'undefined') {\n window.addEventListener('keydown', handleKeyDown);\n }\n\n return handleKeyDown;\n };\n\n const unsubscriber = (listener: (event: KeyboardEvent) => void) => {\n if (typeof window !== 'undefined') {\n window.removeEventListener('keydown', listener);\n }\n };\n\n return { subscriber, unsubscriber };\n};\n\n/**\n * Helper function to create an LG webOS TV remote control subscriber\n * This handles LG-specific key codes and the webOS platform\n * \n * @example\n * ```ts\n * import { configureRemoteControl, createLGRemoteControl } from 'preact-spatial-navigation';\n * \n * const { subscriber, unsubscriber } = createLGRemoteControl();\n * configureRemoteControl({\n * remoteControlSubscriber: subscriber,\n * remoteControlUnsubscriber: unsubscriber,\n * });\n * ```\n */\nexport const createLGRemoteControl = () => {\n const subscriber = (callback: (direction: Direction | null) => void) => {\n const handleKeyDown = (event: KeyboardEvent) => {\n event.preventDefault();\n \n const keyCode = event.keyCode;\n \n switch (keyCode) {\n case TV_REMOTE_KEYS.LG_UP:\n case 38: // Arrow Up\n callback('up');\n break;\n case TV_REMOTE_KEYS.LG_DOWN:\n case 40: // Arrow Down\n callback('down');\n break;\n case TV_REMOTE_KEYS.LG_LEFT:\n case 37: // Arrow Left\n callback('left');\n break;\n case TV_REMOTE_KEYS.LG_RIGHT:\n case 39: // Arrow Right\n callback('right');\n break;\n case TV_REMOTE_KEYS.LG_ENTER:\n case 13: // Enter\n callback('enter');\n break;\n case TV_REMOTE_KEYS.LG_BACK:\n case 461: // LG Back button\n // You can emit a custom event or handle back navigation\n if (typeof window !== 'undefined') {\n // CustomEvent fallback for older browsers (e.g., Chrome 38)\n let event: Event;\n try {\n event = new CustomEvent('lg-back-pressed');\n } catch {\n // Legacy constructor for older engines\n event = document.createEvent('CustomEvent');\n // initCustomEvent exists on legacy engines\n (event as any).initCustomEvent('lg-back-pressed', false, false, undefined);\n }\n window.dispatchEvent(event);\n }\n break;\n default:\n break;\n }\n };\n\n if (typeof window !== 'undefined') {\n document.addEventListener('keydown', handleKeyDown);\n }\n\n return handleKeyDown;\n };\n\n const unsubscriber = (listener: (event: KeyboardEvent) => void) => {\n if (typeof window !== 'undefined') {\n document.removeEventListener('keydown', listener);\n }\n };\n\n return { subscriber, unsubscriber };\n};\n","import { createContext } from 'preact';\nimport { useContext, useState, useEffect, useRef } from 'preact/hooks';\nimport type { JSX, Ref } from 'preact';\n\n/**\n * Device types supported by the library\n * remoteKeys - using remote control with arrow keys\n * remotePointer - using remote control with pointer (e.g., Magic Remote)\n */\nexport type DeviceType = 'remoteKeys' | 'remotePointer' | 'tv' | 'desktop' | 'mobile' | 'tablet';\n\n/**\n * Device type context value\n */\nexport interface DeviceTypeContextValue {\n deviceType: DeviceType;\n deviceTypeRef: Ref<DeviceType>;\n setDeviceType: (deviceType: DeviceType) => void;\n setScrollingIntervalId: (id: NodeJS.Timeout | null) => void;\n isTv: boolean;\n isDesktop: boolean;\n isMobile: boolean;\n isTablet: boolean;\n}\n\n/**\n * Props for SpatialNavigationDeviceTypeProvider\n */\nexport interface SpatialNavigationDeviceTypeProviderProps {\n /** Device type - can be manually specified or auto-detected */\n deviceType?: DeviceType;\n /** Children elements */\n children: JSX.Element | JSX.Element[];\n}\n\nconst DeviceTypeContext = createContext<DeviceTypeContextValue | null>(null);\n\n/**\n * Detect device type based on user agent and screen size\n */\nfunction detectDeviceType(): DeviceType {\n if (typeof window === 'undefined') return 'desktop';\n\n const userAgent = navigator.userAgent.toLowerCase();\n const width = window.innerWidth;\n\n // Check for TV devices\n if (\n userAgent.indexOf('tv') !== -1 ||\n userAgent.indexOf('smarttv') !== -1 ||\n userAgent.indexOf('googletv') !== -1 ||\n userAgent.indexOf('appletv') !== -1 ||\n userAgent.indexOf('hbbtv') !== -1 ||\n userAgent.indexOf('pov_tv') !== -1 ||\n userAgent.indexOf('netcast') !== -1 ||\n userAgent.indexOf('nettv') !== -1\n ) {\n return 'tv';\n }\n\n // Check for mobile devices\n if (\n /android|webos|iphone|ipod|blackberry|iemobile|opera mini/i.test(userAgent) &&\n width < 768\n ) {\n return 'mobile';\n }\n\n // Check for tablets\n if (\n (/ipad|android/i.test(userAgent) && width >= 768) ||\n (width >= 768 && width < 1024)\n ) {\n return 'tablet';\n }\n\n // Default to desktop\n return 'desktop';\n}\n\n/**\n * SpatialNavigationDeviceTypeProvider - Provides device type context\n * Detects or accepts device type and provides it to child components\n */\nexport function SpatialNavigationDeviceTypeProvider({\n deviceType: providedDeviceType,\n children,\n}: SpatialNavigationDeviceTypeProviderProps) {\n const [deviceType, setDeviceType] = useState<DeviceType>(\n providedDeviceType || detectDeviceType()\n );\n const deviceTypeRef = useRef<DeviceType>(deviceType);\n const [_scrollingIntervalId, setScrollingIntervalId] = useState<NodeJS.Timeout | null>(null);\n\n // Update ref when state changes\n useEffect(() => {\n deviceTypeRef.current = deviceType;\n }, [deviceType]);\n\n // Re-detect on window resize (for responsive behavior)\n useEffect(() => {\n if (providedDeviceType) {\n setDeviceType(providedDeviceType);\n return;\n }\n\n const handleResize = () => {\n setDeviceType(detectDeviceType());\n };\n\n window.addEventListener('resize', handleResize);\n return () => window.removeEventListener('resize', handleResize);\n }, [providedDeviceType]);\n\n const contextValue: DeviceTypeContextValue = {\n deviceType,\n deviceTypeRef,\n setDeviceType,\n setScrollingIntervalId,\n isTv: deviceType === 'tv',\n isDesktop: deviceType === 'desktop',\n isMobile: deviceType === 'mobile',\n isTablet: deviceType === 'tablet',\n };\n\n return (\n <DeviceTypeContext.Provider value={contextValue}>\n {children}\n </DeviceTypeContext.Provider>\n );\n}\n\n/**\n * Hook to access device type context\n * \n * @returns Device type context value\n * \n * @example\n * ```tsx\n * function MyComponent() {\n * const { isTv, deviceType } = useDeviceType();\n * \n * return (\n * <div>\n * {isTv ? 'TV Interface' : 'Standard Interface'}\n * </div>\n * );\n * }\n * ```\n */\nexport function useDeviceType(): DeviceTypeContextValue {\n const context = useContext(DeviceTypeContext);\n\n if (!context) {\n // Return default values if not within provider\n const defaultDeviceType = detectDeviceType();\n const deviceTypeRef = useRef<DeviceType>(defaultDeviceType);\n return {\n deviceType: defaultDeviceType,\n deviceTypeRef,\n setDeviceType: () => {\n console.warn('setDeviceType called outside of SpatialNavigationDeviceTypeProvider');\n },\n setScrollingIntervalId: () => {\n console.warn('setScrollingIntervalId called outside of SpatialNavigationDeviceTypeProvider');\n },\n isTv: defaultDeviceType === 'tv',\n isDesktop: defaultDeviceType === 'desktop',\n isMobile: defaultDeviceType === 'mobile',\n isTablet: defaultDeviceType === 'tablet',\n };\n }\n\n return context;\n}\n\n","import SpatialNavigator from '../spatial-navigation/SpatialNavigator';\nimport { useEffect } from 'preact/hooks';\nimport { remoteControlSubscriber, remoteControlUnsubscriber } from '../utils/remoteControl';\nimport { useDeviceType } from '../context/DeviceTypeContext';\n\nexport const useRemoteControl = ({\n spatialNavigator,\n isActive,\n}: {\n spatialNavigator: SpatialNavigator;\n isActive: boolean;\n}) => {\n const { setDeviceType } = useDeviceType();\n \n useEffect(() => {\n if (!remoteControlSubscriber) {\n console.warn(\n '[Preact Spatial Navigation] You probably forgot to configure the remote control. Please call the configuration function.',\n );\n\n return;\n }\n\n if (!isActive) {\n return () => undefined;\n }\n\n const listener = remoteControlSubscriber((direction) => {\n setDeviceType('remoteKeys');\n spatialNavigator.handleKeyDown(direction);\n });\n \n return () => {\n if (!remoteControlUnsubscriber) {\n console.warn(\n '[Preact Spatial Navigation] You did not provide a remote control unsubscriber. Are you sure you called configuration correctly?',\n );\n\n return;\n }\n remoteControlUnsubscriber(listener);\n };\n }, [spatialNavigator, isActive, setDeviceType]);\n};\n\n","import { createContext } from 'preact';\nimport { useContext, useMemo, useReducer } from 'preact/hooks';\n\n/**\n * We store the number of times that we have been asked to lock the navigator\n * to avoid any race conditions\n *\n * It's more reliable than a simple boolean\n */\nconst lockReducer = (state: number, action: 'lock' | 'unlock'): number => {\n switch (action) {\n case 'lock':\n return state + 1;\n case 'unlock':\n return state - 1;\n default:\n return state;\n }\n};\n\nexport const useIsLocked = () => {\n const [lockAmount, dispatch] = useReducer(lockReducer, 0);\n\n const lockActions = useMemo(\n () => ({\n lock: () => dispatch('lock'),\n unlock: () => dispatch('unlock'),\n }),\n [dispatch],\n );\n\n return {\n isLocked: lockAmount !== 0,\n lockActions,\n };\n};\n\nexport const LockSpatialNavigationContext = createContext<{\n lock: () => void;\n unlock: () => void;\n}>({\n lock: () => undefined,\n unlock: () => undefined,\n});\n\nexport const useLockSpatialNavigation = () => {\n const { lock, unlock } = useContext(LockSpatialNavigationContext);\n return { lock, unlock };\n};\n\n","import { createContext } from 'preact';\nimport { useContext } from 'preact/hooks';\n\nexport const IsRootActiveContext = createContext<boolean>(true);\n\nexport const useIsRootActive = () => {\n const isRootActive = useContext(IsRootActiveContext);\n return isRootActive;\n};\n\n","import type { ComponentChildren } from 'preact';\nimport { useEffect, useRef } from 'preact/hooks';\nimport { ParentIdContext } from '../context/ParentIdContext';\nimport { SpatialNavigatorContext } from '../context/SpatialNavigatorContext';\nimport { useCreateSpatialNavigator } from '../hooks/useCreateSpatialNavigator';\nimport { useRemoteControl } from '../hooks/useRemoteControl';\nimport type { OnDirectionHandledWithoutMovement } from '../spatial-navigation/SpatialNavigator';\nimport { LockSpatialNavigationContext, useIsLocked } from '../context/LockSpatialNavigationContext';\nimport { IsRootActiveContext } from '../context/IsRootActiveContext';\n\nconst ROOT_ID = 'root';\n\nexport type SpatialNavigationRootProps = {\n /**\n * Determines if the spatial navigation is active.\n * If false, the spatial navigation will be locked, and no nodes can be focused.\n * This is useful to handle a multi page app: you can disable the non-focused pages' spatial navigation roots.\n *\n * Note: this is a little redundant with the lock system, but it's useful to have a way to disable the spatial navigation from above AND from below.\n */\n isActive?: boolean;\n /**\n * Called when you're reaching a border of the navigator.\n * A use case for this would be the implementation of a side menu\n * that's shared between pages. You can have a separate navigator\n * for your side menu, which would be common across pages, and you'd\n * make this menu active when you reach the left side of your page navigator.\n */\n onDirectionHandledWithoutMovement?: OnDirectionHandledWithoutMovement;\n children: ComponentChildren;\n};\n\nexport const SpatialNavigationRoot = ({\n isActive = true,\n onDirectionHandledWithoutMovement = () => undefined,\n children,\n}: SpatialNavigationRootProps) => {\n // We can't follow the react philosophy here: we can't recreate a navigator if this function changes\n // so we'll have to store its ref and update the ref if there is a new value to this function\n const onDirectionHandledWithoutMovementRef = useRef<OnDirectionHandledWithoutMovement>(\n () => undefined,\n );\n // Update the ref at every render\n onDirectionHandledWithoutMovementRef.current = onDirectionHandledWithoutMovement;\n\n const spatialNavigator = useCreateSpatialNavigator({\n onDirectionHandledWithoutMovementRef,\n });\n\n const { isLocked, lockActions } = useIsLocked();\n\n const isRootActive = isActive && !isLocked;\n useRemoteControl({ spatialNavigator, isActive: isRootActive });\n\n useEffect(() => {\n spatialNavigator.registerNode(ROOT_ID, { orientation: 'vertical' });\n return () => spatialNavigator.unregisterNode(ROOT_ID);\n }, [spatialNavigator]);\n\n return (\n <SpatialNavigatorContext.Provider value={spatialNavigator}>\n <LockSpatialNavigationContext.Provider value={lockActions}>\n <IsRootActiveContext.Provider value={isRootActive}>\n <ParentIdContext.Provider value={ROOT_ID}>{children}</ParentIdContext.Provider>\n </IsRootActiveContext.Provider>\n </LockSpatialNavigationContext.Provider>\n </SpatialNavigatorContext.Provider>\n );\n};\n\n// Backward compatibility: export as SpatialNavigationProvider\nexport { SpatialNavigationRoot as SpatialNavigationProvider };\n\n// Export hook for accessing context\nexport { useSpatialNavigatorContext } from '../context/SpatialNavigatorContext';\n","import{Component as n,createElement as t,options as e,toChildArray as r,Fragment as u,render as o,hydrate as i,createContext as l,createRef as c,cloneElement as f}from\"preact\";export{Component,Fragment,createContext,createElement,createRef}from\"preact\";import{useState as a,useLayoutEffect as s,useEffect as h,useCallback as v,useContext as d,useDebugValue as m,useId as p,useImperativeHandle as y,useMemo as _,useReducer as b,useRef as S}from\"preact/hooks\";export*from\"preact/hooks\";function g(n,t){for(var e in t)n[e]=t[e];return n}function E(n,t){for(var e in n)if(\"__source\"!==e&&!(e in t))return!0;for(var r in t)if(\"__source\"!==r&&n[r]!==t[r])return!0;return!1}function C(n,t){var e=t(),r=a({t:{__:e,u:t}}),u=r[0].t,o=r[1];return s(function(){u.__=e,u.u=t,x(u)&&o({t:u})},[n,e,t]),h(function(){return x(u)&&o({t:u}),n(function(){x(u)&&o({t:u})})},[n]),e}function x(n){var t,e,r=n.u,u=n.__;try{var o=r();return!((t=u)===(e=o)&&(0!==t||1/t==1/e)||t!=t&&e!=e)}catch(n){return!0}}function R(n){n()}function w(n){return n}function k(){return[!1,R]}var I=s;function N(n,t){this.props=n,this.context=t}function M(n,e){function r(n){var t=this.props.ref,r=t==n.ref;return!r&&t&&(t.call?t(null):t.current=null),e?!e(this.props,n)||!r:E(this.props,n)}function u(e){return this.shouldComponentUpdate=r,t(n,e)}return u.displayName=\"Memo(\"+(n.displayName||n.name)+\")\",u.prototype.isReactComponent=!0,u.__f=!0,u.type=n,u}(N.prototype=new n).isPureReactComponent=!0,N.prototype.shouldComponentUpdate=function(n,t){return E(this.props,n)||E(this.state,t)};var T=e.__b;e.__b=function(n){n.type&&n.type.__f&&n.ref&&(n.props.ref=n.ref,n.ref=null),T&&T(n)};var A=\"undefined\"!=typeof Symbol&&Symbol.for&&Symbol.for(\"react.forward_ref\")||3911;function D(n){function t(t){var e=g({},t);return delete e.ref,n(e,t.ref||null)}return t.$$typeof=A,t.render=n,t.prototype.isReactComponent=t.__f=!0,t.displayName=\"ForwardRef(\"+(n.displayName||n.name)+\")\",t}var L=function(n,t){return null==n?null:r(r(n).map(t))},O={map:L,forEach:L,count:function(n){return n?r(n).length:0},only:function(n){var t=r(n);if(1!==t.length)throw\"Children.only\";return t[0]},toArray:r},F=e.__e;e.__e=function(n,t,e,r){if(n.then)for(var u,o=t;o=o.__;)if((u=o.__c)&&u.__c)return null==t.__e&&(t.__e=e.__e,t.__k=e.__k),u.__c(n,t);F(n,t,e,r)};var U=e.unmount;function V(n,t,e){return n&&(n.__c&&n.__c.__H&&(n.__c.__H.__.forEach(function(n){\"function\"==typeof n.__c&&n.__c()}),n.__c.__H=null),null!=(n=g({},n)).__c&&(n.__c.__P===e&&(n.__c.__P=t),n.__c.__e=!0,n.__c=null),n.__k=n.__k&&n.__k.map(function(n){return V(n,t,e)})),n}function W(n,t,e){return n&&e&&(n.__v=null,n.__k=n.__k&&n.__k.map(function(n){return W(n,t,e)}),n.__c&&n.__c.__P===t&&(n.__e&&e.appendChild(n.__e),n.__c.__e=!0,n.__c.__P=e)),n}function P(){this.__u=0,this.o=null,this.__b=null}function j(n){var t=n.__.__c;return t&&t.__a&&t.__a(n)}function z(n){var e,r,u;function o(o){if(e||(e=n()).then(function(n){r=n.default||n},function(n){u=n}),u)throw u;if(!r)throw e;return t(r,o)}return o.displayName=\"Lazy\",o.__f=!0,o}function B(){this.i=null,this.l=null}e.unmount=function(n){var t=n.__c;t&&t.__R&&t.__R(),t&&32&n.__u&&(n.type=null),U&&U(n)},(P.prototype=new n).__c=function(n,t){var e=t.__c,r=this;null==r.o&&(r.o=[]),r.o.push(e);var u=j(r.__v),o=!1,i=function(){o||(o=!0,e.__R=null,u?u(l):l())};e.__R=i;var l=function(){if(!--r.__u){if(r.state.__a){var n=r.state.__a;r.__v.__k[0]=W(n,n.__c.__P,n.__c.__O)}var t;for(r.setState({__a:r.__b=null});t=r.o.pop();)t.forceUpdate()}};r.__u++||32&t.__u||r.setState({__a:r.__b=r.__v.__k[0]}),n.then(i,i)},P.prototype.componentWillUnmount=function(){this.o=[]},P.prototype.render=function(n,e){if(this.__b){if(this.__v.__k){var r=document.createElement(\"div\"),o=this.__v.__k[0].__c;this.__v.__k[0]=V(this.__b,r,o.__O=o.__P)}this.__b=null}var i=e.__a&&t(u,null,n.fallback);return i&&(i.__u&=-33),[t(u,null,e.__a?null:n.children),i]};var H=function(n,t,e){if(++e[1]===e[0]&&n.l.delete(t),n.props.revealOrder&&(\"t\"!==n.props.revealOrder[0]||!n.l.size))for(e=n.i;e;){for(;e.length>3;)e.pop()();if(e[1]<e[0])break;n.i=e=e[2]}};function Z(n){return this.getChildContext=function(){return n.context},n.children}function Y(n){var e=this,r=n.h;if(e.componentWillUnmount=function(){o(null,e.v),e.v=null,e.h=null},e.h&&e.h!==r&&e.componentWillUnmount(),!e.v){for(var u=e.__v;null!==u&&!u.__m&&null!==u.__;)u=u.__;e.h=r,e.v={nodeType:1,parentNode:r,childNodes:[],__k:{__m:u.__m},contains:function(){return!0},insertBefore:function(n,t){this.childNodes.push(n),e.h.insertBefore(n,t)},removeChild:function(n){this.childNodes.splice(this.childNodes.indexOf(n)>>>1,1),e.h.removeChild(n)}}}o(t(Z,{context:e.context},n.__v),e.v)}function $(n,e){var r=t(Y,{__v:n,h:e});return r.containerInfo=e,r}(B.prototype=new n).__a=function(n){var t=this,e=j(t.__v),r=t.l.get(n);return r[0]++,function(u){var o=function(){t.props.revealOrder?(r.push(u),H(t,n,r)):u()};e?e(o):o()}},B.prototype.render=function(n){this.i=null,this.l=new Map;var t=r(n.children);n.revealOrder&&\"b\"===n.revealOrder[0]&&t.reverse();for(var e=t.length;e--;)this.l.set(t[e],this.i=[1,0,this.i]);return n.children},B.prototype.componentDidUpdate=B.prototype.componentDidMount=function(){var n=this;this.l.forEach(function(t,e){H(n,e,t)})};var q=\"undefined\"!=typeof Symbol&&Symbol.for&&Symbol.for(\"react.element\")||60103,G=/^(?:accent|alignment|arabic|baseline|cap|clip(?!PathU)|color|dominant|fill|flood|font|glyph(?!R)|horiz|image(!S)|letter|lighting|marker(?!H|W|U)|overline|paint|pointer|shape|stop|strikethrough|stroke|text(?!L)|transform|underline|unicode|units|v|vector|vert|word|writing|x(?!C))[A-Z]/,J=/^on(Ani|Tra|Tou|BeforeInp|Compo)/,K=/[A-Z0-9]/g,Q=\"undefined\"!=typeof document,X=function(n){return(\"undefined\"!=typeof Symbol&&\"symbol\"==typeof Symbol()?/fil|che|rad/:/fil|che|ra/).test(n)};function nn(n,t,e){return null==t.__k&&(t.textContent=\"\"),o(n,t),\"function\"==typeof e&&e(),n?n.__c:null}function tn(n,t,e){return i(n,t),\"function\"==typeof e&&e(),n?n.__c:null}n.prototype.isReactComponent={},[\"componentWillMount\",\"componentWillReceiveProps\",\"componentWillUpdate\"].forEach(function(t){Object.defineProperty(n.prototype,t,{configurable:!0,get:function(){return this[\"UNSAFE_\"+t]},set:function(n){Object.defineProperty(this,t,{configurable:!0,writable:!0,value:n})}})});var en=e.event;function rn(){}function un(){return this.cancelBubble}function on(){return this.defaultPrevented}e.event=function(n){return en&&(n=en(n)),n.persist=rn,n.isPropagationStopped=un,n.isDefaultPrevented=on,n.nativeEvent=n};var ln,cn={enumerable:!1,configurable:!0,get:function(){return this.class}},fn=e.vnode;e.vnode=function(n){\"string\"==typeof n.type&&function(n){var t=n.props,e=n.type,u={},o=-1===e.indexOf(\"-\");for(var i in t){var l=t[i];if(!(\"value\"===i&&\"defaultValue\"in t&&null==l||Q&&\"children\"===i&&\"noscript\"===e||\"class\"===i||\"className\"===i)){var c=i.toLowerCase();\"defaultValue\"===i&&\"value\"in t&&null==t.value?i=\"value\":\"download\"===i&&!0===l?l=\"\":\"translate\"===c&&\"no\"===l?l=!1:\"o\"===c[0]&&\"n\"===c[1]?\"ondoubleclick\"===c?i=\"ondblclick\":\"onchange\"!==c||\"input\"!==e&&\"textarea\"!==e||X(t.type)?\"onfocus\"===c?i=\"onfocusin\":\"onblur\"===c?i=\"onfocusout\":J.test(i)&&(i=c):c=i=\"oninput\":o&&G.test(i)?i=i.replace(K,\"-$&\").toLowerCase():null===l&&(l=void 0),\"oninput\"===c&&u[i=c]&&(i=\"oninputCapture\"),u[i]=l}}\"select\"==e&&u.multiple&&Array.isArray(u.value)&&(u.value=r(t.children).forEach(function(n){n.props.selected=-1!=u.value.indexOf(n.props.value)})),\"select\"==e&&null!=u.defaultValue&&(u.value=r(t.children).forEach(function(n){n.props.selected=u.multiple?-1!=u.defaultValue.indexOf(n.props.value):u.defaultValue==n.props.value})),t.class&&!t.className?(u.class=t.class,Object.defineProperty(u,\"className\",cn)):(t.className&&!t.class||t.class&&t.className)&&(u.class=u.className=t.className),n.props=u}(n),n.$$typeof=q,fn&&fn(n)};var an=e.__r;e.__r=function(n){an&&an(n),ln=n.__c};var sn=e.diffed;e.diffed=function(n){sn&&sn(n);var t=n.props,e=n.__e;null!=e&&\"textarea\"===n.type&&\"value\"in t&&t.value!==e.value&&(e.value=null==t.value?\"\":t.value),ln=null};var hn={ReactCurrentDispatcher:{current:{readContext:function(n){return ln.__n[n.__c].props.value},useCallback:v,useContext:d,useDebugValue:m,useDeferredValue:w,useEffect:h,useId:p,useImperativeHandle:y,useInsertionEffect:I,useLayoutEffect:s,useMemo:_,useReducer:b,useRef:S,useState:a,useSyncExternalStore:C,useTransition:k}}},vn=\"18.3.1\";function dn(n){return t.bind(null,n)}function mn(n){return!!n&&n.$$typeof===q}function pn(n){return mn(n)&&n.type===u}function yn(n){return!!n&&!!n.displayName&&(\"string\"==typeof n.displayName||n.displayName instanceof String)&&n.displayName.startsWith(\"Memo(\")}function _n(n){return mn(n)?f.apply(null,arguments):n}function bn(n){return!!n.__k&&(o(null,n),!0)}function Sn(n){return n&&(n.base||1===n.nodeType&&n)||null}var gn=function(n,t){return n(t)},En=function(n,t){return n(t)},Cn=u,xn=mn,Rn={useState:a,useId:p,useReducer:b,useEffect:h,useLayoutEffect:s,useInsertionEffect:I,useTransition:k,useDeferredValue:w,useSyncExternalStore:C,startTransition:R,useRef:S,useImperativeHandle:y,useMemo:_,useCallback:v,useContext:d,useDebugValue:m,version:\"18.3.1\",Children:O,render:nn,hydrate:tn,unmountComponentAtNode:bn,createPortal:$,createElement:t,createContext:l,createFactory:dn,cloneElement:_n,createRef:c,Fragment:u,isValidElement:mn,isElement:xn,isFragment:pn,isMemo:yn,findDOMNode:Sn,Component:n,PureComponent:N,memo:M,forwardRef:D,flushSync:En,unstable_batchedUpdates:gn,StrictMode:Cn,Suspense:P,SuspenseList:B,lazy:z,__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED:hn};export{O as Children,N as PureComponent,Cn as StrictMode,P as Suspense,B as SuspenseList,hn as __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,_n as cloneElement,dn as createFactory,$ as createPortal,Rn as default,Sn as findDOMNode,En as flushSync,D as forwardRef,tn as hydrate,xn as isElement,pn as isFragment,yn as isMemo,mn as isValidElement,z as lazy,M as memo,nn as render,R as startTransition,bn as unmountComponentAtNode,gn as unstable_batchedUpdates,w as useDeferredValue,I as useInsertionEffect,C as useSyncExternalStore,k as useTransition,vn as version};\n//# sourceMappingURL=compat.module.js.map\n","import { createContext } from 'preact';\nimport type { ComponentChildren } from 'preact';\nimport { useContext } from 'preact/hooks';\n\nconst SpatialNavigatorDefaultFocusContext = createContext<boolean>(false);\n\nexport const useSpatialNavigatorDefaultFocus = () => {\n const spatialNavigatorDefaultFocus = useContext(SpatialNavigatorDefaultFocusContext);\n return spatialNavigatorDefaultFocus;\n};\n\nexport type DefaultFocusProps = {\n children?: ComponentChildren;\n enable?: boolean;\n};\n\ntype Props = DefaultFocusProps;\n\nexport const DefaultFocus = ({ children, enable = true }: Props) => {\n return (\n <SpatialNavigatorDefaultFocusContext.Provider value={enable}>\n {children}\n </SpatialNavigatorDefaultFocusContext.Provider>\n );\n};\n\n","import { createContext } from 'preact';\nimport { useContext } from 'preact/hooks';\nimport type { Ref } from 'preact';\n\nexport type ScrollToNodeCallback = (childRef: Ref<HTMLElement | null>, additionalOffset?: number) => void;\n\nconst defaultScrollToNodeIfNeeded: ScrollToNodeCallback = () => {\n // Default no-op implementation\n};\n\nexport const ParentScrollContext = createContext<ScrollToNodeCallback>(defaultScrollToNodeIfNeeded);\n\n// Alias for compatibility\nexport const SpatialNavigatorParentScrollContext = ParentScrollContext;\n\nexport const useSpatialNavigatorParentScroll = () => {\n const scrollToNodeIfNeeded = useContext(ParentScrollContext);\n return { scrollToNodeIfNeeded };\n};\n\n","import { useRef } from 'preact/hooks';\nimport uniqueId from 'lodash.uniqueid';\n\nexport const useUniqueId = ({ prefix }: { prefix: string }) => {\n const id = useRef<string | null>(null);\n\n if (id.current === null) {\n id.current = uniqueId(prefix);\n }\n\n return id.current;\n};\n\n","import type { ComponentChildren, JSX, Ref } from 'preact';\nimport { forwardRef } from 'preact/compat';\nimport { useEffect, useImperativeHandle, useRef, useState } from 'preact/hooks';\nimport { useSpatialNavigatorDefaultFocus } from '../context/DefaultFocusContext';\nimport { ParentIdContext, useParentId } from '../context/ParentIdContext';\nimport { useSpatialNavigatorParentScroll } from '../context/ParentScrollContext';\nimport { useSpatialNavigator } from '../context/SpatialNavigatorContext';\nimport { useUniqueId } from '../hooks/useUniqueId';\nimport type { NodeOrientation, SpatialNavigationNodeRef } from '../types';\nimport type { NodeIndexRange } from '@bam.tech/lrud';\nimport { useIsRootActive } from '../context/IsRootActiveContext';\nimport { cloneElement } from 'preact';\n\ntype NonFocusableNodeState = {\n /** Returns whether the root is active or not. An active node is active if one of its children is focused. */\n isActive: boolean;\n /** Returns whether the root is active or not.