{"id":19184145,"url":"https://github.com/noriginmedia/norigin-spatial-navigation","last_synced_at":"2025-04-13T15:06:16.942Z","repository":{"id":37797942,"uuid":"474927865","full_name":"NoriginMedia/Norigin-Spatial-Navigation","owner":"NoriginMedia","description":"React Hooks based Spatial Navigation (Key \u0026 Remote Control Navigation) / Web Browsers, Smart TVs and Connected TVs","archived":false,"fork":false,"pushed_at":"2025-04-09T12:06:54.000Z","size":1613,"stargazers_count":363,"open_issues_count":21,"forks_count":100,"subscribers_count":13,"default_branch":"main","last_synced_at":"2025-04-13T15:06:08.934Z","etag":null,"topics":["key-navigation","react","react-hooks","remote-control","smart-tv","spatial-navigation","tizen","tizen-tv","webos","webos-tv"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/NoriginMedia.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-03-28T09:14:57.000Z","updated_at":"2025-04-10T02:10:12.000Z","dependencies_parsed_at":"2024-06-04T08:56:46.402Z","dependency_job_id":"5bc24789-89a1-42ce-9678-96fe0a2ff08d","html_url":"https://github.com/NoriginMedia/Norigin-Spatial-Navigation","commit_stats":{"total_commits":31,"total_committers":9,"mean_commits":"3.4444444444444446","dds":0.3870967741935484,"last_synced_commit":"3bf35563617dde1060f3e128e12de9ee27fd1107"},"previous_names":[],"tags_count":23,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NoriginMedia%2FNorigin-Spatial-Navigation","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NoriginMedia%2FNorigin-Spatial-Navigation/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NoriginMedia%2FNorigin-Spatial-Navigation/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NoriginMedia%2FNorigin-Spatial-Navigation/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/NoriginMedia","download_url":"https://codeload.github.com/NoriginMedia/Norigin-Spatial-Navigation/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248732485,"owners_count":21152852,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["key-navigation","react","react-hooks","remote-control","smart-tv","spatial-navigation","tizen","tizen-tv","webos","webos-tv"],"created_at":"2024-11-09T11:06:33.851Z","updated_at":"2025-04-13T15:06:16.919Z","avatar_url":"https://github.com/NoriginMedia.png","language":"TypeScript","readme":"# Norigin Spatial Navigation\nNorigin Spatial Navigation is an open-source library that enables navigating between focusable elements built with [ReactJS](https://reactjs.org/) based application software.\nTo be used while developing applications that require key navigation (directional navigation) on Web-browser Apps and other Browser based Smart TVs and Connected TVs.\nOur goal is to make navigation on websites \u0026 apps easy, using React Javascript Framework and React Hooks.\nNavigation can be controlled by your keyboard (browsers) or Remote Controls (Smart TV or Connected TV).\nSoftware developers only need to initialise the service, add the Hook to components that are meant to be focusable and set the initial focus.\nThe 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.\n\n[![npm version](https://badge.fury.io/js/%40noriginmedia%2Fnorigin-spatial-navigation.svg)](https://badge.fury.io/js/%40noriginmedia%2Fnorigin-spatial-navigation)\n\n# Illustrative Demo\nNorigin Spatial Navigation can be used while working with Key Navigation and React JS.\nThis library allows you to navigate across or focus on all navigable components while browsing.\nFor example: hyperlinks, buttons, menu items or any interactible part of the User Interface according to the spatial location on the screen.\n\n![Example](/readme-assets/norigin-spatial-navigation/norigin-spatial-navigation.gif)\n\n[Example Source](https://github.com/NoriginMedia/Norigin-Spatial-Navigation/blob/master/src/App.tsx)\n\n# Supported Devices\nThe Norigin Spatial Navigation library is theoretically intended to work on any web-based platform such as Browsers and Smart TVs.\nFor 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.\nIt can also be used in React Native apps on Android TV and Apple TV, however functionality will be limited.\nThis library is actively used and continuously tested on many devices and updated periodically in the table below:\n\n| Platform | Name |\n|---|---|\n| Web Browsers | Chrome, Firefox, etc. |\n| Smart TVs | [Samsung Tizen](https://developer.tizen.org/?langswitch=en), [LG WebOS](https://webostv.developer.lge.com/), Hisense |\n| Other Connected TV devices | Browser Based settop boxes with Chromium, Ekioh or Webkit browsers |\n| AndroidTV, AppleTV | Only [React Native](https://reactnative.dev/docs/building-for-tv) apps, limited functionality |\n\n# Related Blogs\n1. Use \u0026 benefits of using the Norigin Spatial Navigation library on Smart TVs [here](https://medium.com/p/77ed944d7be7).\n\n# Changelog\nA list of changes for all the versions for the Norigin Spatial Navigation:\n[CHANGELOG.md](https://github.com/NoriginMedia/Norigin-Spatial-Navigation/blob/master/CHANGELOG.md)\n\n# Table of Contents\n* [Installation](#installation)\n* [Usage](#usage)\n* [API](#api)\n* [Technical details and concepts](#technical-details-and-concepts)\n* [Migration from v2](#migration-from-v2-hoc-based-to-v3-hook-based)\n\n# Installation\n```bash\nnpm i @noriginmedia/norigin-spatial-navigation --save\n```\n\n# Usage\n## Initialization\n[Init options](#init-options)\n```jsx\n// Called once somewhere in the root of the app\n\nimport { init } from '@noriginmedia/norigin-spatial-navigation';\n\ninit({\n  // options\n});\n```\n\n## New Distance Calculation Configuration\nStarting from version `2.2.0`, you can configure the method used for distance calculations between focusable components. This can be set during initialization using the `distanceCalculationMethod` option.\n\n### How to use | Available Options\n* `edges`:  Calculates distances using the closest edges of the components.\n* `center`:  Calculates distances using the center points of the components for size-agnostic comparisons. Ideal for non-uniform elements between siblings.\n* `corners`: Calculates distances using the corners of the components, between the nearest corners. This is the default value.\n\n```jsx\nimport { init } from '@noriginmedia/norigin-spatial-navigation';\n\ninit({\n  // options\n  distanceCalculationMethod: 'center', // or 'edges' or 'corners' (default)\n});\n```\n\n## Custom Distance Calculation Function\nIn addition to the predefined distance calculation methods, you can define your own custom distance calculation function. This will override the `getSecondaryAxisDistance` method.\n\nYou can pass your custom distance calculation function during initialization using the customDistanceCalculationFunction option. This function will override the built-in methods.\n\n### How to use | Available Options\nThe custom distance calculation function should follow the DistanceCalculationFunction type signature:\n\n```jsx\ntype DistanceCalculationFunction = (\n  refCorners: Corners,\n  siblingCorners: Corners,\n  isVerticalDirection: boolean,\n  distanceCalculationMethod: DistanceCalculationMethod\n) =\u003e number;\n```\n\n### Example\n```jsx\nimport { init } from '@noriginmedia/norigin-spatial-navigation';\n\n// Define a custom distance calculation function\nconst myCustomDistanceCalculationFunction = (refCorners, siblingCorners, isVerticalDirection, distanceCalculationMethod) =\u003e {\n  // Custom logic for distance calculation\n  const { a: refA, b: refB } = refCorners;\n  const { a: siblingA, b: siblingB } = siblingCorners;\n  const coordinate = isVerticalDirection ? 'x' : 'y';\n\n  const refCoordinateCenter = (refA[coordinate] + refB[coordinate]) / 2;\n  const siblingCoordinateCenter = (siblingA[coordinate] + siblingB[coordinate]) / 2;\n\n  return Math.abs(refCoordinateCenter - siblingCoordinateCenter);\n};\n\n// Initialize with custom distance calculation function\ninit({\n  // options\n  customDistanceCalculationFunction: myCustomDistanceCalculationFunction,\n});\n```\n\n## Making your component focusable\nMost commonly you will have Leaf Focusable components. (See [Tree Hierarchy](#tree-hierarchy-of-focusable-components))\nLeaf component is the one that doesn't have focusable children.\n`ref` is required to link the DOM element with the hook. (to measure its coordinates, size etc.)\n\n```jsx\nimport { useFocusable } from '@noriginmedia/norigin-spatial-navigation';\n\nfunction Button() {\n  const { ref, focused } = useFocusable();\n\n  return (\u003cdiv ref={ref} className={focused ? 'button-focused' : 'button'}\u003e\n    Press me\n  \u003c/div\u003e);\n}\n```\n\n## Wrapping Leaf components with a Focusable Container\nFocusable Container is the one that has other focusable children. (i.e. a scrollable list) (See [Tree Hierarchy](#tree-hierarchy-of-focusable-components))\n`ref` is required to link the DOM element with the hook. (to measure its coordinates, size etc.)\n`FocusContext.Provider` is required in order to provide all children components with the `focusKey` of the Container,\nwhich serves as a Parent Focus Key for them. This way your focusable children components can be deep in the DOM tree\nwhile still being able to know who is their Focusable Parent.\nFocusable Container cannot have `focused` state, but instead propagates focus down to appropriate Child component.\nYou can nest multiple Focusable Containers. When focusing the top level Container, it will propagate focus down until it encounters the first Leaf component.\nI.e. if you set focus to the `Page`, the focus could propagate as following: `Page` -\u003e `ContentWrapper` -\u003e `ContentList` -\u003e `ListItem`.\n\n```jsx\nimport { useFocusable, FocusContext } from '@noriginmedia/norigin-spatial-navigation';\nimport ListItem from './ListItem';\n\nfunction ContentList() {\n  const { ref, focusKey } = useFocusable();\n\n  return (\u003cFocusContext.Provider value={focusKey}\u003e\n    \u003cdiv ref={ref}\u003e\n      \u003cListItem /\u003e\n      \u003cListItem /\u003e\n      \u003cListItem /\u003e\n    \u003c/div\u003e\n  \u003c/FocusContext.Provider\u003e);\n}\n```\n\n## Manually setting the focus\nYou can manually set the focus either to the current component (`focusSelf`), or to any other component providing its `focusKey` to `setFocus`.\nIt is useful when you first open the page, or i.e. when your modal Popup gets mounted.\n\n```jsx\nimport React, { useEffect } from 'react';\nimport { useFocusable, FocusContext, setFocus } from '@noriginmedia/norigin-spatial-navigation';\n\nfunction Popup() {\n  const { ref, focusKey, focusSelf } = useFocusable();\n\n  // Focusing self will focus the Popup, which will pass the focus down to the first Child (ButtonPrimary)\n  // Alternatively you can manually focus any other component by its 'focusKey'\n  useEffect(() =\u003e {\n    focusSelf();\n\n    // alternatively\n    // setFocus('BUTTON_PRIMARY');\n  }, [focusSelf]);\n\n  return (\u003cFocusContext.Provider value={focusKey}\u003e\n    \u003cdiv ref={ref}\u003e\n      \u003cButtonPrimary focusKey={'BUTTON_PRIMARY'} /\u003e\n      \u003cButtonSecondary /\u003e\n    \u003c/div\u003e\n  \u003c/FocusContext.Provider\u003e);\n}\n```\n\n## Tracking children components\nAny Focusable Container can track whether it has any Child focused or not. This feature is disabled by default,\nbut it can be controlled by the `trackChildren` flag passed to the `useFocusable` hook. When enabled, the hook will return\na `hasFocusedChild` flag indicating when a Container component is having focused Child down in the focusable Tree.\nIt is useful for example when you want to style a container differently based on whether it has focused Child or not.\n\n```jsx\nimport { useFocusable, FocusContext } from '@noriginmedia/norigin-spatial-navigation';\nimport MenuItem from './MenuItem';\n\nfunction Menu() {\n  const { ref, focusKey, hasFocusedChild } = useFocusable({trackChildren: true});\n\n  return (\u003cFocusContext.Provider value={focusKey}\u003e\n    \u003cdiv ref={ref} className={hasFocusedChild ? 'menu-expanded' : 'menu-collapsed'}\u003e\n      \u003cMenuItem /\u003e\n      \u003cMenuItem /\u003e\n      \u003cMenuItem /\u003e\n    \u003c/div\u003e\n  \u003c/FocusContext.Provider\u003e);\n}\n```\n\n## Restricting focus to a certain component boundaries\nSometimes 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\na component underneath the Popup. This can be enabled with `isFocusBoundary` flag passed to the `useFocusable` hook.\n\nAdditionally `focusBoundaryDirections` array can be provided to restrict focus movement only in specific directions.\nThat might be useful when defining focus container for menu bar. Please note, that `focusBoundaryDirections` requires\n`isFocusBoundary` to be set to `true`.\n\n```jsx\nimport React, { useEffect } from 'react';\nimport { useFocusable, FocusContext } from '@noriginmedia/norigin-spatial-navigation';\n\nfunction Popup() {\n  const { ref, focusKey, focusSelf } = useFocusable({isFocusBoundary: true, focusBoundaryDirections: ['up', 'down']});\n\n  useEffect(() =\u003e {\n    focusSelf();\n  }, [focusSelf]);\n\n  return (\u003cFocusContext.Provider value={focusKey}\u003e\n    \u003cdiv ref={ref}\u003e\n      \u003cButtonPrimary /\u003e\n      \u003cButtonSecondary /\u003e\n    \u003c/div\u003e\n  \u003c/FocusContext.Provider\u003e);\n}\n```\n\n## Using the library in React Native environment\nIn React Native environment the navigation between focusable (Touchable) components is happening under the hood by the\nnative focusable engine. This library is NOT doing any coordinates measurements or navigation decisions in the native environment.\nBut it can still be used to keep the currently focused element node reference and its focused state, which can be used to\nhighlight components based on the `focused` or `hasFocusedChild` flags.\n\n```jsx\nimport { TouchableOpacity, Text } from 'react-native';\nimport { useFocusable } from '@noriginmedia/norigin-spatial-navigation';\n\nfunction Button() {\n  const { ref, focused, focusSelf } = useFocusable();\n\n  return (\u003cTouchableOpacity\n    ref={ref}\n    onFocus={focusSelf}\n    style={focused ? styles.buttonFocused : styles.button}\n  \u003e\n    \u003cText\u003ePress me\u003c/Text\u003e\n  \u003c/TouchableOpacity\u003e);\n}\n```\nIMPORTANT TO NOTE:\n- [Native mode](#nativemode-boolean-default-false) needs to be **enabled** during initialization when using the library in a React Native environment\n- 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.\n\n# API\n## Top Level exports\n### `SpatialNavigation` (advanced)\nTop level API class singleton that provides access to the Navigation Service and its methods. Not recommended for beginners as it provides access to all internal methods of the core navigation service.\n\n### `init`\n#### Init options\n##### `debug`: boolean (default: false)\nEnables console debugging.\n\n##### `visualDebug`: boolean (default: false)\nEnables visual debugging (all layouts, reference points and siblings reference points are printed on canvases).\n\n##### `nativeMode`: boolean (default: false)\nEnables Native mode. It will **disable** certain web-only functionality:\n- adding window key listeners\n- measuring DOM layout\n- `onFocus` and `onBlur` callbacks don't return coordinates, but still return node ref which can be used to measure layout if needed\n- coordinates calculations when navigating (`smartNavigate` in `SpatialNavigation.ts`)\n- `navigateByDirection`\n- focus propagation down the Tree\n- last focused child feature\n- preferred focus key feature\n\nIn other words, in the Native mode this library **DOES NOT** set the native focus anywhere via the native focus engine.\nNative 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.\nIn 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`.\nManual `setFocus` method is blocked because it will not propagate to the native focus engine and won't do anything.\n\n##### `throttle`: integer (default: 0)\nEnables throttling of the key event listener.\n\n##### `throttleKeypresses`: boolean (default: false)\nWorks only in combination with `throttle` \u003e 0. By default, `throttle` only throttles key down events (i.e. when you press and hold the button).\nWhen this feature is enabled, it will also throttle rapidly fired key presses (rapid \"key down + key up\" events).\n\n##### `useGetBoundingClientRect`: boolean (default: false)\nThis flag enables using `getBoundingClientRect` for measuring element sizes and positions. Default behavior is using DOM `offset` values.\nThe difference is that `getBoundingClientRect` will measure coordinates and sizes post CSS transforms, while `offset` measurement will measure them pre CSS transforms.\nFor example if you have an element with `translateX(50)`, the X position with the default measurement will still be 0, while `getBoundingClientRect` measurement will result in X being 50.\nThe choice depends on how often you are using transforms, and whether you want it to result into coordinates shift or not.\nSometimes you would want element to be *visually* translated, but its coordinates to be calculated as it was before the translation.\n\n##### `shouldFocusDOMNode`: boolean (default: false)\nThis flag makes the underlying _accessible_ DOM node to become focused as well. This is useful for [_accessible_](https://developer.mozilla.org/en-US/docs/Web/Accessibility) web applications.\nNote that it is the developer's responsibility to make the elements accessible! There are [many resources](https://www.google.com/search?q=web+accessibility) online on the subject. [HTML Semantics and Accessibility Cheat Sheet](https://webaim.org/resources/htmlcheatsheet/) is perhaps a good start,\nas it dives directly into the various html tags and how it complies with accessibility. Non-accessible tags like `\u003cdiv\u003e` needs to have the [tabindex](https://developer.mozilla.org/en-US/docs/Web/Accessibility/Keyboard-navigable_JavaScript_widgets) attribute set.\nAlso consider `role` and `aria-label` attributes. But that depends on the application.\n\n[FocusOptions](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus#parameters) can be set using the `domNodeFocusOptions` option documented below.\n\nThis flag is ignored if `nativeMode` is set to `true`.\n\n##### `domNodeFocusOptions`: [FocusOptions](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus#parameters) (default: `{}`)\nThis object is passed to the `focus` method of the underlying _accessible_ DOM node when `shouldFocusDOMNode` is enabled.\n\n### `shouldUseNativeEvents`: boolean (default: false)\nThis flag, when set to true, enables the use of native events for triggering actions, such as clicks or key presses. For instance, the onClick method will be triggered while pressing the enterKey, as well as cliicking the element itself. It is particularly beneficial for enhancing the accessibility of web applications. When shouldUseNativeEvents is active, the underlying accessible DOM node becomes the focus of the event.\n\n##### `rtl`: boolean (default: false)\nThis flag changes focus behavior for layouts in right-to-left (RTL) languages such as Arabic and Hebrew.\n\n### `setKeyMap`\nMethod to set custom key codes (numbers) or key event names (strings) [MDN Docs](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode#non-printable_keys_function_keys). I.e. when the device key codes differ from a standard browser arrow key codes.\n```jsx\nsetKeyMap({\n  left: 9001, // or 'ArrowLeft'\n  up: 9002, // or 'ArrowUp'\n  right: 9003, // or 'ArrowRight'\n  down: 9004, // or 'ArrowDown'\n  enter: 9005 // or 'Enter'\n});\n```\n\nThere 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.\n\n```jsx\nsetKeyMap({\n  left: [205, 214],\n  up: [203, 211],\n  right: [206, 213],\n  down: [204, 212],\n  enter: [195]\n});\n```\n\n### `setThrottle`\nA method for dynamically updating `throttle` and `throttleKeypresses` values. This might be useful if you want to throttle listeners under specific sections or pages.\n\n```jsx\nsetThrottle({\n  throttle: 500,\n  throttleKeypresses: true\n});\n```\n\n### `destroy`\nResets all the settings and the storage of focusable components. Disables the navigation service.\n\n### `doesFocusableExist` (function) `(focusKey: string) =\u003e boolean`\nReturns `true` if Focusable Container with given `focusKey` is created, `false` otherwise.\n\n### `setFocus` (function) `(focusKey: string) =\u003e void`\nMethod to manually set the focus to a component providing its `focusKey`. If `focusKey` is not provided or\nis equal to `ROOT_FOCUS_KEY`, an attempt of focusing one of the force-focusable components is made.\nSee `useFocusable` hook  [`forceFocus`](#forcefocus-default-false) parameter for more details.\n\n### `getCurrentFocusKey` (function) `() =\u003e string`\nReturns the currently focused component's focus key.\n\n### `navigateByDirection` (function) `(direction: string, focusDetails: FocusDetails) =\u003e void`\nMethod to manually navigation to a certain direction. I.e. you can assign a mouse-wheel to navigate Up and Down.\nAlso useful when you have some \"Arrow-like\" UI in the app that is meant to navigate in certain direction when pressed\nwith the mouse or a \"magic remote\" on some TVs.\n\n### `pause` (function)\nPauses all the key event handlers.\n\n### `resume` (function)\nResumes all the key event handlers.\n\n### `updateAllLayouts` (function)\nManually recalculate all the layouts. Rarely used.\n\n### `useFocusable` hook\nThis hook is the main link between the React component (its DOM element) and the navigation service.\nIt is used to register the component in the service, get its `focusKey`, `focused` state etc.\n\n### `updateRtl` (function)\nManually update the focus behavior (whether RTL or LTR)\n\n```jsx\nconst {/* hook output */ } = useFocusable({/* hook params */ });\n```\n\n#### Hook params\n\n##### `focusable` (default: true)\nThis flag indicates that the component can be focused via directional navigation.\nEven if the component is not `focusable`, it still can be focused with the manual `setFocus`.\nThis flag is useful when i.e. you have a Disabled Button that should not be focusable in the disabled state.\n\n##### `saveLastFocusedChild` (default: true)\nBy default, when the focus leaves a Focusable Container, the last focused child of that container is saved.\nSo the next time when you go back to that Container, the last focused child will get the focus.\nIf this feature is disabled, the focus will be always on the first available child of the Container.\n\n##### `trackChildren` (default: false)\nThis flag controls the feature of updating the `hasFocusedChild` flag returned to the hook output.\nSince you don't always need `hasFocusedChild` value, this feature is disabled by default for optimization purposes.\n\n##### `autoRestoreFocus` (default: true)\nBy default, when the currently focused component is unmounted (deleted), navigation service will try to restore the focus\non the nearest available sibling of that component. If this behavior is undesirable, you can disable it by setting this\nflag to `false`.\n\n##### `forceFocus` (default: false)\nThis flag makes the Focusable Container force-focusable. When there's more than one force-focusable component,\nthe closest to the top left viewport corner (0,0) is force-focused. Such containers can be force-focused when there's\nno currently focused component (or `focusKey` points to not existing component) when navigating with arrows.\nAlso, when `focusKey` provided to `setFocus` is not defined or equal to `ROOT_FOCUS_KEY`.\nIn other words, if focus is lost, it can be restored to one of force-focusable components by navigating with arrows\nor by focusing `ROOT_FOCUS_KEY`.\n\n##### `isFocusBoundary` (default: false)\nThis flag makes the Focusable Container keep the focus inside its boundaries. It will only block the focus from leaving\nthe Container via directional navigation. You can still set the focus manually anywhere via `setFocus`.\nUseful when i.e. you have a modal Popup and you don't want the focus to leave it.\n\n##### `focusBoundaryDirections` (optional)\nThis flag sets in which directions focus is blocked from leaving Focusable Container via directional navigation.\nIt accepts an array containing `left`, `right`, `up` and/or `down` values. If not specified, all directions are blocked.\nIt requires `isFocusBoundary` to be enabled to take effect.\n\n##### `focusKey` (optional)\nIf you want your component to have a persistent focus key, it can be set via this property. Otherwise, it will be auto generated.\nUseful when you want to manually set the focus to this component via `setFocus`.\n\n##### `preferredChildFocusKey` (optional)\nUseful when you have a Focusable Container and you want it to propagate the focus to a **specific** child component.\nI.e. when you have a Popup and you want some specific button to be focused instead of the first available.\n\n##### `onEnterPress` (function)\nCallback that is called when the component is focused and Enter key is pressed.\nReceives `extraProps` (see below) and `KeyPressDetails` as arguments.\n\n##### `onEnterRelease` (function)\nCallback that is called when the component is focused and Enter key is released.\nReceives `extraProps` (see below) as argument.\n\n##### `onArrowPress` (function)\nCallback that is called when component is focused and any Arrow key is pressed.\nReceives `direction` (`left`, `right`, `up`, `down`), `extraProps` (see below) and `KeyPressDetails` as arguments.\nThis callback HAS to return `true` if you want to proceed with the default directional navigation behavior, or `false`\nif you want to block the navigation in the specified direction.\n\n##### `onArrowRelease` (function)\nCallback that is called when the component is focused and Arrow key is released.\nReceives `direction` (`left`, `right`, `up`, `down`), `extraProps` (see below) as argument.\n\n##### `onFocus` (function)\nCallback that is called when component gets focus.\nReceives `FocusableComponentLayout`, `extraProps` and `FocusDetails` as arguments.\n\n##### `onBlur` (function)\nCallback that is called when component loses focus.\nReceives `FocusableComponentLayout`, `extraProps` and `FocusDetails` as arguments.\n\n##### `extraProps` (optional)\nAn object that can be passed to the hook in order to be passed back to certain callbacks (see above).\nI.e. you can pass all the `props` of the component here, and get them all back in those callbacks.\n\n#### Hook output\n\n##### `ref` (**required**)\nReference object created by the `useRef` inside the hook. Should be assigned to the DOM element representing a focused\narea for this component. Usually it's a root DOM element of the component.\n\n```jsx\nfunction Button() {\n  const { ref } = useFocusable();\n\n  return (\u003cdiv ref={ref}\u003e\n    Press me\n  \u003c/div\u003e);\n}\n```\n\n##### `focusSelf` (function)\nMethod to set the focus on the current component. I.e. to set the focus to the Page (Container) when it is mounted, or\nthe Popup component when it is displayed.\n\n##### `focused` (boolean)\nFlag that indicates that the current component is focused.\n\n##### `hasFocusedChild` (boolean)\nFlag that indicates that the current component has a focused child somewhere down the Focusable Tree.\nOnly works when `trackChildren` is enabled!\n\n##### `focusKey` (string)\nString that contains the focus key for the component. It is either the same as `focusKey` passed to the hook params,\nor an automatically generated one.\n\n### `FocusContext` (required for Focusable Containers)\nUsed to provide the `focusKey` of the current Focusable Container down the Tree to the next child level. [See Example](#wrapping-leaf-components-with-a-focusable-container)\n\n## Types exported for development\n### `FocusableComponentLayout`\n```ts\ninterface FocusableComponentLayout {\n  left: number; // absolute coordinate on the screen\n  top: number; // absolute coordinate on the screen\n  readonly right: number; // absolute coordinate on the screen\n  readonly bottom: number; // absolute coordinate on the screen\n  width: number;\n  height: number;\n  x: number; // relative to the parent DOM element\n  y: number; // relative to the parent DOM element\n  node: HTMLElement; // or the reference to the native component in React Native\n}\n```\n\n### `KeyPressDetails`\n```ts\ninterface KeyPressDetails {\n  pressedKeys: PressedKeys;\n}\n```\n\n### `PressedKeys`\n```ts\ntype PressedKeys = { [index: string]: number };\n```\n\n### `FocusDetails`\n```ts\ninterface FocusDetails {\n  event?: KeyboardEvent;\n}\n```\n\n## Other Types exported\nThese types are exported, but not necessarily needed for development.\n\n### `KeyMap`\nInterface for the `keyMap` sent to the `setKeyMap` method.\n\n### `UseFocusableConfig`\nInterface for the `useFocusable` params object.\n\n### `UseFocusableResult`\nInterface for the `useFocusable` result object.\n\n# Technical details and concepts\n## Tree Hierarchy of focusable components\nAs mentioned in the [Usage](#usage) section, all focusable components are organized in a Tree structure. Much like a DOM\ntree, the Focusable Tree represents a focusable components' organization in your application. Tree Structure helps to\norganize all the focusable areas in the application, measure them and determine the best paths of navigation between\nthese focusable areas. Without the Tree Structure (assuming all components would be simple Leaf focusable components) it\nwould be extremely hard to measure relative and absolute coordinates of the elements inside the scrolling lists, as well\nas to restrict the focus from jumping outside certain areas. Technically the Focusable Tree structure is achieved by\npassing a focus key of the parent component down via the `FocusContext`. Since React Context can be nested, you can have\nmultiple layers of focusable Containers, each passing their own `focusKey` down the Tree via `FocusContext.Provider` as\nshown in [this example](#wrapping-leaf-components-with-a-focusable-container).\n\n## Navigation Service\n[Navigation Service](https://github.com/NoriginMedia/Norigin-Spatial-Navigation/blob/master/src/SpatialNavigation.ts) is a\n\"brain\" of the library. It is responsible for registering each focusable component in its internal database, storing\nthe node references to measure their coordinates and sizes, and listening to the key press events in order to perform\nthe navigation between these components. The calculation is performed according to the proprietary algorithm, which\nmeasures the coordinate of the current component and all components in the direction of the navigation, and determines the\nbest path to pass the focus to the next component.\n\n# Migration from v2 (HOC based) to v3 (Hook based)\n## Reasons\nThe main reason to ~~finally~~ migrate to Hooks is the deprecation of the `recompose` library that was a backbone for the old\nHOC implementation. As well as the deprecation of the `findDOMNode` API. It's been quite a while since Hooks were first\nintroduced in React, but we were hesitating of migrating to Hooks since it would make the library usage a bit more verbose.\nHowever, recently there has been even more security reasons to migrate away from `recompose`, so we decided that it is time\nto say goodbye to HOC and accept certain drawbacks of the Hook implementation.\nHere are some of the challenges encountered during the migration process:\n\n### Getting node reference\nHOC implementation used a `findDOMNode` API to find a reference to a current DOM element wrapped with the HOC:\n```js\nconst node = SpatialNavigation.isNativeMode() ? this : findDOMNode(this);\n```\nNote that `this` was pointing to an actual component instance even when it was called inside `lifecycle` HOC from `recompose`\nallowing to always find the top-level DOM element, without any additional code required to point to a specific DOM node.\nIt was a nice \"magic\" side effect of the HOC implementation, which is now getting deprecated.\n\nIn the new Hook implementation we are using the recommended `ref` API. It makes a usage of the library a bit more verbose\nsince now you always have to specify which DOM element is considered a \"focusable\" area, because this reference is used\nby the library to calculate the node's coordinates and size. [Example above](#ref-required)\n\n### Passing `parentFocusKey` down the tree\nAnother big challenge was to find a good way of passing the `parentFocusKey` down the Focusable Tree, so every focusable\nchild component would always know its parent component key, in order to enable certain \"tree-based\" features described [here](#tree-hierarchy-of-focusable-components).\nIn the old HOC implementation it was achieved via a combination of `getContext` and `withContext` HOCs. Former one was\nreceiving the `parentFocusKey` from its parent no matter how deep it was in the component tree, and the latter one was\nproviding its own `focusKey` as `parentFocusKey` for its children components.\n\nIn modern React, the only recommended Context API is using Context Providers and Consumers (or `useContext` hook).\nWhile you can easily receive the Context value via `useContext`, the only way to provide the Context down the tree is via\na JSX component `Context.Provider`. This requires some additional code in case you have a Focusable Container component.\nIn order to provide the `parentFocusKey` down the tree, you have to wrap your children components with a `FocusContext.Provider`\nand provide a current `focusKey` as the context value. [Example here](#wrapping-leaf-components-with-a-focusable-container)\n\n## Examples\n### Migrating a [leaf](#making-your-component-focusable) focusable component\n#### HOC Props and Config vs Hook Params\n```jsx\nimport {withFocusable} from '@noriginmedia/norigin-spatial-navigation';\n\n// Component ...\n\nconst FocusableComponent = withFocusable({\n  trackChildren: true,\n  forgetLastFocusedChild: true\n})(Component);\n\nconst ParentComponent = (props) =\u003e (\u003cView\u003e\n  ...\n  \u003cFocusableComponent\n    trackChildren\n    forgetLastFocusedChild\n    focusKey={'FOCUSABLE_COMPONENT'}\n    onEnterPress={props.onItemPress}\n    autoRestoreFocus={false}\n  /\u003e\n  ...\n\u003c/View\u003e);\n```\n\nPlease note that **most** of the features/props could have been passed as either direct JSX `props` to the Focusable Component\nor as an config object passed to the `withFocusable` HOC. It provided certain level of flexibility, while also adding some\nconfusion as to what takes priority if you pass the same option to both the `prop` and a HOC config.\n\nIn the new Hook implementation options can only be passed as a [Hook Params](#hook-params):\n\n```jsx\nconst {/* hook output */ } = useFocusable({\n  trackChildren: true,\n  saveLastFocusedChild: false,\n  onEnterPress: () =\u003e {},\n  focusKey: 'FOCUSABLE_COMPONENT'\n});\n```\n\n#### HOC props passed to the wrapped component vs Hook output values\nHOC was enhancing the wrapped component with certain new `props` such as `focused` etc.:\n```jsx\nimport {withFocusable} from '@noriginmedia/norigin-spatial-navigation';\n\nconst Component = ({focused}) =\u003e (\u003cView\u003e\n  \u003cView style={focused ? styles.focusedStyle : styles.defaultStyle} /\u003e\n\u003c/View\u003e);\n\nconst FocusableComponent = withFocusable()(Component);\n```\n\nHook will provide all these values as the return object of the hook:\n\n```jsx\nconst { focused, focusSelf, ref, ...etc } = useFocusable({/* hook params */ });\n```\n\nThe only additional step when migrating from HOC to Hook (apart from changing `withFocusable` to `useFocusable` implementation)\nis to link the DOM element with the `ref` from the Hook as seen in this [example](#making-your-component-focusable).\nWhile it requires a bit of extra code compared to the HOC version, it also provides a certain level of flexibility if\nyou want to make only a certain part of your UI component to act as a \"focusable\" area.\n\nPlease also note that some params and output values has been renamed. [CHANGELOG](#changelog)\n\n### Migrating a [container](#wrapping-leaf-components-with-a-focusable-container) focusable component\nIn the old HOC implementation there was no additional requirements for the Focusable Container to provide its own `focusKey`\ndown the Tree as a `parentFocusKey` for its children components.\nIn the Hook implementation it is required to wrap your children components with a `FocusContext.Provider` as seen in\nthis [example](#wrapping-leaf-components-with-a-focusable-container).\n\n# Development\n```bash\nnpm i\nnpm start\n```\n\n# Contributing\nPlease follow the [Contribution Guide](https://github.com/NoriginMedia/Norigin-Spatial-Navigation/blob/master/CONTRIBUTING.md)\n\n# License\n**MIT Licensed**\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnoriginmedia%2Fnorigin-spatial-navigation","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnoriginmedia%2Fnorigin-spatial-navigation","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnoriginmedia%2Fnorigin-spatial-navigation/lists"}