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

@byokit/overlay

v0.3.0

Published

A floating bubble over other apps on Android: a panel that opens on tap, per-app visibility rules, a text-free tap log and an optional focused-field reader. On iOS it reports unsupported.

Readme

@byokit/overlay

A floating bubble over other apps on Android, as an Expo module: a panel (a React component the app registers) that opens on tap, per-app visibility rules, a tap log with no text in it, and an optional focused-field reader through the app's own accessibility service. On iOS, the web and Node it reports unsupported (docs/capability-kits.md §7).

import { overlay, stateWords } from '@byokit/overlay';

const state = await overlay.start({ host: 'window', mood: 'calm', notice: { channel: 'bubble', title: 'Bubble', text: 'On', icon: 'ic_bubble' }, panel: 'Panel' });
if (state === 'needs-permission') await overlay.openPermission();
const off = overlay.on('tap', () => overlay.say('Hi'));

Add the config plugin to app.json: ["@byokit/overlay", { "moods": { "calm": "./assets/calm.png" } }]. It copies each mood image into the app's drawables and raises the app's minimum Android version to 8.0 (API 26). The library's manifest brings the overlay and foreground-service permissions, the bubble's service and the panel's activity.

shownFor, setApp and resetApp keep the per-app rules; stateWords(state) gives the sentence to show for a state. @byokit/overlay/focused-field reads the focused text field only when the app calls read().

Nothing reads another app's screen in the background, and the tap log keeps no text and forgets entries after 30 days.

Focused field

@byokit/overlay/focused-field works through the app's own accessibility service; the kit declares none. The service calls ByokitAccessibility.attach(this) in onServiceConnected and ByokitAccessibility.detach(this) in onUnbind, and in onDestroy (idempotent; cancels outstanding inserts), and its config sets android:canRetrieveWindowContent="true" and android:accessibilityFlags="flagRetrieveInteractiveWindows|flagReportViewIds" (the example's is examples/expo/modules/a11y-demo). Subscribe to window/content changes, view focus, text changes and selection changes so Chromium keeps its virtual tree current. The old flagRequestEnhancedWebAccessibility is unused on API 26 and later (the kit's supported Android versions). Then:

import { focusedField } from '@byokit/overlay/focused-field';

if (await focusedField.available()) {
  const field = await focusedField.read();          // { app, text, selection } or null
  const result = await focusedField.insert('Hi');   // 'inserted', 'copied' (then show words('field.copied')) or 'failed'
}

insert puts the text over the selection (or all of it with { replace: 'all' }), reads the field back, and retries up to attempts times (default 2, pausing retryMs, default 150 ms, between tries): while the panel's window is on top Chrome refuses the set, so { attempts: 13, retryMs: 150 } lands it. A contenteditable that dropped only the newlines resolves 'landedWithoutNewlines' when acceptNewlineLoss is set, so the app can accept the text as typed; otherwise the text is copied for the person to paste ('copied') or the insert 'failed'. The field is resolved with input focus across every window root, then accessibility focus, including virtual nodes inside WebView content. Resolution refreshes the field and waits for two agreeing snapshots, with at most three 75 ms pauses while focus settles. Insertion re-acquires the same captured field before its first write, including virtual fields without resource ids (matched by framework node identity). With no focus the kit reports no field. A password node anywhere on the focused path prevents reads, writes and clipboard fallback. The same service also gives the accessibility host (no overlay switch needed), the foreground app that rules and spots: 'per-app' follow, and the keyboard's top so the bubble rests above it. The bubble's window never takes focus, so a tap or long press on it leaves the other app's field focused.

import { focusedField } from '@byokit/overlay/focused-field';

const draft = 'First line\nSecond line';
const capture = new AbortController();
const pending = focusedField.insert(draft, { attempts: 13, retryMs: 150, acceptNewlineLoss: true, signal: capture.signal });
// On capture invalidation: capture.abort();
const result = await pending; // exactly one result, including 'cancelled'
if (result === 'landedWithoutNewlines') { /* typed in, but check the line breaks */ }

Cancellation stops subsequent field reads, set-text actions and clipboard fallback; an action already in progress finishes before native cancellation returns. Every insert settles once. Service detach and native module teardown cancel pending inserts. Existing calls without a signal keep working.

The bubble responds to TalkBack ACTION_CLICK and touch taps through the same click handler, once per activation. It carries a TalkBack label (label in start, or setLabel, cleared with null), and say(text, mood, ms, { announce: true }) reads the pill aloud.

A service that must show the bubble with no JS running (after a reboot or process death) drives it from Kotlin alone: keep a ServiceBubble, call start(config) in onServiceConnected with the persisted rules, and the bubble shows on attach and restores after every rebind. Rules(app-rules).shows(app) is the per-app decision in Kotlin; FocusedFields.read(service) and FocusedFields.insert(node, ...) are the focused-field read and insert for the captured node. The service can hand over the AccessibilityNodeInfo it captured itself (or wrap it with FieldNode.of(node)); the insert retries it and re-acquires only the same field (view id, bounds and package):

class Assistant : AccessibilityService() {
  private lateinit var bubble: ServiceBubble
  private var field: FieldNode? = null
  private var insertCancellation: InsertCancellation? = null

  override fun onServiceConnected() {
    ByokitAccessibility.attach(this)
    bubble = ServiceBubble(ServiceBubble.drawables(this), PrefsSpotStore(this), ServiceBubble.reducedMotion(this))
    bubble.events.add { e ->
      if (e == OverlayEvent.Tap) {
        insertCancellation?.cancel() // invalidate the previous capture before replacing it
        field = FocusedFields.capture(this) // resolves exact focus at tap time
        PanelActivity.launch(this, "Panel", emptyMap())
      }
    }
    bubble.start(ServiceBubble.Config(mood = "calm", label = "Assistant", rules = savedRules()))
  }

  fun insert(draft: String) {
    val node = field ?: return
    insertCancellation?.cancel()
    val cancellation = InsertCancellation() // created before launching the worker
    insertCancellation = cancellation
    thread { // insert waits between tries: off the main thread
      val result = FocusedFields.insert(node, draft, opts = InsertOpts(attempts = 13),
        copy = FocusedFields.clipboard(this), service = this, cancellation = cancellation)
      // result is inserted, landedWithoutNewlines, copied, failed or cancelled, exactly once
    }
  }

  override fun onUnbind(intent: Intent?): Boolean { bubble.stop(); ByokitAccessibility.detach(this); return false }
  override fun onDestroy() { ByokitAccessibility.detach(this); bubble.stop(); super.onDestroy() }
  override fun onAccessibilityEvent(event: AccessibilityEvent?) {}
  override fun onInterrupt() {}
}

Both Kotlin insert overloads accept an optional InsertCancellation; cancel() is idempotent and returns "cancelled" from the pending insert. Call ByokitAccessibility.detach(this) in onUnbind and onDestroy to cancel all service-owned inserts, including those with no explicit cancellation signal. The captured node remains owned by the caller and must stay alive until the insert returns. FieldNode.of(node, service) retains the service owner.

FocusedFields.capture(service) does the same capture as a FieldNode. An app's own foreground service shows the bubble with ServiceBubble(WindowOverlayHost(this), moods, spots), which takes no rules and shows everywhere.

Status: ready to publish (BK-O3). The bubble, both hosts, the panel, the tap log and the focused field are built and proven on an Android emulator (API 36). On iOS, the web and Node overlay.state() is unsupported.

One screen picture and a point marker

@byokit/overlay/screen-frame asks Android's system consent dialog every time frame() is called. It captures one PNG after the dialog leaves, stops the projection and foreground service, and returns a local file:// URI. It never uploads the picture. iOS, web and Node return { status: 'unsupported' }; no ReplayKit support is claimed.

With the config plugin above installed, this runnable handler starts the window overlay, captures, and points at the middle of the resulting image. Call it from a button in a foreground Expo development build (Expo Go cannot load this module). If permission settings open, return to the app and press the button again.

import { overlay } from '@byokit/overlay';
import { screenFrame } from '@byokit/overlay/screen-frame';

export async function showUmerWhere() {
  const state = await overlay.start({
    host: 'window', mood: 'calm',
    notice: { channel: 'guide', title: 'Umer’s guide', text: 'Ready to help.', icon: 'ic_bubble' },
  });
  if (state === 'needs-permission') { await overlay.openPermission(); return; }
  if (state !== 'on') return;
  const frame = await screenFrame.frame();
  if (frame.status !== 'captured') return;
  const result = await overlay.pointHere({
    x: frame.width / 2, y: frame.height / 2,
    label: 'Umer, tap here', space: frame.space, ms: 2500,
  });
  if (result === 'display-changed') { /* ask for a fresh picture after rotating or resizing */ }
  // Optional early removal: await overlay.dismissPoint();
  // After using the image: await screenFrame.clear();
}

frame() returns captured with uri, mimeType: 'image/png', width, height and space; or cancelled, busy, unsupported, or failed with reason: 'timeout' | 'display-changed' | 'capture-failed'. Only one request can run at once. A missing foreground activity fails; denial and system revocation cancel. Capture times out after six seconds; an abandoned consent request times out after sixty seconds. Module teardown settles pending calls and stops its capture. The next request deletes the previous cached PNG, even if consent is denied; clear() also deletes it, and rejects during a capture. Copy a picture the app needs to retain before the next request.

space describes the default display, including system bars: width, height, density, densityDpi, rotation (Android's 0–3 quarter turns), displayId, origin: 'top-left', and unit: 'physical-pixels'. The PNG is not scaled. Android 14+ requests the entire default display; an OEM override to app-only capture or a geometry change fails instead of returning a picture with misleading coordinates. Protected content can be blank, as enforced by Android. If coordinates came from a resized preview, convert them back to the original image pixels first.

pointHere({ x, y, label, space?, ms? }) centres a ring at those full-display pixels, draws the label beside it and announces the full label for TalkBack. It needs an already started overlay (either host). It replaces the previous marker, defaults to 2500 ms (allowed range 1–60000), and returns shown, not-running, needs-permission, display-changed, or unsupported. Pass frame.space to reject stale geometry; without it the current display is used. Coordinates must be finite and inside the display, with a non-empty label. A marker never takes focus or accepts touches; its window opacity stays below Android's pass-through threshold. The bubble keeps its own existing touch behavior. dismissPoint(), stop(), native teardown and host loss remove the marker. Auto-dismiss uses elapsed time, with no animation. A ring near an edge may be clipped and a long visual label is shortened; TalkBack receives it in full.

For an account-free emulator demo and repeatable consent/marker proof, build the Expo example with EXPO_PUBLIC_SCREEN_DEMO=1 and run examples/expo/e2e-screen-frame.sh <emulator-serial>. Its two PNG captures are uploaded by the Android CI job and linked in the pull request.