From bdcd83cae3cf20e51d559f5a363762feab20a843 Mon Sep 17 00:00:00 2001 From: Linh Vo Date: Sat, 4 May 2024 10:50:09 +0700 Subject: [PATCH 1/2] feat: handle accessibility for crossing over thumbs --- src/RangeSlider.tsx | 18 +++++- src/Slider.tsx | 2 +- src/components/Thumb.tsx | 9 ++- src/hooks/useAccessibilityRangeSlider.tsx | 75 +++++++++++++++++++++++ 4 files changed, 97 insertions(+), 7 deletions(-) create mode 100644 src/hooks/useAccessibilityRangeSlider.tsx 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..2efac2e 100644 --- a/src/components/Thumb.tsx +++ b/src/components/Thumb.tsx @@ -15,6 +15,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,6 +80,7 @@ const Thumb = ({ minimumValue, maximumValue, step, + thumbRef, updateValue }: ThumbProps) => { const thumbContainerStyle = React.useMemo>(() => getThumbContainerStyle(CustomThumb ? undefined : size), [CustomThumb, size]) @@ -87,13 +89,13 @@ const Thumb = ({ // Accessibility actions const accessibilityActions = useEvent((event: RN.AccessibilityActionEvent) => { - const tenth = (maximumValue - minimumValue) / 10 + const tenth = Math.round((maximumValue - minimumValue) / 10) switch (event.nativeEvent.actionName) { case 'increment': - updateValue(value + (step || tenth)) + updateValue(+(step || tenth)) break case 'decrement': - updateValue(value - (step || tenth)) + updateValue(-(step || tenth)) break } }) @@ -123,6 +125,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 + } +} From 581b2773b5bf7b1ad9803ff4a3cc89bd196e5a62 Mon Sep 17 00:00:00 2001 From: Linh Vo Date: Tue, 11 Jun 2024 10:45:43 +0700 Subject: [PATCH 2/2] feat: get step value from useRouding --- src/components/Thumb.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/components/Thumb.tsx b/src/components/Thumb.tsx index 2efac2e..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; @@ -86,16 +87,17 @@ const Thumb = ({ 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 = Math.round((maximumValue - minimumValue) / 10) + const stepValue = round((maximumValue - minimumValue) / 10) switch (event.nativeEvent.actionName) { case 'increment': - updateValue(+(step || tenth)) + updateValue(+stepValue) break case 'decrement': - updateValue(-(step || tenth)) + updateValue(-stepValue) break } })