Focus ManagementExploring Focus Management

SpatialNavigationRoot and Focusable Items in TV Apps

The SpatialNavigationRoot component is the foundation of our spatial navigation system in TV apps. It establishes the root of our navigation tree and manages focus within its child components.

Examining SpatialNavigationRoot

Let’s examine the SpatialNavigationRoot implementation in app/(drawer)/index.tsx:

import { SpatialNavigationRoot } from 'react-tv-space-navigation';
import { useIsFocused } from '@react-navigation/native';
import { useMenuContext } from '../../components/MenuContext';
 
export default function IndexScreen() {
  const { isOpen: isMenuOpen } = useMenuContext();
  const isFocused = useIsFocused();
  const isActive = isFocused && !isMenuOpen;
 
  const onDirectionHandledWithoutMovement = useCallback(
    (movement: Direction) => {
      console.log("Direction " + movement);
      if (movement === 'left' && focusedIndex === 0) {
        navigation.dispatch(DrawerActions.openDrawer());
        toggleMenu(true);
      }
    },
    [toggleMenu, focusedIndex, navigation],
  );
 
  return (
    <SpatialNavigationRoot
      isActive={isActive}
      onDirectionHandledWithoutMovement={onDirectionHandledWithoutMovement}>
      <View style={styles.container}>
        {/* Screen content */}
      </View>
    </SpatialNavigationRoot>
  );
}

Key points about SpatialNavigationRoot:

  1. isActive prop:

    • isActive={isActive} determines when this navigation root should be active.
    • isActive is calculated based on whether the screen is focused (isFocused) and the menu is closed (!isMenuOpen).
    • This ensures that spatial navigation only works when the screen is visible and ready for interaction.
  2. onDirectionHandledWithoutMovement prop:

    • This prop takes a callback function that handles directional input when focus can’t move in the specified direction.
    • In our example, it’s used to open the drawer when the user tries to navigate left from the leftmost item.
    • This is crucial for creating a seamless navigation experience, especially at the edges of your interface.
  3. Wrapping the entire screen:

    • SpatialNavigationRoot wraps the entire screen content.
    • This creates a self-contained navigation environment for the screen.

Making Individual Items Focusable

Within the SpatialNavigationRoot, we need to make individual items focusable. This is typically done using the SpatialNavigationFocusableView component.

Let’s look at how this is implemented in our content grid:

const renderScrollableRow = useCallback((title: string, ref: React.RefObject<FlatList>) => {
  const renderItem = useCallback(({ item, index }: { item: CardData; index: number }) => (
    <SpatialNavigationFocusableView
      onSelect={() => { 
        router.push({
          pathname: '/details',
          params: { 
            title: item.title,
            description: item.description,
            headerImage: item.headerImage
          }         
        });
      }}
      onFocus={() => setFocusedIndex(index)}
    >
      {({ isFocused }) => (
        <View style={[styles.highlightThumbnail, isFocused && styles.highlightThumbnailFocused]}>
          <Image source={item.headerImage} style={styles.headerImage} />
          <View style={styles.thumbnailTextContainer}>
            <Text style={styles.thumbnailText}>{item.title}</Text>   
          </View>
        </View>
      )}
    </SpatialNavigationFocusableView>
  ), [router, styles]);
 
  return (
    <View style={styles.highlightsContainer}>
      <Text style={styles.highlightsTitle}>{title}</Text>
      <SpatialNavigationNode>
        <DefaultFocus>
          <SpatialNavigationVirtualizedList 
            data={moviesData} 
            orientation="horizontal" 
            renderItem={renderItem}
            itemSize={scaledPixels(425)}
            numberOfRenderedItems={6}
            numberOfItemsVisibleOnScreen={4}
            onEndReachedThresholdItemsNumber={3}
          />
        </DefaultFocus>
      </SpatialNavigationNode>
    </View>
  );
}, [styles]);

Key points about making items focusable:

  1. SpatialNavigationFocusableView:

    • This component wraps each item in our list, making it focusable.
    • It provides props for handling selection and focus events.
  2. onSelect prop:

    • onSelect defines what happens when the item is selected (typically with the ‘OK’ button on a remote).
    • In this case, it navigates to a details screen with the item’s data.
  3. onFocus prop:

    • onFocus is called when the item gains focus.
    • Here, it updates the focusedIndex state, which can be used for other UI effects or logic.
  4. Render prop pattern:

    • The component uses a render prop that provides an isFocused boolean.
    • This allows us to apply different styles or render different content based on the focus state.
  5. SpatialNavigationVirtualizedList:

    • This is a virtualized list component that works with spatial navigation.
    • It’s optimized for performance when rendering large lists of focusable items.
  6. DefaultFocus:

    • The DefaultFocus component specifies which item should receive focus by default when navigating to this list.

Best Practices for Implementing Focusable Items

  1. Clear Visual Feedback: Always provide clear visual indication of which item is focused. This could be a border, a change in size, or a change in color.

  2. Consistent Behavior: Ensure that all focusable items behave consistently. For example, all items should respond to the ‘OK’ button in a similar manner.

  3. Performance Considerations: For large lists of focusable items, use virtualized lists to maintain smooth performance.

  4. Grouping: Use SpatialNavigationNode to group related focusable items. This can help create more intuitive navigation patterns.

  5. Default Focus: Always specify a sensible default focus for each screen or section. This ensures that users can start navigating immediately upon entering a new area of your app.

  6. Edge Handling: Implement proper handling for navigation at the edges of your focusable areas. This might involve wrapping focus, stopping at edges, or transitioning to different areas of your UI.

Conclusion

Understanding how to implement SpatialNavigationRoot and create focusable items is crucial for developing effective TV interfaces. These components form the foundation of your app’s navigation system, allowing users to easily move through your UI using a remote control.

In the next section, we’ll explore more the focus management techniques and implement if for some custom components.