Skip to content

Commit

Permalink
feat: add properties to configure popover modality and closing
Browse files Browse the repository at this point in the history
  • Loading branch information
web-padawan committed May 15, 2024
1 parent c8f5dde commit bcb7746
Show file tree
Hide file tree
Showing 4 changed files with 180 additions and 0 deletions.
37 changes: 37 additions & 0 deletions packages/popover/src/vaadin-popover.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,43 @@ declare class Popover extends PopoverPositionMixin(
*/
renderer: PopoverRenderer | null | undefined;

/**
* When true, opening the overlay moves focus to the first focusable child,
* or to the overlay part with tabindex if there are no focusable children.
*
* @attr {boolean} focus-trap
*/
focusTrap: boolean;

/**
* When true, the backdrop is not shown and clicks on background elements
* are allowed. Also, modeless popover does not close on Escape press.
*/
modeless: boolean;

/**
* Set to true to disable closing popover overlay on outside click.
*
* @attr {boolean} no-close-on-outside-click
*/
noCloseOnOutsideClick: boolean;

/**
* Set to true to disable closing popover overlay on Escape press.
*
* @attr {boolean} no-close-on-esc
*/
noCloseOnEsc: boolean;

/**
* When true, the overlay has a backdrop (modality curtain) on top of the
* underlying page content, covering the whole viewport. The backdrop isn't
* shown when the popover is modeless.
*
* @attr {boolean} with-backdrop
*/
withBackdrop: boolean;

/**
* Requests an update for the content of the popover.
* While performing the update, it invokes the renderer passed in the `renderer` property.
Expand Down
77 changes: 77 additions & 0 deletions packages/popover/src/vaadin-popover.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,58 @@ class Popover extends PopoverPositionMixin(
type: Object,
},

/**
* When true, opening the overlay moves focus to the first focusable child,
* or to the overlay part with tabindex if there are no focusable children.
*
* @attr {boolean} focus-trap
*/
focusTrap: {
type: Boolean,
value: false,
},

/**
* When true, the backdrop is not shown and clicks on background elements
* are allowed. Also, modeless popover does not close on Escape press.
*/
modeless: {
type: Boolean,
value: false,
},

/**
* Set to true to disable closing popover overlay on outside click.
*
* @attr {boolean} no-close-on-outside-click
*/
noCloseOnOutsideClick: {
type: Boolean,
value: false,
},

/**
* Set to true to disable closing popover overlay on Escape press.
*
* @attr {boolean} no-close-on-esc
*/
noCloseOnEsc: {
type: Boolean,
value: false,
},

/**
* When true, the overlay has a backdrop (modality curtain) on top of the
* underlying page content, covering the whole viewport. The backdrop isn't
* shown when the popover is modeless.
*
* @attr {boolean} with-backdrop
*/
withBackdrop: {
type: Boolean,
value: false,
},

/** @private */
_opened: {
type: Boolean,
Expand All @@ -74,11 +126,16 @@ class Popover extends PopoverPositionMixin(
.positionTarget="${this.target}"
.position="${effectivePosition}"
.opened="${this._opened}"
.modeless="${this.modeless}"
.focusTrap="${this.focusTrap}"
.withBackdrop="${!this.modeless && this.withBackdrop}"
?no-horizontal-overlap="${this.__computeNoHorizontalOverlap(effectivePosition)}"
?no-vertical-overlap="${this.__computeNoVerticalOverlap(effectivePosition)}"
.horizontalAlign="${this.__computeHorizontalAlign(effectivePosition)}"
.verticalAlign="${this.__computeVerticalAlign(effectivePosition)}"
@opened-changed="${this.__onOpenedChanged}"
@vaadin-overlay-escape-press="${this.__onEscapePress}"
@vaadin-overlay-outside-click="${this.__onOutsideClick}"
></vaadin-popover-overlay>
`;
}
Expand Down Expand Up @@ -138,6 +195,26 @@ class Popover extends PopoverPositionMixin(
__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);
Expand Down
61 changes: 61 additions & 0 deletions packages/popover/test/basic.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,45 @@ describe('popover', () => {
});
});

describe('overlay properties', () => {
it('should propagate focusTrap property to the overlay', async () => {
popover.focusTrap = true;
await nextUpdate(popover);
expect(overlay.focusTrap).to.be.true;

popover.focusTrap = false;
await nextUpdate(popover);
expect(overlay.focusTrap).to.be.false;
});

it('should propagate modeless property to the overlay', async () => {
popover.modeless = true;
await nextUpdate(popover);
expect(overlay.modeless).to.be.true;

popover.modeless = false;
await nextUpdate(popover);
expect(overlay.modeless).to.be.false;
});

it('should propagate withBackdrop property to the overlay', async () => {
popover.withBackdrop = true;
await nextUpdate(popover);
expect(overlay.withBackdrop).to.be.true;

popover.withBackdrop = false;
await nextUpdate(popover);
expect(overlay.withBackdrop).to.be.false;
});

it('should not propagate withBackdrop when modeless is true', async () => {
popover.modeless = true;
popover.withBackdrop = true;
await nextUpdate(popover);
expect(overlay.withBackdrop).to.be.false;
});
});

describe('interactions', () => {
let target;

Expand Down Expand Up @@ -149,6 +188,17 @@ describe('popover', () => {
expect(overlay.opened).to.be.false;
});

it('should not close on outside click if noCloseOnOutsideClick is true', async () => {
popover.noCloseOnOutsideClick = true;

target.click();
await nextRender();

outsideClick();
await nextRender();
expect(overlay.opened).to.be.true;
});

it('should close overlay on Escape press by default', async () => {
target.click();
await nextRender();
Expand All @@ -158,6 +208,17 @@ describe('popover', () => {
expect(overlay.opened).to.be.false;
});

it('should not close on Escape press if noCloseOnEsc is true', async () => {
popover.noCloseOnEsc = true;

target.click();
await nextRender();

esc(document.body);
await nextRender();
expect(overlay.opened).to.be.true;
});

it('should close overlay on when popover is detached', async () => {
target.click();
await nextRender();
Expand Down
5 changes: 5 additions & 0 deletions packages/popover/test/typings/popover.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,8 @@ assertType<HTMLElement | undefined>(popover.target);
assertType<PopoverPosition>(popover.position);
assertType<PopoverRenderer | null | undefined>(popover.renderer);
assertType<string>(popover.overlayClass);
assertType<boolean>(popover.modeless);
assertType<boolean>(popover.focusTrap);
assertType<boolean>(popover.withBackdrop);
assertType<boolean>(popover.noCloseOnEsc);
assertType<boolean>(popover.noCloseOnOutsideClick);

0 comments on commit bcb7746

Please sign in to comment.