From a4edfa4ef5f1954291fee325b670c90362122a19 Mon Sep 17 00:00:00 2001 From: Vitaly Rtishchev Date: Thu, 28 Nov 2024 15:59:31 +0400 Subject: [PATCH] [@mantine/core] Slider: Fix `restrictToMarks` prop not working with arrow and Home/End keys correctly --- .../src/components/Slider/Slider/Slider.tsx | 54 +++++++++++++++++++ .../get-step-mark-value.test.ts | 38 +++++++++++++ .../get-step-mark-value.ts | 27 ++++++++++ 3 files changed, 119 insertions(+) create mode 100644 packages/@mantine/core/src/components/Slider/utils/get-step-mark-value/get-step-mark-value.test.ts create mode 100644 packages/@mantine/core/src/components/Slider/utils/get-step-mark-value/get-step-mark-value.ts diff --git a/packages/@mantine/core/src/components/Slider/Slider/Slider.tsx b/packages/@mantine/core/src/components/Slider/Slider/Slider.tsx index 9cdbb831160..f6e6bf4373d 100644 --- a/packages/@mantine/core/src/components/Slider/Slider/Slider.tsx +++ b/packages/@mantine/core/src/components/Slider/Slider/Slider.tsx @@ -28,6 +28,12 @@ import { getChangeValue } from '../utils/get-change-value/get-change-value'; import { getFloatingValue } from '../utils/get-floating-value/get-gloating-value'; import { getPosition } from '../utils/get-position/get-position'; import { getPrecision } from '../utils/get-precision/get-precision'; +import { + getFirstMarkValue, + getLastMarkValue, + getNextMarkValue, + getPreviousMarkValue, +} from '../utils/get-step-mark-value/get-step-mark-value'; import classes from '../Slider.module.css'; export interface SliderProps @@ -254,6 +260,14 @@ export const Slider = factory((_props, ref) => { case 'ArrowUp': { event.preventDefault(); thumb.current?.focus(); + + if (restrictToMarks && marks) { + const nextValue = getNextMarkValue(_value, marks); + setValue(nextValue); + onChangeEnd?.(nextValue); + break; + } + const nextValue = getFloatingValue( Math.min(Math.max(_value + step!, min!), max!), precision @@ -266,6 +280,15 @@ export const Slider = factory((_props, ref) => { case 'ArrowRight': { event.preventDefault(); thumb.current?.focus(); + + if (restrictToMarks && marks) { + const nextValue = + dir === 'rtl' ? getPreviousMarkValue(_value, marks) : getNextMarkValue(_value, marks); + setValue(nextValue); + onChangeEnd?.(nextValue); + break; + } + const nextValue = getFloatingValue( Math.min(Math.max(dir === 'rtl' ? _value - step! : _value + step!, min!), max!), precision @@ -278,6 +301,14 @@ export const Slider = factory((_props, ref) => { case 'ArrowDown': { event.preventDefault(); thumb.current?.focus(); + + if (restrictToMarks && marks) { + const nextValue = getPreviousMarkValue(_value, marks); + setValue(nextValue); + onChangeEnd?.(nextValue); + break; + } + const nextValue = getFloatingValue( Math.min(Math.max(_value - step!, min!), max!), precision @@ -290,6 +321,15 @@ export const Slider = factory((_props, ref) => { case 'ArrowLeft': { event.preventDefault(); thumb.current?.focus(); + + if (restrictToMarks && marks) { + const nextValue = + dir === 'rtl' ? getNextMarkValue(_value, marks) : getPreviousMarkValue(_value, marks); + setValue(nextValue); + onChangeEnd?.(nextValue); + break; + } + const nextValue = getFloatingValue( Math.min(Math.max(dir === 'rtl' ? _value + step! : _value - step!, min!), max!), precision @@ -302,6 +342,13 @@ export const Slider = factory((_props, ref) => { case 'Home': { event.preventDefault(); thumb.current?.focus(); + + if (restrictToMarks && marks) { + setValue(getFirstMarkValue(marks)); + onChangeEnd?.(getFirstMarkValue(marks)); + break; + } + setValue(min!); onChangeEnd?.(min!); break; @@ -310,6 +357,13 @@ export const Slider = factory((_props, ref) => { case 'End': { event.preventDefault(); thumb.current?.focus(); + + if (restrictToMarks && marks) { + setValue(getLastMarkValue(marks)); + onChangeEnd?.(getLastMarkValue(marks)); + break; + } + setValue(max!); onChangeEnd?.(max!); break; diff --git a/packages/@mantine/core/src/components/Slider/utils/get-step-mark-value/get-step-mark-value.test.ts b/packages/@mantine/core/src/components/Slider/utils/get-step-mark-value/get-step-mark-value.test.ts new file mode 100644 index 00000000000..a1c697957bd --- /dev/null +++ b/packages/@mantine/core/src/components/Slider/utils/get-step-mark-value/get-step-mark-value.test.ts @@ -0,0 +1,38 @@ +import { + getFirstMarkValue, + getLastMarkValue, + getNextMarkValue, + getPreviousMarkValue, +} from './get-step-mark-value'; + +describe('@mantine/core/Slider/get-step-mark-value', () => { + it('returns first mark value', () => { + expect(getFirstMarkValue([{ value: 1 }, { value: 2 }])).toBe(2); + expect(getFirstMarkValue([{ value: 1 }])).toBe(1); + expect(getFirstMarkValue([])).toBe(0); + }); + + it('returns last mark value', () => { + expect(getLastMarkValue([{ value: 1 }, { value: 2 }])).toBe(2); + expect(getLastMarkValue([{ value: 1 }])).toBe(1); + expect(getLastMarkValue([])).toBe(100); + }); + + it('returns next mark value', () => { + expect(getNextMarkValue(1, [{ value: 1 }, { value: 2 }])).toBe(2); + expect(getNextMarkValue(1, [{ value: 2 }, { value: 3 }])).toBe(2); + expect(getNextMarkValue(1, [{ value: 0 }, { value: 2 }])).toBe(2); + expect(getNextMarkValue(1, [{ value: 0 }, { value: 1 }])).toBe(1); + expect(getNextMarkValue(1, [{ value: 1 }])).toBe(1); + expect(getNextMarkValue(1, [])).toBe(1); + }); + + it('returns previous mark value', () => { + expect(getPreviousMarkValue(1, [{ value: 1 }, { value: 2 }])).toBe(1); + expect(getPreviousMarkValue(1, [{ value: 2 }, { value: 3 }])).toBe(1); + expect(getPreviousMarkValue(1, [{ value: 0 }, { value: 2 }])).toBe(0); + expect(getPreviousMarkValue(1, [{ value: 0 }, { value: 1 }])).toBe(0); + expect(getPreviousMarkValue(1, [{ value: 1 }])).toBe(1); + expect(getPreviousMarkValue(1, [])).toBe(1); + }); +}); diff --git a/packages/@mantine/core/src/components/Slider/utils/get-step-mark-value/get-step-mark-value.ts b/packages/@mantine/core/src/components/Slider/utils/get-step-mark-value/get-step-mark-value.ts new file mode 100644 index 00000000000..d270771596f --- /dev/null +++ b/packages/@mantine/core/src/components/Slider/utils/get-step-mark-value/get-step-mark-value.ts @@ -0,0 +1,27 @@ +export function getNextMarkValue( + currentValue: number, + marks: { value: number; label?: React.ReactNode }[] +) { + const sortedMarks = [...marks].sort((a, b) => a.value - b.value); + const nextMark = sortedMarks.find((mark) => mark.value > currentValue); + return nextMark ? nextMark.value : currentValue; +} + +export function getPreviousMarkValue( + currentValue: number, + marks: { value: number; label?: React.ReactNode }[] +) { + const sortedMarks = [...marks].sort((a, b) => b.value - a.value); + const previousMark = sortedMarks.find((mark) => mark.value < currentValue); + return previousMark ? previousMark.value : currentValue; +} + +export function getFirstMarkValue(marks: { value: number; label?: React.ReactNode }[]) { + const sortedMarks = [...marks].sort((a, b) => b.value - a.value); + return sortedMarks.length > 0 ? sortedMarks[0].value : 0; +} + +export function getLastMarkValue(marks: { value: number; label?: React.ReactNode }[]) { + const sortedMarks = [...marks].sort((a, b) => a.value - b.value); + return sortedMarks.length > 0 ? sortedMarks[sortedMarks.length - 1].value : 100; +}