@@ -51,6 +51,17 @@ export abstract class Button extends buttonBaseClass implements FormSubmitter {
5151 */
5252 @property ( { type : Boolean , reflect : true } ) disabled = false ;
5353
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+
5465 /**
5566 * The URL that the link button points to.
5667 */
@@ -111,7 +122,7 @@ export abstract class Button extends buttonBaseClass implements FormSubmitter {
111122 constructor ( ) {
112123 super ( ) ;
113124 if ( ! isServer ) {
114- this . addEventListener ( 'click' , this . handleActivationClick ) ;
125+ this . addEventListener ( 'click' , this . handleClick . bind ( this ) ) ;
115126 }
116127 }
117128
@@ -125,7 +136,7 @@ export abstract class Button extends buttonBaseClass implements FormSubmitter {
125136
126137 protected override render ( ) {
127138 // Link buttons may not be disabled
128- const isDisabled = this . disabled && ! this . href ;
139+ const isRippleDisabled = ! this . href && ( this . disabled || this . softDisabled ) ;
129140 const buttonOrLink = this . href ? this . renderLink ( ) : this . renderButton ( ) ;
130141 // TODO(b/310046938): due to a limitation in focus ring/ripple, we can't use
131142 // the same ID for different elements, so we change the ID instead.
@@ -137,7 +148,7 @@ export abstract class Button extends buttonBaseClass implements FormSubmitter {
137148 < md-ripple
138149 part ="ripple "
139150 for =${ buttonId }
140- ?disabled ="${ isDisabled } "> </ md-ripple >
151+ ?disabled ="${ isRippleDisabled } "> </ md-ripple >
141152 ${ buttonOrLink }
142153 ` ;
143154 }
@@ -155,6 +166,7 @@ export abstract class Button extends buttonBaseClass implements FormSubmitter {
155166 id ="button "
156167 class ="button "
157168 ?disabled =${ this . disabled }
169+ aria-disabled =${ this . softDisabled || nothing }
158170 aria-label="${ ariaLabel || nothing } "
159171 aria-haspopup="${ ariaHasPopup || nothing } "
160172 aria-expanded="${ ariaExpanded || nothing } ">
@@ -190,13 +202,22 @@ export abstract class Button extends buttonBaseClass implements FormSubmitter {
190202 ` ;
191203 }
192204
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+
194215 if ( ! isActivationClick ( event ) || ! this . buttonElement ) {
195216 return ;
196217 }
197218 this . focus ( ) ;
198219 dispatchActivationClick ( this . buttonElement ) ;
199- } ;
220+ }
200221
201222 private handleSlotChange ( ) {
202223 this . hasIcon = this . assignedIcons . length > 0 ;
0 commit comments