@@ -13,13 +13,13 @@ import {
1313 booleanAttribute ,
1414 Directive ,
1515 ElementRef ,
16+ HostAttributeToken ,
1617 inject ,
1718 InjectionToken ,
1819 Input ,
1920 NgZone ,
2021 numberAttribute ,
2122 OnDestroy ,
22- OnInit ,
2323 Renderer2 ,
2424} from '@angular/core' ;
2525import { _StructuralStylesLoader , MatRippleLoader , ThemePalette } from '@angular/material/core' ;
@@ -52,6 +52,7 @@ export const MAT_BUTTON_HOST = {
5252 // wants to target all Material buttons.
5353 '[class.mat-mdc-button-base]' : 'true' ,
5454 '[class]' : 'color ? "mat-" + color : ""' ,
55+ '[attr.tabindex]' : '_getTabIndex()' ,
5556} ;
5657
5758/** List of classes to add to buttons instances based on host attribute selector. */
@@ -94,13 +95,17 @@ export class MatButtonBase implements AfterViewInit, OnDestroy {
9495 _animationMode = inject ( ANIMATION_MODULE_TYPE , { optional : true } ) ;
9596
9697 private readonly _focusMonitor = inject ( FocusMonitor ) ;
98+ private _cleanupClick : ( ( ) => void ) | undefined ;
9799
98100 /**
99101 * Handles the lazy creation of the MatButton ripple.
100102 * Used to improve initial load time of large applications.
101103 */
102104 protected _rippleLoader : MatRippleLoader = inject ( MatRippleLoader ) ;
103105
106+ /** Whether the button is set on an anchor node. */
107+ protected _isAnchor : boolean ;
108+
104109 /** Whether this button is a FAB. Used to apply the correct class on the ripple. */
105110 protected _isFab = false ;
106111
@@ -153,18 +158,28 @@ export class MatButtonBase implements AfterViewInit, OnDestroy {
153158 @Input ( { transform : booleanAttribute } )
154159 disabledInteractive : boolean ;
155160
161+ @Input ( {
162+ transform : ( value : unknown ) => ( value == null ? undefined : numberAttribute ( value ) ) ,
163+ } )
164+ tabIndex : number ;
165+
156166 constructor ( ...args : unknown [ ] ) ;
157167
158168 constructor ( ) {
159169 inject ( _CdkPrivateStyleLoader ) . load ( _StructuralStylesLoader ) ;
160170 const config = inject ( MAT_BUTTON_CONFIG , { optional : true } ) ;
161- const element = this . _elementRef . nativeElement ;
171+ const element : HTMLElement = this . _elementRef . nativeElement ;
162172 const classList = ( element as HTMLElement ) . classList ;
163173
174+ this . _isAnchor = element . tagName === 'A' ;
164175 this . disabledInteractive = config ?. disabledInteractive ?? false ;
165176 this . color = config ?. color ?? null ;
166177 this . _rippleLoader ?. configureRipple ( element , { className : 'mat-mdc-button-ripple' } ) ;
167178
179+ if ( this . _isAnchor ) {
180+ this . _setupAsAnchor ( ) ;
181+ }
182+
168183 // For each of the variant selectors that is present in the button's host
169184 // attributes, add the correct corresponding MDC classes.
170185 for ( const { attribute, mdcClasses} of HOST_SELECTOR_MDC_CLASS_PAIR ) {
@@ -179,6 +194,7 @@ export class MatButtonBase implements AfterViewInit, OnDestroy {
179194 }
180195
181196 ngOnDestroy ( ) {
197+ this . _cleanupClick ?.( ) ;
182198 this . _focusMonitor . stopMonitoring ( this . _elementRef ) ;
183199 this . _rippleLoader ?. destroyRipple ( this . _elementRef . nativeElement ) ;
184200 }
@@ -193,10 +209,9 @@ export class MatButtonBase implements AfterViewInit, OnDestroy {
193209 }
194210
195211 protected _getAriaDisabled ( ) {
196- if ( this . ariaDisabled != null ) {
197- return this . ariaDisabled ;
212+ if ( this . _isAnchor ) {
213+ return this . ariaDisabled != null ? this . ariaDisabled : this . disabled || null ;
198214 }
199-
200215 return this . disabled && this . disabledInteractive ? true : null ;
201216 }
202217
@@ -210,74 +225,41 @@ export class MatButtonBase implements AfterViewInit, OnDestroy {
210225 this . disableRipple || this . disabled ,
211226 ) ;
212227 }
213- }
214-
215- /** Shared host configuration for buttons using the `<a>` tag. */
216- export const MAT_ANCHOR_HOST = {
217- // Note that this is basically a noop on anchors,
218- // but it appears that some internal apps depend on it.
219- '[attr.disabled]' : '_getDisabledAttribute()' ,
220- '[class.mat-mdc-button-disabled]' : 'disabled' ,
221- '[class.mat-mdc-button-disabled-interactive]' : 'disabledInteractive' ,
222- '[class._mat-animation-noopable]' : '_animationMode === "NoopAnimations"' ,
223228
224- // Note that we ignore the user-specified tabindex when it's disabled for
225- // consistency with the `mat-button` applied on native buttons where even
226- // though they have an index, they're not tabbable.
227- '[attr.tabindex]' : 'disabled && !disabledInteractive ? -1 : tabIndex' ,
228- '[attr.aria-disabled]' : '_getAriaDisabled()' ,
229- // MDC automatically applies the primary theme color to the button, but we want to support
230- // an unthemed version. If color is undefined, apply a CSS class that makes it easy to
231- // select and style this "theme".
232- '[class.mat-unthemed]' : '!color' ,
233- // Add a class that applies to all buttons. This makes it easier to target if somebody
234- // wants to target all Material buttons.
235- '[class.mat-mdc-button-base]' : 'true' ,
236- '[class]' : 'color ? "mat-" + color : ""' ,
237- } ;
229+ protected _getTabIndex ( ) {
230+ if ( this . _isAnchor ) {
231+ return this . disabled && ! this . disabledInteractive ? - 1 : this . tabIndex ;
232+ }
233+ return this . tabIndex ;
234+ }
238235
239- /**
240- * Anchor button base.
241- */
242- @Directive ( )
243- export class MatAnchorBase extends MatButtonBase implements OnInit , OnDestroy {
244- private _renderer = inject ( Renderer2 ) ;
245- private _cleanupClick : ( ) => void ;
236+ private _setupAsAnchor ( ) {
237+ const renderer = inject ( Renderer2 ) ;
238+ const initialTabIndex = inject ( new HostAttributeToken ( 'tabindex' ) , { optional : true } ) ;
246239
247- @Input ( {
248- transform : ( value : unknown ) => {
249- return value == null ? undefined : numberAttribute ( value ) ;
250- } ,
251- } )
252- tabIndex : number ;
240+ if ( initialTabIndex !== null ) {
241+ this . tabIndex = numberAttribute ( initialTabIndex , undefined ) ;
242+ }
253243
254- ngOnInit ( ) : void {
255244 this . _ngZone . runOutsideAngular ( ( ) => {
256- this . _cleanupClick = this . _renderer . listen (
245+ this . _cleanupClick = renderer . listen (
257246 this . _elementRef . nativeElement ,
258247 'click' ,
259- this . _haltDisabledEvents ,
248+ ( event : Event ) => {
249+ if ( this . disabled ) {
250+ event . preventDefault ( ) ;
251+ event . stopImmediatePropagation ( ) ;
252+ }
253+ } ,
260254 ) ;
261255 } ) ;
262256 }
263-
264- override ngOnDestroy ( ) : void {
265- super . ngOnDestroy ( ) ;
266- this . _cleanupClick ?.( ) ;
267- }
268-
269- _haltDisabledEvents = ( event : Event ) : void => {
270- // A disabled button shouldn't apply any actions
271- if ( this . disabled ) {
272- event . preventDefault ( ) ;
273- event . stopImmediatePropagation ( ) ;
274- }
275- } ;
276-
277- protected override _getAriaDisabled ( ) {
278- if ( this . ariaDisabled != null ) {
279- return this . ariaDisabled ;
280- }
281- return this . disabled || null ;
282- }
283257}
258+
259+ // tslint:disable:variable-name
260+ /**
261+ * Anchor button base.
262+ */
263+ export const MatAnchorBase = MatButtonBase ;
264+ export type MatAnchorBase = MatButtonBase ;
265+ // tslint:enable:variable-name
0 commit comments