mirror of
https://github.com/streamich/react-use.git
synced 2026-01-18 14:06:52 +00:00
143 lines
3.8 KiB
TypeScript
143 lines
3.8 KiB
TypeScript
/* eslint-disable */
|
|
import { useEffect, useRef, RefObject, CSSProperties } from 'react';
|
|
|
|
import { isClient, off, on } from './util';
|
|
import useMountedState from './useMountedState';
|
|
import useSetState from './useSetState';
|
|
|
|
export interface State {
|
|
isSliding: boolean;
|
|
value: number;
|
|
}
|
|
|
|
export interface Options {
|
|
onScrub: (value: number) => void;
|
|
onScrubStart: () => void;
|
|
onScrubStop: () => void;
|
|
reverse: boolean;
|
|
styles: boolean | CSSProperties;
|
|
vertical?: boolean;
|
|
}
|
|
|
|
const noop = () => {};
|
|
|
|
const useSlider = (ref: RefObject<HTMLElement>, options: Partial<Options> = {}): State => {
|
|
const isMounted = useMountedState();
|
|
const isSliding = useRef(false);
|
|
const frame = useRef(0);
|
|
const [state, setState] = useSetState<State>({
|
|
isSliding: false,
|
|
value: 0,
|
|
});
|
|
|
|
useEffect(() => {
|
|
if (isClient) {
|
|
const styles = options.styles === undefined ? true : options.styles;
|
|
const reverse = options.reverse === undefined ? false : options.reverse;
|
|
|
|
if (ref.current && styles) {
|
|
ref.current.style.userSelect = 'none';
|
|
}
|
|
|
|
const startScrubbing = () => {
|
|
if (!isSliding.current && isMounted()) {
|
|
(options.onScrubStart || noop)();
|
|
isSliding.current = true;
|
|
setState({ isSliding: true });
|
|
bindEvents();
|
|
}
|
|
};
|
|
|
|
const stopScrubbing = () => {
|
|
if (isSliding.current && isMounted()) {
|
|
(options.onScrubStop || noop)();
|
|
isSliding.current = false;
|
|
setState({ isSliding: false });
|
|
unbindEvents();
|
|
}
|
|
};
|
|
|
|
const onMouseDown = (event: MouseEvent) => {
|
|
startScrubbing();
|
|
onMouseMove(event);
|
|
};
|
|
const onMouseMove = options.vertical
|
|
? (event: MouseEvent) => onScrub(event.clientY)
|
|
: (event: MouseEvent) => onScrub(event.clientX);
|
|
|
|
const onTouchStart = (event: TouchEvent) => {
|
|
startScrubbing();
|
|
onTouchMove(event);
|
|
};
|
|
const onTouchMove = options.vertical
|
|
? (event: TouchEvent) => onScrub(event.changedTouches[0].clientY)
|
|
: (event: TouchEvent) => onScrub(event.changedTouches[0].clientX);
|
|
|
|
const bindEvents = () => {
|
|
on(document, 'mousemove', onMouseMove);
|
|
on(document, 'mouseup', stopScrubbing);
|
|
|
|
on(document, 'touchmove', onTouchMove);
|
|
on(document, 'touchend', stopScrubbing);
|
|
};
|
|
|
|
const unbindEvents = () => {
|
|
off(document, 'mousemove', onMouseMove);
|
|
off(document, 'mouseup', stopScrubbing);
|
|
|
|
off(document, 'touchmove', onTouchMove);
|
|
off(document, 'touchend', stopScrubbing);
|
|
};
|
|
|
|
const onScrub = (clientXY: number) => {
|
|
cancelAnimationFrame(frame.current);
|
|
|
|
frame.current = requestAnimationFrame(() => {
|
|
if (isMounted() && ref.current) {
|
|
const rect = ref.current.getBoundingClientRect();
|
|
const pos = options.vertical ? rect.top : rect.left;
|
|
const length = options.vertical ? rect.height : rect.width;
|
|
|
|
// Prevent returning 0 when element is hidden by CSS
|
|
if (!length) {
|
|
return;
|
|
}
|
|
|
|
let value = (clientXY - pos) / length;
|
|
|
|
if (value > 1) {
|
|
value = 1;
|
|
} else if (value < 0) {
|
|
value = 0;
|
|
}
|
|
|
|
if (reverse) {
|
|
value = 1 - value;
|
|
}
|
|
|
|
setState({
|
|
value,
|
|
});
|
|
|
|
(options.onScrub || noop)(value);
|
|
}
|
|
});
|
|
};
|
|
|
|
on(ref.current, 'mousedown', onMouseDown);
|
|
on(ref.current, 'touchstart', onTouchStart);
|
|
|
|
return () => {
|
|
off(ref.current, 'mousedown', onMouseDown);
|
|
off(ref.current, 'touchstart', onTouchStart);
|
|
};
|
|
} else {
|
|
return undefined;
|
|
}
|
|
}, [ref, options.vertical]);
|
|
|
|
return state;
|
|
};
|
|
|
|
export default useSlider;
|