### 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 (
<>
{showDetails && }
>
);
}
```
### Response
This pattern does not have a direct response, but ensures focus is correctly applied to conditionally rendered components.
```
--------------------------------
### Install Norigin Spatial Navigation Agent
Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/README.md
Use this command to add the Norigin Spatial Navigation agent to your project. This is the primary method for integrating the library's capabilities.
```bash
npx skills add NoriginMedia/Norigin-Spatial-Navigation
```
--------------------------------
### Enable RTL at Startup
Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/docs/guides/rtl-support.md
Pass `rtl: true` to the `init` function to enable right-to-left navigation support from the start. This changes the behavior of arrow key navigation.
```typescript
import { init } from '@noriginmedia/norigin-spatial-navigation-core';
init({
rtl: true
});
```
--------------------------------
### Implement a partial focus boundary
Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/docs/guides/focus-boundaries.md
Example of a sidebar that prevents focus from moving left while allowing movement in other directions.
```typescript
const { ref, focusKey } = useFocusable({
focusKey: 'SIDEBAR',
isFocusBoundary: true,
focusBoundaryDirections: ['left']
});
```
--------------------------------
### Implement a modal dialog with focus trapping
Source: https://github.com/noriginmedia/norigin-spatial-navigation/blob/main/docs/guides/focus-boundaries.md
A complete example showing a modal that traps focus upon mounting and restores it upon unmounting.
```typescript
import React, { useEffect } from 'react';
import {
useFocusable,
FocusContext
} from '@noriginmedia/norigin-spatial-navigation-react';
interface ModalProps {
onConfirm: () => void;
onCancel: () => void;
}
function ConfirmButton({
label,
onPress
}: {
label: string;
onPress: () => void;
}) {
const { ref, focused } = useFocusable({
onEnterPress: () => onPress()
});
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
```