6
6
* found in the LICENSE file at https://angular.io/license
7
7
*/
8
8
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' ;
9
15
import {
10
- ChangeDetectorRef ,
11
- ElementRef ,
12
- NgZone ,
13
- Optional ,
14
- QueryList ,
15
- EventEmitter ,
16
+ ANIMATION_MODULE_TYPE ,
16
17
AfterContentChecked ,
17
18
AfterContentInit ,
18
19
AfterViewInit ,
19
- OnDestroy ,
20
+ ChangeDetectorRef ,
20
21
Directive ,
22
+ ElementRef ,
23
+ EventEmitter ,
21
24
Inject ,
22
25
Input ,
26
+ NgZone ,
27
+ OnDestroy ,
28
+ Optional ,
29
+ Output ,
30
+ QueryList ,
23
31
booleanAttribute ,
32
+ inject ,
24
33
numberAttribute ,
25
- Output ,
26
- ANIMATION_MODULE_TYPE ,
27
34
} 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' ;
32
35
import {
33
- merge ,
34
- of as observableOf ,
35
- Subject ,
36
36
EMPTY ,
37
- Observer ,
38
37
Observable ,
39
- timer ,
38
+ Observer ,
39
+ Subject ,
40
40
fromEvent ,
41
+ merge ,
42
+ of as observableOf ,
43
+ timer ,
41
44
} 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' ;
44
46
45
47
/** Config used to bind passive event listeners */
46
48
const passiveEventListenerOptions = normalizePassiveListenerOptions ( {
@@ -153,6 +155,8 @@ export abstract class MatPaginatedTabHeader
153
155
/** Event emitted when a label is focused. */
154
156
@Output ( ) readonly indexFocused : EventEmitter < number > = new EventEmitter < number > ( ) ;
155
157
158
+ private _sharedResizeObserver = inject ( SharedResizeObserver ) ;
159
+
156
160
constructor (
157
161
protected _elementRef : ElementRef < HTMLElement > ,
158
162
protected _changeDetectorRef : ChangeDetectorRef ,
@@ -192,7 +196,18 @@ export abstract class MatPaginatedTabHeader
192
196
193
197
ngAfterContentInit ( ) {
194
198
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
+
196
211
const realign = ( ) => {
197
212
this . updatePagination ( ) ;
198
213
this . _alignInkBarToSelectedTab ( ) ;
@@ -207,15 +222,9 @@ export abstract class MatPaginatedTabHeader
207
222
208
223
this . _keyManager . updateActiveItem ( this . _selectedIndex ) ;
209
224
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
217
226
// 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 ( ) )
219
228
. pipe ( takeUntil ( this . _destroyed ) )
220
229
. subscribe ( ( ) => {
221
230
// We need to defer this to give the browser some time to recalculate
0 commit comments