feat(react): add slider component (#644)

This commit is contained in:
rahim
2026-02-28 00:48:51 -08:00
committed by GitHub
parent e63d591d6e
commit 2f8ca094ad
21 changed files with 1276 additions and 12 deletions
+1
View File
@@ -1,4 +1,5 @@
export { composeCallbacks } from './compose-callbacks';
export { identity } from './identity';
export { noop } from './noop';
export { type Throttled, throttle } from './throttle';
export { tryCatch } from './try-catch';
@@ -0,0 +1,101 @@
import { describe, expect, it, vi } from 'vitest';
import { throttle } from '../throttle';
describe('throttle', () => {
it('fires on trailing edge after the specified delay', () => {
vi.useFakeTimers();
const callback = vi.fn();
const throttled = throttle(callback, 100);
throttled('first');
expect(callback).not.toHaveBeenCalled();
vi.advanceTimersByTime(100);
expect(callback).toHaveBeenCalledOnce();
expect(callback).toHaveBeenCalledWith('first');
vi.useRealTimers();
});
it('coalesces rapid calls and uses the latest arguments', () => {
vi.useFakeTimers();
const callback = vi.fn();
const throttled = throttle(callback, 100);
throttled('first');
throttled('second');
throttled('third');
vi.advanceTimersByTime(100);
expect(callback).toHaveBeenCalledOnce();
expect(callback).toHaveBeenCalledWith('third');
vi.useRealTimers();
});
it('cancel prevents the pending invocation', () => {
vi.useFakeTimers();
const callback = vi.fn();
const throttled = throttle(callback, 100);
throttled('value');
throttled.cancel();
vi.advanceTimersByTime(200);
expect(callback).not.toHaveBeenCalled();
vi.useRealTimers();
});
it('accepts new calls after cancel', () => {
vi.useFakeTimers();
const callback = vi.fn();
const throttled = throttle(callback, 100);
throttled('before-cancel');
throttled.cancel();
throttled('after-cancel');
vi.advanceTimersByTime(100);
expect(callback).toHaveBeenCalledOnce();
expect(callback).toHaveBeenCalledWith('after-cancel');
vi.useRealTimers();
});
it('schedules a new timer after the previous one fires', () => {
vi.useFakeTimers();
const callback = vi.fn();
const throttled = throttle(callback, 100);
throttled('first-batch');
vi.advanceTimersByTime(100);
expect(callback).toHaveBeenCalledOnce();
throttled('second-batch');
vi.advanceTimersByTime(100);
expect(callback).toHaveBeenCalledTimes(2);
expect(callback).toHaveBeenLastCalledWith('second-batch');
vi.useRealTimers();
});
it('cancel is a no-op when no timer is pending', () => {
const callback = vi.fn();
const throttled = throttle(callback, 100);
expect(() => throttled.cancel()).not.toThrow();
});
});
+34
View File
@@ -0,0 +1,34 @@
/** A throttled function that can be cancelled. */
export interface Throttled<Args extends unknown[]> {
(...args: Args): void;
/** Cancel any pending trailing-edge invocation. */
cancel(): void;
}
/**
* Trailing-edge throttle: the first call schedules a timer; subsequent calls
* within the window update the arguments. The function fires once per `ms`
* window with the latest arguments.
*/
export function throttle<Args extends unknown[]>(fn: (...args: Args) => void, ms: number): Throttled<Args> {
let timerId: ReturnType<typeof setTimeout> | null = null;
let latestArgs: Args;
const throttled = (...args: Args): void => {
latestArgs = args;
if (timerId !== null) return;
timerId = setTimeout(() => {
timerId = null;
fn(...latestArgs);
}, ms);
};
throttled.cancel = (): void => {
if (timerId !== null) {
clearTimeout(timerId);
timerId = null;
}
};
return throttled;
}