From 1f3d34ade9b58fa575824a3c3b521133b9821be8 Mon Sep 17 00:00:00 2001 From: 43081j <43081j@users.noreply.github.com> Date: Sun, 10 Sep 2023 18:28:32 +0100 Subject: [PATCH] fix (select): close select on menu close There are some situations where the menu can close when the outer select is still open and unaware. For example, clicking the field will open the menu, then clicking it again will close the menu **but not the select**. This is because the menu observes outside clicks in order to close itself. We don't currently observe those closes to update our own state inside `md-select`, so the UI still shows as if it is open but without a menu. The fix here is to always reflect the state of the menu on `closed`. --- select/harness.ts | 15 +++++++++++++++ select/internal/select.ts | 10 +++++++++- select/select_test.ts | 31 ++++++++++++++++++++++++++++++- 3 files changed, 54 insertions(+), 2 deletions(-) diff --git a/select/harness.ts b/select/harness.ts index a9a83146104..06144e85a4c 100644 --- a/select/harness.ts +++ b/select/harness.ts @@ -20,6 +20,9 @@ export class SelectHarness extends Harness { field.click(); } + async clickAndWaitForMenu() { + const menu = this.getMenu(); + const menuOpen = menu.open === true; + const waitForMenu = new Promise((resolve) => { + menu.addEventListener(menuOpen ? 'closed' : 'opened', () => { + resolve(); + }, {once: true}); + }); + await this.click(); + await waitForMenu; + } + async clickOption(index: number) { const menu = this.element.renderRoot.querySelector('md-menu')!; if (!menu.open) { diff --git a/select/internal/select.ts b/select/internal/select.ts index 8fe703b5299..0919f9b6cc6 100644 --- a/select/internal/select.ts +++ b/select/internal/select.ts @@ -271,7 +271,7 @@ export abstract class Select extends LitElement { @opening=${this.handleOpening} @opened=${this.redispatchEvent} @closing=${this.redispatchEvent} - @closed=${this.redispatchEvent} + @closed=${this.handleClosed} @close-menu=${this.handleCloseMenu} @request-selection=${this.handleRequestSelection} @request-deselection=${this.handleRequestDeselection}> @@ -425,6 +425,14 @@ export abstract class Select extends LitElement { super.firstUpdated(changed); } + /** + * Closes the select if the inner menu closed + */ + private handleClosed(e: Event) { + this.open = false; + redispatchEvent(this, e); + } + /** * Focuses and activates the last selected item upon opening, and resets other * active items. diff --git a/select/select_test.ts b/select/select_test.ts index e6537ca89bd..946682064f3 100644 --- a/select/select_test.ts +++ b/select/select_test.ts @@ -45,10 +45,39 @@ describe('', () => { const selectEl = root.querySelector('md-outlined-select')!; await selectEl.updateComplete; - await new SelectHarness(selectEl).clickOption(1); + const harness = new SelectHarness(selectEl); + await harness.clickAndWaitForMenu(); + await harness.clickOption(1); expect(changed).toBeTrue(); }); + + it('closes select when field re-clicked', async () => { + render( + html` + + + + `, + root); + const selectEl = root.querySelector('md-outlined-select')!; + await selectEl.updateComplete; + + const spanEl = selectEl.shadowRoot!.querySelector( + 'span.select' + )!; + const menuEl = selectEl.shadowRoot!.querySelector('md-menu')!; + + const harness = new SelectHarness(selectEl); + await harness.clickAndWaitForMenu(); + expect(spanEl.classList.contains('open')).toBeTrue(); + expect(menuEl.open).toBeTrue(); + + await harness.clickAndWaitForMenu(); + + expect(menuEl.open).toBeFalse(); + expect(spanEl.classList.contains('open')).toBeFalse(); + }); }); describe('', () => {