@@ -2,6 +2,12 @@ import type {Rect, Strategy} from '@floating-ui/core';
22import {getWindow, isWebKit} from '@floating-ui/utils/dom';
33
44import {getDocumentElement} from '../platform/getDocumentElement';
5+ import {getWindowScrollBarX} from './getWindowScrollBarX';
6+
7+ // Safety check: ensure the scrollbar space is reasonable in case this
8+ // calculation is affected by unusual styles.
9+ // Most scrollbars leave 15-18px of space.
10+ const SCROLLBAR_MAX = 25;
511
612export function getViewportRect(element: Element, strategy: Strategy): Rect {
713 const win = getWindow(element);
@@ -25,6 +31,32 @@ export function getViewportRect(element: Element, strategy: Strategy): Rect {
2531 }
2632 }
2733
34+ const windowScrollbarX = getWindowScrollBarX(html);
35+ // <html> `overflow: hidden` + `scrollbar-gutter: stable` reduces the
36+ // visual width of the <html> but this is not considered in the size
37+ // of `html.clientWidth`.
38+ if (windowScrollbarX <= 0) {
39+ const doc = html.ownerDocument;
40+ const body = doc.body;
41+ const bodyStyles = getComputedStyle(body);
42+ const bodyMarginInline =
43+ doc.compatMode === 'CSS1Compat'
44+ ? parseFloat(bodyStyles.marginLeft) +
45+ parseFloat(bodyStyles.marginRight) || 0
46+ : 0;
47+ const clippingStableScrollbarWidth = Math.abs(
48+ html.clientWidth - body.clientWidth - bodyMarginInline,
49+ );
50+
51+ if (clippingStableScrollbarWidth <= SCROLLBAR_MAX) {
52+ width -= clippingStableScrollbarWidth;
53+ }
54+ } else if (windowScrollbarX <= SCROLLBAR_MAX) {
55+ // If the <body> scrollbar is on the left, the width needs to be extended
56+ // by the scrollbar amount so there isn't extra space on the right.
57+ width += windowScrollbarX;
58+ }
59+
2860 return {
2961 width,
3062 height,
0 commit comments