From 8697a136c3f335005fa80d40afdd2ab8211fdf47 Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Thu, 23 Jun 2022 07:10:00 +0000 Subject: [PATCH] [IMP] web: tooltip: style, delay and empty tooltips This commit is threefold: 1) it improves the style of tooltips (mostly, a larger font size) 2) it adds an option, `data-tooltip-delay` that allows to specify the delay before the tooltip opens 3) it handles the case where the tooltip is empty (no tooltip displayed) Part-of: odoo/odoo#92475 --- .../web/static/src/core/tooltip/tooltip.scss | 5 +- .../src/core/tooltip/tooltip_service.js | 75 +++++++++++++------ .../core/tooltip/tooltip_service_tests.js | 36 ++++++++- 3 files changed, 91 insertions(+), 25 deletions(-) diff --git a/addons/web/static/src/core/tooltip/tooltip.scss b/addons/web/static/src/core/tooltip/tooltip.scss index 98933b8ad56..faacce47bf4 100644 --- a/addons/web/static/src/core/tooltip/tooltip.scss +++ b/addons/web/static/src/core/tooltip/tooltip.scss @@ -1,5 +1,5 @@ .o-tooltip { - font-size: smaller; + font-size: small; .o-tooltip--string { background-color: $o-tooltip-title-background-color; @@ -8,18 +8,21 @@ } .o-tooltip--help { + font-size: smaller; white-space: pre-line; padding: 8px; margin-bottom: 0px; } .o-tooltip--technical { + font-size: smaller; padding: 8px; margin: 0 0 0 15px; list-style-type: circle; .o-tooltip--technical--title { font-weight: bold; + margin-right: 4px; } } } diff --git a/addons/web/static/src/core/tooltip/tooltip_service.js b/addons/web/static/src/core/tooltip/tooltip_service.js index bc591385fc8..1aa80452a63 100644 --- a/addons/web/static/src/core/tooltip/tooltip_service.js +++ b/addons/web/static/src/core/tooltip/tooltip_service.js @@ -19,6 +19,9 @@ const { whenReady } = owl; * "data-tooltip-position": * * + * The opening delay can be modified with the "data-tooltip-delay" attribute (default: 400): + * + * * For advanced tooltips containing dynamic and/or html content, the * "data-tooltip-template" and "data-tooltip-info" attributes can be used. * For example, let's suppose the following qweb template: @@ -42,7 +45,6 @@ export const tooltipService = { let openTooltipTimeout; let closeTooltip; let target = null; - let positionX; let positionY; @@ -56,6 +58,29 @@ export const tooltipService = { } } + /** + * Checks that the target is in the DOM and we're hovering the target. + * @returns {boolean} + */ + function shouldCleanup() { + if (!target) { + return; + } + if (!document.body.contains(target)) { + return cleanup(); // target is no longer in the DOM + } + const targetRect = target.getBoundingClientRect(); + if ( + positionX < targetRect.left || + positionX > targetRect.right || + positionY < targetRect.top || + positionY > targetRect.bottom + ) { + return true; // mouse is no longer hovering the target + } + return false; + } + /** * Opens the tooltip after a delay, if the event's target has a data-tooltip * attribute. @@ -76,35 +101,39 @@ export const tooltipService = { info = dataset.tooltipInfo ? JSON.parse(dataset.tooltipInfo) : null; } cleanup(); + if (tooltip === "" && !template) { + return; + } + + // register mouse position first in case the target + // contains disabled elements which would prevent + // to track mousemove. The position is needed to check + // whether the element is hovered or not + positionX = ev.x; + positionY = ev.y; + + const delay = dataset.tooltipDelay ? JSON.parse(dataset.tooltipDelay) : OPEN_DELAY; openTooltipTimeout = browser.setTimeout(() => { - const position = dataset.tooltipPosition; - closeTooltip = popover.add( - target, - Tooltip, - { tooltip, template, info }, - { position } - ); - }, OPEN_DELAY); + if (shouldCleanup()) { + cleanup(); + } else { + const position = dataset.tooltipPosition; + closeTooltip = popover.add( + target, + Tooltip, + { tooltip, template, info }, + { position } + ); + } + }, delay); } whenReady(() => { // Regularly check that the target is still in the DOM and we're still // hovering it, because if not, we have to close the tooltipd browser.setInterval(() => { - if (!target) { - return; - } - if (!document.body.contains(target)) { - return cleanup(); // target is no longer in the DOM - } - const targetRect = target.getBoundingClientRect(); - if ( - positionX < targetRect.left || - positionX > targetRect.right || - positionY < targetRect.top || - positionY > targetRect.bottom - ) { - return cleanup(); // mouse is no longer hovering the target + if (shouldCleanup()) { + cleanup(); } }, CLOSE_DELAY); diff --git a/addons/web/static/tests/core/tooltip/tooltip_service_tests.js b/addons/web/static/tests/core/tooltip/tooltip_service_tests.js index 463d20084bd..247ce04c321 100644 --- a/addons/web/static/tests/core/tooltip/tooltip_service_tests.js +++ b/addons/web/static/tests/core/tooltip/tooltip_service_tests.js @@ -8,6 +8,7 @@ import { registry } from "@web/core/registry"; import { clearRegistryWithCleanup, makeTestEnv } from "../../helpers/mock_env"; import { getFixture, nextTick, patchWithCleanup, triggerEvent } from "../../helpers/utils"; import { registerCleanup } from "../../helpers/cleanup"; +import { makeFakeLocalizationService } from "../../helpers/mock_services"; const { App, Component, useState, xml } = owl; @@ -28,7 +29,7 @@ const mainComponents = registry.category("main_components"); * @param {{[templateName:string]: string}} [options.templates] additional templates * @returns {Promise} */ -async function makeParent(Child, options = {}) { +export async function makeParent(Child, options = {}) { const target = getFixture(); // add the popover service to the registry -> will add the PopoverContainer @@ -44,6 +45,7 @@ async function makeParent(Child, options = {}) { registry.category("services").add("popover", popoverService); registry.category("services").add("tooltip", tooltipService); + registry.category("services").add("localization", makeFakeLocalizationService()); const env = await makeTestEnv(); patchWithCleanup(env.services.popover, { @@ -341,4 +343,36 @@ QUnit.module("Tooltip service", (hooks) => { "" ); }); + + QUnit.test("empty tooltip, no template", async (assert) => { + class MyComponent extends Component { + get tooltip() { + return ""; + } + } + MyComponent.template = xml``; + let simulateTimeout = () => {}; + const mockSetTimeout = (fn) => { + simulateTimeout = fn; + }; + await makeParent(MyComponent, { mockSetTimeout }); + + assert.containsNone(target, ".o_popover_container .o_popover"); + target.querySelector("button").dispatchEvent(new Event("mouseenter")); + await nextTick(); + simulateTimeout(); + await nextTick(); + assert.containsNone(target, ".o_popover_container .o_popover"); + }); + + QUnit.test("tooltip with a delay", async (assert) => { + class MyComponent extends Component {} + MyComponent.template = xml``; + const mockSetTimeout = (fn, delay) => { + assert.strictEqual(delay, 2000); + }; + await makeParent(MyComponent, { mockSetTimeout }); + target.querySelector("button").dispatchEvent(new Event("mouseenter")); + await nextTick(); + }); });