From 146fc6add470d336c61e161cb44344d721cef649 Mon Sep 17 00:00:00 2001 From: gpbl Date: Sun, 25 Sep 2022 19:30:01 -0500 Subject: [PATCH] fix: disabled modifiers with min/max range selections (#1566) --- .../SelectRange/SelectRangeContext.test.ts | 22 +----- .../SelectRange/SelectRangeContext.tsx | 72 ++++++++++--------- website/examples/range-min-max.tsx | 5 +- .../examples/range-min-max.test.tsx | 69 +++++++----------- 4 files changed, 67 insertions(+), 101 deletions(-) diff --git a/packages/react-day-picker/src/contexts/SelectRange/SelectRangeContext.test.ts b/packages/react-day-picker/src/contexts/SelectRange/SelectRangeContext.test.ts index 4ae978906a..16f522f748 100644 --- a/packages/react-day-picker/src/contexts/SelectRange/SelectRangeContext.test.ts +++ b/packages/react-day-picker/src/contexts/SelectRange/SelectRangeContext.test.ts @@ -147,11 +147,13 @@ describe('when a complete range of days is selected', () => { }); describe('when the max number of the selected days is reached', () => { + const from = today; + const to = addDays(today, 6); const selected = { from, to }; const dayPickerProps: DayPickerRangeProps = { ...initialProps, selected, - max: Math.abs(differenceInCalendarDays(to, from)) + max: 7 }; beforeAll(() => { setup(dayPickerProps); @@ -182,9 +184,6 @@ describe('when the max number of the selected days is reached', () => { stubEvent ); }); - test('should not call onSelect', () => { - expect(dayPickerProps.onSelect).not.toHaveBeenCalled(); - }); }); }); @@ -263,19 +262,4 @@ describe('when the minimum number of days are selected', () => { ); }); }); - - describe('when "onDayClick" is called with a day before "to"', () => { - const day = subDays(to, 1); - const activeModifiers: ActiveModifiers = { selected: true }; - - beforeAll(() => { - result.current.onDayClick?.(day, activeModifiers, stubEvent); - }); - afterAll(() => { - jest.resetAllMocks(); - }); - test('should not call "onSelect"', () => { - expect(dayPickerProps.onSelect).not.toHaveBeenCalled(); - }); - }); }); diff --git a/packages/react-day-picker/src/contexts/SelectRange/SelectRangeContext.tsx b/packages/react-day-picker/src/contexts/SelectRange/SelectRangeContext.tsx index 72a95850f7..37f9fdd421 100644 --- a/packages/react-day-picker/src/contexts/SelectRange/SelectRangeContext.tsx +++ b/packages/react-day-picker/src/contexts/SelectRange/SelectRangeContext.tsx @@ -1,8 +1,8 @@ import React, { createContext, ReactNode, useContext } from 'react'; +import addDays from 'date-fns/addDays'; import differenceInCalendarDays from 'date-fns/differenceInCalendarDays'; -import isAfter from 'date-fns/isAfter'; -import isBefore from 'date-fns/isBefore'; +import subDays from 'date-fns/subDays'; import { DayPickerBase } from 'types/DayPickerBase'; import { DayPickerRangeProps, isDayPickerRange } from 'types/DayPickerRange'; @@ -90,20 +90,8 @@ export function SelectRangeProviderInternal({ const onDayClick: DayClickEventHandler = (day, activeModifiers, e) => { initialProps.onDayClick?.(day, activeModifiers, e); - const range = addToRange(day, selected); - if ( - (min || max) && - selected && - range?.to && - range.from && - range.from !== range.to - ) { - const diff = Math.abs(differenceInCalendarDays(range?.to, range?.from)); - if ((min && diff < min) || (max && diff >= max)) { - return; - } - } - initialProps.onSelect?.(range, day, activeModifiers, e); + const newRange = addToRange(day, selected); + initialProps.onSelect?.(newRange, day, activeModifiers, e); }; const modifiers: SelectRangeModifiers = { @@ -128,26 +116,40 @@ export function SelectRangeProviderInternal({ } } - if (min && selectedFrom && selectedTo) { - modifiers.disabled.push((date: Date) => { - return ( - (isBefore(date, selectedFrom) && - differenceInCalendarDays(selectedFrom, date) < min) || - (isAfter(date, selectedTo) && - differenceInCalendarDays(date, selectedFrom) < min) - ); - }); + if (min) { + if (selectedFrom && !selectedTo) { + modifiers.disabled.push({ + after: subDays(selectedFrom, min - 1), + before: addDays(selectedFrom, min - 1) + }); + } + if (selectedFrom && selectedTo) { + modifiers.disabled.push({ + after: selectedFrom, + before: addDays(selectedFrom, min - 1) + }); + } } - - if (max && selectedFrom && selectedTo) { - modifiers.disabled.push((date: Date) => { - return ( - (isBefore(date, selectedFrom) && - differenceInCalendarDays(selectedTo, date) >= max) || - (isAfter(date, selectedTo) && - differenceInCalendarDays(date, selectedFrom) >= max) - ); - }); + if (max) { + if (selectedFrom && !selectedTo) { + modifiers.disabled.push({ + before: addDays(selectedFrom, -max + 1) + }); + modifiers.disabled.push({ + after: addDays(selectedFrom, max - 1) + }); + } + if (selectedFrom && selectedTo) { + const selectedCount = + differenceInCalendarDays(selectedTo, selectedFrom) + 1; + const offset = max - selectedCount; + modifiers.disabled.push({ + before: subDays(selectedFrom, offset) + }); + modifiers.disabled.push({ + after: addDays(selectedTo, offset) + }); + } } return ( diff --git a/website/examples/range-min-max.tsx b/website/examples/range-min-max.tsx index fde6d11acf..992c5d0b1b 100644 --- a/website/examples/range-min-max.tsx +++ b/website/examples/range-min-max.tsx @@ -21,9 +21,10 @@ export default function App() { return ( { @@ -16,52 +17,30 @@ beforeEach(() => { }); describe('when the first day is clicked', () => { - const fromDay = new Date(2021, 10, 15); + const fromDay = setDate(today, 14); beforeEach(() => clickDay(fromDay)); - test('should disable before the allowed range', () => { - expect(getAllEnabledDays()[0]).toHaveAttribute( - 'aria-label', - '1st November (Monday)' - ); + test('the clicked day should be selected', () => { + expect(getDayButton(fromDay)).toHaveAttribute('aria-pressed', 'true'); }); - test('should disable after the allowed range', () => { - const enabledDays = getAllEnabledDays(); - expect(enabledDays[enabledDays.length - 1]).toHaveAttribute( - 'aria-label', - '30th November (Tuesday)' - ); + test('the days below the min value should be disabled', () => { + expect(getDayButton(setDate(today, 13))).toBeDisabled(); + expect(getDayButton(setDate(today, 14))).toBeDisabled(); + expect(getDayButton(setDate(today, 15))).toBeDisabled(); }); - describe('when clicking a day after the from date', () => { - const toDay = new Date(2021, 10, 17); - const expectedSelectedDays = [ - new Date(2021, 10, 15), - new Date(2021, 10, 16), - new Date(2021, 10, 17) - ]; - beforeEach(() => clickDay(toDay)); - test.each(expectedSelectedDays)('%s should be selected', (day) => { - expect(getDayButton(day)).toHaveAttribute('aria-pressed', 'true'); - }); - test('should enable the days up to the clicked day', () => { - const enabledDays = getAllEnabledDays(); - expect(enabledDays[enabledDays.length - 1]).toHaveAttribute( - 'aria-label', - '19th November (Friday)' - ); - }); + test('the days between max and min should be enabled', () => { + expect(getDayButton(setDate(today, 9))).not.toBeDisabled(); + expect(getDayButton(setDate(today, 10))).not.toBeDisabled(); + expect(getDayButton(setDate(today, 11))).not.toBeDisabled(); + expect(getDayButton(setDate(today, 12))).not.toBeDisabled(); + expect(getDayButton(setDate(today, 16))).not.toBeDisabled(); + expect(getDayButton(setDate(today, 17))).not.toBeDisabled(); + expect(getDayButton(setDate(today, 18))).not.toBeDisabled(); + expect(getDayButton(setDate(today, 19))).not.toBeDisabled(); }); - describe('when clicking a day before the from date', () => { - const toDay = new Date(2021, 10, 11); - const expectedSelectedDays = [ - new Date(2021, 10, 11), - new Date(2021, 10, 12), - new Date(2021, 10, 13), - new Date(2021, 10, 14), - new Date(2021, 10, 15) - ]; - beforeEach(() => clickDay(toDay)); - test.each(expectedSelectedDays)('%s should be selected', (day) => { - expect(getDayButton(day)).toHaveAttribute('aria-pressed', 'true'); - }); + test('the days above the max value should be disabled', () => { + expect(getDayButton(setDate(today, 7))).toBeDisabled(); + expect(getDayButton(setDate(today, 8))).toBeDisabled(); + expect(getDayButton(setDate(today, 20))).toBeDisabled(); + expect(getDayButton(setDate(today, 21))).toBeDisabled(); }); });