diff --git a/packages/popover/src/vaadin-popover.d.ts b/packages/popover/src/vaadin-popover.d.ts
index beb9e5bc26..cf1ed27269 100644
--- a/packages/popover/src/vaadin-popover.d.ts
+++ b/packages/popover/src/vaadin-popover.d.ts
@@ -77,6 +77,24 @@ export type PopoverEventMap = HTMLElementEventMap & PopoverCustomEventMap;
declare class Popover extends PopoverPositionMixin(
PopoverTargetMixin(OverlayClassMixin(ThemePropertyMixin(ElementMixin(HTMLElement)))),
) {
+ /**
+ * Sets the default focus delay to be used by all popover instances,
+ * except for those that have focus delay configured using property.
+ */
+ static setDefaultFocusDelay(focusDelay: number): void;
+
+ /**
+ * Sets the default hide delay to be used by all popover instances,
+ * except for those that have hide delay configured using property.
+ */
+ static setDefaultHideDelay(hideDelay: number): void;
+
+ /**
+ * Sets the default hover delay to be used by all popover instances,
+ * except for those that have hover delay configured using property.
+ */
+ static setDefaultHoverDelay(delay: number): void;
+
/**
* String used to label the overlay to screen reader users.
*
@@ -114,6 +132,9 @@ declare class Popover extends PopoverPositionMixin(
/**
* The delay in milliseconds before the popover is opened
* on focus when the corresponding trigger is used.
+ *
+ * When not specified, the global default (500ms) is used.
+ *
* @attr {number} focus-delay
*/
focusDelay: number;
@@ -122,6 +143,9 @@ declare class Popover extends PopoverPositionMixin(
* The delay in milliseconds before the popover is closed
* on losing hover, when the corresponding trigger is used.
* On blur, the popover is closed immediately.
+ *
+ * When not specified, the global default (500ms) is used.
+ *
* @attr {number} hide-delay
*/
hideDelay: number;
@@ -129,6 +153,9 @@ declare class Popover extends PopoverPositionMixin(
/**
* The delay in milliseconds before the popover is opened
* on hover when the corresponding trigger is used.
+ *
+ * When not specified, the global default (500ms) is used.
+ *
* @attr {number} hover-delay
*/
hoverDelay: number;
diff --git a/packages/popover/src/vaadin-popover.js b/packages/popover/src/vaadin-popover.js
index f572956067..86c442beb5 100644
--- a/packages/popover/src/vaadin-popover.js
+++ b/packages/popover/src/vaadin-popover.js
@@ -22,6 +22,12 @@ import { ThemePropertyMixin } from '@vaadin/vaadin-themable-mixin/vaadin-theme-p
import { PopoverPositionMixin } from './vaadin-popover-position-mixin.js';
import { PopoverTargetMixin } from './vaadin-popover-target-mixin.js';
+const DEFAULT_DELAY = 500;
+
+let defaultFocusDelay = DEFAULT_DELAY;
+let defaultHoverDelay = DEFAULT_DELAY;
+let defaultHideDelay = DEFAULT_DELAY;
+
/**
* Controller for handling popover opened state.
*/
@@ -40,17 +46,20 @@ class PopoverOpenedStateController {
/** @private */
get __focusDelay() {
- return this.host.focusDelay || 0;
+ const popover = this.host;
+ return popover.focusDelay != null && popover.focusDelay > 0 ? popover.focusDelay : defaultFocusDelay;
}
/** @private */
get __hoverDelay() {
- return this.host.hoverDelay || 0;
+ const popover = this.host;
+ return popover.hoverDelay != null && popover.hoverDelay > 0 ? popover.hoverDelay : defaultHoverDelay;
}
/** @private */
get __hideDelay() {
- return this.host.hideDelay || 0;
+ const popover = this.host;
+ return popover.hideDelay != null && popover.hideDelay > 0 ? popover.hideDelay : defaultHideDelay;
}
/**
@@ -247,6 +256,9 @@ class Popover extends PopoverPositionMixin(
/**
* The delay in milliseconds before the popover is opened
* on focus when the corresponding trigger is used.
+ *
+ * When not specified, the global default (500ms) is used.
+ *
* @attr {number} focus-delay
*/
focusDelay: {
@@ -257,6 +269,9 @@ class Popover extends PopoverPositionMixin(
* The delay in milliseconds before the popover is closed
* on losing hover, when the corresponding trigger is used.
* On blur, the popover is closed immediately.
+ *
+ * When not specified, the global default (500ms) is used.
+ *
* @attr {number} hide-delay
*/
hideDelay: {
@@ -266,6 +281,9 @@ class Popover extends PopoverPositionMixin(
/**
* The delay in milliseconds before the popover is opened
* on hover when the corresponding trigger is used.
+ *
+ * When not specified, the global default (500ms) is used.
+ *
* @attr {number} hover-delay
*/
hoverDelay: {
@@ -393,6 +411,36 @@ class Popover extends PopoverPositionMixin(
];
}
+ /**
+ * Sets the default focus delay to be used by all popover instances,
+ * except for those that have focus delay configured using property.
+ *
+ * @param {number} delay
+ */
+ static setDefaultFocusDelay(focusDelay) {
+ defaultFocusDelay = focusDelay != null && focusDelay >= 0 ? focusDelay : DEFAULT_DELAY;
+ }
+
+ /**
+ * Sets the default hide delay to be used by all popover instances,
+ * except for those that have hide delay configured using property.
+ *
+ * @param {number} hideDelay
+ */
+ static setDefaultHideDelay(hideDelay) {
+ defaultHideDelay = hideDelay != null && hideDelay >= 0 ? hideDelay : DEFAULT_DELAY;
+ }
+
+ /**
+ * Sets the default hover delay to be used by all popover instances,
+ * except for those that have hover delay configured using property.
+ *
+ * @param {number} delay
+ */
+ static setDefaultHoverDelay(hoverDelay) {
+ defaultHoverDelay = hoverDelay != null && hoverDelay >= 0 ? hoverDelay : DEFAULT_DELAY;
+ }
+
constructor() {
super();
diff --git a/packages/popover/test/a11y.test.js b/packages/popover/test/a11y.test.js
index cf09b284a4..3a38f1213d 100644
--- a/packages/popover/test/a11y.test.js
+++ b/packages/popover/test/a11y.test.js
@@ -12,13 +12,19 @@ import {
import { sendKeys } from '@web/test-runner-commands';
import sinon from 'sinon';
import './not-animated-styles.js';
-import '../vaadin-popover.js';
import { getDeepActiveElement } from '@vaadin/a11y-base/src/focus-utils.js';
+import { Popover } from '../vaadin-popover.js';
import { mouseenter, mouseleave } from './helpers.js';
describe('a11y', () => {
let popover, target, overlay;
+ before(() => {
+ Popover.setDefaultFocusDelay(0);
+ Popover.setDefaultHoverDelay(0);
+ Popover.setDefaultHideDelay(0);
+ });
+
beforeEach(async () => {
popover = fixtureSync('');
target = fixtureSync('');
diff --git a/packages/popover/test/nested.test.js b/packages/popover/test/nested.test.js
index 2ac9c63e8a..7c383aa828 100644
--- a/packages/popover/test/nested.test.js
+++ b/packages/popover/test/nested.test.js
@@ -1,12 +1,18 @@
import { expect } from '@vaadin/chai-plugins';
import { esc, fixtureSync, nextRender, nextUpdate, outsideClick } from '@vaadin/testing-helpers';
import './not-animated-styles.js';
-import '../vaadin-popover.js';
+import { Popover } from '../vaadin-popover.js';
import { mouseenter, mouseleave } from './helpers.js';
describe('nested popover', () => {
let popover, target, secondPopover, secondTarget;
+ before(() => {
+ Popover.setDefaultFocusDelay(0);
+ Popover.setDefaultHoverDelay(0);
+ Popover.setDefaultHideDelay(0);
+ });
+
beforeEach(async () => {
popover = fixtureSync('');
target = fixtureSync('');
diff --git a/packages/popover/test/timers.test.js b/packages/popover/test/timers.test.js
index cbf813bc9a..f9764daedc 100644
--- a/packages/popover/test/timers.test.js
+++ b/packages/popover/test/timers.test.js
@@ -1,29 +1,38 @@
import { expect } from '@vaadin/chai-plugins';
-import {
- aTimeout,
- esc,
- fire,
- fixtureSync,
- focusout,
- nextRender,
- nextUpdate,
- outsideClick,
-} from '@vaadin/testing-helpers';
+import { aTimeout, esc, fixtureSync, focusout, nextRender, nextUpdate, outsideClick } from '@vaadin/testing-helpers';
+import sinon from 'sinon';
import './not-animated-styles.js';
-import '../src/vaadin-popover.js';
+import { Popover } from '../src/vaadin-popover.js';
+import { mouseenter, mouseleave } from './helpers.js';
describe('timers', () => {
- let popover, target, overlay;
+ let popover, target, overlay, clock;
- function mouseenter(target) {
- fire(target, 'mouseenter');
- }
+ // Used as a fallback delay
+ const DEFAULT_DELAY = 500;
+
+ async function createPopover(target, focus) {
+ const element = fixtureSync('');
+ element.target = target;
+ element.trigger = focus ? ['focus'] : ['hover'];
- function mouseleave(target, relatedTarget) {
- const eventProps = relatedTarget ? { relatedTarget } : {};
- fire(target, 'mouseleave', undefined, eventProps);
+ // We use fake timers in reset tests, so native timers won't work.
+ // Trigger a timeout to ensure LitElement popover initial render.
+ if (clock) {
+ await clock.tickAsync(1);
+ } else {
+ await nextUpdate(element);
+ }
+
+ return element.shadowRoot.querySelector('vaadin-popover-overlay');
}
+ before(() => {
+ Popover.setDefaultFocusDelay(0);
+ Popover.setDefaultHoverDelay(0);
+ Popover.setDefaultHideDelay(0);
+ });
+
beforeEach(async () => {
popover = fixtureSync('');
popover.renderer = (root) => {
@@ -208,4 +217,268 @@ describe('timers', () => {
expect(overlay.opened).to.be.false;
});
});
+
+ describe('setDefaultHoverDelay', () => {
+ let target, overlay;
+
+ beforeEach(() => {
+ target = fixtureSync('
Target
');
+ });
+
+ afterEach(() => {
+ Popover.setDefaultHoverDelay(0);
+ });
+
+ it('should change default delay for newly created popover', async () => {
+ Popover.setDefaultHoverDelay(2);
+
+ overlay = await createPopover(target);
+
+ mouseenter(target);
+ await aTimeout(2);
+
+ expect(overlay.opened).to.be.true;
+ });
+
+ it('should change default hover delay for existing popover', async () => {
+ overlay = await createPopover(target);
+
+ Popover.setDefaultHoverDelay(2);
+
+ mouseenter(target);
+ await aTimeout(2);
+
+ expect(overlay.opened).to.be.true;
+ });
+
+ describe('reset hover delay', () => {
+ beforeEach(() => {
+ clock = sinon.useFakeTimers({
+ shouldClearNativeTimers: true,
+ });
+ });
+
+ afterEach(() => {
+ // Hide tooltip
+ mouseleave(target);
+ clock.tick(DEFAULT_DELAY);
+
+ // Reset timers
+ clock.restore();
+ });
+
+ it('should reset hover delay when providing a negative number', async () => {
+ Popover.setDefaultHoverDelay(-1);
+
+ overlay = await createPopover(target);
+
+ mouseenter(target);
+ await clock.tickAsync(DEFAULT_DELAY);
+
+ expect(overlay.opened).to.be.true;
+ });
+
+ it('should reset hover delay when providing null instead of number', async () => {
+ Popover.setDefaultHoverDelay(null);
+
+ overlay = await createPopover(target);
+
+ mouseenter(target);
+ await clock.tickAsync(DEFAULT_DELAY);
+
+ expect(overlay.opened).to.be.true;
+ });
+
+ it('should reset hover delay when providing undefined instead of number', async () => {
+ Popover.setDefaultHoverDelay(undefined);
+
+ overlay = await createPopover(target);
+
+ mouseenter(target);
+ await clock.tickAsync(DEFAULT_DELAY);
+
+ expect(overlay.opened).to.be.true;
+ });
+ });
+ });
+
+ describe('setDefaultFocusDelay', () => {
+ let target, overlay;
+
+ beforeEach(() => {
+ target = fixtureSync('Target
');
+ });
+
+ afterEach(() => {
+ Popover.setDefaultFocusDelay(0);
+ });
+
+ it('should change default delay for newly created popover', async () => {
+ Popover.setDefaultFocusDelay(2);
+
+ overlay = await createPopover(target, true);
+
+ target.focus();
+ await aTimeout(2);
+
+ expect(overlay.opened).to.be.true;
+ });
+
+ it('should change default focus delay for existing popover', async () => {
+ overlay = await createPopover(target, true);
+
+ Popover.setDefaultFocusDelay(2);
+
+ target.focus();
+ await aTimeout(2);
+
+ expect(overlay.opened).to.be.true;
+ });
+
+ describe('reset focus delay', () => {
+ beforeEach(() => {
+ clock = sinon.useFakeTimers({
+ shouldClearNativeTimers: true,
+ });
+ });
+
+ afterEach(() => {
+ // Hide tooltip
+ focusout(target);
+ clock.tick(DEFAULT_DELAY);
+
+ // Reset timers
+ clock.restore();
+ });
+
+ it('should reset focus delay when providing a negative number', async () => {
+ Popover.setDefaultFocusDelay(-1);
+
+ overlay = await createPopover(target, true);
+
+ target.focus();
+ await clock.tickAsync(DEFAULT_DELAY);
+
+ expect(overlay.opened).to.be.true;
+ });
+
+ it('should reset focus delay when providing null instead of number', async () => {
+ Popover.setDefaultFocusDelay(null);
+
+ overlay = await createPopover(target, true);
+
+ target.focus();
+ await clock.tickAsync(DEFAULT_DELAY);
+
+ expect(overlay.opened).to.be.true;
+ });
+
+ it('should reset focus delay when providing undefined instead of number', async () => {
+ Popover.setDefaultFocusDelay(undefined);
+
+ overlay = await createPopover(target, true);
+
+ target.focus();
+ await clock.tickAsync(DEFAULT_DELAY);
+
+ expect(overlay.opened).to.be.true;
+ });
+ });
+ });
+
+ describe('setDefaultHideDelay', () => {
+ let target, overlay;
+
+ beforeEach(() => {
+ target = fixtureSync('Target
');
+ });
+
+ afterEach(() => {
+ Popover.setDefaultHideDelay(0);
+ });
+
+ it('should change default hide delay for newly created popover', async () => {
+ Popover.setDefaultHideDelay(2);
+
+ overlay = await createPopover(target);
+
+ mouseenter(target);
+ await nextUpdate(overlay);
+ expect(overlay.opened).to.be.true;
+
+ mouseleave(target);
+ await aTimeout(2);
+
+ expect(overlay.opened).to.be.false;
+ });
+
+ it('should change default hide delay for existing popover', async () => {
+ overlay = await createPopover(target);
+
+ Popover.setDefaultHideDelay(2);
+
+ mouseenter(target);
+ await nextUpdate(overlay);
+ expect(overlay.opened).to.be.true;
+
+ mouseleave(target);
+ await aTimeout(2);
+
+ expect(overlay.opened).to.be.false;
+ });
+
+ describe('reset hide delay', () => {
+ beforeEach(() => {
+ clock = sinon.useFakeTimers({
+ shouldClearNativeTimers: true,
+ });
+ });
+
+ afterEach(() => {
+ clock.restore();
+ });
+
+ it('should reset hide delay when providing a negative number', async () => {
+ Popover.setDefaultHideDelay(-1);
+
+ overlay = await createPopover(target);
+
+ mouseenter(target);
+ await clock.tickAsync(DEFAULT_DELAY);
+
+ mouseleave(target);
+ await clock.tickAsync(DEFAULT_DELAY);
+
+ expect(overlay.opened).to.be.false;
+ });
+
+ it('should reset hide delay when providing null instead of number', async () => {
+ Popover.setDefaultHideDelay(null);
+
+ overlay = await createPopover(target);
+
+ mouseenter(target);
+ await clock.tickAsync(DEFAULT_DELAY);
+
+ mouseleave(target);
+ await clock.tickAsync(DEFAULT_DELAY);
+
+ expect(overlay.opened).to.be.false;
+ });
+
+ it('should reset hide delay when providing undefined instead of number', async () => {
+ Popover.setDefaultHideDelay(undefined);
+
+ overlay = await createPopover(target);
+
+ mouseenter(target);
+ await clock.tickAsync(DEFAULT_DELAY);
+
+ mouseleave(target);
+ await clock.tickAsync(DEFAULT_DELAY);
+
+ expect(overlay.opened).to.be.false;
+ });
+ });
+ });
});
diff --git a/packages/popover/test/trigger.test.js b/packages/popover/test/trigger.test.js
index 002dd1807f..bdc5d82228 100644
--- a/packages/popover/test/trigger.test.js
+++ b/packages/popover/test/trigger.test.js
@@ -12,12 +12,18 @@ import {
} from '@vaadin/testing-helpers';
import { resetMouse, sendKeys, sendMouse } from '@web/test-runner-commands';
import './not-animated-styles.js';
-import '../vaadin-popover.js';
+import { Popover } from '../vaadin-popover.js';
import { mouseenter, mouseleave } from './helpers.js';
describe('trigger', () => {
let popover, target, overlay;
+ before(() => {
+ Popover.setDefaultFocusDelay(0);
+ Popover.setDefaultHoverDelay(0);
+ Popover.setDefaultHideDelay(0);
+ });
+
beforeEach(async () => {
popover = fixtureSync('');
target = fixtureSync('');
diff --git a/packages/popover/test/typings/popover.types.ts b/packages/popover/test/typings/popover.types.ts
index b629b3b7f8..1052863aec 100644
--- a/packages/popover/test/typings/popover.types.ts
+++ b/packages/popover/test/typings/popover.types.ts
@@ -1,4 +1,3 @@
-import '../../vaadin-popover.js';
import type { ElementMixinClass } from '@vaadin/component-base/src/element-mixin.js';
import type { OverlayClassMixinClass } from '@vaadin/component-base/src/overlay-class-mixin.js';
import type { ThemePropertyMixinClass } from '@vaadin/vaadin-themable-mixin/vaadin-theme-property-mixin.js';
@@ -10,6 +9,7 @@ import type {
PopoverRenderer,
PopoverTrigger,
} from '../../vaadin-popover.js';
+import { Popover } from '../../vaadin-popover.js';
const assertType = (actual: TExpected) => actual;
@@ -53,3 +53,7 @@ popover.addEventListener('opened-changed', (event) => {
popover.addEventListener('closed', (event) => {
assertType(event);
});
+
+assertType<(delay: number) => void>(Popover.setDefaultFocusDelay);
+assertType<(delay: number) => void>(Popover.setDefaultHideDelay);
+assertType<(delay: number) => void>(Popover.setDefaultHoverDelay);