@tvuikit/navigation-react 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../src/react/NavigationProvider.tsx","../../../node_modules/react/cjs/react-compiler-runtime.production.js","../../../node_modules/react/cjs/react-compiler-runtime.development.js","../../../node_modules/react/compiler-runtime.js","../src/react/focusGroupContext.ts","../src/react/useFocusable.ts","../src/react/useCustomFocusable.ts","../src/react/useFocusEvents.ts","../src/react/FocusGroup.tsx","../src/react/Focusable.tsx","../src/react/CustomFocusable.tsx","../src/react/DebugPanel.tsx"],"sourcesContent":["import React, { createContext, useContext, useEffect, useMemo, useRef } from \"react\";\nimport type { Direction, NavigationStore } from \"@tvuikit/navigation\";\nimport { createDomRegistry, type DomRegistry } from \"@tvuikit/navigation-dom\";\n\n/**\n * Key mapping configuration for navigation.\n * Each direction can have one or more keys that trigger navigation.\n */\nexport type KeyMap = {\n /** Key(s) that trigger up navigation. Default: \"ArrowUp\" */\n up?: string | readonly string[];\n /** Key(s) that trigger down navigation. Default: \"ArrowDown\" */\n down?: string | readonly string[];\n /** Key(s) that trigger left navigation. Default: \"ArrowLeft\" */\n left?: string | readonly string[];\n /** Key(s) that trigger right navigation. Default: \"ArrowRight\" */\n right?: string | readonly string[];\n};\n\n/**\n * Default key mappings using standard arrow keys.\n */\nexport const DEFAULT_KEY_MAP: Readonly<Required<KeyMap>> = {\n up: \"ArrowUp\",\n down: \"ArrowDown\",\n left: \"ArrowLeft\",\n right: \"ArrowRight\",\n} as const;\n\n/**\n * Action key mapping configuration.\n * Maps action names to one or more keys that trigger them.\n * When an action key is pressed, an \"action\" event is dispatched with the action name and focused node.\n */\nexport type ActionKeys = {\n [action: string]: string | readonly string[];\n};\n\n/**\n * Default action key mappings for common TV remote actions.\n */\nexport const DEFAULT_ACTION_KEYS: Readonly<ActionKeys> = {\n select: [\"Enter\", \" \"],\n back: [\"Escape\", \"Backspace\"],\n} as const;\n\n/**\n * Context value for the NavigationProvider.\n */\nexport type NavigationContextValue = {\n /** The navigation store */\n store: NavigationStore;\n /**\n * The DOM source name used for DOM components.\n * Default: \"dom\"\n */\n domSource: string;\n /**\n * The DOM registry for registering DOM elements.\n * This is the actual DomRegistry with HTMLElement support.\n */\n domRegistry: DomRegistry;\n};\n\n/**\n * Context for NavigationProvider.\n * Provides the store and DOM registry to child components.\n */\nexport const NavigationContext = createContext<NavigationContextValue | null>(null);\n\nexport type NavigationProviderProps = {\n children: React.ReactNode;\n /**\n * The navigation store instance.\n */\n store: NavigationStore;\n /**\n * Source name for DOM elements.\n * Default: \"dom\"\n */\n domSource?: string;\n /**\n * Auto-capture arrow keys for navigation.\n * Default: true\n */\n captureKeyboard?: boolean;\n /**\n * Auto-sync registry on each animation frame.\n * Default: true\n */\n autoSync?: boolean;\n /**\n * Focus this node ID on mount.\n * Can be a local ID (e.g., \"btn-1\") or full ID (e.g., \"dom:btn-1\").\n * If it doesn't contain \":\", the domSource prefix is added automatically.\n */\n initialFocus?: string;\n /**\n * Custom key mappings for navigation.\n * Allows overriding default arrow keys for devices with custom remotes.\n *\n * @example\n * // Use WASD keys\n * keyMap={{ up: \"w\", down: \"s\", left: \"a\", right: \"d\" }}\n *\n * @example\n * // Support both arrow keys and WASD\n * keyMap={{\n * up: [\"ArrowUp\", \"w\"],\n * down: [\"ArrowDown\", \"s\"],\n * left: [\"ArrowLeft\", \"a\"],\n * right: [\"ArrowRight\", \"d\"]\n * }}\n *\n * @example\n * // TV remote with custom key codes\n * keyMap={{ up: \"VK_UP\", down: \"VK_DOWN\", left: \"VK_LEFT\", right: \"VK_RIGHT\" }}\n */\n keyMap?: KeyMap;\n /**\n * Action key mappings for non-navigation actions.\n * When an action key is pressed, an \"action\" event is dispatched.\n * Set to `false` to disable action key handling entirely.\n *\n * Default actions:\n * - \"select\": Enter, Space\n * - \"back\": Escape, Backspace\n *\n * @example\n * // Add custom actions\n * actionKeys={{\n * select: [\"Enter\", \" \", \"OK\"],\n * back: [\"Escape\", \"Back\"],\n * info: [\"i\", \"Info\"],\n * menu: [\"m\", \"Menu\"],\n * }}\n *\n * @example\n * // Subscribe to action events\n * useFocusEvents(\"action\", ({ action, nodeId }) => {\n * if (action === \"select\" && nodeId === \"play-btn\") playVideo();\n * });\n */\n actionKeys?: ActionKeys | false;\n};\n\n/**\n * Provider component for spatial navigation using a NavigationStore.\n *\n * For DOM-only apps, this is all you need:\n * ```tsx\n * const nav = createNavigationStore();\n *\n * <NavigationProvider store={nav}>\n * <Focusable id=\"btn-1\">Click</Focusable>\n * </NavigationProvider>\n * ```\n *\n * For mixed renderer apps (DOM + Pixi/Three.js):\n * ```tsx\n * const nav = createNavigationStore();\n *\n * <NavigationProvider store={nav}>\n * <Focusable id=\"btn-1\">Click</Focusable>\n * <PixiStage>\n * <PixiTile store={nav} id=\"tile-1\" rect={rect} />\n * </PixiStage>\n * </NavigationProvider>\n * ```\n */\nexport function NavigationProvider({\n children,\n store,\n domSource = \"dom\",\n captureKeyboard = true,\n autoSync = true,\n initialFocus,\n keyMap,\n actionKeys,\n}: NavigationProviderProps) {\n // Capture initial props in ref so they don't cause re-creation\n const initialPropsRef = useRef({ store, domSource });\n\n // Create and register the DOM registry (stable for lifetime)\n const domRegistry = useMemo(() => {\n const { store: s, domSource: source } = initialPropsRef.current;\n\n // Check if a DOM registry already exists for this source\n const existing = s.getExistingSource(source);\n if (existing) {\n // Verify it's a DomRegistry (has getElement method)\n if (typeof (existing as DomRegistry).getElement === \"function\") {\n return existing as DomRegistry;\n }\n console.warn(\n `NavigationProvider: source \"${source}\" exists but is not a DomRegistry. Creating new DomRegistry.`\n );\n }\n\n // Create a new DomRegistry and add it to the store\n const dom = createDomRegistry();\n s.addSource(source, dom);\n return dom;\n }, []);\n\n // Memoize context value\n const contextValue = useMemo<NavigationContextValue>(\n () => ({\n store: initialPropsRef.current.store,\n domSource: initialPropsRef.current.domSource,\n domRegistry,\n }),\n [domRegistry]\n );\n\n // Auto-sync effect\n useEffect(() => {\n if (!autoSync) return;\n let raf = 0;\n const loop = () => {\n store.sync();\n raf = requestAnimationFrame(loop);\n };\n raf = requestAnimationFrame(loop);\n return () => cancelAnimationFrame(raf);\n }, [autoSync, store]);\n\n // Merge custom keyMap with defaults\n const resolvedKeyMap = useMemo(() => {\n if (!keyMap) return DEFAULT_KEY_MAP;\n return {\n up: keyMap.up ?? DEFAULT_KEY_MAP.up,\n down: keyMap.down ?? DEFAULT_KEY_MAP.down,\n left: keyMap.left ?? DEFAULT_KEY_MAP.left,\n right: keyMap.right ?? DEFAULT_KEY_MAP.right,\n };\n }, [keyMap]);\n\n // Resolve action keys (false = disabled, undefined = use defaults, object = merge with defaults)\n const resolvedActionKeys = useMemo(() => {\n if (actionKeys === false) return null;\n if (!actionKeys) return DEFAULT_ACTION_KEYS;\n // Merge custom action keys with defaults\n return { ...DEFAULT_ACTION_KEYS, ...actionKeys };\n }, [actionKeys]);\n\n // Keyboard capture effect\n useEffect(() => {\n if (!captureKeyboard) return;\n\n // Build a lookup map for O(1) navigation key matching\n const keyToDirection = new Map<string, Direction>();\n const addNavKeys = (keys: string | readonly string[], direction: Direction) => {\n if (typeof keys === \"string\") {\n keyToDirection.set(keys, direction);\n } else {\n for (const k of keys) keyToDirection.set(k, direction);\n }\n };\n addNavKeys(resolvedKeyMap.up, \"up\");\n addNavKeys(resolvedKeyMap.down, \"down\");\n addNavKeys(resolvedKeyMap.left, \"left\");\n addNavKeys(resolvedKeyMap.right, \"right\");\n\n // Build a lookup map for O(1) action key matching\n const keyToAction = new Map<string, string>();\n if (resolvedActionKeys) {\n for (const [action, keys] of Object.entries(resolvedActionKeys)) {\n if (typeof keys === \"string\") {\n keyToAction.set(keys, action);\n } else {\n for (const k of keys) keyToAction.set(k, action);\n }\n }\n }\n\n const handleKeyDown = (e: KeyboardEvent) => {\n // First check navigation keys\n const direction = keyToDirection.get(e.key);\n if (direction) {\n e.preventDefault();\n store.move(direction, { sync: true });\n return;\n }\n\n // Then check action keys\n const action = keyToAction.get(e.key);\n if (action) {\n e.preventDefault();\n const focusedId = store.focused;\n const bounds = focusedId ? store.registry.bounds(focusedId) : null;\n store.engine.dispatchEvent(\n new CustomEvent(\"action\", {\n detail: { action, nodeId: focusedId, bounds, key: e.key },\n })\n );\n }\n };\n\n window.addEventListener(\"keydown\", handleKeyDown);\n return () => window.removeEventListener(\"keydown\", handleKeyDown);\n }, [captureKeyboard, store, resolvedKeyMap, resolvedActionKeys]);\n\n // Initial focus effect\n useEffect(() => {\n // Wait a tick for elements to be registered\n const timer = setTimeout(() => {\n // If explicit initialFocus provided, use it\n if (initialFocus) {\n const fullId = initialFocus.includes(\":\")\n ? initialFocus\n : `${domSource}:${initialFocus}`;\n store.focus(fullId);\n return;\n }\n\n // Auto-focus: find first available focusable element\n // Sync the registry first to ensure bounds are up-to-date\n store.sync();\n\n const ids = store.registry.ids();\n for (let i = 0; i < ids.length; i++) {\n const id = ids[i]!;\n const isEnabled = store.registry.enabled?.(id) ?? true;\n const isVisible = store.registry.visible?.(id) ?? true;\n if (isEnabled && isVisible) {\n store.focus(id);\n return;\n }\n }\n }, 0);\n return () => clearTimeout(timer);\n }, [store, initialFocus, domSource]);\n\n return (\n <NavigationContext.Provider value={contextValue}>\n {children}\n </NavigationContext.Provider>\n );\n}\n\n/**\n * Hook to access the NavigationStore from context.\n * Throws if used outside of a NavigationProvider.\n */\nexport function useNavigationStore(): NavigationStore {\n const ctx = useContext(NavigationContext);\n if (!ctx) {\n throw new Error(\"useNavigationStore must be used within a <NavigationProvider>\");\n }\n return ctx.store;\n}\n\n/**\n * Hook to access the full navigation context.\n * Returns null if not within a provider.\n */\nexport function useNavigationContext(): NavigationContextValue | null {\n return useContext(NavigationContext);\n}\n","/**\n * @license React\n * react-compiler-runtime.production.js\n *\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n\"use strict\";\nvar ReactSharedInternals =\n require(\"react\").__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;\nexports.c = function (size) {\n return ReactSharedInternals.H.useMemoCache(size);\n};\n","/**\n * @license React\n * react-compiler-runtime.development.js\n *\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n\"use strict\";\n\"production\" !== process.env.NODE_ENV &&\n (function () {\n var ReactSharedInternals =\n require(\"react\").__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;\n exports.c = function (size) {\n var dispatcher = ReactSharedInternals.H;\n null === dispatcher &&\n console.error(\n \"Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:\\n1. You might have mismatching versions of React and the renderer (such as React DOM)\\n2. You might be breaking the Rules of Hooks\\n3. You might have more than one copy of React in the same app\\nSee https://react.dev/link/invalid-hook-call for tips about how to debug and fix this problem.\"\n );\n return dispatcher.useMemoCache(size);\n };\n })();\n","/**\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n'use strict';\n\nif (process.env.NODE_ENV === 'production') {\n module.exports = require('./cjs/react-compiler-runtime.production.js');\n} else {\n module.exports = require('./cjs/react-compiler-runtime.development.js');\n}\n","import { createContext } from \"react\";\n\nexport type FocusGroupContextValue = {\n groupId: string;\n};\n\nexport const FocusGroupContext = createContext<FocusGroupContextValue | null>(null);\n\n\n","import { useCallback, useContext, useMemo, useRef, useSyncExternalStore } from \"react\";\nimport type { NodeId } from \"@tvuikit/navigation\";\nimport { NavigationContext } from \"./NavigationProvider.js\";\nimport { FocusGroupContext } from \"./focusGroupContext.js\";\n\nexport type UseFocusableOptions = {\n id: NodeId;\n /**\n * Optional focus group id for this node.\n * If not provided, inherits from the nearest FocusGroup context.\n */\n groupId?: string | null;\n /**\n * If true, element cannot receive focus.\n */\n disabled?: boolean;\n /**\n * Called when this element gains focus.\n */\n onFocus?: () => void;\n /**\n * Called when this element loses focus.\n */\n onBlur?: () => void;\n};\n\n/**\n * Hook to register a DOM element as focusable and track its focus state.\n *\n * Uses `useSyncExternalStore` for tear-free focus state updates,\n * compatible with React concurrent features.\n *\n * @example\n * ```tsx\n * function Button({ id }: { id: string }) {\n * const { ref, focused } = useFocusable({ id });\n * return (\n * <button ref={ref} className={focused ? \"focused\" : \"\"}>\n * Click me\n * </button>\n * );\n * }\n * ```\n */\nexport function useFocusable(opts: UseFocusableOptions) {\n const ctx = useContext(NavigationContext);\n if (!ctx) throw new Error(\"useFocusable must be used within <NavigationProvider>\");\n\n const groupCtx = useContext(FocusGroupContext);\n\n const { store, domSource, domRegistry } = ctx;\n const id = opts.id;\n const groupId = opts.groupId !== undefined ? opts.groupId : groupCtx?.groupId;\n const disabled = opts.disabled;\n const onFocusCallback = opts.onFocus;\n const onBlurCallback = opts.onBlur;\n\n // Compute the full navigation ID (namespaced with domSource)\n const navId = useMemo(() => `${domSource}:${id}`, [domSource, id]);\n\n // Store previous focused state to avoid notifying when nothing changed\n const prevFocusedRef = useRef<boolean | null>(null);\n\n // Subscribe function - uses store's subscribe method\n const subscribe = useCallback(\n (onStoreChange: () => void) => {\n const unsubscribe = store.subscribe(() => {\n const nowFocused = store.isFocused(navId);\n if (prevFocusedRef.current !== nowFocused) {\n prevFocusedRef.current = nowFocused;\n onStoreChange();\n }\n });\n\n // Initialize previous state\n prevFocusedRef.current = store.isFocused(navId);\n\n return unsubscribe;\n },\n [store, navId]\n );\n\n // Snapshot function - returns current focused state\n const getSnapshot = useCallback(() => store.isFocused(navId), [store, navId]);\n\n // Server snapshot\n const getServerSnapshot = useCallback(() => false, []);\n\n const focused = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n\n // Ref callback for DOM registration - stable unless dependencies change\n const ref = useCallback(\n (el: HTMLElement | null) => {\n if (!el) {\n domRegistry.unregister(id);\n return;\n }\n const registerOpts: {\n groupId?: string | null;\n enabled?: boolean;\n onFocus?: () => void;\n onBlur?: () => void;\n } = {};\n if (groupId !== undefined) registerOpts.groupId = groupId;\n if (disabled !== undefined) registerOpts.enabled = !disabled;\n if (onFocusCallback) registerOpts.onFocus = onFocusCallback;\n if (onBlurCallback) registerOpts.onBlur = onBlurCallback;\n domRegistry.register(id, el, Object.keys(registerOpts).length > 0 ? registerOpts : undefined);\n },\n [domRegistry, id, groupId, disabled, onFocusCallback, onBlurCallback]\n );\n\n // Memoize return value to prevent unnecessary re-renders\n return useMemo(() => ({ ref, focused }), [ref, focused]);\n}\n","import { useCallback, useContext, useEffect, useMemo, useRef, useSyncExternalStore } from \"react\";\nimport type { NodeId, Rect, NavigationStore } from \"@tvuikit/navigation\";\nimport { FocusGroupContext } from \"./focusGroupContext.js\";\n\nexport type UseCustomFocusableOptions = {\n /**\n * The navigation store instance.\n * Required - pass the store directly to avoid cross-reconciler context issues.\n */\n store: NavigationStore;\n /**\n * Source name for this focusable (e.g., \"pixi\", \"three\").\n * Default: \"custom\"\n */\n source?: string;\n /**\n * Unique identifier for this focusable node (local ID, without source prefix).\n * Will be namespaced as \"source:id\" internally.\n */\n id: NodeId;\n /**\n * Bounding rectangle in viewport coordinates.\n * Must be in the same coordinate space as DOM elements (client coordinates).\n * For canvas/WebGL elements, add the canvas offset to local coordinates.\n */\n rect: Rect;\n /**\n * Optional focus group id for this node.\n * If not provided, attempts to inherit from the nearest FocusGroup context.\n */\n groupId?: string | null;\n /**\n * If true, element cannot receive focus.\n */\n disabled?: boolean;\n /**\n * Called when this element gains focus.\n */\n onFocus?: () => void;\n /**\n * Called when this element loses focus.\n */\n onBlur?: () => void;\n};\n\n/**\n * Hook to register a custom (non-DOM) element as focusable and track its focus state.\n *\n * Use this for elements rendered via WebGL, Canvas, or other non-DOM technologies.\n * Unlike `useFocusable`, you must provide the bounding rect manually and pass\n * the store directly (to avoid cross-reconciler context issues).\n *\n * Uses `useSyncExternalStore` for tear-free focus state updates,\n * compatible with React concurrent features.\n *\n * @example\n * ```tsx\n * function PixiTile({ store, id, x, y, canvasOffset }: Props) {\n * const rect = useMemo(() => ({\n * x: canvasOffset.x + x,\n * y: canvasOffset.y + y,\n * w: 200,\n * h: 150,\n * }), [canvasOffset.x, canvasOffset.y, x, y]);\n *\n * const { focused } = useCustomFocusable({\n * store,\n * source: \"pixi\",\n * id,\n * rect,\n * });\n *\n * return <Graphics draw={(g) => drawTile(g, focused)} />;\n * }\n * ```\n */\nexport function useCustomFocusable(opts: UseCustomFocusableOptions): { focused: boolean } {\n const { store, source = \"custom\", id, rect, groupId: explicitGroupId, disabled, onFocus, onBlur } = opts;\n\n // Try to get group from context (may not work across reconcilers, but try)\n const groupCtx = useContext(FocusGroupContext);\n const groupId = explicitGroupId !== undefined ? explicitGroupId : (groupCtx?.groupId ?? null);\n\n // Compute the full navigation ID (namespaced)\n const navId = useMemo(() => `${source}:${id}`, [source, id]);\n\n // Get the source registry\n const registry = useMemo(() => store.getSource(source), [store, source]);\n\n // Register/update with the source registry\n useEffect(() => {\n registry.register({\n id,\n rect,\n groupId,\n ...(disabled !== undefined && { enabled: !disabled }),\n ...(onFocus && { onFocus }),\n ...(onBlur && { onBlur }),\n });\n\n return () => {\n registry.unregister(id);\n };\n }, [registry, id, rect, groupId, disabled, onFocus, onBlur]);\n\n // Store previous focused state to avoid notifying when nothing changed\n const prevFocusedRef = useRef<boolean | null>(null);\n\n // Subscribe function - uses store's subscribe method\n const subscribe = useCallback(\n (onStoreChange: () => void) => {\n // Subscribe to store changes\n const unsubscribe = store.subscribe(() => {\n const nowFocused = store.isFocused(navId);\n // Only trigger update if state actually changed\n if (prevFocusedRef.current !== nowFocused) {\n prevFocusedRef.current = nowFocused;\n onStoreChange();\n }\n });\n\n // Initialize previous state\n prevFocusedRef.current = store.isFocused(navId);\n\n return unsubscribe;\n },\n [store, navId]\n );\n\n // Snapshot function - returns current focused state\n const getSnapshot = useCallback(() => store.isFocused(navId), [store, navId]);\n\n // Server snapshot (same as client for this use case)\n const getServerSnapshot = useCallback(() => false, []);\n\n const focused = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n\n // Memoize return value to prevent unnecessary re-renders\n return useMemo(() => ({ focused }), [focused]);\n}\n","import { useContext, useEffect, useRef } from \"react\";\nimport type { NavigationEventMap, NavigationStore } from \"@tvuikit/navigation\";\nimport { NavigationContext } from \"./NavigationProvider.js\";\n\ntype EventType = keyof NavigationEventMap;\n\n/**\n * Handler map for subscribing to multiple events at once.\n */\nexport type FocusEventHandlers = {\n [K in EventType]?: (detail: NavigationEventMap[K][\"detail\"]) => void;\n};\n\ntype UseFocusEventsOptions = {\n store?: NavigationStore;\n};\n\n/**\n * Subscribe to a single navigation event.\n *\n * @example\n * ```tsx\n * useFocusEvents(\"focus\", ({ nodeId }) => {\n * console.log(\"Focused:\", nodeId);\n * });\n * ```\n */\nexport function useFocusEvents<T extends EventType>(\n type: T,\n handler: (detail: NavigationEventMap[T][\"detail\"]) => void,\n options?: UseFocusEventsOptions\n): void;\n\n/**\n * Subscribe to multiple navigation events at once.\n *\n * @example\n * ```tsx\n * useFocusEvents({\n * focus: ({ nodeId }) => console.log(\"Focused:\", nodeId),\n * move: ({ from, to, direction }) => console.log(\"Moved:\", direction),\n * reject: ({ reason }) => {\n * if (reason === \"boundary-blocked\") playBoundarySound();\n * },\n * });\n * ```\n */\nexport function useFocusEvents(\n handlers: FocusEventHandlers,\n options?: UseFocusEventsOptions\n): void;\n\n/**\n * Subscribe to navigation engine events with automatic cleanup.\n *\n * Can be used in two ways:\n * 1. Single event: `useFocusEvents(\"focus\", handler)`\n * 2. Multiple events: `useFocusEvents({ focus: handler1, move: handler2 })`\n *\n * Works within NavigationProvider (uses context) or with explicit store option.\n *\n * @example\n * ```tsx\n * // Single event\n * useFocusEvents(\"focus\", ({ nodeId }) => {\n * console.log(\"Focused:\", nodeId);\n * });\n *\n * // Multiple events\n * useFocusEvents({\n * focus: ({ nodeId }) => console.log(\"Focused:\", nodeId),\n * move: ({ from, to }) => analytics.track(\"nav\", { from, to }),\n * reject: ({ reason }) => {\n * if (reason === \"boundary-blocked\") playBoundarySound();\n * },\n * });\n *\n * // With explicit store (for cross-reconciler use)\n * useFocusEvents(\"focus\", handler, { store });\n * useFocusEvents({ focus: handler }, { store });\n * ```\n */\nexport function useFocusEvents<T extends EventType>(\n typeOrHandlers: T | FocusEventHandlers,\n handlerOrOptions?: ((detail: NavigationEventMap[T][\"detail\"]) => void) | UseFocusEventsOptions,\n options?: UseFocusEventsOptions\n): void {\n const ctx = useContext(NavigationContext);\n\n // Normalize arguments\n const isMultiEvent = typeof typeOrHandlers === \"object\";\n const handlers: FocusEventHandlers = isMultiEvent\n ? typeOrHandlers\n : { [typeOrHandlers]: handlerOrOptions as (detail: NavigationEventMap[T][\"detail\"]) => void };\n const opts = isMultiEvent\n ? (handlerOrOptions as UseFocusEventsOptions | undefined)\n : options;\n\n const store = opts?.store ?? ctx?.store;\n\n // Use ref to store handlers - avoids re-subscribing when handlers change\n const handlersRef = useRef<FocusEventHandlers>(handlers);\n handlersRef.current = handlers;\n\n // Track which event types we're subscribed to for re-subscription detection\n const eventTypesKey = Object.keys(handlers).sort().join(\",\");\n\n useEffect(() => {\n if (!store) {\n console.warn(\"useFocusEvents: No store available. Use within NavigationProvider or pass store option.\");\n return;\n }\n\n const cleanups: (() => void)[] = [];\n\n for (const type of Object.keys(handlersRef.current) as EventType[]) {\n const handleEvent = (e: Event) => {\n const handler = handlersRef.current[type];\n if (handler) {\n const detail = (e as CustomEvent).detail;\n handler(detail);\n }\n };\n\n store.addEventListener(type, handleEvent);\n cleanups.push(() => store.removeEventListener(type, handleEvent));\n }\n\n return () => {\n for (const cleanup of cleanups) {\n cleanup();\n }\n };\n }, [store, eventTypesKey]);\n}\n","import React, { useContext, useEffect, useMemo } from \"react\";\nimport { NavigationContext } from \"./NavigationProvider.js\";\nimport { FocusGroupContext } from \"./focusGroupContext.js\";\nimport type { DirectionBoundary, NavigationStore } from \"@tvuikit/navigation\";\n\nexport type FocusGroupProps = {\n /**\n * Group id used for focus history + restore-on-enter behavior.\n * Nested groups default to `${parent}/${id}`.\n */\n id: string;\n /**\n * If true, combine nested ids as `${parent}/${id}`.\n * If false, use `id` as-is.\n *\n * Default: true\n */\n nested?: boolean;\n /**\n * Configure restore-on-enter for this group.\n * When true, entering this group will restore focus to the last focused node.\n *\n * Default: true\n */\n restoreHistoryOnEnter?: boolean;\n /**\n * If true, clears this group's focus history on mount.\n * Default: false\n */\n resetHistoryOnMount?: boolean;\n /**\n * Optional: focus a specific node within this group on mount.\n * Uses local ids (the ID without source prefix).\n */\n focusOnMountId?: string;\n /**\n * If true, focus cannot leave this group in any direction.\n * Equivalent to `boundary={{ left: true, right: true, up: true, down: true }}`.\n */\n trap?: boolean;\n /**\n * Block specific directions from leaving the group.\n * For example, `{ right: true }` prevents navigating right out of this group.\n */\n boundary?: DirectionBoundary;\n /**\n * Optional store prop for use outside of NavigationProvider context.\n * Required when using FocusGroup across reconciler boundaries (e.g., in Pixi).\n */\n store?: NavigationStore;\n children: React.ReactNode;\n};\n\nexport function FocusGroup(props: FocusGroupProps) {\n const parent = useContext(FocusGroupContext);\n const ctx = useContext(NavigationContext);\n\n // Get store from props or context\n const store = props.store ?? ctx?.store;\n\n const groupId = useMemo(() => {\n if (props.nested === false || !parent) return props.id;\n return `${parent.groupId}/${props.id}`;\n }, [parent, props.id, props.nested]);\n\n // Configure restore-on-enter for this group (default: true)\n useEffect(() => {\n if (!store) return;\n const enabled = props.restoreHistoryOnEnter ?? true;\n store.setGroupRestoreOnEnter(groupId, enabled);\n return () => store.clearGroupRestoreOnEnter(groupId);\n }, [store, groupId, props.restoreHistoryOnEnter]);\n\n // Configure boundary for this group\n useEffect(() => {\n if (!store) return;\n\n // Compute effective boundary\n let boundary: DirectionBoundary | null = null;\n if (props.trap) {\n boundary = { left: true, right: true, up: true, down: true };\n } else if (props.boundary) {\n boundary = props.boundary;\n }\n\n if (boundary) {\n store.setGroupBoundary(groupId, boundary);\n return () => store.clearGroupBoundary(groupId);\n }\n }, [store, groupId, props.trap, props.boundary]);\n\n // Reset history at mount (optional)\n useEffect(() => {\n if (!store) return;\n if (!props.resetHistoryOnMount) return;\n store.clearGroupHistory(groupId);\n }, [store, groupId, props.resetHistoryOnMount]);\n\n // Focus a specific node in this group at mount (optional)\n useEffect(() => {\n if (!store) return;\n const id = props.focusOnMountId;\n if (!id) return;\n // Focus the group, requesting a specific node\n store.focusGroup(groupId, { id });\n }, [store, groupId, props.focusOnMountId]);\n\n const value = useMemo(() => ({ groupId }), [groupId]);\n return <FocusGroupContext.Provider value={value}>{props.children}</FocusGroupContext.Provider>;\n}\n","import React, { type CSSProperties, type ElementType, type ReactNode } from \"react\";\nimport { useFocusable } from \"./useFocusable.js\";\n\ntype FocusableOwnProps<T extends ElementType = \"div\"> = {\n /**\n * Unique focusable ID.\n */\n id: string;\n /**\n * Element type to render. Default: \"div\"\n */\n as?: T;\n /**\n * Class name added when focused.\n */\n focusedClassName?: string;\n /**\n * Style applied when focused.\n */\n focusedStyle?: CSSProperties;\n /**\n * Called when this element gains focus.\n */\n onFocus?: () => void;\n /**\n * Called when this element loses focus.\n */\n onBlur?: () => void;\n /**\n * If true, element cannot receive focus.\n */\n disabled?: boolean;\n /**\n * Children.\n */\n children?: ReactNode;\n};\n\ntype FocusableProps<T extends ElementType = \"div\"> = FocusableOwnProps<T> &\n Omit<React.ComponentPropsWithoutRef<T>, keyof FocusableOwnProps<T>>;\n\nexport function Focusable<T extends ElementType = \"div\">({\n id,\n as,\n focusedClassName,\n focusedStyle,\n onFocus,\n onBlur,\n disabled,\n children,\n className,\n style,\n ...rest\n}: FocusableProps<T>) {\n const { ref, focused } = useFocusable({\n id,\n ...(disabled !== undefined && { disabled }),\n ...(onFocus && { onFocus }),\n ...(onBlur && { onBlur }),\n });\n\n const Component = as || \"div\";\n\n // Merge classNames\n const mergedClassName = focused && focusedClassName\n ? className\n ? `${className} ${focusedClassName}`\n : focusedClassName\n : className;\n\n // Merge styles\n const mergedStyle = focused && focusedStyle\n ? style\n ? { ...style, ...focusedStyle }\n : focusedStyle\n : style;\n\n return (\n <Component\n ref={ref}\n className={mergedClassName}\n style={mergedStyle}\n data-tvkit-focused={focused || undefined}\n {...rest}\n >\n {children}\n </Component>\n );\n}\n","import type { ReactNode } from \"react\";\nimport type { NodeId, Rect, NavigationStore } from \"@tvuikit/navigation\";\nimport { useCustomFocusable } from \"./useCustomFocusable.js\";\n\nexport type CustomFocusableProps = {\n /**\n * The navigation store instance.\n * Required - pass the store directly to avoid cross-reconciler context issues.\n */\n store: NavigationStore;\n /**\n * Source name for this focusable (e.g., \"pixi\", \"three\").\n * Default: \"custom\"\n */\n source?: string;\n /**\n * Unique identifier for this focusable node.\n * In mixed renderer scenarios with CombinedRegistry, this is the local ID\n * (e.g., \"tile-1\") which gets namespaced automatically (e.g., \"pixi:tile-1\").\n */\n id: NodeId;\n /**\n * Bounding rectangle in viewport coordinates.\n * Must be in the same coordinate space as DOM elements (client coordinates).\n * For canvas/WebGL elements, add the canvas offset to local coordinates.\n */\n rect: Rect;\n /**\n * Optional focus group id for this node.\n * If not provided, inherits from the nearest FocusGroup context.\n */\n groupId?: string | null;\n /**\n * If true, element cannot receive focus.\n */\n disabled?: boolean;\n /**\n * Called when this element gains focus.\n */\n onFocus?: () => void;\n /**\n * Called when this element loses focus.\n */\n onBlur?: () => void;\n /**\n * Render prop that receives the focused state.\n * Use this to apply visual focus effects to your custom-rendered element.\n */\n children: (focused: boolean) => ReactNode;\n};\n\n/**\n * Wrapper component for custom (non-DOM) focusable elements.\n *\n * Uses a render prop pattern to provide the focused state to your custom component.\n * Works with any renderer (Pixi.js, Three.js, Canvas 2D, etc.) - you just need to\n * provide the bounding rect in viewport coordinates.\n *\n * @example\n * ```tsx\n * // With Pixi.js\n * <CustomFocusable\n * id=\"tile-1\"\n * rect={{ x: canvasX + localX, y: canvasY + localY, w: 200, h: 150 }}\n * >\n * {(focused) => (\n * <Container scale={focused ? 1.1 : 1}>\n * <Graphics draw={drawTile} />\n * </Container>\n * )}\n * </CustomFocusable>\n *\n * // With callbacks\n * <CustomFocusable\n * id=\"tile-2\"\n * rect={tileRect}\n * onFocus={() => playSound('focus')}\n * onBlur={() => stopPreview()}\n * >\n * {(focused) => <CanvasTile focused={focused} />}\n * </CustomFocusable>\n *\n * // Disabled\n * <CustomFocusable id=\"tile-3\" rect={tileRect} disabled>\n * {(focused) => <CanvasTile focused={focused} />}\n * </CustomFocusable>\n * ```\n */\nexport function CustomFocusable({\n store,\n source,\n id,\n rect,\n groupId,\n disabled,\n onFocus,\n onBlur,\n children,\n}: CustomFocusableProps): ReactNode {\n const { focused } = useCustomFocusable({\n store,\n id,\n rect,\n ...(source && { source }),\n ...(groupId !== undefined && { groupId }),\n ...(disabled !== undefined && { disabled }),\n ...(onFocus && { onFocus }),\n ...(onBlur && { onBlur }),\n });\n\n return children(focused);\n}\n","import { memo, useState, type CSSProperties } from \"react\";\nimport { useFocusEvents } from \"./useFocusEvents.js\";\n\nexport type DebugPanelPosition = {\n /** Vertical position (default: \"bottom\") */\n vertical?: \"top\" | \"bottom\";\n /** Horizontal position (default: \"right\") */\n horizontal?: \"left\" | \"right\";\n};\n\nexport type DebugPanelProps = {\n /** Custom class name for the panel container */\n className?: string;\n /** Custom inline styles for the panel container */\n style?: CSSProperties;\n /** Whether to use default inline styles (default: true) */\n useDefaultStyles?: boolean;\n /** Position of the panel (default: { vertical: \"bottom\", horizontal: \"right\" }) */\n position?: DebugPanelPosition;\n /** Padding from the edge in pixels or CSS string (default: \"1rem\") */\n padding?: number | string;\n /** Whether the panel starts expanded (default: false) */\n defaultExpanded?: boolean;\n};\n\nconst COLLAPSED_SIZE = 40;\n\nconst baseContainerStyle: CSSProperties = {\n position: \"fixed\",\n background: \"rgba(18, 18, 26, 0.95)\",\n border: \"1px solid rgba(255, 255, 255, 0.1)\",\n borderRadius: \"12px\",\n fontSize: \"0.8rem\",\n backdropFilter: \"blur(12px)\",\n zIndex: 1000,\n fontFamily: \"system-ui, sans-serif\",\n color: \"#ffffff\",\n transition: \"all 0.2s ease\",\n};\n\nconst collapsedStyle: CSSProperties = {\n width: COLLAPSED_SIZE,\n height: COLLAPSED_SIZE,\n padding: 0,\n cursor: \"pointer\",\n};\n\nconst expandedStyle: CSSProperties = {\n minWidth: \"200px\",\n padding: \"1rem\",\n paddingTop: \"2.5rem\",\n};\n\nconst toggleButtonStyle: CSSProperties = {\n position: \"absolute\",\n top: 0,\n left: 0,\n width: COLLAPSED_SIZE,\n height: COLLAPSED_SIZE,\n border: \"none\",\n background: \"transparent\",\n color: \"#00d4aa\",\n fontSize: \"1.25rem\",\n fontWeight: 600,\n cursor: \"pointer\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n transition: \"color 0.2s ease\",\n};\n\nconst collapsedToggleStyle: CSSProperties = {\n ...toggleButtonStyle,\n position: \"relative\",\n};\n\nconst headingStyle: CSSProperties = {\n fontSize: \"0.75rem\",\n fontWeight: 600,\n textTransform: \"uppercase\",\n letterSpacing: \"0.05em\",\n color: \"#00d4aa\",\n marginBottom: \"0.75rem\",\n margin: 0,\n};\n\nconst infoContainerStyle: CSSProperties = {\n display: \"flex\",\n flexDirection: \"column\",\n gap: \"0.5rem\",\n};\n\nconst rowStyle: CSSProperties = {\n display: \"flex\",\n justifyContent: \"space-between\",\n color: \"#9ca3af\",\n};\n\nconst valueStyle: CSSProperties = {\n color: \"#ffffff\",\n fontFamily: \"monospace\",\n};\n\nconst contentStyle: CSSProperties = {\n animation: \"debugPanelFadeIn 0.2s ease\",\n};\n\nfunction getPositionStyle(\n position: DebugPanelPosition,\n padding: number | string\n): CSSProperties {\n const paddingValue = typeof padding === \"number\" ? `${padding}px` : padding;\n const style: CSSProperties = {};\n\n if (position.vertical === \"top\") {\n style.top = paddingValue;\n } else {\n style.bottom = paddingValue;\n }\n\n if (position.horizontal === \"left\") {\n style.left = paddingValue;\n } else {\n style.right = paddingValue;\n }\n\n return style;\n}\n\n/**\n * A collapsible debug panel that displays navigation state information.\n * Shows the currently focused element, previously focused element, last move direction, and last rejection.\n *\n * @example\n * ```tsx\n * // Default: collapsed, bottom-right corner\n * <DebugPanel />\n *\n * // Start expanded\n * <DebugPanel defaultExpanded />\n *\n * // Top-left corner with custom padding\n * <DebugPanel position={{ vertical: \"top\", horizontal: \"left\" }} padding={20} />\n *\n * // With custom className (disables default styles)\n * <DebugPanel className=\"my-debug-panel\" useDefaultStyles={false} />\n * ```\n */\nexport const DebugPanel = memo(function DebugPanel({\n className,\n style,\n useDefaultStyles = true,\n position = {},\n padding = \"1rem\",\n defaultExpanded = false,\n}: DebugPanelProps) {\n const [expanded, setExpanded] = useState(defaultExpanded);\n const [focusedId, setFocusedId] = useState<string | null>(null);\n const [previousId, setPreviousId] = useState<string>(\"-\");\n const [lastMove, setLastMove] = useState<string>(\"-\");\n const [lastReject, setLastReject] = useState<string>(\"-\");\n\n useFocusEvents({\n focus: ({ nodeId }) => setFocusedId(nodeId),\n blur: ({ nodeId }) => setPreviousId(nodeId),\n move: ({ direction }) => setLastMove(direction),\n reject: ({ direction, reason }) => setLastReject(`${direction} (${reason})`),\n });\n\n const resolvedPosition: DebugPanelPosition = {\n vertical: position.vertical ?? \"bottom\",\n horizontal: position.horizontal ?? \"right\",\n };\n\n const containerStyle = useDefaultStyles\n ? {\n ...baseContainerStyle,\n ...getPositionStyle(resolvedPosition, padding),\n ...(expanded ? expandedStyle : collapsedStyle),\n ...style,\n }\n : style;\n\n return (\n <div className={className} style={containerStyle}>\n <button\n style={useDefaultStyles ? (expanded ? toggleButtonStyle : collapsedToggleStyle) : undefined}\n onClick={() => setExpanded(!expanded)}\n aria-label={expanded ? \"Collapse debug panel\" : \"Expand debug panel\"}\n aria-expanded={expanded}\n >\n {expanded ? \"×\" : \"D\"}\n </button>\n {expanded && (\n <div style={useDefaultStyles ? contentStyle : undefined}>\n <h3 style={useDefaultStyles ? headingStyle : undefined}>Debug</h3>\n <div style={useDefaultStyles ? infoContainerStyle : undefined}>\n <div style={useDefaultStyles ? rowStyle : undefined}>\n <span>Focused:</span>\n <span style={useDefaultStyles ? valueStyle : undefined}>\n {focusedId ?? \"none\"}\n </span>\n </div>\n <div style={useDefaultStyles ? rowStyle : undefined}>\n <span>Previous:</span>\n <span style={useDefaultStyles ? valueStyle : undefined}>\n {previousId}\n </span>\n </div>\n <div style={useDefaultStyles ? rowStyle : undefined}>\n <span>Last move:</span>\n <span style={useDefaultStyles ? valueStyle : undefined}>\n {lastMove}\n </span>\n </div>\n <div style={useDefaultStyles ? rowStyle : undefined}>\n <span>Last reject:</span>\n <span style={useDefaultStyles ? valueStyle : undefined}>\n {lastReject}\n </span>\n </div>\n </div>\n </div>\n )}\n </div>\n );\n});\n"],"names":["DEFAULT_KEY_MAP","up","down","left","right","DEFAULT_ACTION_KEYS","select","back","NavigationContext","createContext","NavigationProvider","children","store","domSource","captureKeyboard","autoSync","initialFocus","keyMap","actionKeys","initialPropsRef","useRef","domRegistry","useMemo","s","source","current","existing","getExistingSource","getElement","console","warn","dom","createDomRegistry","addSource","contextValue","useEffect","raf","loop","sync","requestAnimationFrame","cancelAnimationFrame","resolvedKeyMap","resolvedActionKeys","keyToDirection","Map","addNavKeys","keys","direction","set","k","keyToAction","action","Object","entries","handleKeyDown","e","get","key","preventDefault","move","focusedId","focused","bounds","registry","engine","dispatchEvent","CustomEvent","detail","nodeId","window","addEventListener","removeEventListener","timer","setTimeout","fullId","includes","focus","ids","i","length","id","isEnabled","enabled","isVisible","visible","clearTimeout","useNavigationStore","ctx","useContext","Error","useNavigationContext","ReactSharedInternals","require$$0","reactCompilerRuntime_production","size","reactCompilerRuntime_development","dispatcher","compilerRuntimeModule","require$$1","FocusGroupContext","useFocusable","opts","$","_c","groupCtx","groupId","undefined","disabled","onFocusCallback","onFocus","onBlurCallback","onBlur","navId","prevFocusedRef","t0","onStoreChange","unsubscribe","subscribe","nowFocused","isFocused","t1","useSyncExternalStore","_temp","t2","el","unregister","registerOpts","register","ref","t3","useCustomFocusable","rect","explicitGroupId","getSource","t4","t5","t6","useFocusEvents","typeOrHandlers","handlerOrOptions","options","isMultiEvent","handlers","handlersRef","eventTypesKey","sort","join","cleanups","type","handleEvent","handler","push","cleanup","FocusGroup","props","parent","bb0","nested","restoreHistoryOnEnter","setGroupRestoreOnEnter","clearGroupRestoreOnEnter","boundary","trap","setGroupBoundary","clearGroupBoundary","resetHistoryOnMount","clearGroupHistory","t7","t8","focusOnMountId","focusGroup","t9","value","t10","Focusable","as","className","focusedClassName","focusedStyle","rest","style","Component","mergedClassName","mergedStyle","jsx","CustomFocusable","COLLAPSED_SIZE","baseContainerStyle","position","background","border","borderRadius","fontSize","backdropFilter","zIndex","fontFamily","color","transition","collapsedStyle","width","height","padding","cursor","expandedStyle","minWidth","paddingTop","toggleButtonStyle","top","fontWeight","display","alignItems","justifyContent","collapsedToggleStyle","headingStyle","textTransform","letterSpacing","marginBottom","margin","infoContainerStyle","flexDirection","gap","rowStyle","valueStyle","contentStyle","animation","getPositionStyle","paddingValue","vertical","bottom","horizontal","DebugPanel","memo","useDefaultStyles","defaultExpanded","expanded","setExpanded","useState","setFocusedId","previousId","setPreviousId","lastMove","setLastMove","lastReject","setLastReject","Symbol","for","blur","nodeId_0","reject","direction_0","reason","resolvedPosition","containerStyle","t11","jsxs","t12"],"mappings":"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","x_google_ignoreList":[1,2,3]}
@@ -0,0 +1,87 @@
1
+ import type { ReactNode } from "react";
2
+ import type { NodeId, Rect, NavigationStore } from "@tvuikit/navigation";
3
+ export type CustomFocusableProps = {
4
+ /**
5
+ * The navigation store instance.
6
+ * Required - pass the store directly to avoid cross-reconciler context issues.
7
+ */
8
+ store: NavigationStore;
9
+ /**
10
+ * Source name for this focusable (e.g., "pixi", "three").
11
+ * Default: "custom"
12
+ */
13
+ source?: string;
14
+ /**
15
+ * Unique identifier for this focusable node.
16
+ * In mixed renderer scenarios with CombinedRegistry, this is the local ID
17
+ * (e.g., "tile-1") which gets namespaced automatically (e.g., "pixi:tile-1").
18
+ */
19
+ id: NodeId;
20
+ /**
21
+ * Bounding rectangle in viewport coordinates.
22
+ * Must be in the same coordinate space as DOM elements (client coordinates).
23
+ * For canvas/WebGL elements, add the canvas offset to local coordinates.
24
+ */
25
+ rect: Rect;
26
+ /**
27
+ * Optional focus group id for this node.
28
+ * If not provided, inherits from the nearest FocusGroup context.
29
+ */
30
+ groupId?: string | null;
31
+ /**
32
+ * If true, element cannot receive focus.
33
+ */
34
+ disabled?: boolean;
35
+ /**
36
+ * Called when this element gains focus.
37
+ */
38
+ onFocus?: () => void;
39
+ /**
40
+ * Called when this element loses focus.
41
+ */
42
+ onBlur?: () => void;
43
+ /**
44
+ * Render prop that receives the focused state.
45
+ * Use this to apply visual focus effects to your custom-rendered element.
46
+ */
47
+ children: (focused: boolean) => ReactNode;
48
+ };
49
+ /**
50
+ * Wrapper component for custom (non-DOM) focusable elements.
51
+ *
52
+ * Uses a render prop pattern to provide the focused state to your custom component.
53
+ * Works with any renderer (Pixi.js, Three.js, Canvas 2D, etc.) - you just need to
54
+ * provide the bounding rect in viewport coordinates.
55
+ *
56
+ * @example
57
+ * ```tsx
58
+ * // With Pixi.js
59
+ * <CustomFocusable
60
+ * id="tile-1"
61
+ * rect={{ x: canvasX + localX, y: canvasY + localY, w: 200, h: 150 }}
62
+ * >
63
+ * {(focused) => (
64
+ * <Container scale={focused ? 1.1 : 1}>
65
+ * <Graphics draw={drawTile} />
66
+ * </Container>
67
+ * )}
68
+ * </CustomFocusable>
69
+ *
70
+ * // With callbacks
71
+ * <CustomFocusable
72
+ * id="tile-2"
73
+ * rect={tileRect}
74
+ * onFocus={() => playSound('focus')}
75
+ * onBlur={() => stopPreview()}
76
+ * >
77
+ * {(focused) => <CanvasTile focused={focused} />}
78
+ * </CustomFocusable>
79
+ *
80
+ * // Disabled
81
+ * <CustomFocusable id="tile-3" rect={tileRect} disabled>
82
+ * {(focused) => <CanvasTile focused={focused} />}
83
+ * </CustomFocusable>
84
+ * ```
85
+ */
86
+ export declare function CustomFocusable({ store, source, id, rect, groupId, disabled, onFocus, onBlur, children, }: CustomFocusableProps): ReactNode;
87
+ //# sourceMappingURL=CustomFocusable.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CustomFocusable.d.ts","sourceRoot":"","sources":["../../src/react/CustomFocusable.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAGzE,MAAM,MAAM,oBAAoB,GAAG;IACjC;;;OAGG;IACH,KAAK,EAAE,eAAe,CAAC;IACvB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,EAAE,EAAE,MAAM,CAAC;IACX;;;;OAIG;IACH,IAAI,EAAE,IAAI,CAAC;IACX;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB;;;OAGG;IACH,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,SAAS,CAAC;CAC3C,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,wBAAgB,eAAe,CAAC,EAC9B,KAAK,EACL,MAAM,EACN,EAAE,EACF,IAAI,EACJ,OAAO,EACP,QAAQ,EACR,OAAO,EACP,MAAM,EACN,QAAQ,GACT,EAAE,oBAAoB,GAAG,SAAS,CAalC"}
@@ -0,0 +1,42 @@
1
+ import { type CSSProperties } from "react";
2
+ export type DebugPanelPosition = {
3
+ /** Vertical position (default: "bottom") */
4
+ vertical?: "top" | "bottom";
5
+ /** Horizontal position (default: "right") */
6
+ horizontal?: "left" | "right";
7
+ };
8
+ export type DebugPanelProps = {
9
+ /** Custom class name for the panel container */
10
+ className?: string;
11
+ /** Custom inline styles for the panel container */
12
+ style?: CSSProperties;
13
+ /** Whether to use default inline styles (default: true) */
14
+ useDefaultStyles?: boolean;
15
+ /** Position of the panel (default: { vertical: "bottom", horizontal: "right" }) */
16
+ position?: DebugPanelPosition;
17
+ /** Padding from the edge in pixels or CSS string (default: "1rem") */
18
+ padding?: number | string;
19
+ /** Whether the panel starts expanded (default: false) */
20
+ defaultExpanded?: boolean;
21
+ };
22
+ /**
23
+ * A collapsible debug panel that displays navigation state information.
24
+ * Shows the currently focused element, previously focused element, last move direction, and last rejection.
25
+ *
26
+ * @example
27
+ * ```tsx
28
+ * // Default: collapsed, bottom-right corner
29
+ * <DebugPanel />
30
+ *
31
+ * // Start expanded
32
+ * <DebugPanel defaultExpanded />
33
+ *
34
+ * // Top-left corner with custom padding
35
+ * <DebugPanel position={{ vertical: "top", horizontal: "left" }} padding={20} />
36
+ *
37
+ * // With custom className (disables default styles)
38
+ * <DebugPanel className="my-debug-panel" useDefaultStyles={false} />
39
+ * ```
40
+ */
41
+ export declare const DebugPanel: import("react").NamedExoticComponent<DebugPanelProps>;
42
+ //# sourceMappingURL=DebugPanel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DebugPanel.d.ts","sourceRoot":"","sources":["../../src/react/DebugPanel.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,aAAa,EAAE,MAAM,OAAO,CAAC;AAG3D,MAAM,MAAM,kBAAkB,GAAG;IAC/B,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAC;IAC5B,6CAA6C;IAC7C,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,gDAAgD;IAChD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mDAAmD;IACnD,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,2DAA2D;IAC3D,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,mFAAmF;IACnF,QAAQ,CAAC,EAAE,kBAAkB,CAAC;IAC9B,sEAAsE;IACtE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1B,yDAAyD;IACzD,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,CAAC;AA0GF;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,UAAU,uDA8ErB,CAAC"}
@@ -0,0 +1,51 @@
1
+ import React from "react";
2
+ import type { DirectionBoundary, NavigationStore } from "@tvuikit/navigation";
3
+ export type FocusGroupProps = {
4
+ /**
5
+ * Group id used for focus history + restore-on-enter behavior.
6
+ * Nested groups default to `${parent}/${id}`.
7
+ */
8
+ id: string;
9
+ /**
10
+ * If true, combine nested ids as `${parent}/${id}`.
11
+ * If false, use `id` as-is.
12
+ *
13
+ * Default: true
14
+ */
15
+ nested?: boolean;
16
+ /**
17
+ * Configure restore-on-enter for this group.
18
+ * When true, entering this group will restore focus to the last focused node.
19
+ *
20
+ * Default: true
21
+ */
22
+ restoreHistoryOnEnter?: boolean;
23
+ /**
24
+ * If true, clears this group's focus history on mount.
25
+ * Default: false
26
+ */
27
+ resetHistoryOnMount?: boolean;
28
+ /**
29
+ * Optional: focus a specific node within this group on mount.
30
+ * Uses local ids (the ID without source prefix).
31
+ */
32
+ focusOnMountId?: string;
33
+ /**
34
+ * If true, focus cannot leave this group in any direction.
35
+ * Equivalent to `boundary={{ left: true, right: true, up: true, down: true }}`.
36
+ */
37
+ trap?: boolean;
38
+ /**
39
+ * Block specific directions from leaving the group.
40
+ * For example, `{ right: true }` prevents navigating right out of this group.
41
+ */
42
+ boundary?: DirectionBoundary;
43
+ /**
44
+ * Optional store prop for use outside of NavigationProvider context.
45
+ * Required when using FocusGroup across reconciler boundaries (e.g., in Pixi).
46
+ */
47
+ store?: NavigationStore;
48
+ children: React.ReactNode;
49
+ };
50
+ export declare function FocusGroup(props: FocusGroupProps): import("react/jsx-runtime").JSX.Element;
51
+ //# sourceMappingURL=FocusGroup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FocusGroup.d.ts","sourceRoot":"","sources":["../../src/react/FocusGroup.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAyC,MAAM,OAAO,CAAC;AAG9D,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAE9E,MAAM,MAAM,eAAe,GAAG;IAC5B;;;OAGG;IACH,EAAE,EAAE,MAAM,CAAC;IACX;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;;OAKG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC;;;OAGG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IACf;;;OAGG;IACH,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B;;;OAGG;IACH,KAAK,CAAC,EAAE,eAAe,CAAC;IACxB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B,CAAC;AAEF,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,2CAwDhD"}
@@ -0,0 +1,39 @@
1
+ import React, { type CSSProperties, type ElementType, type ReactNode } from "react";
2
+ type FocusableOwnProps<T extends ElementType = "div"> = {
3
+ /**
4
+ * Unique focusable ID.
5
+ */
6
+ id: string;
7
+ /**
8
+ * Element type to render. Default: "div"
9
+ */
10
+ as?: T;
11
+ /**
12
+ * Class name added when focused.
13
+ */
14
+ focusedClassName?: string;
15
+ /**
16
+ * Style applied when focused.
17
+ */
18
+ focusedStyle?: CSSProperties;
19
+ /**
20
+ * Called when this element gains focus.
21
+ */
22
+ onFocus?: () => void;
23
+ /**
24
+ * Called when this element loses focus.
25
+ */
26
+ onBlur?: () => void;
27
+ /**
28
+ * If true, element cannot receive focus.
29
+ */
30
+ disabled?: boolean;
31
+ /**
32
+ * Children.
33
+ */
34
+ children?: ReactNode;
35
+ };
36
+ type FocusableProps<T extends ElementType = "div"> = FocusableOwnProps<T> & Omit<React.ComponentPropsWithoutRef<T>, keyof FocusableOwnProps<T>>;
37
+ export declare function Focusable<T extends ElementType = "div">({ id, as, focusedClassName, focusedStyle, onFocus, onBlur, disabled, children, className, style, ...rest }: FocusableProps<T>): import("react/jsx-runtime").JSX.Element;
38
+ export {};
39
+ //# sourceMappingURL=Focusable.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Focusable.d.ts","sourceRoot":"","sources":["../../src/react/Focusable.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,KAAK,aAAa,EAAE,KAAK,WAAW,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAGpF,KAAK,iBAAiB,CAAC,CAAC,SAAS,WAAW,GAAG,KAAK,IAAI;IACtD;;OAEG;IACH,EAAE,EAAE,MAAM,CAAC;IACX;;OAEG;IACH,EAAE,CAAC,EAAE,CAAC,CAAC;IACP;;OAEG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;OAEG;IACH,YAAY,CAAC,EAAE,aAAa,CAAC;IAC7B;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;OAEG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB,CAAC;AAEF,KAAK,cAAc,CAAC,CAAC,SAAS,WAAW,GAAG,KAAK,IAAI,iBAAiB,CAAC,CAAC,CAAC,GACvE,IAAI,CAAC,KAAK,CAAC,wBAAwB,CAAC,CAAC,CAAC,EAAE,MAAM,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAC;AAEtE,wBAAgB,SAAS,CAAC,CAAC,SAAS,WAAW,GAAG,KAAK,EAAE,EACvD,EAAE,EACF,EAAE,EACF,gBAAgB,EAChB,YAAY,EACZ,OAAO,EACP,MAAM,EACN,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,KAAK,EACL,GAAG,IAAI,EACR,EAAE,cAAc,CAAC,CAAC,CAAC,2CAmCnB"}
@@ -0,0 +1,166 @@
1
+ import React from "react";
2
+ import type { NavigationStore } from "@tvuikit/navigation";
3
+ import { type DomRegistry } from "@tvuikit/navigation-dom";
4
+ /**
5
+ * Key mapping configuration for navigation.
6
+ * Each direction can have one or more keys that trigger navigation.
7
+ */
8
+ export type KeyMap = {
9
+ /** Key(s) that trigger up navigation. Default: "ArrowUp" */
10
+ up?: string | readonly string[];
11
+ /** Key(s) that trigger down navigation. Default: "ArrowDown" */
12
+ down?: string | readonly string[];
13
+ /** Key(s) that trigger left navigation. Default: "ArrowLeft" */
14
+ left?: string | readonly string[];
15
+ /** Key(s) that trigger right navigation. Default: "ArrowRight" */
16
+ right?: string | readonly string[];
17
+ };
18
+ /**
19
+ * Default key mappings using standard arrow keys.
20
+ */
21
+ export declare const DEFAULT_KEY_MAP: Readonly<Required<KeyMap>>;
22
+ /**
23
+ * Action key mapping configuration.
24
+ * Maps action names to one or more keys that trigger them.
25
+ * When an action key is pressed, an "action" event is dispatched with the action name and focused node.
26
+ */
27
+ export type ActionKeys = {
28
+ [action: string]: string | readonly string[];
29
+ };
30
+ /**
31
+ * Default action key mappings for common TV remote actions.
32
+ */
33
+ export declare const DEFAULT_ACTION_KEYS: Readonly<ActionKeys>;
34
+ /**
35
+ * Context value for the NavigationProvider.
36
+ */
37
+ export type NavigationContextValue = {
38
+ /** The navigation store */
39
+ store: NavigationStore;
40
+ /**
41
+ * The DOM source name used for DOM components.
42
+ * Default: "dom"
43
+ */
44
+ domSource: string;
45
+ /**
46
+ * The DOM registry for registering DOM elements.
47
+ * This is the actual DomRegistry with HTMLElement support.
48
+ */
49
+ domRegistry: DomRegistry;
50
+ };
51
+ /**
52
+ * Context for NavigationProvider.
53
+ * Provides the store and DOM registry to child components.
54
+ */
55
+ export declare const NavigationContext: React.Context<NavigationContextValue | null>;
56
+ export type NavigationProviderProps = {
57
+ children: React.ReactNode;
58
+ /**
59
+ * The navigation store instance.
60
+ */
61
+ store: NavigationStore;
62
+ /**
63
+ * Source name for DOM elements.
64
+ * Default: "dom"
65
+ */
66
+ domSource?: string;
67
+ /**
68
+ * Auto-capture arrow keys for navigation.
69
+ * Default: true
70
+ */
71
+ captureKeyboard?: boolean;
72
+ /**
73
+ * Auto-sync registry on each animation frame.
74
+ * Default: true
75
+ */
76
+ autoSync?: boolean;
77
+ /**
78
+ * Focus this node ID on mount.
79
+ * Can be a local ID (e.g., "btn-1") or full ID (e.g., "dom:btn-1").
80
+ * If it doesn't contain ":", the domSource prefix is added automatically.
81
+ */
82
+ initialFocus?: string;
83
+ /**
84
+ * Custom key mappings for navigation.
85
+ * Allows overriding default arrow keys for devices with custom remotes.
86
+ *
87
+ * @example
88
+ * // Use WASD keys
89
+ * keyMap={{ up: "w", down: "s", left: "a", right: "d" }}
90
+ *
91
+ * @example
92
+ * // Support both arrow keys and WASD
93
+ * keyMap={{
94
+ * up: ["ArrowUp", "w"],
95
+ * down: ["ArrowDown", "s"],
96
+ * left: ["ArrowLeft", "a"],
97
+ * right: ["ArrowRight", "d"]
98
+ * }}
99
+ *
100
+ * @example
101
+ * // TV remote with custom key codes
102
+ * keyMap={{ up: "VK_UP", down: "VK_DOWN", left: "VK_LEFT", right: "VK_RIGHT" }}
103
+ */
104
+ keyMap?: KeyMap;
105
+ /**
106
+ * Action key mappings for non-navigation actions.
107
+ * When an action key is pressed, an "action" event is dispatched.
108
+ * Set to `false` to disable action key handling entirely.
109
+ *
110
+ * Default actions:
111
+ * - "select": Enter, Space
112
+ * - "back": Escape, Backspace
113
+ *
114
+ * @example
115
+ * // Add custom actions
116
+ * actionKeys={{
117
+ * select: ["Enter", " ", "OK"],
118
+ * back: ["Escape", "Back"],
119
+ * info: ["i", "Info"],
120
+ * menu: ["m", "Menu"],
121
+ * }}
122
+ *
123
+ * @example
124
+ * // Subscribe to action events
125
+ * useFocusEvents("action", ({ action, nodeId }) => {
126
+ * if (action === "select" && nodeId === "play-btn") playVideo();
127
+ * });
128
+ */
129
+ actionKeys?: ActionKeys | false;
130
+ };
131
+ /**
132
+ * Provider component for spatial navigation using a NavigationStore.
133
+ *
134
+ * For DOM-only apps, this is all you need:
135
+ * ```tsx
136
+ * const nav = createNavigationStore();
137
+ *
138
+ * <NavigationProvider store={nav}>
139
+ * <Focusable id="btn-1">Click</Focusable>
140
+ * </NavigationProvider>
141
+ * ```
142
+ *
143
+ * For mixed renderer apps (DOM + Pixi/Three.js):
144
+ * ```tsx
145
+ * const nav = createNavigationStore();
146
+ *
147
+ * <NavigationProvider store={nav}>
148
+ * <Focusable id="btn-1">Click</Focusable>
149
+ * <PixiStage>
150
+ * <PixiTile store={nav} id="tile-1" rect={rect} />
151
+ * </PixiStage>
152
+ * </NavigationProvider>
153
+ * ```
154
+ */
155
+ export declare function NavigationProvider({ children, store, domSource, captureKeyboard, autoSync, initialFocus, keyMap, actionKeys, }: NavigationProviderProps): import("react/jsx-runtime").JSX.Element;
156
+ /**
157
+ * Hook to access the NavigationStore from context.
158
+ * Throws if used outside of a NavigationProvider.
159
+ */
160
+ export declare function useNavigationStore(): NavigationStore;
161
+ /**
162
+ * Hook to access the full navigation context.
163
+ * Returns null if not within a provider.
164
+ */
165
+ export declare function useNavigationContext(): NavigationContextValue | null;
166
+ //# sourceMappingURL=NavigationProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NavigationProvider.d.ts","sourceRoot":"","sources":["../../src/react/NavigationProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAgE,MAAM,OAAO,CAAC;AACrF,OAAO,KAAK,EAAa,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtE,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAE9E;;;GAGG;AACH,MAAM,MAAM,MAAM,GAAG;IACnB,4DAA4D;IAC5D,EAAE,CAAC,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC;IAChC,gEAAgE;IAChE,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC;IAClC,gEAAgE;IAChE,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC;IAClC,kEAAkE;IAClE,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC;CACpC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,eAAe,EAAE,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAK7C,CAAC;AAEX;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAAG;IACvB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC;CAC9C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,mBAAmB,EAAE,QAAQ,CAAC,UAAU,CAG3C,CAAC;AAEX;;GAEG;AACH,MAAM,MAAM,sBAAsB,GAAG;IACnC,2BAA2B;IAC3B,KAAK,EAAE,eAAe,CAAC;IACvB;;;OAGG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,WAAW,EAAE,WAAW,CAAC;CAC1B,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,iBAAiB,8CAAqD,CAAC;AAEpF,MAAM,MAAM,uBAAuB,GAAG;IACpC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B;;OAEG;IACH,KAAK,EAAE,eAAe,CAAC;IACvB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,UAAU,CAAC,EAAE,UAAU,GAAG,KAAK,CAAC;CACjC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,QAAQ,EACR,KAAK,EACL,SAAiB,EACjB,eAAsB,EACtB,QAAe,EACf,YAAY,EACZ,MAAM,EACN,UAAU,GACX,EAAE,uBAAuB,2CAgKzB;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,IAAI,eAAe,CAMpD;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,sBAAsB,GAAG,IAAI,CAEpE"}
@@ -0,0 +1,5 @@
1
+ export type FocusGroupContextValue = {
2
+ groupId: string;
3
+ };
4
+ export declare const FocusGroupContext: import("react").Context<FocusGroupContextValue | null>;
5
+ //# sourceMappingURL=focusGroupContext.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"focusGroupContext.d.ts","sourceRoot":"","sources":["../../src/react/focusGroupContext.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,sBAAsB,GAAG;IACnC,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,eAAO,MAAM,iBAAiB,wDAAqD,CAAC"}