@react-native-assets/slider
Version:
Lightweight slider for React-Native and React-Native-Web. A Range slider is included
87 lines (73 loc) • 3.7 kB
text/typescript
import React from 'react'
import { useEvent } from './useEvent'
import useThumb from './useThumb'
type Props = {
step: number;
range: [number, number];
minimumRange: number;
minimumValue: number;
maximumValue: number;
slideOnTap?: boolean;
crossingAllowed?: boolean;
onValueChange?: (range: [number, number]) => boolean | void;
}
/** Handle the state of a range slider */
const useRange = ({ step, range: propValue, minimumRange, minimumValue, maximumValue, slideOnTap, onValueChange, crossingAllowed }: Props) => {
const [minProp, maxProp] = propValue
const minRef = React.useRef<number>(minProp)
const maxRef = React.useRef<number>(maxProp)
// When updating the props, we immediately apply the change to the refs in order to have the correct values in useThumb
minRef.current = React.useMemo(() => Math.max(minimumValue, minProp), [minProp, minimumValue])
maxRef.current = React.useMemo(() => Math.min(maximumValue, maxProp), [maxProp, maximumValue])
const onMinChange = useEvent((min: number) => {
minRef.current = min
onValueChange?.([min, maxRef.current].sort((a, b) => a - b) as [number, number])
})
const onMaxChange = useEvent((max: number) => {
maxRef.current = max
onValueChange?.([minRef.current, max].sort((a, b) => a - b) as [number, number])
})
// Min value thumb
const { updateValue: updateMinValue, canMove: canMoveMin, value: minValue } = useThumb({
minimumValue,
maximumValue: Math.max(minimumValue, maxRef.current - minimumRange),
value: minProp,
step,
slideOnTap,
onValueChange: onMinChange
})
// Max value thumb
const { updateValue: updateMaxValue, canMove: canMoveMax, value: maxValue } = useThumb({
minimumValue: Math.min(maximumValue, minRef.current + minimumRange),
maximumValue,
value: maxProp,
step,
slideOnTap,
onValueChange: onMaxChange
})
const range = React.useMemo(() => [minValue, maxValue].sort((a, b) => a - b) as [number, number], [maxValue, minValue])
const currentThumb = React.useRef<'min' | 'max'>()
// Method to update the lower or higher bound according to which one is the closest
const updateClosestValue = useEvent((value: number, state: 'press' | 'release' | 'drag') => {
let isMinClosest = false
// When moving a thumb, we don't want to let it cross the other thumb
if (currentThumb.current && !crossingAllowed) isMinClosest = currentThumb.current === 'min'
else if (!currentThumb.current) isMinClosest = Math.abs(value - minValue) < Math.abs(value - maxValue) || (minValue === maxValue && value < minValue)
// if the current thumb is the min, we keep it as long as it's below the max
else if (currentThumb.current === 'min') isMinClosest = value <= maxValue
// Otherwise, if we hold the max thumb, we switch only if the value is below the min
else isMinClosest = value < minValue
// We update the state accordingly
isMinClosest ? updateMinValue(value) : updateMaxValue(value)
const newThumb = isMinClosest ? 'min' : 'max' // We set the thumb being currently moved
// When the 2 thumbs cross, we set the other thumb to the max possible value
if (state === 'drag' && newThumb !== currentThumb.current) isMinClosest ? updateMaxValue(minValue) : updateMinValue(maxValue)
currentThumb.current = state === 'release' ? undefined : newThumb // We release the thumb, or keep maintaining it
return isMinClosest ? [value, maxValue] : [minValue, value]
})
const canMove = useEvent((value: number) => {
return canMoveMax(value) || canMoveMin(value)
})
return { updateMinValue, updateMaxValue, updateClosestValue, canMove, range }
}
export default useRange