Fix Chromium scrollbar-induced layout shifts
This commit addresses an issue in Chromium on Linux and Windows where the appearance of a vertical scrollbar causes unexpected horizontal layout shifts. This behavior typically occurs when the window is resized, a card is opened or a script is selected, resulting in content being pushed to the left. The solution implemented involves using `scrollbar-gutter: stable` to ensure space is always allocated for the scrollbar, thus preventing any shift in the page layout. This fix primarily affects Chromium-based browsers on Linux and Windows. It has no impact on Firefox on any platform, or any browser on macOS (including Chromium). Because these render the scrollbar as an overlay, and do not suffer from this issue. Steps to reproduce the issue using Chromium browser on Linux/Windows: 1. Open the app with a height large enough where a vertical scrollbar is not visible. 2. Resize the window to a height that triggers a vertical scrollbar. 3. Notice the layout shift as the body content moves to the right. Changes: - Add a CSS mixin to handle scrollbar gutter allocation with a fallback. - Add support for modal dialog background lock to handle `scrollbar-gutter: stable;` in calculations to avoid layout shift when a modal is open. - Add E2E test to avoid regression. - Update DevToolkit to accommodate new scrollbar spacing.
This commit is contained in:
@@ -1,7 +1,19 @@
|
||||
import { formatAssertionMessage } from '@tests/shared/FormatAssertionMessage';
|
||||
|
||||
export function assertLayoutStability(selector: string, action: ()=> void): void {
|
||||
interface LayoutStabilityTestOptions {
|
||||
excludeWidth: boolean;
|
||||
excludeHeight: boolean;
|
||||
}
|
||||
|
||||
export function assertLayoutStability(
|
||||
selector: string,
|
||||
action: ()=> void,
|
||||
options: Partial<LayoutStabilityTestOptions> | undefined = undefined,
|
||||
): void {
|
||||
// arrange
|
||||
if (options?.excludeWidth === true && options?.excludeHeight === true) {
|
||||
throw new Error('Invalid test configuration: both width and height exclusions specified.');
|
||||
}
|
||||
let initialMetrics: ViewportMetrics | undefined;
|
||||
captureViewportMetrics(selector, (metrics) => {
|
||||
initialMetrics = metrics;
|
||||
@@ -11,10 +23,22 @@ export function assertLayoutStability(selector: string, action: ()=> void): void
|
||||
// assert
|
||||
captureViewportMetrics(selector, (metrics) => {
|
||||
const finalMetrics = metrics;
|
||||
expect(initialMetrics).to.deep.equal(finalMetrics, formatAssertionMessage([
|
||||
const assertionContext = [
|
||||
`Expected (initial metrics before action): ${JSON.stringify(initialMetrics)}`,
|
||||
`Actual (final metrics after action): ${JSON.stringify(finalMetrics)}`,
|
||||
]));
|
||||
];
|
||||
if (options?.excludeWidth !== true) {
|
||||
expect(initialMetrics?.x).to.equal(finalMetrics.x, formatAssertionMessage([
|
||||
'Width instability detected',
|
||||
...assertionContext,
|
||||
]));
|
||||
}
|
||||
if (options?.excludeHeight !== true) {
|
||||
expect(initialMetrics?.x).to.equal(finalMetrics.x, formatAssertionMessage([
|
||||
'Height instability detected',
|
||||
...assertionContext,
|
||||
]));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,19 @@
|
||||
const SmallScreen: ViewportScenario = {
|
||||
name: 'iPhone SE', width: 375, height: 667,
|
||||
};
|
||||
|
||||
const MediumScreen: ViewportScenario = {
|
||||
name: '13-inch Laptop', width: 1280, height: 800,
|
||||
};
|
||||
|
||||
export const LargeScreen: ViewportScenario = {
|
||||
name: '4K Ultra HD Desktop', width: 3840, height: 2160,
|
||||
};
|
||||
|
||||
export const ViewportTestScenarios: readonly ViewportScenario[] = [
|
||||
{ name: 'iPhone SE', width: 375, height: 667 },
|
||||
{ name: '13-inch Laptop', width: 1280, height: 800 },
|
||||
{ name: '4K Ultra HD Desktop', width: 3840, height: 2160 },
|
||||
SmallScreen,
|
||||
MediumScreen,
|
||||
LargeScreen,
|
||||
] as const;
|
||||
|
||||
interface ViewportScenario {
|
||||
|
||||
Reference in New Issue
Block a user