Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: prevent focus from moving beyond toDate and fromDate #1468

Merged
merged 2 commits into from
Aug 15, 2022
Merged
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
94 changes: 27 additions & 67 deletions packages/react-day-picker/src/contexts/Focus/FocusContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import addWeeks from 'date-fns/addWeeks';
import addYears from 'date-fns/addYears';
import endOfWeek from 'date-fns/endOfWeek';
import startOfWeek from 'date-fns/startOfWeek';
import max from 'date-fns/max';
import min from 'date-fns/min';
import isSameDay from 'date-fns/isSameDay';

import { useDayPicker } from 'contexts/DayPicker';

Expand Down Expand Up @@ -67,7 +70,7 @@ export function FocusProvider(props: { children: ReactNode }): JSX.Element {
modifiers
);

const { weekStartsOn } = useDayPicker();
const { fromDate, toDate, weekStartsOn } = useDayPicker();

// TODO: cleanup and test obscure code below
const focusTarget =
Expand All @@ -83,75 +86,32 @@ export function FocusProvider(props: { children: ReactNode }): JSX.Element {
setFocusedDay(date);
};

const focusDayBefore = () => {
const moveFocus = (addFn: typeof addDays, after: boolean) => {
Copy link
Owner

Choose a reason for hiding this comment

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

I like this change - I'd better move it in an external function tho... I will update the code when merged.

if (!focusedDay) return;
const before = addDays(focusedDay, -1);
focus(before);
navigation.goToDate(before, focusedDay);
};
const focusDayAfter = () => {
if (!focusedDay) return;
const after = addDays(focusedDay, 1);
focus(after);
navigation.goToDate(after, focusedDay);
};
const focusWeekBefore = () => {
if (!focusedDay) return;
const up = addWeeks(focusedDay, -1);
focus(up);
navigation.goToDate(up, focusedDay);
};
const focusWeekAfter = () => {
if (!focusedDay) return;
const down = addWeeks(focusedDay, 1);
focus(down);
navigation.goToDate(down, focusedDay);
};

const focusStartOfWeek = (): void => {
if (!focusedDay) return;
const dayToFocus = startOfWeek(focusedDay, { weekStartsOn });
navigation.goToDate(dayToFocus, focusedDay);
focus(dayToFocus);
};

const focusEndOfWeek = (): void => {
if (!focusedDay) return;
const dayToFocus = endOfWeek(focusedDay, { weekStartsOn });
navigation.goToDate(dayToFocus, focusedDay);
focus(dayToFocus);
};

const focusMonthBefore = (): void => {
if (!focusedDay) return;

const monthBefore = addMonths(focusedDay, -1);
navigation.goToDate(monthBefore, focusedDay);
focus(monthBefore);
let newFocusedDay = addFn(focusedDay, after ? 1 : -1);
if (!after && fromDate) {
newFocusedDay = max([fromDate, newFocusedDay]);
}
if (after && toDate) {
newFocusedDay = min([toDate, newFocusedDay]);
}
if (isSameDay(focusedDay, newFocusedDay)) return;
navigation.goToDate(newFocusedDay, focusedDay);
focus(newFocusedDay);
};

const focusMonthAfter = () => {
if (!focusedDay) return;
const monthAfter = addMonths(focusedDay, 1);
navigation.goToDate(monthAfter, focusedDay);
focus(monthAfter);
};

const focusYearBefore = () => {
if (!focusedDay) return;

const yearBefore = addYears(focusedDay, -1);
navigation.goToDate(yearBefore, focusedDay);
focus(yearBefore);
};

const focusYearAfter = () => {
if (!focusedDay) return;

const yearAfter = addYears(focusedDay, 1);
navigation.goToDate(yearAfter, focusedDay);
focus(yearAfter);
};
const focusDayBefore = () => moveFocus(addDays, false);
const focusDayAfter = () => moveFocus(addDays, true);
const focusWeekBefore = () => moveFocus(addWeeks, false);
const focusWeekAfter = () => moveFocus(addWeeks, true);
const focusStartOfWeek = () =>
moveFocus((date) => startOfWeek(date, { weekStartsOn }), false);
const focusEndOfWeek = () =>
moveFocus((date) => endOfWeek(date, { weekStartsOn }), true);
const focusMonthBefore = () => moveFocus(addMonths, false);
const focusMonthAfter = () => moveFocus(addMonths, true);
const focusYearBefore = () => moveFocus(addYears, false);
const focusYearAfter = () => moveFocus(addYears, true);

const value: FocusContextValue = {
focusedDay,
Expand Down
112 changes: 112 additions & 0 deletions packages/react-day-picker/src/contexts/Focus/useFocusContext.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { customRenderHook } from 'test/render';
import { freezeBeforeAll } from 'test/utils';

import { FocusContextValue, useFocusContext } from 'contexts/Focus';
import isSameDay from 'date-fns/isSameDay';

let renderResult: RenderResult<FocusContextValue>;

Expand Down Expand Up @@ -80,6 +81,16 @@ describe('when a day is focused', () => {
test('should focus the day before', () => {
expect(renderResult.current.focusedDay).toEqual(dayBefore);
});
test('should not focus the day before if the day is disabled', async () => {
const { result } = customRenderHook(() => useFocusContext(), {
fromDate: day
});
await act(async () => {
await result.current.focus(day);
await result.current.focusDayBefore();
});
expect(result.current.focusedDay).toEqual(day);
});
test.todo('should call the navigation goToDate');
});
describe('when "focusDayAfter" is called', () => {
Expand All @@ -90,6 +101,16 @@ describe('when a day is focused', () => {
const dayAfter = addDays(day, 1);
expect(renderResult.current.focusedDay).toEqual(dayAfter);
});
test('should not focus the day after if the day is disabled', async () => {
const { result } = customRenderHook(() => useFocusContext(), {
toDate: day
});
await act(async () => {
await result.current.focus(day);
await result.current.focusDayAfter();
});
expect(result.current.focusedDay).toEqual(day);
});
test.todo('should call the navigation goToDate');
});
describe('when "focusWeekBefore" is called', () => {
Expand All @@ -100,6 +121,17 @@ describe('when a day is focused', () => {
const prevWeek = addWeeks(day, -1);
expect(renderResult.current.focusedDay).toEqual(prevWeek);
});
test('should not focus the day in the previous week if the day is disabled', async () => {
const fromDate = addDays(day, -3);
const { result } = customRenderHook(() => useFocusContext(), {
fromDate
});
await act(async () => {
await result.current.focus(day);
await result.current.focusWeekBefore();
});
expect(result.current.focusedDay).toEqual(fromDate);
});
test.todo('should call the navigation goToDate');
});
describe('when "focusWeekAfter" is called', () => {
Expand All @@ -110,6 +142,17 @@ describe('when a day is focused', () => {
const nextWeek = addWeeks(day, 1);
expect(renderResult.current.focusedDay).toEqual(nextWeek);
});
test('should not focus the day in the next week if the day is disabled', async () => {
const toDate = addDays(day, 4);
const { result } = customRenderHook(() => useFocusContext(), {
toDate
});
await act(async () => {
await result.current.focus(day);
await result.current.focusWeekAfter();
});
expect(result.current.focusedDay).toEqual(toDate);
});
test.todo('should call the navigation goToDate');
});
describe('when "focusStartOfWeek" is called', () => {
Expand All @@ -120,6 +163,17 @@ describe('when a day is focused', () => {
const firstDayOfWeek = startOfWeek(day);
expect(renderResult.current.focusedDay).toEqual(firstDayOfWeek);
});
test('should not focus the first day of the week if the day is disabled', async () => {
const fromDate = addDays(startOfWeek(day), 1);
const { result } = customRenderHook(() => useFocusContext(), {
fromDate
});
await act(async () => {
await result.current.focus(day);
await result.current.focusStartOfWeek();
});
expect(result.current.focusedDay).toEqual(fromDate);
});
test.todo('should call the navigation goToDate');
});
describe('when "focusEndOfWeek" is called', () => {
Expand All @@ -130,6 +184,20 @@ describe('when a day is focused', () => {
const lastDayOfWeek = endOfWeek(day);
expect(renderResult.current.focusedDay).toEqual(lastDayOfWeek);
});
test('should not focus the last day of the week if the day is disabled', async () => {
const toDate = addDays(endOfWeek(day), -1);
const { result } = customRenderHook(() => useFocusContext(), {
toDate
});
await act(async () => {
await result.current.focus(day);
await result.current.focusEndOfWeek();
});
expect(
result.current.focusedDay &&
isSameDay(result.current.focusedDay, toDate)
).toBe(true);
});
test.todo('should call the navigation goToDate');
});
describe('when "focusMonthBefore" is called', () => {
Expand All @@ -140,6 +208,17 @@ describe('when a day is focused', () => {
const monthBefore = addMonths(day, -1);
expect(renderResult.current.focusedDay).toEqual(monthBefore);
});
test('should not focus the day in the month before if the day is disabled', async () => {
const fromDate = addDays(day, -10);
const { result } = customRenderHook(() => useFocusContext(), {
fromDate
});
await act(async () => {
await result.current.focus(day);
await result.current.focusMonthBefore();
});
expect(result.current.focusedDay).toEqual(fromDate);
});
test.todo('should call the navigation goToDate');
});
describe('when "focusMonthAfter" is called', () => {
Expand All @@ -150,6 +229,17 @@ describe('when a day is focused', () => {
const monthAfter = addMonths(day, 1);
expect(renderResult.current.focusedDay).toEqual(monthAfter);
});
test('should not focus the day in the month after if the day is disabled', async () => {
const toDate = addDays(day, 10);
const { result } = customRenderHook(() => useFocusContext(), {
toDate
});
await act(async () => {
await result.current.focus(day);
await result.current.focusMonthAfter();
});
expect(result.current.focusedDay).toEqual(toDate);
});
test.todo('should call the navigation goToDate');
});
describe('when "focusYearBefore" is called', () => {
Expand All @@ -160,6 +250,17 @@ describe('when a day is focused', () => {
const prevYear = addYears(day, -1);
expect(renderResult.current.focusedDay).toEqual(prevYear);
});
test('should not focus the day in the year before if the day is disabled', async () => {
const fromDate = addMonths(day, -10);
const { result } = customRenderHook(() => useFocusContext(), {
fromDate
});
await act(async () => {
await result.current.focus(day);
await result.current.focusYearBefore();
});
expect(result.current.focusedDay).toEqual(fromDate);
});
test.todo('should call the navigation goToDate');
});
describe('when "focusYearAfter" is called', () => {
Expand All @@ -170,6 +271,17 @@ describe('when a day is focused', () => {
const nextYear = addYears(day, 1);
expect(renderResult.current.focusedDay).toEqual(nextYear);
});
test('should not focus the day in the year after if the day is disabled', async () => {
const toDate = addMonths(day, 10);
const { result } = customRenderHook(() => useFocusContext(), {
toDate
});
await act(async () => {
await result.current.focus(day);
await result.current.focusYearAfter();
});
expect(result.current.focusedDay).toEqual(toDate);
});
test.todo('should call the navigation goToDate');
});
describe('when blur is called', () => {
Expand Down