npm.io
2.0.0 • Published 1 week ago

jb-popover

Licence
MIT
Version
2.0.0
Deps
1
Size
425 kB
Vulns
0
Weekly
0
Stars
1

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.

Keywords