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

jb-popover

v2.0.1

Published

popover web component

Readme

jb-popover

Responsive web component: an anchored popover on desktop, a centered tablet panel, and a draggable bottom sheet on mobile.

Examples | React wrapper | Styling

Installation

npm i jb-popover
import 'jb-popover';

const popover = document.querySelector('jb-popover');
popover.bindTarget(document.querySelector('#trigger'));
popover.open();
<button id="trigger">Choose a value</button>
<jb-popover aria-label="Choose a value">
  <div>Your content</div>
</jb-popover>

API reference

Options are JavaScript properties, not HTML attributes, unless listed as attributes below.

Attributes

| Attribute | Meaning | | --- | --- | | is-open | Boolean attribute parsed by jb-core; opens or requests an animated close. | | id | Enables an owned URL hash entry when opened in mobile/tablet mode. | | aria-label | Accessible label for the internal dialog when modal behavior is active. |

Properties

| Property | Type | Default | Meaning | | --- | --- | --- | --- | | isOpen | boolean (read-only) | false | Remains true through dismissal; false when the closed event fires. | | isMobileMode | boolean (read-only) | viewport | True at widths up to 48rem, including tablets. | | isBottomSheet | boolean (read-only) | viewport | True at widths up to 40rem. | | positionArea | { inline?, block? } | { inline: 'start', block: 'after' } | Preferred desktop anchor alignment; partial assignments preserve other fields. | | autoPlacement | boolean | true | Flip above/below when the opposite side has more room and shift inside the viewport with an 8px margin. | | swipeToClose | boolean | true | Allow downward dismissal from the handle or backdrop on bottom sheets. | | dragFromContent | boolean | false | Also allow eligible gestures starting in content. | | modal | boolean or 'auto' | 'auto' | Auto enables modal behavior on mobile/tablet and leaves desktop non-modal. | | closeOnEscape | boolean | true | Allow Escape to request dismissal of the topmost popover. | | restoreFocus | boolean | false | Restore previous focus on close; opt in only for triggers that do not open on focus. | | autoCloseOnBackgroundClick | boolean | true | Automatically close after an allowed backdrop-click request. | | overflowHandler | 'NONE' or 'SLIDE' | 'NONE' | Legacy desktop hover correction. Prefer automatic placement for viewport collisions. | | overflowDom | HTMLElement or null | null | Boundary for legacy overflow correction; otherwise uses the viewport. | | PopoverHashPath | string or null (read-only) | from id | Hash path for this instance. | | JBID | symbol (read-only) | unique | Instance identifier. |

Methods

| Method | Behavior | | --- | --- | | open() | Opens, positions, activates observers, and applies modal/history behavior. Interrupts an in-progress dismissal. | | close() | Animates dismissal without dispatching a close request. Emits closed on completion. | | bindTarget(element) | Bind or replace the desktop anchor, including while already open. | | unBindTarget() | Remove the anchor and its observers. | | checkInitialOpenness() | Check whether the URL hash addresses this popover; called on connection. |

Events

| Event | Detail | Behavior | | --- | --- | --- | | load | none | Component connected, before internal listeners are registered. | | init | none | Connection initialization completed. | | url-open | none | The current URL hash caused opening. | | before-close | { eventType } | Cancelable request for swipe, backdrop, or Escape dismissal. | | close | { eventType } | Follows an allowed request; also cancelable for swipe/backdrop/Escape. Browser-back dispatches a notification. | | closed | none | Non-cancelable completion event after any animated close, including programmatic close. |

Dismissal events bubble and cross shadow boundaries. The close event is a request/notification, not animation completion.

event.detail.eventType identifies the trigger:

| Value | Trigger | | --- | --- | | SWIPE_DOWN | A downward drag or flick crossed the dismissal threshold. | | BACKGROUND_CLICK | The mobile/tablet backdrop was clicked. | | ESCAPE_KEY | Escape was pressed while this was the topmost popover. | | HISTORY_BACK_EVENT | Navigation left this instance's owned history entry. | | OUTSIDE_CLICK, CLOSE_BUTTON_CLICK | Reserved legacy reasons. |

Use event.preventDefault() synchronously to reject a request. A rejected swipe returns smoothly. For asynchronous confirmation, prevent first and call close() after confirmation.

popover.addEventListener('before-close', event => {
  if (hasUnsavedChanges) event.preventDefault();
  console.log(event.detail.eventType);
});
popover.addEventListener('closed', () => {
  console.log('Dismissal animation finished');
});

Open and close

popover.open();
popover.close(); // starts the exit animation

isOpen remains true until exit completes. Calling open() during exit cancels the dismissal. Reduced-motion preferences remove timed movement. Do not remove the element before closed if you want its exit animation to finish.

Anchor position

Supported inline values: start, end, center, center-before, center-after. Supported block values: before, after. Inline alignment follows the anchor's text direction.

popover.positionArea = { inline: 'end', block: 'before' };
popover.autoPlacement = false; // use the requested alignment exactly

Anchor height/width changes, nested scroll containers, and shadow-root scroll ancestors are observed while open. Rebinding or reconnecting restores observation. Transformed containing blocks are included in coordinate calculations. Scroll-driven work is batched before repaint; closed popovers do not measure anchors on resize.

Overflow handling

autoPlacement adjusts placement against the viewport. The legacy overflowHandler = 'SLIDE' shifts content upward on desktop mouse enter, using overflowDom when supplied; mouse leave restores it.

Mobile swipe dismissal

At widths up to 40rem, drag from the handle, non-content sheet area, or anywhere on the backdrop. Drag beyond 30% of sheet height or flick downward to close. Content and backdrop follow the finger. A returning sheet can be grabbed again without jumping to its origin.

dragFromContent is disabled by default. If enabled, every scrollable ancestor must already be at the top when the touch starts. Reaching the top by scrolling requires a new gesture. Interactive controls and horizontal gestures are excluded.

<jb-popover aria-label="Choose a date">
  <div data-jb-popover-no-drag>Place a calendar or swiper here.</div>
</jb-popover>

data-jb-popover-no-drag excludes the marked content region and its descendants. swipeToClose = false disables all sheet dragging. Tablet and desktop layouts do not use touch dismissal.

Modal behavior and accessibility

With modal = 'auto', mobile and tablet overlays lock page scrolling, make surrounding content inert, contain keyboard focus, and expose an internal role="dialog" with aria-modal="true". Supply aria-label to name the dialog. Initial focus goes to the first focusable content element with autofocus, or the content container when none exists, before the background becomes inert. Set restoreFocus = true to restore previous focus on close. It defaults to false so focus-triggered pickers do not reopen after selection.

Set modal = false for non-modal pickers that need to retain input focus, or modal = true to opt into these behaviors on desktop. Desktop is non-modal by default. Escape targets the topmost popover; nested modal popovers keep scrolling locked until the final modal closes. These popovers coordinate with each other, not an application's independent overlay manager.

The sheet accounts for safe-area insets and visual-viewport height/offset changes, including space taken by a virtual keyboard. Oversized mobile content remains scrollable.

Mobile URL hash state

An id enables a history entry when an open popover enters mobile/tablet mode. Router state fields are preserved. Once created, the entry remains owned through a resize back to desktop, so browser back still dismisses it. An existing matching deep link is not pushed again.

Closing consumes only the current entry owned by this instance. Disconnecting restores the previous URL/state for a current owned entry without navigating away. Avoid assigning the same id to multiple popovers. Browser-back remains a notification and does not use before-close.

CSS parts and variables

| Part | Purpose | | --- | --- | | content | Sheet/panel content container. | | drag-handle | Bottom-sheet drag area. | | backdrop | Independent overlay background, blur, and fade. |

| CSS variable | Purpose | | --- | --- | | --jb-popover-z-index | Stacking order. | | --jb-popover-bg-color | Content background. | | --jb-popover-back-bg-color | Backdrop background. | | --jb-popover-handle-color | Drag indicator color. | | --jb-popover-border-radius | Content corner radius. | | --jb-popover-padding | Desktop content padding. | | --jb-popover-box-shadow | Content shadow. | | --jb-popover-top | Unanchored desktop top position. | | --jb-popover-margin-top | Desktop top margin. |

jb-popover::part(backdrop) {
  background: rgb(0 0 0 / 40%);
  backdrop-filter: blur(8px);
}

Tests

From the design-system repository root, run deno task test --name=jb-popover. This builds the package, checks formatting/types, and runs the popover Storybook and browser suites. See test details.