Skip to content

Commit 4873825

Browse files
committed
fix(material/tabs): use ResizeObserver to react to size changes
1 parent b01f682 commit 4873825

File tree

2 files changed

+39
-29
lines changed

2 files changed

+39
-29
lines changed

src/material/tabs/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ ng_module(
3030
"//src/cdk/coercion",
3131
"//src/cdk/keycodes",
3232
"//src/cdk/observers",
33+
"//src/cdk/observers/private",
3334
"//src/cdk/platform",
3435
"//src/cdk/portal",
3536
"//src/cdk/scrolling",

src/material/tabs/paginated-tab-header.ts

Lines changed: 38 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -6,41 +6,43 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9+
import {FocusKeyManager, FocusableOption} from '@angular/cdk/a11y';
10+
import {Direction, Directionality} from '@angular/cdk/bidi';
11+
import {ENTER, SPACE, hasModifierKey} from '@angular/cdk/keycodes';
12+
import {SharedResizeObserver} from '@angular/cdk/observers/private';
13+
import {Platform, normalizePassiveListenerOptions} from '@angular/cdk/platform';
14+
import {ViewportRuler} from '@angular/cdk/scrolling';
915
import {
10-
ChangeDetectorRef,
11-
ElementRef,
12-
NgZone,
13-
Optional,
14-
QueryList,
15-
EventEmitter,
16+
ANIMATION_MODULE_TYPE,
1617
AfterContentChecked,
1718
AfterContentInit,
1819
AfterViewInit,
19-
OnDestroy,
20+
ChangeDetectorRef,
2021
Directive,
22+
ElementRef,
23+
EventEmitter,
2124
Inject,
2225
Input,
26+
NgZone,
27+
OnDestroy,
28+
Optional,
29+
Output,
30+
QueryList,
2331
booleanAttribute,
32+
inject,
2433
numberAttribute,
25-
Output,
26-
ANIMATION_MODULE_TYPE,
2734
} from '@angular/core';
28-
import {Direction, Directionality} from '@angular/cdk/bidi';
29-
import {ViewportRuler} from '@angular/cdk/scrolling';
30-
import {FocusKeyManager, FocusableOption} from '@angular/cdk/a11y';
31-
import {ENTER, SPACE, hasModifierKey} from '@angular/cdk/keycodes';
3235
import {
33-
merge,
34-
of as observableOf,
35-
Subject,
3636
EMPTY,
37-
Observer,
3837
Observable,
39-
timer,
38+
Observer,
39+
Subject,
4040
fromEvent,
41+
merge,
42+
of as observableOf,
43+
timer,
4144
} from 'rxjs';
42-
import {take, switchMap, startWith, skip, takeUntil, filter} from 'rxjs/operators';
43-
import {Platform, normalizePassiveListenerOptions} from '@angular/cdk/platform';
45+
import {debounceTime, filter, skip, startWith, switchMap, takeUntil} from 'rxjs/operators';
4446

4547
/** Config used to bind passive event listeners */
4648
const passiveEventListenerOptions = normalizePassiveListenerOptions({
@@ -153,6 +155,8 @@ export abstract class MatPaginatedTabHeader
153155
/** Event emitted when a label is focused. */
154156
@Output() readonly indexFocused: EventEmitter<number> = new EventEmitter<number>();
155157

158+
private _sharedResizeObserver = inject(SharedResizeObserver);
159+
156160
constructor(
157161
protected _elementRef: ElementRef<HTMLElement>,
158162
protected _changeDetectorRef: ChangeDetectorRef,
@@ -192,7 +196,18 @@ export abstract class MatPaginatedTabHeader
192196

193197
ngAfterContentInit() {
194198
const dirChange = this._dir ? this._dir.change : observableOf('ltr');
195-
const resize = this._viewportRuler.change(150);
199+
// We need to debounce resize events because the alignment logic is expensive.
200+
// If someone animates the width of tabs, we don't want to realign on every animation frame.
201+
// Once we haven't seen any more resize events in the last 32ms (~2 animaion frames) we can
202+
// re-align.
203+
const resize = this._sharedResizeObserver
204+
.observe(this._elementRef.nativeElement)
205+
.pipe(debounceTime(32));
206+
// Note: we do not actually need to watch these events for proper functioning of the tabs,
207+
// the resize events above should capture any viewport resize that we care about. However,
208+
// removing this is fairly breaking for screenshot tests, so we're leaving it here for now.
209+
const viewportResize = this._viewportRuler.change(150);
210+
196211
const realign = () => {
197212
this.updatePagination();
198213
this._alignInkBarToSelectedTab();
@@ -207,15 +222,9 @@ export abstract class MatPaginatedTabHeader
207222

208223
this._keyManager.updateActiveItem(this._selectedIndex);
209224

210-
// Defer the first call in order to allow for slower browsers to lay out the elements.
211-
// This helps in cases where the user lands directly on a page with paginated tabs.
212-
// Note that we use `onStable` instead of `requestAnimationFrame`, because the latter
213-
// can hold up tests that are in a background tab.
214-
this._ngZone.onStable.pipe(take(1)).subscribe(realign);
215-
216-
// On dir change or window resize, realign the ink bar and update the orientation of
225+
// On dir change or resize, realign the ink bar and update the orientation of
217226
// the key manager if the direction has changed.
218-
merge(dirChange, resize, this._items.changes, this._itemsResized())
227+
merge(dirChange, viewportResize, resize, this._items.changes, this._itemsResized())
219228
.pipe(takeUntil(this._destroyed))
220229
.subscribe(() => {
221230
// We need to defer this to give the browser some time to recalculate

0 commit comments

Comments
 (0)