diff --git a/packages/demo/src/events/events.ts b/packages/demo/src/events/events.ts index 9b6c2e2b..f170a2f6 100644 --- a/packages/demo/src/events/events.ts +++ b/packages/demo/src/events/events.ts @@ -15,6 +15,12 @@ export default class Example { onOpen: () => { this.log('onOpen event fire!\n'); }, + onBeforeClose: reason => { + this.log(`onBeforeClose event fire! Reason: "${reason}"\n`); + + // - returning false would preventing from closing the drop + // return false; + }, onClose: reason => { this.log(`onClose event fire! Reason: "${reason}"\n`); }, diff --git a/packages/multiple-select-vanilla/src/MultipleSelectInstance.ts b/packages/multiple-select-vanilla/src/MultipleSelectInstance.ts index 519e023f..ccc9620c 100644 --- a/packages/multiple-select-vanilla/src/MultipleSelectInstance.ts +++ b/packages/multiple-select-vanilla/src/MultipleSelectInstance.ts @@ -1460,21 +1460,23 @@ export class MultipleSelectInstance { } close(reason?: CloseReason) { - this._isOpen = false; - this.options.isOpen = false; - this.parentElm.classList.remove('ms-parent-open'); - this.choiceElm?.querySelector('div.ms-icon-caret')?.classList.remove('open'); - if (this.dropElm) { - this.dropElm.style.display = 'none'; - this.dropElm.ariaExpanded = 'false'; + if (this.options.onBeforeClose(reason) !== false) { + this._isOpen = false; + this.options.isOpen = false; + this.parentElm.classList.remove('ms-parent-open'); + this.choiceElm?.querySelector('div.ms-icon-caret')?.classList.remove('open'); + if (this.dropElm) { + this.dropElm.style.display = 'none'; + this.dropElm.ariaExpanded = 'false'; - if (this.options.container) { - this.parentElm.appendChild(this.dropElm); - this.dropElm.style.top = 'auto'; - this.dropElm.style.left = 'auto'; + if (this.options.container) { + this.parentElm.appendChild(this.dropElm); + this.dropElm.style.top = 'auto'; + this.dropElm.style.left = 'auto'; + } } + this.options.onClose(reason); } - this.options.onClose(reason); } /** diff --git a/packages/multiple-select-vanilla/src/constants.ts b/packages/multiple-select-vanilla/src/constants.ts index af948597..63513c6c 100644 --- a/packages/multiple-select-vanilla/src/constants.ts +++ b/packages/multiple-select-vanilla/src/constants.ts @@ -69,6 +69,7 @@ const DEFAULTS: Partial = { onBeforeOpen: noopFalse, onChange: noopFalse, onOpen: noopFalse, + onBeforeClose: noopTrue, onClose: noopFalse, onCheckAll: noopFalse, onUncheckAll: noopFalse, diff --git a/packages/multiple-select-vanilla/src/models/multipleSelectOption.interface.ts b/packages/multiple-select-vanilla/src/models/multipleSelectOption.interface.ts index 5f1a2dfa..a4f60a35 100644 --- a/packages/multiple-select-vanilla/src/models/multipleSelectOption.interface.ts +++ b/packages/multiple-select-vanilla/src/models/multipleSelectOption.interface.ts @@ -303,6 +303,10 @@ export interface MultipleSelectOption extends MultipleSelectLocale { /** Fires when the filter is cleared. */ onClear: () => void; + /** Fires just before the close event is called. You can return `false` to prevent the drop from closing. */ + // biome-ignore lint/suspicious/noConfusingVoidType: could return a boolean or not return anything + onBeforeClose: (reason?: CloseReason, e?: Event) => boolean | void; + /** Fires when the dropdown list is close. */ onClose: (reason?: CloseReason) => void; diff --git a/playwright/e2e/events.spec.ts b/playwright/e2e/events.spec.ts index e9181954..dc5108dd 100644 --- a/playwright/e2e/events.spec.ts +++ b/playwright/e2e/events.spec.ts @@ -20,6 +20,7 @@ test.describe('Events Demo', () => { 'onBlur event fire!', 'onOpen event fire!', 'onFocus event fire!', + 'onBeforeClose event fire! Reason: "toggle.close"', 'onClose event fire! Reason: "toggle.close"', ].join('\n'), ); @@ -35,6 +36,7 @@ test.describe('Events Demo', () => { 'onBlur event fire!', 'onOpen event fire!', 'onFocus event fire!', + 'onBeforeClose event fire! Reason: "toggle.close"', 'onClose event fire! Reason: "toggle.close"', 'onBeforeOpen event fire!', 'onBlur event fire!', @@ -53,6 +55,7 @@ test.describe('Events Demo', () => { 'onBlur event fire!', 'onOpen event fire!', 'onFocus event fire!', + 'onBeforeClose event fire! Reason: "toggle.close"', 'onClose event fire! Reason: "toggle.close"', 'onBeforeOpen event fire!', 'onBlur event fire!', @@ -73,6 +76,7 @@ test.describe('Events Demo', () => { 'onBlur event fire!', 'onOpen event fire!', 'onFocus event fire!', + 'onBeforeClose event fire! Reason: "toggle.close"', 'onClose event fire! Reason: "toggle.close"', 'onBeforeOpen event fire!', 'onBlur event fire!', @@ -96,6 +100,7 @@ test.describe('Events Demo', () => { 'onBlur event fire!', 'onOpen event fire!', 'onFocus event fire!', + 'onBeforeClose event fire! Reason: "toggle.close"', 'onClose event fire! Reason: "toggle.close"', 'onBeforeOpen event fire!', 'onBlur event fire!', @@ -114,7 +119,7 @@ test.describe('Events Demo', () => { ].join('\n'), ); - const parentLoc = await page.locator('.ms-parent') + const parentLoc = await page.locator('.ms-parent'); await parentLoc.click(); await parentLoc.press('Tab'); await expect(textareaLoc).toHaveText( @@ -125,6 +130,7 @@ test.describe('Events Demo', () => { 'onBlur event fire!', 'onOpen event fire!', 'onFocus event fire!', + 'onBeforeClose event fire! Reason: "toggle.close"', 'onClose event fire! Reason: "toggle.close"', 'onBeforeOpen event fire!', 'onBlur event fire!', @@ -141,6 +147,7 @@ test.describe('Events Demo', () => { 'onFilter event fire! text: ', 'onFilterClear event fire!', 'onFocus event fire!', + 'onBeforeClose event fire! Reason: "toggle.close"', 'onClose event fire! Reason: "toggle.close"', 'onBlur event fire!', ].join('\n'),