Skip to content

Commit 1467af2

Browse files
committed
feat(rangeCalendar): allow to customise the behavior when the pointer is released outside of the component
1 parent 7332bd6 commit 1467af2

File tree

4 files changed

+36
-10
lines changed

4 files changed

+36
-10
lines changed

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

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ import {useRef} from 'react';
2222
* A range calendar displays one or more date grids and allows users to select a contiguous range of dates.
2323
*/
2424
export function useRangeCalendar<T extends DateValue>(props: AriaRangeCalendarProps<T>, state: RangeCalendarState, ref: RefObject<FocusableElement | null>): CalendarAria {
25-
let res = useCalendarBase(props, state);
25+
let {pointerUpOutsideAction = 'select', ...otherProps} = props;
26+
let res = useCalendarBase(otherProps, state);
2627

2728
// We need to ignore virtual pointer events from VoiceOver due to these bugs.
2829
// https://bugs.webkit.org/show_bug.cgi?id=222627
@@ -36,7 +37,13 @@ export function useRangeCalendar<T extends DateValue>(props: AriaRangeCalendarPr
3637
isVirtualClick.current = e.width === 0 && e.height === 0;
3738
});
3839

39-
// Stop range selection when pressing or releasing a pointer outside the calendar body,
40+
const pointerUpOutsideActionMapping = {
41+
clear: () => state.clearSelection(),
42+
reset: () => state.setAnchorDate(null),
43+
select: () => state.selectFocusedDate()
44+
};
45+
46+
// Execute method corresponding to `pointerUpOutsideAction` when pressing or releasing a pointer outside the calendar body,
4047
// except when pressing the next or previous buttons to switch months.
4148
let endDragging = (e: PointerEvent) => {
4249
if (isVirtualClick.current) {
@@ -55,19 +62,20 @@ export function useRangeCalendar<T extends DateValue>(props: AriaRangeCalendarPr
5562
ref.current.contains(document.activeElement) &&
5663
(!ref.current.contains(target) || !target.closest('button, [role="button"]'))
5764
) {
58-
state.selectFocusedDate();
65+
pointerUpOutsideActionMapping[pointerUpOutsideAction]();
5966
}
6067
};
6168

6269
useEvent(windowRef, 'pointerup', endDragging);
6370

64-
// Also stop range selection on blur, e.g. tabbing away from the calendar.
71+
// Also execute method corresponding to `pointerUpOutsideAction` on blur,
72+
// e.g. tabbing away from the calendar.
6573
res.calendarProps.onBlur = e => {
6674
if (!ref.current) {
6775
return;
6876
}
6977
if ((!e.relatedTarget || !ref.current.contains(e.relatedTarget)) && state.anchorDate) {
70-
state.selectFocusedDate();
78+
pointerUpOutsideActionMapping[pointerUpOutsideAction]();
7179
}
7280
};
7381

packages/@react-stately/calendar/src/types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,5 +122,7 @@ export interface RangeCalendarState extends CalendarStateBase {
122122
/** Whether the user is currently dragging over the calendar. */
123123
readonly isDragging: boolean,
124124
/** Sets whether the user is dragging over the calendar. */
125-
setDragging(isDragging: boolean): void
125+
setDragging(isDragging: boolean): void,
126+
/** Clears the current selection. */
127+
clearSelection(): void
126128
}

packages/@react-stately/calendar/src/useRangeCalendarState.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,11 @@ export function useRangeCalendarState<T extends DateValue = DateValue>(props: Ra
191191
return calendar.isInvalid(date) || isInvalid(date, availableRangeRef.current?.start, availableRangeRef.current?.end);
192192
},
193193
isDragging,
194-
setDragging
194+
setDragging,
195+
clearSelection() {
196+
setAnchorDate(null);
197+
setValue(null);
198+
}
195199
};
196200
}
197201

packages/@react-types/calendar/src/index.d.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,8 @@ export interface CalendarPropsBase {
6767
* The day that starts the week.
6868
*/
6969
firstDayOfWeek?: 'sun' | 'mon' | 'tue' | 'wed' | 'thu' | 'fri' | 'sat',
70-
/**
71-
* Determines the alignment of the visible months on initial render based on the current selection or current date if there is no selection.
70+
/**
71+
* Determines the alignment of the visible months on initial render based on the current selection or current date if there is no selection.
7272
* @default 'center'
7373
*/
7474
selectionAlignment?: 'start' | 'center' | 'end'
@@ -86,7 +86,19 @@ export interface RangeCalendarProps<T extends DateValue> extends CalendarPropsBa
8686

8787
export interface AriaCalendarProps<T extends DateValue> extends CalendarProps<T>, DOMProps, AriaLabelingProps {}
8888

89-
export interface AriaRangeCalendarProps<T extends DateValue> extends RangeCalendarProps<T>, DOMProps, AriaLabelingProps {}
89+
export interface AriaRangeCalendarProps<T extends DateValue> extends RangeCalendarProps<T>, DOMProps, AriaLabelingProps {
90+
/**
91+
* Controls the behavior when a pointer is released outside the calendar:
92+
*
93+
* - `clear`: clear the currently selected range of dates.
94+
*
95+
* - `reset`: reset the selection to the previously selected range of dates.
96+
*
97+
* - `select`: select the currently hovered range of dates.
98+
* @default 'select'
99+
*/
100+
pointerUpOutsideAction?: 'clear' | 'reset' | 'select'
101+
}
90102

91103
export type PageBehavior = 'single' | 'visible';
92104

0 commit comments

Comments
 (0)