From 6f97340fb925386b44406f24b72084b8e9b068bc Mon Sep 17 00:00:00 2001 From: web-padawan Date: Wed, 15 May 2024 15:35:45 +0300 Subject: [PATCH 1/2] feat: add properties to control popover closing behavior --- .../popover/src/vaadin-popover-overlay.js | 2 +- packages/popover/src/vaadin-popover.d.ts | 18 ++++++ packages/popover/src/vaadin-popover.js | 56 +++++++++++++++++ packages/popover/test/basic.test.js | 63 +++++++++++++++++-- .../popover/test/typings/popover.types.ts | 2 + 5 files changed, 136 insertions(+), 5 deletions(-) diff --git a/packages/popover/src/vaadin-popover-overlay.js b/packages/popover/src/vaadin-popover-overlay.js index 76a4b05c50..6ca7aa422b 100644 --- a/packages/popover/src/vaadin-popover-overlay.js +++ b/packages/popover/src/vaadin-popover-overlay.js @@ -57,7 +57,7 @@ class PopoverOverlay extends PopoverOverlayMixin(DirMixin(ThemableMixin(PolylitM render() { return html` -
+
`; diff --git a/packages/popover/src/vaadin-popover.d.ts b/packages/popover/src/vaadin-popover.d.ts index 065d240baf..bf69edc414 100644 --- a/packages/popover/src/vaadin-popover.d.ts +++ b/packages/popover/src/vaadin-popover.d.ts @@ -32,6 +32,24 @@ declare class Popover extends PopoverPositionMixin( */ renderer: PopoverRenderer | null | undefined; + /** + * Set to true to disable closing popover overlay on outside click. + * Closing on outside click only works when the popover is modal. + * + * @attr {boolean} no-close-on-outside-click + */ + noCloseOnOutsideClick: boolean; + + /** + * Set to true to disable closing popover overlay on Escape press. + * When the popover is modal, pressing Escape anywhere in the + * document closes the overlay. Otherwise, only Escape press + * from the popover itself or its target closes the overlay. + * + * @attr {boolean} no-close-on-esc + */ + noCloseOnEsc: boolean; + /** * Requests an update for the content of the popover. * While performing the update, it invokes the renderer passed in the `renderer` property. diff --git a/packages/popover/src/vaadin-popover.js b/packages/popover/src/vaadin-popover.js index d84b76c691..605ff80845 100644 --- a/packages/popover/src/vaadin-popover.js +++ b/packages/popover/src/vaadin-popover.js @@ -49,6 +49,30 @@ class Popover extends PopoverPositionMixin( type: Object, }, + /** + * Set to true to disable closing popover overlay on outside click. + * Closing on outside click only works when the popover is modal. + * + * @attr {boolean} no-close-on-outside-click + */ + noCloseOnOutsideClick: { + type: Boolean, + value: false, + }, + + /** + * Set to true to disable closing popover overlay on Escape press. + * When the popover is modal, pressing Escape anywhere in the + * document closes the overlay. Otherwise, only Escape press + * from the popover itself or its target closes the overlay. + * + * @attr {boolean} no-close-on-esc + */ + noCloseOnEsc: { + type: Boolean, + value: false, + }, + /** @private */ _opened: { type: Boolean, @@ -60,6 +84,7 @@ class Popover extends PopoverPositionMixin( constructor() { super(); this.__onTargetClick = this.__onTargetClick.bind(this); + this.__onTargetKeydown = this.__onTargetKeydown.bind(this); } /** @protected */ @@ -79,6 +104,8 @@ class Popover extends PopoverPositionMixin( .horizontalAlign="${this.__computeHorizontalAlign(effectivePosition)}" .verticalAlign="${this.__computeVerticalAlign(effectivePosition)}" @opened-changed="${this.__onOpenedChanged}" + @vaadin-overlay-escape-press="${this.__onEscapePress}" + @vaadin-overlay-outside-click="${this.__onOutsideClick}" > `; } @@ -118,6 +145,7 @@ class Popover extends PopoverPositionMixin( */ _addTargetListeners(target) { target.addEventListener('click', this.__onTargetClick); + target.addEventListener('keydown', this.__onTargetKeydown); } /** @@ -127,6 +155,7 @@ class Popover extends PopoverPositionMixin( */ _removeTargetListeners(target) { target.removeEventListener('click', this.__onTargetClick); + target.removeEventListener('keydown', this.__onTargetKeydown); } /** @private */ @@ -134,10 +163,37 @@ class Popover extends PopoverPositionMixin( this._opened = !this._opened; } + /** @private */ + __onTargetKeydown(event) { + if (event.key === 'Escape' && !this.noCloseOnEsc) { + this._opened = false; + } + } + /** @private */ __onOpenedChanged(event) { this._opened = event.detail.value; } + + /** + * Close the popover if `noCloseOnEsc` isn't set to true. + * @private + */ + __onEscapePress(e) { + if (this.noCloseOnEsc) { + e.preventDefault(); + } + } + + /** + * Close the popover if `noCloseOnOutsideClick` isn't set to true, + * @private + */ + __onOutsideClick(e) { + if (this.noCloseOnOutsideClick) { + e.preventDefault(); + } + } } defineCustomElement(Popover); diff --git a/packages/popover/test/basic.test.js b/packages/popover/test/basic.test.js index 6e106266b9..0b93879133 100644 --- a/packages/popover/test/basic.test.js +++ b/packages/popover/test/basic.test.js @@ -149,16 +149,19 @@ describe('popover', () => { expect(overlay.opened).to.be.false; }); - it('should close overlay on Escape press by default', async () => { + it('should not close on outside click if noCloseOnOutsideClick is true', async () => { + popover.noCloseOnOutsideClick = true; + await nextUpdate(popover); + target.click(); await nextRender(); - esc(document.body); + outsideClick(); await nextRender(); - expect(overlay.opened).to.be.false; + expect(overlay.opened).to.be.true; }); - it('should close overlay on when popover is detached', async () => { + it('should close overlay when popover is detached', async () => { target.click(); await nextRender(); @@ -166,5 +169,57 @@ describe('popover', () => { await nextRender(); expect(overlay.opened).to.be.false; }); + + describe('Escape press', () => { + beforeEach(async () => { + target.click(); + await nextRender(); + }); + + it('should close overlay on global Escape press by default', async () => { + esc(document.body); + await nextRender(); + expect(overlay.opened).to.be.false; + }); + + it('should close overlay on internal Escape press by default', async () => { + esc(overlay.$.overlay); + await nextRender(); + expect(overlay.opened).to.be.false; + }); + + it('should close overlay on target Escape press by default', async () => { + esc(target); + await nextRender(); + expect(overlay.opened).to.be.false; + }); + + it('should not close on global Escape press if noCloseOnEsc is true', async () => { + popover.noCloseOnEsc = true; + await nextUpdate(popover); + + esc(document.body); + await nextRender(); + expect(overlay.opened).to.be.true; + }); + + it('should not close overlay on internal Escape if noCloseOnEsc is true', async () => { + popover.noCloseOnEsc = true; + await nextUpdate(popover); + + esc(overlay.$.overlay); + await nextRender(); + expect(overlay.opened).to.be.true; + }); + + it('should not close overlay on target Escape if noCloseOnEsc is true', async () => { + popover.noCloseOnEsc = true; + await nextUpdate(popover); + + esc(target); + await nextRender(); + expect(overlay.opened).to.be.true; + }); + }); }); }); diff --git a/packages/popover/test/typings/popover.types.ts b/packages/popover/test/typings/popover.types.ts index e21c3a2c45..e5c7f50dcd 100644 --- a/packages/popover/test/typings/popover.types.ts +++ b/packages/popover/test/typings/popover.types.ts @@ -23,3 +23,5 @@ assertType(popover.target); assertType(popover.position); assertType(popover.renderer); assertType(popover.overlayClass); +assertType(popover.noCloseOnEsc); +assertType(popover.noCloseOnOutsideClick); From 2b33a8a6963d6ff36f769b30e8bbf7243d3c0d3c Mon Sep 17 00:00:00 2001 From: web-padawan Date: Thu, 16 May 2024 10:12:25 +0300 Subject: [PATCH 2/2] chore: replace comma in the JSDoc --- packages/popover/src/vaadin-popover.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/popover/src/vaadin-popover.js b/packages/popover/src/vaadin-popover.js index 605ff80845..ae42cf7341 100644 --- a/packages/popover/src/vaadin-popover.js +++ b/packages/popover/src/vaadin-popover.js @@ -186,7 +186,7 @@ class Popover extends PopoverPositionMixin( } /** - * Close the popover if `noCloseOnOutsideClick` isn't set to true, + * Close the popover if `noCloseOnOutsideClick` isn't set to true. * @private */ __onOutsideClick(e) {