{"version":3,"file":"throttle-debounce.mjs","names":[],"sources":["../../../src/common/exec/throttle-debounce.ts"],"sourcesContent":["// General explaination https://css-tricks.com/debouncing-throttling-explained-examples/\n// From https://github.com/cowboy/jquery-throttle-debounce\n// And https://github.com/wuct/raf-throttle/blob/master/rafThrottle.js\n\nimport { promisify } from './promise'\n\n// const DEBUG = false\n// const log = DEBUG ? LoggerLazy('zeed:throttle', 'error') : () => {}\n\n/**\n * A special throttle implementation that tries to distribute execution\n * in an optimal way.\n *\n * **Functionality:** For UI usage the function is executed on first occasion (`leading`).\n * If more calls follow it will again be executed at end (`trailing`).\n * If the next call is inside the timeframe, it is delayed until `trailing`.\n * This avoids timewise too close calls.\n * It is possible to `cancel` the timeout and to `flush` a call, e.g. if\n * leaving UI situation where a final call is required to write data or similar.\n */\nexport function throttle<F extends (...args: any[]) => any>(\n  callback: F,\n  opt: {\n    delay?: number\n    trailing?: boolean\n    leading?: boolean\n  } = {},\n): F & {\n  /** Stop all timers, do not exec nothing */\n  cancel: () => void\n\n  /** Stop all timers and execute right now. */\n  immediate: (...args: Parameters<F>) => Promise<void>\n\n  /** Stop all timers and execute trailing call, if exists. */\n  stop: () => void\n\n  dispose: () => void\n} {\n  const { delay = 100, trailing = true, leading = true } = opt\n\n  let timeoutID: any\n  let checkpoint = 0\n  let visited = 0\n  let trailingExec: () => void | undefined\n\n  // const debugCheckpoint = Date.now()\n\n  function clearExistingTimeout() {\n    if (timeoutID) {\n      clearTimeout(timeoutID)\n      timeoutID = undefined\n      return true\n    }\n    return false\n  }\n\n  function wrapper(this: any, ...args: any[]) {\n    const now = Date.now()\n    const elapsed = now - checkpoint\n\n    // function debugElapsed() {\n    //   const dnow = Date.now()\n    //   return `total ${(dnow - debugCheckpoint).toFixed(1)}ms - elapsed ${(\n    //     dnow - checkpoint\n    //   ).toFixed(1)}ms - visited ${visited}x`\n    // }\n\n    const exec = () => {\n      visited = 0\n      checkpoint = Date.now()\n      callback.apply(this, args)\n    }\n\n    trailingExec = exec\n\n    // Make sure enough time has passed since last call\n    if (elapsed > delay || !timeoutID) {\n      // DEBUG && log('elapsed', debugElapsed())\n\n      // Leading execute once immediately\n      if (leading) {\n        if (elapsed > delay) {\n          // DEBUG && log('🚀 leading', debugElapsed())\n          exec()\n        }\n        else {\n          ++visited // at least trigger trailing this way\n        }\n      }\n\n      const timeout = elapsed > delay ? delay : delay - elapsed\n      // log(`⏱ start timeout with ${timeout.toFixed(1)}ms`, debugElapsed())\n\n      // Prepare for next round\n      clearExistingTimeout()\n      checkpoint = now\n\n      // Delay. We should not get here if timeout has not been reached before\n      timeoutID = setTimeout(() => {\n        // DEBUG && log('⏱ reached timeout', debugElapsed())\n        clearExistingTimeout()\n        // Only execute on trailing or when visited again, but do not twice if leading\n        if (trailing && (!leading || visited > 0)) {\n          // DEBUG && log('🚀 trailing', debugElapsed())\n          trailingExec?.()\n        }\n      }, timeout)\n    }\n    else {\n      // Count visits\n      ++visited\n      // DEBUG && log('visited', debugElapsed())\n    }\n  }\n\n  wrapper.cancel = clearExistingTimeout\n\n  wrapper.stop = () => {\n    if (clearExistingTimeout() && trailingExec)\n      trailingExec()\n  }\n\n  wrapper.immediate = async function immediate(this: any, ...args: Parameters<F>[]) {\n    clearExistingTimeout()\n    checkpoint = Date.now()\n    callback.apply(this, args)\n  }\n\n  wrapper.dispose = () => wrapper.stop()\n\n  return wrapper as any\n}\n\n/**\n * Debounce fits best for filtering a large peak of events.\n * For UI event filtering throttle is probably a better choice.\n *\n * **Functionality:**  It only fires after triggers pause for `delay` ms.\n */\nexport function debounce<F extends (...args: any[]) => any | Promise<any>>(\n  callback: F,\n  opt: {\n    delay?: number\n  } = {},\n): F & {\n  cancel: () => void\n  immediate: (...args: Parameters<F>) => Promise<void>\n  dispose: () => void\n} {\n  const { delay = 100 } = opt\n  let timeoutID: any\n  let running = false\n  let lastArguments: any[] | undefined\n\n  function clearExistingTimeout() {\n    if (timeoutID) {\n      // log('clear')\n      clearTimeout(timeoutID)\n      timeoutID = undefined\n    }\n  }\n\n  async function exec() {\n    try {\n      clearExistingTimeout()\n      if (lastArguments != null) {\n        // log('exec')\n        const args = [...lastArguments]\n        lastArguments = undefined\n        running = true\n        await promisify(callback(...args))\n        running = false\n        // log('exec done')\n        if (lastArguments != null) {\n          clearExistingTimeout()\n          // log('exec trigger next')\n          timeoutID = setTimeout(exec, delay)\n        }\n      }\n    }\n    catch (err) { }\n  }\n\n  function wrapper(this: any, ...args: any[]) {\n    lastArguments = [...args]\n    clearExistingTimeout()\n    // log('trigger')\n    if (running === false)\n      timeoutID = setTimeout(exec, delay)\n  }\n\n  async function immediate(this: any, ...args: any[]) {\n    clearExistingTimeout()\n    lastArguments = [...args]\n    await exec()\n  }\n\n  wrapper.cancel = clearExistingTimeout\n  wrapper.dispose = clearExistingTimeout\n  wrapper.immediate = immediate\n\n  return wrapper as any\n}\n"],"mappings":";;;;;;;;;;;;;;AAoBA,SAAgB,SACd,UACA,MAII,EAAE,EAYN;CACA,MAAM,EAAE,QAAQ,KAAK,WAAW,MAAM,UAAU,SAAS;CAEzD,IAAI;CACJ,IAAI,aAAa;CACjB,IAAI,UAAU;CACd,IAAI;CAIJ,SAAS,uBAAuB;AAC9B,MAAI,WAAW;AACb,gBAAa,UAAU;AACvB,eAAY;AACZ,UAAO;;AAET,SAAO;;CAGT,SAAS,QAAmB,GAAG,MAAa;EAC1C,MAAM,MAAM,KAAK,KAAK;EACtB,MAAM,UAAU,MAAM;EAStB,MAAM,aAAa;AACjB,aAAU;AACV,gBAAa,KAAK,KAAK;AACvB,YAAS,MAAM,MAAM,KAAK;;AAG5B,iBAAe;AAGf,MAAI,UAAU,SAAS,CAAC,WAAW;AAIjC,OAAI,QACF,KAAI,UAAU,MAEZ,OAAM;OAGN,GAAE;GAIN,MAAM,UAAU,UAAU,QAAQ,QAAQ,QAAQ;AAIlD,yBAAsB;AACtB,gBAAa;AAGb,eAAY,iBAAiB;AAE3B,0BAAsB;AAEtB,QAAI,aAAa,CAAC,WAAW,UAAU,GAErC,iBAAgB;MAEjB,QAAQ;QAIX,GAAE;;AAKN,SAAQ,SAAS;AAEjB,SAAQ,aAAa;AACnB,MAAI,sBAAsB,IAAI,aAC5B,eAAc;;AAGlB,SAAQ,YAAY,eAAe,UAAqB,GAAG,MAAuB;AAChF,wBAAsB;AACtB,eAAa,KAAK,KAAK;AACvB,WAAS,MAAM,MAAM,KAAK;;AAG5B,SAAQ,gBAAgB,QAAQ,MAAM;AAEtC,QAAO;;;;;;;;AAST,SAAgB,SACd,UACA,MAEI,EAAE,EAKN;CACA,MAAM,EAAE,QAAQ,QAAQ;CACxB,IAAI;CACJ,IAAI,UAAU;CACd,IAAI;CAEJ,SAAS,uBAAuB;AAC9B,MAAI,WAAW;AAEb,gBAAa,UAAU;AACvB,eAAY;;;CAIhB,eAAe,OAAO;AACpB,MAAI;AACF,yBAAsB;AACtB,OAAI,iBAAiB,MAAM;IAEzB,MAAM,OAAO,CAAC,GAAG,cAAc;AAC/B,oBAAgB;AAChB,cAAU;AACV,UAAM,UAAU,SAAS,GAAG,KAAK,CAAC;AAClC,cAAU;AAEV,QAAI,iBAAiB,MAAM;AACzB,2BAAsB;AAEtB,iBAAY,WAAW,MAAM,MAAM;;;WAIlC,KAAK;;CAGd,SAAS,QAAmB,GAAG,MAAa;AAC1C,kBAAgB,CAAC,GAAG,KAAK;AACzB,wBAAsB;AAEtB,MAAI,YAAY,MACd,aAAY,WAAW,MAAM,MAAM;;CAGvC,eAAe,UAAqB,GAAG,MAAa;AAClD,wBAAsB;AACtB,kBAAgB,CAAC,GAAG,KAAK;AACzB,QAAM,MAAM;;AAGd,SAAQ,SAAS;AACjB,SAAQ,UAAU;AAClB,SAAQ,YAAY;AAEpB,QAAO"}