Skip to content

Commit 12f2f4e

Browse files
QzCuriousLFDanLu
andauthored
Fix use range calendar with mobile scrolling (#5976)
* remove redundant code * fix type * fix(useRangeCalendar): mobile scrolling range selection * test cases --------- Co-authored-by: Daniel Lu <dl1644@gmail.com>
1 parent 4f26a4b commit 12f2f4e

File tree

2 files changed

+155
-5
lines changed

2 files changed

+155
-5
lines changed

packages/@react-aria/calendar/src/useRangeCalendar.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,21 +50,22 @@ export function useRangeCalendar<T extends DateValue>(props: AriaRangeCalendarPr
5050
}
5151

5252
let target = e.target as Element;
53-
let body = document.getElementById(res.calendarProps.id);
5453
if (
55-
body &&
56-
body.contains(document.activeElement) &&
57-
(!body.contains(target) || !target.closest('button, [role="button"]'))
54+
ref.current &&
55+
ref.current.contains(document.activeElement) &&
56+
(!ref.current.contains(target) || !target.closest('button, [role="button"]'))
5857
) {
5958
state.selectFocusedDate();
6059
}
6160
};
6261

6362
useEvent(windowRef, 'pointerup', endDragging);
64-
useEvent(windowRef, 'pointercancel', endDragging);
6563

6664
// Also stop range selection on blur, e.g. tabbing away from the calendar.
6765
res.calendarProps.onBlur = e => {
66+
if (!ref.current) {
67+
return;
68+
}
6869
if ((!e.relatedTarget || !ref.current.contains(e.relatedTarget)) && state.anchorDate) {
6970
state.selectFocusedDate();
7071
}

packages/@react-spectrum/calendar/test/RangeCalendar.test.js

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ function type(key) {
2828
}
2929

3030
describe('RangeCalendar', () => {
31+
/** @type {ReturnType<typeof userEvent['setup']>} */
3132
let user;
3233
beforeAll(() => {
3334
user = userEvent.setup({delay: null, pointerMap});
@@ -691,6 +692,154 @@ describe('RangeCalendar', () => {
691692
expect(start).toEqual(new CalendarDate(2019, 6, 15));
692693
expect(end).toEqual(new CalendarDate(2019, 6, 20));
693694
});
695+
696+
it('allow touch-scroll thought calendar by onto a day and then finalize selection', async () => {
697+
let onChange = jest.fn();
698+
699+
let {getAllByLabelText, getByText} = render(
700+
<RangeCalendar
701+
defaultValue={{
702+
start: new CalendarDate(2019, 6, 15),
703+
end: new CalendarDate(2019, 6, 20)
704+
}}
705+
onChange={onChange} />
706+
);
707+
708+
// start a range selection
709+
await user.click(getByText('23'));
710+
let selectedDates = getAllByLabelText('selected', {exact: false});
711+
expect(selectedDates[0].textContent).toBe('23');
712+
expect(selectedDates[selectedDates.length - 1].textContent).toBe('23');
713+
expect(onChange).toHaveBeenCalledTimes(0);
714+
715+
// scroll through the calendar
716+
// simulate touch scroll by touch-move on a day
717+
let dayEl = getByText('10');
718+
fireEvent.pointerDown(dayEl, {pointerType: 'touch'});
719+
fireEvent.pointerCancel(dayEl, {pointerType: 'touch'});
720+
721+
// finalize selection
722+
await user.click(getByText('25'));
723+
selectedDates = getAllByLabelText('selected', {exact: false});
724+
expect(selectedDates[0].textContent).toBe('23');
725+
expect(selectedDates[selectedDates.length - 1].textContent).toBe('25');
726+
expect(onChange).toHaveBeenCalledTimes(1);
727+
expect(onChange).toHaveBeenCalledWith({
728+
start: new CalendarDate(2019, 6, 23),
729+
end: new CalendarDate(2019, 6, 25)
730+
});
731+
});
732+
733+
it('allow touch-scroll thought calendar by onto a disabled day and then finalize selection', async () => {
734+
let onChange = jest.fn();
735+
736+
let {getAllByLabelText, getByText} = render(
737+
<RangeCalendar
738+
defaultValue={{
739+
start: new CalendarDate(2019, 6, 15),
740+
end: new CalendarDate(2019, 6, 20)
741+
}}
742+
onChange={onChange} />
743+
);
744+
745+
// start a range selection
746+
await user.click(getByText('23'));
747+
let selectedDates = getAllByLabelText('selected', {exact: false});
748+
expect(selectedDates[0].textContent).toBe('23');
749+
expect(selectedDates[selectedDates.length - 1].textContent).toBe('23');
750+
expect(onChange).toHaveBeenCalledTimes(0);
751+
752+
// scroll through the calendar
753+
// simulate touch scroll by touch-move on a disabled day (May 31)
754+
let disabledDayEl = getByText('31');
755+
fireEvent.pointerDown(disabledDayEl, {pointerType: 'touch'});
756+
fireEvent.pointerCancel(disabledDayEl, {pointerType: 'touch'});
757+
758+
// finalize selection
759+
await user.click(getByText('25'));
760+
selectedDates = getAllByLabelText('selected', {exact: false});
761+
expect(selectedDates[0].textContent).toBe('23');
762+
expect(selectedDates[selectedDates.length - 1].textContent).toBe('25');
763+
expect(onChange).toHaveBeenCalledTimes(1);
764+
expect(onChange).toHaveBeenCalledWith({
765+
start: new CalendarDate(2019, 6, 23),
766+
end: new CalendarDate(2019, 6, 25)
767+
});
768+
});
769+
770+
it('allow touch-scroll thought calendar by onto a weekday and then finalize selection', async () => {
771+
let onChange = jest.fn();
772+
773+
let {getAllByLabelText, getByText} = render(
774+
<RangeCalendar
775+
defaultValue={{
776+
start: new CalendarDate(2019, 6, 15),
777+
end: new CalendarDate(2019, 6, 20)
778+
}}
779+
onChange={onChange} />
780+
);
781+
782+
// start a range selection
783+
await user.click(getByText('23'));
784+
let selectedDates = getAllByLabelText('selected', {exact: false});
785+
expect(selectedDates[0].textContent).toBe('23');
786+
expect(selectedDates[selectedDates.length - 1].textContent).toBe('23');
787+
expect(onChange).toHaveBeenCalledTimes(0);
788+
789+
// scroll through the calendar
790+
// simulate touch scroll by touch-move on a weekday
791+
let weekdayEl = getByText('M');
792+
fireEvent.pointerDown(weekdayEl, {pointerType: 'touch'});
793+
fireEvent.pointerCancel(weekdayEl, {pointerType: 'touch'});
794+
795+
// finalize selection
796+
await user.click(getByText('25'));
797+
selectedDates = getAllByLabelText('selected', {exact: false});
798+
expect(selectedDates[0].textContent).toBe('23');
799+
expect(selectedDates[selectedDates.length - 1].textContent).toBe('25');
800+
expect(onChange).toHaveBeenCalledTimes(1);
801+
expect(onChange).toHaveBeenCalledWith({
802+
start: new CalendarDate(2019, 6, 23),
803+
end: new CalendarDate(2019, 6, 25)
804+
});
805+
});
806+
807+
it('allow touch-scroll thought calendar by onto a heading and then finalize selection', async () => {
808+
let onChange = jest.fn();
809+
810+
let {getAllByLabelText, getByText, getByRole} = render(
811+
<RangeCalendar
812+
defaultValue={{
813+
start: new CalendarDate(2019, 6, 15),
814+
end: new CalendarDate(2019, 6, 20)
815+
}}
816+
onChange={onChange} />
817+
);
818+
819+
// start a range selection
820+
await user.click(getByText('23'));
821+
let selectedDates = getAllByLabelText('selected', {exact: false});
822+
expect(selectedDates[0].textContent).toBe('23');
823+
expect(selectedDates[selectedDates.length - 1].textContent).toBe('23');
824+
expect(onChange).toHaveBeenCalledTimes(0);
825+
826+
// scroll through the calendar
827+
// simulate touch scroll by touch-move on heading
828+
let headingEl = getByRole('heading');
829+
fireEvent.pointerDown(headingEl, {pointerType: 'touch'});
830+
fireEvent.pointerCancel(headingEl, {pointerType: 'touch'});
831+
832+
// finalize selection
833+
await user.click(getByText('25'));
834+
selectedDates = getAllByLabelText('selected', {exact: false});
835+
expect(selectedDates[0].textContent).toBe('23');
836+
expect(selectedDates[selectedDates.length - 1].textContent).toBe('25');
837+
expect(onChange).toHaveBeenCalledTimes(1);
838+
expect(onChange).toHaveBeenCalledWith({
839+
start: new CalendarDate(2019, 6, 23),
840+
end: new CalendarDate(2019, 6, 25)
841+
});
842+
});
694843
});
695844

696845
it('clicking outside calendar commits selection', async () => {

0 commit comments

Comments
 (0)