diff --git a/docs/datepicker.md b/docs/datepicker.md index 171e2eb013..f162eacbab 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 `` 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 713a9d847b..70a1764b20 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 db5abd3cfe..6ad1010eb5 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 e9f704bd4a..c66023b167 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 2861edb13c..36feb1c8ba 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 daa1462f7e..f07e07b793 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) { @@ -1139,6 +1141,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} diff --git a/src/month.jsx b/src/month.jsx index 6984519643..121c5071c6 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 (
diff --git a/src/week.jsx b/src/week.jsx index 3288e72b64..6ec1fb5bfa 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 3ef8da74e0..07c8511917 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 {
{yearsList}
diff --git a/test/calendar_test.test.js b/test/calendar_test.test.js index 820f328033..cb713b1088 100644 --- a/test/calendar_test.test.js +++ b/test/calendar_test.test.js @@ -858,7 +858,7 @@ describe("Calendar", () => { expect(weekLabel.at(0).text()).toBe("Foo"); }); - it("should track the currently hovered day", () => { + it("should track the currently hovered day (Mouse Event)", () => { const calendar = mount( { ); }); + it("should track the currently hovered day (Pointer Event)", () => { + const calendar = mount( + {}} + onSelect={() => {}} + usePointerEvent + />, + ); + const day = calendar.find(Day).first(); + day.simulate("pointerenter"); + const month = calendar.find(Month).first(); + expect(month.prop("selectingDate")).toBeDefined(); + expect(utils.isSameDay(month.prop("selectingDate"), day.prop("day"))).toBe( + true, + ); + }); + it("should clear the hovered day when the mouse leaves", () => { const calendar = mount( { }); 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( @@ -2614,5 +2614,32 @@ describe("DatePicker", () => { expect(onYearMouseEnterSpy).toHaveBeenCalled(); expect(onYearMouseLeaveSpy).toHaveBeenCalled(); }); + + it("should call onYearMouseEnter and onYearMouseEnter (Pointer Event)", () => { + const onYearMouseEnterSpy = jest.fn(); + const onYearMouseLeaveSpy = jest.fn(); + const datePicker = mount( + , + ); + + const dateInputWrapper = datePicker.find("input"); + dateInputWrapper.simulate("click"); + const calendarWrapper = datePicker.find("Calendar"); + const selectedYear = calendarWrapper.find( + ".react-datepicker__year-text--selected", + ); + + selectedYear.simulate("pointerenter"); + selectedYear.simulate("pointerleave"); + + expect(onYearMouseEnterSpy).toHaveBeenCalled(); + expect(onYearMouseLeaveSpy).toHaveBeenCalled(); + }); }); }); diff --git a/test/day_test.test.js b/test/day_test.test.js index be98cc8552..428cb15a3a 100644 --- a/test/day_test.test.js +++ b/test/day_test.test.js @@ -930,6 +930,15 @@ describe("Day", () => { shallowDay.find(".react-datepicker__day").simulate("mouseenter"); expect(onMouseEnterCalled).toBe(true); }); + + it("should call onPointerEnter if day is hovered", () => { + const shallowDay = renderDay(newDate(), { + onMouseEnter, + usePointerEvent: true, + }); + shallowDay.find(".react-datepicker__day").simulate("pointerenter"); + expect(onMouseEnterCalled).toBe(true); + }); }); describe("for a start date picker with selectsRange prop", () => { diff --git a/test/month_test.test.js b/test/month_test.test.js index f053e4c9a0..8998f61936 100644 --- a/test/month_test.test.js +++ b/test/month_test.test.js @@ -130,7 +130,7 @@ describe("Month", () => { expect(mouseLeaveCalled).toBe(true); }); - it("should call the provided onDayMouseEnter function", () => { + it("should call the provided onDayMouseEnter (Mouse Event) function", () => { let dayMouseEntered = null; function onDayMouseEnter(day) { @@ -145,6 +145,25 @@ describe("Month", () => { expect(utils.isSameDay(day.prop("day"), dayMouseEntered)).toBe(true); }); + it("should call the provided onDayMouseEnter (Pointer Event) function", () => { + let dayMouseEntered = null; + + function onDayMouseEnter(day) { + dayMouseEntered = day; + } + + const month = mount( + , + ); + const day = month.find(Day).first(); + day.simulate("pointerenter"); + expect(utils.isSameDay(day.prop("day"), dayMouseEntered)).toBe(true); + }); + it("should use its month order in handleDayClick", () => { const order = 2; let orderValueMatched = false; diff --git a/test/week_test.test.js b/test/week_test.test.js index cde8f4e7b3..e3384b666c 100644 --- a/test/week_test.test.js +++ b/test/week_test.test.js @@ -2,7 +2,7 @@ import React from "react"; import Week from "../src/week"; import WeekNumber from "../src/week_number"; import Day from "../src/day"; -import { shallow } from "enzyme"; +import { shallow, mount } from "enzyme"; import * as utils from "../src/date_utils"; describe("Week", () => { @@ -161,7 +161,7 @@ describe("Week", () => { expect(weekNumberElement.prop("weekNumber")).toBe(9); }); - it("should call the provided onDayMouseEnter function", () => { + it("should call the provided onDayMouseEnter (Mouse Event) function", () => { let dayMouseEntered = null; function onDayMouseEnter(day) { @@ -169,7 +169,7 @@ describe("Week", () => { } const weekStart = utils.newDate(); - const week = shallow( + const week = mount( , ); const day = week.find(Day).first(); @@ -177,6 +177,27 @@ describe("Week", () => { expect(day.prop("day")).toEqual(dayMouseEntered); }); + it("should call the provided onDayMouseEnter (Pointer Event) function", () => { + let dayMouseEntered = null; + + function onDayMouseEnter(day) { + dayMouseEntered = day; + } + + const weekStart = utils.newDate(); + // NOTE: `shallow` cannot correctly perform `simulate("pointerenter")`, so `mount` is used + const week = mount( + , + ); + const day = week.find(Day).first(); + day.simulate("pointerenter"); + expect(day.prop("day")).toEqual(dayMouseEntered); + }); + describe("handleWeekClick", () => { it("should call onWeekSelect prop with correct arguments", () => { const onWeekSelect = jest.fn();