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 ,
25
+ Injector ,
22
26
Input ,
27
+ NgZone ,
28
+ OnDestroy ,
29
+ Optional ,
30
+ Output ,
31
+ QueryList ,
32
+ afterNextRender ,
23
33
booleanAttribute ,
34
+ inject ,
24
35
numberAttribute ,
25
- Output ,
26
- ANIMATION_MODULE_TYPE ,
27
36
} 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
37
import {
33
- merge ,
34
- of as observableOf ,
35
- Subject ,
36
38
EMPTY ,
37
- Observer ,
38
39
Observable ,
39
- timer ,
40
+ Observer ,
41
+ Subject ,
40
42
fromEvent ,
43
+ merge ,
44
+ of as observableOf ,
45
+ timer ,
41
46
} from 'rxjs' ;
42
- import { take , switchMap , startWith , skip , takeUntil , filter } from 'rxjs/operators' ;
43
- import { Platform , normalizePassiveListenerOptions } from '@angular/cdk/platform' ;
47
+ import { debounceTime , filter , skip , startWith , switchMap , takeUntil } from 'rxjs/operators' ;
44
48
45
49
/** Config used to bind passive event listeners */
46
50
const passiveEventListenerOptions = normalizePassiveListenerOptions ( {
@@ -153,6 +157,10 @@ export abstract class MatPaginatedTabHeader
153
157
/** Event emitted when a label is focused. */
154
158
@Output ( ) readonly indexFocused : EventEmitter < number > = new EventEmitter < number > ( ) ;
155
159
160
+ private _sharedResizeObserver = inject ( SharedResizeObserver ) ;
161
+
162
+ private _injector = inject ( Injector ) ;
163
+
156
164
constructor (
157
165
protected _elementRef : ElementRef < HTMLElement > ,
158
166
protected _changeDetectorRef : ChangeDetectorRef ,
@@ -192,7 +200,18 @@ export abstract class MatPaginatedTabHeader
192
200
193
201
ngAfterContentInit ( ) {
194
202
const dirChange = this . _dir ? this . _dir . change : observableOf ( 'ltr' ) ;
195
- const resize = this . _viewportRuler . change ( 150 ) ;
203
+ // We need to debounce resize events because the alignment logic is expensive.
204
+ // If someone animates the width of tabs, we don't want to realign on every animation frame.
205
+ // Once we haven't seen any more resize events in the last 32ms (~2 animaion frames) we can
206
+ // re-align.
207
+ const resize = this . _sharedResizeObserver
208
+ . observe ( this . _elementRef . nativeElement )
209
+ . pipe ( debounceTime ( 32 ) , takeUntil ( this . _destroyed ) ) ;
210
+ // Note: We do not actually need to watch these events for proper functioning of the tabs,
211
+ // the resize events above should capture any viewport resize that we care about. However,
212
+ // removing this is fairly breaking for screenshot tests, so we're leaving it here for now.
213
+ const viewportResize = this . _viewportRuler . change ( 150 ) . pipe ( takeUntil ( this . _destroyed ) ) ;
214
+
196
215
const realign = ( ) => {
197
216
this . updatePagination ( ) ;
198
217
this . _alignInkBarToSelectedTab ( ) ;
@@ -207,15 +226,14 @@ export abstract class MatPaginatedTabHeader
207
226
208
227
this . _keyManager . updateActiveItem ( this . _selectedIndex ) ;
209
228
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 ) ;
229
+ // Note: We do not need to realign after the first render for proper functioning of the tabs
230
+ // the resize events above should fire when we first start observing the element. However,
231
+ // removing this is fairly breaking for screenshot tests, so we're leaving it here for now.
232
+ afterNextRender ( realign , { injector : this . _injector } ) ;
215
233
216
- // On dir change or window resize, realign the ink bar and update the orientation of
234
+ // On dir change or resize, realign the ink bar and update the orientation of
217
235
// the key manager if the direction has changed.
218
- merge ( dirChange , resize , this . _items . changes , this . _itemsResized ( ) )
236
+ merge ( dirChange , viewportResize , resize , this . _items . changes , this . _itemsResized ( ) )
219
237
. pipe ( takeUntil ( this . _destroyed ) )
220
238
. subscribe ( ( ) => {
221
239
// We need to defer this to give the browser some time to recalculate
0 commit comments