@@ -51,6 +51,17 @@ export abstract class Button extends buttonBaseClass implements FormSubmitter {
51
51
*/
52
52
@property ( { type : Boolean , reflect : true } ) disabled = false ;
53
53
54
+ /**
55
+ * Whether or not the button is "soft-disabled" (disabled but still
56
+ * focusable).
57
+ *
58
+ * Use this when a button needs increased visibility when disabled. See
59
+ * https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#kbd_disabled_controls
60
+ * for more guidance on when this is needed.
61
+ */
62
+ @property ( { type : Boolean , attribute : 'soft-disabled' , reflect : true } )
63
+ softDisabled = false ;
64
+
54
65
/**
55
66
* The URL that the link button points to.
56
67
*/
@@ -111,7 +122,7 @@ export abstract class Button extends buttonBaseClass implements FormSubmitter {
111
122
constructor ( ) {
112
123
super ( ) ;
113
124
if ( ! isServer ) {
114
- this . addEventListener ( 'click' , this . handleActivationClick ) ;
125
+ this . addEventListener ( 'click' , this . handleClick . bind ( this ) ) ;
115
126
}
116
127
}
117
128
@@ -125,7 +136,7 @@ export abstract class Button extends buttonBaseClass implements FormSubmitter {
125
136
126
137
protected override render ( ) {
127
138
// Link buttons may not be disabled
128
- const isDisabled = this . disabled && ! this . href ;
139
+ const isRippleDisabled = ! this . href && ( this . disabled || this . softDisabled ) ;
129
140
const buttonOrLink = this . href ? this . renderLink ( ) : this . renderButton ( ) ;
130
141
// TODO(b/310046938): due to a limitation in focus ring/ripple, we can't use
131
142
// the same ID for different elements, so we change the ID instead.
@@ -137,7 +148,7 @@ export abstract class Button extends buttonBaseClass implements FormSubmitter {
137
148
< md-ripple
138
149
part ="ripple "
139
150
for =${ buttonId }
140
- ?disabled ="${ isDisabled } "> </ md-ripple >
151
+ ?disabled ="${ isRippleDisabled } "> </ md-ripple >
141
152
${ buttonOrLink }
142
153
` ;
143
154
}
@@ -155,6 +166,7 @@ export abstract class Button extends buttonBaseClass implements FormSubmitter {
155
166
id ="button "
156
167
class ="button "
157
168
?disabled =${ this . disabled }
169
+ aria-disabled =${ this . softDisabled || nothing }
158
170
aria-label="${ ariaLabel || nothing } "
159
171
aria-haspopup="${ ariaHasPopup || nothing } "
160
172
aria-expanded="${ ariaExpanded || nothing } ">
@@ -190,13 +202,22 @@ export abstract class Button extends buttonBaseClass implements FormSubmitter {
190
202
` ;
191
203
}
192
204
193
- private readonly handleActivationClick = ( event : MouseEvent ) => {
205
+ private handleClick ( event : MouseEvent ) {
206
+ // If the button is soft-disabled, we need to explicitly prevent the click
207
+ // from propagating to other event listeners as well as prevent the default
208
+ // action.
209
+ if ( ! this . href && this . softDisabled ) {
210
+ event . stopImmediatePropagation ( ) ;
211
+ event . preventDefault ( ) ;
212
+ return ;
213
+ }
214
+
194
215
if ( ! isActivationClick ( event ) || ! this . buttonElement ) {
195
216
return ;
196
217
}
197
218
this . focus ( ) ;
198
219
dispatchActivationClick ( this . buttonElement ) ;
199
- } ;
220
+ }
200
221
201
222
private handleSlotChange ( ) {
202
223
this . hasIcon = this . assignedIcons . length > 0 ;
0 commit comments