From dcfc96e779c0dd6005f4697450d1edcf7809e8ea Mon Sep 17 00:00:00 2001 From: Westbrook Johnson Date: Thu, 7 Mar 2024 16:12:06 -0500 Subject: [PATCH] fix(picker): allow open/close in tablet --- packages/picker/src/Picker.ts | 61 ++++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 23 deletions(-) diff --git a/packages/picker/src/Picker.ts b/packages/picker/src/Picker.ts index f0cdea9891..0707049bb9 100644 --- a/packages/picker/src/Picker.ts +++ b/packages/picker/src/Picker.ts @@ -49,7 +49,7 @@ import { IS_MOBILE, MatchMediaController, } from '@spectrum-web-components/reactive-controllers/src/MatchMedia.js'; -import type { Overlay } from '@spectrum-web-components/overlay/src/Overlay.js'; +import { Overlay } from '@spectrum-web-components/overlay/src/Overlay.js'; import type { FieldLabel } from '@spectrum-web-components/field-label'; const chevronClass = { @@ -180,17 +180,23 @@ export class PickerBase extends SizedMixin(Focusable, { noDefaultSize: true }) { } this.pointerdownState = this.open; this.preventNextToggle = 'maybe'; + let cleanupAction = 0; const cleanup = (): void => { - document.removeEventListener('pointerup', cleanup); - document.removeEventListener('pointercancel', cleanup); - requestAnimationFrame(() => { - // Complete cleanup on the animation frame so that `click` can go first. - this.preventNextToggle = 'no'; + cancelAnimationFrame(cleanupAction); + cleanupAction = requestAnimationFrame(async () => { + document.removeEventListener('pointerup', cleanup); + document.removeEventListener('pointercancel', cleanup); + this.button.removeEventListener('click', cleanup); + requestAnimationFrame(() => { + // Complete cleanup on the second animation frame so that `click` can go first. + this.preventNextToggle = 'no'; + }); }); }; // Ensure that however the pointer goes up we do `cleanup()`. document.addEventListener('pointerup', cleanup); document.addEventListener('pointercancel', cleanup); + this.button.addEventListener('click', cleanup); this.handleActivate(); } @@ -235,6 +241,7 @@ export class PickerBase extends SizedMixin(Focusable, { noDefaultSize: true }) { } public handleChange(event: Event): void { + this.preventNextToggle = 'no'; const target = event.target as Menu; const [selected] = target.selectedItems; event.stopPropagation(); @@ -358,6 +365,30 @@ export class PickerBase extends SizedMixin(Focusable, { noDefaultSize: true }) { | undefined; } + protected handleBeforetoggle( + event: Event & { + target: Overlay; + newState: 'open' | 'closed'; + } + ): void { + if (event.composedPath()[0] !== event.target) { + return; + } + if (event.newState === 'closed') { + if (this.preventNextToggle === 'no') { + this.open = false; + } else if (!this.pointerdownState) { + // Prevent browser driven closure while opening the Picker + // and the expected event series has not completed. + this.overlayElement.manuallyKeepOpen(); + } + } + if (!this.open) { + this.optionsMenu.updateSelectedItemIndex(); + this.optionsMenu.closeDescendentOverlays(); + } + } + protected renderLabelContent(content: Node[]): TemplateResult | Node[] { if (this.value && this.selectedItem) { return content; @@ -454,23 +485,7 @@ export class PickerBase extends SizedMixin(Focusable, { noDefaultSize: true }) { .willPreventClose=${this.preventNextToggle !== 'no' && this.open && this.dependenciesLoaded} - @beforetoggle=${( - event: Event & { - target: Overlay; - newState: 'open' | 'closed'; - } - ) => { - if (event.composedPath()[0] !== event.target) { - return; - } - if (event.newState === 'closed') { - this.open = false; - } - if (!this.open) { - this.optionsMenu.updateSelectedItemIndex(); - this.optionsMenu.closeDescendentOverlays(); - } - }} + @beforetoggle=${this.handleBeforetoggle} > ${container}