[IMP] web: debounce: support "animationFrame" delay
By passing "animationFrame" as delay value, the debounced function will be called just before the next animation frame following the moment if has been effectively called. Part-of: odoo/odoo#92475
This commit is contained in:
committed by
Aaron Bohy
parent
7a8052efd8
commit
e5152781cc
@@ -1,6 +1,6 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { browser } from "../browser/browser";
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
|
||||
/**
|
||||
* Creates a version of the function where only the last call between two
|
||||
@@ -33,7 +33,7 @@ export function throttleForAnimation(func) {
|
||||
|
||||
/**
|
||||
* Creates and returns a new debounced version of the passed function (func)
|
||||
* which will postpone its execution until after wait milliseconds
|
||||
* which will postpone its execution until after 'delay' milliseconds
|
||||
* have elapsed since the last time it was invoked. The debounced function
|
||||
* will return a Promise that will be resolved when the function (func)
|
||||
* has been fully executed. If `immediate` is passed, trigger the function
|
||||
@@ -41,26 +41,31 @@ export function throttleForAnimation(func) {
|
||||
*
|
||||
* @template T the return type of the original function
|
||||
* @param {(...args) => T} func the function to debounce
|
||||
* @param {number} wait how long should elapse before the function is called
|
||||
* @param {number | "animationFrame"} delay how long should elapse before the function
|
||||
* is called. If 'animationFrame' is given instead of a number, 'requestAnimationFrame'
|
||||
* will be used instead of 'setTimeout'.
|
||||
* @param {boolean} [immediate=false] whether the function should be called on
|
||||
* the leading edge instead of the trailing edge.
|
||||
* @returns {{ (...args): Promise<T>, cancel: () => void }} the debounced function
|
||||
*/
|
||||
export function debounce(func, wait, immediate = false) {
|
||||
let timeout;
|
||||
export function debounce(func, delay, immediate = false) {
|
||||
let handle;
|
||||
const funcName = func.name ? func.name + " (debounce)" : "debounce";
|
||||
const useAnimationFrame = delay === "animationFrame";
|
||||
const setFnName = useAnimationFrame ? "requestAnimationFrame" : "setTimeout";
|
||||
const clearFnName = useAnimationFrame ? "cancelAnimationFrame" : "clearTimeout";
|
||||
return Object.assign(
|
||||
{
|
||||
[funcName](...args) {
|
||||
return new Promise((resolve) => {
|
||||
const callNow = immediate && !timeout;
|
||||
browser.clearTimeout(timeout);
|
||||
timeout = browser.setTimeout(() => {
|
||||
timeout = null;
|
||||
const callNow = immediate && !handle;
|
||||
browser[clearFnName](handle);
|
||||
handle = browser[setFnName](() => {
|
||||
handle = null;
|
||||
if (!immediate) {
|
||||
Promise.resolve(func.apply(this, args)).then(resolve);
|
||||
}
|
||||
}, wait);
|
||||
}, delay);
|
||||
if (callNow) {
|
||||
Promise.resolve(func.apply(this, args)).then(resolve);
|
||||
}
|
||||
@@ -69,7 +74,7 @@ export function debounce(func, wait, immediate = false) {
|
||||
}[funcName],
|
||||
{
|
||||
cancel() {
|
||||
browser.clearTimeout(timeout);
|
||||
browser[clearFnName](handle);
|
||||
},
|
||||
}
|
||||
);
|
||||
@@ -82,10 +87,10 @@ export function debounce(func, wait, immediate = false) {
|
||||
* @deprecated this function has behaviour that is unexpected considering its
|
||||
* name, prefer _.throttle until this function is rewritten
|
||||
* @param {Function} func
|
||||
* @param {number} wait
|
||||
* @param {number} delay
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function throttle(func, wait) {
|
||||
export function throttle(func, delay) {
|
||||
let waiting = false;
|
||||
const funcName = func.name ? func.name + " (throttle)" : "throttle";
|
||||
return {
|
||||
@@ -96,7 +101,7 @@ export function throttle(func, wait) {
|
||||
browser.setTimeout(function () {
|
||||
waiting = false;
|
||||
func.call(context, ...args);
|
||||
}, wait);
|
||||
}, delay);
|
||||
}
|
||||
},
|
||||
}[funcName];
|
||||
|
||||
@@ -14,7 +14,7 @@ let browser;
|
||||
let baseConfig;
|
||||
let BlockUI, props;
|
||||
|
||||
QUnit.module("UI", {
|
||||
QUnit.module("UI service", {
|
||||
async beforeEach() {
|
||||
target = getFixture();
|
||||
serviceRegistry.add("ui", uiService);
|
||||
|
||||
@@ -74,7 +74,7 @@ QUnit.module("utils", () => {
|
||||
assert.step("myFunc");
|
||||
return 42;
|
||||
};
|
||||
const myDebouncedFunc = debounce(myFunc, 3000, true);
|
||||
const myDebouncedFunc = debounce(myFunc, 3000, { immediate: true });
|
||||
myDebouncedFunc().then((x) => {
|
||||
assert.step("resolved " + x);
|
||||
});
|
||||
@@ -98,6 +98,22 @@ QUnit.module("utils", () => {
|
||||
assert.verifySteps(["resolved 42"]);
|
||||
});
|
||||
|
||||
QUnit.test("debounce with 'animationFrame' delay", async function (assert) {
|
||||
const { execRegisteredTimeouts } = mockTimeout();
|
||||
const execRegisteredAnimationFrames = mockAnimationFrame();
|
||||
const myFunc = () => {
|
||||
assert.step("myFunc");
|
||||
};
|
||||
debounce(myFunc, "animationFrame")();
|
||||
assert.verifySteps([]);
|
||||
|
||||
execRegisteredTimeouts(); // should have no effect as we wait for the animation frame
|
||||
assert.verifySteps([]);
|
||||
|
||||
execRegisteredAnimationFrames(); // should call the function
|
||||
assert.verifySteps(["myFunc"]);
|
||||
});
|
||||
|
||||
QUnit.test("debounced call can be cancelled", async function (assert) {
|
||||
assert.expect(3);
|
||||
const { execRegisteredTimeouts } = mockTimeout();
|
||||
|
||||
Reference in New Issue
Block a user