@@ -114,6 +114,21 @@ export class Dialog extends LitElement {
114
114
@state ( ) private hasActions = false ;
115
115
@state ( ) private hasIcon = false ;
116
116
117
+ // See https://bugs.chromium.org/p/chromium/issues/detail?id=1512224
118
+ // Chrome v120 has a bug where escape keys do not trigger cancels. If we get
119
+ // a dialog "close" event that is triggered without a "cancel" after an escape
120
+ // keydown, then we need to manually trigger our closing logic.
121
+ //
122
+ // This bug occurs when pressing escape to close a dialog without first
123
+ // interacting with the dialog's content.
124
+ //
125
+ // Cleanup tracking:
126
+ // https://github.com/material-components/material-web/issues/5330
127
+ // This can be removed when full CloseWatcher support added and the above bug
128
+ // in Chromium is fixed to fire 'cancel' with one escape press and close with
129
+ // multiple.
130
+ private escapePressedWithoutCancel = false ;
131
+
117
132
constructor ( ) {
118
133
super ( ) ;
119
134
if ( ! isServer ) {
@@ -243,6 +258,8 @@ export class Dialog extends LitElement {
243
258
role=${ this . type === 'alert' ? 'alertdialog' : nothing }
244
259
@cancel=${ this . handleCancel }
245
260
@click=${ this . handleDialogClick }
261
+ @close=${ this . handleClose }
262
+ @keydown=${ this . handleKeydown }
246
263
.returnValue=${ this . returnValue || nothing } >
247
264
< div class ="container " @click =${ this . handleContentClick } >
248
265
< div class ="headline ">
@@ -328,6 +345,7 @@ export class Dialog extends LitElement {
328
345
return ;
329
346
}
330
347
348
+ this . escapePressedWithoutCancel = false ;
331
349
const preventDefault = ! redispatchEvent ( this , event ) ;
332
350
// We always prevent default on the original dialog event since we'll
333
351
// animate closing it before it actually closes.
@@ -339,6 +357,30 @@ export class Dialog extends LitElement {
339
357
this . close ( ) ;
340
358
}
341
359
360
+ private handleClose ( ) {
361
+ if ( ! this . escapePressedWithoutCancel ) {
362
+ return ;
363
+ }
364
+
365
+ this . escapePressedWithoutCancel = false ;
366
+ this . dialog ?. dispatchEvent ( new Event ( 'cancel' , { cancelable : true } ) ) ;
367
+ }
368
+
369
+ private handleKeydown ( event : KeyboardEvent ) {
370
+ if ( event . key !== 'Escape' ) {
371
+ return ;
372
+ }
373
+
374
+ // An escape key was pressed. If a "close" event fires next without a
375
+ // "cancel" event first, then we know we're in the Chrome v120 bug.
376
+ this . escapePressedWithoutCancel = true ;
377
+ // Wait a full task for the cancel/close event listeners to fire, then
378
+ // reset the flag.
379
+ setTimeout ( ( ) => {
380
+ this . escapePressedWithoutCancel = false ;
381
+ } ) ;
382
+ }
383
+
342
384
private async animateDialog ( animation : DialogAnimation ) {
343
385
const { dialog, scrim, container, headline, content, actions} = this ;
344
386
if ( ! dialog || ! scrim || ! container || ! headline || ! content || ! actions ) {
0 commit comments