diff --git a/CHANGELOG.md b/CHANGELOG.md
index d80ac78e3..c5fff7dc6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -20,7 +20,7 @@ npm install react-day-picker@latest
- Added support for [UTC dates](https://daypicker.dev/docs/localization#utc-dates) and [Jalali Calendar](https://daypicker.dev/docs/localization#jalali-calendar).
- [Enhanced accessibility](https://daypicker.dev/docs/accessibility) to better comply with [WCAG 2.1](https://www.w3.org/TR/WCAG21/) recommendations.
- [Simplified styles](https://daypicker.dev/docs/styling) and new CSS variables for easier customization.
-- Improved selection logic for [range mode](https://daypicker.dev/docs/selection-modes).
+- New `excludeDisabled` prop for [range mode](https://daypicker.dev/docs/selection-modes#exclude-disabled).
- New `dropdown-years` and `dropdown-months` caption layouts.
- New `hideWeekdayRow` and `hideNavigation` props.
- Updated for a complete [custom components](https://daypicker.dev/guides/custom-components) support.
diff --git a/examples/ModifiersDisabled.test.tsx b/examples/ModifiersDisabled.test.tsx
index ecd5c3705..91bcae04f 100644
--- a/examples/ModifiersDisabled.test.tsx
+++ b/examples/ModifiersDisabled.test.tsx
@@ -5,13 +5,20 @@ import { render } from "@/test/render";
import { ModifiersDisabled } from "./ModifiersDisabled";
+const today = new Date(2024, 6, 22);
+
+beforeAll(() => jest.setSystemTime(today));
+afterAll(() => jest.useRealTimers());
+
const days = [
- new Date(2024, 5, 2),
- new Date(2024, 5, 9),
- new Date(2024, 5, 29)
+ new Date(2024, 6, 6),
+ new Date(2024, 6, 13),
+ new Date(2024, 6, 20),
+ new Date(2024, 6, 27)
];
test.each(days)("the day %s should be disabled", (day) => {
render();
+ // return all month's
expect(dateButton(day)).toBeDisabled();
});
diff --git a/examples/ModifiersDisabled.tsx b/examples/ModifiersDisabled.tsx
index ac611821b..b890d4eeb 100644
--- a/examples/ModifiersDisabled.tsx
+++ b/examples/ModifiersDisabled.tsx
@@ -3,11 +3,5 @@ import React from "react";
import { DayPicker } from "react-day-picker";
export function ModifiersDisabled() {
- return (
-
- );
+ return ;
}
diff --git a/examples/RangeExcludeDisabled.tsx b/examples/RangeExcludeDisabled.tsx
new file mode 100644
index 000000000..fee7588de
--- /dev/null
+++ b/examples/RangeExcludeDisabled.tsx
@@ -0,0 +1,9 @@
+import React from "react";
+
+import { DayPicker } from "react-day-picker";
+
+export function RangeExcludeDisabled() {
+ return (
+
+ );
+}
diff --git a/examples/index.ts b/examples/index.ts
index 7bb79cad5..ba806e736 100644
--- a/examples/index.ts
+++ b/examples/index.ts
@@ -43,6 +43,7 @@ export * from "./MultipleMonthsPaged";
export * from "./NumberingSystem";
export * from "./OutsideDays";
export * from "./Range";
+export * from "./RangeExcludeDisabled";
export * from "./RangeMinMax";
export * from "./RangeShiftKey";
export * from "./Rtl";
diff --git a/src/selection/useRange.test.tsx b/src/selection/useRange.test.tsx
new file mode 100644
index 000000000..476b20f5a
--- /dev/null
+++ b/src/selection/useRange.test.tsx
@@ -0,0 +1,114 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import { act, renderHook } from "@/test/render";
+
+import { dateLib } from "../lib";
+
+import { useRange } from "./useRange";
+
+describe("useRange", () => {
+ test("initialize with initiallySelected date range", () => {
+ const initiallySelected = {
+ from: new Date(2023, 6, 1),
+ to: new Date(2023, 6, 5)
+ };
+ const { result } = renderHook(() =>
+ useRange(
+ { mode: "range", selected: initiallySelected, required: false },
+ dateLib
+ )
+ );
+
+ expect(result.current.selected).toEqual(initiallySelected);
+ });
+
+ test("update the selected range on select", () => {
+ const initiallySelected = {
+ from: new Date(2023, 6, 1),
+ to: new Date(2023, 6, 5)
+ };
+ const { result } = renderHook(() =>
+ useRange(
+ { mode: "range", selected: initiallySelected, required: false },
+ dateLib
+ )
+ );
+
+ act(() => {
+ result.current.select?.(new Date(2023, 6, 10), {}, {} as any);
+ });
+
+ expect(result.current.selected).toEqual({
+ from: new Date(2023, 6, 1),
+ to: new Date(2023, 6, 10)
+ });
+ });
+
+ test("reset range if new range exceeds max days", () => {
+ const { result } = renderHook(() =>
+ useRange(
+ {
+ mode: "range",
+ selected: undefined,
+ required: false,
+ max: 5
+ },
+ dateLib
+ )
+ );
+
+ act(() => {
+ result.current.select?.(new Date(2023, 6, 1), {}, {} as any);
+ result.current.select?.(new Date(2023, 6, 10), {}, {} as any);
+ });
+
+ expect(result.current.selected).toEqual({
+ from: new Date(2023, 6, 10),
+ to: undefined
+ });
+ });
+
+ test("reset range if new range is less than min days", () => {
+ const { result } = renderHook(() =>
+ useRange(
+ { mode: "range", selected: undefined, required: false, min: 5 },
+ dateLib
+ )
+ );
+
+ act(() => {
+ result.current.select?.(new Date(2023, 6, 1), {}, {} as any);
+ result.current.select?.(new Date(2023, 6, 3), {}, {} as any);
+ });
+
+ expect(result.current.selected).toEqual({
+ from: new Date(2023, 6, 3),
+ to: undefined
+ });
+ });
+
+ test("exclude disabled dates when selecting range", () => {
+ const disabled = [{ from: new Date(2023, 6, 5), to: new Date(2023, 6, 7) }];
+ const { result } = renderHook(() =>
+ useRange(
+ {
+ mode: "range",
+ selected: undefined,
+ required: false,
+ excludeDisabled: true,
+ disabled
+ },
+ dateLib
+ )
+ );
+
+ act(() => {
+ result.current.select?.(new Date(2023, 6, 1), {}, {} as any);
+ result.current.select?.(new Date(2023, 6, 10), {}, {} as any);
+ });
+
+ expect(result.current.selected).toEqual({
+ from: new Date(2023, 6, 10),
+ to: undefined
+ });
+ });
+});
diff --git a/src/selection/useRange.tsx b/src/selection/useRange.tsx
index b8f550eba..bb2ea0ed9 100644
--- a/src/selection/useRange.tsx
+++ b/src/selection/useRange.tsx
@@ -18,6 +18,7 @@ export function useRange(
const {
mode,
disabled,
+ excludeDisabled,
selected: initiallySelected,
required,
onSelect
@@ -74,7 +75,11 @@ export function useRange(
let newDate = newRange.from;
while (dateLib.differenceInCalendarDays(newRange.to, newDate) > 0) {
newDate = dateLib.addDays(newDate, 1);
- if (disabled && dateMatchModifiers(newDate, disabled, dateLib)) {
+ if (
+ excludeDisabled &&
+ disabled &&
+ dateMatchModifiers(newDate, disabled, dateLib)
+ ) {
newRange.from = triggerDate;
newRange.to = undefined;
break;
diff --git a/src/types/props.ts b/src/types/props.ts
index 0c5d04de2..eb64f468d 100644
--- a/src/types/props.ts
+++ b/src/types/props.ts
@@ -546,6 +546,12 @@ export interface PropsRangeRequired {
mode: "range";
required: true;
disabled?: Matcher | Matcher[] | undefined;
+ /**
+ * When `true`, the range will reset when including a disabled day.
+ *
+ * @since V9.0.2
+ */
+ excludeDisabled?: boolean | undefined;
/** The selected range. */
selected: DateRange;
/** Event handler when a range is selected. */
@@ -569,6 +575,12 @@ export interface PropsRange {
mode: "range";
required?: false | undefined;
disabled?: Matcher | Matcher[] | undefined;
+ /**
+ * When `true`, the range will reset when including a disabled day.
+ *
+ * @since V9.0.2
+ */
+ excludeDisabled?: boolean | undefined;
/** The selected range. */
selected?: DateRange | undefined;
/** Event handler when the selection changes. */
diff --git a/test/render.tsx b/test/render.tsx
index de85b0ce1..4ccc6848f 100644
--- a/test/render.tsx
+++ b/test/render.tsx
@@ -1 +1,7 @@
-export { screen, act, within, render } from "@testing-library/react";
+export {
+ screen,
+ act,
+ within,
+ render,
+ renderHook
+} from "@testing-library/react";
diff --git a/website/docs/docs/selection-modes.mdx b/website/docs/docs/selection-modes.mdx
index 3180208fb..0ba52409e 100644
--- a/website/docs/docs/selection-modes.mdx
+++ b/website/docs/docs/selection-modes.mdx
@@ -194,17 +194,38 @@ export function RangeMinMax() {
## Disabling Dates
-To disable specific days, use the `disabled` prop. The prop accepts a [`Matcher`](../api/type-aliases/Matcher.md) or an array of matchers that can be used to make some days not selectable.
+To disable specific days, use the `disabled` prop. Disabled dates cannot be selected.
+
+The prop accepts a [`Matcher`](../api/type-aliases/Matcher.md) or an array of matchers that can be used to make some days not selectable.
+
+```tsx
+// disable today
+
+
+// disable weekends
+
+```
+
+
+
+
+
+### Excluding Disabled Dates from Range {#exclude-disabled}
+
+When using the `range` mode, disabled dates will be included in the selected range. Use the `excludeDisabled` prop to prevent this behavior. The range will reset when a disabled date is included.
```tsx
```
-
+
## Customizing Selections