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-keyboard

v0.28.0

Published

Soft-keyboard handling for sigx-lynx — KeyboardStickyView, KeyboardAvoidingView and keyboard hooks

Readme

@sigx/lynx-keyboard

Soft-keyboard handling for SignalX on Lynx, with an API mirroring React Native's (KeyboardAvoidingView, KeyboardStickyView/InputAccessoryView, useKeyboard). Keeps a composer input — and an accessory toolbar above it — pinned to the top edge of the on-screen keyboard.

Keyboard height reaches JS through the safe-area bridge (@sigx/lynx-safe-area): the native publisher reports the IME height as the keyboard inset on every safeAreaChanged event. This package turns that inset into ready-made layout primitives — no extra native module needed. Keyboard handling stays a separate concern from safe-area, mirroring the RN ecosystem split (react-native core / react-native-keyboard-controller vs react-native-safe-area-context).

📚 Documentation

Full guides, API reference and live examples → https://sigx.dev/lynx/modules/keyboard/overview/

Install

pnpm add @sigx/lynx-keyboard

Requires <SafeAreaProvider> (from @sigx/lynx-safe-area) at the app root — the same provider every safe-area hook already needs.

Usage

The proven chat-screen shape: the content area shrinks (KeyboardAvoidingView), the composer bar rides the keyboard (KeyboardStickyView). The bar's translate and the area's padding are both max(0, keyboard - bottomInset), so the list bottom always ends exactly where the bar lands.

import { component } from '@sigx/lynx';
import { KeyboardAvoidingView, KeyboardStickyView } from '@sigx/lynx-keyboard';

const ChatScreen = component(() => () => (
  <view style={{ display: 'flex', flexDirection: 'column', flexGrow: 1, flexShrink: 1, flexBasis: 0 }}>
    <KeyboardAvoidingView behavior="padding">
      <scroll-view style={{ flexGrow: 1, flexShrink: 1, flexBasis: 0 }}>
        {/* messages */}
      </scroll-view>
    </KeyboardAvoidingView>
    <KeyboardStickyView>
      {/* toolbar row (formatting buttons, attachments, …) */}
      {/* input row */}
    </KeyboardStickyView>
  </view>
));

Use one primitive per subtree: a bar inside both a padding KeyboardAvoidingView and a KeyboardStickyView lifts twice.

API

<KeyboardStickyView>

Pins its children to the keyboard's top edge with an MT-animated translateY (smooth 60fps, no per-frame thread crossing). When the keyboard is closed the bar rests in its natural flex position. Aliases: KeyboardAccessoryView, KeyboardToolbar.

| Prop | Type | Default | Description | | --- | --- | --- | --- | | offset | number | 0 | Extra gap (dp) above the keyboard. | | animated | boolean | true | false = discrete BG re-render (debug fallback). | | discountBottomInset | boolean | true | Subtract the bottom safe-area inset from the lift. Keep true when an ancestor <SafeAreaView edges={['bottom']}> already pads the home indicator. | | pinned | boolean | false | Freeze the bar in flow (translateY pinned to 0, instantly). For the WhatsApp keyboard ⇄ emoji-panel swap: pin in the same frame an app panel takes the keyboard's remembered lift as flow height and the bar never moves — the system keyboard's own show/hide animation does all visible motion, covering or revealing the already-painted panel (see @sigx/lynx-emoji's KeyboardPanelPicker and the showcase Chat composer). | | extraLiftSV | SharedValue<number> | — | An extra lift the bar must also clear: it rides max(keyboardLift, extraLiftSV). Pass a bottom accessory's live height — e.g. @sigx/lynx-navigation's useSheetHeight() for an emoji sheet — so the bar sits above whichever of the keyboard or the accessory is taller, riding the finger frame-for-frame during a drag (folded on the MT via useDerivedValue([…], 'max')). Because it's a max, the keyboard ⇄ sheet swap is dip-free as long as one shrinks while the other grows. Must be in the same unit as the lift (dp). |

Note: the bar's transform is controlled internally (the MT binding writes translateY via setStyleProperties; the non-animated path writes an inline transform). A transform passed through style will be overridden — wrap children in their own view if you need an additional transform.

<KeyboardAvoidingView>

Wraps content and keeps it above the keyboard. Layout-affecting, so it applies inline BG styles (the same pattern as <SafeAreaView> — MT-driven layout writes don't reflow <scroll-view>).

| Prop | Type | Default | Description | | --- | --- | --- | --- | | behavior | 'padding' \| 'translate' \| 'height' | 'padding' | padding shrinks the column; translate shifts it; height appends a spacer. | | keyboardVerticalOffset | number | 0 | Added to the computed lift (RN parity). | | discountBottomInset | boolean | true | Same as on KeyboardStickyView — set false to lift by the full keyboard height when no ancestor pads the bottom inset. | | pinned | boolean | false | Freeze avoidance (lift 0) while a sibling already holds the keyboard's space in flow — pair with <KeyboardStickyView pinned>. | | extraLiftSV | SharedValue<number> | — | Also shrink by an accessory's live height: content shrinks by max(keyboardLift, extraLiftSV). Pass e.g. @sigx/lynx-navigation's useSheetHeight() so an emoji sheet overlaying the bottom pushes the thread up too, not only the keyboard. Symmetric to KeyboardStickyView's extraLiftSV. |

Hooks

  • useKeyboard(): Computed<{ height, visible }> — BG-reactive keyboard state.
  • useKeyboardLift(discountBottomInset?, offset?): Computed<number> — the raw lift value.
  • useKeyboardLiftSV(discountBottomInset?, offset?, duration?): SharedValue<number> — smoothly animated MT SharedValue tracking the lift; bind with useAnimatedStyle(ref, sv, 'translateY', { factor: -1 }). duration is in seconds (the @sigx/lynx-motion convention), default 0.25.
  • rememberedKeyboardLift(): number — the LAST observed lift, or 0 if the keyboard has never been shown. See below. Like every length in this package it is in Lynx logical pixels — the unit @sigx/lynx-safe-area reports insets in, which the docs there call dp/pt.
  • resetRememberedKeyboardLift(): void — clears that memory, including the persisted copy. A test seam, not app API: calling it from a screen throws away the app-wide measurement every keyboard-sized panel seeds itself from.

Remembered keyboard height

A panel that must occupy exactly the keyboard's space — a WhatsApp-style emoji sheet — has to know how tall the keyboard is before showing it. "How tall is this device's keyboard" is a fact about the app's environment, not about whichever component was mounted when it last appeared, so the observation is module-level and persisted:

  • any mounted useKeyboardLift() / useKeyboardLiftSV() with the default shape (bottom inset discounted, no offset) records what it sees;
  • the value survives the keyboard closing, screens unmounting, and app restarts (via the optional @sigx/lynx-storage peer — without it the memory is simply per-run);
  • a real observation always supersedes a restored one, even if shorter (a different IME or split screen genuinely changes the height), so a stale value can never strand a panel too tall.

@sigx/lynx-sheet seeds its { keyboard: true } detent from this. Without it, the first keyboard-sized panel opened on a screen where nothing has been typed yet falls back to fallbackPx and then visibly corrects itself the moment the real keyboard returns — 53 px of input-row jump on a Pixel 9 Pro XL (#811).

Web

Renders on web (sigx run:web), but nothing ever lifts. Everything here is pure JS and nothing throws — <KeyboardAvoidingView> and <KeyboardStickyView> mount, the hooks return live signals, rememberedKeyboardLift() answers 0. The height they all read is @sigx/lynx-safe-area's keyboard inset, and on web that package has no publisher behind it, so the inset stays 0 for the session.

That is the right answer for a desktop browser, where the keyboard is hardware. It is the wrong one for a mobile browser or an installed PWA, whose on-screen keyboard resizes the visual viewport — a web shim would publish visualViewport height changes into the same safe-area globalProps key, and every primitive here would start working unchanged. Not wired today, so don't ship a web build that depends on the lift.

Gotchas

  • One primitive per subtree. A bar inside both a behavior="padding" <KeyboardAvoidingView> and a <KeyboardStickyView> lifts twice. Pick the one that matches the shape.
  • The sticky lift is invisible to layout measurement. translateY is written on the main thread via setStyleProperties, so anything measuring the bar's position from the background thread — a popup anchored to a caret, an absolute overlay — reads its un-lifted box. Anchor to the same useKeyboardLiftSV() instead of measuring.
  • transform on <KeyboardStickyView> is overridden. The component owns that property. Wrap the children in a view of your own if you need another transform.
  • discountBottomInset defaults to true. The keyboard covers the home-indicator region, so a bar already padded by <SafeAreaView edges={['bottom']}> must only rise by the difference. Set it false when nothing above you pads the bottom inset, or the bar sits too low by exactly that inset.
  • duration is seconds. useKeyboardLiftSV(…, duration) follows the @sigx/lynx-motion convention, not milliseconds — 0.25, not 250.
  • Won't do, and why: no prev/next/done accessory toolbar (RN's InputAccessoryView role is covered by <KeyboardStickyView> — build the row you want inside it); no interactive/drag-to-dismiss keyboard (Lynx exposes no interactive dismiss gesture); no separate keyboard window; and a single offset rather than the peer's closed/opened pair, because the closed position is the natural flex position here.

How it works

  • Height sourceuseSafeAreaInsets().value.keyboard. There is no separate keyboard event API in Lynx; the safe-area publisher is canonical.
  • The liftmax(0, keyboard - bottomInset): the keyboard covers the home-indicator region, so a bar that already sits above the bottom inset only needs to rise by the difference. Never add both.
  • BG→MT bridge — the keyboard inset is a BG-only signal (deliberately not a SharedValue in lynx-safe-area). useKeyboardLiftSV watches it from a BG effect and dispatches an MT withTiming (from @sigx/lynx-motion) toward each new target; the tween then runs entirely on the main thread.
  • Transform vs layout — only translateY is MT-animated. Padding/height go through inline BG styles because MT layout writes land after the first layout pass and <scroll-view> won't reflow.

Demo

See the Keyboard screen in examples/showcaseInput & KeyboardKeyboard (route /keyboard).

License

MIT