### Start Example App Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/CONTRIBUTING.md Build packages and run the demo application using Turbo to verify your setup. This script ensures that changes are reflected instantly. ```bash npm run start ``` -------------------------------- ### Install React Packages Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/docs/guides/installation.md Use these commands to install the core and React-specific packages for React applications. ```bash npm install @noriginmedia/norigin-spatial-navigation-core @noriginmedia/norigin-spatial-navigation-react ``` ```bash yarn add @noriginmedia/norigin-spatial-navigation-core @noriginmedia/norigin-spatial-navigation-react ``` -------------------------------- ### Install Core Package Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/docs/guides/installation.md Install the framework-agnostic core package for projects without React bindings. ```bash npm install @noriginmedia/norigin-spatial-navigation-core ``` -------------------------------- ### Install Legacy Package Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/docs/guides/installation.md Use this package for backwards compatibility when upgrading from older versions. ```bash npm install @noriginmedia/norigin-spatial-navigation ``` -------------------------------- ### Install Project Dependencies Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/CONTRIBUTING.md Install all project dependencies from the repository root using npm. This command handles dependencies for all workspaces within the monorepo. ```bash npm install ``` -------------------------------- ### Changeset File Format Example Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/CLAUDE.md Demonstrates the YAML frontmatter format for creating a changeset, specifying package bumps and a user-facing summary. ```markdown --- '@noriginmedia/norigin-spatial-navigation-core': minor '@noriginmedia/norigin-spatial-navigation-react': minor '@noriginmedia/norigin-spatial-navigation': minor --- Short, user-facing description of the change. ``` -------------------------------- ### Custom Remote Control Setup Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/docs/guides/key-mapping.md Discover non-standard key codes using a listener and apply them to the key map. ```typescript // Step 1: Discover key codes window.addEventListener('keydown', (e) => { console.log('keyCode:', e.keyCode, 'key:', e.key, 'code:', e.code); }); // Step 2: Apply the discovered codes import { setKeyMap } from '@noriginmedia/norigin-spatial-navigation-react'; setKeyMap({ left: [402], // example: custom device left key code right: [403], // example: custom device right key code up: [400], // example: custom device up key code down: [401], // example: custom device down key code enter: [13, 32] // enter or space }); ``` -------------------------------- ### Common Repository Commands Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/CLAUDE.md Lists essential npm commands for managing the monorepo, including installation, building, testing, linting, and formatting. ```bash npm install # Install all workspaces; also sets up the Husky hooks. npm run start # Build packages and run the demo app via Turbo. npm run build # Build every package via Turbo. npm run test # Run the test suite for every package that has tests. npm run lint # ESLint check across all workspaces. npm run prettier # Prettier format check across the repo. npm run prettier:fix # Auto-format files with Prettier. npm run changeset # Generate a changeset for user-visible changes. ``` -------------------------------- ### Language-Driven RTL Toggle Example Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/docs/guides/rtl-support.md This React example demonstrates how to toggle RTL support based on the current application language. It updates both the navigation direction and the document's `dir` attribute for CSS. ```typescript import React, { useEffect } from 'react'; import { init, updateRtl } from '@noriginmedia/norigin-spatial-navigation-core'; // Initialize with the default direction init({ rtl: false }); const RTL_LANGUAGES = ['ar', 'he', 'fa', 'ur']; function App({ language }: { language: string }) { const isRtl = RTL_LANGUAGES.includes(language); useEffect(() => { updateRtl(isRtl); // Also update the document direction for CSS document.documentElement.dir = isRtl ? 'rtl' : 'ltr'; }, [isRtl]); return
{/* your app */}
; } ``` -------------------------------- ### Focus on Conditional Render Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/docs/guides/programmatic-focus.md Example demonstrating how to manage focus when components are conditionally rendered, ensuring focus is set only after the component has mounted. ```APIDOC ## Focus on Conditional Render ### Description When a component is conditionally rendered, wait until it mounts before focusing it. ### Method This is a usage pattern, not a specific API method. It involves using `useEffect` hook in React. ### Parameters None directly for this pattern, but relies on `setFocus` and component lifecycle. ### Request Example ```typescript import { useState, useEffect } from 'react'; import { setFocus } from '@noriginmedia/norigin-spatial-navigation-core'; function Parent() { const [showDetails, setShowDetails] = useState(false); const handleOpenDetails = () => { setShowDetails(true); }; useEffect(() => { if (showDetails) { // Component just mounted, safe to focus now setFocus('DETAILS_PANEL'); } }, [showDetails]); return ( <> ); } function Modal({ onConfirm, onCancel }: ModalProps) { const { ref, focusKey, focusSelf } = useFocusable({ focusKey: 'MODAL', isFocusBoundary: true, // trap focus inside the modal trackChildren: true }); // Focus the modal when it mounts useEffect(() => { focusSelf(); }, [focusSelf]); return (

Are you sure?

); } ``` -------------------------------- ### Generate Changeset for User-Visible Changes Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/CONTRIBUTING.md Initiate the changeset generation process for user-visible changes. This interactive prompt guides you through selecting affected packages, determining the version bump type, and writing a summary for the changelog. ```bash npm run changeset ``` -------------------------------- ### onBlur Callback Example Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/docs/guides/event-callbacks.md Logs the component's props to the console when it loses focus. Useful for debugging or resetting component state. ```typescript const { ref } = useFocusable({ onBlur: (layout, props, details) => { console.log('Lost focus:', props); } }); ``` -------------------------------- ### onEnterRelease Callback Example Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/docs/guides/event-callbacks.md Logs a message to the console when the Enter key is released while the component is focused. ```typescript const { ref } = useFocusable({ onEnterRelease: (props) => { console.log('Enter released'); } }); ``` -------------------------------- ### Container Component Example Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/skills/norigin-spatial-navigation-react/SKILL.md A 'Container' component wraps focusable children and provides its `focusKey` via `FocusContext.Provider`. It uses `useFocusable` with `trackChildren: true` to manage focus within its hierarchy. ```tsx import { useFocusable, FocusContext } from '@noriginmedia/norigin-spatial-navigation-react'; function Menu({ items }: Props) { const { ref, focusKey, hasFocusedChild } = useFocusable({ trackChildren: true, saveLastFocusedChild: true }); return (
{items.map((i) => ( ))}
); } ``` -------------------------------- ### Minimal Leaf Component with useFocusable Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/docs/api-reference/useFocusable.md A basic example of a button component using useFocusable to manage its focus state and apply a visual outline when focused. ```typescript function Button({ label }: { label: string }) { const { ref, focused } = useFocusable(); return ( ); } ``` -------------------------------- ### onEnterPress Callback Example Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/docs/guides/event-callbacks.md Logs the title of the selected item and navigates to its watch page when the Enter key is pressed. Requires 'id' and 'title' in extraProps. ```typescript const { ref } = useFocusable<{ id: string; title: string }>({ extraProps: { id: 'asset-1', title: 'Movie Title' }, onEnterPress: (props, details) => { console.log('Selected:', props.title); navigate(`/watch/${props.id}`); } }); ``` -------------------------------- ### onFocus Callback Example Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/docs/guides/event-callbacks.md Scrolls the parent container to make the focused element visible when it gains focus. Requires a parentRef to the scrollable container. ```typescript const { ref } = useFocusable({ onFocus: (layout, props, details) => { // Scroll the parent container so this element is visible parentRef.current?.scrollTo({ left: layout.x, behavior: 'smooth' }); } }); ``` -------------------------------- ### Using focusSelf to Self-Focus on Mount Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/docs/api-reference/useFocusable.md An example of a Modal component that uses `focusSelf()` within a `useEffect` hook to ensure it gains focus immediately upon mounting. This is useful for modal dialogs or other elements that should be focused by default. ```typescript import { useEffect } from 'react'; import { useFocusable } from '@noriginmedia/norigin-spatial-navigation-react'; function Modal() { const { ref, focusKey, focusSelf } = useFocusable({ focusKey: 'MODAL', isFocusBoundary: true }); useEffect(() => { focusSelf(); }, [focusSelf]); return
{/* modal content */}
; } ``` -------------------------------- ### Handle Arrow Release for Cleanup Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/docs/guides/event-callbacks.md Use onArrowRelease to clear intervals or timers started in onArrowPress. This example stops a timer when the 'right' arrow is released. ```typescript const timerRef = useRef(null); const { ref, focused } = useFocusable({ onArrowPress: (direction) => { if (direction === 'right' && timerRef.current === null) { timerRef.current = setInterval(() => { setProgress((p) => Math.min(p + 5, 100)); }, 100); } return true; }, onArrowRelease: (direction) => { if (direction === 'right') { clearInterval(timerRef.current); timerRef.current = null; } } }); ``` -------------------------------- ### Leaf Component Example Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/skills/norigin-spatial-navigation-react/SKILL.md A 'Leaf' component is an interactive element that receives focus. It uses `useFocusable` to get its `ref` and `focused` state, which should be attached to a DOM element. ```tsx import { useFocusable } from '@noriginmedia/norigin-spatial-navigation-react'; function Button({ label, onPress }: Props) { const { ref, focused } = useFocusable({ onEnterPress: onPress }); return (
{label}
); } ``` -------------------------------- ### Override Default Keyboard Mapping Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/docs/api-reference/SpatialNavigation.md Use `setKeyMap` to define custom key codes or event names for navigation actions. Refer to the Key Mapping guide for comprehensive documentation and examples. ```typescript setKeyMap(keyMap: { left?: number | string | (number | string)[]; right?: number | string | (number | string)[]; up?: number | string | (number | string)[]; down?: number | string | (number | string)[]; enter?: number | string | (number | string)[]; }): void ``` -------------------------------- ### init Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/docs/api-reference/SpatialNavigation.md Initializes the spatial navigation system with the provided configuration options. This function sets up the core behavior and event handling for navigation. ```APIDOC ## init ### Description Initializes the spatial navigation system with the provided configuration options. This function sets up the core behavior and event handling for navigation. ### Method `init(options: SpatialNavigationOptions)` ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body - **debug** (boolean) - Optional - Enable debug logs. - **visualDebug** (boolean) - Optional - Enable visual debug overlay. - **rtl** (boolean) - Optional - Enable right-to-left layout mode. Left and right navigation directions are swapped. Defaults to `false`. - **distanceCalculationMethod** ('center' | 'edges' | 'corners') - Optional - Algorithm used to calculate distance between components. Defaults to `'corners'`. - **customDistanceCalculationFunction** (function) - Optional - Override the secondary-axis distance calculation. - **onUtterText** ((text: string) => void) - Optional - Global callback invoked with a concatenated accessibility label string whenever focus changes. Wire this to your platform's Text-To-Speech engine. - **focusOnPresetKey** (boolean) - Optional - When a component is added whose focus key was already set as the current focus key (via `setFocus` before it mounted), automatically focus it. Set to `false` to disable this implicit refocus on add. Defaults to `true`. - **throttle** (number) - Optional - Throttling delay in milliseconds for navigation events. - **throttleKeypresses** (boolean) - Optional - Whether to throttle keypress events. ### Request Example ```typescript import { init } from '@noriginmedia/norigin-spatial-navigation-core'; init({ debug: false, visualDebug: false, distanceCalculationMethod: 'center', throttle: 150, throttleKeypresses: true, onUtterText: (text) => { // Hand the string off to the platform's Text-To-Speech engine platformTTS.speak(text); } }); ``` ### Response #### Success Response (void) This method does not return a value. #### Response Example N/A ``` -------------------------------- ### init(config?) Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/docs/api-reference/SpatialNavigation.md Initializes the spatial navigation service. This function must be called once before any components mount, typically at the top of your application module. It accepts an optional configuration object to customize navigation behavior. ```APIDOC ## init(config?) ### Description Initializes the spatial navigation service. Must be called once before any components mount, typically at the top of your application module. ### Method `init(config?: { debug?: boolean; visualDebug?: boolean; nativeMode?: boolean; throttle?: number; throttleKeypresses?: boolean; useGetBoundingClientRect?: boolean; shouldFocusDOMNode?: boolean; domNodeFocusOptions?: FocusOptions; shouldUseNativeEvents?: boolean; rtl?: boolean; distanceCalculationMethod?: 'center' | 'edges' | 'corners'; customDistanceCalculationFunction?: (refCorners: Corners, siblingCorners: Corners, isVerticalDirection: boolean, distanceCalculationMethod: string) => number; onUtterText?: (text: string) => void; focusOnPresetKey?: boolean; }): void` ### Parameters #### Config options - **debug** (boolean) - Optional - Log navigation decisions to the browser console. Default: `false` - **visualDebug** (boolean) - Optional - Draw a canvas overlay showing component bounding boxes and navigation paths. Default: `false` - **nativeMode** (boolean) - Optional - **Deprecated.** Disable DOM key event listeners (for React Native). You must drive navigation manually. Default: `false` - **throttle** (number) - Optional - Milliseconds to wait between processing repeated key presses. `0` means no throttle. Default: `0` - **throttleKeypresses** (boolean) - Optional - When `true` and `throttle > 0`, throttle key repeat events while a key is held down. Default: `false` - **useGetBoundingClientRect** (boolean) - Optional - Use `getBoundingClientRect()` instead of `offsetLeft/Top` for layout measurement. Use this when elements are CSS-transformed or scaled. Default: `false` - **shouldFocusDOMNode** (boolean) - Optional - Call `HTMLElement.focus()` on the focused component's DOM node, enabling native browser focus behavior and accessibility. Default: `false` - **domNodeFocusOptions** (FocusOptions) - Optional - Options passed to `HTMLElement.focus()` when `shouldFocusDOMNode` is `true`. Default: `undefined` - **shouldUseNativeEvents** (boolean) - Optional - Do not call `preventDefault()` on key events, allowing the browser to handle them natively as well. Default: `false` - **rtl** (boolean) - Optional - Enable Right-to-Left layout support. Default: `false` - **distanceCalculationMethod** ('center' | 'edges' | 'corners') - Optional - Specifies the method for calculating distances between focusable elements. Default: `'center'` - **customDistanceCalculationFunction** ((refCorners: Corners, siblingCorners: Corners, isVerticalDirection: boolean, distanceCalculationMethod: string) => number) - Optional - A custom function to calculate the distance between focusable elements. - **onUtterText** ((text: string) => void) - Optional - Callback function to handle spoken text. - **focusOnPresetKey** (boolean) - Optional - If true, allows focusing on preset keys. Default: `false` ``` -------------------------------- ### getCurrentFocusKey() Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/docs/guides/programmatic-focus.md Get the focus key of the currently focused component. ```APIDOC ## getCurrentFocusKey() ### Description Get the focus key of the currently focused component. ### Method `getCurrentFocusKey` ### Parameters None ### Response #### Success Response (200) - **focusKey** (string) - The focus key of the currently focused component, or null if no component is focused. ``` -------------------------------- ### Build All Packages Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/CONTRIBUTING.md Build every package in the monorepo using Turbo. This command compiles all workspace packages, preparing them for testing or deployment. ```bash npm run build ``` -------------------------------- ### Wire Up App with Spatial Navigation Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/docs/guides/quick-start.md Initialize the navigation service and render your main application component, including the `ButtonRow` which manages focusable elements. ```typescript import React from 'react'; import { init } from '@noriginmedia/norigin-spatial-navigation-react'; init({ debug: false, visualDebug: false }); function App() { return (
); } export default App; ``` -------------------------------- ### Implement custom distance calculation Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/docs/guides/distance-calculation.md Example of overriding the secondary-axis distance to bias navigation toward the vertical center. ```typescript init({ distanceCalculationMethod: 'corners', customDistanceCalculationFunction: ( refCorners, siblingCorners, isVerticalDirection ) => { // Use the midpoint of each component const refMidX = (refCorners.nearPlumbLinePoint.x + refCorners.farPlumbLinePoint.x) / 2; const sibMidX = (siblingCorners.nearPlumbLinePoint.x + siblingCorners.farPlumbLinePoint.x) / 2; const refMidY = (refCorners.nearStraightLinePoint.y + refCorners.farStraightLinePoint.y) / 2; const sibMidY = (siblingCorners.nearStraightLinePoint.y + siblingCorners.farStraightLinePoint.y) / 2; if (isVerticalDirection) { return Math.abs(refMidX - sibMidX); } return Math.abs(refMidY - sibMidY); } }); ``` -------------------------------- ### Initialize Spatial Navigation Service Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/docs/guides/quick-start.md Call `init` once at the top level of your application before any components mount. This sets up the core navigation service. ```typescript import { init } from '@noriginmedia/norigin-spatial-navigation-core'; init({ debug: false, visualDebug: false }); ``` -------------------------------- ### Initialize with onUtterText Callback Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/docs/guides/accessibility-labels.md Connect the `onUtterText` callback to your platform's Text-To-Speech (TTS) API during the initialization of the spatial navigation library. This ensures that accessibility text is routed correctly. ```typescript import { init } from '@noriginmedia/norigin-spatial-navigation-core'; init({ debug: false, visualDebug: false, onUtterText: (text) => { // Replace with your platform's TTS API platformTTS.speak(text); } }); ``` -------------------------------- ### Component with All Callbacks using useFocusable Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/docs/api-reference/useFocusable.md Demonstrates a Card component utilizing all available callbacks from useFocusable, including onFocus, onBlur, onEnterPress, onEnterRelease, onArrowPress, and onArrowRelease. Ensure the hook and its types are imported. ```typescript import { useFocusable, FocusableComponentLayout, KeyPressDetails, FocusDetails } from '@noriginmedia/norigin-spatial-navigation-react'; interface CardProps { id: string; title: string; } function Card({ id, title }: CardProps) { const { ref, focused } = useFocusable({ focusKey: `card-${id}`, extraProps: { id, title }, onFocus: ( layout: FocusableComponentLayout, props: CardProps, details: FocusDetails ) => { console.log( 'Focused card:', props.title, 'at position', layout.x, layout.y ); }, onBlur: (layout: FocusableComponentLayout, props: CardProps) => { console.log('Blurred card:', props.title); }, onEnterPress: (props: CardProps, details: KeyPressDetails) => { console.log('Enter pressed on:', props.title); }, onEnterRelease: (props: CardProps) => { console.log('Enter released on:', props.title); }, onArrowPress: ( direction: string, props: CardProps, details: KeyPressDetails ) => { console.log('Arrow pressed:', direction, 'on', props.title); return true; // allow default navigation }, onArrowRelease: (direction: string, props: CardProps) => { console.log('Arrow released:', direction, 'on', props.title); } }); return (
{title}
); } ``` -------------------------------- ### Initialize Spatial Navigation Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/skills/norigin-spatial-navigation-react/SKILL.md Call `init` once at application startup to configure navigation behavior. Options include debugging, throttle, and distance calculation method. ```tsx import { init as initNavigation } from '@noriginmedia/norigin-spatial-navigation-core'; initNavigation({ debug: false, visualDebug: false, throttle: 0, distanceCalculationMethod: 'corners' // 'center' | 'edges' | 'corners' }); ``` -------------------------------- ### Get Current Focus Key Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/docs/api-reference/SpatialNavigation.md Retrieve the focus key of the component that is currently focused. This is useful for tracking user interaction or debugging. ```typescript import { getCurrentFocusKey } from '@noriginmedia/norigin-spatial-navigation-react'; const currentKey = getCurrentFocusKey(); console.log('Currently focused:', currentKey); ``` -------------------------------- ### onArrowPress Callback Example Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/docs/guides/event-callbacks.md Customizes spatial navigation behavior. Returns false to prevent default navigation if at the leftmost item and direction is 'left'. ```typescript const { ref } = useFocusable({ onArrowPress: (direction, props, details) => { if (direction === 'left' && currentIndex === 0) { // At the leftmost item, prevent navigation out of the list return false; } return true; } }); ``` -------------------------------- ### Initialize and Set Initial Focus Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/docs/guides/debugging.md Ensure `init()` is called before components mount and `setFocus()` is called after mount, typically within a `useEffect` hook, to establish initial focus. Debug logs can confirm if key events are reaching the library. ```typescript import { init, setFocus, ROOT_FOCUS_KEY } from '@noriginmedia/norigin-spatial-navigation-core'; init({ debug: true }); // In your root component: useEffect(() => { setFocus(ROOT_FOCUS_KEY); }, []); ``` -------------------------------- ### Create a Container and Set Initial Focus Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/docs/guides/quick-start.md Group focusable components within a `FocusContext.Provider`. Use `focusSelf` in `useEffect` to set initial focus when the component mounts. ```typescript import React, { useEffect } from 'react'; import { useFocusable, FocusContext, ROOT_FOCUS_KEY } from '@noriginmedia/norigin-spatial-navigation-react'; function ButtonRow() { const { ref, focusKey, focusSelf } = useFocusable({ focusKey: 'BUTTON_ROW' }); useEffect(() => { focusSelf(); }, []); return (
); } ``` -------------------------------- ### Monorepo Structure Overview Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/CLAUDE.md Illustrates the directory layout of the Turborepo monorepo, highlighting the core engine, React binding, and legacy packages. ```tree apps/ react-demo/ # Example React app for trying changes end-to-end packages/ core/ # @noriginmedia/norigin-spatial-navigation-core (the engine) react/ # @noriginmedia/norigin-spatial-navigation-react (useFocusable hook) legacy/ # @noriginmedia/norigin-spatial-navigation (re-exports core + react) ``` -------------------------------- ### Enable Debugging and Visual Debugging Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/skills/norigin-spatial-navigation-react/SKILL.md Turn on diagnostics in init to help with debugging spatial navigation issues. This is useful for identifying problems with focus behavior and hitboxes. ```tsx init({ debug: true, visualDebug: true }); ``` -------------------------------- ### Run Test Suite Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/CONTRIBUTING.md Execute the test suite for all packages that have tests defined. This command ensures the stability and correctness of the codebase. ```bash npm run test ``` -------------------------------- ### Focus on Mount Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/skills/norigin-spatial-navigation-react/SKILL.md Shows how to automatically focus an element when it mounts using `isFocusBoundary` and `useEffect`. ```tsx // Focus on mount const { focusSelf } = useFocusable({ isFocusBoundary: true }); useEffect(() => { focusSelf(); }, [focusSelf]); ``` -------------------------------- ### Initialize Spatial Navigation Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/docs/api-reference/SpatialNavigation.md Initialize the spatial navigation system with custom configuration options. This includes enabling/disabling debug modes, setting the distance calculation method, and configuring the onUtterText callback for accessibility. ```typescript import { init } from '@noriginmedia/norigin-spatial-navigation-core'; init({ debug: false, visualDebug: false, distanceCalculationMethod: 'center', throttle: 150, throttleKeypresses: true, onUtterText: (text) => { // Hand the string off to the platform's Text-To-Speech engine platformTTS.speak(text); } }); ``` -------------------------------- ### Using accessibilityLabel with useFocusable Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/docs/api-reference/useFocusable.md This example demonstrates how to use the accessibilityLabel prop with the useFocusable hook for both individual focusable components (Asset) and container regions (Row). The label is reactive and updates on focus changes. ```typescript import { useFocusable, FocusContext } from '@noriginmedia/norigin-spatial-navigation-react'; function Asset({ title }: { title: string }) { const { ref, focused } = useFocusable({ accessibilityLabel: title }); return (
{title}
); } function Row({ title }: { title: string }) { const { ref, focusKey } = useFocusable({ accessibilityLabel: title }); return (

{title}

); } ``` -------------------------------- ### Toggle Right-to-Left (RTL) Mode Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/docs/api-reference/SpatialNavigation.md Use `updateRtl` to enable or disable right-to-left navigation mode. This is particularly useful for supporting different application languages. See RTL Support guide for more details. ```typescript import { updateRtl } from '@noriginmedia/norigin-spatial-navigation-react'; updateRtl(true); // enable RTL updateRtl(false); // revert to LTR ``` -------------------------------- ### Initialize spatial navigation with visual debugging Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/docs/guides/distance-calculation.md Enable visual debugging by setting the visualDebug property to true during initialization. This requires the debug property to also be enabled. ```typescript init({ debug: true, visualDebug: true, distanceCalculationMethod: 'corners' }); ``` -------------------------------- ### Check Code Formatting with Prettier Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/CONTRIBUTING.md Run Prettier to check code formatting across the repository. This ensures consistent code style according to the defined Prettier configuration. ```bash npm run prettier ``` -------------------------------- ### resume() Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/docs/api-reference/SpatialNavigation.md Resumes key event handling that was previously suspended by calling the `pause()` function. This restores normal navigation behavior. ```APIDOC ## `resume()` ### Description Resume key event handling after a `pause()`. ### Method ```typescript resume(): void ``` ### Usage Example ```typescript import { resume } from '@noriginmedia/norigin-spatial-navigation-react'; resume(); ``` ``` -------------------------------- ### Save and Restore Focus with `getCurrentFocusKey` and `setFocus` Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/docs/guides/programmatic-focus.md Use `getCurrentFocusKey` to save the current focus before an action that might disrupt focus. Restore focus using `setFocus` when the action is complete, for example, after exiting fullscreen mode. ```typescript import { getCurrentFocusKey, setFocus } from '@noriginmedia/norigin-spatial-navigation-core'; function openFullscreen() { const savedKey = getCurrentFocusKey(); enterFullscreenMode(); // When fullscreen exits, restore focus document.addEventListener( 'fullscreenchange', () => { if (!document.fullscreenElement) { setFocus(savedKey); } }, { once: true } ); } ``` -------------------------------- ### Run ESLint for Code Linting Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/CONTRIBUTING.md Execute ESLint to check code style across all workspaces. This command helps identify and fix potential code quality issues. ```bash npm run lint ``` -------------------------------- ### Saving and Restoring Focus Across Navigation Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/docs/guides/programmatic-focus.md Illustrates a common pattern for TV applications where focus is saved before navigating to a new screen and restored upon returning. ```APIDOC ## Saving and Restoring Focus Across Navigation ### Description A common TV app pattern is to save focus before navigating to a new screen and restore it when returning. ### Method This is a usage pattern, not a specific API method. It involves using `getCurrentFocusKey` and `setFocus`. ### Parameters None directly for this pattern, but relies on `getCurrentFocusKey` and `setFocus`. ### Request Example ```typescript import { getCurrentFocusKey, setFocus } from '@noriginmedia/norigin-spatial-navigation-core'; const focusHistory: string[] = []; function navigateTo(screen: string) { focusHistory.push(getCurrentFocusKey()); showScreen(screen); setFocus(`${screen}-first-item`); } function goBack() { const previousKey = focusHistory.pop(); showPreviousScreen(); if (previousKey) { setFocus(previousKey); } } ``` ### Response This pattern does not have a direct response, but describes a method for managing focus state during navigation. ``` -------------------------------- ### Implement deep linking focus Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/docs/guides/programmatic-focus.md Verify component existence before setting focus to handle deep links or dynamic initial states. ```typescript import { useEffect } from 'react'; import { doesFocusableExist, setFocus } from '@noriginmedia/norigin-spatial-navigation-core'; function App({ initialItemId }: { initialItemId?: string }) { useEffect(() => { if (initialItemId) { const focusKey = `item-${initialItemId}`; if (doesFocusableExist(focusKey)) { setFocus(focusKey); return; } } // Fallback to default focus setFocus('MAIN_MENU'); }, [initialItemId]); return ; } ``` -------------------------------- ### Basic useFocusable Hook Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/skills/norigin-spatial-navigation-react/SKILL.md Demonstrates the basic usage of the `useFocusable` hook for both leaf elements and container elements. ```tsx // Leaf const { ref, focused } = useFocusable(); // Container const { ref, focusKey } = useFocusable();
{children}
``` -------------------------------- ### Save and Restore Focus Across Navigation Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/docs/guides/programmatic-focus.md Implement a common TV app pattern by saving the current focus key before navigating to a new screen and restoring it upon return. This ensures a seamless user experience. ```typescript import { getCurrentFocusKey, setFocus } from '@noriginmedia/norigin-spatial-navigation-core'; const focusHistory: string[] = []; function navigateTo(screen: string) { focusHistory.push(getCurrentFocusKey()); showScreen(screen); setFocus(`${screen}-first-item`); } function goBack() { const previousKey = focusHistory.pop(); showPreviousScreen(); if (previousKey) { setFocus(previousKey); } } ``` -------------------------------- ### setThrottle(options?) Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/docs/api-reference/SpatialNavigation.md Updates throttle settings at runtime without requiring reinitialization. This allows for dynamic adjustment of navigation responsiveness. ```APIDOC ## `setThrottle(options?)` ### Description Update throttle settings at runtime without reinitializing. Useful for adjusting navigation responsiveness dynamically. ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body - **options** (object) - Optional - An object containing throttle settings. - **throttle** (number) - Optional - The delay in milliseconds between navigation movements. A value of 0 removes throttling. - **throttleKeypresses** (boolean) - Optional - Whether to throttle keypress events. Defaults to false. ### Request Example ```json { "options": { "throttle": 200, "throttleKeypresses": true } } ``` ### Response #### Success Response (200) This method does not return a value (`void`). ``` -------------------------------- ### Set initial focus on mount Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/docs/guides/programmatic-focus.md Use useEffect to trigger focus on a specific component when the application mounts. ```typescript import { useEffect } from 'react'; import { setFocus } from '@noriginmedia/norigin-spatial-navigation-core'; function App() { useEffect(() => { setFocus('MAIN_MENU'); }, []); return ; } ``` -------------------------------- ### Enable RTL at Runtime Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/docs/guides/rtl-support.md Use the `updateRtl` function to dynamically switch between RTL and LTR layouts without reinitializing the service. This is useful for user-selectable languages. ```typescript import { updateRtl } from '@noriginmedia/norigin-spatial-navigation-core'; // Switch to RTL updateRtl(true); // Switch back to LTR updateRtl(false); ``` -------------------------------- ### navigateByDirection(direction) Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/docs/guides/programmatic-focus.md Simulate a directional arrow key press from the current focus position. ```APIDOC ## navigateByDirection(direction) ### Description Simulate a directional arrow key press from the current focus position. Useful for gamepad buttons, virtual remote controls, or custom navigation triggers. ### Method `navigateByDirection` ### Parameters #### Path Parameters - **direction** (string) - Required - The direction to navigate ('up', 'down', 'left', 'right'). - **focusDetails** (object) - Optional - Additional details about the focus action. ### Request Example ```typescript import { navigateByDirection } from '@noriginmedia/norigin-spatial-navigation-core'; // Simulate pressing right arrow navigateByDirection('right'); // Can also be used in response to gamepad input window.addEventListener('gamepadconnected', () => { setInterval(() => { const gamepad = navigator.getGamepads()[0]; if (gamepad?.buttons[14]?.pressed) navigateByDirection('left'); if (gamepad?.buttons[15]?.pressed) navigateByDirection('right'); if (gamepad?.buttons[12]?.pressed) navigateByDirection('up'); if (gamepad?.buttons[13]?.pressed) navigateByDirection('down'); }, 50); }); ``` ``` -------------------------------- ### Native Event Handling Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/docs/guides/key-mapping.md Configure initialization to preserve browser default scroll behavior. ```typescript init({ shouldUseNativeEvents: true // browser scroll behavior is preserved }); ``` -------------------------------- ### Supporting Types Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/docs/guides/event-callbacks.md Defines the data structures passed to the event callbacks, including layout, focus, and key press details. ```APIDOC ## Supporting Types ### `FocusableComponentLayout` Passed to `onFocus` and `onBlur`. Contains the component's position and size at the moment focus changed. ```typescript interface FocusableComponentLayout { left: number; // Distance from the left edge of the page top: number; // Distance from the top edge of the page width: number; // Element width height: number; // Element height x: number; // Same as left y: number; // Same as top readonly right: number; // left + width readonly bottom: number; // top + height node: HTMLElement; // The actual DOM node } ``` ### `FocusDetails` Passed to `onFocus` and `onBlur`. Contains optional context about how focus changed. ```typescript interface FocusDetails { event?: Event; // Native keyboard event (if triggered by key press) nativeEvent?: Event; // Same as event (alternative field) [key: string]: any; // Any extra data passed via setFocus(key, focusDetails) } ``` ### `KeyPressDetails` Passed to `onEnterPress` and `onArrowPress`. Contains a map of currently held keys. ```typescript interface KeyPressDetails { pressedKeys: PressedKeys; } type PressedKeys = { [keyCode: string]: number; // key code → timestamp when the key was pressed }; ``` ``` -------------------------------- ### setFocus(focusKey) Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/docs/guides/programmatic-focus.md Move focus to any component by its focus key. Useful for initial focus on mount or deep linking. ```APIDOC ## setFocus(focusKey) ### Description Move focus to any component by its focus key. You must know the target's focus key. ### Method `setFocus` ### Parameters #### Path Parameters - **focusKey** (string) - Required - The unique key of the component to focus. - **focusDetails** (object) - Optional - Additional details about the focus action. ### Request Example ```typescript import { setFocus, ROOT_FOCUS_KEY } from '@noriginmedia/norigin-spatial-navigation-core'; // Move focus to a specific component setFocus('PLAY_BUTTON'); // Boot navigation at app start (routes to first eligible child) setFocus(ROOT_FOCUS_KEY); ``` ### Setting Initial Focus on Mount ```typescript import { useEffect } from 'react'; import { setFocus } from '@noriginmedia/norigin-spatial-navigation-core'; function App() { useEffect(() => { setFocus('MAIN_MENU'); }, []); return ; } ``` ### Providing a Stable `focusKey` For `setFocus` to work, the target component must declare a matching `focusKey`: ```typescript function PlayButton() { const { ref, focused } = useFocusable({ focusKey: 'PLAY_BUTTON' }); return ( ); } // Elsewhere: setFocus('PLAY_BUTTON'); ``` ### Deep Linking When your app loads with a URL that points to a specific item, focus that item once it mounts: ```typescript import { useEffect } from 'react'; import { doesFocusableExist, setFocus } from '@noriginmedia/norigin-spatial-navigation-core'; function App({ initialItemId }: { initialItemId?: string }) { useEffect(() => { if (initialItemId) { const focusKey = `item-${initialItemId}`; if (doesFocusableExist(focusKey)) { setFocus(focusKey); return; } } // Fallback to default focus setFocus('MAIN_MENU'); }, [initialItemId]); return ; } ``` ``` -------------------------------- ### Manual Focus Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/skills/norigin-spatial-navigation-react/SKILL.md Illustrates how to manually set focus to an element using its key. ```tsx // Manual focus if (doesFocusableExist('my-key')) setFocus('my-key'); ``` -------------------------------- ### Enable Visual Debugger Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/docs/guides/debugging.md Activate the visual debugger by setting `visualDebug: true` in the `init` configuration. This renders an overlay showing bounding boxes of focusable components, the currently focused element, and navigation candidate lines. ```typescript init({ debug: false, visualDebug: true }); ``` -------------------------------- ### Norigin Spatial Navigation Core API Imports Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/skills/norigin-spatial-navigation-react/SKILL.md Imports the core functions for spatial navigation from the `@noriginmedia/norigin-spatial-navigation-core` package. These functions provide the fundamental capabilities for managing focus and navigation within an application. ```tsx import { init, setFocus, getCurrentFocusKey, doesFocusableExist, navigateByDirection, pause, resume, setKeyMap, setThrottle, destroy } from '@noriginmedia/norigin-spatial-navigation-core'; ``` -------------------------------- ### Default Key Map Configuration Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/docs/guides/key-mapping.md The default mapping used by the library for directional navigation and selection. ```text left: [37, 'ArrowLeft'] right: [39, 'ArrowRight'] up: [38, 'ArrowUp'] down: [40, 'ArrowDown'] enter: [13, 'Enter'] ``` -------------------------------- ### Enable Native DOM Focus Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/docs/guides/performance.md Configures the library to trigger the native HTMLElement.focus() method, useful for screen reader compatibility and CSS :focus states. ```typescript init({ shouldFocusDOMNode: true }); ``` ```typescript init({ shouldFocusDOMNode: true, domNodeFocusOptions: { preventScroll: true } }); ``` -------------------------------- ### Enable Bounding Client Rect for Accurate Positioning Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/docs/guides/debugging.md Set `useGetBoundingClientRect: true` in `init` to accurately measure component positions when CSS transforms or scaling are involved. This ensures correct measurement by the library. ```typescript init({ useGetBoundingClientRect: true }); ``` -------------------------------- ### Set Initial Focus to Root Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/docs/api-reference/SpatialNavigation.md Initialize spatial navigation by setting the focus to the root of the focus tree using the `ROOT_FOCUS_KEY` constant and the `setFocus` function. ```typescript import { ROOT_FOCUS_KEY, setFocus } from '@noriginmedia/norigin-spatial-navigation-react'; setFocus(ROOT_FOCUS_KEY); ``` -------------------------------- ### doesFocusableExist(focusKey) Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/docs/guides/programmatic-focus.md Check if a component with the given focus key is currently mounted and focusable. ```APIDOC ## doesFocusableExist(focusKey) ### Description Check if a component with the given focus key is currently mounted and focusable. ### Method `doesFocusableExist` ### Parameters #### Path Parameters - **focusKey** (string) - Required - The focus key to check. ### Response #### Success Response (200) - **exists** (boolean) - True if the focusable component exists, false otherwise. ``` -------------------------------- ### Development Logging for onUtterText Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/docs/guides/accessibility-labels.md During development, use `console.log` to observe the accessibility text being passed to the `onUtterText` callback. This is a simpler alternative to integrating with a platform-specific TTS API. ```typescript init({ onUtterText: (text) => { // eslint-disable-next-line no-console console.log('onUtterText', text); } }); ``` -------------------------------- ### Auto-Format Files with Prettier Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/CONTRIBUTING.md Automatically format files using Prettier to fix code style issues. This command applies the Prettier configuration to ensure consistent formatting. ```bash npm run prettier:fix ```