@himanshu-sorathiya/react-kit 1.0.27 → 1.0.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -417,85 +417,1094 @@ export type UseKeyReturn = () => void;
417
417
  * ```
418
418
  */
419
419
  export declare function useKey(key: string, handler: (event: KeyboardEvent) => void, options?: UseKeyOptions): UseKeyReturn;
420
- export interface DebounceOptions {
420
+ /**
421
+ * Configuration for `useBatcher`.
422
+ *
423
+ * All three flush triggers (`maxSize`, `maxWait`, `quietPeriod`) are
424
+ * optional and independent — configure any combination, and whichever
425
+ * condition is met first triggers the flush. If none are configured, the
426
+ * batch only ever flushes when `flush()` is called manually.
427
+ */
428
+ export interface BatchOptions<Item> {
429
+ /**
430
+ * Flushes the batch immediately once it reaches this many items.
431
+ * Checked on every `add()` call, before either timer-based trigger is
432
+ * considered.
433
+ *
434
+ * Invalid input (not a positive number) is ignored — not clamped to a
435
+ * fallback — with a dev-mode warning, since there's no sensible
436
+ * default size to fall back to.
437
+ *
438
+ * @defaultValue `undefined` (no size ceiling)
439
+ */
440
+ maxSize?: number;
441
+ /**
442
+ * A hard ceiling, in milliseconds, on how long a batch can sit before
443
+ * flushing — measured from the moment the *first* item of the current
444
+ * batch was added, not the most recent one. Guarantees a maximum
445
+ * latency per item regardless of how long the batch keeps growing.
446
+ *
447
+ * Invalid input (not a non-negative number) is ignored with a dev-mode
448
+ * warning.
449
+ *
450
+ * @defaultValue `undefined` (no time ceiling)
451
+ */
421
452
  maxWait?: number;
422
- leading?: boolean;
423
- trailing?: boolean;
453
+ /**
454
+ * Flushes the batch once this many milliseconds pass with no further
455
+ * items added — resets on every `add()` call, unlike `maxWait`. Use
456
+ * this when you want to wait for activity to genuinely settle before
457
+ * flushing, rather than enforcing a hard per-item latency ceiling.
458
+ *
459
+ * If both `maxWait` and `quietPeriod` are configured and `quietPeriod`
460
+ * is not shorter than `maxWait`, `maxWait` will almost always win the
461
+ * race — a dev-mode warning flags this combination.
462
+ *
463
+ * Invalid input (not a non-negative number) is ignored with a dev-mode
464
+ * warning.
465
+ *
466
+ * @defaultValue `undefined` (no quiet-period trigger)
467
+ */
468
+ quietPeriod?: number;
469
+ /**
470
+ * Called with a snapshot of the current batch contents every time it
471
+ * changes — after every `add()`, and after every flush or `cancel()`
472
+ * (with an empty array). This is the mechanism for getting live,
473
+ * reactive access to what's currently queued (e.g. a "3 items
474
+ * queued…" indicator) without `useBatcher` itself needing to hold the
475
+ * full item list in React state.
476
+ *
477
+ * Safe to pass a fresh inline function on every render — it's captured
478
+ * in a ref and refreshed via effect, same as `onFlush`.
479
+ *
480
+ * @defaultValue `undefined`
481
+ */
482
+ onItemsChange?: (items: readonly Item[]) => void;
424
483
  }
425
- export interface UseDebounceReturn {
426
- run: <Args extends unknown[]>(func: (...args: Args) => void, ...args: Args) => void;
427
- cancel: () => void;
484
+ /**
485
+ * The object returned by `useBatcher`.
486
+ */
487
+ export interface UseBatcherReturn<Item> {
488
+ /**
489
+ * Adds `item` to the current batch. Never rejected, never deferred to
490
+ * a future call — the item is always accepted immediately. Depending
491
+ * on the configured triggers, this may also cause an immediate flush
492
+ * (e.g. if `maxSize` is now reached).
493
+ */
494
+ add: (item: Item) => void;
495
+ /**
496
+ * Immediately flushes whatever is currently batched, bypassing
497
+ * `maxWait`/`quietPeriod` entirely. A no-op if the batch is empty —
498
+ * `onFlush` is never called with zero items.
499
+ */
428
500
  flush: () => void;
501
+ /**
502
+ * Discards the current batch entirely, without ever calling
503
+ * `onFlush`. Clears any pending timers.
504
+ */
505
+ cancel: () => void;
506
+ /**
507
+ * Suspends all automatic flush triggers (`maxSize`, `maxWait`,
508
+ * `quietPeriod`). Items added via `add()` while paused still
509
+ * accumulate normally — pausing stops the flushing machinery, not the
510
+ * accumulation. The only way to flush while paused is a manual
511
+ * `flush()` call.
512
+ */
513
+ pause: () => void;
514
+ /**
515
+ * Resumes automatic flushing. If the batch already meets `maxSize`
516
+ * (because items kept arriving while paused), flushes immediately.
517
+ * Otherwise, re-arms `maxWait`/`quietPeriod` timers from scratch —
518
+ * pausing does not preserve partial progress toward either deadline;
519
+ * resuming starts a fresh clock for whatever's still batched.
520
+ */
521
+ resume: () => void;
522
+ /** The number of items currently in the batch. */
523
+ size: number;
524
+ /** `true` whenever `size > 0`. */
429
525
  isPending: boolean;
526
+ /** `true` after `pause()`, until the next `resume()`. */
527
+ isPaused: boolean;
528
+ }
529
+ /**
530
+ * Groups rapid, individual `add()` calls into batches, flushing the whole
531
+ * accumulated group to `onFlush` at once — instead of debouncing,
532
+ * throttling, or rate-limiting, all of which discard some calls along the
533
+ * way. Nothing added to a `useBatcher` is ever dropped; it's only ever
534
+ * grouped.
535
+ *
536
+ * A batch flushes when any configured trigger fires first: reaching
537
+ * `maxSize` items, `maxWait` milliseconds since the batch's first item,
538
+ * or `quietPeriod` milliseconds of no new items. All three are optional
539
+ * and can be combined freely. If none are configured, only a manual
540
+ * `flush()` call ever empties the batch.
541
+ *
542
+ * Unlike the debounce/throttle/rate-limit hook families, there's no
543
+ * separate `Callback`/`State`/`Value` wrapper — `onFlush` is a single
544
+ * fixed handler (captured in a ref and always current, so a fresh inline
545
+ * function on every render is safe), and live access to the current batch
546
+ * contents is available via the `onItemsChange` option instead of a
547
+ * dedicated hook.
548
+ *
549
+ * @example
550
+ * ```tsx
551
+ * function AnalyticsProvider({ children }: { children: React.ReactNode }) {
552
+ * const { add } = useBatcher<AnalyticsEvent>(
553
+ * (events) => sendAnalyticsBatch(events),
554
+ * { maxSize: 20, maxWait: 5000 },
555
+ * );
556
+ *
557
+ * // `track` can be called as often as needed — events are grouped and
558
+ * // sent in batches of up to 20, at least once every 5 seconds.
559
+ * const track = (event: AnalyticsEvent) => add(event);
560
+ *
561
+ * return (
562
+ * <AnalyticsContext.Provider value={{ track }}>
563
+ * {children}
564
+ * </AnalyticsContext.Provider>
565
+ * );
566
+ * }
567
+ * ```
568
+ *
569
+ * @param onFlush - Called with every item accumulated since the last
570
+ * flush, as a snapshot array. Safe to pass a fresh inline function on
571
+ * every render.
572
+ * @param options - See {@link BatchOptions}.
573
+ * @returns See {@link UseBatcherReturn}.
574
+ */
575
+ export declare function useBatcher<Item>(onFlush: (items: readonly Item[]) => void, options?: BatchOptions<Item>): UseBatcherReturn<Item>;
576
+ /**
577
+ * Adds an optional custom equality comparator to any hook that manages a
578
+ * value and needs to decide whether a "new" value is actually different
579
+ * enough to justify a re-render.
580
+ *
581
+ * Used by the `State` and `Value` variants of debounce/throttle/rate-limit
582
+ * (e.g. `useDebouncedValue`, `useThrottledState`) to avoid committing a
583
+ * value that is deeply/semantically equal to the value already held,
584
+ * which would otherwise trigger a wasted re-render.
585
+ *
586
+ * @example
587
+ * ```tsx
588
+ * useDebouncedValue(user, 300, {
589
+ * equalityFn: (previous, next) => previous.id === next.id,
590
+ * });
591
+ * ```
592
+ */
593
+ export interface EqualityFnOption<T> {
594
+ /**
595
+ * Called with the currently-held value and the candidate next value.
596
+ * Return `true` if they should be treated as equal (no update should be
597
+ * committed); return `false` to allow the update through.
598
+ *
599
+ * Defaults to `Object.is` when omitted, or when an invalid (non-function)
600
+ * value is provided — see the consuming hook's dev-mode warning.
601
+ */
602
+ equalityFn?: (previous: T, next: T) => boolean;
430
603
  }
431
- export declare function useDebounce(delay: number, options?: DebounceOptions): UseDebounceReturn;
604
+ /**
605
+ * Configuration for `useDebouncer` and every hook built on top of it.
606
+ *
607
+ * @remarks
608
+ * `leading` and `trailing` may both be set to `false` at the same time —
609
+ * the hook will not silently correct this for you. That configuration
610
+ * means the debounced function will never run; a dev-mode warning is
611
+ * logged to help catch it early, but the choice itself is respected.
612
+ */
613
+ export interface DebounceOptions {
614
+ /**
615
+ * A hard ceiling, in milliseconds, on how long invocation can be
616
+ * deferred. If a continuous stream of calls keeps pushing the trailing
617
+ * timer out, `maxWait` forces a synchronous invocation once this many
618
+ * milliseconds have elapsed since the current cycle began — regardless
619
+ * of whether the trailing window has settled yet.
620
+ *
621
+ * Only evaluated at the moment `run()` is called; it is not an
622
+ * independent background timer, so it can't fire while no calls are
623
+ * coming in.
624
+ *
625
+ * @defaultValue `undefined` (no ceiling)
626
+ */
627
+ maxWait?: number;
628
+ /**
629
+ * When `true`, invokes on the leading edge — immediately, on the first
630
+ * call of a new debounce cycle (i.e. when no cycle is currently active).
631
+ *
632
+ * @defaultValue `false`
633
+ */
634
+ leading?: boolean;
635
+ /**
636
+ * When `true`, invokes on the trailing edge — once `delay` milliseconds
637
+ * have elapsed with no further calls.
638
+ *
639
+ * Defaults to `true` regardless of `leading`'s value. With
640
+ * `{ leading: true, trailing: true }` (both edges enabled), a single
641
+ * isolated call only fires once (the leading edge) — the trailing edge
642
+ * only fires if at least one more call arrives before the window
643
+ * closes.
644
+ *
645
+ * @defaultValue `true`
646
+ */
647
+ trailing?: boolean;
648
+ }
649
+ /**
650
+ * Options for `useDebouncedState`: all of {@link DebounceOptions}, plus an
651
+ * optional equality comparator used to skip committing a value equivalent
652
+ * to the one already held.
653
+ */
654
+ export type UseDebouncedStateOptions<T> = DebounceOptions & EqualityFnOption<T>;
655
+ /**
656
+ * Options for `useDebouncedValue`: all of {@link DebounceOptions}, plus an
657
+ * optional equality comparator used to skip committing a value equivalent
658
+ * to the one already held.
659
+ */
660
+ export type UseDebouncedValueOptions<T> = DebounceOptions & EqualityFnOption<T>;
661
+ /**
662
+ * The object returned by `useDebouncedCallback`.
663
+ */
432
664
  export interface UseDebouncedCallbackReturn<Args extends unknown[]> {
665
+ /**
666
+ * A stable, debounced wrapper around `func`. Safe to call from any
667
+ * event handler; internally always invokes the most recently rendered
668
+ * `func`, even though `debouncedFunc`'s own identity doesn't change
669
+ * across re-renders.
670
+ */
433
671
  debouncedFunc: (...args: Args) => void;
672
+ /** See {@link UseDebouncerReturn.cancel}. */
434
673
  cancel: () => void;
674
+ /** See {@link UseDebouncerReturn.flush}. */
435
675
  flush: () => void;
676
+ /** See {@link UseDebouncerReturn.isPending}. */
436
677
  isPending: boolean;
437
678
  }
679
+ /**
680
+ * Debounces a single function.
681
+ *
682
+ * `func` is captured in a ref and refreshed on every render — you can pass
683
+ * a fresh inline closure every time without resetting the pending debounce
684
+ * cycle, and the debounced wrapper always calls the *latest* `func`,
685
+ * closing over whatever props/state were current when it actually fires
686
+ * (never a stale closure from whenever the wrapper was first created).
687
+ *
688
+ * @example
689
+ * ```tsx
690
+ * function SearchBox() {
691
+ * const { debouncedFunc: handleSearch, isPending } = useDebouncedCallback(
692
+ * (query: string) => fetchResults(query),
693
+ * 400,
694
+ * );
695
+ *
696
+ * return (
697
+ * <>
698
+ * <input onChange={(e) => handleSearch(e.target.value)} />
699
+ * {isPending && <span>Typing…</span>}
700
+ * </>
701
+ * );
702
+ * }
703
+ * ```
704
+ *
705
+ * @param func - The function to debounce. Safe to pass a new closure on
706
+ * every render.
707
+ * @param delay - See {@link useDebouncer}.
708
+ * @param options - See {@link DebounceOptions}.
709
+ * @returns See {@link UseDebouncedCallbackReturn}.
710
+ */
438
711
  export declare function useDebouncedCallback<Args extends unknown[]>(func: (...args: Args) => void, delay: number, options?: DebounceOptions): UseDebouncedCallbackReturn<Args>;
712
+ /**
713
+ * The tuple returned by `useDebouncedState`, mirroring `useState`'s
714
+ * `[value, setValue]` shape with a third element carrying debounce
715
+ * controls.
716
+ */
439
717
  export type UseDebouncedStateReturn<T> = [
718
+ /** The current, committed state value. */
440
719
  T,
441
- (value: T) => void,
720
+ /**
721
+ * Schedules a debounced update to state. Accepts either a plain value
722
+ * or a `useState`-style functional updater (`(previous) => next`).
723
+ *
724
+ * The functional-updater form composes correctly across multiple rapid
725
+ * calls made before the debounce settles — e.g. calling
726
+ * `setValue((p) => p + 1)` three times in a row schedules a cumulative
727
+ * `+3`, not three competing `+1`s racing to be "the" pending value.
728
+ * `previous` in that case refers to the most recently *scheduled*
729
+ * value, not necessarily the currently-committed state — this matters
730
+ * if you're chaining updates faster than the debounce settles.
731
+ */
732
+ (value: T | ((previous: T) => T)) => void,
442
733
  {
734
+ /** See {@link UseDebouncerReturn.isPending}. */
443
735
  isPending: boolean;
736
+ /**
737
+ * Cancels any pending debounced update. Also resyncs the internal
738
+ * "next value to commit" tracking back to the current committed
739
+ * state, so a subsequent functional update starts from the right
740
+ * baseline instead of building on a discarded value.
741
+ */
444
742
  cancel: () => void;
743
+ /** See {@link UseDebouncerReturn.flush}. */
445
744
  flush: () => void;
446
- forceSetValue: (value: T) => void;
745
+ /**
746
+ * Bypasses debounce scheduling entirely and applies the value (or
747
+ * updater) immediately. Also cancels any debounced update that was
748
+ * still pending, so it can't land afterward and silently overwrite
749
+ * this forced value.
750
+ */
751
+ forceSetValue: (value: T | ((previous: T) => T)) => void;
447
752
  }
448
753
  ];
449
- export declare function useDebouncedState<T>(initialValue: T | (() => T), delay: number, options?: DebounceOptions): UseDebouncedStateReturn<T>;
450
- export type UseDebouncedValueReturn<T> = T;
451
- export declare function useDebouncedValue<T>(value: T, delay: number, options?: DebounceOptions): UseDebouncedValueReturn<T>;
754
+ /**
755
+ * A `useState`-shaped hook whose setter defers its effect on state until
756
+ * the debounce window settles, instead of applying immediately.
757
+ *
758
+ * @example
759
+ * ```tsx
760
+ * function NoteEditor() {
761
+ * const [note, setNote, { isPending, flush, forceSetValue }] =
762
+ * useDebouncedState("", 800);
763
+ *
764
+ * return (
765
+ * <>
766
+ * <textarea onChange={(e) => setNote(e.target.value)} />
767
+ * <p>Committed: {note}</p>
768
+ * {isPending && <span>Unsaved changes…</span>}
769
+ * <button onClick={flush}>Save Now</button>
770
+ * <button onClick={() => forceSetValue("")}>Reset</button>
771
+ * </>
772
+ * );
773
+ * }
774
+ * ```
775
+ *
776
+ * @param initialValue - Initial state value, or a `useState`-style lazy
777
+ * initializer function (`() => T`).
778
+ * @param delay - See {@link useDebouncer}.
779
+ * @param options - See {@link UseDebouncedStateOptions}.
780
+ * @returns See {@link UseDebouncedStateReturn}.
781
+ */
782
+ export declare function useDebouncedState<T>(initialValue: T | (() => T), delay: number, options?: UseDebouncedStateOptions<T>): UseDebouncedStateReturn<T>;
783
+ /**
784
+ * The tuple returned by `useDebouncedValue`.
785
+ */
786
+ export type UseDebouncedValueReturn<T> = [
787
+ /** The debounced (lagging) mirror of the source value. */
788
+ T,
789
+ {
790
+ /** See {@link UseDebouncerReturn.isPending}. */
791
+ isPending: boolean;
792
+ /** Cancels the pending sync to the latest source value. */
793
+ cancel: () => void;
794
+ /** Immediately commits the latest source value, bypassing the delay. */
795
+ flush: () => void;
796
+ }
797
+ ];
798
+ /**
799
+ * Mirrors `value`, but the mirror only updates `delay` milliseconds after
800
+ * `value` stops changing — a safe, stable dependency for an expensive
801
+ * effect (filtering, fetching) that shouldn't re-run on every keystroke.
802
+ *
803
+ * @example
804
+ * ```tsx
805
+ * function ProductFilterField() {
806
+ * const [query, setQuery] = useState("");
807
+ * const [debouncedQuery] = useDebouncedValue(query, 350);
808
+ *
809
+ * // `debouncedQuery` only updates 350ms after typing stops.
810
+ *
811
+ * return (
812
+ * <input value={query} onChange={(e) => setQuery(e.target.value)} />
813
+ * );
814
+ * }
815
+ * ```
816
+ *
817
+ * @param value - The source value to debounce. Every change re-arms the
818
+ * debounce window.
819
+ * @param delay - See {@link useDebouncer}.
820
+ * @param options - See {@link UseDebouncedValueOptions}.
821
+ * @returns See {@link UseDebouncedValueReturn}.
822
+ */
823
+ export declare function useDebouncedValue<T>(value: T, delay: number, options?: UseDebouncedValueOptions<T>): UseDebouncedValueReturn<T>;
824
+ /**
825
+ * The object returned by `useDebouncer`.
826
+ */
827
+ export interface UseDebouncerReturn {
828
+ /**
829
+ * Registers `func` (with `args`) as the function this debounce cycle
830
+ * will invoke, and (re)arms the debounce timer.
831
+ *
832
+ * Each call to `run()` accepts its own function — you are not locked
833
+ * into debouncing a single, fixed callback. If `run()` is called again
834
+ * before the pending timer fires, the previously-registered function
835
+ * and arguments are discarded entirely in favor of the new ones (a
836
+ * "last write wins" swap, not a queue).
837
+ *
838
+ * @example
839
+ * ```ts
840
+ * const { run } = useDebouncer(300);
841
+ *
842
+ * run((query: string) => fetchResults(query), searchTerm);
843
+ * ```
844
+ */
845
+ run: <Args extends unknown[]>(func: (...args: Args) => void, ...args: Args) => void;
846
+ /**
847
+ * Clears the pending timer and discards whatever function/arguments
848
+ * were registered via `run()`, without invoking anything. Sets
849
+ * `isPending` back to `false`.
850
+ */
851
+ cancel: () => void;
852
+ /**
853
+ * If a function is currently pending invocation, invokes it immediately
854
+ * (with its most recently registered arguments) and clears the timer.
855
+ * If nothing is pending, this is a no-op.
856
+ */
857
+ flush: () => void;
858
+ /**
859
+ * `true` whenever a trailing invocation is still scheduled to happen
860
+ * when the current cycle settles. `false` once it's known nothing
861
+ * further will fire — including immediately after a leading-edge
862
+ * invocation if `trailing` is disabled, not merely once the full window
863
+ * elapses.
864
+ */
865
+ isPending: boolean;
866
+ }
867
+ /**
868
+ * The debounce engine underlying every hook in this family.
869
+ *
870
+ * `useDebouncer` is a low-level scheduling primitive: a single timer and a
871
+ * single "next function to run" slot. Calling `run(func, ...args)`
872
+ * registers `func` as the occupant of that slot; if `run()` is called
873
+ * again before the timer fires, the new function/arguments replace the old
874
+ * ones outright.
875
+ *
876
+ * Most consumers won't reach for this directly — `useDebouncedCallback`,
877
+ * `useDebouncedState`, and `useDebouncedValue` are thin, purpose-built
878
+ * wrappers around it for the common cases (debouncing one fixed callback,
879
+ * a piece of state, or an incoming value). Use `useDebouncer` directly when
880
+ * you need the "swap, don't queue" behavior across genuinely different
881
+ * functions — see the example below.
882
+ *
883
+ * @example
884
+ * ```tsx
885
+ * function DocumentActionBar() {
886
+ * const { run, isPending } = useDebouncer(1000);
887
+ *
888
+ * const handleSave = () => run(() => saveDocument());
889
+ * const handleCancel = () => run(() => discardChanges());
890
+ *
891
+ * return (
892
+ * <>
893
+ * <button onClick={handleSave}>Save</button>
894
+ * <button onClick={handleCancel}>Cancel</button>
895
+ * {isPending && <span>Pending…</span>}
896
+ * </>
897
+ * );
898
+ * }
899
+ * ```
900
+ *
901
+ * @param delay - Milliseconds to wait before a trailing invocation fires.
902
+ * Coerced to a non-negative number; invalid input falls back to `0` with a
903
+ * dev-mode warning.
904
+ * @param options - See {@link DebounceOptions}.
905
+ * @returns See {@link UseDebouncerReturn}.
906
+ */
907
+ export declare function useDebouncer(delay: number, options?: DebounceOptions): UseDebouncerReturn;
908
+ /**
909
+ * Configuration for `useRateLimiter` and every hook built on top of it.
910
+ */
452
911
  export interface RateLimitOptions {
912
+ /**
913
+ * Called whenever a call is rejected because no executions remain in
914
+ * the current window.
915
+ *
916
+ * Safe to pass a fresh inline function on every render — it's captured
917
+ * in a ref and refreshed via effect, so it never causes the underlying
918
+ * timers to reset.
919
+ *
920
+ * @defaultValue `undefined`
921
+ */
453
922
  onRateLimitReached?: () => void;
923
+ /**
924
+ * Determines how the allowance replenishes once consumed.
925
+ *
926
+ * - `"burst"` — fixed window. All executions reset to the full `limit`
927
+ * at once, only once the entire `windowMs` duration has elapsed
928
+ * since the window began.
929
+ * - `"gradual"` — trickle refill. Executions return proportionately as
930
+ * time passes (`windowMs / limit` per execution), with no hard reset
931
+ * boundary.
932
+ *
933
+ * @defaultValue `"burst"`
934
+ */
454
935
  refillStrategy?: "burst" | "gradual";
455
936
  }
456
- export interface UseRateLimitReturn {
457
- run: <Args extends unknown[]>(func: (...args: Args) => void, ...args: Args) => void;
937
+ /**
938
+ * Options for `useRateLimitedState`: all of {@link RateLimitOptions}, plus
939
+ * an optional equality comparator used to skip committing a value
940
+ * equivalent to the one already held.
941
+ */
942
+ export type UseRateLimitedStateOptions<T> = RateLimitOptions & EqualityFnOption<T>;
943
+ /**
944
+ * Options for `useRateLimitedValue`: all of {@link RateLimitOptions}, plus
945
+ * an optional equality comparator used to skip committing a value
946
+ * equivalent to the one already held.
947
+ */
948
+ export type UseRateLimitedValueOptions<T> = RateLimitOptions & EqualityFnOption<T>;
949
+ /**
950
+ * The object returned by `useRateLimitedCallback`.
951
+ */
952
+ export interface UseRateLimitedCallbackReturn<Args extends unknown[]> {
953
+ /**
954
+ * A stable, rate-limited wrapper around `func`. Always invokes the
955
+ * most recently rendered `func`, even though `rateLimitedFunc`'s own
956
+ * identity doesn't change across re-renders.
957
+ *
958
+ * @returns `true` if `func` was invoked, `false` if the call was
959
+ * rejected because the allowance is exhausted.
960
+ */
961
+ rateLimitedFunc: (...args: Args) => boolean;
962
+ /** See {@link UseRateLimiterReturn.reset}. */
963
+ reset: () => void;
964
+ /** See {@link UseRateLimiterReturn.remaining}. */
458
965
  remaining: number;
966
+ /** See {@link UseRateLimiterReturn.isRateLimited}. */
459
967
  isRateLimited: boolean;
460
968
  }
461
- export declare function useRateLimit(limit: number, windowMs: number, options?: RateLimitOptions): UseRateLimitReturn;
462
- export interface UseRateLimitedCallbackReturn<Args extends unknown[]> {
463
- rateLimitedFunc: (...args: Args) => void;
969
+ /**
970
+ * Rate-limits a single function.
971
+ *
972
+ * `func` is captured in a ref and refreshed on every render — you can
973
+ * pass a fresh inline closure every time without resetting the
974
+ * underlying allowance tracking, and the wrapper always calls the
975
+ * *latest* `func`.
976
+ *
977
+ * @example
978
+ * ```tsx
979
+ * function SearchBox() {
980
+ * const { rateLimitedFunc, isRateLimited } = useRateLimitedCallback(
981
+ * (query: string) => fetch(`/api/search?q=${query}`),
982
+ * 10,
983
+ * 60_000,
984
+ * { refillStrategy: "gradual" },
985
+ * );
986
+ *
987
+ * return (
988
+ * <input
989
+ * disabled={isRateLimited}
990
+ * onChange={(e) => rateLimitedFunc(e.target.value)}
991
+ * />
992
+ * );
993
+ * }
994
+ * ```
995
+ *
996
+ * @param func - The function to rate-limit. Safe to pass a new closure
997
+ * on every render.
998
+ * @param limit - See {@link useRateLimiter}.
999
+ * @param windowMs - See {@link useRateLimiter}.
1000
+ * @param options - See {@link RateLimitOptions}.
1001
+ * @returns See {@link UseRateLimitedCallbackReturn}.
1002
+ */
1003
+ export declare function useRateLimitedCallback<Args extends unknown[]>(func: (...args: Args) => void, limit: number, windowMs: number, options?: RateLimitOptions): UseRateLimitedCallbackReturn<Args>;
1004
+ /**
1005
+ * The tuple returned by `useRateLimitedState`, mirroring `useState`'s
1006
+ * `[value, setValue]` shape with a third element carrying rate-limit
1007
+ * controls.
1008
+ */
1009
+ export type UseRateLimitedStateReturn<T> = [
1010
+ /** The current, committed state value. */
1011
+ T,
1012
+ /**
1013
+ * Attempts to update state. Accepts either a plain value or a
1014
+ * `useState`-style functional updater (`(previous) => next`).
1015
+ *
1016
+ * Unlike the debounce/throttle `State` variants, nothing here is ever
1017
+ * deferred — the update either applies immediately (against the
1018
+ * genuinely current state, via React's own functional `setState`) or
1019
+ * is rejected outright and state doesn't change at all. There's no
1020
+ * "most recently scheduled value" to reason about, since nothing is
1021
+ * ever queued.
1022
+ *
1023
+ * @returns `true` if the update was applied, `false` if it was
1024
+ * rejected because the allowance is exhausted.
1025
+ */
1026
+ (value: T | ((previous: T) => T)) => boolean,
1027
+ {
1028
+ /** See {@link UseRateLimiterReturn.remaining}. */
1029
+ remaining: number;
1030
+ /** See {@link UseRateLimiterReturn.isRateLimited}. */
1031
+ isRateLimited: boolean;
1032
+ /** See {@link UseRateLimiterReturn.reset}. */
1033
+ reset: () => void;
1034
+ /**
1035
+ * Bypasses rate-limit enforcement entirely and applies the value
1036
+ * (or updater) immediately. Does not consume any of the allowance.
1037
+ */
1038
+ forceSetValue: (value: T | ((previous: T) => T)) => void;
1039
+ }
1040
+ ];
1041
+ /**
1042
+ * A `useState`-shaped hook whose setter can be rejected once the
1043
+ * allowance for the current window is exhausted, instead of always
1044
+ * applying.
1045
+ *
1046
+ * @example
1047
+ * ```tsx
1048
+ * function GenerationDemo() {
1049
+ * const [result, generate, { remaining, isRateLimited }] =
1050
+ * useRateLimitedState<string | null>(null, 3, 60_000);
1051
+ *
1052
+ * const handleGenerate = () => {
1053
+ * if (!generate(`Result #${Math.random()}`)) {
1054
+ * toast("Free limit reached — try again in a minute.");
1055
+ * }
1056
+ * };
1057
+ *
1058
+ * return (
1059
+ * <>
1060
+ * <button onClick={handleGenerate} disabled={isRateLimited}>
1061
+ * Generate ({remaining} left)
1062
+ * </button>
1063
+ * <p>{result}</p>
1064
+ * </>
1065
+ * );
1066
+ * }
1067
+ * ```
1068
+ *
1069
+ * @param initialValue - Initial state value, or a `useState`-style lazy
1070
+ * initializer function (`() => T`).
1071
+ * @param limit - See {@link useRateLimiter}.
1072
+ * @param windowMs - See {@link useRateLimiter}.
1073
+ * @param options - See {@link UseRateLimitedStateOptions}.
1074
+ * @returns See {@link UseRateLimitedStateReturn}.
1075
+ */
1076
+ export declare function useRateLimitedState<T>(initialValue: T | (() => T), limit: number, windowMs: number, options?: UseRateLimitedStateOptions<T>): UseRateLimitedStateReturn<T>;
1077
+ /**
1078
+ * The tuple returned by `useRateLimitedValue`.
1079
+ */
1080
+ export type UseRateLimitedValueReturn<T> = [
1081
+ /** The rate-limited mirror of the source value. */
1082
+ T,
1083
+ {
1084
+ /** See {@link UseRateLimiterReturn.remaining}. */
1085
+ remaining: number;
1086
+ /** See {@link UseRateLimiterReturn.isRateLimited}. */
1087
+ isRateLimited: boolean;
1088
+ /** See {@link UseRateLimiterReturn.reset}. */
1089
+ reset: () => void;
1090
+ }
1091
+ ];
1092
+ /**
1093
+ * Mirrors `value`, but the mirror updates at most `limit` times per
1094
+ * `windowMs` window.
1095
+ *
1096
+ * @remarks
1097
+ * This has a meaningfully weaker guarantee than `useDebouncedValue` /
1098
+ * `useThrottledValue`. Those hooks eventually deliver the *latest* value
1099
+ * once their window settles, even if intermediate values were skipped.
1100
+ * This hook does not — once the allowance is exhausted, incoming changes
1101
+ * to `value` are dropped outright until the allowance refills, with no
1102
+ * queueing and no catch-up. The mirror simply stays frozen at whatever it
1103
+ * last committed until it's allowed to update again.
1104
+ *
1105
+ * @example
1106
+ * ```tsx
1107
+ * function NotificationFeed({ latestNotification }: { latestNotification: string }) {
1108
+ * const [visibleNotification] = useRateLimitedValue(latestNotification, 3, 10_000);
1109
+ *
1110
+ * // At most 3 notifications surface per 10s window; any beyond that
1111
+ * // are silently dropped rather than queued for later display.
1112
+ *
1113
+ * return <Toast message={visibleNotification} />;
1114
+ * }
1115
+ * ```
1116
+ *
1117
+ * @param value - The source value to rate-limit. Every change is subject
1118
+ * to the current allowance.
1119
+ * @param limit - See {@link useRateLimiter}.
1120
+ * @param windowMs - See {@link useRateLimiter}.
1121
+ * @param options - See {@link UseRateLimitedValueOptions}.
1122
+ * @returns See {@link UseRateLimitedValueReturn}.
1123
+ */
1124
+ export declare function useRateLimitedValue<T>(value: T, limit: number, windowMs: number, options?: UseRateLimitedValueOptions<T>): UseRateLimitedValueReturn<T>;
1125
+ /**
1126
+ * The object returned by `useRateLimiter`.
1127
+ */
1128
+ export interface UseRateLimiterReturn {
1129
+ /**
1130
+ * Attempts to invoke `func` (with `args`) against the current
1131
+ * allowance.
1132
+ *
1133
+ * Unlike `useDebouncer`/`useThrottler`, nothing here is ever deferred —
1134
+ * every call to `run()` resolves synchronously, right now: either an
1135
+ * execution is available and `func` runs immediately, or it isn't and
1136
+ * `func` doesn't run at all (no queueing, no later catch-up).
1137
+ *
1138
+ * Each call accepts its own function, so a single `useRateLimiter`
1139
+ * instance can gate several different actions against one shared
1140
+ * allowance if needed.
1141
+ *
1142
+ * @returns `true` if `func` was invoked, `false` if the call was
1143
+ * rejected because the current window's allowance is exhausted.
1144
+ *
1145
+ * @example
1146
+ * ```ts
1147
+ * const { run } = useRateLimiter(3, 10_000);
1148
+ *
1149
+ * const didFire = run(() => submitForm());
1150
+ * if (!didFire) showToast("Too many attempts — please wait.");
1151
+ * ```
1152
+ */
1153
+ run: <Args extends unknown[]>(func: (...args: Args) => void, ...args: Args) => boolean;
1154
+ /**
1155
+ * Immediately restores the full allowance and clears any in-progress
1156
+ * background refill, bypassing the normal window/refill timing
1157
+ * entirely. Useful after an unrelated event that should grant a fresh
1158
+ * allowance outright — e.g. a successful CAPTCHA, or a plan upgrade.
1159
+ */
1160
+ reset: () => void;
1161
+ /**
1162
+ * The number of executions currently available. Kept accurate in real
1163
+ * time — including while idle, with no further calls to `run()` —
1164
+ * by a self-scheduling background timer that mirrors whichever
1165
+ * `refillStrategy` is configured.
1166
+ */
464
1167
  remaining: number;
1168
+ /** Convenience flag, equivalent to `remaining === 0`. */
465
1169
  isRateLimited: boolean;
466
1170
  }
467
- export declare function useRateLimitedCallback<Args extends unknown[]>(func: (...args: Args) => void, limit: number, windowMs: number, options?: RateLimitOptions): UseRateLimitedCallbackReturn<Args>;
1171
+ /**
1172
+ * The rate-limiting engine underlying every hook in this family.
1173
+ *
1174
+ * `useRateLimiter` grants up to `limit` executions per `windowMs` window,
1175
+ * replenished according to `refillStrategy`. Unlike debounce or throttle,
1176
+ * it never delays or reshapes *when* something runs — every call is an
1177
+ * immediate accept-or-reject decision against the current allowance.
1178
+ *
1179
+ * Most consumers won't reach for this directly — `useRateLimitedCallback`,
1180
+ * `useRateLimitedState`, and `useRateLimitedValue` are thin, purpose-built
1181
+ * wrappers around it for the common cases.
1182
+ *
1183
+ * @example
1184
+ * ```tsx
1185
+ * function SubmitButton() {
1186
+ * const { run, remaining, isRateLimited } = useRateLimiter(3, 10_000, {
1187
+ * refillStrategy: "burst",
1188
+ * onRateLimitReached: () => toast("Too many attempts."),
1189
+ * });
1190
+ *
1191
+ * return (
1192
+ * <button onClick={() => run(submitForm)} disabled={isRateLimited}>
1193
+ * Submit ({remaining} left)
1194
+ * </button>
1195
+ * );
1196
+ * }
1197
+ * ```
1198
+ *
1199
+ * @param limit - Maximum executions allowed per window. Coerced to a
1200
+ * non-negative integer with a floor of `1`; invalid input falls back to
1201
+ * `1` with a dev-mode warning.
1202
+ * @param windowMs - Length of the rate-limit window, in milliseconds.
1203
+ * Coerced to a non-negative number; invalid input falls back to `0` with
1204
+ * a dev-mode warning. A value of `0` disables rate limiting entirely
1205
+ * (every call is allowed) rather than causing a division error — also
1206
+ * dev-warned, since it's rarely intentional.
1207
+ * @param options - See {@link RateLimitOptions}.
1208
+ * @returns See {@link UseRateLimiterReturn}.
1209
+ */
1210
+ export declare function useRateLimiter(limit: number, windowMs: number, options?: RateLimitOptions): UseRateLimiterReturn;
1211
+ /**
1212
+ * Configuration for `useThrottler` and every hook built on top of it.
1213
+ *
1214
+ * @remarks
1215
+ * `leading` and `trailing` may both be set to `false` at the same time —
1216
+ * the hook will not silently correct this for you. That configuration
1217
+ * means the throttled function will never run; a dev-mode warning is
1218
+ * logged to help catch it early, but the choice itself is respected.
1219
+ */
468
1220
  export interface ThrottleOptions {
1221
+ /**
1222
+ * When `true`, invokes immediately on the first call of a new cooldown
1223
+ * window.
1224
+ *
1225
+ * @defaultValue `true`
1226
+ */
469
1227
  leading?: boolean;
1228
+ /**
1229
+ * When `true`, invokes once more at the end of the cooldown window,
1230
+ * using the most recently passed function/arguments — but only if at
1231
+ * least one call arrived after the leading edge fired. A single
1232
+ * isolated call with both edges enabled only fires once.
1233
+ *
1234
+ * @defaultValue `true`
1235
+ */
470
1236
  trailing?: boolean;
471
1237
  }
472
- export interface UseThrottleReturn {
473
- run: <Args extends unknown[]>(func: (...args: Args) => void, ...args: Args) => void;
474
- cancel: () => void;
475
- flush: () => void;
476
- isPending: boolean;
477
- }
478
- export declare function useThrottle(delay: number, options?: ThrottleOptions): UseThrottleReturn;
1238
+ /**
1239
+ * Options for `useThrottledState`: all of {@link ThrottleOptions}, plus an
1240
+ * optional equality comparator used to skip committing a value equivalent
1241
+ * to the one already held.
1242
+ */
1243
+ export type UseThrottledStateOptions<T> = ThrottleOptions & EqualityFnOption<T>;
1244
+ /**
1245
+ * Options for `useThrottledValue`: all of {@link ThrottleOptions}, plus an
1246
+ * optional equality comparator used to skip committing a value equivalent
1247
+ * to the one already held.
1248
+ */
1249
+ export type UseThrottledValueOptions<T> = ThrottleOptions & EqualityFnOption<T>;
1250
+ /**
1251
+ * The object returned by `useThrottledCallback`.
1252
+ */
479
1253
  export interface UseThrottledCallbackReturn<Args extends unknown[]> {
1254
+ /**
1255
+ * A stable, throttled wrapper around `func`. Safe to attach directly
1256
+ * to an event listener; internally always invokes the most recently
1257
+ * rendered `func`, even though `throttledFunc`'s own identity doesn't
1258
+ * change across re-renders.
1259
+ */
480
1260
  throttledFunc: (...args: Args) => void;
1261
+ /** See {@link UseThrottlerReturn.cancel}. */
481
1262
  cancel: () => void;
1263
+ /** See {@link UseThrottlerReturn.flush}. */
482
1264
  flush: () => void;
1265
+ /** See {@link UseThrottlerReturn.isPending}. */
483
1266
  isPending: boolean;
484
1267
  }
1268
+ /**
1269
+ * Throttles a single function.
1270
+ *
1271
+ * `func` is captured in a ref and refreshed on every render — you can pass
1272
+ * a fresh inline closure every time without resetting the running cooldown
1273
+ * window, and the throttled wrapper always calls the *latest* `func`,
1274
+ * closing over whatever props/state were current when it actually fires
1275
+ * (never a stale closure from whenever the wrapper was first created).
1276
+ *
1277
+ * @example
1278
+ * ```tsx
1279
+ * function WindowSizeLogger() {
1280
+ * const { throttledFunc: handleResize } = useThrottledCallback(
1281
+ * () => console.log("width:", window.innerWidth),
1282
+ * 200,
1283
+ * );
1284
+ *
1285
+ * useEffect(() => {
1286
+ * window.addEventListener("resize", handleResize);
1287
+ * return () => window.removeEventListener("resize", handleResize);
1288
+ * }, [handleResize]);
1289
+ *
1290
+ * return null;
1291
+ * }
1292
+ * ```
1293
+ *
1294
+ * @param func - The function to throttle. Safe to pass a new closure on
1295
+ * every render.
1296
+ * @param delay - See {@link useThrottler}.
1297
+ * @param options - See {@link ThrottleOptions}.
1298
+ * @returns See {@link UseThrottledCallbackReturn}.
1299
+ */
485
1300
  export declare function useThrottledCallback<Args extends unknown[]>(func: (...args: Args) => void, delay: number, options?: ThrottleOptions): UseThrottledCallbackReturn<Args>;
1301
+ /**
1302
+ * The tuple returned by `useThrottledState`, mirroring `useState`'s
1303
+ * `[value, setValue]` shape with a third element carrying throttle
1304
+ * controls.
1305
+ */
486
1306
  export type UseThrottledStateReturn<T> = [
1307
+ /** The current (throttled) state value. */
487
1308
  T,
488
- (value: T) => void,
1309
+ /**
1310
+ * Schedules a throttled update to state. Accepts either a plain value
1311
+ * or a `useState`-style functional updater (`(previous) => next`).
1312
+ *
1313
+ * The functional-updater form composes correctly across multiple rapid
1314
+ * calls made before the cooldown window settles — e.g. calling
1315
+ * `setValue((p) => p + 1)` three times in a row schedules a cumulative
1316
+ * `+3`, not three competing `+1`s racing to be "the" pending value.
1317
+ * `previous` in that case refers to the most recently *scheduled*
1318
+ * value, not necessarily the currently-committed state — this matters
1319
+ * if you're chaining updates faster than the window settles.
1320
+ */
1321
+ (value: T | ((previous: T) => T)) => void,
489
1322
  {
1323
+ /** See {@link UseThrottlerReturn.isPending}. */
490
1324
  isPending: boolean;
1325
+ /**
1326
+ * Cancels any pending throttled update. Also resyncs the internal
1327
+ * "next value to commit" tracking back to the current committed
1328
+ * state, so a subsequent functional update starts from the right
1329
+ * baseline instead of building on a discarded value.
1330
+ */
491
1331
  cancel: () => void;
1332
+ /** See {@link UseThrottlerReturn.flush}. */
492
1333
  flush: () => void;
493
- forceSetValue: (value: T) => void;
1334
+ /**
1335
+ * Bypasses throttle scheduling entirely and applies the value (or
1336
+ * updater) immediately. Also cancels any throttled update that was
1337
+ * still pending, so it can't land afterward and silently overwrite
1338
+ * this forced value.
1339
+ */
1340
+ forceSetValue: (value: T | ((previous: T) => T)) => void;
494
1341
  }
495
1342
  ];
496
- export declare function useThrottledState<T>(initialValue: T | (() => T), delay: number, options?: ThrottleOptions): UseThrottledStateReturn<T>;
497
- export type UseThrottledValueReturn<T> = T;
498
- export declare function useThrottledValue<T>(value: T, delay: number, options?: ThrottleOptions): UseThrottledValueReturn<T>;
1343
+ /**
1344
+ * A `useState`-shaped hook whose setter rate-limits its effect on state
1345
+ * instead of applying immediately.
1346
+ *
1347
+ * @example
1348
+ * ```tsx
1349
+ * function ScoreBoard() {
1350
+ * const [score, setScore, { flush, forceSetValue, isPending }] =
1351
+ * useThrottledState(() => expensiveInitialScore(), 500);
1352
+ *
1353
+ * return (
1354
+ * <>
1355
+ * <p>Score: {score}</p>
1356
+ * <button onClick={() => setScore((s) => s + 1)}>+1 (throttled)</button>
1357
+ * <button onClick={flush}>Flush pending update</button>
1358
+ * <button onClick={() => forceSetValue(0)}>Reset instantly</button>
1359
+ * {isPending && <span>update queued…</span>}
1360
+ * </>
1361
+ * );
1362
+ * }
1363
+ * ```
1364
+ *
1365
+ * @param initialValue - Initial state value, or a `useState`-style lazy
1366
+ * initializer function (`() => T`).
1367
+ * @param delay - See {@link useThrottler}.
1368
+ * @param options - See {@link UseThrottledStateOptions}.
1369
+ * @returns See {@link UseThrottledStateReturn}.
1370
+ */
1371
+ export declare function useThrottledState<T>(initialValue: T | (() => T), delay: number, options?: UseThrottledStateOptions<T>): UseThrottledStateReturn<T>;
1372
+ /**
1373
+ * The tuple returned by `useThrottledValue`.
1374
+ */
1375
+ export type UseThrottledValueReturn<T> = [
1376
+ /** The throttled (rate-limited) mirror of the source value. */
1377
+ T,
1378
+ {
1379
+ /** See {@link UseThrottlerReturn.isPending}. */
1380
+ isPending: boolean;
1381
+ /** Cancels the pending sync to the latest source value. */
1382
+ cancel: () => void;
1383
+ /** Immediately commits the latest source value, bypassing the wait. */
1384
+ flush: () => void;
1385
+ }
1386
+ ];
1387
+ /**
1388
+ * Mirrors `value`, but the mirror updates at most once every `delay`
1389
+ * milliseconds — a safe, rate-limited dependency for an expensive
1390
+ * downstream render (chart, canvas, map) driven by a fast-changing source
1391
+ * like a slider or live coordinates.
1392
+ *
1393
+ * @remarks
1394
+ * With the default `{ leading: true }`, the mirror updates almost
1395
+ * immediately on the first change — throttle is about limiting *rate*,
1396
+ * not deferring the first response the way debounce does.
1397
+ *
1398
+ * @example
1399
+ * ```tsx
1400
+ * function SliderDemo() {
1401
+ * const [raw, setRaw] = useState(0);
1402
+ * const [throttled] = useThrottledValue(raw, 100);
1403
+ *
1404
+ * return (
1405
+ * <>
1406
+ * <input
1407
+ * type="range"
1408
+ * value={raw}
1409
+ * onChange={(e) => setRaw(Number(e.target.value))}
1410
+ * />
1411
+ * <HeavyPreview value={throttled} />
1412
+ * </>
1413
+ * );
1414
+ * }
1415
+ * ```
1416
+ *
1417
+ * @param value - The source value to throttle. Every change is subject to
1418
+ * the cooldown window.
1419
+ * @param delay - See {@link useThrottler}.
1420
+ * @param options - See {@link UseThrottledValueOptions}.
1421
+ * @returns See {@link UseThrottledValueReturn}.
1422
+ */
1423
+ export declare function useThrottledValue<T>(value: T, delay: number, options?: UseThrottledValueOptions<T>): UseThrottledValueReturn<T>;
1424
+ /**
1425
+ * The object returned by `useThrottler`.
1426
+ */
1427
+ export interface UseThrottlerReturn {
1428
+ /**
1429
+ * Registers `func` (with `args`) as the function this cooldown window
1430
+ * will invoke.
1431
+ *
1432
+ * Each call to `run()` accepts its own function — you are not locked
1433
+ * into throttling a single, fixed callback. If a trailing invocation is
1434
+ * still pending when `run()` is called again, the previously-registered
1435
+ * function and arguments are replaced by the new ones — whichever call
1436
+ * was most recent before the window closes is the one that fires (a
1437
+ * "last write wins" swap, not a queue).
1438
+ *
1439
+ * @example
1440
+ * ```ts
1441
+ * const { run } = useThrottler(200);
1442
+ *
1443
+ * run((x: number, y: number) => logPosition(x, y), clientX, clientY);
1444
+ * ```
1445
+ */
1446
+ run: <Args extends unknown[]>(func: (...args: Args) => void, ...args: Args) => void;
1447
+ /**
1448
+ * Clears any pending trailing invocation and resets the cooldown
1449
+ * window entirely — the next call to `run()` is treated as the start
1450
+ * of a fresh window. Sets `isPending` back to `false`.
1451
+ */
1452
+ cancel: () => void;
1453
+ /**
1454
+ * If a trailing invocation is currently pending, invokes it
1455
+ * immediately (with its most recently registered arguments) and clears
1456
+ * the timer. If nothing is pending, this is a no-op.
1457
+ */
1458
+ flush: () => void;
1459
+ /**
1460
+ * `true` whenever a trailing invocation is still scheduled to fire
1461
+ * before the current cooldown window closes. `false` once it's known
1462
+ * nothing further will happen — including right after a leading-edge
1463
+ * invocation with no follow-up call yet, not merely once the whole
1464
+ * window elapses.
1465
+ */
1466
+ isPending: boolean;
1467
+ }
1468
+ /**
1469
+ * The throttle engine underlying every hook in this family.
1470
+ *
1471
+ * `useThrottler` is a low-level scheduling primitive: a single cooldown
1472
+ * window and a single "next function to run" slot. Unlike debounce,
1473
+ * additional calls that arrive mid-window do not push the window out
1474
+ * further — they just update which function/arguments will fire when the
1475
+ * *existing* window closes. This is what keeps a continuous stream of
1476
+ * calls (mousemove, scroll, resize) firing at a steady cadence instead of
1477
+ * only ever firing once activity stops.
1478
+ *
1479
+ * Most consumers won't reach for this directly — `useThrottledCallback`,
1480
+ * `useThrottledState`, and `useThrottledValue` are thin, purpose-built
1481
+ * wrappers around it for the common cases. Use `useThrottler` directly
1482
+ * when you need the "swap, don't queue" behavior across genuinely
1483
+ * different functions.
1484
+ *
1485
+ * @example
1486
+ * ```tsx
1487
+ * function ActivityLogger() {
1488
+ * const { run, isPending } = useThrottler(1000);
1489
+ *
1490
+ * return (
1491
+ * <div
1492
+ * onMouseMove={(e) => run(logMouseMove, e.clientX, e.clientY)}
1493
+ * onKeyDown={(e) => run(logKeyPress, e.key)}
1494
+ * >
1495
+ * {isPending ? "Recording…" : "Idle"}
1496
+ * </div>
1497
+ * );
1498
+ * }
1499
+ * ```
1500
+ *
1501
+ * @param delay - Length of the cooldown window, in milliseconds. Coerced
1502
+ * to a non-negative number; invalid input falls back to `0` with a
1503
+ * dev-mode warning.
1504
+ * @param options - See {@link ThrottleOptions}.
1505
+ * @returns See {@link UseThrottlerReturn}.
1506
+ */
1507
+ export declare function useThrottler(delay: number, options?: ThrottleOptions): UseThrottlerReturn;
499
1508
  export type ScrollAlign = "start" | "center" | "end" | "auto";
500
1509
  export type Axis = "vertical" | "horizontal";
501
1510
  export interface ScrollToOffsetOptions {