Norigin Spatial Navigation

repository·main·Indexed 19 days ago

https://github.com/noriginmedia/norigin-spatial-navigation

A lightweight, high-performance library for spatial focus management in TV applications and web interfaces using remote control inputs. It features an automatic spatial algorithm that calculates focus movement based on component positioning, eliminating manual focus wiring. Compatible with Samsung Tizen, LG webOS, Hisense Vidaa, Vizio, Titan OS, and React Native TV environments. Available as a framework-agnostic core, React hooks bindings, and a specialized React Native TV layout adapter.

Tokens
39.6K
Snippets
115
Records
155
Agent score
66%

What's inside Norigin Spatial Navigation

  1. Overview of Norigin Spatial Navigation

    main

    Norigin Spatial Navigation is a library designed to bridge the gap between application interfaces and TV remote controls. It provides an automatic spatial algorithm that eliminates the need for manual focus wiring (e.g., manually defining that 'on right press, focus element X'). Instead, you point your components in a direction, and the library calculates the next focusable element based on spatial relationships.

    Key features include:

    • Automatic spatial algorithm: Handles focus movement automatically based on component positioning.
    • TV-optimized: Built specifically for TV environments and actively used on Tizen, webOS, Hisense, Vizio, and Chromium-based STBs.
    • Lightweight: Minimal footprint and low dependency count, making it suitable for constrained TV hardware.
  2. Overview of Norigin Spatial Navigation Core

    main
    Norigin Spatial Navigation Core is a framework-agnostic module designed to provide the underlying logic for spatial navigation in TV applications. It handles the complex calculations and state management required to navigate between focusable elements using directional inputs (like D-pads), independent of any specific UI framework.
  3. Use @noriginmedia/norigin-spatial-navigation-react-native-tvos for React Native TV apps

    main
    The @noriginmedia/norigin-spatial-navigation-react-native-tvos package provides React hooks and a specialized React Native TV layout adapter designed specifically for use with Norigin Spatial Navigation in React Native TV environments. It allows you to leverage the core spatial navigation logic within the React Native TV ecosystem.
  4. What is Spatial Navigation and how does it work?

    main

    Spatial navigation replaces pointer-based interaction (mouse/touch) with directional input (arrow keys, D-pads). Instead of clicking, users navigate by pressing directional keys, and the application moves focus between elements based on their physical position on the screen.

    When a directional key is pressed, the library:

    1. Identifies all registered focusable components.
    2. Filters for components located in the pressed direction relative to the current focus.
    3. Calculates a weighted distance score using primary-axis (direction of press) and secondary-axis (perpendicular) distances.
    4. Selects the component with the lowest score, prioritizing 'adjacent' components (those that overlap significantly on the perpendicular axis) over diagonal ones.
  5. RTL behavior with Focus Hierarchy and Custom Key Maps

    main

    Focus Hierarchy

    RTL mode only changes how arrow key directions are interpreted. It does not affect FocusContext, boundaries, or the focus hierarchy logic. Your component implementation remains the same.

    Custom Key Maps

    If you have defined custom key mappings using setKeyMap, RTL mode is applied on top of those mappings. The library swaps the logical meaning of left and right navigation internally, regardless of which physical keys you have mapped to those directions.

  6. How the Parent-Child Focus Hierarchy works

    main

    Focusable components are organized into a tree structure. A container component can be focusable itself, and its children are nested within it.

    • Context Detection: Children automatically detect their parent via FocusContext.
    • Focus Routing: When focus enters a container, the library routes it to the appropriate child.
    • Focus Memory: Containers can use the saveLastFocusedChild capability to remember which child was last focused and restore that specific child when focus returns to the container.
  7. How focus hierarchy and context work

    main

    Focusable components form a tree that mirrors your React component tree. This hierarchy controls how focus is routed when entering a container, which child is restored when returning to an area, and how hasFocusedChild status propagates upward.

    To establish a parent-child relationship in the focus tree, a parent component must wrap its children in a FocusContext.Provider and pass its own focusKey as the value. Children calling useFocusable will automatically read this context to register themselves under that parent.

    import {
      useFocusable,
      FocusContext
    } from '@noriginmedia/norigin-spatial-navigation-react';
    
    function Row() {
      const { ref, focusKey } = useFocusable({ trackChildren: true });
    
      return (
        // Pass focusKey down so children register under this parent
        <FocusContext.Provider value={focusKey}>
          <div ref={ref} style={{ display: 'flex', gap: '12px' }}>
            <Card title="Item 1" />
            <Card title="Item 2" />
            <Card title="Item 3" />
          </div>
        </FocusContext.Provider>
      );
    }
    
    function Card({ title }: { title: string }) {
      const { ref, focused } = useFocusable();
    
      return (
        <div ref={ref} style={{ outline: focused ? '2px solid white' : 'none' }}>
          {title}
        </div>
      );
    }
  8. Create a container with child tracking

    main

    To manage a group of focusable children, use useFocusable with trackChildren: true and saveLastFocusedChild: true. This allows the container to know if it has a focused child and remember which child was last focused. Use FocusContext.Provider to pass the container's focusKey down to its children so they can be navigated relative to the container.

    Note: Options like trackChildren and saveLastFocusedChild are only read at registration time and are not reactive.

    function Row({ title }: { title: string }) {
      const { ref, focusKey, hasFocusedChild } = useFocusable({
        trackChildren: true,
        saveLastFocusedChild: true
      });
    
      return (
        <FocusContext.Provider value={focusKey}>
          <div ref={ref}>
            <h2>{title}</h2>
            <Card id="1" title="Item 1" />
            <Card id="2" title="Item 2" />
          </div>
        </FocusContext.Provider>
      );
    }
  9. Implement a custom Layout Adapter

    main

    A full layout adapter must implement the following conceptual contract:

    • measureLayout(component): Returns a Promise resolving to layout data: { left, top, right, bottom, width, height, x, y, node }.
    • focusNode(component): Applies native focus (e.g., HTMLElement.focus()) when shouldFocusDOMNode is enabled.
    • blurNode(component): Clears native focus styling (e.g., removing a data-focused attribute).
    • addEventListeners({ keyDown, keyUp }): Wires directional keys to the service.
    • removeEventListeners(): Tears down the listeners.

    If providing a partial object, you only need to supply the methods you wish to override; the rest will be merged from the default adapter.

  10. Using Focus Keys for component identification

    main

    Every focusable component is identified by a unique string called a focus key. The library uses these keys to track the current focus and to enable programmatic focus control.

    • Automatic Keys: If no key is provided, the library generates one (e.g., sn:focusable-item-0).
    • Manual Keys: You can provide a stable, human-readable key using the focusKey option. This is highly recommended for easier programmatic focus management.
    • Root Key: The constant ROOT_FOCUS_KEY (value: 'SN:ROOT') identifies the root of the focus tree.
  11. How accessibility labels are combined and announced

    main

    The library manages how labels are concatenated to prevent redundant announcements during navigation. It tracks which parent containers already contain the currently focused component (parentsHavingFocusedChild).

    The announcement logic follows these steps:

    1. When focus moves, the library walks up the focus tree from the new leaf.
    2. It identifies "newly entered regions"—ancestors that were not already parents of the previous focus.
    3. It collects the accessibilityLabel of these new ancestors (ordered top-down).
    4. It appends the leaf component's accessibilityLabel.
    5. The resulting strings are joined with ', ' and passed to onUtterText.

    Example Behavior: If you move from Home (inside Menu) to Inception (inside Row 1 which is inside Content):

    • First move (HomeInception): Announces "Recommended, Movies, Inception" because Content and Row 1 are new regions.
    • Subsequent move (InceptionInterstellar): Announces "Interstellar" because the user is still within the same parent regions.