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

@sigx/lynx-navigation

v0.34.0

Published

Type-first native navigator for sigx-lynx — Stack, Tabs, Drawer, modals, lazy routes, deep links

Downloads

1,714

Readme

@sigx/lynx-navigation

Type-first native navigator for SignalX on Lynx. Define routes once with defineRoutes, augment the Register interface, and every navigator API — useNav, useParams, useSearch, <Link>, <Tabs.Screen>, <Drawer> — picks up precise per-route param/search inference.

The navigator ships native UI primitives (Stack, Tabs, Drawer, modal and bottom-sheet presentation), focus hooks, deep-link integration, lazy routes, screen options, and persistence — all reactive via sigx signals, all typed.

Status — 1.0 candidate. Public surface is frozen; every export is locked by the test suite in __tests__/public-surface.test.ts.

📚 Documentation

Full guides, the complete API reference, presentation modes, nested stacks and live examples → sigx.dev/lynx/modules/navigation/overview

Install

pnpm add @sigx/lynx-navigation

Peer-deps: @sigx/lynx, @sigx/lynx-icons, @sigx/lynx-motion, @sigx/lynx-sheet, and @sigx/lynx-linking (URL parsing, hardware back and deep-link wiring). Recommended: @sigx/lynx-storage for stack persistence.

A taste

// src/routes.ts
import { defineRoutes } from '@sigx/lynx-navigation';
import { z } from 'zod';
import { Home } from './screens/Home';
import { Profile } from './screens/Profile';

export const routes = defineRoutes({
    home: { component: Home },
    profile: {
        component: Profile,
        params: z.object({ id: z.string() }),
        path: '/users/:id',
    },
});

declare module '@sigx/lynx-navigation' {
    interface Register { routes: typeof routes }
}
// src/App.tsx
import { NavigationRoot, Stack } from '@sigx/lynx-navigation';
import { routes } from './routes';

export const App = () => (
    <NavigationRoot routes={routes} initialRoute="home">
        <Stack />
    </NavigationRoot>
);

From there: typed useNav() / <Link> navigation, per-tab nested stacks, modal/sheet presentation, focus hooks, deep linking via useLinkingNav, and stack persistence via useNavSerializer. Full reference, prop tables and runtime gotchas live on the docs site.

Bottom sheets (presentation: 'sheet') declare their resting heights as <Screen detents={[{ fraction: 0.4 }, { fraction: 0.9 }]} initialDetentIndex={0}> — @sigx/lynx-sheet DetentSpecs (px or fraction-of-screen; { keyboard } specs resolve via their fallbackPx since route sheets provide no keyboard environment — keyboard-aware detents are a @sigx/lynx-sheet <BottomSheet> feature), resolved to px; the drag/snap/dismiss mechanics run on that package's shared sheet engine. Sheets drag from anywhere on their surface by default, with drag↔scroll arbitration: taps, input focus and horizontal gestures pass through, and scrollable content coordinates automatically when wrapped in @sigx/lynx-gestures' <ScrollView> (below the max detent the sheet owns drags and content scroll is locked; at the max detent content scrolls, and pulling down from the top hands the gesture back to the sheet). For raw <scroll-view>/<list> content that can't coordinate, set <Screen dragMode="grabber"> (drag only from the top strip zone; grabberPx sizes the strip, default 28) — or dragMode="none" for backdrop/programmatic dismiss only.

useSheetHeight() returns a bindable SharedValue<number> of the top sheet's live visible height in px (0 when none, tracking the finger as the sheet drags). Bind it to animate a sibling to the sheet — e.g. a chat composer bar that must sit above whichever is taller, the keyboard or the sheet: useDerivedValue([keyboardLift, useSheetHeight()], 'max') (see @sigx/lynx-motion). Returns a constant 0 under animated={false}.

<Screen backdrop={false}> makes a sheet non-modal / inline: no dim, and the region above the sheet surface passes taps straight through to the screen below (the sheet's own layer is translated down to its top edge, so only the backdrop ever covered that region). Use it for a keyboard-accessory panel — an emoji picker sheet under a chat composer whose input must stay tappable while the sheet is open. backdropDismiss is then moot (no backdrop to tap); dismiss by dragging down or nav.pop(). Default true (the modal bottom-sheet look).

Animated transitions pre-stage the work they'd otherwise compete with: a push commits its render immediately, parks the incoming screen off-screen, and holds the slide until the runtime goes quiet — the screen's mount, its post-mount effect flushes, native layout, and any <list> initial cell builds all land while the outgoing screen is still presented (bounded, so a screen that never settles still transitions promptly). Pops do the same for the reveal relayout of the underneath screen. Heavy destinations (long lists, dense forms) therefore slide in smoothly instead of stuttering mid-transition; the cost is a short, capped delay before the motion starts. No API — this is how push/pop behave.

push(name, params, search, { animated: false }) presents a sheet AT its initial detent instantly — no slide. Use it to reveal a sheet by some other motion: e.g. open an emoji sheet behind the soft keyboard, then blur the input so the keyboard's own dismissal uncovers the sheet (the app animates nothing). useSheetHeight reads the detent height from the first frame, so a bar bound to max(keyboardLift, sheetHeight) never dips at the swap. A non-animated dismiss (pop(1, { animated: false })) returns the height to 0 the same way.

Transition geometry follows device rotation (#856): the card/modal slide distances, the route-sheet detents and the edge-back commit threshold all read the live screen size (useScreen() / useScreenMT() from @sigx/lynx) at plan-build time, rather than a value snapshotted when the bundle loaded. A push while the device is in landscape slides the full landscape width.

The iOS-style edge-swipe back (a 20px strip on the left edge of a card screen; opt out with <NavigationRoot edgeSwipeEnabled={false}>) follows the finger and, on release, commits when the drag passed a third of the screen width or the flick was faster than 300 px/s. Otherwise it springs back.

Errors

Everything this package throws is a SigxError from @sigx/lynx-core, with the message [@sigx/lynx-navigation] <action> failed: <detail> and a stable code — branch on the code, never on the message:

import { isSigxError } from '@sigx/lynx-core';
import { hrefFor, type NavigationErrorCode } from '@sigx/lynx-navigation';

try {
    const href = hrefFor('profile', { id: incomingId });
} catch (e) {
    if (isSigxError(e) && e.code === ('route_not_registered' satisfies NavigationErrorCode)) {
        // deep link naming a screen this build doesn't have → fall back home
    }
}

Codes: no_navigator (a navigator hook used outside the component that provides it), no_route_registry, route_not_registered, invalid_params, invalid_search, invalid_path, invalid_stack, unsupported_schema. The single exception is compilePath() given a non-string template, which stays a TypeError.

Diagnostics that don't throw — a useNavSerializer write that failed, a rejected exitApp() — go to the lynx-navigation logger namespace and stream to the sigx dev terminal.

Inline <BottomSheet> — moved to @sigx/lynx-sheet

The inline (route-free) <BottomSheet> now lives in @sigx/lynx-sheet — the unified sheet package whose engine also powers presentation: 'sheet' above. It supersedes the component this package used to export, adding DetentSpec geometry ({ keyboard: true }, { fraction }, topOffset caps), dismissible, backdrop, and full-surface drag with scroll arbitration. Import it from there:

import { BottomSheet } from '@sigx/lynx-sheet';

License

MIT