@hps1978/tv-lrud-spatial
v0.0.17
Published
Spatial navigation library for react-native-web-tv (forked from @bbc/tv-lrud-spatial)
Readme
LRUD Spatial - React Native Web TV Edition (Community Fork)
Thank you to the original BBC maintainers and contributors for providing a great foundation for this work.
This is a fork of the BBC's @bbc/tv-lrud-spatial project, maintained by Harpreet Singh.
- Original author: BBC
- Fork maintainer: Harpreet Singh [email protected]
- License: Apache-2.0 (see LICENSE)
This fork documents and implements spatial navigation behavior aligned with React Native Web TV focus semantics.
It is implemented with the sole purpose of being used with the RNW TV branch. If anyone finds it useful for other purposes, that is great too.
Status
Experimental.
RNW TV Mapping
TVFocusGuideView to DOM/LRUD mapping
| RNW TV concept | LRUD marker in this fork | Notes |
|---|---|---|
| container={true} / tv-focusable container | .lrud-container + tabindex="0" | Participates as a focusable container candidate |
| focusable={false} | .lrud-ignore | Excludes node and descendants from candidate search. |
| autoFocus={true} | data-autofocus="true" | Enables focus memory behavior via data-focus |
| destinations={[ref1, ref2, ...]} | data-destinations="id1 id2 ..." | First valid id has priority on container entry |
| trapFocusUp | data-block-exit="up" | Blocks leaving container upward |
| trapFocusDown | data-block-exit="down" | Blocks leaving container downward |
| trapFocusLeft | data-block-exit="left" | Blocks leaving container to the left |
| trapFocusRight | data-block-exit="right" | Blocks leaving container to the right |
data-block-exit supports multiple directions as a space-separated list.
Fork-Specific Definitions
Container types
- Focusable container:
.lrud-containerwithtabindex="0". - Non-focusable container:
.lrud-containerwithout focusable semantics, still usable for block-exit scoping.
Screen-level scoping with lrud-screen-<name>
- If the currently focused element is inside an element whose id starts with
lrud-screen-, LRUD applies screen/header-aware scope resolution. - This is intended for app-driven screen boundaries (for example React Navigation style screen containers).
- This convention must be applied by the App layer by assigning ids such as
lrud-screen-home,lrud-screen-settings, and their relative headers if defined separately such aslrud-screen-home-header-left. - Smaller scope means fewer focus candidates to evaluate, which improves next-focus computation performance.
ID conventions used by the resolver
- Screen root:
id="lrud-screen-<screenName>"- Examples:
lrud-screen-home,lrud-screen-details.
- Examples:
- Header root:
id="lrud-screen-<screenName>-<headerName>"- Examples:
lrud-screen-home-header,lrud-screen-home-header-left.
- Examples:
- Parsing rule in the resolver:
- First
-after thelrud-screen-prefix separates screen vs header. - No
-after prefix => treated as a screen id. - With
-=> treated as a header id that belongs to the parsed screen.
- First
Direction-aware scoping behavior
When origin is inside a screen (SCREEN type):
up: include screen + headers for that screen.- Internally, headers are queried by prefix
"${screen}-".
- Internally, headers are queried by prefix
left,right,down: restrict to the screen only.
When origin is inside a header (HEADER type):
down: include owning screen (if found) + sibling headers matching the current header prefix query.left,right,up: keep navigation in header scope (closest header + matching header siblings).
This enables header <-> screen transitions only on vertical intent (up from screen, down from header), while reducing accidental cross-scope jumps for horizontal moves.
Virtualized list container marker with tabindex="-999"
tabindex="-999"is used to mark special TVFocusGuideView containers created for FlatList/virtualized-list behavior.- During parent-container resolution, these internal containers are skipped by default so app-level containers remain the effective scoping boundary.
- This avoids incorrectly scoping navigation to internal list wrappers.
Test coverage for these scoping conventions
test/lrud.test.jsincludes container scoping cases usinglrud-screen-*ids:should scope navigation to the current screen container(test/layouts/screen-scope.html)should scope navigation to blocked container within a screen(test/layouts/screen-block-exit-right.html)should block right exit when parent container blocks it even if child allows it(test/layouts/nested-parent-blocks-right.html)
test/lrud-directional.test.jsincludes directional screen/header scoping fixtures:should keep left navigation scoped to a single header and not enter screen content(test/directional-layouts/screen-scope-header-single.html)should move between same-screen left/right headers and not jump into screen content(test/directional-layouts/screen-scope-header-multiple.html)
- There is still no dedicated fixture in this repo that explicitly asserts
tabindex="-999"(virtualized-list marker) behavior in isolation.
Other TV-specific test coverage in this repo
test/lrud.test.jscovers TVFocusGuideView-style container behavior:- Autofocus and focus memory (
test/layouts/autofocus-container.html,test/layouts/nested-autofocus.html) - Destinations priority and fallback (
test/layouts/destinations-container.html,test/layouts/destinations-only-container.html) - Trap focus/block exit behavior (
test/layouts/block-exit-container.html,test/layouts/block-exit-container-focusable.html,test/layouts/block-exit-no-autofocus.html) - Ignore and disabled semantics (
test/layouts/ignore-elements.html) - Mixed TV-like composition scenarios (
test/layouts/tv-focus.html)
- Autofocus and focus memory (
test/lrud-directional.test.jscovers directional selection behavior that impacts TV feel and predictability:- Axis overlap and alignment priority (
test/directional-layouts/axis-overlap-priority.html,test/directional-layouts/alignment-priority.html) - Overlap threshold handling (
test/directional-layouts/overlap-weighting.html) - Screen/header scope boundaries for directional moves (
test/directional-layouts/screen-scope-header-single.html,test/directional-layouts/screen-scope-header-multiple.html) - Complex directional and edge-case candidate filtering (
test/directional-layouts/complex-direction.html,test/directional-layouts/edge-cases.html,test/directional-layouts/directional-validation.html)
- Axis overlap and alignment priority (
Focus memory
data-focusstores the last focused child id.data-focusis updated only for ancestor containers withdata-autofocus="true".- If no valid remembered target exists, fallback behavior is used.
Destination priority
On container entry, resolution order is:
data-destinations(first valid id in list)data-focus(only when autofocus is enabled)- Fallback candidate selection
If destinations are configured but none resolve, fallback is used and the no-valid-destination callback can run.
Block exit behavior
- If requested direction is listed in
data-block-exit, focus search is constrained to the blocking container scope. - If direction is not blocked, candidate search continues outside the container as normal.
Candidate validity and directional filtering
- Focus candidates are directional: candidates behind the movement direction are excluded.
- Per-candidate overlap tolerance is configurable by
data-lrud-overlap-threshold. - Default overlap tolerance is
0.3when attribute is absent.
Sorting strategy in this fork
- Tier 1: candidates overlapping the origin axis band are prioritized.
- Tier 1 sorting: distance, then alignment delta, then DOM order.
- Tier 2 (non-overlap) sorting: alignment delta, then distance, then DOM order.
This gives nearby on-axis movement preference while keeping alignment-first behavior for sparse layouts.
Ignored/excluded candidates
.lrud-ignoreexcludes the element and descendants.[disabled]excludes the element.tabindex="-1"elements are excluded from normal candidate selection.
Reference Attributes
| Attribute | Type | Meaning |
|---|---|---|
| .lrud-container | Class | Marks a container boundary |
| .lrud-ignore | Class | Excludes subtree from navigation |
| data-autofocus | Attribute | Enables focus memory behavior |
| data-focus | Attribute | Stores last focused child id |
| data-destinations | Attribute | Preferred destination id list |
| data-block-exit | Attribute | Directional focus trap flags |
| data-lrud-overlap-threshold | Attribute | Directional overlap tolerance per candidate |
| id="lrud-screen-<name>" | Id convention | App-defined screen scope boundary for candidate search |
| tabindex="-999" | Attribute value | Internal FlatList TVFocusGuideView marker, ignored in default parent scoping |
Related
License
Apache-2.0
