React Hooks based Spatial Navigation (Key & Remote Control Navigation) / Web Browsers, Smart TVs and Connected TVs

Overview

Norigin Spatial Navigation

Norigin Spatial Navigation is an open-source library that enables navigating between focusable elements built with ReactJS based application software. To be used while developing applications that require key navigation (directional navigation) on Web-browser Apps and other Browser based Smart TVs and Connected TVs. Our goal is to make navigation on websites & apps easy, using React Javascript Framework and React Hooks. Navigation can be controlled by your keyboard (browsers) or Remote Controls (Smart TV or Connected TV). Software developers only need to initialise the service, add the Hook to components that are meant to be focusable and set the initial focus. The Spatial Navigation library will automatically determine which components to focus next while navigating with the directional keys. We keep the library light, simple, and with minimal third-party dependencies.

npm version

Illustrative Demo

Norigin Spatial Navigation can be used while working with Key Navigation and React JS. This library allows you to navigate across or focus on all navigable components while browsing. For example: hyperlinks, buttons, menu items or any interactible part of the User Interface according to the spatial location on the screen.

Example

Example Source

Supported Devices

The Norigin Spatial Navigation library is theoretically intended to work on any web-based platform such as Browsers and Smart TVs. For as long as the UI/UX is built with the React Framework, it works on the Samsung Tizen TVs, LG WebOS TVs, Hisense Vidaa TVs and a range of other Connected TVs. It can also be used in React Native apps on Android TV and Apple TV, however functionality will be limited. This library is actively used and continuously tested on many devices and updated periodically in the table below:

Platform Name
Web Browsers Chrome, Firefox, etc.
Smart TVs Samsung Tizen, LG WebOS, Hisense
Other Connected TV devices Browser Based settop boxes with Chromium, Ekioh or Webkit browsers
AndroidTV, AppleTV Only React Native apps, limited functionality

Related Blogs

  1. Use & benefits of using the Norigin Spatial Navigation library on Smart TVs here.

Changelog

A list of changes for all the versions for the Norigin Spatial Navigation: CHANGELOG.md

Table of Contents

Installation

npm i @noriginmedia/norigin-spatial-navigation --save

Usage

Initialization

Init options

// Called once somewhere in the root of the app

import { init } from '@noriginmedia/norigin-spatial-navigation';

init({
  // options
});

Making your component focusable

Most commonly you will have Leaf Focusable components. (See Tree Hierarchy) Leaf component is the one that doesn't have focusable children. ref is required to link the DOM element with the hook. (to measure its coordinates, size etc.)

import { useFocusable } from '@noriginmedia/norigin-spatial-navigation';

function Button() {
  const { ref, focused } = useFocusable();

  return (<div ref={ref} className={focused ? 'button-focused' : 'button'}>
    Press me
  </div>);
}

Wrapping Leaf components with a Focusable Container

Focusable Container is the one that has other focusable children. (i.e. a scrollable list) (See Tree Hierarchy) ref is required to link the DOM element with the hook. (to measure its coordinates, size etc.) FocusContext.Provider is required in order to provide all children components with the focusKey of the Container, which serves as a Parent Focus Key for them. This way your focusable children components can be deep in the DOM tree while still being able to know who is their Focusable Parent. Focusable Container cannot have focused state, but instead propagates focus down to appropriate Child component. You can nest multiple Focusable Containers. When focusing the top level Container, it will propagate focus down until it encounters the first Leaf component. I.e. if you set focus to the Page, the focus could propagate as following: Page -> ContentWrapper -> ContentList -> ListItem.

import { useFocusable, FocusContext } from '@noriginmedia/norigin-spatial-navigation';
import ListItem from './ListItem';

function ContentList() {
  const { ref, focusKey } = useFocusable();

  return (<FocusContext.Provider value={focusKey}>
    <div ref={ref}>
      <ListItem />
      <ListItem />
      <ListItem />
    </div>
  </FocusContext.Provider>);
}

Manually setting the focus

You can manually set the focus either to the current component (focusSelf), or to any other component providing its focusKey to setFocus. It is useful when you first open the page, or i.e. when your modal Popup gets mounted.

import React, { useEffect } from 'react';
import { useFocusable, FocusContext } from '@noriginmedia/norigin-spatial-navigation';

function Popup() {
  const { ref, focusKey, focusSelf, setFocus } = useFocusable();

  // Focusing self will focus the Popup, which will pass the focus down to the first Child (ButtonPrimary)
  // Alternatively you can manually focus any other component by its 'focusKey'
  useEffect(() => {
    focusSelf();

    // alternatively
    // setFocus('BUTTON_PRIMARY');
  }, [focusSelf]);

  return (<FocusContext.Provider value={focusKey}>
    <div ref={ref}>
      <ButtonPrimary focusKey={'BUTTON_PRIMARY'} />
      <ButtonSecondary />
    </div>
  </FocusContext.Provider>);
}

Tracking children components

Any Focusable Container can track whether it has any Child focused or not. This feature is disabled by default, but it can be controlled by the trackChildren flag passed to the useFocusable hook. When enabled, the hook will return a hasFocusedChild flag indicating when a Container component is having focused Child down in the focusable Tree. It is useful for example when you want to style a container differently based on whether it has focused Child or not.

import { useFocusable, FocusContext } from '@noriginmedia/norigin-spatial-navigation';
import MenuItem from './MenuItem';

function Menu() {
  const { ref, focusKey, hasFocusedChild } = useFocusable({trackChildren: true});

  return (<FocusContext.Provider value={focusKey}>
    <div ref={ref} className={hasFocusedChild ? 'menu-expanded' : 'menu-collapsed'}>
      <MenuItem />
      <MenuItem />
      <MenuItem />
    </div>
  </FocusContext.Provider>);
}

Restricting focus to a certain component boundaries

Sometimes you don't want the focus to leave your component, for example when displaying a Popup, you don't want the focus to go to a component underneath the Popup. This can be enabled with isFocusBoundary flag passed to the useFocusable hook.

import React, { useEffect } from 'react';
import { useFocusable, FocusContext } from '@noriginmedia/norigin-spatial-navigation';

function Popup() {
  const { ref, focusKey, focusSelf } = useFocusable({isFocusBoundary: true});

  useEffect(() => {
    focusSelf();
  }, [focusSelf]);

  return (<FocusContext.Provider value={focusKey}>
    <div ref={ref}>
      <ButtonPrimary />
      <ButtonSecondary />
    </div>
  </FocusContext.Provider>);
}

Using the library in React Native environment

In React Native environment the navigation between focusable (Touchable) components is happening under the hood by the native focusable engine. This library is NOT doing any coordinates measurements or navigation decisions in the native environment. But it can still be used to keep the currently focused element node reference and its focused state, which can be used to highlight components based on the focused or hasFocusedChild flags.

import { TouchableOpacity, Text } from 'react-native';
import { useFocusable } from '@noriginmedia/norigin-spatial-navigation';

function Button() {
  const { ref, focused, focusSelf } = useFocusable();

  return (<TouchableOpacity
    ref={ref}
    onFocus={focusSelf}
    style={focused ? styles.buttonFocused : styles.button}
  >
    <Text>Press me</Text>
  </TouchableOpacity>);
}

IMPORTANT TO NOTE:

  • Native mode needs to be enabled during initialization when using the library in a React Native environment
  • In order to "sync" the focus events coming from the native focus engine to the hook the onFocus callback needs to be linked with the focusSelf method. This way the hook will know that the component became focused and will set the focused flag accordingly.

API

Top Level exports

init

Init options

debug: boolean (default: false)

Enables console debugging.

visualDebug: boolean (default: false)

Enables visual debugging (all layouts, reference points and siblings reference points are printed on canvases).

nativeMode: boolean (default: false)

Enables Native mode. It will disable certain web-only functionality:

  • adding window key listeners
  • measuring DOM layout
  • onFocus and onBlur callbacks don't return coordinates, but still return node ref which can be used to measure layout if needed
  • coordinates calculations when navigating (smartNavigate in SpatialNavigation.ts)
  • navigateByDirection
  • focus propagation down the Tree
  • last focused child feature
  • preferred focus key feature

In other words, in the Native mode this library DOES NOT set the native focus anywhere via the native focus engine. Native mode should be only used to keep the Tree of focusable components and to set the focused and hasFocusedChild flags to enable styling for focused components and containers. In Native mode you can only call focusSelf in the component that gets native focus (via onFocus callback of the Touchable components) to flag it as focused. Manual setFocus method is blocked because it will not propagate to the native focus engine and won't do anything.

throttle: integer (default: 0)

Enables throttling of the key event listener.

throttleKeypresses: boolean (default: false)

Works only in combination with throttle > 0. By default, throttle only throttles key down events (i.e. when you press and hold the button). When this feature is enabled, it will also throttle rapidly fired key presses (rapid "key down + key up" events).

setKeyMap

Method to set custom key codes. I.e. when the device key codes differ from a standard browser arrow key codes.

setKeyMap({
  left: 9001,
  up: 9002,
  right: 9003,
  down: 9004,
  enter: 9005
});

There is also support for mapping multiple key codes to a single direction. This can be useful when working with gamepads that utilize a joystick and a directional pad and you want to make use of both.

setKeyMap({
  left: [205, 214],
  up: [203, 211],
  right: [206, 213],
  down: [204, 212],
  enter: [195]
});

setThrottle

A method for dynamically updating throttle and throttleKeypresses values. This might be useful if you want to throttle listeners under specific sections or pages.

setThrottle({
  throttle: 500,
  throttleKeypresses: true
});

destroy

Resets all the settings and the storage of focusable components. Disables the navigation service.

useFocusable hook

This hook is the main link between the React component (its DOM element) and the navigation service. It is used to register the component in the service, get its focusKey, focused state etc.

const {/* hook output */ } = useFocusable({/* hook params */ });

Hook params

focusable (default: true)

This flag indicates that the component can be focused via directional navigation. Even if the component is not focusable, it still can be focused with the manual setFocus. This flag is useful when i.e. you have a Disabled Button that should not be focusable in the disabled state.

saveLastFocusedChild (default: true)

By default, when the focus leaves a Focusable Container, the last focused child of that container is saved. So the next time when you go back to that Container, the last focused child will get the focus. If this feature is disabled, the focus will be always on the first available child of the Container.

trackChildren (default: false)

This flag controls the feature of updating the hasFocusedChild flag returned to the hook output. Since you don't always need hasFocusedChild value, this feature is disabled by default for optimization purposes.

autoRestoreFocus (default: true)

By default, when the currently focused component is unmounted (deleted), navigation service will try to restore the focus on the nearest available sibling of that component. If this behavior is undesirable, you can disable it by setting this flag to false.

isFocusBoundary (default: false)

This flag makes the Focusable Container keep the focus inside its boundaries. It will only block the focus from leaving the Container via directional navigation. You can still set the focus manually anywhere via setFocus. Useful when i.e. you have a modal Popup and you don't want the focus to leave it.

focusKey (optional)

If you want your component to have a persistent focus key, it can be set via this property. Otherwise, it will be auto generated. Useful when you want to manually set the focus to this component via setFocus.

preferredChildFocusKey (optional)

Useful when you have a Focusable Container and you want it to propagate the focus to a specific child component. I.e. when you have a Popup and you want some specific button to be focused instead of the first available.

onEnterPress (function)

Callback that is called when the component is focused and Enter key is pressed. Receives extraProps (see below) and KeyPressDetails as arguments.

onEnterRelease (function)

Callback that is called when the component is focused and Enter key is released. Receives extraProps (see below) as argument.

onArrowPress (function)

Callback that is called when component is focused and any Arrow key is pressed. Receives direction (left, right, up, down), extraProps (see below) and KeyPressDetails as arguments. This callback HAS to return true if you want to proceed with the default directional navigation behavior, or false if you want to block the navigation in the specified direction.

onFocus (function)

Callback that is called when component gets focus. Receives FocusableComponentLayout, extraProps and FocusDetails as arguments.

onBlur (function)

Callback that is called when component loses focus. Receives FocusableComponentLayout, extraProps and FocusDetails as arguments.

extraProps (optional)

An object that can be passed to the hook in order to be passed back to certain callbacks (see above). I.e. you can pass all the props of the component here, and get them all back in those callbacks.

Hook output

ref (required)

Reference object created by the useRef inside the hook. Should be assigned to the DOM element representing a focused area for this component. Usually it's a root DOM element of the component.

function Button() {
  const { ref } = useFocusable();

  return (<div ref={ref}>
    Press me
  </div>);
}
focusSelf (function)

Method to set the focus on the current component. I.e. to set the focus to the Page (Container) when it is mounted, or the Popup component when it is displayed.

setFocus (function) (focusKey: string) => void

Method to manually set the focus to a component providing its focusKey.

focused (boolean)

Flag that indicates that the current component is focused.

hasFocusedChild (boolean)

Flag that indicates that the current component has a focused child somewhere down the Focusable Tree. Only works when trackChildren is enabled!

focusKey (string)

String that contains the focus key for the component. It is either the same as focusKey passed to the hook params, or an automatically generated one.

getCurrentFocusKey (function) () => string

Returns the currently focused component's focus key.

navigateByDirection (function) (direction: string, focusDetails: FocusDetails) => void

Method to manually navigation to a certain direction. I.e. you can assign a mouse-wheel to navigate Up and Down. Also useful when you have some "Arrow-like" UI in the app that is meant to navigate in certain direction when pressed with the mouse or a "magic remote" on some TVs.

pause (function)

Pauses all the key event handlers.

resume (function)

Resumes all the key event handlers.

updateAllLayouts (function)

Manually recalculate all the layouts. Rarely used.

FocusContext (required for Focusable Containers)

Used to provide the focusKey of the current Focusable Container down the Tree to the next child level. See Example

Types exported for development

FocusableComponentLayout

interface FocusableComponentLayout {
  left: number; // absolute coordinate on the screen
  top: number; // absolute coordinate on the screen
  width: number;
  height: number;
  x: number; // relative to the parent DOM element
  y: number; // relative to the parent DOM element
  node: HTMLElement; // or the reference to the native component in React Native
}

KeyPressDetails

interface KeyPressDetails {
  pressedKeys: PressedKeys;
}

PressedKeys

type PressedKeys = { [index: string]: number };

FocusDetails

interface FocusDetails {
  event?: KeyboardEvent;
}

Other Types exported

These types are exported, but not necessarily needed for development.

KeyMap

Interface for the keyMap sent to the setKeyMap method.

UseFocusableConfig

Interface for the useFocusable params object.

UseFocusableResult

Interface for the useFocusable result object.

Technical details and concepts

Tree Hierarchy of focusable components

As mentioned in the Usage section, all focusable components are organized in a Tree structure. Much like a DOM tree, the Focusable Tree represents a focusable components' organization in your application. Tree Structure helps to organize all the focusable areas in the application, measure them and determine the best paths of navigation between these focusable areas. Without the Tree Structure (assuming all components would be simple Leaf focusable components) it would be extremely hard to measure relative and absolute coordinates of the elements inside the scrolling lists, as well as to restrict the focus from jumping outside certain areas. Technically the Focusable Tree structure is achieved by passing a focus key of the parent component down via the FocusContext. Since React Context can be nested, you can have multiple layers of focusable Containers, each passing their own focusKey down the Tree via FocusContext.Provider as shown in this example.

Navigation Service

Navigation Service is a "brain" of the library. It is responsible for registering each focusable component in its internal database, storing the node references to measure their coordinates and sizes, and listening to the key press events in order to perform the navigation between these components. The calculation is performed according to the proprietary algorithm, which measures the coordinate of the current component and all components in the direction of the navigation, and determines the best path to pass the focus to the next component.

Migration from v2 (HOC based) to v3 (Hook based)

Reasons

The main reason to finally migrate to Hooks is the deprecation of the recompose library that was a backbone for the old HOC implementation. As well as the deprecation of the findDOMNode API. It's been quite a while since Hooks were first introduced in React, but we were hesitating of migrating to Hooks since it would make the library usage a bit more verbose. However, recently there has been even more security reasons to migrate away from recompose, so we decided that it is time to say goodbye to HOC and accept certain drawbacks of the Hook implementation. Here are some of the challenges encountered during the migration process:

Getting node reference

HOC implementation used a findDOMNode API to find a reference to a current DOM element wrapped with the HOC:

const node = SpatialNavigation.isNativeMode() ? this : findDOMNode(this);

Note that this was pointing to an actual component instance even when it was called inside lifecycle HOC from recompose allowing to always find the top-level DOM element, without any additional code required to point to a specific DOM node. It was a nice "magic" side effect of the HOC implementation, which is now getting deprecated.

In the new Hook implementation we are using the recommended ref API. It makes a usage of the library a bit more verbose since now you always have to specify which DOM element is considered a "focusable" area, because this reference is used by the library to calculate the node's coordinates and size. Example above

Passing parentFocusKey down the tree

Another big challenge was to find a good way of passing the parentFocusKey down the Focusable Tree, so every focusable child component would always know its parent component key, in order to enable certain "tree-based" features described here. In the old HOC implementation it was achieved via a combination of getContext and withContext HOCs. Former one was receiving the parentFocusKey from its parent no matter how deep it was in the component tree, and the latter one was providing its own focusKey as parentFocusKey for its children components.

In modern React, the only recommended Context API is using Context Providers and Consumers (or useContext hook). While you can easily receive the Context value via useContext, the only way to provide the Context down the tree is via a JSX component Context.Provider. This requires some additional code in case you have a Focusable Container component. In order to provide the parentFocusKey down the tree, you have to wrap your children components with a FocusContext.Provider and provide a current focusKey as the context value. Example here

Examples

Migrating a leaf focusable component

HOC Props and Config vs Hook Params

import {withFocusable} from '@noriginmedia/norigin-spatial-navigation';

// Component ...

const FocusableComponent = withFocusable({
  trackChildren: true,
  forgetLastFocusedChild: true
})(Component);

const ParentComponent = (props) => (<View>
  ...
  <FocusableComponent
    trackChildren
    forgetLastFocusedChild
    focusKey={'FOCUSABLE_COMPONENT'}
    onEnterPress={props.onItemPress}
    autoRestoreFocus={false}
  />
  ...
</View>);

Please note that most of the features/props could have been passed as either direct JSX props to the Focusable Component or as an config object passed to the withFocusable HOC. It provided certain level of flexibility, while also adding some confusion as to what takes priority if you pass the same option to both the prop and a HOC config.

In the new Hook implementation options can only be passed as a Hook Params:

const {/* hook output */ } = useFocusable({
  trackChildren: true,
  saveLastFocusedChild: false,
  onEnterPress: () => {},
  focusKey: 'FOCUSABLE_COMPONENT'
});

HOC props passed to the wrapped component vs Hook output values

HOC was enhancing the wrapped component with certain new props such as focused etc.:

import {withFocusable} from '@noriginmedia/norigin-spatial-navigation';

const Component = ({focused}) => (<View>
  <View style={focused ? styles.focusedStyle : styles.defaultStyle} />
</View>);

const FocusableComponent = withFocusable()(Component);

Hook will provide all these values as the return object of the hook:

const { focused, focusSelf, ref, ...etc } = useFocusable({/* hook params */ });

The only additional step when migrating from HOC to Hook (apart from changing withFocusable to useFocusable implementation) is to link the DOM element with the ref from the Hook as seen in this example. While it requires a bit of extra code compared to the HOC version, it also provides a certain level of flexibility if you want to make only a certain part of your UI component to act as a "focusable" area.

Please also note that some params and output values has been renamed. CHANGELOG

Migrating a container focusable component

In the old HOC implementation there was no additional requirements for the Focusable Container to provide its own focusKey down the Tree as a parentFocusKey for its children components. In the Hook implementation it is required to wrap your children components with a FocusContext.Provider as seen in this example.

Development

npm i
npm start

Contributing

Please follow the Contribution Guide

License

MIT Licensed

Comments
  • React native example

    React native example

    Hello, could you give a sample react native tv app example because i have tried following the docs but i just cant fully understand how to use this library

    question 
    opened by coucoseth 19
  • focusSelf causes everything to be focused on Chrome

    focusSelf causes everything to be focused on Chrome

    Describe the bug Calling focusSelf() causes all focusable components to be in focusable state until arrow buttons are pressed

    To Reproduce see this example: https://codesandbox.io/s/spatial-nav-example-66wiu9

    Expected behavior First focusable child should be in focused state only.

    help wanted 
    opened by tieugioh 10
  • Get prev focusKey on onFocus handler

    Get prev focusKey on onFocus handler

    Hi all.

    Could you please help how I can know prev focus key?

    Fox example I have input filed and button to set focus to this field and if I press this button I should automatically set focus to input (not focus from library) but if use general arrow navigation I don't set focus to input.

    Input component:

    const Input = () => {
      const inputRef = useRef<HTMLInputElement>(null!);
      const { ref } = useFocusable({
        focusKey: 'SEARCH_INPUT',   <<---
        onFocus() {
          // todo: check if focus was set from button
          // inputRef.current.focus();
        },
      });
    
      return (
        <div ref={ref} ...>
          <input ref={inputRef} ... />
        </div>
      );
    };
    

    Button component:

    const Button = () => {
      const { setFocus } = useFocusable({ focusable: false });
    
      return (
        <div
          onClick={() => setFocus('SEARCH_INPUT')}
        >set focus to search input</div>
      )
    };
    

    Maybe we can pass additional params to setFocus method and get it in onFocus or something like that?

    Right now it's not valid: image

    Thank you.

    question 
    opened by siarheipashkevich 9
  • Get current focusKey on beforePopState

    Get current focusKey on beforePopState

    I would like to store the current focusKey in session storage before navigating to a new page (during the beforePopState event), so that I can setFocus() to that focusKey when returning to the page. I know that the debug mode is spitting this info out to the console, but how can i get it programmatically?

    Thanks for such an awesome library!

    enhancement 
    opened by babysteps 9
  • onFocus callback always returns y:0

    onFocus callback always returns y:0

    Hey all! I've been migrating an app over to your new hooks library, and I've ran into a slight issue - the onFocus callback seems to return y:0 regardless of the elements position. The Dom and component structure are identical to pre-migration, the only difference being the provider. The structure itself is a bunch of focusable rows on top of each other that all share the same parent component. CSS top is calculating correctly, just not y.

    Anyway, my question is - are we now calculating x and y differently from the previous HOC library? Has anyone else ran into this problem?

    Cheers

    opened by maidbarion 8
  • Navigating over the elements be skipping the elements

    Navigating over the elements be skipping the elements

    Hello all,

    I have integrated this library with my react application. I have designed a grid view where I have added the Focus Container and also added each element from the grid as a Leaf Focusable component. I have added all the required configurations as well. But the behavior is skipping the focus over the third element when the focus moved from left to right and the same when the navigation started from right to left it skipping the second element. (Considering the grid has 4 columns)

    Please find the code snippet as below,

    const RibbonRowContent = function ({ ribbonContentList, onAssetPress, onAssetFocus }: RibbonRowContentProperties) { const { ref, focusKey } = useFocusable({}); return ( <FocusContext.Provider value={focusKey}> <ContentRowScrollingWrapper ref={ref}> {ribbonContentList.map((element: any) => ( <RibbonAsset key={element.videoId} assetDetails={element} onEnterPress={onAssetPress} onFocus={onAssetFocus} /> ))} </ContentRowScrollingWrapper> </FocusContext.Provider> ); };

    Please let me know how I can resolve this issue, since similar kind of behaviour getting for the Navigation Drawer Menu integrated.

    opened by kartikKarekarGSLAB 7
  • fix(build): add global object

    fix(build): add global object "this" to webpack and missing eslint packages

    • Fixed the "Reference error: self is not defined" caused by wepback setting global object to "self" instead of "this", which is not working with typescript. This error occurs when you add the package to your repository and try to build in it.
    • Also adding this missing eslint packages needed to commit on this repository.
    opened by pickymtr 7
  • Fixes #36 - Babel and CoreJS used instead of ts-loader to transpile and polyfill

    Fixes #36 - Babel and CoreJS used instead of ts-loader to transpile and polyfill

    @predikament & @asgvard do you want to check this? Main change is moving from ts-loader to Babel+CoreJS that provides a clean and polyfilled ES5 build, this closes #36

    bug 
    opened by MFlyer 6
  • Nodes not focusable anymore after hot reload

    Nodes not focusable anymore after hot reload

    Describe the bug In a Vite.js live reload environment, focusable elements are not focusable anymore after hot reloading the component.

    To Reproduce Steps to reproduce the behavior:

    1. Init a simple Vite.js project
    2. Add the Norigin Spatial Navigation library
    3. Add a simple component that uses useFocusable
    4. Run the project
    5. Make a change to the component

    Expected behavior After a hot reload, the component should still be focusable.

    Additional context Not sure if it is Vite.js specific. Perhaps it can be reproduced in Webpack as well.

    I've cloned and linked the repo to do some debugging and found that the useEffectOnce is the culprit. After a hot reload, the useEffect is triggered which removes the node from the registry. But the effectCalled ref isn't cleared, so the node never gets added again.

    bug 
    opened by ChristiaanScheermeijer 6
  • accept `focusDetails` argument in `focusSelf`

    accept `focusDetails` argument in `focusSelf`

    An application scrolls to an element in onFocus handler. onFocus can be called either automatically (for example, on page load) or in response of user action (keyboard event or, in case of webOS, mouse event). For the sake of convenience, cursor hover on webOS is handled by calling focusSelf

    Because of UX, scroll on automatic focus has behavior: 'instant', on user action — behavior: 'smooth'. But because there's no way to pass MouseEvent to focusSelf, there's no way to distinguish "mouse hover focus" from "page load focus". Accepting focusDetails argument in focusSelf would allow application to pass MouseEvent when it's necessary to do that and then do something like this:

    const onFocus = (layout, extraProps, focusDetails) => {
      scrollIntoViewIfNeeded(layout.node, {
        behavior:
          focusDetails?.event instanceof KeyboardEvent ||
          focusDetails?.event instanceof MouseEvent
            ? 'smooth'
            : 'instant',
      });
    };
    

    This change isn't critical, since application can call setFocus(focusKey, { event }) instead of focusSelf()/focusSelf({ event }), but a shorter option is convenient to have

    enhancement 
    opened by zemlanin 5
  • Back button listener

    Back button listener

    Hey there! Great library!

    Just one question, how would you implement the back button listener?

    I was looking at what the lib provides but didn't find any mention of what could we do to handle the back navigation.

    question 
    opened by xrofa 5
  • How to replace `LastFocusedChild` for specific container?

    How to replace `LastFocusedChild` for specific container?

    Hi all.

    Could you please suggest hot to replace LastFocusedChild for specific container when I back to this specific container.

    For example: I've the container with children and leave this container on second child, but when I'm going to return to this component I want that the focus will be on fourth child. How I can implement this?

    I don't have direct access to the SpatialNavigationService for example.

    opened by siarheipashkevich 0
  • [Question] Accessibility and focus

    [Question] Accessibility and focus

    Hello! Thank you for this library!

    Is there a recommended way to handle a11y? Meaning, for each element that is focused, the TV text-to-speech when activated to read a given label.

    At the moment I am working on a project, and the usual approach is to place the role and aria-label properties on each element. When the component is focused, then the TV reads the label to the user.

    Do you have a standard way of handling this outside of what I described? Do you plan to? Maybe even a way to enforce it, as some TV platforms require this functionality.

    Would this work?

    import { useFocusable } from '@noriginmedia/norigin-spatial-navigation';
    
    function MovieTile({ title, director }) {
      const { ref, focused } = useFocusable();
    
      return (
        <div ref={ref} role="" arial-label={`${title}, ${director}`} className={focused ? 'movie-focused' : 'movie'}>
          <img src="example" />
          <div>{ title }</div>
          <div>{ director }</div>
        </div>
      );
    }
    
    opened by dasilvaluis 0
  • How to handle

    How to handle "Back" button functionality

    We've recently started using Norigin Spatial Navigation on our React-based Smart TV application and we love it! However, one integral part of navigating smart TV apps with the remote is the option to press the "back" button. I didn't see it in the documentation, nor do I see many people talking about it. Now I'm questioning my sanity for thinking this should be a feature of the library.

    Anyways, how would you suggest we go about implementing this on our end? Would the best solution be a global listener for the back keycode and then setting the focus manually based on that? Any help would be appreciated, thanks!

    opened by emilehay 7
  • Back button handler

    Back button handler

    Hi NoriginMedia, me and my team are using your library in our react TV app and we made a small improvement in order to handle the back button.

    The Back event could be handled defining the new onBackPress callback in the useFocusable configuration: this callback could be used in both component and Focusable Context.

    As README.md explains, that callback is called when the component is focused and Back key is pressed. If no callback was provided to the focused component, the event will go up through the Focusable Tree as long as one defined callback was found or the top was reached up.

    The back key is set to 27, like the ESC key; of course it could be set with setKeyMap method.

    A small example of the behaviour was provided on App.tsx demo page: when the back event was fired while the focus is on a Content, the focus jumps to the side menu; if the back event was fired while the focus is on the "Asset 3" element, an alert will show up.

    I hope it could be usefull.

    --Luca

    opened by lcaprini 12
  • Navigation between rails not happening as expected

    Navigation between rails not happening as expected

    Describe the bug We are using the Norigin Media Spatial Navigation library to handle remote navigation on CTVs. In this context, we wanted to check why we are seeing the below navigation behaviour

    Goal

    Achieve similar or close to Apple’s TV Navigation behavior

    To Reproduce Steps to reproduce the behaviour:

    1. Go to https://github.com/krackjack234/Norigin-Spatial-Navigation/tree/issues/53/Down-Navigation-Not-Working. The code is forked from main branch of norigin media repo where we have made few modifications
    2. Build and run the app
    3. Click on the DPAD right button of the keyboard to bring focus from the menu to the first rail (TV Channels)
    4. Press right to go to the 4th card (Asset 4) in the first rail
    5. Press down

    Expected behaviour The focus should come to the second card of the 2nd rail (Series) based on the distance of the last focused card (i.e 4th card of first rail i.e.TV Channels rail)

    Actual behaviour On pressing down, the focus goes to the 4th card of the 3rd rail (Recommended) completely skipping the 2nd rail.

    Demo Code Changes

    Remove FocusContext.Provider for each of the ContentRow so that all the rows are tested for nearest sibling in a single FocusContext.Provider

    Proposal

    Expose ADJACENT_SLICE_THRESHOLD , ADJACENT_SLICE_WEIGHT , DIAGONAL_SLICE_WEIGHT or similar so we can configure and achieve different navigation behaviors

    Screenshots

    Spatial-Navigation-Demo

    Additional context We wanted to check below

    1. Why we are seeing this behaviour and how can we override this to ensure the focus goes to the 2nd rail?
    2. Currently, we have put all the rails in a single FocusContextProvider so that we can match the Apple tv navigation behaviour. How can we achieve the Apple TV navigation behaviour where on pressing Dpad Up button, the focus always goes to the closest card in the left direction instead of in the right direction?

    Thanks!

    enhancement 
    opened by krackjack234 6
Releases(v1.1.5)
  • v1.1.5(Dec 20, 2022)

    Added

    • Add setThrottle to dynamically change throttle time. In relation to issue #45

    Changed

    • Remove event listeners for unbindEventHandlers regardless of throttle value
    Source code(tar.gz)
    Source code(zip)
  • v1.1.4(Dec 15, 2022)

  • v1.1.3(Dec 9, 2022)

  • v1.1.2(Dec 7, 2022)

    Added focusDetails argument in focusSelf (and also in setFocus).

    Also, I modified the focusDetails interface to include either an event, nativeEvent or any key-value combination required. This object will be received at the onFocus callback.

    Source code(tar.gz)
    Source code(zip)
  • v1.1.1(Sep 13, 2022)

    Previous versions still had some arrow functions in the bundle coming from the Webpack itself, even though TS config had ES5 as target. Now the bundle is ES5 compatible.

    Source code(tar.gz)
    Source code(zip)
  • v1.1.0(Sep 8, 2022)

    [1.1.0]

    Added

    • Support for React v18 StrictMode. Added useEffectOnce to avoid multiple effect runs on mount that was breaking the generation of the focusKeys.

    Fixed

    • Few TS errors that somehow not being checked when the app is built and published ¯_(ツ)_/¯.
    Source code(tar.gz)
    Source code(zip)
  • v1.0.6(Sep 7, 2022)

    [1.0.6]

    Added

    • Function (getCurrentFocusKey) for retrieving the currently focused component's focus key (#30)
    • Support for passing multiple key codes per direction in setKeyMap (#29)
    Source code(tar.gz)
    Source code(zip)
  • v1.0.5(Jun 16, 2022)

    Added

    • Added generic P type for the props passed to useFocusable hook that is available in all callbacks that bounce props back.

    Changed

    • Changed all lodash imports to cherry-picked ones to avoid the whole lodash lib to be bundled in the project.
    Source code(tar.gz)
    Source code(zip)
  • v1.0.4(May 12, 2022)

    [1.0.4]

    Added

    • Eslint dependencies required by eslint-config-airbnb

    Fixed

    • Fixed issue in Node environment - Webpack global object is now this instead of self
    Source code(tar.gz)
    Source code(zip)
  • v1.0.2(Apr 25, 2022)

  • v1.0.1(Mar 28, 2022)

Owner
Norigin Media
Norigin Media
A Vite React app using Metamask wallet, connected to the block-chain, interaction with smart contracts

It is a Vite React app using Metamask wallet, connected to the block-chain, interaction with smart contracts. Using Giphy API and styled with Tailwind CSS.

Yuriy Chamkoriyski 2 Feb 25, 2022
Decentralized-Public-Key-Infrastructure - A decentralized Public Key Infrastructure using Truffle and React Bootstrap

Decentralized-Public-Key-Infrastructure A decentralized Public Key Infrastructur

M. Adil Fayyaz 5 Jul 16, 2022
An NFT Marketplace running on ethereum, binance smart chain, polygon, avalanche, fantom, optimism and arbitrum powered by 0x smart contracts. Made in React/Next JS, MUI and Typescript.

An NFT Marketplace running on ethereum, binance smart chain, polygon, avalanche, fantom, optimism and arbitrum powered by 0x smart contracts. Made in React/Next JS, MUI and Typescript.

DexKit 35 Dec 29, 2022
Solvent Connect Wallet - A react web app that help its users to see information about the connected wallet and make airdrop or send solana

Solvent Connect Wallet - A react web app that help its users to see information about the connected wallet and make airdrop or send solana

Bhagya Mudgal 0 Aug 1, 2022
The Remote Keyboard Tutoring System is a web-based system that can be attached to any (electronic) keyboard synthesizer through a MIDI connector

The Remote Keyboard Tutoring System is a web-based system that can be attached to any (electronic) keyboard synthesizer through a MIDI connector. Once our system is connected to the keyboard, the user can interactively learn, play or teach in combination with the web application that we provide.

Department of Computer Engineering, University of Peradeniya 3 Nov 15, 2022
With Expense Tracker you control your expenses and take control of your finances

?? Tecnologias Esse projeto foi desenvolvido com as seguintes tecnologias: React

carlos viana 1 Dec 31, 2021
Key-racing is a simple and easy-to-use keyboard trainer that help you master ten-finger typing skills

Key-racing is a simple and easy-to-use keyboard trainer that help you master ten-finger typing skills

null 0 Dec 25, 2021
Flagsmith is an open source, fully featured, Feature Flag and Remote Config service

Flagsmith is an open source, fully featured, Feature Flag and Remote Config service. Use our hosted API, deploy to your own private cloud, or run on-premise.

Flagsmith 2.1k Jan 8, 2023
ReviewZon is a ML based tool that helps users make smart decisions by analyzing amazon reviews of a product.

ReviewZon ReviewZon is a ML based tool that helps users make smart decisions by analyzing amazon reviews of a product. Objective Our project’s aim is

Sankalp Mukim 3 Jan 7, 2022
React Context driven role-based-access-control package

React Context driven role-based-access-control package

Nurbek Zhussip 3 Dec 22, 2022
Smart-face-detector - A face detector application made with React JS, Node JS, Express JS, and PostgreSQL.

Getting Started with Create React App This project was bootstrapped with Create React App. Available Scripts In the project directory, you can run: np

Ken Tandrian 1 Sep 24, 2022
A fork of Ethereum Boilerplate and demostrates how to build upgradeable smart contracts by showcasing it in the form of a simple NFT minter dAp

This repository contains tutorial on building Upgradable Smart Contracts and showcasing it with a simple NFT Minter dApp with React and Moralis.

Yoseph Kurnia Soenggoro 45 Nov 16, 2022
A web application for financial control using React JS with Typescript

A web application for financial control using React JS with Typescript

Jailson Santos 1 Apr 4, 2022
An interesting project that can control iRobot Create 2 from a React.js web app, a native mobile app, a wireless Xbox controller, or even a smartwatch

An interesting project that can control iRobot Create 2 from a React.js web app, a native mobile app, a wireless Xbox controller, or even a smartwatch

null 2 Sep 21, 2022
React Truffle Box - Start using smart contracts from a react app

This box comes with everything you need to start using smart contracts from a react app. This is as barebones as it gets, so nothing stands in your way.

✨ Mario ECS ✨ 2 Feb 15, 2022
A Dapp built with react which uses web3.js to interact with solidity smart contracts hosted on the rinkeby blockchain.

A Dapp built with react which uses web3.js to interact with solidity smart contracts hosted on the rinkeby blockchain

Tahmeed Tarek 13 Dec 22, 2022
A smart city community centered application Built With JavaScript React JS Redux

A smart city community centered application I am building for the Hackers In Residence under the Nkwashi program. Implementing solutions and automation for potential future problems in the city.

Blessed Jason Mwanza 4 Apr 25, 2022
A Checkers game built to demonstrate Mina Snapps Smart Contracts at work

Mina CheckerSnapp A Checkers game built to demonstrate Mina Snapps Smart Contracts at work. Game works according to the rules other than the following

Vim 5 Dec 12, 2022