From 931a39a162fb5c26ce1d86923daba2f8b2b8e70f Mon Sep 17 00:00:00 2001 From: XiaoYan Li Date: Wed, 10 Dec 2025 15:38:57 +0800 Subject: [PATCH] feat(RAC): expose `--page-width` and `--visual-viewport-width` from Modal --- packages/@react-aria/utils/src/useViewportSize.ts | 14 ++++++++++++-- packages/react-aria-components/src/Modal.tsx | 7 ++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/packages/@react-aria/utils/src/useViewportSize.ts b/packages/@react-aria/utils/src/useViewportSize.ts index 30fc9d26385..1f0888d90a6 100644 --- a/packages/@react-aria/utils/src/useViewportSize.ts +++ b/packages/@react-aria/utils/src/useViewportSize.ts @@ -88,10 +88,20 @@ export function useViewportSize(): ViewportSize { return size; } +/** + * Get the viewport size without the scrollbar. + */ function getViewportSize(): ViewportSize { return { // Multiply by the visualViewport scale to get the "natural" size, unaffected by pinch zooming. - width: visualViewport ? visualViewport.width * visualViewport.scale : window.innerWidth, - height: visualViewport ? visualViewport.height * visualViewport.scale : window.innerHeight + width: visualViewport + // The visual viewport width may include the scrollbar gutter. We should use the minimum width between + // the visual viewport and the document element to ensure that the scrollbar width is always excluded. + // See: https://github.com/w3c/csswg-drafts/issues/8099 + ? Math.min(visualViewport.width * visualViewport.scale, document.documentElement.clientWidth) + : document.documentElement.clientWidth, + height: visualViewport + ? visualViewport.height * visualViewport.scale + : document.documentElement.clientHeight }; } diff --git a/packages/react-aria-components/src/Modal.tsx b/packages/react-aria-components/src/Modal.tsx index 5aeccea36a5..4564c846871 100644 --- a/packages/react-aria-components/src/Modal.tsx +++ b/packages/react-aria-components/src/Modal.tsx @@ -184,17 +184,22 @@ function ModalOverlayInner({UNSTABLE_portalContainer, ...props}: ModalOverlayInn }); let viewport = useViewportSize(); + let pageWidth: number | undefined = undefined; let pageHeight: number | undefined = undefined; if (typeof document !== 'undefined') { let scrollingElement = isScrollable(document.body) ? document.body : document.scrollingElement || document.documentElement; - // Prevent Firefox from adding scrollbars when the page has a fractional height. + // Prevent Firefox from adding scrollbars when the page has a fractional width/height. + let fractionalWidthDifference = scrollingElement.getBoundingClientRect().width % 1; let fractionalHeightDifference = scrollingElement.getBoundingClientRect().height % 1; + pageWidth = scrollingElement.scrollWidth - fractionalWidthDifference; pageHeight = scrollingElement.scrollHeight - fractionalHeightDifference; } let style = { ...renderProps.style, + '--visual-viewport-width': viewport.width + 'px', '--visual-viewport-height': viewport.height + 'px', + '--page-width': pageWidth !== undefined ? pageWidth + 'px' : undefined, '--page-height': pageHeight !== undefined ? pageHeight + 'px' : undefined };