diff --git a/addons/web/static/src/core/utils/timing.js b/addons/web/static/src/core/utils/timing.js index d68965f836f..d9cebde8a65 100644 --- a/addons/web/static/src/core/utils/timing.js +++ b/addons/web/static/src/core/utils/timing.js @@ -1,58 +1,8 @@ /** @odoo-module **/ import { browser } from "@web/core/browser/browser"; - import { onWillUnmount, useComponent } from "@odoo/owl"; -/** - * Creates a version of the function where only the last call between two - * animation frames is executed before the browser's next repaint. This - * effectively throttles the function to the display's refresh rate. - * NB: The first call is always called immediately (leading edge). - * - * @template {Function} T - * @param {T} func the function to throttle - * @returns {T & { cancel: () => void }} the throttled function - */ -export function throttleForAnimation(func) { - let handle = null; - const calls = new Set(); - const funcName = func.name ? `${func.name} (throttleForAnimation)` : "throttleForAnimation"; - const pending = () => { - if (calls.size) { - handle = browser.requestAnimationFrame(pending); - const { args, resolve } = [...calls].pop(); - calls.clear(); - Promise.resolve(func.apply(this, args)).then(resolve); - } else { - handle = null; - } - }; - return Object.assign( - { - /** @type {any} */ - [funcName](...args) { - return new Promise((resolve) => { - const isNew = handle === null; - if (isNew) { - handle = browser.requestAnimationFrame(pending); - Promise.resolve(func.apply(this, args)).then(resolve); - } else { - calls.add({ args, resolve }); - } - }); - }, - }[funcName], - { - cancel() { - browser.cancelAnimationFrame(handle); - calls.clear(); - handle = null; - }, - } - ); -} - /** * Creates and returns a new debounced version of the passed function (func) * which will postpone its execution until after 'delay' milliseconds @@ -103,24 +53,6 @@ export function debounce(func, delay, immediate = false) { ); } -/** - * Hook that returns a debounced version of the given function, and cancels - * the potential pending execution on willUnmount. - * @see debounce - * @template {Function} T - * @param {T} callback - * @param {number | "animationFrame"} delay - * @param {boolean} [immediate=false] whether the function should be called on - * the leading edge instead of the trailing edge. - * @returns {T & { cancel: () => void }} - */ -export function useDebounced(callback, delay, immediate = false) { - const component = useComponent(); - const debounced = debounce(callback.bind(component), delay, immediate); - onWillUnmount(() => debounced.cancel()); - return debounced; -} - /** * Function that calls recursively a request to an animation frame. * Useful to call a function repetitively, until asked to stop, that needs constant rerendering. @@ -144,3 +76,72 @@ export function setRecurringAnimationFrame(callback) { return stop; } + +/** + * Creates a version of the function where only the last call between two + * animation frames is executed before the browser's next repaint. This + * effectively throttles the function to the display's refresh rate. + * NB: The first call is always called immediately (leading edge). + * + * @template {Function} T + * @param {T} func the function to throttle + * @returns {T & { cancel: () => void }} the throttled function + */ +export function throttleForAnimation(func) { + let handle = null; + const calls = new Set(); + const funcName = func.name ? `${func.name} (throttleForAnimation)` : "throttleForAnimation"; + const pending = () => { + if (calls.size) { + handle = browser.requestAnimationFrame(pending); + const { args, resolve } = [...calls].pop(); + calls.clear(); + Promise.resolve(func.apply(this, args)).then(resolve); + } else { + handle = null; + } + }; + return Object.assign( + { + /** @type {any} */ + [funcName](...args) { + return new Promise((resolve) => { + const isNew = handle === null; + if (isNew) { + handle = browser.requestAnimationFrame(pending); + Promise.resolve(func.apply(this, args)).then(resolve); + } else { + calls.add({ args, resolve }); + } + }); + }, + }[funcName], + { + cancel() { + browser.cancelAnimationFrame(handle); + calls.clear(); + handle = null; + }, + } + ); +} + +// ----------------------------------- HOOKS ----------------------------------- + +/** + * Hook that returns a debounced version of the given function, and cancels + * the potential pending execution on willUnmount. + * @see debounce + * @template {Function} T + * @param {T} callback + * @param {number | "animationFrame"} delay + * @param {boolean} [immediate=false] whether the function should be called on + * the leading edge instead of the trailing edge. + * @returns {T & { cancel: () => void }} + */ +export function useDebounced(callback, delay, immediate = false) { + const component = useComponent(); + const debounced = debounce(callback.bind(component), delay, immediate); + onWillUnmount(() => debounced.cancel()); + return debounced; +}