From a2a72de2cf6430d327ac1a4cb0af6e14d7218f7d Mon Sep 17 00:00:00 2001 From: Cory LaViska Date: Tue, 20 Feb 2024 14:13:23 -0500 Subject: [PATCH] backport 1788 --- src/components/dialog/dialog.component.ts | 11 +++++++++- src/components/drawer/drawer.component.ts | 13 ++++++++++- src/components/dropdown/dropdown.component.ts | 15 +++++++++++-- src/components/select/select.component.ts | 14 ++++++++++++ src/components/tooltip/tooltip.component.ts | 13 ++++++++++- src/declaration.d.ts | 22 +++++++++++++++++++ 6 files changed, 83 insertions(+), 5 deletions(-) diff --git a/src/components/dialog/dialog.component.ts b/src/components/dialog/dialog.component.ts index 238af4360..646b8c0cb 100644 --- a/src/components/dialog/dialog.component.ts +++ b/src/components/dialog/dialog.component.ts @@ -74,6 +74,7 @@ export default class WaDialog extends WebAwesomeElement { private readonly localize = new LocalizeController(this); private originalTrigger: HTMLElement | null; public modal = new Modal(this); + private closeWatcher: CloseWatcher | null; @query('.dialog') dialog: HTMLElement; @query('.dialog__panel') panel: HTMLElement; @@ -111,6 +112,7 @@ export default class WaDialog extends WebAwesomeElement { super.disconnectedCallback(); this.modal.deactivate(); unlockBodyScrolling(this); + this.closeWatcher?.destroy(); } private requestClose(source: 'close-button' | 'keyboard' | 'overlay') { @@ -129,7 +131,14 @@ export default class WaDialog extends WebAwesomeElement { } private addOpenListeners() { - document.addEventListener('keydown', this.handleDocumentKeyDown); + if ('CloseWatcher' in window) { + this.closeWatcher?.destroy(); + this.closeWatcher = new CloseWatcher(); + this.closeWatcher.onclose = () => this.requestClose('keyboard'); + } else { + this.closeWatcher?.destroy(); + document.addEventListener('keydown', this.handleDocumentKeyDown); + } } private removeOpenListeners() { diff --git a/src/components/drawer/drawer.component.ts b/src/components/drawer/drawer.component.ts index 29f9b6f3f..3696d2052 100644 --- a/src/components/drawer/drawer.component.ts +++ b/src/components/drawer/drawer.component.ts @@ -81,6 +81,7 @@ export default class WaDrawer extends WebAwesomeElement { private readonly localize = new LocalizeController(this); private originalTrigger: HTMLElement | null; public modal = new Modal(this); + private closeWatcher: CloseWatcher | null; @query('.drawer') drawer: HTMLElement; @query('.drawer__panel') panel: HTMLElement; @@ -129,6 +130,7 @@ export default class WaDrawer extends WebAwesomeElement { disconnectedCallback() { super.disconnectedCallback(); unlockBodyScrolling(this); + this.closeWatcher?.destroy(); } private requestClose(source: 'close-button' | 'keyboard' | 'overlay') { @@ -147,7 +149,16 @@ export default class WaDrawer extends WebAwesomeElement { } private addOpenListeners() { - document.addEventListener('keydown', this.handleDocumentKeyDown); + if ('CloseWatcher' in window) { + this.closeWatcher?.destroy(); + if (!this.contained) { + this.closeWatcher = new CloseWatcher(); + this.closeWatcher.onclose = () => this.requestClose('keyboard'); + } + } else { + document.addEventListener('keydown', this.handleDocumentKeyDown); + this.closeWatcher?.destroy(); + } } private removeOpenListeners() { diff --git a/src/components/dropdown/dropdown.component.ts b/src/components/dropdown/dropdown.component.ts index 9803a3a28..11f146f73 100644 --- a/src/components/dropdown/dropdown.component.ts +++ b/src/components/dropdown/dropdown.component.ts @@ -48,6 +48,7 @@ export default class WaDropdown extends WebAwesomeElement { @query('.dropdown__panel') panel: HTMLSlotElement; private readonly localize = new LocalizeController(this); + private closeWatcher: CloseWatcher | null; /** * Indicates whether or not the dropdown is open. You can toggle this attribute to show and hide the dropdown, or you @@ -140,7 +141,7 @@ export default class WaDropdown extends WebAwesomeElement { private handleKeyDown = (event: KeyboardEvent) => { // Close when escape is pressed inside an open dropdown. We need to listen on the panel itself and stop propagation // in case any ancestors are also listening for this key. - if (this.open && event.key === 'Escape') { + if (this.open && event.key === 'Escape' && !this.closeWatcher) { event.stopPropagation(); this.hide(); this.focusOnTrigger(); @@ -334,7 +335,16 @@ export default class WaDropdown extends WebAwesomeElement { addOpenListeners() { this.panel.addEventListener('wa-select', this.handlePanelSelect); - this.panel.addEventListener('keydown', this.handleKeyDown); + if ('CloseWatcher' in window) { + this.closeWatcher?.destroy(); + this.closeWatcher = new CloseWatcher(); + this.closeWatcher.onclose = () => { + this.hide(); + this.focusOnTrigger(); + }; + } else { + this.panel.addEventListener('keydown', this.handleKeyDown); + } document.addEventListener('keydown', this.handleDocumentKeyDown); document.addEventListener('mousedown', this.handleDocumentMouseDown); } @@ -346,6 +356,7 @@ export default class WaDropdown extends WebAwesomeElement { } document.removeEventListener('keydown', this.handleDocumentKeyDown); document.removeEventListener('mousedown', this.handleDocumentMouseDown); + this.closeWatcher?.destroy(); } @watch('open', { waitUntilFirstUpdate: true }) diff --git a/src/components/select/select.component.ts b/src/components/select/select.component.ts index b1026e963..0b4891548 100644 --- a/src/components/select/select.component.ts +++ b/src/components/select/select.component.ts @@ -88,6 +88,7 @@ export default class WaSelect extends WebAwesomeElement implements WebAwesomeFor private readonly localize = new LocalizeController(this); private typeToSelectString = ''; private typeToSelectTimeout: number; + private closeWatcher: CloseWatcher | null; @query('.select') popup: WaPopup; @query('.select__combobox') combobox: HTMLSlotElement; @@ -236,6 +237,17 @@ export default class WaSelect extends WebAwesomeElement implements WebAwesomeFor if (this.getRootNode() !== document) { this.getRootNode().addEventListener('focusin', this.handleDocumentFocusIn); } + + if ('CloseWatcher' in window) { + this.closeWatcher?.destroy(); + this.closeWatcher = new CloseWatcher(); + this.closeWatcher.onclose = () => { + if (this.open) { + this.hide(); + this.displayInput.focus({ preventScroll: true }); + } + }; + } } private removeOpenListeners() { @@ -246,6 +258,8 @@ export default class WaSelect extends WebAwesomeElement implements WebAwesomeFor if (this.getRootNode() !== document) { this.getRootNode().removeEventListener('focusin', this.handleDocumentFocusIn); } + + this.closeWatcher?.destroy(); } private handleFocus() { diff --git a/src/components/tooltip/tooltip.component.ts b/src/components/tooltip/tooltip.component.ts index 8514f894d..80ca56024 100644 --- a/src/components/tooltip/tooltip.component.ts +++ b/src/components/tooltip/tooltip.component.ts @@ -45,6 +45,7 @@ export default class WaTooltip extends WebAwesomeElement { private hoverTimeout: number; private readonly localize = new LocalizeController(this); + private closeWatcher: CloseWatcher | null; @query('slot:not([name])') defaultSlot: HTMLSlotElement; @query('.tooltip__body') body: HTMLElement; @@ -108,6 +109,7 @@ export default class WaTooltip extends WebAwesomeElement { disconnectedCallback() { // Cleanup this event in case the tooltip is removed while open + this.closeWatcher?.destroy(); document.removeEventListener('keydown', this.handleDocumentKeyDown); } @@ -181,7 +183,15 @@ export default class WaTooltip extends WebAwesomeElement { // Show this.emit('wa-show'); - document.addEventListener('keydown', this.handleDocumentKeyDown); + if ('CloseWatcher' in window) { + this.closeWatcher?.destroy(); + this.closeWatcher = new CloseWatcher(); + this.closeWatcher.onclose = () => { + this.hide(); + }; + } else { + document.addEventListener('keydown', this.handleDocumentKeyDown); + } await stopAnimations(this.body); this.body.hidden = false; @@ -194,6 +204,7 @@ export default class WaTooltip extends WebAwesomeElement { } else { // Hide this.emit('wa-hide'); + this.closeWatcher?.destroy(); document.removeEventListener('keydown', this.handleDocumentKeyDown); await stopAnimations(this.body); diff --git a/src/declaration.d.ts b/src/declaration.d.ts index 2b9065278..7a971479e 100644 --- a/src/declaration.d.ts +++ b/src/declaration.d.ts @@ -14,3 +14,25 @@ declare namespace Chai { interface HTMLInputElement { showPicker: () => void; } + +/* eslint-disable */ +interface CloseWatcher extends EventTarget { + new (options?: CloseWatcherOptions): CloseWatcher; + requestClose(): void; + close(): void; + destroy(): void; + + oncancel: (event: Event) => void | null; + onclose: (event: Event) => void | null; +} + +declare const CloseWatcher: CloseWatcher; + +interface CloseWatcherOptions { + signal: AbortSignal; +} + +declare interface Window { + CloseWatcher?: CloseWatcher; +} +/* eslint-enable */