All files / src/support/utils function.ts

100% Statements 34/34
100% Branches 28/28
100% Functions 7/7
100% Lines 34/34

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109        11x 54x 54x                                                         11x             11x               14x 14x   14x 13x 13x 13x     13x     14x     32x   14x             14x   29x 29x 29x   29x 28x 28x 28x 28x 28x 28x           28x 28x   28x 5x     28x   1x     14x   14x    
import { isNumber, isFunction } from 'support/utils/types';
import { from } from 'support/utils/array';
import { rAF, cAF } from 'support/compatibility/apis';
 
const clearTimeouts = (id: number | undefined) => {
  id && window.clearTimeout(id);
  id && cAF!(id);
};
 
type DebounceTiming = number | false | null | undefined;
 
export interface DebounceOptions<FunctionToDebounce extends (...args: any) => any> {
  /**
   * The timeout for debouncing. If null, no debounce is applied.
   */
  _timeout?: DebounceTiming | (() => DebounceTiming);
  /**
   * A maximum amount of ms. before the function will be called even with debounce.
   */
  _maxDelay?: DebounceTiming | (() => DebounceTiming);
  /**
   * Function which merges parameters for each canceled debounce.
   * If parameters can't be merged the function will return null, otherwise it returns the merged parameters.
   */
  _mergeParams?: (
    prev: Parameters<FunctionToDebounce>,
    curr: Parameters<FunctionToDebounce>
  ) => Parameters<FunctionToDebounce> | false | null | undefined;
}
 
export interface Debounced<FunctionToDebounce extends (...args: any) => any> {
  (...args: Parameters<FunctionToDebounce>): ReturnType<FunctionToDebounce>;
  _flush(): void;
}
 
export const noop = () => {}; // eslint-disable-line
 
/**
 * Debounces the given function either with a timeout or a animation frame.
 * @param functionToDebounce The function which shall be debounced.
 * @param options Options for debouncing.
 */
export const debounce = <FunctionToDebounce extends (...args: any) => any>(
  functionToDebounce: FunctionToDebounce,
  options?: DebounceOptions<FunctionToDebounce>
): Debounced<FunctionToDebounce> => {
  let timeoutId: number | undefined;
  let maxTimeoutId: number | undefined;
  let prevArguments: Parameters<FunctionToDebounce> | null | undefined;
  let latestArguments: Parameters<FunctionToDebounce> | null | undefined;
  const { _timeout, _maxDelay, _mergeParams } = options || {};
  const setT = window.setTimeout;
 
  const invokeFunctionToDebounce = function (args: IArguments) {
    clearTimeouts(timeoutId);
    clearTimeouts(maxTimeoutId);
    maxTimeoutId = timeoutId = prevArguments = undefined;
    // eslint-disable-next-line
    // @ts-ignore
    functionToDebounce.apply(this, args);
  };
 
  const mergeParms = (
    curr: Parameters<FunctionToDebounce>
  ): Parameters<FunctionToDebounce> | false | null | undefined =>
    _mergeParams && prevArguments ? _mergeParams(prevArguments, curr) : curr;
 
  const flush = () => {
    /* istanbul ignore next */
    if (timeoutId) {
      invokeFunctionToDebounce(mergeParms(latestArguments!) || latestArguments!);
    }
  };
 
  const debouncedFn = function () {
    // eslint-disable-next-line prefer-rest-params
    const args: Parameters<FunctionToDebounce> = from(arguments) as Parameters<FunctionToDebounce>;
    const finalTimeout = isFunction(_timeout) ? _timeout() : _timeout;
    const hasTimeout = isNumber(finalTimeout) && finalTimeout >= 0;
 
    if (hasTimeout) {
      const finalMaxWait = isFunction(_maxDelay) ? _maxDelay() : _maxDelay;
      const hasMaxWait = isNumber(finalMaxWait) && finalMaxWait >= 0;
      const setTimeoutFn = finalTimeout > 0 ? setT : rAF!;
      const mergeParamsResult = mergeParms(args);
      const invokedArgs = mergeParamsResult || args;
      const boundInvoke = invokeFunctionToDebounce.bind(0, invokedArgs);
 
      // if (!mergeParamsResult) {
      //   invokeFunctionToDebounce(prevArguments || args);
      // }
 
      clearTimeouts(timeoutId);
      timeoutId = setTimeoutFn(boundInvoke, finalTimeout as number) as number;
 
      if (hasMaxWait && !maxTimeoutId) {
        maxTimeoutId = setT(flush, finalMaxWait as number);
      }
 
      prevArguments = latestArguments = invokedArgs;
    } else {
      invokeFunctionToDebounce(args);
    }
  };
  debouncedFn._flush = flush;
 
  return debouncedFn as Debounced<FunctionToDebounce>;
};