@himanshu-sorathiya/react-kit 1.0.27 → 1.0.28

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