tabs.js 81 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014
  1. /**
  2. * @license
  3. * Copyright Google LLC All Rights Reserved.
  4. *
  5. * Use of this source code is governed by an MIT-style license that can be
  6. * found in the LICENSE file at https://angular.io/license
  7. */
  8. import { Directive, ElementRef, Inject, InjectionToken, NgZone, Optional, TemplateRef, ChangeDetectionStrategy, Component, ContentChild, Input, ViewChild, ViewContainerRef, ViewEncapsulation, ChangeDetectorRef, Output, EventEmitter, ComponentFactoryResolver, forwardRef, ContentChildren, Attribute, NgModule } from '@angular/core';
  9. import { ANIMATION_MODULE_TYPE } from '@angular/platform-browser/animations';
  10. import { CdkPortal, TemplatePortal, CdkPortalOutlet, PortalHostDirective, PortalModule } from '@angular/cdk/portal';
  11. import { mixinDisabled, mixinColor, mixinDisableRipple, MAT_RIPPLE_GLOBAL_OPTIONS, mixinTabIndex, RippleRenderer, MatCommonModule, MatRippleModule } from '@angular/material/core';
  12. import { Subject, Subscription, merge, of, timer, fromEvent } from 'rxjs';
  13. import { animate, state, style, transition, trigger } from '@angular/animations';
  14. import { Directionality } from '@angular/cdk/bidi';
  15. import { startWith, distinctUntilChanged, takeUntil } from 'rxjs/operators';
  16. import { coerceNumberProperty, coerceBooleanProperty } from '@angular/cdk/coercion';
  17. import { ViewportRuler } from '@angular/cdk/scrolling';
  18. import { FocusKeyManager, FocusMonitor, A11yModule } from '@angular/cdk/a11y';
  19. import { END, ENTER, HOME, SPACE, hasModifierKey } from '@angular/cdk/keycodes';
  20. import { Platform, normalizePassiveListenerOptions } from '@angular/cdk/platform';
  21. import { ObserversModule } from '@angular/cdk/observers';
  22. import { CommonModule } from '@angular/common';
  23. /**
  24. * @fileoverview added by tsickle
  25. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  26. */
  27. /**
  28. * Injection token for the MatInkBar's Positioner.
  29. * @type {?}
  30. */
  31. const _MAT_INK_BAR_POSITIONER = new InjectionToken('MatInkBarPositioner', {
  32. providedIn: 'root',
  33. factory: _MAT_INK_BAR_POSITIONER_FACTORY
  34. });
  35. /**
  36. * The default positioner function for the MatInkBar.
  37. * \@docs-private
  38. * @return {?}
  39. */
  40. function _MAT_INK_BAR_POSITIONER_FACTORY() {
  41. /** @type {?} */
  42. const method = (/**
  43. * @param {?} element
  44. * @return {?}
  45. */
  46. (element) => ({
  47. left: element ? (element.offsetLeft || 0) + 'px' : '0',
  48. width: element ? (element.offsetWidth || 0) + 'px' : '0',
  49. }));
  50. return method;
  51. }
  52. /**
  53. * The ink-bar is used to display and animate the line underneath the current active tab label.
  54. * \@docs-private
  55. */
  56. class MatInkBar {
  57. /**
  58. * @param {?} _elementRef
  59. * @param {?} _ngZone
  60. * @param {?} _inkBarPositioner
  61. * @param {?=} _animationMode
  62. */
  63. constructor(_elementRef, _ngZone, _inkBarPositioner, _animationMode) {
  64. this._elementRef = _elementRef;
  65. this._ngZone = _ngZone;
  66. this._inkBarPositioner = _inkBarPositioner;
  67. this._animationMode = _animationMode;
  68. }
  69. /**
  70. * Calculates the styles from the provided element in order to align the ink-bar to that element.
  71. * Shows the ink bar if previously set as hidden.
  72. * @param {?} element
  73. * @return {?}
  74. */
  75. alignToElement(element) {
  76. this.show();
  77. if (typeof requestAnimationFrame !== 'undefined') {
  78. this._ngZone.runOutsideAngular((/**
  79. * @return {?}
  80. */
  81. () => {
  82. requestAnimationFrame((/**
  83. * @return {?}
  84. */
  85. () => this._setStyles(element)));
  86. }));
  87. }
  88. else {
  89. this._setStyles(element);
  90. }
  91. }
  92. /**
  93. * Shows the ink bar.
  94. * @return {?}
  95. */
  96. show() {
  97. this._elementRef.nativeElement.style.visibility = 'visible';
  98. }
  99. /**
  100. * Hides the ink bar.
  101. * @return {?}
  102. */
  103. hide() {
  104. this._elementRef.nativeElement.style.visibility = 'hidden';
  105. }
  106. /**
  107. * Sets the proper styles to the ink bar element.
  108. * @private
  109. * @param {?} element
  110. * @return {?}
  111. */
  112. _setStyles(element) {
  113. /** @type {?} */
  114. const positions = this._inkBarPositioner(element);
  115. /** @type {?} */
  116. const inkBar = this._elementRef.nativeElement;
  117. inkBar.style.left = positions.left;
  118. inkBar.style.width = positions.width;
  119. }
  120. }
  121. MatInkBar.decorators = [
  122. { type: Directive, args: [{
  123. selector: 'mat-ink-bar',
  124. host: {
  125. 'class': 'mat-ink-bar',
  126. '[class._mat-animation-noopable]': `_animationMode === 'NoopAnimations'`,
  127. },
  128. },] },
  129. ];
  130. /** @nocollapse */
  131. MatInkBar.ctorParameters = () => [
  132. { type: ElementRef },
  133. { type: NgZone },
  134. { type: undefined, decorators: [{ type: Inject, args: [_MAT_INK_BAR_POSITIONER,] }] },
  135. { type: String, decorators: [{ type: Optional }, { type: Inject, args: [ANIMATION_MODULE_TYPE,] }] }
  136. ];
  137. /**
  138. * @fileoverview added by tsickle
  139. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  140. */
  141. /**
  142. * Decorates the `ng-template` tags and reads out the template from it.
  143. */
  144. class MatTabContent {
  145. /**
  146. * @param {?} template
  147. */
  148. constructor(template) {
  149. this.template = template;
  150. }
  151. }
  152. MatTabContent.decorators = [
  153. { type: Directive, args: [{ selector: '[matTabContent]' },] },
  154. ];
  155. /** @nocollapse */
  156. MatTabContent.ctorParameters = () => [
  157. { type: TemplateRef }
  158. ];
  159. /**
  160. * @fileoverview added by tsickle
  161. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  162. */
  163. /**
  164. * Used to flag tab labels for use with the portal directive
  165. */
  166. class MatTabLabel extends CdkPortal {
  167. }
  168. MatTabLabel.decorators = [
  169. { type: Directive, args: [{
  170. selector: '[mat-tab-label], [matTabLabel]',
  171. },] },
  172. ];
  173. /**
  174. * @fileoverview added by tsickle
  175. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  176. */
  177. // Boilerplate for applying mixins to MatTab.
  178. /**
  179. * \@docs-private
  180. */
  181. class MatTabBase {
  182. }
  183. /** @type {?} */
  184. const _MatTabMixinBase = mixinDisabled(MatTabBase);
  185. class MatTab extends _MatTabMixinBase {
  186. /**
  187. * @param {?} _viewContainerRef
  188. */
  189. constructor(_viewContainerRef) {
  190. super();
  191. this._viewContainerRef = _viewContainerRef;
  192. /**
  193. * Plain text label for the tab, used when there is no template label.
  194. */
  195. this.textLabel = '';
  196. /**
  197. * Portal that will be the hosted content of the tab
  198. */
  199. this._contentPortal = null;
  200. /**
  201. * Emits whenever the internal state of the tab changes.
  202. */
  203. this._stateChanges = new Subject();
  204. /**
  205. * The relatively indexed position where 0 represents the center, negative is left, and positive
  206. * represents the right.
  207. */
  208. this.position = null;
  209. /**
  210. * The initial relatively index origin of the tab if it was created and selected after there
  211. * was already a selected tab. Provides context of what position the tab should originate from.
  212. */
  213. this.origin = null;
  214. /**
  215. * Whether the tab is currently active.
  216. */
  217. this.isActive = false;
  218. }
  219. /**
  220. * \@docs-private
  221. * @return {?}
  222. */
  223. get content() {
  224. return this._contentPortal;
  225. }
  226. /**
  227. * @param {?} changes
  228. * @return {?}
  229. */
  230. ngOnChanges(changes) {
  231. if (changes.hasOwnProperty('textLabel') || changes.hasOwnProperty('disabled')) {
  232. this._stateChanges.next();
  233. }
  234. }
  235. /**
  236. * @return {?}
  237. */
  238. ngOnDestroy() {
  239. this._stateChanges.complete();
  240. }
  241. /**
  242. * @return {?}
  243. */
  244. ngOnInit() {
  245. this._contentPortal = new TemplatePortal(this._explicitContent || this._implicitContent, this._viewContainerRef);
  246. }
  247. }
  248. MatTab.decorators = [
  249. { type: Component, args: [{selector: 'mat-tab',
  250. template: "<ng-template><ng-content></ng-content></ng-template>",
  251. inputs: ['disabled'],
  252. changeDetection: ChangeDetectionStrategy.OnPush,
  253. encapsulation: ViewEncapsulation.None,
  254. exportAs: 'matTab',
  255. },] },
  256. ];
  257. /** @nocollapse */
  258. MatTab.ctorParameters = () => [
  259. { type: ViewContainerRef }
  260. ];
  261. MatTab.propDecorators = {
  262. templateLabel: [{ type: ContentChild, args: [MatTabLabel, { static: false },] }],
  263. _explicitContent: [{ type: ContentChild, args: [MatTabContent, { read: TemplateRef, static: true },] }],
  264. _implicitContent: [{ type: ViewChild, args: [TemplateRef, { static: true },] }],
  265. textLabel: [{ type: Input, args: ['label',] }],
  266. ariaLabel: [{ type: Input, args: ['aria-label',] }],
  267. ariaLabelledby: [{ type: Input, args: ['aria-labelledby',] }]
  268. };
  269. /**
  270. * @fileoverview added by tsickle
  271. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  272. */
  273. /**
  274. * Animations used by the Material tabs.
  275. * \@docs-private
  276. * @type {?}
  277. */
  278. const matTabsAnimations = {
  279. /**
  280. * Animation translates a tab along the X axis.
  281. */
  282. translateTab: trigger('translateTab', [
  283. // Note: transitions to `none` instead of 0, because some browsers might blur the content.
  284. state('center, void, left-origin-center, right-origin-center', style({ transform: 'none' })),
  285. // If the tab is either on the left or right, we additionally add a `min-height` of 1px
  286. // in order to ensure that the element has a height before its state changes. This is
  287. // necessary because Chrome does seem to skip the transition in RTL mode if the element does
  288. // not have a static height and is not rendered. See related issue: #9465
  289. state('left', style({ transform: 'translate3d(-100%, 0, 0)', minHeight: '1px' })),
  290. state('right', style({ transform: 'translate3d(100%, 0, 0)', minHeight: '1px' })),
  291. transition('* => left, * => right, left => center, right => center', animate('{{animationDuration}} cubic-bezier(0.35, 0, 0.25, 1)')),
  292. transition('void => left-origin-center', [
  293. style({ transform: 'translate3d(-100%, 0, 0)' }),
  294. animate('{{animationDuration}} cubic-bezier(0.35, 0, 0.25, 1)')
  295. ]),
  296. transition('void => right-origin-center', [
  297. style({ transform: 'translate3d(100%, 0, 0)' }),
  298. animate('{{animationDuration}} cubic-bezier(0.35, 0, 0.25, 1)')
  299. ])
  300. ])
  301. };
  302. /**
  303. * @fileoverview added by tsickle
  304. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  305. */
  306. /**
  307. * The portal host directive for the contents of the tab.
  308. * \@docs-private
  309. */
  310. class MatTabBodyPortal extends CdkPortalOutlet {
  311. /**
  312. * @param {?} componentFactoryResolver
  313. * @param {?} viewContainerRef
  314. * @param {?} _host
  315. */
  316. constructor(componentFactoryResolver, viewContainerRef, _host) {
  317. super(componentFactoryResolver, viewContainerRef);
  318. this._host = _host;
  319. /**
  320. * Subscription to events for when the tab body begins centering.
  321. */
  322. this._centeringSub = Subscription.EMPTY;
  323. /**
  324. * Subscription to events for when the tab body finishes leaving from center position.
  325. */
  326. this._leavingSub = Subscription.EMPTY;
  327. }
  328. /**
  329. * Set initial visibility or set up subscription for changing visibility.
  330. * @return {?}
  331. */
  332. ngOnInit() {
  333. super.ngOnInit();
  334. this._centeringSub = this._host._beforeCentering
  335. .pipe(startWith(this._host._isCenterPosition(this._host._position)))
  336. .subscribe((/**
  337. * @param {?} isCentering
  338. * @return {?}
  339. */
  340. (isCentering) => {
  341. if (isCentering && !this.hasAttached()) {
  342. this.attach(this._host._content);
  343. }
  344. }));
  345. this._leavingSub = this._host._afterLeavingCenter.subscribe((/**
  346. * @return {?}
  347. */
  348. () => {
  349. this.detach();
  350. }));
  351. }
  352. /**
  353. * Clean up centering subscription.
  354. * @return {?}
  355. */
  356. ngOnDestroy() {
  357. super.ngOnDestroy();
  358. this._centeringSub.unsubscribe();
  359. this._leavingSub.unsubscribe();
  360. }
  361. }
  362. MatTabBodyPortal.decorators = [
  363. { type: Directive, args: [{
  364. selector: '[matTabBodyHost]'
  365. },] },
  366. ];
  367. /** @nocollapse */
  368. MatTabBodyPortal.ctorParameters = () => [
  369. { type: ComponentFactoryResolver },
  370. { type: ViewContainerRef },
  371. { type: MatTabBody, decorators: [{ type: Inject, args: [forwardRef((/**
  372. * @return {?}
  373. */
  374. () => MatTabBody)),] }] }
  375. ];
  376. /**
  377. * Wrapper for the contents of a tab.
  378. * \@docs-private
  379. */
  380. class MatTabBody {
  381. /**
  382. * @param {?} _elementRef
  383. * @param {?} _dir
  384. * @param {?} changeDetectorRef
  385. */
  386. constructor(_elementRef, _dir, changeDetectorRef) {
  387. this._elementRef = _elementRef;
  388. this._dir = _dir;
  389. /**
  390. * Subscription to the directionality change observable.
  391. */
  392. this._dirChangeSubscription = Subscription.EMPTY;
  393. /**
  394. * Emits when an animation on the tab is complete.
  395. */
  396. this._translateTabComplete = new Subject();
  397. /**
  398. * Event emitted when the tab begins to animate towards the center as the active tab.
  399. */
  400. this._onCentering = new EventEmitter();
  401. /**
  402. * Event emitted before the centering of the tab begins.
  403. */
  404. this._beforeCentering = new EventEmitter();
  405. /**
  406. * Event emitted before the centering of the tab begins.
  407. */
  408. this._afterLeavingCenter = new EventEmitter();
  409. /**
  410. * Event emitted when the tab completes its animation towards the center.
  411. */
  412. this._onCentered = new EventEmitter(true);
  413. // Note that the default value will always be overwritten by `MatTabBody`, but we need one
  414. // anyway to prevent the animations module from throwing an error if the body is used on its own.
  415. /**
  416. * Duration for the tab's animation.
  417. */
  418. this.animationDuration = '500ms';
  419. if (_dir) {
  420. this._dirChangeSubscription = _dir.change.subscribe((/**
  421. * @param {?} dir
  422. * @return {?}
  423. */
  424. (dir) => {
  425. this._computePositionAnimationState(dir);
  426. changeDetectorRef.markForCheck();
  427. }));
  428. }
  429. // Ensure that we get unique animation events, because the `.done` callback can get
  430. // invoked twice in some browsers. See https://github.com/angular/angular/issues/24084.
  431. this._translateTabComplete.pipe(distinctUntilChanged((/**
  432. * @param {?} x
  433. * @param {?} y
  434. * @return {?}
  435. */
  436. (x, y) => {
  437. return x.fromState === y.fromState && x.toState === y.toState;
  438. }))).subscribe((/**
  439. * @param {?} event
  440. * @return {?}
  441. */
  442. event => {
  443. // If the transition to the center is complete, emit an event.
  444. if (this._isCenterPosition(event.toState) && this._isCenterPosition(this._position)) {
  445. this._onCentered.emit();
  446. }
  447. if (this._isCenterPosition(event.fromState) && !this._isCenterPosition(this._position)) {
  448. this._afterLeavingCenter.emit();
  449. }
  450. }));
  451. }
  452. /**
  453. * The shifted index position of the tab body, where zero represents the active center tab.
  454. * @param {?} position
  455. * @return {?}
  456. */
  457. set position(position) {
  458. this._positionIndex = position;
  459. this._computePositionAnimationState();
  460. }
  461. /**
  462. * After initialized, check if the content is centered and has an origin. If so, set the
  463. * special position states that transition the tab from the left or right before centering.
  464. * @return {?}
  465. */
  466. ngOnInit() {
  467. if (this._position == 'center' && this.origin != null) {
  468. this._position = this._computePositionFromOrigin();
  469. }
  470. }
  471. /**
  472. * @return {?}
  473. */
  474. ngOnDestroy() {
  475. this._dirChangeSubscription.unsubscribe();
  476. this._translateTabComplete.complete();
  477. }
  478. /**
  479. * @param {?} event
  480. * @return {?}
  481. */
  482. _onTranslateTabStarted(event) {
  483. /** @type {?} */
  484. const isCentering = this._isCenterPosition(event.toState);
  485. this._beforeCentering.emit(isCentering);
  486. if (isCentering) {
  487. this._onCentering.emit(this._elementRef.nativeElement.clientHeight);
  488. }
  489. }
  490. /**
  491. * The text direction of the containing app.
  492. * @return {?}
  493. */
  494. _getLayoutDirection() {
  495. return this._dir && this._dir.value === 'rtl' ? 'rtl' : 'ltr';
  496. }
  497. /**
  498. * Whether the provided position state is considered center, regardless of origin.
  499. * @param {?} position
  500. * @return {?}
  501. */
  502. _isCenterPosition(position) {
  503. return position == 'center' ||
  504. position == 'left-origin-center' ||
  505. position == 'right-origin-center';
  506. }
  507. /**
  508. * Computes the position state that will be used for the tab-body animation trigger.
  509. * @private
  510. * @param {?=} dir
  511. * @return {?}
  512. */
  513. _computePositionAnimationState(dir = this._getLayoutDirection()) {
  514. if (this._positionIndex < 0) {
  515. this._position = dir == 'ltr' ? 'left' : 'right';
  516. }
  517. else if (this._positionIndex > 0) {
  518. this._position = dir == 'ltr' ? 'right' : 'left';
  519. }
  520. else {
  521. this._position = 'center';
  522. }
  523. }
  524. /**
  525. * Computes the position state based on the specified origin position. This is used if the
  526. * tab is becoming visible immediately after creation.
  527. * @private
  528. * @return {?}
  529. */
  530. _computePositionFromOrigin() {
  531. /** @type {?} */
  532. const dir = this._getLayoutDirection();
  533. if ((dir == 'ltr' && this.origin <= 0) || (dir == 'rtl' && this.origin > 0)) {
  534. return 'left-origin-center';
  535. }
  536. return 'right-origin-center';
  537. }
  538. }
  539. MatTabBody.decorators = [
  540. { type: Component, args: [{selector: 'mat-tab-body',
  541. template: "<div class=\"mat-tab-body-content\" #content [@translateTab]=\"{ value: _position, params: {animationDuration: animationDuration} }\" (@translateTab.start)=\"_onTranslateTabStarted($event)\" (@translateTab.done)=\"_translateTabComplete.next($event)\"><ng-template matTabBodyHost></ng-template></div>",
  542. styles: [".mat-tab-body-content{height:100%;overflow:auto}.mat-tab-group-dynamic-height .mat-tab-body-content{overflow:hidden}"],
  543. encapsulation: ViewEncapsulation.None,
  544. changeDetection: ChangeDetectionStrategy.OnPush,
  545. animations: [matTabsAnimations.translateTab],
  546. host: {
  547. 'class': 'mat-tab-body',
  548. },
  549. },] },
  550. ];
  551. /** @nocollapse */
  552. MatTabBody.ctorParameters = () => [
  553. { type: ElementRef },
  554. { type: Directionality, decorators: [{ type: Optional }] },
  555. { type: ChangeDetectorRef }
  556. ];
  557. MatTabBody.propDecorators = {
  558. _onCentering: [{ type: Output }],
  559. _beforeCentering: [{ type: Output }],
  560. _afterLeavingCenter: [{ type: Output }],
  561. _onCentered: [{ type: Output }],
  562. _portalHost: [{ type: ViewChild, args: [PortalHostDirective, { static: false },] }],
  563. _content: [{ type: Input, args: ['content',] }],
  564. origin: [{ type: Input }],
  565. animationDuration: [{ type: Input }],
  566. position: [{ type: Input }]
  567. };
  568. /**
  569. * @fileoverview added by tsickle
  570. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  571. */
  572. // Boilerplate for applying mixins to MatTabLabelWrapper.
  573. /**
  574. * \@docs-private
  575. */
  576. class MatTabLabelWrapperBase {
  577. }
  578. /** @type {?} */
  579. const _MatTabLabelWrapperMixinBase = mixinDisabled(MatTabLabelWrapperBase);
  580. /**
  581. * Used in the `mat-tab-group` view to display tab labels.
  582. * \@docs-private
  583. */
  584. class MatTabLabelWrapper extends _MatTabLabelWrapperMixinBase {
  585. /**
  586. * @param {?} elementRef
  587. */
  588. constructor(elementRef) {
  589. super();
  590. this.elementRef = elementRef;
  591. }
  592. /**
  593. * Sets focus on the wrapper element
  594. * @return {?}
  595. */
  596. focus() {
  597. this.elementRef.nativeElement.focus();
  598. }
  599. /**
  600. * @return {?}
  601. */
  602. getOffsetLeft() {
  603. return this.elementRef.nativeElement.offsetLeft;
  604. }
  605. /**
  606. * @return {?}
  607. */
  608. getOffsetWidth() {
  609. return this.elementRef.nativeElement.offsetWidth;
  610. }
  611. }
  612. MatTabLabelWrapper.decorators = [
  613. { type: Directive, args: [{
  614. selector: '[matTabLabelWrapper]',
  615. inputs: ['disabled'],
  616. host: {
  617. '[class.mat-tab-disabled]': 'disabled',
  618. '[attr.aria-disabled]': '!!disabled',
  619. }
  620. },] },
  621. ];
  622. /** @nocollapse */
  623. MatTabLabelWrapper.ctorParameters = () => [
  624. { type: ElementRef }
  625. ];
  626. /**
  627. * @fileoverview added by tsickle
  628. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  629. */
  630. /**
  631. * Config used to bind passive event listeners
  632. * @type {?}
  633. */
  634. const passiveEventListenerOptions = (/** @type {?} */ (normalizePassiveListenerOptions({ passive: true })));
  635. /**
  636. * The distance in pixels that will be overshot when scrolling a tab label into view. This helps
  637. * provide a small affordance to the label next to it.
  638. * @type {?}
  639. */
  640. const EXAGGERATED_OVERSCROLL = 60;
  641. /**
  642. * Amount of milliseconds to wait before starting to scroll the header automatically.
  643. * Set a little conservatively in order to handle fake events dispatched on touch devices.
  644. * @type {?}
  645. */
  646. const HEADER_SCROLL_DELAY = 650;
  647. /**
  648. * Interval in milliseconds at which to scroll the header
  649. * while the user is holding their pointer.
  650. * @type {?}
  651. */
  652. const HEADER_SCROLL_INTERVAL = 100;
  653. /**
  654. * Base class for a tab header that supported pagination.
  655. * @abstract
  656. */
  657. class MatPaginatedTabHeader {
  658. /**
  659. * @param {?} _elementRef
  660. * @param {?} _changeDetectorRef
  661. * @param {?} _viewportRuler
  662. * @param {?} _dir
  663. * @param {?} _ngZone
  664. * @param {?=} _platform
  665. * @param {?=} _animationMode
  666. */
  667. constructor(_elementRef, _changeDetectorRef, _viewportRuler, _dir, _ngZone, _platform, _animationMode) {
  668. this._elementRef = _elementRef;
  669. this._changeDetectorRef = _changeDetectorRef;
  670. this._viewportRuler = _viewportRuler;
  671. this._dir = _dir;
  672. this._ngZone = _ngZone;
  673. this._platform = _platform;
  674. this._animationMode = _animationMode;
  675. /**
  676. * The distance in pixels that the tab labels should be translated to the left.
  677. */
  678. this._scrollDistance = 0;
  679. /**
  680. * Whether the header should scroll to the selected index after the view has been checked.
  681. */
  682. this._selectedIndexChanged = false;
  683. /**
  684. * Emits when the component is destroyed.
  685. */
  686. this._destroyed = new Subject();
  687. /**
  688. * Whether the controls for pagination should be displayed
  689. */
  690. this._showPaginationControls = false;
  691. /**
  692. * Whether the tab list can be scrolled more towards the end of the tab label list.
  693. */
  694. this._disableScrollAfter = true;
  695. /**
  696. * Whether the tab list can be scrolled more towards the beginning of the tab label list.
  697. */
  698. this._disableScrollBefore = true;
  699. /**
  700. * Stream that will stop the automated scrolling.
  701. */
  702. this._stopScrolling = new Subject();
  703. this._selectedIndex = 0;
  704. /**
  705. * Event emitted when the option is selected.
  706. */
  707. this.selectFocusedIndex = new EventEmitter();
  708. /**
  709. * Event emitted when a label is focused.
  710. */
  711. this.indexFocused = new EventEmitter();
  712. // Bind the `mouseleave` event on the outside since it doesn't change anything in the view.
  713. _ngZone.runOutsideAngular((/**
  714. * @return {?}
  715. */
  716. () => {
  717. fromEvent(_elementRef.nativeElement, 'mouseleave')
  718. .pipe(takeUntil(this._destroyed))
  719. .subscribe((/**
  720. * @return {?}
  721. */
  722. () => {
  723. this._stopInterval();
  724. }));
  725. }));
  726. }
  727. /**
  728. * The index of the active tab.
  729. * @return {?}
  730. */
  731. get selectedIndex() { return this._selectedIndex; }
  732. /**
  733. * @param {?} value
  734. * @return {?}
  735. */
  736. set selectedIndex(value) {
  737. value = coerceNumberProperty(value);
  738. if (this._selectedIndex != value) {
  739. this._selectedIndexChanged = true;
  740. this._selectedIndex = value;
  741. if (this._keyManager) {
  742. this._keyManager.updateActiveItemIndex(value);
  743. }
  744. }
  745. }
  746. /**
  747. * @return {?}
  748. */
  749. ngAfterViewInit() {
  750. // We need to handle these events manually, because we want to bind passive event listeners.
  751. fromEvent(this._previousPaginator.nativeElement, 'touchstart', passiveEventListenerOptions)
  752. .pipe(takeUntil(this._destroyed))
  753. .subscribe((/**
  754. * @return {?}
  755. */
  756. () => {
  757. this._handlePaginatorPress('before');
  758. }));
  759. fromEvent(this._nextPaginator.nativeElement, 'touchstart', passiveEventListenerOptions)
  760. .pipe(takeUntil(this._destroyed))
  761. .subscribe((/**
  762. * @return {?}
  763. */
  764. () => {
  765. this._handlePaginatorPress('after');
  766. }));
  767. }
  768. /**
  769. * @return {?}
  770. */
  771. ngAfterContentInit() {
  772. /** @type {?} */
  773. const dirChange = this._dir ? this._dir.change : of(null);
  774. /** @type {?} */
  775. const resize = this._viewportRuler.change(150);
  776. /** @type {?} */
  777. const realign = (/**
  778. * @return {?}
  779. */
  780. () => {
  781. this.updatePagination();
  782. this._alignInkBarToSelectedTab();
  783. });
  784. this._keyManager = new FocusKeyManager(this._items)
  785. .withHorizontalOrientation(this._getLayoutDirection())
  786. .withWrap();
  787. this._keyManager.updateActiveItem(0);
  788. // Defer the first call in order to allow for slower browsers to lay out the elements.
  789. // This helps in cases where the user lands directly on a page with paginated tabs.
  790. typeof requestAnimationFrame !== 'undefined' ? requestAnimationFrame(realign) : realign();
  791. // On dir change or window resize, realign the ink bar and update the orientation of
  792. // the key manager if the direction has changed.
  793. merge(dirChange, resize, this._items.changes).pipe(takeUntil(this._destroyed)).subscribe((/**
  794. * @return {?}
  795. */
  796. () => {
  797. realign();
  798. this._keyManager.withHorizontalOrientation(this._getLayoutDirection());
  799. }));
  800. // If there is a change in the focus key manager we need to emit the `indexFocused`
  801. // event in order to provide a public event that notifies about focus changes. Also we realign
  802. // the tabs container by scrolling the new focused tab into the visible section.
  803. this._keyManager.change.pipe(takeUntil(this._destroyed)).subscribe((/**
  804. * @param {?} newFocusIndex
  805. * @return {?}
  806. */
  807. newFocusIndex => {
  808. this.indexFocused.emit(newFocusIndex);
  809. this._setTabFocus(newFocusIndex);
  810. }));
  811. }
  812. /**
  813. * @return {?}
  814. */
  815. ngAfterContentChecked() {
  816. // If the number of tab labels have changed, check if scrolling should be enabled
  817. if (this._tabLabelCount != this._items.length) {
  818. this.updatePagination();
  819. this._tabLabelCount = this._items.length;
  820. this._changeDetectorRef.markForCheck();
  821. }
  822. // If the selected index has changed, scroll to the label and check if the scrolling controls
  823. // should be disabled.
  824. if (this._selectedIndexChanged) {
  825. this._scrollToLabel(this._selectedIndex);
  826. this._checkScrollingControls();
  827. this._alignInkBarToSelectedTab();
  828. this._selectedIndexChanged = false;
  829. this._changeDetectorRef.markForCheck();
  830. }
  831. // If the scroll distance has been changed (tab selected, focused, scroll controls activated),
  832. // then translate the header to reflect this.
  833. if (this._scrollDistanceChanged) {
  834. this._updateTabScrollPosition();
  835. this._scrollDistanceChanged = false;
  836. this._changeDetectorRef.markForCheck();
  837. }
  838. }
  839. /**
  840. * @return {?}
  841. */
  842. ngOnDestroy() {
  843. this._destroyed.next();
  844. this._destroyed.complete();
  845. this._stopScrolling.complete();
  846. }
  847. /**
  848. * Handles keyboard events on the header.
  849. * @param {?} event
  850. * @return {?}
  851. */
  852. _handleKeydown(event) {
  853. // We don't handle any key bindings with a modifier key.
  854. if (hasModifierKey(event)) {
  855. return;
  856. }
  857. switch (event.keyCode) {
  858. case HOME:
  859. this._keyManager.setFirstItemActive();
  860. event.preventDefault();
  861. break;
  862. case END:
  863. this._keyManager.setLastItemActive();
  864. event.preventDefault();
  865. break;
  866. case ENTER:
  867. case SPACE:
  868. this.selectFocusedIndex.emit(this.focusIndex);
  869. this._itemSelected(event);
  870. break;
  871. default:
  872. this._keyManager.onKeydown(event);
  873. }
  874. }
  875. /**
  876. * Callback for when the MutationObserver detects that the content has changed.
  877. * @return {?}
  878. */
  879. _onContentChanges() {
  880. /** @type {?} */
  881. const textContent = this._elementRef.nativeElement.textContent;
  882. // We need to diff the text content of the header, because the MutationObserver callback
  883. // will fire even if the text content didn't change which is inefficient and is prone
  884. // to infinite loops if a poorly constructed expression is passed in (see #14249).
  885. if (textContent !== this._currentTextContent) {
  886. this._currentTextContent = textContent || '';
  887. // The content observer runs outside the `NgZone` by default, which
  888. // means that we need to bring the callback back in ourselves.
  889. this._ngZone.run((/**
  890. * @return {?}
  891. */
  892. () => {
  893. this.updatePagination();
  894. this._alignInkBarToSelectedTab();
  895. this._changeDetectorRef.markForCheck();
  896. }));
  897. }
  898. }
  899. /**
  900. * Updates the view whether pagination should be enabled or not.
  901. *
  902. * WARNING: Calling this method can be very costly in terms of performance. It should be called
  903. * as infrequently as possible from outside of the Tabs component as it causes a reflow of the
  904. * page.
  905. * @return {?}
  906. */
  907. updatePagination() {
  908. this._checkPaginationEnabled();
  909. this._checkScrollingControls();
  910. this._updateTabScrollPosition();
  911. }
  912. /**
  913. * Tracks which element has focus; used for keyboard navigation
  914. * @return {?}
  915. */
  916. get focusIndex() {
  917. return this._keyManager ? (/** @type {?} */ (this._keyManager.activeItemIndex)) : 0;
  918. }
  919. /**
  920. * When the focus index is set, we must manually send focus to the correct label
  921. * @param {?} value
  922. * @return {?}
  923. */
  924. set focusIndex(value) {
  925. if (!this._isValidIndex(value) || this.focusIndex === value || !this._keyManager) {
  926. return;
  927. }
  928. this._keyManager.setActiveItem(value);
  929. }
  930. /**
  931. * Determines if an index is valid. If the tabs are not ready yet, we assume that the user is
  932. * providing a valid index and return true.
  933. * @param {?} index
  934. * @return {?}
  935. */
  936. _isValidIndex(index) {
  937. if (!this._items) {
  938. return true;
  939. }
  940. /** @type {?} */
  941. const tab = this._items ? this._items.toArray()[index] : null;
  942. return !!tab && !tab.disabled;
  943. }
  944. /**
  945. * Sets focus on the HTML element for the label wrapper and scrolls it into the view if
  946. * scrolling is enabled.
  947. * @param {?} tabIndex
  948. * @return {?}
  949. */
  950. _setTabFocus(tabIndex) {
  951. if (this._showPaginationControls) {
  952. this._scrollToLabel(tabIndex);
  953. }
  954. if (this._items && this._items.length) {
  955. this._items.toArray()[tabIndex].focus();
  956. // Do not let the browser manage scrolling to focus the element, this will be handled
  957. // by using translation. In LTR, the scroll left should be 0. In RTL, the scroll width
  958. // should be the full width minus the offset width.
  959. /** @type {?} */
  960. const containerEl = this._tabListContainer.nativeElement;
  961. /** @type {?} */
  962. const dir = this._getLayoutDirection();
  963. if (dir == 'ltr') {
  964. containerEl.scrollLeft = 0;
  965. }
  966. else {
  967. containerEl.scrollLeft = containerEl.scrollWidth - containerEl.offsetWidth;
  968. }
  969. }
  970. }
  971. /**
  972. * The layout direction of the containing app.
  973. * @return {?}
  974. */
  975. _getLayoutDirection() {
  976. return this._dir && this._dir.value === 'rtl' ? 'rtl' : 'ltr';
  977. }
  978. /**
  979. * Performs the CSS transformation on the tab list that will cause the list to scroll.
  980. * @return {?}
  981. */
  982. _updateTabScrollPosition() {
  983. /** @type {?} */
  984. const scrollDistance = this.scrollDistance;
  985. /** @type {?} */
  986. const platform = this._platform;
  987. /** @type {?} */
  988. const translateX = this._getLayoutDirection() === 'ltr' ? -scrollDistance : scrollDistance;
  989. // Don't use `translate3d` here because we don't want to create a new layer. A new layer
  990. // seems to cause flickering and overflow in Internet Explorer. For example, the ink bar
  991. // and ripples will exceed the boundaries of the visible tab bar.
  992. // See: https://github.com/angular/components/issues/10276
  993. // We round the `transform` here, because transforms with sub-pixel precision cause some
  994. // browsers to blur the content of the element.
  995. this._tabList.nativeElement.style.transform = `translateX(${Math.round(translateX)}px)`;
  996. // Setting the `transform` on IE will change the scroll offset of the parent, causing the
  997. // position to be thrown off in some cases. We have to reset it ourselves to ensure that
  998. // it doesn't get thrown off. Note that we scope it only to IE and Edge, because messing
  999. // with the scroll position throws off Chrome 71+ in RTL mode (see #14689).
  1000. // @breaking-change 9.0.0 Remove null check for `platform` after it can no longer be undefined.
  1001. if (platform && (platform.TRIDENT || platform.EDGE)) {
  1002. this._tabListContainer.nativeElement.scrollLeft = 0;
  1003. }
  1004. }
  1005. /**
  1006. * Sets the distance in pixels that the tab header should be transformed in the X-axis.
  1007. * @return {?}
  1008. */
  1009. get scrollDistance() { return this._scrollDistance; }
  1010. /**
  1011. * @param {?} value
  1012. * @return {?}
  1013. */
  1014. set scrollDistance(value) {
  1015. this._scrollTo(value);
  1016. }
  1017. /**
  1018. * Moves the tab list in the 'before' or 'after' direction (towards the beginning of the list or
  1019. * the end of the list, respectively). The distance to scroll is computed to be a third of the
  1020. * length of the tab list view window.
  1021. *
  1022. * This is an expensive call that forces a layout reflow to compute box and scroll metrics and
  1023. * should be called sparingly.
  1024. * @param {?} direction
  1025. * @return {?}
  1026. */
  1027. _scrollHeader(direction) {
  1028. /** @type {?} */
  1029. const viewLength = this._tabListContainer.nativeElement.offsetWidth;
  1030. // Move the scroll distance one-third the length of the tab list's viewport.
  1031. /** @type {?} */
  1032. const scrollAmount = (direction == 'before' ? -1 : 1) * viewLength / 3;
  1033. return this._scrollTo(this._scrollDistance + scrollAmount);
  1034. }
  1035. /**
  1036. * Handles click events on the pagination arrows.
  1037. * @param {?} direction
  1038. * @return {?}
  1039. */
  1040. _handlePaginatorClick(direction) {
  1041. this._stopInterval();
  1042. this._scrollHeader(direction);
  1043. }
  1044. /**
  1045. * Moves the tab list such that the desired tab label (marked by index) is moved into view.
  1046. *
  1047. * This is an expensive call that forces a layout reflow to compute box and scroll metrics and
  1048. * should be called sparingly.
  1049. * @param {?} labelIndex
  1050. * @return {?}
  1051. */
  1052. _scrollToLabel(labelIndex) {
  1053. /** @type {?} */
  1054. const selectedLabel = this._items ? this._items.toArray()[labelIndex] : null;
  1055. if (!selectedLabel) {
  1056. return;
  1057. }
  1058. // The view length is the visible width of the tab labels.
  1059. /** @type {?} */
  1060. const viewLength = this._tabListContainer.nativeElement.offsetWidth;
  1061. const { offsetLeft, offsetWidth } = selectedLabel.elementRef.nativeElement;
  1062. /** @type {?} */
  1063. let labelBeforePos;
  1064. /** @type {?} */
  1065. let labelAfterPos;
  1066. if (this._getLayoutDirection() == 'ltr') {
  1067. labelBeforePos = offsetLeft;
  1068. labelAfterPos = labelBeforePos + offsetWidth;
  1069. }
  1070. else {
  1071. labelAfterPos = this._tabList.nativeElement.offsetWidth - offsetLeft;
  1072. labelBeforePos = labelAfterPos - offsetWidth;
  1073. }
  1074. /** @type {?} */
  1075. const beforeVisiblePos = this.scrollDistance;
  1076. /** @type {?} */
  1077. const afterVisiblePos = this.scrollDistance + viewLength;
  1078. if (labelBeforePos < beforeVisiblePos) {
  1079. // Scroll header to move label to the before direction
  1080. this.scrollDistance -= beforeVisiblePos - labelBeforePos + EXAGGERATED_OVERSCROLL;
  1081. }
  1082. else if (labelAfterPos > afterVisiblePos) {
  1083. // Scroll header to move label to the after direction
  1084. this.scrollDistance += labelAfterPos - afterVisiblePos + EXAGGERATED_OVERSCROLL;
  1085. }
  1086. }
  1087. /**
  1088. * Evaluate whether the pagination controls should be displayed. If the scroll width of the
  1089. * tab list is wider than the size of the header container, then the pagination controls should
  1090. * be shown.
  1091. *
  1092. * This is an expensive call that forces a layout reflow to compute box and scroll metrics and
  1093. * should be called sparingly.
  1094. * @return {?}
  1095. */
  1096. _checkPaginationEnabled() {
  1097. /** @type {?} */
  1098. const isEnabled = this._tabList.nativeElement.scrollWidth > this._elementRef.nativeElement.offsetWidth;
  1099. if (!isEnabled) {
  1100. this.scrollDistance = 0;
  1101. }
  1102. if (isEnabled !== this._showPaginationControls) {
  1103. this._changeDetectorRef.markForCheck();
  1104. }
  1105. this._showPaginationControls = isEnabled;
  1106. }
  1107. /**
  1108. * Evaluate whether the before and after controls should be enabled or disabled.
  1109. * If the header is at the beginning of the list (scroll distance is equal to 0) then disable the
  1110. * before button. If the header is at the end of the list (scroll distance is equal to the
  1111. * maximum distance we can scroll), then disable the after button.
  1112. *
  1113. * This is an expensive call that forces a layout reflow to compute box and scroll metrics and
  1114. * should be called sparingly.
  1115. * @return {?}
  1116. */
  1117. _checkScrollingControls() {
  1118. // Check if the pagination arrows should be activated.
  1119. this._disableScrollBefore = this.scrollDistance == 0;
  1120. this._disableScrollAfter = this.scrollDistance == this._getMaxScrollDistance();
  1121. this._changeDetectorRef.markForCheck();
  1122. }
  1123. /**
  1124. * Determines what is the maximum length in pixels that can be set for the scroll distance. This
  1125. * is equal to the difference in width between the tab list container and tab header container.
  1126. *
  1127. * This is an expensive call that forces a layout reflow to compute box and scroll metrics and
  1128. * should be called sparingly.
  1129. * @return {?}
  1130. */
  1131. _getMaxScrollDistance() {
  1132. /** @type {?} */
  1133. const lengthOfTabList = this._tabList.nativeElement.scrollWidth;
  1134. /** @type {?} */
  1135. const viewLength = this._tabListContainer.nativeElement.offsetWidth;
  1136. return (lengthOfTabList - viewLength) || 0;
  1137. }
  1138. /**
  1139. * Tells the ink-bar to align itself to the current label wrapper
  1140. * @return {?}
  1141. */
  1142. _alignInkBarToSelectedTab() {
  1143. /** @type {?} */
  1144. const selectedItem = this._items && this._items.length ?
  1145. this._items.toArray()[this.selectedIndex] : null;
  1146. /** @type {?} */
  1147. const selectedLabelWrapper = selectedItem ? selectedItem.elementRef.nativeElement : null;
  1148. if (selectedLabelWrapper) {
  1149. this._inkBar.alignToElement(selectedLabelWrapper);
  1150. }
  1151. else {
  1152. this._inkBar.hide();
  1153. }
  1154. }
  1155. /**
  1156. * Stops the currently-running paginator interval.
  1157. * @return {?}
  1158. */
  1159. _stopInterval() {
  1160. this._stopScrolling.next();
  1161. }
  1162. /**
  1163. * Handles the user pressing down on one of the paginators.
  1164. * Starts scrolling the header after a certain amount of time.
  1165. * @param {?} direction In which direction the paginator should be scrolled.
  1166. * @return {?}
  1167. */
  1168. _handlePaginatorPress(direction) {
  1169. // Avoid overlapping timers.
  1170. this._stopInterval();
  1171. // Start a timer after the delay and keep firing based on the interval.
  1172. timer(HEADER_SCROLL_DELAY, HEADER_SCROLL_INTERVAL)
  1173. // Keep the timer going until something tells it to stop or the component is destroyed.
  1174. .pipe(takeUntil(merge(this._stopScrolling, this._destroyed)))
  1175. .subscribe((/**
  1176. * @return {?}
  1177. */
  1178. () => {
  1179. const { maxScrollDistance, distance } = this._scrollHeader(direction);
  1180. // Stop the timer if we've reached the start or the end.
  1181. if (distance === 0 || distance >= maxScrollDistance) {
  1182. this._stopInterval();
  1183. }
  1184. }));
  1185. }
  1186. /**
  1187. * Scrolls the header to a given position.
  1188. * @private
  1189. * @param {?} position Position to which to scroll.
  1190. * @return {?} Information on the current scroll distance and the maximum.
  1191. */
  1192. _scrollTo(position) {
  1193. /** @type {?} */
  1194. const maxScrollDistance = this._getMaxScrollDistance();
  1195. this._scrollDistance = Math.max(0, Math.min(maxScrollDistance, position));
  1196. // Mark that the scroll distance has changed so that after the view is checked, the CSS
  1197. // transformation can move the header.
  1198. this._scrollDistanceChanged = true;
  1199. this._checkScrollingControls();
  1200. return { maxScrollDistance, distance: this._scrollDistance };
  1201. }
  1202. }
  1203. /** @nocollapse */
  1204. MatPaginatedTabHeader.ctorParameters = () => [
  1205. { type: ElementRef },
  1206. { type: ChangeDetectorRef },
  1207. { type: ViewportRuler },
  1208. { type: Directionality, decorators: [{ type: Optional }] },
  1209. { type: NgZone },
  1210. { type: Platform },
  1211. { type: String }
  1212. ];
  1213. /**
  1214. * @fileoverview added by tsickle
  1215. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  1216. */
  1217. /**
  1218. * The header of the tab group which displays a list of all the tabs in the tab group. Includes
  1219. * an ink bar that follows the currently selected tab. When the tabs list's width exceeds the
  1220. * width of the header container, then arrows will be displayed to allow the user to scroll
  1221. * left and right across the header.
  1222. * \@docs-private
  1223. */
  1224. class MatTabHeader extends MatPaginatedTabHeader {
  1225. /**
  1226. * @param {?} elementRef
  1227. * @param {?} changeDetectorRef
  1228. * @param {?} viewportRuler
  1229. * @param {?} dir
  1230. * @param {?} ngZone
  1231. * @param {?} platform
  1232. * @param {?=} animationMode
  1233. */
  1234. constructor(elementRef, changeDetectorRef, viewportRuler, dir, ngZone, platform,
  1235. // @breaking-change 9.0.0 `_animationMode` parameter to be made required.
  1236. animationMode) {
  1237. super(elementRef, changeDetectorRef, viewportRuler, dir, ngZone, platform, animationMode);
  1238. this._disableRipple = false;
  1239. }
  1240. /**
  1241. * Whether the ripple effect is disabled or not.
  1242. * @return {?}
  1243. */
  1244. get disableRipple() { return this._disableRipple; }
  1245. /**
  1246. * @param {?} value
  1247. * @return {?}
  1248. */
  1249. set disableRipple(value) { this._disableRipple = coerceBooleanProperty(value); }
  1250. /**
  1251. * @protected
  1252. * @param {?} event
  1253. * @return {?}
  1254. */
  1255. _itemSelected(event) {
  1256. event.preventDefault();
  1257. }
  1258. }
  1259. MatTabHeader.decorators = [
  1260. { type: Component, args: [{selector: 'mat-tab-header',
  1261. template: "<div class=\"mat-tab-header-pagination mat-tab-header-pagination-before mat-elevation-z4\" #previousPaginator aria-hidden=\"true\" mat-ripple [matRippleDisabled]=\"_disableScrollBefore || disableRipple\" [class.mat-tab-header-pagination-disabled]=\"_disableScrollBefore\" (click)=\"_handlePaginatorClick('before')\" (mousedown)=\"_handlePaginatorPress('before')\" (touchend)=\"_stopInterval()\"><div class=\"mat-tab-header-pagination-chevron\"></div></div><div class=\"mat-tab-label-container\" #tabListContainer (keydown)=\"_handleKeydown($event)\"><div #tabList class=\"mat-tab-list\" [class._mat-animation-noopable]=\"_animationMode === 'NoopAnimations'\" role=\"tablist\" (cdkObserveContent)=\"_onContentChanges()\"><div class=\"mat-tab-labels\"><ng-content></ng-content></div><mat-ink-bar></mat-ink-bar></div></div><div class=\"mat-tab-header-pagination mat-tab-header-pagination-after mat-elevation-z4\" #nextPaginator aria-hidden=\"true\" mat-ripple [matRippleDisabled]=\"_disableScrollAfter || disableRipple\" [class.mat-tab-header-pagination-disabled]=\"_disableScrollAfter\" (mousedown)=\"_handlePaginatorPress('after')\" (click)=\"_handlePaginatorClick('after')\" (touchend)=\"_stopInterval()\"><div class=\"mat-tab-header-pagination-chevron\"></div></div>",
  1262. styles: [".mat-tab-header{display:flex;overflow:hidden;position:relative;flex-shrink:0}.mat-tab-header-pagination{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;position:relative;display:none;justify-content:center;align-items:center;min-width:32px;cursor:pointer;z-index:2;-webkit-tap-highlight-color:transparent;touch-action:none}.mat-tab-header-pagination-controls-enabled .mat-tab-header-pagination{display:flex}.mat-tab-header-pagination-before,.mat-tab-header-rtl .mat-tab-header-pagination-after{padding-left:4px}.mat-tab-header-pagination-before .mat-tab-header-pagination-chevron,.mat-tab-header-rtl .mat-tab-header-pagination-after .mat-tab-header-pagination-chevron{transform:rotate(-135deg)}.mat-tab-header-pagination-after,.mat-tab-header-rtl .mat-tab-header-pagination-before{padding-right:4px}.mat-tab-header-pagination-after .mat-tab-header-pagination-chevron,.mat-tab-header-rtl .mat-tab-header-pagination-before .mat-tab-header-pagination-chevron{transform:rotate(45deg)}.mat-tab-header-pagination-chevron{border-style:solid;border-width:2px 2px 0 0;content:'';height:8px;width:8px}.mat-tab-header-pagination-disabled{box-shadow:none;cursor:default}.mat-tab-list{flex-grow:1;position:relative;transition:transform .5s cubic-bezier(.35,0,.25,1)}.mat-ink-bar{position:absolute;bottom:0;height:2px;transition:.5s cubic-bezier(.35,0,.25,1)}._mat-animation-noopable.mat-ink-bar{transition:none;animation:none}.mat-tab-group-inverted-header .mat-ink-bar{bottom:auto;top:0}@media (-ms-high-contrast:active){.mat-ink-bar{outline:solid 2px;height:0}}.mat-tab-labels{display:flex}[mat-align-tabs=center] .mat-tab-labels{justify-content:center}[mat-align-tabs=end] .mat-tab-labels{justify-content:flex-end}.mat-tab-label-container{display:flex;flex-grow:1;overflow:hidden;z-index:1}._mat-animation-noopable.mat-tab-list{transition:none;animation:none}.mat-tab-label{height:48px;padding:0 24px;cursor:pointer;box-sizing:border-box;opacity:.6;min-width:160px;text-align:center;display:inline-flex;justify-content:center;align-items:center;white-space:nowrap;position:relative}.mat-tab-label:focus{outline:0}.mat-tab-label:focus:not(.mat-tab-disabled){opacity:1}@media (-ms-high-contrast:active){.mat-tab-label:focus{outline:dotted 2px}}.mat-tab-label.mat-tab-disabled{cursor:default}@media (-ms-high-contrast:active){.mat-tab-label.mat-tab-disabled{opacity:.5}}.mat-tab-label .mat-tab-label-content{display:inline-flex;justify-content:center;align-items:center;white-space:nowrap}@media (-ms-high-contrast:active){.mat-tab-label{opacity:1}}@media (max-width:599px){.mat-tab-label{min-width:72px}}"],
  1263. inputs: ['selectedIndex'],
  1264. outputs: ['selectFocusedIndex', 'indexFocused'],
  1265. encapsulation: ViewEncapsulation.None,
  1266. changeDetection: ChangeDetectionStrategy.OnPush,
  1267. host: {
  1268. 'class': 'mat-tab-header',
  1269. '[class.mat-tab-header-pagination-controls-enabled]': '_showPaginationControls',
  1270. '[class.mat-tab-header-rtl]': "_getLayoutDirection() == 'rtl'",
  1271. },
  1272. },] },
  1273. ];
  1274. /** @nocollapse */
  1275. MatTabHeader.ctorParameters = () => [
  1276. { type: ElementRef },
  1277. { type: ChangeDetectorRef },
  1278. { type: ViewportRuler },
  1279. { type: Directionality, decorators: [{ type: Optional }] },
  1280. { type: NgZone },
  1281. { type: Platform },
  1282. { type: String, decorators: [{ type: Optional }, { type: Inject, args: [ANIMATION_MODULE_TYPE,] }] }
  1283. ];
  1284. MatTabHeader.propDecorators = {
  1285. _items: [{ type: ContentChildren, args: [MatTabLabelWrapper,] }],
  1286. _inkBar: [{ type: ViewChild, args: [MatInkBar, { static: true },] }],
  1287. _tabListContainer: [{ type: ViewChild, args: ['tabListContainer', { static: true },] }],
  1288. _tabList: [{ type: ViewChild, args: ['tabList', { static: true },] }],
  1289. _nextPaginator: [{ type: ViewChild, args: ['nextPaginator', { static: false },] }],
  1290. _previousPaginator: [{ type: ViewChild, args: ['previousPaginator', { static: false },] }],
  1291. disableRipple: [{ type: Input }]
  1292. };
  1293. /**
  1294. * @fileoverview added by tsickle
  1295. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  1296. */
  1297. /**
  1298. * Used to generate unique ID's for each tab component
  1299. * @type {?}
  1300. */
  1301. let nextId = 0;
  1302. /**
  1303. * A simple change event emitted on focus or selection changes.
  1304. */
  1305. class MatTabChangeEvent {
  1306. }
  1307. /**
  1308. * Injection token that can be used to provide the default options the tabs module.
  1309. * @type {?}
  1310. */
  1311. const MAT_TABS_CONFIG = new InjectionToken('MAT_TABS_CONFIG');
  1312. // Boilerplate for applying mixins to MatTabGroup.
  1313. /**
  1314. * \@docs-private
  1315. */
  1316. class MatTabGroupBase {
  1317. /**
  1318. * @param {?} _elementRef
  1319. */
  1320. constructor(_elementRef) {
  1321. this._elementRef = _elementRef;
  1322. }
  1323. }
  1324. /** @type {?} */
  1325. const _MatTabGroupMixinBase = mixinColor(mixinDisableRipple(MatTabGroupBase), 'primary');
  1326. /**
  1327. * Material design tab-group component. Supports basic tab pairs (label + content) and includes
  1328. * animated ink-bar, keyboard navigation, and screen reader.
  1329. * See: https://material.io/design/components/tabs.html
  1330. */
  1331. class MatTabGroup extends _MatTabGroupMixinBase {
  1332. /**
  1333. * @param {?} elementRef
  1334. * @param {?} _changeDetectorRef
  1335. * @param {?=} defaultConfig
  1336. * @param {?=} _animationMode
  1337. */
  1338. constructor(elementRef, _changeDetectorRef, defaultConfig, _animationMode) {
  1339. super(elementRef);
  1340. this._changeDetectorRef = _changeDetectorRef;
  1341. this._animationMode = _animationMode;
  1342. /**
  1343. * The tab index that should be selected after the content has been checked.
  1344. */
  1345. this._indexToSelect = 0;
  1346. /**
  1347. * Snapshot of the height of the tab body wrapper before another tab is activated.
  1348. */
  1349. this._tabBodyWrapperHeight = 0;
  1350. /**
  1351. * Subscription to tabs being added/removed.
  1352. */
  1353. this._tabsSubscription = Subscription.EMPTY;
  1354. /**
  1355. * Subscription to changes in the tab labels.
  1356. */
  1357. this._tabLabelSubscription = Subscription.EMPTY;
  1358. this._dynamicHeight = false;
  1359. this._selectedIndex = null;
  1360. /**
  1361. * Position of the tab header.
  1362. */
  1363. this.headerPosition = 'above';
  1364. /**
  1365. * Output to enable support for two-way binding on `[(selectedIndex)]`
  1366. */
  1367. this.selectedIndexChange = new EventEmitter();
  1368. /**
  1369. * Event emitted when focus has changed within a tab group.
  1370. */
  1371. this.focusChange = new EventEmitter();
  1372. /**
  1373. * Event emitted when the body animation has completed
  1374. */
  1375. this.animationDone = new EventEmitter();
  1376. /**
  1377. * Event emitted when the tab selection has changed.
  1378. */
  1379. this.selectedTabChange = new EventEmitter(true);
  1380. this._groupId = nextId++;
  1381. this.animationDuration = defaultConfig && defaultConfig.animationDuration ?
  1382. defaultConfig.animationDuration : '500ms';
  1383. }
  1384. /**
  1385. * Whether the tab group should grow to the size of the active tab.
  1386. * @return {?}
  1387. */
  1388. get dynamicHeight() { return this._dynamicHeight; }
  1389. /**
  1390. * @param {?} value
  1391. * @return {?}
  1392. */
  1393. set dynamicHeight(value) { this._dynamicHeight = coerceBooleanProperty(value); }
  1394. /**
  1395. * The index of the active tab.
  1396. * @return {?}
  1397. */
  1398. get selectedIndex() { return this._selectedIndex; }
  1399. /**
  1400. * @param {?} value
  1401. * @return {?}
  1402. */
  1403. set selectedIndex(value) {
  1404. this._indexToSelect = coerceNumberProperty(value, null);
  1405. }
  1406. /**
  1407. * Duration for the tab animation. Will be normalized to milliseconds if no units are set.
  1408. * @return {?}
  1409. */
  1410. get animationDuration() { return this._animationDuration; }
  1411. /**
  1412. * @param {?} value
  1413. * @return {?}
  1414. */
  1415. set animationDuration(value) {
  1416. this._animationDuration = /^\d+$/.test(value) ? value + 'ms' : value;
  1417. }
  1418. /**
  1419. * Background color of the tab group.
  1420. * @return {?}
  1421. */
  1422. get backgroundColor() { return this._backgroundColor; }
  1423. /**
  1424. * @param {?} value
  1425. * @return {?}
  1426. */
  1427. set backgroundColor(value) {
  1428. /** @type {?} */
  1429. const nativeElement = this._elementRef.nativeElement;
  1430. nativeElement.classList.remove(`mat-background-${this.backgroundColor}`);
  1431. if (value) {
  1432. nativeElement.classList.add(`mat-background-${value}`);
  1433. }
  1434. this._backgroundColor = value;
  1435. }
  1436. /**
  1437. * After the content is checked, this component knows what tabs have been defined
  1438. * and what the selected index should be. This is where we can know exactly what position
  1439. * each tab should be in according to the new selected index, and additionally we know how
  1440. * a new selected tab should transition in (from the left or right).
  1441. * @return {?}
  1442. */
  1443. ngAfterContentChecked() {
  1444. // Don't clamp the `indexToSelect` immediately in the setter because it can happen that
  1445. // the amount of tabs changes before the actual change detection runs.
  1446. /** @type {?} */
  1447. const indexToSelect = this._indexToSelect = this._clampTabIndex(this._indexToSelect);
  1448. // If there is a change in selected index, emit a change event. Should not trigger if
  1449. // the selected index has not yet been initialized.
  1450. if (this._selectedIndex != indexToSelect) {
  1451. /** @type {?} */
  1452. const isFirstRun = this._selectedIndex == null;
  1453. if (!isFirstRun) {
  1454. this.selectedTabChange.emit(this._createChangeEvent(indexToSelect));
  1455. }
  1456. // Changing these values after change detection has run
  1457. // since the checked content may contain references to them.
  1458. Promise.resolve().then((/**
  1459. * @return {?}
  1460. */
  1461. () => {
  1462. this._tabs.forEach((/**
  1463. * @param {?} tab
  1464. * @param {?} index
  1465. * @return {?}
  1466. */
  1467. (tab, index) => tab.isActive = index === indexToSelect));
  1468. if (!isFirstRun) {
  1469. this.selectedIndexChange.emit(indexToSelect);
  1470. }
  1471. }));
  1472. }
  1473. // Setup the position for each tab and optionally setup an origin on the next selected tab.
  1474. this._tabs.forEach((/**
  1475. * @param {?} tab
  1476. * @param {?} index
  1477. * @return {?}
  1478. */
  1479. (tab, index) => {
  1480. tab.position = index - indexToSelect;
  1481. // If there is already a selected tab, then set up an origin for the next selected tab
  1482. // if it doesn't have one already.
  1483. if (this._selectedIndex != null && tab.position == 0 && !tab.origin) {
  1484. tab.origin = indexToSelect - this._selectedIndex;
  1485. }
  1486. }));
  1487. if (this._selectedIndex !== indexToSelect) {
  1488. this._selectedIndex = indexToSelect;
  1489. this._changeDetectorRef.markForCheck();
  1490. }
  1491. }
  1492. /**
  1493. * @return {?}
  1494. */
  1495. ngAfterContentInit() {
  1496. this._subscribeToTabLabels();
  1497. // Subscribe to changes in the amount of tabs, in order to be
  1498. // able to re-render the content as new tabs are added or removed.
  1499. this._tabsSubscription = this._tabs.changes.subscribe((/**
  1500. * @return {?}
  1501. */
  1502. () => {
  1503. /** @type {?} */
  1504. const indexToSelect = this._clampTabIndex(this._indexToSelect);
  1505. // Maintain the previously-selected tab if a new tab is added or removed and there is no
  1506. // explicit change that selects a different tab.
  1507. if (indexToSelect === this._selectedIndex) {
  1508. /** @type {?} */
  1509. const tabs = this._tabs.toArray();
  1510. for (let i = 0; i < tabs.length; i++) {
  1511. if (tabs[i].isActive) {
  1512. // Assign both to the `_indexToSelect` and `_selectedIndex` so we don't fire a changed
  1513. // event, otherwise the consumer may end up in an infinite loop in some edge cases like
  1514. // adding a tab within the `selectedIndexChange` event.
  1515. this._indexToSelect = this._selectedIndex = i;
  1516. break;
  1517. }
  1518. }
  1519. }
  1520. this._subscribeToTabLabels();
  1521. this._changeDetectorRef.markForCheck();
  1522. }));
  1523. }
  1524. /**
  1525. * @return {?}
  1526. */
  1527. ngOnDestroy() {
  1528. this._tabsSubscription.unsubscribe();
  1529. this._tabLabelSubscription.unsubscribe();
  1530. }
  1531. /**
  1532. * Re-aligns the ink bar to the selected tab element.
  1533. * @return {?}
  1534. */
  1535. realignInkBar() {
  1536. if (this._tabHeader) {
  1537. this._tabHeader._alignInkBarToSelectedTab();
  1538. }
  1539. }
  1540. /**
  1541. * @param {?} index
  1542. * @return {?}
  1543. */
  1544. _focusChanged(index) {
  1545. this.focusChange.emit(this._createChangeEvent(index));
  1546. }
  1547. /**
  1548. * @private
  1549. * @param {?} index
  1550. * @return {?}
  1551. */
  1552. _createChangeEvent(index) {
  1553. /** @type {?} */
  1554. const event = new MatTabChangeEvent;
  1555. event.index = index;
  1556. if (this._tabs && this._tabs.length) {
  1557. event.tab = this._tabs.toArray()[index];
  1558. }
  1559. return event;
  1560. }
  1561. /**
  1562. * Subscribes to changes in the tab labels. This is needed, because the \@Input for the label is
  1563. * on the MatTab component, whereas the data binding is inside the MatTabGroup. In order for the
  1564. * binding to be updated, we need to subscribe to changes in it and trigger change detection
  1565. * manually.
  1566. * @private
  1567. * @return {?}
  1568. */
  1569. _subscribeToTabLabels() {
  1570. if (this._tabLabelSubscription) {
  1571. this._tabLabelSubscription.unsubscribe();
  1572. }
  1573. this._tabLabelSubscription = merge(...this._tabs.map((/**
  1574. * @param {?} tab
  1575. * @return {?}
  1576. */
  1577. tab => tab._stateChanges)))
  1578. .subscribe((/**
  1579. * @return {?}
  1580. */
  1581. () => this._changeDetectorRef.markForCheck()));
  1582. }
  1583. /**
  1584. * Clamps the given index to the bounds of 0 and the tabs length.
  1585. * @private
  1586. * @param {?} index
  1587. * @return {?}
  1588. */
  1589. _clampTabIndex(index) {
  1590. // Note the `|| 0`, which ensures that values like NaN can't get through
  1591. // and which would otherwise throw the component into an infinite loop
  1592. // (since Math.max(NaN, 0) === NaN).
  1593. return Math.min(this._tabs.length - 1, Math.max(index || 0, 0));
  1594. }
  1595. /**
  1596. * Returns a unique id for each tab label element
  1597. * @param {?} i
  1598. * @return {?}
  1599. */
  1600. _getTabLabelId(i) {
  1601. return `mat-tab-label-${this._groupId}-${i}`;
  1602. }
  1603. /**
  1604. * Returns a unique id for each tab content element
  1605. * @param {?} i
  1606. * @return {?}
  1607. */
  1608. _getTabContentId(i) {
  1609. return `mat-tab-content-${this._groupId}-${i}`;
  1610. }
  1611. /**
  1612. * Sets the height of the body wrapper to the height of the activating tab if dynamic
  1613. * height property is true.
  1614. * @param {?} tabHeight
  1615. * @return {?}
  1616. */
  1617. _setTabBodyWrapperHeight(tabHeight) {
  1618. if (!this._dynamicHeight || !this._tabBodyWrapperHeight) {
  1619. return;
  1620. }
  1621. /** @type {?} */
  1622. const wrapper = this._tabBodyWrapper.nativeElement;
  1623. wrapper.style.height = this._tabBodyWrapperHeight + 'px';
  1624. // This conditional forces the browser to paint the height so that
  1625. // the animation to the new height can have an origin.
  1626. if (this._tabBodyWrapper.nativeElement.offsetHeight) {
  1627. wrapper.style.height = tabHeight + 'px';
  1628. }
  1629. }
  1630. /**
  1631. * Removes the height of the tab body wrapper.
  1632. * @return {?}
  1633. */
  1634. _removeTabBodyWrapperHeight() {
  1635. /** @type {?} */
  1636. const wrapper = this._tabBodyWrapper.nativeElement;
  1637. this._tabBodyWrapperHeight = wrapper.clientHeight;
  1638. wrapper.style.height = '';
  1639. this.animationDone.emit();
  1640. }
  1641. /**
  1642. * Handle click events, setting new selected index if appropriate.
  1643. * @param {?} tab
  1644. * @param {?} tabHeader
  1645. * @param {?} index
  1646. * @return {?}
  1647. */
  1648. _handleClick(tab, tabHeader, index) {
  1649. if (!tab.disabled) {
  1650. this.selectedIndex = tabHeader.focusIndex = index;
  1651. }
  1652. }
  1653. /**
  1654. * Retrieves the tabindex for the tab.
  1655. * @param {?} tab
  1656. * @param {?} idx
  1657. * @return {?}
  1658. */
  1659. _getTabIndex(tab, idx) {
  1660. if (tab.disabled) {
  1661. return null;
  1662. }
  1663. return this.selectedIndex === idx ? 0 : -1;
  1664. }
  1665. }
  1666. MatTabGroup.decorators = [
  1667. { type: Component, args: [{selector: 'mat-tab-group',
  1668. exportAs: 'matTabGroup',
  1669. template: "<mat-tab-header #tabHeader [selectedIndex]=\"selectedIndex\" [disableRipple]=\"disableRipple\" (indexFocused)=\"_focusChanged($event)\" (selectFocusedIndex)=\"selectedIndex = $event\"><div class=\"mat-tab-label\" role=\"tab\" matTabLabelWrapper mat-ripple cdkMonitorElementFocus *ngFor=\"let tab of _tabs; let i = index\" [id]=\"_getTabLabelId(i)\" [attr.tabIndex]=\"_getTabIndex(tab, i)\" [attr.aria-posinset]=\"i + 1\" [attr.aria-setsize]=\"_tabs.length\" [attr.aria-controls]=\"_getTabContentId(i)\" [attr.aria-selected]=\"selectedIndex == i\" [attr.aria-label]=\"tab.ariaLabel || null\" [attr.aria-labelledby]=\"(!tab.ariaLabel && tab.ariaLabelledby) ? tab.ariaLabelledby : null\" [class.mat-tab-label-active]=\"selectedIndex == i\" [disabled]=\"tab.disabled\" [matRippleDisabled]=\"tab.disabled || disableRipple\" (click)=\"_handleClick(tab, tabHeader, i)\"><div class=\"mat-tab-label-content\"><ng-template [ngIf]=\"tab.templateLabel\"><ng-template [cdkPortalOutlet]=\"tab.templateLabel\"></ng-template></ng-template><ng-template [ngIf]=\"!tab.templateLabel\">{{tab.textLabel}}</ng-template></div></div></mat-tab-header><div class=\"mat-tab-body-wrapper\" [class._mat-animation-noopable]=\"_animationMode === 'NoopAnimations'\" #tabBodyWrapper><mat-tab-body role=\"tabpanel\" *ngFor=\"let tab of _tabs; let i = index\" [id]=\"_getTabContentId(i)\" [attr.aria-labelledby]=\"_getTabLabelId(i)\" [class.mat-tab-body-active]=\"selectedIndex == i\" [content]=\"tab.content\" [position]=\"tab.position\" [origin]=\"tab.origin\" [animationDuration]=\"animationDuration\" (_onCentered)=\"_removeTabBodyWrapperHeight()\" (_onCentering)=\"_setTabBodyWrapperHeight($event)\"></mat-tab-body></div>",
  1670. styles: [".mat-tab-group{display:flex;flex-direction:column}.mat-tab-group.mat-tab-group-inverted-header{flex-direction:column-reverse}.mat-tab-label{height:48px;padding:0 24px;cursor:pointer;box-sizing:border-box;opacity:.6;min-width:160px;text-align:center;display:inline-flex;justify-content:center;align-items:center;white-space:nowrap;position:relative}.mat-tab-label:focus{outline:0}.mat-tab-label:focus:not(.mat-tab-disabled){opacity:1}@media (-ms-high-contrast:active){.mat-tab-label:focus{outline:dotted 2px}}.mat-tab-label.mat-tab-disabled{cursor:default}@media (-ms-high-contrast:active){.mat-tab-label.mat-tab-disabled{opacity:.5}}.mat-tab-label .mat-tab-label-content{display:inline-flex;justify-content:center;align-items:center;white-space:nowrap}@media (-ms-high-contrast:active){.mat-tab-label{opacity:1}}@media (max-width:599px){.mat-tab-label{padding:0 12px}}@media (max-width:959px){.mat-tab-label{padding:0 12px}}.mat-tab-group[mat-stretch-tabs]>.mat-tab-header .mat-tab-label{flex-basis:0;flex-grow:1}.mat-tab-body-wrapper{position:relative;overflow:hidden;display:flex;transition:height .5s cubic-bezier(.35,0,.25,1)}._mat-animation-noopable.mat-tab-body-wrapper{transition:none;animation:none}.mat-tab-body{top:0;left:0;right:0;bottom:0;position:absolute;display:block;overflow:hidden;flex-basis:100%}.mat-tab-body.mat-tab-body-active{position:relative;overflow-x:hidden;overflow-y:auto;z-index:1;flex-grow:1}.mat-tab-group.mat-tab-group-dynamic-height .mat-tab-body.mat-tab-body-active{overflow-y:hidden}"],
  1671. encapsulation: ViewEncapsulation.None,
  1672. changeDetection: ChangeDetectionStrategy.OnPush,
  1673. inputs: ['color', 'disableRipple'],
  1674. host: {
  1675. 'class': 'mat-tab-group',
  1676. '[class.mat-tab-group-dynamic-height]': 'dynamicHeight',
  1677. '[class.mat-tab-group-inverted-header]': 'headerPosition === "below"',
  1678. },
  1679. },] },
  1680. ];
  1681. /** @nocollapse */
  1682. MatTabGroup.ctorParameters = () => [
  1683. { type: ElementRef },
  1684. { type: ChangeDetectorRef },
  1685. { type: undefined, decorators: [{ type: Inject, args: [MAT_TABS_CONFIG,] }, { type: Optional }] },
  1686. { type: String, decorators: [{ type: Optional }, { type: Inject, args: [ANIMATION_MODULE_TYPE,] }] }
  1687. ];
  1688. MatTabGroup.propDecorators = {
  1689. _tabs: [{ type: ContentChildren, args: [MatTab,] }],
  1690. _tabBodyWrapper: [{ type: ViewChild, args: ['tabBodyWrapper', { static: false },] }],
  1691. _tabHeader: [{ type: ViewChild, args: ['tabHeader', { static: false },] }],
  1692. dynamicHeight: [{ type: Input }],
  1693. selectedIndex: [{ type: Input }],
  1694. headerPosition: [{ type: Input }],
  1695. animationDuration: [{ type: Input }],
  1696. backgroundColor: [{ type: Input }],
  1697. selectedIndexChange: [{ type: Output }],
  1698. focusChange: [{ type: Output }],
  1699. animationDone: [{ type: Output }],
  1700. selectedTabChange: [{ type: Output }]
  1701. };
  1702. /**
  1703. * @fileoverview added by tsickle
  1704. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  1705. */
  1706. /**
  1707. * Navigation component matching the styles of the tab group header.
  1708. * Provides anchored navigation with animated ink bar.
  1709. */
  1710. class MatTabNav extends MatPaginatedTabHeader {
  1711. /**
  1712. * @param {?} elementRef
  1713. * @param {?} dir
  1714. * @param {?} ngZone
  1715. * @param {?} changeDetectorRef
  1716. * @param {?} viewportRuler
  1717. * @param {?=} platform
  1718. * @param {?=} animationMode
  1719. */
  1720. constructor(elementRef, dir, ngZone, changeDetectorRef, viewportRuler,
  1721. /**
  1722. * @deprecated @breaking-change 9.0.0 `platform` parameter to become required.
  1723. */
  1724. platform, animationMode) {
  1725. super(elementRef, changeDetectorRef, viewportRuler, dir, ngZone, platform, animationMode);
  1726. this._disableRipple = false;
  1727. /**
  1728. * Theme color of the nav bar.
  1729. */
  1730. this.color = 'primary';
  1731. }
  1732. /**
  1733. * Background color of the tab nav.
  1734. * @return {?}
  1735. */
  1736. get backgroundColor() { return this._backgroundColor; }
  1737. /**
  1738. * @param {?} value
  1739. * @return {?}
  1740. */
  1741. set backgroundColor(value) {
  1742. /** @type {?} */
  1743. const classList = this._elementRef.nativeElement.classList;
  1744. classList.remove(`mat-background-${this.backgroundColor}`);
  1745. if (value) {
  1746. classList.add(`mat-background-${value}`);
  1747. }
  1748. this._backgroundColor = value;
  1749. }
  1750. /**
  1751. * Whether the ripple effect is disabled or not.
  1752. * @return {?}
  1753. */
  1754. get disableRipple() { return this._disableRipple; }
  1755. /**
  1756. * @param {?} value
  1757. * @return {?}
  1758. */
  1759. set disableRipple(value) { this._disableRipple = coerceBooleanProperty(value); }
  1760. /**
  1761. * @protected
  1762. * @return {?}
  1763. */
  1764. _itemSelected() {
  1765. // noop
  1766. }
  1767. /**
  1768. * @return {?}
  1769. */
  1770. ngAfterContentInit() {
  1771. this.updateActiveLink();
  1772. super.ngAfterContentInit();
  1773. }
  1774. /**
  1775. * Notifies the component that the active link has been changed.
  1776. * \@breaking-change 8.0.0 `element` parameter to be removed.
  1777. * @param {?=} _element
  1778. * @return {?}
  1779. */
  1780. updateActiveLink(_element) {
  1781. if (!this._items) {
  1782. return;
  1783. }
  1784. /** @type {?} */
  1785. const items = this._items.toArray();
  1786. for (let i = 0; i < items.length; i++) {
  1787. if (items[i].active) {
  1788. this.selectedIndex = i;
  1789. this._changeDetectorRef.markForCheck();
  1790. return;
  1791. }
  1792. }
  1793. // The ink bar should hide itself if no items are active.
  1794. this.selectedIndex = -1;
  1795. this._inkBar.hide();
  1796. }
  1797. }
  1798. MatTabNav.decorators = [
  1799. { type: Component, args: [{selector: '[mat-tab-nav-bar]',
  1800. exportAs: 'matTabNavBar, matTabNav',
  1801. inputs: ['color'],
  1802. template: "<div class=\"mat-tab-header-pagination mat-tab-header-pagination-before mat-elevation-z4\" #previousPaginator aria-hidden=\"true\" mat-ripple [matRippleDisabled]=\"_disableScrollBefore || disableRipple\" [class.mat-tab-header-pagination-disabled]=\"_disableScrollBefore\" (click)=\"_handlePaginatorClick('before')\" (mousedown)=\"_handlePaginatorPress('before')\" (touchend)=\"_stopInterval()\"><div class=\"mat-tab-header-pagination-chevron\"></div></div><div class=\"mat-tab-link-container\" #tabListContainer (keydown)=\"_handleKeydown($event)\"><div class=\"mat-tab-list\" #tabList (cdkObserveContent)=\"_onContentChanges()\"><div class=\"mat-tab-links\"><ng-content></ng-content></div><mat-ink-bar></mat-ink-bar></div></div><div class=\"mat-tab-header-pagination mat-tab-header-pagination-after mat-elevation-z4\" #nextPaginator aria-hidden=\"true\" mat-ripple [matRippleDisabled]=\"_disableScrollAfter || disableRipple\" [class.mat-tab-header-pagination-disabled]=\"_disableScrollAfter\" (mousedown)=\"_handlePaginatorPress('after')\" (click)=\"_handlePaginatorClick('after')\" (touchend)=\"_stopInterval()\"><div class=\"mat-tab-header-pagination-chevron\"></div></div>",
  1803. styles: [".mat-tab-header{display:flex;overflow:hidden;position:relative;flex-shrink:0}.mat-tab-header-pagination{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;position:relative;display:none;justify-content:center;align-items:center;min-width:32px;cursor:pointer;z-index:2;-webkit-tap-highlight-color:transparent;touch-action:none}.mat-tab-header-pagination-controls-enabled .mat-tab-header-pagination{display:flex}.mat-tab-header-pagination-before,.mat-tab-header-rtl .mat-tab-header-pagination-after{padding-left:4px}.mat-tab-header-pagination-before .mat-tab-header-pagination-chevron,.mat-tab-header-rtl .mat-tab-header-pagination-after .mat-tab-header-pagination-chevron{transform:rotate(-135deg)}.mat-tab-header-pagination-after,.mat-tab-header-rtl .mat-tab-header-pagination-before{padding-right:4px}.mat-tab-header-pagination-after .mat-tab-header-pagination-chevron,.mat-tab-header-rtl .mat-tab-header-pagination-before .mat-tab-header-pagination-chevron{transform:rotate(45deg)}.mat-tab-header-pagination-chevron{border-style:solid;border-width:2px 2px 0 0;content:'';height:8px;width:8px}.mat-tab-header-pagination-disabled{box-shadow:none;cursor:default}.mat-tab-list{flex-grow:1;position:relative;transition:transform .5s cubic-bezier(.35,0,.25,1)}.mat-tab-links{display:flex}[mat-align-tabs=center] .mat-tab-links{justify-content:center}[mat-align-tabs=end] .mat-tab-links{justify-content:flex-end}.mat-ink-bar{position:absolute;bottom:0;height:2px;transition:.5s cubic-bezier(.35,0,.25,1)}._mat-animation-noopable.mat-ink-bar{transition:none;animation:none}.mat-tab-group-inverted-header .mat-ink-bar{bottom:auto;top:0}@media (-ms-high-contrast:active){.mat-ink-bar{outline:solid 2px;height:0}}.mat-tab-link-container{display:flex;flex-grow:1;overflow:hidden;z-index:1}.mat-tab-link{height:48px;padding:0 24px;cursor:pointer;box-sizing:border-box;opacity:.6;min-width:160px;text-align:center;display:inline-flex;justify-content:center;align-items:center;white-space:nowrap;vertical-align:top;text-decoration:none;position:relative;overflow:hidden;-webkit-tap-highlight-color:transparent}.mat-tab-link:focus{outline:0}.mat-tab-link:focus:not(.mat-tab-disabled){opacity:1}@media (-ms-high-contrast:active){.mat-tab-link:focus{outline:dotted 2px}}.mat-tab-link.mat-tab-disabled{cursor:default}@media (-ms-high-contrast:active){.mat-tab-link.mat-tab-disabled{opacity:.5}}.mat-tab-link .mat-tab-label-content{display:inline-flex;justify-content:center;align-items:center;white-space:nowrap}@media (-ms-high-contrast:active){.mat-tab-link{opacity:1}}[mat-stretch-tabs] .mat-tab-link{flex-basis:0;flex-grow:1}.mat-tab-link.mat-tab-disabled{pointer-events:none}@media (max-width:599px){.mat-tab-link{min-width:72px}}"],
  1804. host: {
  1805. 'class': 'mat-tab-nav-bar mat-tab-header',
  1806. '[class.mat-tab-header-pagination-controls-enabled]': '_showPaginationControls',
  1807. '[class.mat-tab-header-rtl]': "_getLayoutDirection() == 'rtl'",
  1808. '[class.mat-primary]': 'color !== "warn" && color !== "accent"',
  1809. '[class.mat-accent]': 'color === "accent"',
  1810. '[class.mat-warn]': 'color === "warn"',
  1811. },
  1812. encapsulation: ViewEncapsulation.None,
  1813. changeDetection: ChangeDetectionStrategy.OnPush,
  1814. },] },
  1815. ];
  1816. /** @nocollapse */
  1817. MatTabNav.ctorParameters = () => [
  1818. { type: ElementRef },
  1819. { type: Directionality, decorators: [{ type: Optional }] },
  1820. { type: NgZone },
  1821. { type: ChangeDetectorRef },
  1822. { type: ViewportRuler },
  1823. { type: Platform, decorators: [{ type: Optional }] },
  1824. { type: String, decorators: [{ type: Optional }, { type: Inject, args: [ANIMATION_MODULE_TYPE,] }] }
  1825. ];
  1826. MatTabNav.propDecorators = {
  1827. _items: [{ type: ContentChildren, args: [forwardRef((/**
  1828. * @return {?}
  1829. */
  1830. () => MatTabLink)), { descendants: true },] }],
  1831. _inkBar: [{ type: ViewChild, args: [MatInkBar, { static: true },] }],
  1832. _tabListContainer: [{ type: ViewChild, args: ['tabListContainer', { static: true },] }],
  1833. _tabList: [{ type: ViewChild, args: ['tabList', { static: true },] }],
  1834. _nextPaginator: [{ type: ViewChild, args: ['nextPaginator', { static: false },] }],
  1835. _previousPaginator: [{ type: ViewChild, args: ['previousPaginator', { static: false },] }],
  1836. backgroundColor: [{ type: Input }],
  1837. disableRipple: [{ type: Input }],
  1838. color: [{ type: Input }]
  1839. };
  1840. // Boilerplate for applying mixins to MatTabLink.
  1841. class MatTabLinkBase {
  1842. }
  1843. /** @type {?} */
  1844. const _MatTabLinkMixinBase = mixinTabIndex(mixinDisableRipple(mixinDisabled(MatTabLinkBase)));
  1845. /**
  1846. * Link inside of a `mat-tab-nav-bar`.
  1847. */
  1848. class MatTabLink extends _MatTabLinkMixinBase {
  1849. /**
  1850. * @param {?} _tabNavBar
  1851. * @param {?} elementRef
  1852. * @param {?} ngZone
  1853. * @param {?} platform
  1854. * @param {?} globalRippleOptions
  1855. * @param {?} tabIndex
  1856. * @param {?} _focusMonitor
  1857. * @param {?=} animationMode
  1858. */
  1859. constructor(_tabNavBar, elementRef, ngZone, platform, globalRippleOptions, tabIndex, _focusMonitor, animationMode) {
  1860. super();
  1861. this._tabNavBar = _tabNavBar;
  1862. this.elementRef = elementRef;
  1863. this._focusMonitor = _focusMonitor;
  1864. /**
  1865. * Whether the tab link is active or not.
  1866. */
  1867. this._isActive = false;
  1868. this._tabLinkRipple = new RippleRenderer(this, ngZone, elementRef, platform);
  1869. this._tabLinkRipple.setupTriggerEvents(elementRef.nativeElement);
  1870. this.rippleConfig = globalRippleOptions || {};
  1871. this.tabIndex = parseInt(tabIndex) || 0;
  1872. if (animationMode === 'NoopAnimations') {
  1873. this.rippleConfig.animation = { enterDuration: 0, exitDuration: 0 };
  1874. }
  1875. _focusMonitor.monitor(elementRef);
  1876. }
  1877. /**
  1878. * Whether the link is active.
  1879. * @return {?}
  1880. */
  1881. get active() { return this._isActive; }
  1882. /**
  1883. * @param {?} value
  1884. * @return {?}
  1885. */
  1886. set active(value) {
  1887. if (value !== this._isActive) {
  1888. this._isActive = value;
  1889. this._tabNavBar.updateActiveLink(this.elementRef);
  1890. }
  1891. }
  1892. /**
  1893. * Whether ripples are disabled on interaction.
  1894. * \@docs-private
  1895. * @return {?}
  1896. */
  1897. get rippleDisabled() {
  1898. return this.disabled || this.disableRipple || this._tabNavBar.disableRipple ||
  1899. !!this.rippleConfig.disabled;
  1900. }
  1901. /**
  1902. * @return {?}
  1903. */
  1904. focus() {
  1905. this.elementRef.nativeElement.focus();
  1906. }
  1907. /**
  1908. * @return {?}
  1909. */
  1910. ngOnDestroy() {
  1911. this._tabLinkRipple._removeTriggerEvents();
  1912. this._focusMonitor.stopMonitoring(this.elementRef);
  1913. }
  1914. }
  1915. MatTabLink.decorators = [
  1916. { type: Directive, args: [{
  1917. selector: '[mat-tab-link], [matTabLink]',
  1918. exportAs: 'matTabLink',
  1919. inputs: ['disabled', 'disableRipple', 'tabIndex'],
  1920. host: {
  1921. 'class': 'mat-tab-link',
  1922. '[attr.aria-current]': 'active ? "page" : null',
  1923. '[attr.aria-disabled]': 'disabled',
  1924. '[attr.tabIndex]': 'tabIndex',
  1925. '[class.mat-tab-disabled]': 'disabled',
  1926. '[class.mat-tab-label-active]': 'active',
  1927. }
  1928. },] },
  1929. ];
  1930. /** @nocollapse */
  1931. MatTabLink.ctorParameters = () => [
  1932. { type: MatTabNav },
  1933. { type: ElementRef },
  1934. { type: NgZone },
  1935. { type: Platform },
  1936. { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [MAT_RIPPLE_GLOBAL_OPTIONS,] }] },
  1937. { type: String, decorators: [{ type: Attribute, args: ['tabindex',] }] },
  1938. { type: FocusMonitor },
  1939. { type: String, decorators: [{ type: Optional }, { type: Inject, args: [ANIMATION_MODULE_TYPE,] }] }
  1940. ];
  1941. MatTabLink.propDecorators = {
  1942. active: [{ type: Input }]
  1943. };
  1944. /**
  1945. * @fileoverview added by tsickle
  1946. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  1947. */
  1948. class MatTabsModule {
  1949. }
  1950. MatTabsModule.decorators = [
  1951. { type: NgModule, args: [{
  1952. imports: [
  1953. CommonModule,
  1954. MatCommonModule,
  1955. PortalModule,
  1956. MatRippleModule,
  1957. ObserversModule,
  1958. A11yModule,
  1959. ],
  1960. // Don't export all components because some are only to be used internally.
  1961. exports: [
  1962. MatCommonModule,
  1963. MatTabGroup,
  1964. MatTabLabel,
  1965. MatTab,
  1966. MatTabNav,
  1967. MatTabLink,
  1968. MatTabContent,
  1969. ],
  1970. declarations: [
  1971. MatTabGroup,
  1972. MatTabLabel,
  1973. MatTab,
  1974. MatInkBar,
  1975. MatTabLabelWrapper,
  1976. MatTabNav,
  1977. MatTabLink,
  1978. MatTabBody,
  1979. MatTabBodyPortal,
  1980. MatTabHeader,
  1981. MatTabContent,
  1982. ],
  1983. },] },
  1984. ];
  1985. /**
  1986. * @fileoverview added by tsickle
  1987. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  1988. */
  1989. /**
  1990. * @fileoverview added by tsickle
  1991. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  1992. */
  1993. /**
  1994. * @fileoverview added by tsickle
  1995. * @suppress {checkTypes,extraRequire,missingOverride,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
  1996. */
  1997. export { MatInkBar, _MAT_INK_BAR_POSITIONER, MatTabBody, MatTabBodyPortal, MatTabHeader, MatTabLabelWrapper, MatTab, MatTabLabel, MatTabNav, MatTabLink, MatTabContent, MatTabsModule, MatTabChangeEvent, MAT_TABS_CONFIG, MatTabGroup, matTabsAnimations, _MAT_INK_BAR_POSITIONER_FACTORY as ɵa23, MatPaginatedTabHeader as ɵb23 };
  1998. //# sourceMappingURL=tabs.js.map