npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@osiris-smarttv/screen-navigation

v0.1.2

Published

TV screen stack coordinator — lifecycle, keep-alive ports, automatic focus restore

Readme

@osiris-smarttv/screen-navigation

Screen stack coordinator for React TV apps — intents, lifecycle, keep-alive, and focus restore in one place.

On lean-back devices, navigation is more than URL changes. Screens need a stack, cached views, remote Back, and spatial focus that survives tab switches. @osiris-smarttv/screen-navigation gives you a single coordinator for all of that. React Router syncs to the stack; it does not own it.

 Remote / UI          ScreenCoordinator (SSOT)          Adapters
 ───────────          ─────────────────────────          ────────
 push / pop / back ──► stack + lifecycle + focus  ──────► React Router (URL)
 detail deeplink   ──► intent queue + registry    ──────► @osiris-smarttv/keep-alive
 memory warning  ──► eviction policy              ──────► Norigin focus restore

Why use it

| Problem on TV | What this package does | |---|---| | Back button handled in five places | back() / pop() unified with stack policy | | Route change ≠ screen lifecycle | Explicit enter · resume · pause · exit · refresh phases | | Heavy screens remount on every tab | Registry cache: true + keep-alive port | | Focus lost after navigation | ScreenFocusScope + deferred focus policy | | Memory pressure on console | Optional bridge + planMemoryEviction | | Ad-hoc navigate(-1) everywhere | Typed push · replace · reset · reload · prefetch |


Install

npm i @osiris-smarttv/screen-navigation @osiris-smarttv/keep-alive

Peer dependencies

| Package | Purpose | |---|---| | react react-dom | Provider & hooks | | react-router-dom | URL sync adapter | | @osiris-smarttv/keep-alive | Page cache port (optional but recommended) | | @noriginmedia/norigin-spatial-navigation-core | Focus key model | | @noriginmedia/norigin-spatial-navigation-react | Focus scope integration |


Package exports

| Entry | Contents | |---|---| | @osiris-smarttv/screen-navigation | React provider, hooks, route helper, adapters | | @osiris-smarttv/screen-navigation/core | Pure TypeScript — no React imports |


Quick start

1. Declare screens

import { defineScreenRegistry } from '@osiris-smarttv/screen-navigation'

export const screenRegistry = defineScreenRegistry({
  home: {
    id: 'home',
    buildKey: () => '/main',
    buildPath: () => '/main',
    cache: true,
    stackPolicy: 'replace-top',
    defaultFocusKey: () => 'HOME_HERO',
    matchCacheKey: (key) => key === '/main',
  },
  detail: {
    id: 'detail',
    buildKey: (p) => `/detail/${p.id}`,
    buildPath: (p) => `/detail/${p.id}`,
    cache: true,
    stackPolicy: 'push',
    parseParamsFromCacheKey: (key) => {
      const id = key.split('/').pop() ?? ''
      return { id }
    },
    matchCacheKey: (key) => key.startsWith('/detail/'),
  },
})

2. Mount the provider

Wrap your router shell (inside BrowserRouter / RouterProvider):

import { ScreenNavigationProvider } from '@osiris-smarttv/screen-navigation'
import { screenRegistry } from './screenRegistry'

export function AppNavigation({ children }: { children: React.ReactNode }) {
  return (
    <ScreenNavigationProvider
      registry={screenRegistry}
      homeScreenId="home"
      popAtRoot="home"
      memoryPressureBridge
    >
      {children}
    </ScreenNavigationProvider>
  )
}

3. Wire routes

Use createScreenRouteElement so each route gets a focus scope:

import type { RouteObject } from 'react-router-dom'
import { createScreenRouteElement } from '@osiris-smarttv/screen-navigation'
import { HomeScreen } from './HomeScreen'
import { DetailScreen } from './DetailScreen'

export const routes: RouteObject[] = [
  { path: '/main', element: createScreenRouteElement(HomeScreen) },
  { path: '/detail/:id', element: createScreenRouteElement(DetailScreen) },
]

4. Navigate from UI

import { useScreenNavigation } from '@osiris-smarttv/screen-navigation'

function OpenDetail({ id }: { id: string }) {
  const nav = useScreenNavigation()

  return (
    <button onClick={() => nav.push('detail', { id })}>
      Open detail
    </button>
  )
}

useScreenNavigation() returns:

  • push(screenId, params?, options?)
  • pop(options?) · back() (remote Back)
  • replace · reset · reload · prefetch
  • snapshot — current stack frames
  • coordinator — escape hatch to core API

Screen lifecycle

Subscribe per screen with useScreenLifecycle:

import { useScreenLifecycle } from '@osiris-smarttv/screen-navigation'

export function PlayerScreen() {
  useScreenLifecycle({
    onEnter: () => console.log('first mount'),
    onResume: () => player.resume(),
    onPause: () => player.pause(),
    onExit: () => player.teardown(),
    onRefresh: () => player.reload(),
  })

  return <PlayerChrome />
}

Phases map to TV navigation reality: a cached screen resumes instead of re-entering when you return to it.


Remote Back

ScreenNavigationProvider already wraps children with ScreenBackListener — remote Back keys route through coordinator.handleRemoteBack() and respect stack policy.

For overlays that capture back (player, modal), stop propagation or render a nested listener with enabled={false} on the default handler while your layer is active.

isRemoteBackKey is exported if you need custom key routing.


Stack graph & prefetch

Declare allowed transitions and optional prefetch targets:

import type { ScreenStackGraph } from '@osiris-smarttv/screen-navigation/core'

export const stackGraph: ScreenStackGraph = {
  home: [{ to: 'detail', prefetch: true }],
  detail: [{ to: 'home' }],
}

Pass stackGraph + stackGraphPolicy to the provider. Use onPrefetch to warm routes or data before the user lands.


Core-only usage

Use @osiris-smarttv/screen-navigation/core when you need the coordinator without React — tests, native shells, or custom bindings:

import {
  ScreenCoordinator,
  defineScreenRegistry,
  NavigationIntentKind,
  ScreenCacheMode,
} from '@osiris-smarttv/screen-navigation/core'

const coordinator = new ScreenCoordinator({ registry })
coordinator.attachRouterPort(myRouterPort)
coordinator.attachCachePort(myCachePort)
await coordinator.push('home')

Highlights: ScreenLifecycleBus, NavigationIntentQueue, scheduleDeferredFocusApply, planMemoryEviction, ScreenTelemetryBus.


Works with @osiris-smarttv/keep-alive

The provider wires createKeepAliveCachePort automatically when AliveScope is mounted. Registry cache: true keeps DOM snapshots; reload / pop call clearEntry / refreshEntry through the port.

Recommended tree:

AliveScope
└─ ScreenNavigationProvider
   └─ KeepAliveOutlet (from keep-alive/router)
      └─ your routes

Source privacy on npm

Published tarballs are public but ship only compiled output:

  • dist/**
  • README.md
  • package.json, LICENSE

src/** is excluded via the files allowlist — git repo can stay private while npm consumers get minified dist only.


Local development

yarn install
yarn build
yarn typecheck
yarn test

Monorepo consumers can alias src/ for hot reload; npm installs resolve dist/.

Publish

yarn publish:check
yarn publish:dry-run
yarn publish:manual

See PUBLISHING.md and ARCHITECTURE.md.

License

MIT © OsirisTech SmartTV