Use close watcher when supported in place of escape key handlers (#1788)

* Use close watcher when supported in place of escape key handlers

* Update src/components/select/select.component.ts

---------

Co-authored-by: Cory LaViska <cory@abeautifulsite.net>
This commit is contained in:
Luke Warlow
2024-01-23 16:07:46 +00:00
committed by GitHub
parent 1d626c1357
commit 0a319c3646
6 changed files with 78 additions and 6 deletions

View File

@@ -75,6 +75,7 @@ export default class SlDialog extends ShoelaceElement {
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;
@@ -112,6 +113,7 @@ export default class SlDialog extends ShoelaceElement {
super.disconnectedCallback();
this.modal.deactivate();
unlockBodyScrolling(this);
this.closeWatcher?.destroy();
}
private requestClose(source: 'close-button' | 'keyboard' | 'overlay') {
@@ -130,10 +132,17 @@ export default class SlDialog extends ShoelaceElement {
}
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 {
document.addEventListener('keydown', this.handleDocumentKeyDown);
}
}
private removeOpenListeners() {
this.closeWatcher?.destroy();
document.removeEventListener('keydown', this.handleDocumentKeyDown);
}

View File

@@ -81,6 +81,7 @@ export default class SlDrawer extends ShoelaceElement {
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 SlDrawer extends ShoelaceElement {
disconnectedCallback() {
super.disconnectedCallback();
unlockBodyScrolling(this);
this.closeWatcher?.destroy();
}
private requestClose(source: 'close-button' | 'keyboard' | 'overlay') {
@@ -147,11 +149,20 @@ export default class SlDrawer extends ShoelaceElement {
}
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);
}
}
private removeOpenListeners() {
document.removeEventListener('keydown', this.handleDocumentKeyDown);
this.closeWatcher?.destroy();
}
private handleDocumentKeyDown = (event: KeyboardEvent) => {

View File

@@ -48,6 +48,7 @@ export default class SlDropdown extends ShoelaceElement {
@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
@@ -149,7 +150,7 @@ export default class SlDropdown extends ShoelaceElement {
private handleDocumentKeyDown = (event: KeyboardEvent) => {
// Close when escape or tab is pressed
if (event.key === 'Escape' && this.open) {
if (event.key === 'Escape' && this.open && !this.closeWatcher) {
event.stopPropagation();
this.focusOnTrigger();
this.hide();
@@ -334,7 +335,16 @@ export default class SlDropdown extends ShoelaceElement {
addOpenListeners() {
this.panel.addEventListener('sl-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 SlDropdown extends ShoelaceElement {
}
document.removeEventListener('keydown', this.handleDocumentKeyDown);
document.removeEventListener('mousedown', this.handleDocumentMouseDown);
this.closeWatcher?.destroy();
}
@watch('open', { waitUntilFirstUpdate: true })

View File

@@ -81,6 +81,7 @@ export default class SlSelect extends ShoelaceElement implements ShoelaceFormCon
private readonly localize = new LocalizeController(this);
private typeToSelectString = '';
private typeToSelectTimeout: number;
private closeWatcher: CloseWatcher | null;
@query('.select') popup: SlPopup;
@query('.select__combobox') combobox: HTMLSlotElement;
@@ -222,6 +223,16 @@ export default class SlSelect extends ShoelaceElement implements ShoelaceFormCon
// https://github.com/shoelace-style/shoelace/issues/1763
//
const root = this.getRootNode();
if ('CloseWatcher' in window) {
this.closeWatcher?.destroy();
this.closeWatcher = new CloseWatcher();
this.closeWatcher.onclose = () => {
if (this.open) {
this.hide();
this.displayInput.focus({ preventScroll: true });
}
}
}
root.addEventListener('focusin', this.handleDocumentFocusIn);
root.addEventListener('keydown', this.handleDocumentKeyDown);
root.addEventListener('mousedown', this.handleDocumentMouseDown);
@@ -232,6 +243,7 @@ export default class SlSelect extends ShoelaceElement implements ShoelaceFormCon
root.removeEventListener('focusin', this.handleDocumentFocusIn);
root.removeEventListener('keydown', this.handleDocumentKeyDown);
root.removeEventListener('mousedown', this.handleDocumentMouseDown);
this.closeWatcher?.destroy();
}
private handleFocus() {
@@ -264,7 +276,7 @@ export default class SlSelect extends ShoelaceElement implements ShoelaceFormCon
}
// Close when pressing escape
if (event.key === 'Escape' && this.open) {
if (event.key === 'Escape' && this.open && !this.closeWatcher) {
event.preventDefault();
event.stopPropagation();
this.hide();

View File

@@ -45,6 +45,7 @@ export default class SlTooltip extends ShoelaceElement {
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 SlTooltip extends ShoelaceElement {
disconnectedCallback() {
// Cleanup this event in case the tooltip is removed while open
this.closeWatcher?.destroy();
document.removeEventListener('keydown', this.handleDocumentKeyDown);
}
@@ -181,7 +183,13 @@ export default class SlTooltip extends ShoelaceElement {
// Show
this.emit('sl-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 +202,7 @@ export default class SlTooltip extends ShoelaceElement {
} else {
// Hide
this.emit('sl-hide');
this.closeWatcher?.destroy();
document.removeEventListener('keydown', this.handleDocumentKeyDown);
await stopAnimations(this.body);

20
src/declaration.d.ts vendored
View File

@@ -14,3 +14,23 @@ declare namespace Chai {
interface HTMLInputElement {
showPicker: () => void;
}
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;
}