diff --git a/src/RangeSlider.tsx b/src/RangeSlider.tsx index 188e33d..87ab9de 100644 --- a/src/RangeSlider.tsx +++ b/src/RangeSlider.tsx @@ -6,6 +6,7 @@ import Thumb from './components/Thumb' import ResponderView from './components/ResponderView' import useDrag from './hooks/useDrag' import useCustomMarks from './hooks/useCustomMarks' +import { useAccessibilityRangeSlider } from './hooks/useAccessibilityRangeSlider' export type SliderProps = RN.ViewProps & { range?: [number, number]; @@ -120,16 +121,27 @@ const Slider = React.forwardRef((props: SliderProps, forwa const { marks, onLayoutUpdateMarks } = useCustomMarks(CustomMark, { step, minimumValue, maximumValue, activeValues: range, inverted, vertical }) + const { + minThumbRef, + maxThumbRef, + updateAccessibilityMinValue, + updateAccessibilityMaxValue, blurThumbs + } = useAccessibilityRangeSlider({ min, max, updateMaxValue, updateMinValue }) + return ( { + // We need to blur the min/max thumb if it is focused when the user interacts with the slider + blurThumbs() + onPress(value) + }} onMove={onMove} onRelease={onRelease} enabled={enabled} vertical={vertical} inverted={inverted} onLayout={onLayoutUpdateMarks} > - + crossingAllowed ? updateAccessibilityMinValue(value) : updateMinValue(min + value)} value={min} thumb='min' /> - + crossingAllowed ? updateAccessibilityMaxValue(value) : updateMaxValue(max + value)} value={max} thumb='max' /> {marks} diff --git a/src/Slider.tsx b/src/Slider.tsx index 3fe45ee..1a69261 100644 --- a/src/Slider.tsx +++ b/src/Slider.tsx @@ -110,7 +110,7 @@ const Slider = React.forwardRef((props: SliderProps, forwa enabled={enabled} vertical={vertical} inverted={inverted} onLayout={onLayoutUpdateMarks} > - + updateValue(value + thumbValue)} value={value} /> {marks} diff --git a/src/components/Thumb.tsx b/src/components/Thumb.tsx index 2f5ef36..961db88 100644 --- a/src/components/Thumb.tsx +++ b/src/components/Thumb.tsx @@ -1,6 +1,7 @@ import React from 'react' import * as RN from 'react-native' import { useEvent } from '../hooks/useEvent' +import useRounding from '../hooks/useRounding' export type ThumbProps = { style?: RN.StyleProp; @@ -15,6 +16,7 @@ export type ThumbProps = { step: number updateValue: (value: number) => void CustomThumb?: React.ComponentType<{ value: number; thumb?: 'min' | 'max' }>; + thumbRef?: React.LegacyRef } function getThumbContainerStyle (size?: number) { @@ -79,21 +81,23 @@ const Thumb = ({ minimumValue, maximumValue, step, + thumbRef, updateValue }: ThumbProps) => { const thumbContainerStyle = React.useMemo>(() => getThumbContainerStyle(CustomThumb ? undefined : size), [CustomThumb, size]) const containerStyle = React.useMemo>(() => getContainerStyle(thumbRadius), [thumbRadius]) const thumbViewStyle = React.useMemo>(() => [getThumbStyle(size, color), style as RN.ImageStyle], [style, size, color]) - + // Hook for rounding functionality + const round = useRounding({ step, minimumValue, maximumValue }) // Accessibility actions const accessibilityActions = useEvent((event: RN.AccessibilityActionEvent) => { - const tenth = (maximumValue - minimumValue) / 10 + const stepValue = round((maximumValue - minimumValue) / 10) switch (event.nativeEvent.actionName) { case 'increment': - updateValue(value + (step || tenth)) + updateValue(+stepValue) break case 'decrement': - updateValue(value - (step || tenth)) + updateValue(-stepValue) break } }) @@ -123,6 +127,7 @@ const Thumb = ({ accessibilityValue={accessibilityValues} accessibilityRole='adjustable' accessibilityLabel={thumb} + ref={thumbRef} // This is for web // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore diff --git a/src/hooks/useAccessibilityRangeSlider.tsx b/src/hooks/useAccessibilityRangeSlider.tsx new file mode 100644 index 0000000..c7ec21f --- /dev/null +++ b/src/hooks/useAccessibilityRangeSlider.tsx @@ -0,0 +1,75 @@ +import React, { useCallback } from 'react' +import { View } from 'react-native' +interface AccessibilityRangeSliderProps { + min: number; + max: number; + updateMaxValue: (value: number) => void; + updateMinValue: (value: number) => void; +} +/** + * Custom hook to manage accessibility features for a range slider component. + * It provides methods to update the minimum and maximum values of the range slider, + * @param props -Props containing the minimum value, maximum value, and functions to update them. + * @returns An object containing references to the minimum and maximum thumb elements, + * and functions to update their values. + */ +export const useAccessibilityRangeSlider = (props: AccessibilityRangeSliderProps) => { + const { min, max, updateMaxValue, updateMinValue } = props + + // Refs to hold references to the minimum and maximum thumb elements + const minThumbRef = React.useRef(null) + const maxThumbRef = React.useRef(null) + + /** + * Function to update the minimum value of the range slider + * If the new value exceeds the maximum value, it updates the maximum value instead. + * @param value - The value to be added to the current minimum value. + */ + const updateAccessibilityMinValue = useCallback((value: number) => { + const newValue = min + value + if (newValue > max) { + // If the new value exceeds the maximum value, update the maximum and min value instead + updateMinValue(max) + updateMaxValue(min + value) + // Then focus on the maximum thumb for accessibility + maxThumbRef.current?.focus() + } else { + updateMinValue(newValue) + } + }, [min, max, updateMaxValue, updateMinValue]) + + /** + * Function to update the maximum value of the range slider + * If the new value exceeds the minimum value, it updates the maximum value instead. + * @param value - The value to be added to the current maximum value. + */ + const updateAccessibilityMaxValue = useCallback((value: number) => { + const newValue = max + value + + if (newValue < min) { + // If the new value is less than the minimum value, update the minimum and maximum value instead + updateMaxValue(min) + updateMinValue(max + value) + // Then focus on the minimum thumb for accessibility + minThumbRef.current?.focus() + } else { + updateMaxValue(newValue) + } + }, [min, max, updateMaxValue, updateMinValue]) + + /** + * Function to blur thumbs + */ + const blurThumbs = useCallback(() => { + minThumbRef.current?.blur() + maxThumbRef.current?.blur() + }, []) + + return { + minThumbRef, + maxThumbRef, + updateAccessibilityMinValue, + updateAccessibilityMaxValue, + blurThumbs + } +}