From a2872576cbc0c7085956e4f3af0757e53b92f147 Mon Sep 17 00:00:00 2001 From: Mert Akinc <7282195+m-akinc@users.noreply.github.com> Date: Tue, 20 Feb 2024 17:20:36 -0600 Subject: [PATCH 1/3] Clean up dialog/drawer properly if cancel event skipped --- .../nimble-components/src/dialog/index.ts | 29 ++++++++++++++++--- .../nimble-components/src/dialog/styles.ts | 8 ++++- .../nimble-components/src/dialog/template.ts | 1 + .../src/dialog/tests/dialog.spec.ts | 16 ++++++++++ .../nimble-components/src/drawer/index.ts | 26 +++++++++++++++-- .../nimble-components/src/drawer/template.ts | 1 + .../src/drawer/tests/drawer.spec.ts | 9 ++++++ 7 files changed, 83 insertions(+), 7 deletions(-) diff --git a/packages/nimble-components/src/dialog/index.ts b/packages/nimble-components/src/dialog/index.ts index 44398168c6..26db4d0ddc 100644 --- a/packages/nimble-components/src/dialog/index.ts +++ b/packages/nimble-components/src/dialog/index.ts @@ -107,8 +107,7 @@ export class Dialog extends FoundationElement { throw new Error('Dialog is not open'); } this.dialogElement.close(); - this.resolveShow!(reason); - this.resolveShow = undefined; + this.notifyClosed(reason); } public slottedFooterElementsChanged( @@ -125,11 +124,33 @@ export class Dialog extends FoundationElement { if (this.preventDismiss) { event.preventDefault(); } else { - this.resolveShow!(UserDismissed); - this.resolveShow = undefined; + this.notifyClosed(UserDismissed); } return true; } + + /** + * @internal + */ + public closeHandler(): void { + if (this.resolveShow) { + // If + // - the browser implements dialogs with the CloseWatcher API, and + // - the user presses ESC without first interacting with the dialog (e.g. clicking, scrolling), + // the cancel event is not fired and the dialog just closes. + this.notifyClosed(UserDismissed); + } + } + + private notifyClosed(reason: CloseReason | UserDismissed): void { + if (!this.resolveShow) { + throw new Error( + 'Do not call notifyClosed unless there is a promise to resolve' + ); + } + this.resolveShow(reason); + this.resolveShow = undefined; + } } // eslint-disable-next-line @typescript-eslint/no-empty-interface diff --git a/packages/nimble-components/src/dialog/styles.ts b/packages/nimble-components/src/dialog/styles.ts index 653fcd6c0b..5d0b1c3c1c 100644 --- a/packages/nimble-components/src/dialog/styles.ts +++ b/packages/nimble-components/src/dialog/styles.ts @@ -15,7 +15,8 @@ import { elevation3BoxShadow, dialogSmallWidth, dialogSmallHeight, - dialogSmallMaxHeight + dialogSmallMaxHeight, + borderHoverColor } from '../theme-provider/design-tokens'; import { modalBackdropColorThemeColorStatic, @@ -25,6 +26,7 @@ import { import { Theme } from '../theme-provider/types'; import { themeBehavior } from '../utilities/style/theme'; import { accessiblyHidden } from '../utilities/style/accessibly-hidden'; +import { focusVisible } from '../utilities/style/focus'; export const styles = css` ${display('grid')} @@ -44,6 +46,10 @@ export const styles = css` display: flex; } + dialog${focusVisible} { + outline: 2px solid ${borderHoverColor}; + } + header { min-height: 48px; padding: 24px 24px 0px 24px; diff --git a/packages/nimble-components/src/dialog/template.ts b/packages/nimble-components/src/dialog/template.ts index d27899a530..23f2b63a57 100644 --- a/packages/nimble-components/src/dialog/template.ts +++ b/packages/nimble-components/src/dialog/template.ts @@ -8,6 +8,7 @@ export const template = html` role="dialog" part="control" @cancel="${(x, c) => x.cancelHandler(c.event)}" + @close="${x => x.closeHandler()}" aria-labelledby="header" >