From 7413115ff1c1d1c2eb40e2f64fee019c3a394a90 Mon Sep 17 00:00:00 2001 From: Raphael Fakhri <153192858+RaphaelFakhri@users.noreply.github.com> Date: Wed, 21 Oct 2026 06:41:20 +0000 Subject: [PATCH 1/2] fix(web): count steps from minimumValue --- .../RNCSliderNativeComponent.web.test.tsx | 62 +++++++++++++++++++ package/src/RNCSliderNativeComponent.web.tsx | 3 +- 2 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 package/__test__/RNCSliderNativeComponent.web.test.tsx diff --git a/package/__test__/RNCSliderNativeComponent.web.test.tsx b/package/__test__/RNCSliderNativeComponent.web.test.tsx new file mode 100644 index 00000000..71c46432 --- /dev/null +++ b/package/__test__/RNCSliderNativeComponent.web.test.tsx @@ -0,0 +1,62 @@ +import React from 'react'; +import {fireEvent, render} from '@testing-library/react-native'; +import RCTSliderWebComponent from '../src/RNCSliderNativeComponent.web'; + +// Only the props relevant to the tests are passed +const WebSlider = RCTSliderWebComponent as React.ComponentType; + +describe('RCTSliderWebComponent', () => { + beforeAll(() => { + // The web component subscribes to DOM events, which the + // react-native test environment doesn't provide + const listeners = { + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + }; + Object.assign(global, listeners, {document: listeners}); + }); + + // Without a layout the track is 1px wide and starts at x = 0, + // so `pageX` is the fraction of the track left of the touch + const releaseAt = (props: object, pageX: number) => { + const onSlidingComplete = jest.fn(); + const {getByTestId} = render( + , + ); + fireEvent(getByTestId('slider'), 'responderRelease', { + nativeEvent: {pageX}, + }); + return onSlidingComplete.mock.calls[0][0].nativeEvent.value; + }; + + it('Counts steps from the minimum value', () => { + const props = { + minimumValue: 1, + maximumValue: 10, + lowerLimit: 1, + upperLimit: 10, + step: 2, + }; + // 1 + 9 * 0.25 = 3.25, the nearest step is 3 + expect(releaseAt(props, 0.25)).toBe(3); + // 1 + 9 * 0.5 = 5.5, the nearest step is 5 + expect(releaseAt(props, 0.5)).toBe(5); + }); + + it('Counts steps from the minimum value when inverted', () => { + const props = { + minimumValue: 1, + maximumValue: 10, + lowerLimit: 1, + upperLimit: 10, + step: 2, + inverted: true, + }; + // 10 - 9 * 0.75 = 3.25, the nearest step is 3 + expect(releaseAt(props, 0.75)).toBe(3); + }); +}); diff --git a/package/src/RNCSliderNativeComponent.web.tsx b/package/src/RNCSliderNativeComponent.web.tsx index e11e1853..ba976ee3 100644 --- a/package/src/RNCSliderNativeComponent.web.tsx +++ b/package/src/RNCSliderNativeComponent.web.tsx @@ -290,8 +290,9 @@ const RCTSliderWebComponent = React.forwardRef( ? maximumValue - ((maximumValue - minimumValue) * x) / width : minimumValue + ((maximumValue - minimumValue) * x) / width; + // Steps are counted from minimumValue, as on the native platforms const valueAfterStep = step - ? Math.round(newValue / step) * step + ? minimumValue + Math.round((newValue - minimumValue) / step) * step : newValue; const valueAfterLowerLimit = valueAfterStep < lowerLimit ? lowerLimit : valueAfterStep; From 9f0b648cea039e1dd27eeda885e1f9182a3b1584 Mon Sep 17 00:00:00 2001 From: Raphael Fakhri <153192858+RaphaelFakhri@users.noreply.github.com> Date: Wed, 21 Oct 2026 12:33:32 +0000 Subject: [PATCH 2/2] fix(web): count accessibility action steps from minimumValue Round the increment and decrement accessibility actions to the step grid that starts at minimumValue, as dragging does. Without a step, the actions still move by a tenth of the range. Co-authored-by: Bartosz Klonowski <70535775+BartoszKlonowski@users.noreply.github.com> --- .../RNCSliderNativeComponent.web.test.tsx | 33 +++++++++++++++++++ package/src/RNCSliderNativeComponent.web.tsx | 15 +++++++-- 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/package/__test__/RNCSliderNativeComponent.web.test.tsx b/package/__test__/RNCSliderNativeComponent.web.test.tsx index 71c46432..272de863 100644 --- a/package/__test__/RNCSliderNativeComponent.web.test.tsx +++ b/package/__test__/RNCSliderNativeComponent.web.test.tsx @@ -33,6 +33,24 @@ describe('RCTSliderWebComponent', () => { return onSlidingComplete.mock.calls[0][0].nativeEvent.value; }; + // Accessibility actions don't report a value change, so the value is + // read from the sliding start event that the next touch emits + const valueAfterAction = (props: object, actionName: string) => { + const onSlidingStart = jest.fn(); + const {getByTestId} = render( + , + ); + fireEvent(getByTestId('slider'), 'accessibilityAction', { + nativeEvent: {actionName}, + }); + fireEvent(getByTestId('slider'), 'responderGrant'); + return onSlidingStart.mock.calls[0][0].nativeEvent.value; + }; + it('Counts steps from the minimum value', () => { const props = { minimumValue: 1, @@ -59,4 +77,19 @@ describe('RCTSliderWebComponent', () => { // 10 - 9 * 0.75 = 3.25, the nearest step is 3 expect(releaseAt(props, 0.75)).toBe(3); }); + + it('Counts accessibility action steps from the minimum value', () => { + const props = { + value: 5, + minimumValue: 1, + maximumValue: 13, + lowerLimit: 1, + upperLimit: 13, + step: 3, + }; + // 5 + 3 = 8, the nearest step is 7 + expect(valueAfterAction(props, 'increment')).toBe(7); + // 5 - 3 = 2, the nearest step is 1 + expect(valueAfterAction(props, 'decrement')).toBe(1); + }); }); diff --git a/package/src/RNCSliderNativeComponent.web.tsx b/package/src/RNCSliderNativeComponent.web.tsx index ba976ee3..19c73cb2 100644 --- a/package/src/RNCSliderNativeComponent.web.tsx +++ b/package/src/RNCSliderNativeComponent.web.tsx @@ -290,7 +290,6 @@ const RCTSliderWebComponent = React.forwardRef( ? maximumValue - ((maximumValue - minimumValue) * x) / width : minimumValue + ((maximumValue - minimumValue) * x) / width; - // Steps are counted from minimumValue, as on the native platforms const valueAfterStep = step ? minimumValue + Math.round((newValue - minimumValue) / step) * step : newValue; @@ -318,10 +317,20 @@ const RCTSliderWebComponent = React.forwardRef( const tenth = (maximumValue - minimumValue) / 10; switch (event.nativeEvent.actionName) { case 'increment': - updateValue(value + (step || tenth)); + updateValue( + step + ? Math.round((value + step - minimumValue) / step) * step + + minimumValue + : value + tenth, + ); break; case 'decrement': - updateValue(value - (step || tenth)); + updateValue( + step + ? Math.round((value - step - minimumValue) / step) * step + + minimumValue + : value - tenth, + ); break; } };