@base-ui/react
Version:
Base UI is a library of headless ('unstyled') React components and low-level hooks. You gain complete control over your app's CSS and accessibility features.
31 lines (28 loc) • 1.63 kB
JavaScript
'use client';
import * as React from 'react';
import { ownerDocument } from '@base-ui/utils/owner';
import { useScrollLock } from '@base-ui/utils/useScrollLock';
import { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';
// Touch-opened popups normally avoid scroll locking so users can still swipe outside to dismiss.
// This hook re-enables scroll lock only when the popup is effectively full-width.
// Treat popups with up to 20px of total horizontal gutter as full-width so common ~10px side
// padding still locks scroll, since that leaves too little outside space for a reliable swipe.
const VIEWPORT_WIDTH_TOLERANCE_PX = 20;
/**
* Manages scroll lock for anchored popups. For non-touch opens, scroll lock is applied when
* enabled. For touch opens, scroll lock is applied only when the positioner width is effectively
* viewport-sized.
*/
export function useAnchoredPopupScrollLock(enabled, touchOpen, positionerElement, referenceElement) {
const [touchOpenShouldLockScroll, setTouchOpenShouldLockScroll] = React.useState(false);
useIsoLayoutEffect(() => {
if (!enabled || !touchOpen || positionerElement == null) {
setTouchOpenShouldLockScroll(false);
return;
}
const viewportWidth = ownerDocument(positionerElement).documentElement.clientWidth;
const popupWidth = positionerElement.offsetWidth;
setTouchOpenShouldLockScroll(viewportWidth > 0 && popupWidth > 0 && popupWidth >= viewportWidth - VIEWPORT_WIDTH_TOLERANCE_PX);
}, [enabled, touchOpen, positionerElement]);
useScrollLock(enabled && (!touchOpen || touchOpenShouldLockScroll), referenceElement);
}