Repository: Hacker0x01/react-datepicker
Pull request #4553 (2024-03-01 10:37:41)

feat : add usePointerEvent props
I want to fix a [bug](https://github.com/Hacker0x01/react-datepicker/pull/4542#issue-2159347503) I reported in a previous PR,
Inspired by the [comments](https://github.com/Hacker0x01/react-datepicker/pull/4542#discussion_r1506667247) in the previous PR, we have modified the policy to allow users to freely choose by Props.


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/Hacker0x01/react-datepicker/pull/4553?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Hacker0x01) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 95.57%. Comparing base [(`5f20755`)](https://app.codecov.io/gh/Hacker0x01/react-datepicker/commit/5f2075599d1a9d0c5b845674870bed3fba24c626?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Hacker0x01) to head [(`685f0be`)](https://app.codecov.io/gh/Hacker0x01/react-datepicker/pull/4553?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Hacker0x01).
> Report is 12 commits behind head on main.


<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #4553      +/-   ##
==========================================
+ Coverage   95.51%   95.57%   +0.05%     
==========================================
  Files          29       29              
  Lines        2566     2576      +10     
  Branches     1053     1067      +14     
==========================================
+ Hits         2451     2462      +11     
+ Misses        115      114       -1     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/Hacker0x01/react-datepicker/pull/4553?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Hacker0x01).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Hacker0x01).

yuki0410-dev: Thank you for your review.
We have made corrections in response to your comments.
yuki0410-dev: I have closed my previous [PR](https://github.com/Hacker0x01/react-datepicker/pull/4542).

Is there anything else I can do to get this PR merged?


Patch:
diff --git a/docs/datepicker.md b/docs/datepicker.md
index 171e2eb0..f162eacb 100644
--- a/docs/datepicker.md
+++ b/docs/datepicker.md
@@ -106,3 +106,4 @@ General datepicker component.
 | `icon`                       | `string` or `element`          |                 | Allows using a custom calendar icon. Accepts a string (icon class name) or a React component (e.g., custom SVG). If a string is passed, an `<i>` element is rendered with that string as its class name. If a React component is passed, it is rendered as-is.                                                       |
 | `calendarIconClassName`      | `string`                       |                 | Accepts a string that will be added as an additional CSS class to the calendar icon, allowing further styling customization.                                                                                                                                                                                         |
 | `showIcon`                   | `bool`                         | `true`          | Determines whether the calendar icon is displayed. Set to `true` to display the icon, and `false` to hide it. If `icon` prop is also provided, the custom icon will be displayed when `showIcon` is `true`.                                                                                                          |
+| `usePointerEvent`            | `bool`                         | false           | True if Pointer Events (e.g, onPointerEnter, onPointerLeave) are used internally instead of Mouse Events (e.g, onMouseEnter, onMouseLeave).                                                                                                                                                                          |
diff --git a/docs/index.md b/docs/index.md
index 713a9d84..70a1764b 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -160,3 +160,4 @@
 | `wrapperClassName`              | `string`                       |                            |             |
 | `yearDropdownItemNumber`        | `number`                       |                            |             |
 | `yearItemNumber`                | `number`                       | `DEFAULT_YEAR_ITEM_NUMBER` |             |
+| `usePointerEvent`               | `bool`                         | `false`                    |             |
diff --git a/docs/year.md b/docs/year.md
index db5abd3c..6ad1010e 100644
--- a/docs/year.md
+++ b/docs/year.md
@@ -26,3 +26,4 @@
 | `setPreSelection`             | `func`           |               |             |
 | `startDate`                   | `instanceOfDate` |               |             |
 | `yearItemNumber`              | `number`         |               |             |
+| `usePointerEvent`             | `bool`           |               |             |
diff --git a/src/calendar.jsx b/src/calendar.jsx
index e9f704bd..c66023b1 100644
--- a/src/calendar.jsx
+++ b/src/calendar.jsx
@@ -196,6 +196,7 @@ export default class Calendar extends React.Component {
     renderMonthContent: PropTypes.func,
     renderQuarterContent: PropTypes.func,
     renderYearContent: PropTypes.func,
+    usePointerEvent: PropTypes.bool,
     onDayMouseEnter: PropTypes.func,
     onMonthMouseLeave: PropTypes.func,
     onYearMouseEnter: PropTypes.func,
@@ -897,6 +898,7 @@ export default class Calendar extends React.Component {
             onDayClick={this.handleDayClick}
             handleOnKeyDown={this.props.handleOnDayKeyDown}
             handleOnMonthKeyDown={this.props.handleOnKeyDown}
+            usePointerEvent={this.props.usePointerEvent}
             onDayMouseEnter={this.handleDayMouseEnter}
             onMouseLeave={this.handleMonthMouseLeave}
             onWeekSelect={this.props.onWeekSelect}
diff --git a/src/day.jsx b/src/day.jsx
index 2861edb1..36feb1c8 100644
--- a/src/day.jsx
+++ b/src/day.jsx
@@ -32,6 +32,7 @@ export default class Day extends React.Component {
     shouldFocusDayInline: PropTypes.bool,
     month: PropTypes.number,
     onClick: PropTypes.func,
+    usePointerEvent: PropTypes.bool,
     onMouseEnter: PropTypes.func,
     preSelection: PropTypes.instanceOf(Date),
     selected: PropTypes.object,
@@ -434,7 +435,8 @@ export default class Day extends React.Component {
       className={this.getClassNames(this.props.day)}
       onKeyDown={this.handleOnKeyDown}
       onClick={this.handleClick}
-      onMouseEnter={this.handleMouseEnter}
+      onMouseEnter={!this.props.usePointerEvent ? this.handleMouseEnter : undefined}
+      onPointerEnter={this.props.usePointerEvent ? this.handleMouseEnter : undefined}
       tabIndex={this.getTabIndex()}
       aria-label={this.getAriaLabel()}
       role="option"
diff --git a/src/index.jsx b/src/index.jsx
index 86a3ee51..d3bb6205 100644
--- a/src/index.jsx
+++ b/src/index.jsx
@@ -134,6 +134,7 @@ export default class DatePicker extends React.Component {
       customTimeInput: null,
       calendarStartDay: undefined,
       toggleCalendarOnIconClick: false,
+      usePointerEvent: false,
     };
   }
 
@@ -311,6 +312,7 @@ export default class DatePicker extends React.Component {
     customTimeInput: PropTypes.element,
     weekAriaLabelPrefix: PropTypes.string,
     monthAriaLabelPrefix: PropTypes.string,
+    usePointerEvent: PropTypes.bool,
   };
 
   constructor(props) {
@@ -1152,6 +1154,7 @@ export default class DatePicker extends React.Component {
         isInputFocused={this.state.focused}
         customTimeInput={this.props.customTimeInput}
         setPreSelection={this.setPreSelection}
+        usePointerEvent={this.props.usePointerEvent}
       >
         {this.props.children}
       </WrappedCalendar>
diff --git a/src/month.jsx b/src/month.jsx
index 69845196..121c5071 100644
--- a/src/month.jsx
+++ b/src/month.jsx
@@ -86,6 +86,7 @@ export default class Month extends React.Component {
     maxDate: PropTypes.instanceOf(Date),
     minDate: PropTypes.instanceOf(Date),
     onDayClick: PropTypes.func,
+    usePointerEvent: PropTypes.bool,
     onDayMouseEnter: PropTypes.func,
     onMouseLeave: PropTypes.func,
     onWeekSelect: PropTypes.func,
@@ -311,6 +312,7 @@ export default class Month extends React.Component {
           day={currentWeekStart}
           month={utils.getMonth(this.props.day)}
           onDayClick={this.handleDayClick}
+          usePointerEvent={this.props.usePointerEvent}
           onDayMouseEnter={this.handleDayMouseEnter}
           onWeekSelect={this.props.onWeekSelect}
           formatWeekNumber={this.props.formatWeekNumber}
@@ -684,7 +686,8 @@ export default class Month extends React.Component {
 
               this.onMonthKeyDown(ev, m);
             }}
-            onMouseEnter={() => this.onMonthMouseEnter(m)}
+            onMouseEnter={!this.props.usePointerEvent ? () => this.onMonthMouseEnter(m) : undefined}
+            onPointerEnter={this.props.usePointerEvent ? () => this.onMonthMouseEnter(m) : undefined}
             tabIndex={this.getTabIndex(m)}
             className={this.getMonthClassNames(m)}
             role="option"
@@ -715,7 +718,8 @@ export default class Month extends React.Component {
             onKeyDown={(ev) => {
               this.onQuarterKeyDown(ev, q);
             }}
-            onMouseEnter={() => this.onQuarterMouseEnter(q)}
+            onMouseEnter={!this.props.usePointerEvent ? () => this.onQuarterMouseEnter(q) : undefined}
+            onPointerEnter={this.props.usePointerEvent ? () => this.onQuarterMouseEnter(q) : undefined}
             className={this.getQuarterClassNames(q)}
             aria-selected={this.isSelectedQuarter(day, q, selected)}
             tabIndex={this.getQuarterTabIndex(q)}
@@ -760,7 +764,8 @@ export default class Month extends React.Component {
     return (
       <div
         className={this.getClassNames()}
-        onMouseLeave={this.handleMouseLeave}
+        onMouseLeave={!this.props.usePointerEvent ? this.handleMouseLeave : undefined}
+        onPointerLeave={this.props.usePointerEvent ? this.handleMouseLeave : undefined}
         aria-label={`${ariaLabelPrefix} ${utils.formatDate(day, "yyyy-MM")}`}
         role="listbox"
       >
diff --git a/src/week.jsx b/src/week.jsx
index 3288e72b..6ec1fb5b 100644
--- a/src/week.jsx
+++ b/src/week.jsx
@@ -44,6 +44,7 @@ export default class Week extends React.Component {
     minDate: PropTypes.instanceOf(Date),
     month: PropTypes.number,
     onDayClick: PropTypes.func,
+    usePointerEvent: PropTypes.bool,
     onDayMouseEnter: PropTypes.func,
     onWeekSelect: PropTypes.func,
     preSelection: PropTypes.instanceOf(Date),
@@ -149,6 +150,7 @@ export default class Week extends React.Component {
             day={day}
             month={this.props.month}
             onClick={this.handleDayClick.bind(this, day)}
+            usePointerEvent={this.props.usePointerEvent}
             onMouseEnter={this.handleDayMouseEnter.bind(this, day)}
             minDate={this.props.minDate}
             maxDate={this.props.maxDate}
diff --git a/src/year.jsx b/src/year.jsx
index 3ef8da74..07c85119 100644
--- a/src/year.jsx
+++ b/src/year.jsx
@@ -17,6 +17,7 @@ export default class Year extends React.Component {
     inline: PropTypes.bool,
     maxDate: PropTypes.instanceOf(Date),
     minDate: PropTypes.instanceOf(Date),
+    usePointerEvent: PropTypes.bool,
     onYearMouseEnter: PropTypes.func.isRequired,
     onYearMouseLeave: PropTypes.func.isRequired,
     selectingDate: PropTypes.instanceOf(Date),
@@ -259,8 +260,10 @@ export default class Year extends React.Component {
           }}
           tabIndex={this.getYearTabIndex(y)}
           className={this.getYearClassNames(y)}
-          onMouseEnter={(ev) => onYearMouseEnter(ev, y)}
-          onMouseLeave={(ev) => onYearMouseLeave(ev, y)}
+          onMouseEnter={!this.props.usePointerEvent ? (ev) => onYearMouseEnter(ev, y) : undefined}
+          onPointerEnter={this.props.usePointerEvent ? (ev) => onYearMouseEnter(ev, y) : undefined}
+          onMouseLeave={!this.props.usePointerEvent ? (ev) => onYearMouseLeave(ev, y) : undefined}
+          onPointerLeave={this.props.usePointerEvent ? (ev) => onYearMouseLeave(ev, y) : undefined}
           key={y}
           aria-current={this.isCurrentYear(y) ? "date" : undefined}
         >
@@ -273,7 +276,8 @@ export default class Year extends React.Component {
       <div className={this.getYearContainerClassNames()}>
         <div
           className="react-datepicker__year-wrapper"
-          onMouseLeave={this.props.clearSelectingDate}
+          onMouseLeave={!this.props.usePointerEvent ? this.props.clearSelectingDate : undefined}
+          onPointerLeave={this.props.usePointerEvent ? this.props.clearSelectingDate : undefined}
         >
           {yearsList}
         </div>


Test patch:
diff --git a/test/calendar_test.test.js b/test/calendar_test.test.js
index 761d609b..d452add0 100644
--- a/test/calendar_test.test.js
+++ b/test/calendar_test.test.js
@@ -5,10 +5,9 @@
 import React from "react";
 import Calendar from "../src/calendar";
 import Month from "../src/month";
-import Day from "../src/day";
 import ReactDOM from "react-dom";
 import TestUtils from "react-dom/test-utils";
-import { render } from "@testing-library/react";
+import { render, fireEvent } from "@testing-library/react";
 import YearDropdown from "../src/year_dropdown";
 import MonthDropdown from "../src/month_dropdown";
 import MonthYearDropdown from "../src/month_year_dropdown";
@@ -859,21 +858,46 @@ describe("Calendar", () => {
     expect(weekLabel.at(0).text()).toBe("Foo");
   });
 
-  it("should track the currently hovered day", () => {
-    const calendar = mount(
+  it("should track the currently hovered day (Mouse Event)", () => {
+    const onDayMouseEnterSpy = jest.fn();
+
+    const { container } = render(
       <Calendar
         dateFormat={dateFormat}
         dropdownMode="scroll"
         onClickOutside={() => {}}
         onSelect={() => {}}
+        onDayMouseEnter={onDayMouseEnterSpy}
       />,
     );
-    const day = calendar.find(Day).first();
-    day.simulate("mouseenter");
-    const month = calendar.find(Month).first();
-    expect(month.prop("selectingDate")).toBeDefined();
-    expect(utils.isSameDay(month.prop("selectingDate"), day.prop("day"))).toBe(
-      true,
+
+    const day = container.querySelector(".react-datepicker__day");
+    fireEvent.mouseEnter(day);
+
+    expect(onDayMouseEnterSpy).toHaveBeenLastCalledWith(
+      utils.getStartOfWeek(utils.getStartOfMonth(utils.newDate())),
+    );
+  });
+
+  it("should track the currently hovered day (Pointer Event)", () => {
+    const onDayMouseEnterSpy = jest.fn();
+
+    const { container } = render(
+      <Calendar
+        dateFormat={dateFormat}
+        dropdownMode="scroll"
+        onClickOutside={() => {}}
+        onSelect={() => {}}
+        onDayMouseEnter={onDayMouseEnterSpy}
+        usePointerEvent
+      />,
+    );
+
+    const day = container.querySelector(".react-datepicker__day");
+    fireEvent.pointerEnter(day);
+
+    expect(onDayMouseEnterSpy).toHaveBeenLastCalledWith(
+      utils.getStartOfWeek(utils.getStartOfMonth(utils.newDate())),
     );
   });
 
diff --git a/test/datepicker_test.test.js b/test/datepicker_test.test.js
index da87c65b..dda639b3 100644
--- a/test/datepicker_test.test.js
+++ b/test/datepicker_test.test.js
@@ -2589,27 +2589,52 @@ describe("DatePicker", () => {
   });
 
   describe("Year picker", () => {
-    it("should call onYearMouseEnter and onYearMouseEnter", () => {
+    it("should call onYearMouseEnter and onYearMouseEnter (Mouse Event)", () => {
       const onYearMouseEnterSpy = jest.fn();
       const onYearMouseLeaveSpy = jest.fn();
-      const datePicker = mount(
+      const { container } = render(
+        <DatePicker
+          selected={new Date(2023, 0, 1)}
+          showYearPicker
+          onYearMouseEnter={onYearMouseEnterSpy}
+          onYearMouseLeave={onYearMouseLeaveSpy}
+        />,
+      );
+
+      const dateInput = container.querySelector("input");
+      fireEvent.focus(dateInput);
+      const selectedYear = container.querySelector(
+        ".react-datepicker__year-text--selected",
+      );
+
+      fireEvent.mouseEnter(selectedYear);
+      fireEvent.mouseLeave(selectedYear);
+
+      expect(onYearMouseEnterSpy).toHaveBeenCalled();
+      expect(onYearMouseLeaveSpy).toHaveBeenCalled();
+    });
+
+    it("should call onYearMouseEnter and onYearMouseEnter (Pointer Event)", () => {
+      const onYearMouseEnterSpy = jest.fn();
+      const onYearMouseLeaveSpy = jest.fn();
+      const { container } = render(
         <DatePicker
           selected={new Date(2023, 0, 1)}
           showYearPicker
           onYearMouseEnter={onYearMouseEnterSpy}
           onYearMouseLeave={onYearMouseLeaveSpy}
+          usePointerEvent
         />,
       );
 
-      const dateInputWrapper = datePicker.find("input");
-      dateInputWrapper.simulate("click");
-      const calendarWrapper = datePicker.find("Calendar");
-      const selectedYear = calendarWrapper.find(
+      const dateInput = container.querySelector("input");
+      fireEvent.focus(dateInput);
+      const selectedYear = container.querySelector(
         ".react-datepicker__year-text--selected",
       );
 
-      selectedYear.simulate("mouseenter");
-      selectedYear.simulate("mouseleave");
+      fireEvent.pointerEnter(selectedYear);
+      fireEvent.pointerLeave(selectedYear);
 
       expect(onYearMouseEnterSpy).toHaveBeenCalled();
       expect(onYearMouseLeaveSpy).toHaveBeenCalled();
diff --git a/test/day_test.test.js b/test/day_test.test.js
index be98cc85..6f7b768e 100644
--- a/test/day_test.test.js
+++ b/test/day_test.test.js
@@ -1,4 +1,5 @@
 import React from "react";
+import { render, fireEvent } from "@testing-library/react";
 import { es } from "date-fns/locale";
 import Day from "../src/day";
 import { mount, shallow } from "enzyme";
@@ -915,20 +916,30 @@ describe("Day", () => {
   });
 
   describe("mouse enter", () => {
-    var onMouseEnterCalled;
+    it("should call onMouseEnter if day is hovered", () => {
+      const onMouseEnterSpy = jest.fn();
 
-    function onMouseEnter() {
-      onMouseEnterCalled = true;
-    }
+      const day = newDate();
 
-    beforeEach(() => {
-      onMouseEnterCalled = false;
+      const { container } = render(
+        <Day day={day} onMouseEnter={onMouseEnterSpy} />,
+      );
+
+      fireEvent.mouseEnter(container.querySelector(".react-datepicker__day"));
+      expect(onMouseEnterSpy).toHaveBeenCalled();
     });
 
-    it("should call onMouseEnter if day is hovered", () => {
-      const shallowDay = renderDay(newDate(), { onMouseEnter });
-      shallowDay.find(".react-datepicker__day").simulate("mouseenter");
-      expect(onMouseEnterCalled).toBe(true);
+    it("should call onPointerEnter if day is hovered", () => {
+      const onMouseEnterSpy = jest.fn();
+
+      const day = newDate();
+
+      const { container } = render(
+        <Day day={day} onMouseEnter={onMouseEnterSpy} usePointerEvent />,
+      );
+
+      fireEvent.pointerEnter(container.querySelector(".react-datepicker__day"));
+      expect(onMouseEnterSpy).toHaveBeenCalled();
     });
   });
 
diff --git a/test/month_test.test.js b/test/month_test.test.js
index f053e4c9..b88d878c 100644
--- a/test/month_test.test.js
+++ b/test/month_test.test.js
@@ -130,19 +130,42 @@ describe("Month", () => {
     expect(mouseLeaveCalled).toBe(true);
   });
 
-  it("should call the provided onDayMouseEnter function", () => {
-    let dayMouseEntered = null;
+  it("should call the provided onDayMouseEnter (Mouse Event) function", () => {
+    const onDayMouseEnterSpy = jest.fn();
 
-    function onDayMouseEnter(day) {
-      dayMouseEntered = day;
-    }
+    const startDay = utils.newDate();
 
-    const month = mount(
-      <Month day={utils.newDate()} onDayMouseEnter={onDayMouseEnter} />,
+    const { container } = render(
+      <Month day={startDay} onDayMouseEnter={onDayMouseEnterSpy} />,
+    );
+
+    const day = container.querySelector(".react-datepicker__day");
+    fireEvent.mouseEnter(day);
+
+    expect(onDayMouseEnterSpy).toHaveBeenLastCalledWith(
+      utils.getStartOfWeek(utils.getStartOfMonth(startDay)),
+    );
+  });
+
+  it("should call the provided onDayMouseEnter (Pointer Event) function", () => {
+    const onDayMouseEnterSpy = jest.fn();
+
+    const startDay = utils.newDate();
+
+    const { container } = render(
+      <Month
+        day={startDay}
+        onDayMouseEnter={onDayMouseEnterSpy}
+        usePointerEvent
+      />,
+    );
+
+    const day = container.querySelector(".react-datepicker__day");
+    fireEvent.pointerEnter(day);
+
+    expect(onDayMouseEnterSpy).toHaveBeenLastCalledWith(
+      utils.getStartOfWeek(utils.getStartOfMonth(startDay)),
     );
-    const day = month.find(Day).first();
-    day.simulate("mouseenter");
-    expect(utils.isSameDay(day.prop("day"), dayMouseEntered)).toBe(true);
   });
 
   it("should use its month order in handleDayClick", () => {
diff --git a/test/week_test.test.js b/test/week_test.test.js
index cde8f4e7..117e89ef 100644
--- a/test/week_test.test.js
+++ b/test/week_test.test.js
@@ -1,4 +1,5 @@
 import React from "react";
+import { render, fireEvent } from "@testing-library/react";
 import Week from "../src/week";
 import WeekNumber from "../src/week_number";
 import Day from "../src/day";
@@ -161,20 +162,38 @@ describe("Week", () => {
     expect(weekNumberElement.prop("weekNumber")).toBe(9);
   });
 
-  it("should call the provided onDayMouseEnter function", () => {
-    let dayMouseEntered = null;
+  it("should call the provided onDayMouseEnter (Mouse Event) function", () => {
+    const onDayMouseEnterSpy = jest.fn();
+    const weekStart = utils.newDate();
+    const { container } = render(
+      <Week day={weekStart} onDayMouseEnter={onDayMouseEnterSpy} />,
+    );
 
-    function onDayMouseEnter(day) {
-      dayMouseEntered = day;
-    }
+    const day = container.querySelector(".react-datepicker__day");
+    fireEvent.mouseEnter(day);
 
+    expect(onDayMouseEnterSpy).toHaveBeenLastCalledWith(
+      utils.getStartOfWeek(weekStart),
+    );
+  });
+
+  it("should call the provided onDayMouseEnter (Pointer Event) function", () => {
+    const onDayMouseEnterSpy = jest.fn();
     const weekStart = utils.newDate();
-    const week = shallow(
-      <Week day={weekStart} onDayMouseEnter={onDayMouseEnter} />,
+    const { container } = render(
+      <Week
+        day={weekStart}
+        onDayMouseEnter={onDayMouseEnterSpy}
+        usePointerEvent
+      />,
+    );
+
+    const day = container.querySelector(".react-datepicker__day");
+    fireEvent.pointerEnter(day);
+
+    expect(onDayMouseEnterSpy).toHaveBeenLastCalledWith(
+      utils.getStartOfWeek(weekStart),
     );
-    const day = week.find(Day).first();
-    day.simulate("mouseenter");
-    expect(day.prop("day")).toEqual(dayMouseEntered);
   });
 
   describe("handleWeekClick", () => {