Skip to content

Commit 5dcbba7

Browse files
authored
fix(material/bottom-sheet): page jumping if backdrop-filter is applied (#30840)
The bottom sheet has an animation where it starts off-screen and animates in. At the same time it moves focus into itself. It seems like under certain conditions (e.g. having `backdrop-filter` on the `body`) this causes the entire page the jump in a jarring way due to the focus being moved. These changes resolve the issue by telling the browser not to scroll the page when moving focus. Fixes #30774.
1 parent 1089098 commit 5dcbba7

File tree

4 files changed

+22
-11
lines changed

4 files changed

+22
-11
lines changed

Diff for: goldens/cdk/dialog/index.api.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export class CdkDialogContainer<C extends DialogConfig = DialogConfig> extends B
5858
// (undocumented)
5959
protected _document: Document;
6060
// (undocumented)
61-
protected _elementRef: ElementRef<any>;
61+
protected _elementRef: ElementRef<HTMLElement>;
6262
// (undocumented)
6363
protected _focusTrapFactory: FocusTrapFactory;
6464
// (undocumented)
@@ -69,7 +69,7 @@ export class CdkDialogContainer<C extends DialogConfig = DialogConfig> extends B
6969
_recaptureFocus(): void;
7070
// (undocumented)
7171
_removeAriaLabelledBy(id: string): void;
72-
protected _trapFocus(): void;
72+
protected _trapFocus(options?: FocusOptions): void;
7373
// (undocumented)
7474
static ɵcmp: i0.ɵɵComponentDeclaration<CdkDialogContainer<any>, "cdk-dialog-container", never, {}, {}, never, never, true, never>;
7575
// (undocumented)

Diff for: goldens/material/bottom-sheet/index.api.md

+2
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ export class MatBottomSheetContainer extends CdkDialogContainer implements OnDes
8888
// (undocumented)
8989
ngOnDestroy(): void;
9090
// (undocumented)
91+
protected _trapFocus(): void;
92+
// (undocumented)
9193
static ɵcmp: i0.ɵɵComponentDeclaration<MatBottomSheetContainer, "mat-bottom-sheet-container", never, {}, {}, never, never, true, never>;
9294
// (undocumented)
9395
static ɵfac: i0.ɵɵFactoryDeclaration<MatBottomSheetContainer, never>;

Diff for: src/cdk/dialog/dialog-container.ts

+9-9
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export class CdkDialogContainer<C extends DialogConfig = DialogConfig>
7373
extends BasePortalOutlet
7474
implements OnDestroy
7575
{
76-
protected _elementRef = inject(ElementRef);
76+
protected _elementRef = inject<ElementRef<HTMLElement>>(ElementRef);
7777
protected _focusTrapFactory = inject(FocusTrapFactory);
7878
readonly _config: C;
7979
private _interactivityChecker = inject(InteractivityChecker);
@@ -254,7 +254,7 @@ export class CdkDialogContainer<C extends DialogConfig = DialogConfig>
254254
* Moves the focus inside the focus trap. When autoFocus is not set to 'dialog', if focus
255255
* cannot be moved then focus will go to the dialog container.
256256
*/
257-
protected _trapFocus() {
257+
protected _trapFocus(options?: FocusOptions) {
258258
if (this._isDestroyed) {
259259
return;
260260
}
@@ -274,23 +274,23 @@ export class CdkDialogContainer<C extends DialogConfig = DialogConfig>
274274
// if the focus isn't inside the dialog already, because it's possible that the consumer
275275
// turned off `autoFocus` in order to move focus themselves.
276276
if (!this._containsFocus()) {
277-
element.focus();
277+
element.focus(options);
278278
}
279279
break;
280280
case true:
281281
case 'first-tabbable':
282-
const focusedSuccessfully = this._focusTrap?.focusInitialElement();
282+
const focusedSuccessfully = this._focusTrap?.focusInitialElement(options);
283283
// If we weren't able to find a focusable element in the dialog, then focus the dialog
284284
// container instead.
285285
if (!focusedSuccessfully) {
286-
this._focusDialogContainer();
286+
this._focusDialogContainer(options);
287287
}
288288
break;
289289
case 'first-heading':
290-
this._focusByCssSelector('h1, h2, h3, h4, h5, h6, [role="heading"]');
290+
this._focusByCssSelector('h1, h2, h3, h4, h5, h6, [role="heading"]', options);
291291
break;
292292
default:
293-
this._focusByCssSelector(this._config.autoFocus!);
293+
this._focusByCssSelector(this._config.autoFocus!, options);
294294
break;
295295
}
296296
},
@@ -345,10 +345,10 @@ export class CdkDialogContainer<C extends DialogConfig = DialogConfig>
345345
}
346346

347347
/** Focuses the dialog container. */
348-
private _focusDialogContainer() {
348+
private _focusDialogContainer(options?: FocusOptions) {
349349
// Note that there is no focus method when rendering on the server.
350350
if (this._elementRef.nativeElement.focus) {
351-
this._elementRef.nativeElement.focus();
351+
this._elementRef.nativeElement.focus(options);
352352
}
353353
}
354354

Diff for: src/material/bottom-sheet/bottom-sheet-container.ts

+9
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,15 @@ export class MatBottomSheetContainer extends CdkDialogContainer implements OnDes
132132
});
133133
}
134134

135+
protected override _trapFocus(): void {
136+
// The bottom sheet starts off-screen and animates in, and at the same time we trap focus
137+
// within it. With some styles this appears to cause the page to jump around. See:
138+
// https://github.com/angular/components/issues/30774. Preventing the browser from
139+
// scrolling resolves the issue and isn't really necessary since the bottom sheet
140+
// normally isn't scrollable.
141+
super._trapFocus({preventScroll: true});
142+
}
143+
135144
protected _handleAnimationEvent(isStart: boolean, animationName: string) {
136145
const isEnter = animationName === ENTER_ANIMATION;
137146
const isExit = animationName === EXIT_ANIMATION;

0 commit comments

Comments
 (0)