@@ -19,7 +19,6 @@ import {
19
19
NgZone ,
20
20
numberAttribute ,
21
21
OnDestroy ,
22
- OnInit ,
23
22
Renderer2 ,
24
23
} from '@angular/core' ;
25
24
import { _StructuralStylesLoader , MatRippleLoader , ThemePalette } from '@angular/material/core' ;
@@ -52,8 +51,13 @@ export const MAT_BUTTON_HOST = {
52
51
// wants to target all Material buttons.
53
52
'[class.mat-mdc-button-base]' : 'true' ,
54
53
'[class]' : 'color ? "mat-" + color : ""' ,
54
+ '[attr.tabindex]' : '_getTabIndex()' ,
55
55
} ;
56
56
57
+ function transformTabIndex ( value : unknown ) : number | undefined {
58
+ return value == null ? undefined : numberAttribute ( value ) ;
59
+ }
60
+
57
61
/** List of classes to add to buttons instances based on host attribute selector. */
58
62
const HOST_SELECTOR_MDC_CLASS_PAIR : { attribute : string ; mdcClasses : string [ ] } [ ] = [
59
63
{
@@ -94,13 +98,18 @@ export class MatButtonBase implements AfterViewInit, OnDestroy {
94
98
_animationMode = inject ( ANIMATION_MODULE_TYPE , { optional : true } ) ;
95
99
96
100
private readonly _focusMonitor = inject ( FocusMonitor ) ;
101
+ private _cleanupClick : ( ( ) => void ) | undefined ;
102
+ private _renderer = inject ( Renderer2 ) ;
97
103
98
104
/**
99
105
* Handles the lazy creation of the MatButton ripple.
100
106
* Used to improve initial load time of large applications.
101
107
*/
102
108
protected _rippleLoader : MatRippleLoader = inject ( MatRippleLoader ) ;
103
109
110
+ /** Whether the button is set on an anchor node. */
111
+ protected _isAnchor : boolean ;
112
+
104
113
/** Whether this button is a FAB. Used to apply the correct class on the ripple. */
105
114
protected _isFab = false ;
106
115
@@ -153,14 +162,28 @@ export class MatButtonBase implements AfterViewInit, OnDestroy {
153
162
@Input ( { transform : booleanAttribute } )
154
163
disabledInteractive : boolean ;
155
164
165
+ /** Tab index for the button. */
166
+ @Input ( { transform : transformTabIndex } )
167
+ tabIndex : number ;
168
+
169
+ /**
170
+ * Backwards-compatibility input that handles pre-existing `[tabindex]` bindings.
171
+ * @docs -private
172
+ */
173
+ @Input ( { alias : 'tabindex' , transform : transformTabIndex } )
174
+ set _tabindex ( value : number ) {
175
+ this . tabIndex = value ;
176
+ }
177
+
156
178
constructor ( ...args : unknown [ ] ) ;
157
179
158
180
constructor ( ) {
159
181
inject ( _CdkPrivateStyleLoader ) . load ( _StructuralStylesLoader ) ;
160
182
const config = inject ( MAT_BUTTON_CONFIG , { optional : true } ) ;
161
- const element = this . _elementRef . nativeElement ;
183
+ const element : HTMLElement = this . _elementRef . nativeElement ;
162
184
const classList = ( element as HTMLElement ) . classList ;
163
185
186
+ this . _isAnchor = element . tagName === 'A' ;
164
187
this . disabledInteractive = config ?. disabledInteractive ?? false ;
165
188
this . color = config ?. color ?? null ;
166
189
this . _rippleLoader ?. configureRipple ( element , { className : 'mat-mdc-button-ripple' } ) ;
@@ -176,9 +199,16 @@ export class MatButtonBase implements AfterViewInit, OnDestroy {
176
199
177
200
ngAfterViewInit ( ) {
178
201
this . _focusMonitor . monitor ( this . _elementRef , true ) ;
202
+
203
+ // Some internal tests depend on the timing of this,
204
+ // otherwise we could bind it in the constructor.
205
+ if ( this . _isAnchor ) {
206
+ this . _setupAsAnchor ( ) ;
207
+ }
179
208
}
180
209
181
210
ngOnDestroy ( ) {
211
+ this . _cleanupClick ?.( ) ;
182
212
this . _focusMonitor . stopMonitoring ( this . _elementRef ) ;
183
213
this . _rippleLoader ?. destroyRipple ( this . _elementRef . nativeElement ) ;
184
214
}
@@ -197,6 +227,10 @@ export class MatButtonBase implements AfterViewInit, OnDestroy {
197
227
return this . ariaDisabled ;
198
228
}
199
229
230
+ if ( this . _isAnchor ) {
231
+ return this . disabled || null ;
232
+ }
233
+
200
234
return this . disabled && this . disabledInteractive ? true : null ;
201
235
}
202
236
@@ -210,74 +244,30 @@ export class MatButtonBase implements AfterViewInit, OnDestroy {
210
244
this . disableRipple || this . disabled ,
211
245
) ;
212
246
}
213
- }
214
247
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"' ,
248
+ protected _getTabIndex ( ) {
249
+ if ( this . _isAnchor ) {
250
+ return this . disabled && ! this . disabledInteractive ? - 1 : this . tabIndex ;
251
+ }
252
+ return this . tabIndex ;
253
+ }
223
254
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
- } ;
255
+ private _setupAsAnchor ( ) {
256
+ this . _cleanupClick = this . _ngZone . runOutsideAngular ( ( ) =>
257
+ this . _renderer . listen ( this . _elementRef . nativeElement , 'click' , ( event : Event ) => {
258
+ if ( this . disabled ) {
259
+ event . preventDefault ( ) ;
260
+ event . stopImmediatePropagation ( ) ;
261
+ }
262
+ } ) ,
263
+ ) ;
264
+ }
265
+ }
238
266
267
+ // tslint:disable:variable-name
239
268
/**
240
269
* Anchor button base.
241
270
*/
242
- @Directive ( )
243
- export class MatAnchorBase extends MatButtonBase implements OnInit , OnDestroy {
244
- private _renderer = inject ( Renderer2 ) ;
245
- private _cleanupClick : ( ) => void ;
246
-
247
- @Input ( {
248
- transform : ( value : unknown ) => {
249
- return value == null ? undefined : numberAttribute ( value ) ;
250
- } ,
251
- } )
252
- tabIndex : number ;
253
-
254
- ngOnInit ( ) : void {
255
- this . _ngZone . runOutsideAngular ( ( ) => {
256
- this . _cleanupClick = this . _renderer . listen (
257
- this . _elementRef . nativeElement ,
258
- 'click' ,
259
- this . _haltDisabledEvents ,
260
- ) ;
261
- } ) ;
262
- }
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
- }
283
- }
271
+ export const MatAnchorBase = MatButtonBase ;
272
+ export type MatAnchorBase = MatButtonBase ;
273
+ // tslint:enable:variable-name
0 commit comments