You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A GeckoView web extension providing WICG-compatible spatial navigation for Android TV, AAOS, and D-pad/keyboard navigation. Designed for seamless integration with flutter-geckoview and other GeckoView host applications.
Features
Core Navigation
✅ Geometric spatial navigation - Multi-pass scoring algorithm for accurate directional navigation
✅ Focus groups - Define navigation regions with data-focus-group attributes
✅ WICG API compatibility - window.navigate(), Element.spatialNavigationSearch(), etc.
✅ Focus trap detection - Handles modals/dialogs with escape affordances
✅ ARIA accessibility - Optional live region announcements
✅ Framework-aware refresh - Deferred updates for React/Vue/Angular
✅ Strict TypeScript - Fully typed codebase with noImplicitAny: true
✅ Performance - <5ms navigation latency (benchmarked for 1000+ elements)
Installation
GitHub Packages (npm)
# Configure npm to use GitHub Packages for @dart-technologies scopeecho"@dart-technologies:registry=https://npm.pkg.github.com">> .npmrc
# Install the package
npm install @dart-technologies/spatial-navigation-geckoview
Git Submodule (recommended for flutter-geckoview)
For Flutter/native projects that bundle the extension as an asset:
# Add as submodule
git submodule add https://github.com/dart-technologies/spatial-navigation-geckoview.git lib/assets/spatial_navigation
# Initialize and update
git submodule update --init --recursive
# Build the extensioncd lib/assets/spatial_navigation
npm install
npm run build:all
Update submodule to latest:
cd lib/assets/spatial_navigation
git pull origin main
npm install
npm run build:all
cd ../../..
git add lib/assets/spatial_navigation
git commit -m "chore: update spatial-navigation-geckoview submodule"
Manual Installation
Clone and build:
git clone https://github.com/dart-technologies/spatial-navigation-geckoview.git
cd spatial-navigation-geckoview
npm install
npm run build:all
<!-- Optional: Configure via global --><script>window.spatialNavConfig={color: '#00BCD4',// Teal focus coloroutlineWidth: 4,autoRefocus: true,enableAria: true,// Enable accessibilitytraverseShadowDom: true,// For Web Components};</script><!-- Focus groups for navigation regions --><navdata-focus-group="main-nav;boundary=contain"><button>Home</button><button>Search</button></nav><maindata-focus-group="content;enterMode=last"><!-- Content area remembers last focused element --></main>
Programmatic Navigation
// WICG-compatible APIwindow.navigate('down');// Move focus downwindow.navigate('right');// Move focus right// Find next target without movingconstnext=document.activeElement.spatialNavigationSearch('down');console.log('Next target:',next);// Get focusable elements in containerconstfocusables=document.body.focusableAreas({mode: 'visible'});// Get navigation containerconstcontainer=button.getSpatialNavigationContainer();
Configuration
All options can be set via window.spatialNavConfig:
// Before focus changes (cancelable)document.addEventListener('navbeforefocus',(e)=>{console.log('Moving',e.detail.dir,'to',e.target);// e.preventDefault() to cancel});// When hitting boundarydocument.addEventListener('navnotarget',(e)=>{console.log('Boundary reached:',e.detail.dir);if(e.detail.inTrap){console.log('In trap, escape:',e.detail.escapeKey);}});
Focus Exit Events
// When navigation leaves web contentdocument.addEventListener('spatialNavigationExit',(e)=>{console.log('Exiting web content:',e.detail.direction);// Native app handles from here});
Native Messaging Protocol
Messages from Extension → Native
Type
Payload
Description
spatialNavInit
{ url, version }
Extension initialized
focusChange
{ direction, fromElement, toElement }
Focus moved
focusExit
{ direction, inTrap }
Reached boundary
Messages from Native → Extension
Type
Payload
Description
navigate
{ direction }
Request navigation
configUpdate
{ ...config }
Update config
refresh
{}
Re-scan focusables
Building
# Install dependencies
npm install
# Build all outputs
npm run build:all
# Build minified only
npm run build
# Build debug (unminified)
npm run build:debug
# Run tests# Run tests
npm test# Run performance benchmarks
npm run test:benchmark