diff --git a/addons/web/static/src/core/tooltip/tooltip_hook.js b/addons/web/static/src/core/tooltip/tooltip_hook.js
deleted file mode 100644
index 24d9d05de06..00000000000
--- a/addons/web/static/src/core/tooltip/tooltip_hook.js
+++ /dev/null
@@ -1,120 +0,0 @@
-/** @odoo-module **/
-
-import { browser } from "@web/core/browser/browser";
-import { useEffect, useListener } from "@web/core/utils/hooks";
-import { usePopover } from "@web/core/popover/popover_hook";
-import { Tooltip } from "./tooltip";
-
-/**
- * The useTooltip hook allows to display custom tooltips on every elements with
- * a "data-tooltip" attribute inside the Component using the hook. This attribute
- * can be set on elements for which we prefer a custom tooltip instead of the
- * native one displaying the value of the "title" attribute.
- *
- * Usage (with a parent somewhere using the useTooltip hook):
- *
- *
- * The ideal position of the tooltip can be specified thanks to the attribute
- * "data-tooltip-position":
- *
- *
- * 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:
- *
- *
- *
info.x
- *
info.y
- *
- *
- * This template can then be used in a tooltip as follows:
- *
- * with "info" being a stringified object with two keys "x" and "y".
- */
-const OPEN_DELAY = 400;
-const CLOSE_DELAY = 200;
-export function useTooltip() {
- const popover = usePopover();
- let openTooltipTimeout;
- let closeTooltip;
- let target = null;
-
- /**
- * Closes the currently opened tooltip if any, or prevent it from opening.
- */
- function cleanup() {
- browser.clearTimeout(openTooltipTimeout);
- if (closeTooltip) {
- closeTooltip();
- }
- }
-
- /**
- * Opens the tooltip after a delay, if the event's target has a data-tooltip
- * attribute.
- *
- * @param {MouseEvent} ev a "mouseenter" event
- */
- function onMouseEnter(ev) {
- const dataset = ev.target.dataset;
- const tooltip = dataset.tooltip;
- let template, info;
- if ("tooltipTemplate" in dataset) {
- template = dataset.tooltipTemplate;
- info = dataset.tooltipInfo ? JSON.parse(dataset.tooltipInfo) : null;
- }
- if (tooltip || template) {
- cleanup();
- target = ev.target;
- openTooltipTimeout = browser.setTimeout(() => {
- const position = dataset.tooltipPosition;
- closeTooltip = popover.add(
- target,
- Tooltip,
- { tooltip, template, info },
- { position }
- );
- }, OPEN_DELAY);
- }
- }
-
- // Track mouse position to be able to detect that we are no longer hovering
- // the target, thus that we should close the tooltip
- let positionX;
- let positionY;
- useListener("mousemove", (ev) => {
- positionX = ev.x;
- positionY = ev.y;
- });
-
- // Regularly check that the target is still in the DOM and we're still
- // hovering it, because if not, we have to close the tooltip
- useEffect(
- () => {
- const interval = 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
- }
- }, CLOSE_DELAY);
- return () => browser.clearInterval(interval);
- },
- () => []
- );
-
- // Listen (using event delegation) to "mouseenter" events on all nodes with
- // the "data-tooltip" or "data-tooltip-template" attribute, to open the tooltip.
- const selector = "[data-tooltip], [data-tooltip-template]";
- useListener("mouseenter", selector, onMouseEnter, { capture: true });
-}
diff --git a/addons/web/static/src/core/tooltip/tooltip_service.js b/addons/web/static/src/core/tooltip/tooltip_service.js
new file mode 100644
index 00000000000..bc591385fc8
--- /dev/null
+++ b/addons/web/static/src/core/tooltip/tooltip_service.js
@@ -0,0 +1,125 @@
+/** @odoo-module **/
+
+import { browser } from "@web/core/browser/browser";
+import { registry } from "@web/core/registry";
+import { Tooltip } from "./tooltip";
+
+const { whenReady } = owl;
+
+/**
+ * The tooltip service allows to display custom tooltips on every elements with
+ * a "data-tooltip" attribute. This attribute can be set on elements for which
+ * we prefer a custom tooltip instead of the native one displaying the value of
+ * the "title" attribute.
+ *
+ * Usage:
+ *
+ *
+ * The ideal position of the tooltip can be specified thanks to the attribute
+ * "data-tooltip-position":
+ *
+ *
+ * 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:
+ *
+ *
+ *
info.x
+ *
info.y
+ *
+ *
+ * This template can then be used in a tooltip as follows:
+ *
+ * with "info" being a stringified object with two keys "x" and "y".
+ */
+
+const OPEN_DELAY = 400;
+const CLOSE_DELAY = 200;
+
+export const tooltipService = {
+ dependencies: ["popover"],
+ start(env, { popover }) {
+ let openTooltipTimeout;
+ let closeTooltip;
+ let target = null;
+
+ let positionX;
+ let positionY;
+
+ /**
+ * Closes the currently opened tooltip if any, or prevent it from opening.
+ */
+ function cleanup() {
+ browser.clearTimeout(openTooltipTimeout);
+ if (closeTooltip) {
+ closeTooltip();
+ }
+ }
+
+ /**
+ * Opens the tooltip after a delay, if the event's target has a data-tooltip
+ * attribute.
+ *
+ * @param {MouseEvent} ev a "mouseenter" event
+ */
+ function onMouseenter(ev) {
+ let el = ev.target;
+ if (!el.matches("[data-tooltip], [data-tooltip-template]")) {
+ return;
+ }
+ target = el;
+ const dataset = el.dataset;
+ const tooltip = dataset.tooltip;
+ let template, info;
+ if ("tooltipTemplate" in dataset) {
+ template = dataset.tooltipTemplate;
+ info = dataset.tooltipInfo ? JSON.parse(dataset.tooltipInfo) : null;
+ }
+ cleanup();
+ openTooltipTimeout = browser.setTimeout(() => {
+ const position = dataset.tooltipPosition;
+ closeTooltip = popover.add(
+ target,
+ Tooltip,
+ { tooltip, template, info },
+ { position }
+ );
+ }, OPEN_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
+ }
+ }, CLOSE_DELAY);
+
+ // Track mouse position to be able to detect that we are no longer hovering
+ // the target, thus that we should close the tooltip
+ document.body.addEventListener("mousemove", (ev) => {
+ positionX = ev.x;
+ positionY = ev.y;
+ });
+
+ // Listen (using event delegation) to "mouseenter" events on all nodes with
+ // the "data-tooltip" or "data-tooltip-template" attribute, to open the tooltip.
+ document.body.addEventListener("mouseenter", onMouseenter, { capture: true });
+ });
+ },
+};
+
+registry.category("services").add("tooltip", tooltipService);
diff --git a/addons/web/static/src/webclient/webclient.js b/addons/web/static/src/webclient/webclient.js
index 8efe92e4a6e..cf0260f1dd7 100644
--- a/addons/web/static/src/webclient/webclient.js
+++ b/addons/web/static/src/webclient/webclient.js
@@ -3,7 +3,6 @@
import { ActionContainer } from "./actions/action_container";
import { NavBar } from "./navbar/navbar";
import { useBus, useEffect, useService } from "@web/core/utils/hooks";
-import { useTooltip } from "@web/core/tooltip/tooltip_hook";
import { NotUpdatable } from "../core/utils/components";
import { MainComponentsContainer } from "../core/main_components_container";
import { useOwnDebugContext } from "../core/debug/debug_context";
@@ -46,7 +45,6 @@ export class WebClient extends Component {
() => []
);
useExternalListener(window, "click", this.onGlobalClick, { capture: true });
- useTooltip();
}
mounted() {
diff --git a/addons/web/static/tests/core/tooltip/tooltip_hook_tests.js b/addons/web/static/tests/core/tooltip/tooltip_hook_tests.js
deleted file mode 100644
index feb25e7217d..00000000000
--- a/addons/web/static/tests/core/tooltip/tooltip_hook_tests.js
+++ /dev/null
@@ -1,287 +0,0 @@
-/** @odoo-module **/
-
-import { browser } from "@web/core/browser/browser";
-import { PopoverContainer } from "@web/core/popover/popover_container";
-import { popoverService } from "@web/core/popover/popover_service";
-import { useTooltip } from "@web/core/tooltip/tooltip_hook";
-import { registry } from "@web/core/registry";
-import { registerCleanup } from "../../helpers/cleanup";
-import { clearRegistryWithCleanup, makeTestEnv } from "../../helpers/mock_env";
-import { getFixture, nextTick, patchWithCleanup, triggerEvent } from "../../helpers/utils";
-
-const { Component, mount, useState, xml } = owl;
-
-const mainComponents = registry.category("main_components");
-
-/**
- * Creates and mounts a parent component that use the "useTooltip" hook.
- *
- * @param {Component} Child a child Component that contains nodes with "data-tooltip" attribute
- * @param {Object} [options]
- * @param {function} options.mockSetTimeout the mocked setTimeout to use (by default, calls the
- * callback directly)
- * @param {function} options.mockSetInterval the mocked setInterval to use (by default, calls the
- * callback directly)
- * @param {function} options.mockClearTimeout the mocked clearTimeout to use (by default, does nothing)
- * @param {function} options.mockClearInterval the mocked clearInterval to use (by default, does nothing)
- * @returns {Promise}
- */
-async function makeParent(Child, options = {}) {
- const fixture = getFixture();
-
- // add the popover service to the registry -> will add the PopoverContainer
- // to the mainComponentRegistry
- clearRegistryWithCleanup(mainComponents);
- registry.category("services").add("popover", popoverService);
- const env = await makeTestEnv();
-
- class Parent extends Component {
- setup() {
- this.Components = mainComponents.getEntries();
- useTooltip();
- }
- }
- Parent.template = xml`
-