{"version":3,"file":"useStepRepeat.mjs","names":[],"sources":["../../../../package/common/numberInput/useStepRepeat.ts"],"sourcesContent":["import { useCallback, useEffect, useRef } from 'react';\n\n/** Time a button must be held before the value starts repeating. */\nconst INITIAL_DELAY_MS = 400;\n/** Interval between repeats right after the hold kicks in. */\nconst SLOW_INTERVAL_MS = 120;\n/** Interval the repeat accelerates to once the hold has been going a while. */\nconst FAST_INTERVAL_MS = 50;\n/** How long the slow phase lasts before switching to `FAST_INTERVAL_MS`. */\nconst ACCELERATE_AFTER_MS = 1000;\n\nexport interface UseStepRepeatResult {\n  /** Fires one step immediately, then starts the hold-to-repeat timer. */\n  start: (direction: 1 | -1, multiplier: number) => void;\n  /** Cancels a running repeat. Safe to call when nothing is running. */\n  stop: () => void;\n}\n\n/**\n * Hold-to-repeat for the stepper buttons: one step on press, then an accelerating repeat.\n *\n * The release is watched on `window` rather than by capturing the pointer, which would retarget\n * the following click past the node Button's ripple listens on. Capture would have guaranteed a\n * `pointerup`, so the extra listeners cover the ways a press can end without one.\n *\n * `onStep` returns whether anything changed; `false` ends the repeat, so a hold at a bound stops.\n */\nexport function useStepRepeat(onStep: (delta: number) => boolean): UseStepRepeatResult {\n  const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n  const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);\n  // Read at repeat time, so a repeat always steps from the latest value.\n  const onStepRef = useRef(onStep);\n  onStepRef.current = onStep;\n\n  // One stable reference to attach and detach with; `stop` and `onEndEvent` also refer to each\n  // other, so both go through the ref.\n  const handlersRef = useRef<{ stop: () => void; onEndEvent: (e: Event) => void }>(null!);\n\n  const stop = useCallback(() => {\n    if (timeoutRef.current !== null) {\n      clearTimeout(timeoutRef.current);\n      timeoutRef.current = null;\n    }\n    if (intervalRef.current !== null) {\n      clearInterval(intervalRef.current);\n      intervalRef.current = null;\n    }\n    if (typeof window === 'undefined') return;\n    const { onEndEvent } = handlersRef.current;\n    window.removeEventListener('pointerup', onEndEvent);\n    window.removeEventListener('pointercancel', onEndEvent);\n    window.removeEventListener('pointermove', onEndEvent);\n    window.removeEventListener('blur', onEndEvent);\n    document.removeEventListener('visibilitychange', onEndEvent);\n  }, []);\n\n  handlersRef.current = {\n    stop,\n    onEndEvent: (e: Event) => {\n      // A move ends the hold only if nothing is pressed — how a mouse released outside the\n      // window reports itself on re-entry.\n      if (e.type === 'pointermove' && (e as PointerEvent).buttons !== 0) return;\n      if (e.type === 'visibilitychange' && !document.hidden) return;\n      handlersRef.current.stop();\n    },\n  };\n\n  const start = useCallback(\n    (direction: 1 | -1, multiplier: number) => {\n      stop();\n      const { onEndEvent } = handlersRef.current;\n      window.addEventListener('pointerup', onEndEvent);\n      window.addEventListener('pointercancel', onEndEvent);\n      window.addEventListener('pointermove', onEndEvent);\n      window.addEventListener('blur', onEndEvent);\n      document.addEventListener('visibilitychange', onEndEvent);\n\n      const fire = () => {\n        if (!onStepRef.current(direction * multiplier)) stop();\n      };\n      fire();\n\n      timeoutRef.current = setTimeout(() => {\n        timeoutRef.current = null;\n        const startedAt = Date.now();\n        let isFast = false;\n        const tick = () => {\n          fire();\n          if (intervalRef.current === null) return;\n          if (!isFast && Date.now() - startedAt >= ACCELERATE_AFTER_MS) {\n            isFast = true;\n            if (intervalRef.current !== null) clearInterval(intervalRef.current);\n            intervalRef.current = setInterval(tick, FAST_INTERVAL_MS);\n          }\n        };\n        intervalRef.current = setInterval(tick, SLOW_INTERVAL_MS);\n      }, INITIAL_DELAY_MS);\n    },\n    [stop],\n  );\n\n  useEffect(() => stop, [stop]);\n\n  return { start, stop };\n}\n\nexport default useStepRepeat;\n"],"mappings":"4EAGA,IAAM,EAAmB,IAEnB,EAAmB,IAEnB,EAAmB,GAEnB,EAAsB,IAkB5B,SAAgB,EAAc,EAAyD,CACrF,IAAM,EAAa,EAA6C,IAAI,EAC9D,EAAc,EAA8C,IAAI,EAEhE,EAAY,EAAO,CAAM,EAC/B,EAAU,QAAU,EAIpB,IAAM,EAAc,EAA6D,IAAK,EAEhF,EAAO,MAAkB,CAS7B,GARI,EAAW,UAAY,OACzB,aAAa,EAAW,OAAO,EAC/B,EAAW,QAAU,MAEnB,EAAY,UAAY,OAC1B,cAAc,EAAY,OAAO,EACjC,EAAY,QAAU,MAEpB,OAAO,OAAW,IAAa,OACnC,GAAM,CAAE,cAAe,EAAY,QACnC,OAAO,oBAAoB,YAAa,CAAU,EAClD,OAAO,oBAAoB,gBAAiB,CAAU,EACtD,OAAO,oBAAoB,cAAe,CAAU,EACpD,OAAO,oBAAoB,OAAQ,CAAU,EAC7C,SAAS,oBAAoB,mBAAoB,CAAU,CAC7D,EAAG,CAAC,CAAC,EAEL,EAAY,QAAU,CACpB,OACA,WAAa,GAAa,EAGpB,EAAE,OAAS,eAAkB,EAAmB,UAAY,KAC5D,EAAE,OAAS,oBAAuB,SAAS,SAC/C,EAAY,QAAQ,KAAK,CAC3B,CACF,EAEA,IAAM,EAAQ,GACX,EAAmB,IAAuB,CACzC,EAAK,EACL,GAAM,CAAE,cAAe,EAAY,QACnC,OAAO,iBAAiB,YAAa,CAAU,EAC/C,OAAO,iBAAiB,gBAAiB,CAAU,EACnD,OAAO,iBAAiB,cAAe,CAAU,EACjD,OAAO,iBAAiB,OAAQ,CAAU,EAC1C,SAAS,iBAAiB,mBAAoB,CAAU,EAExD,IAAM,MAAa,CACZ,EAAU,QAAQ,EAAY,CAAU,GAAG,EAAK,CACvD,EACA,EAAK,EAEL,EAAW,QAAU,eAAiB,CACpC,EAAW,QAAU,KACrB,IAAM,EAAY,KAAK,IAAI,EACvB,EAAS,GACP,MAAa,CACjB,EAAK,EACD,EAAY,UAAY,MACxB,CAAC,GAAU,KAAK,IAAI,EAAI,GAAa,IACvC,EAAS,GACL,EAAY,UAAY,MAAM,cAAc,EAAY,OAAO,EACnE,EAAY,QAAU,YAAY,EAAM,CAAgB,EAE5D,EACA,EAAY,QAAU,YAAY,EAAM,CAAgB,CAC1D,EAAG,CAAgB,CACrB,EACA,CAAC,CAAI,CACP,EAIA,OAFA,MAAgB,EAAM,CAAC,CAAI,CAAC,EAErB,CAAE,QAAO,MAAK,CACvB"}