Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions src/RangeSlider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down Expand Up @@ -120,16 +121,27 @@ const Slider = React.forwardRef<RN.View, SliderProps>((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 (
<RN.View {...others}>
<ResponderView style={styleSheet[vertical ? 'vertical' : 'horizontal']} ref={forwardedRef} maximumValue={maximumValue} minimumValue={minimumValue} step={step}
onPress={onPress} onMove={onMove} onRelease={onRelease}
onPress={(value) => {
// We need to blur the min/max thumb if it is focused when the user interacts with the slider
blurThumbs()
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find this suspicious. What would have the focuse, then?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Sharcoux In the example, if I focus on the min thumb, and then drag this thumb over the max thumb, you will see that the min thumb remains focused while I'm interacting with the max thumb.

Screen.Recording.2024-05-23.at.5.08.06.PM.mov

onPress(value)
}} onMove={onMove} onRelease={onRelease}
enabled={enabled} vertical={vertical} inverted={inverted} onLayout={onLayoutUpdateMarks}
>
<Track color={outboundColor} style={minStyle} length={minTrackPct * 100} vertical={vertical} thickness={trackHeight} />
<Thumb key='min' {...thumbProps} updateValue={updateMinValue} value={min} thumb='min' />
<Thumb key='min' {...thumbProps} thumbRef={minThumbRef} updateValue={(value) => crossingAllowed ? updateAccessibilityMinValue(value) : updateMinValue(min + value)} value={min} thumb='min' />
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that a better approach would be to uniquely identify each thumb with their "key", and order them based on their current value. This would require accepting the range to be reverted internally: [3, 1] would be a valid range internally/

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Sharcoux If I order them based on their current value with their 'key', how can I focus on the max/min thumbs if I increase/decrease the value beyond them?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you order them based on their current value, the thumb would not change, so you would not need to handle the focus switch.

<Track color={inboundColor} style={midStyle} length={(maxTrackPct - minTrackPct) * 100} vertical={vertical} thickness={trackHeight} />
<Thumb key='max' {...thumbProps} updateValue={updateMaxValue} value={max} thumb='max' />
<Thumb key='max' {...thumbProps} thumbRef={maxThumbRef} updateValue={(value) => crossingAllowed ? updateAccessibilityMaxValue(value) : updateMaxValue(max + value)} value={max} thumb='max' />
<Track color={outboundColor} style={maxStyle} length={(1 - maxTrackPct) * 100} vertical={vertical} thickness={trackHeight} />
{marks}
</ResponderView>
Expand Down
2 changes: 1 addition & 1 deletion src/Slider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ const Slider = React.forwardRef<RN.View, SliderProps>((props: SliderProps, forwa
enabled={enabled} vertical={vertical} inverted={inverted} onLayout={onLayoutUpdateMarks}
>
<Track color={minimumTrackTintColor} style={minStyle} length={percentage * 100} vertical={vertical} thickness={trackHeight} />
<Thumb {...thumbProps} updateValue={updateValue} value={value} />
<Thumb {...thumbProps} updateValue={(thumbValue) => updateValue(value + thumbValue)} value={value} />
<Track color={maximumTrackTintColor} style={maxStyle} length={(1 - percentage) * 100} vertical={vertical} thickness={trackHeight} />
{marks}
</ResponderView>
Expand Down
13 changes: 9 additions & 4 deletions src/components/Thumb.tsx
Original file line number Diff line number Diff line change
@@ -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<RN.ViewStyle>;
Expand All @@ -15,6 +16,7 @@ export type ThumbProps = {
step: number
updateValue: (value: number) => void
CustomThumb?: React.ComponentType<{ value: number; thumb?: 'min' | 'max' }>;
thumbRef?: React.LegacyRef<RN.View>
}

function getThumbContainerStyle (size?: number) {
Expand Down Expand Up @@ -79,21 +81,23 @@ const Thumb = ({
minimumValue,
maximumValue,
step,
thumbRef,
updateValue
}: ThumbProps) => {
const thumbContainerStyle = React.useMemo<RN.StyleProp<RN.ViewStyle>>(() => getThumbContainerStyle(CustomThumb ? undefined : size), [CustomThumb, size])
const containerStyle = React.useMemo<RN.StyleProp<RN.ViewStyle>>(() => getContainerStyle(thumbRadius), [thumbRadius])
const thumbViewStyle = React.useMemo<RN.StyleProp<RN.ImageStyle>>(() => [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
}
})
Expand Down Expand Up @@ -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
Expand Down
75 changes: 75 additions & 0 deletions src/hooks/useAccessibilityRangeSlider.tsx
Original file line number Diff line number Diff line change
@@ -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<View>(null)
const maxThumbRef = React.useRef<View>(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
}
}