Repository: Hacker0x01/react-datepicker
Pull request #4654 (2024-04-02 07:58:23)

(feature) swapRange optional prop
---
name: `swapRange` prop
about: Gives more flexibility on input flow
---

**Problem**
When a user works with extensive date ranges, it could be tedious to scroll back and forth if the user makes a mistake. 

**Changes**
The change adds an optional property `swapRange` to `DatePicker` component that allows the user to swap dates if we want the end user to pick dates in any order. This is a real request from the end users, and it will be handy if you work with big date ranges. 

## Screenshots
![Знімок екрана 2024-04-02 о 10 51 56](https://github.com/Hacker0x01/react-datepicker/assets/29524085/ad6d0637-3e05-4a5e-97dd-e95a8b2281c4)



## Contribution checklist
- [x] I have followed the [contributing guidelines](https://github.com/Hacker0x01/react-datepicker/blob/main/CONTRIBUTING.md).
- [x] I have added sufficient test coverage for my changes.
- [x] I have formatted my code with Prettier and checked for linting issues with ESLint for code readability.



Patch:
diff --git a/docs-site/src/components/Examples/index.js b/docs-site/src/components/Examples/index.js
index fef9e6f2..b7120c0e 100644
--- a/docs-site/src/components/Examples/index.js
+++ b/docs-site/src/components/Examples/index.js
@@ -105,6 +105,7 @@ import SelectsMultiple from "../../examples/selectsMultiple";
 import CalendarIconExternal from "../../examples/calendarIconExternal";
 import CalendarIconSvgIcon from "../../examples/calendarIconSvgIcon";
 import ToggleCalendarOnIconClick from "../../examples/toggleCalendarOnIconClick";
+import SwapRange from "../../examples/rangeSwapRange";
 
 import "./style.scss";
 import "react-datepicker/dist/react-datepicker.css";
@@ -461,6 +462,12 @@ export default class exampleComponents extends React.Component {
       title: "Range Quarter Picker for one quarter picker",
       component: RangeQuarterPickerSelectsRange,
     },
+    {
+      title: "Range Swap Range",
+      description:
+        "Swap the start and end date if the end date is before the start date in a pick sequence.",
+      component: SwapRange,
+    },
     {
       title: "Read only datepicker",
       component: ReadOnly,
diff --git a/docs-site/src/examples/rangeSwapRange.js b/docs-site/src/examples/rangeSwapRange.js
new file mode 100644
index 00000000..d78e30da
--- /dev/null
+++ b/docs-site/src/examples/rangeSwapRange.js
@@ -0,0 +1,22 @@
+() => {
+  const [startDate, setStartDate] = useState(new Date());
+  const [endDate, setEndDate] = useState(null);
+  const onChange = (dates) => {
+    const [start, end] = dates;
+    setStartDate(start);
+    setEndDate(end);
+  };
+  return (
+    <DatePicker
+      swapRange
+      selected={startDate}
+      onChange={onChange}
+      startDate={startDate}
+      endDate={endDate}
+      excludeDates={[addDays(new Date(), 1), addDays(new Date(), 5)]}
+      selectsRange
+      selectsDisabledDaysInRange
+      inline
+    />
+  );
+};
diff --git a/src/index.jsx b/src/index.jsx
index 03d84ba3..5168b25d 100644
--- a/src/index.jsx
+++ b/src/index.jsx
@@ -115,6 +115,7 @@ export default class DatePicker extends React.Component {
       showQuarterYearPicker: false,
       showWeekPicker: false,
       strictParsing: false,
+      swapRange: false,
       timeIntervals: 30,
       timeCaption: "Time",
       previousMonthAriaLabel: "Previous Month",
@@ -253,6 +254,7 @@ export default class DatePicker extends React.Component {
     showWeekNumbers: PropTypes.bool,
     showYearDropdown: PropTypes.bool,
     strictParsing: PropTypes.bool,
+    swapRange: PropTypes.bool,
     forceShowMonthNavigation: PropTypes.bool,
     showDisabledMonthNavigation: PropTypes.bool,
     startDate: PropTypes.instanceOf(Date),
@@ -634,6 +636,7 @@ export default class DatePicker extends React.Component {
       selectsMultiple,
       selectedDates,
       minTime,
+      swapRange,
     } = this.props;
 
     if (
@@ -690,7 +693,11 @@ export default class DatePicker extends React.Component {
           if (changedDate === null) {
             onChange([null, null], event);
           } else if (isDateBefore(changedDate, startDate)) {
-            onChange([changedDate, null], event);
+            if (swapRange) {
+              onChange([changedDate, startDate], event);
+            } else {
+              onChange([changedDate, null], event);
+            }
           } else {
             onChange([startDate, changedDate], event);
           }


Test patch:
diff --git a/test/datepicker_test.test.js b/test/datepicker_test.test.js
index bd2c7b37..bc51ebcb 100644
--- a/test/datepicker_test.test.js
+++ b/test/datepicker_test.test.js
@@ -2217,6 +2217,42 @@ describe("DatePicker", () => {
       );
       expect(endDate).toBeNull();
     });
+
+    it("should swap dates of range when endDate set before startDate", () => {
+      const selected = utils.newDate("2024-04-03");
+      const selectedPrevious = utils.subDays(selected, 3);
+      let [startDate, endDate] = [selected, null];
+      const onChange = (dates = []) => {
+        [startDate, endDate] = dates;
+      };
+      const { container } = render(
+        <DatePicker
+          swapRange
+          selected={startDate}
+          onChange={onChange}
+          startDate={startDate}
+          endDate={endDate}
+          selectsRange
+          inline
+        />,
+      );
+
+      let selectedDay = findSelectedDay(container, selectedPrevious);
+      // Ensure that we're dealing with a date at the beginning of the month
+      if (!selectedDay) {
+        // If it's the beginning of the month & if the selectedPrevious is not being displayed, navigate to the previous month and reselect the selectedPrevious
+        goToLastMonth(container);
+        selectedDay = findSelectedDay(container, selectedPrevious);
+      }
+
+      fireEvent.click(selectedDay);
+      expect(utils.formatDate(startDate, "yyyy-MM-dd")).toBe(
+        utils.formatDate(selectedPrevious, "yyyy-MM-dd"),
+      );
+      expect(utils.formatDate(endDate, "yyyy-MM-dd")).toBe(
+        utils.formatDate(selected, "yyyy-MM-dd"),
+      );
+    });
   });
 
   describe("selectsRange without inline", () => {