Skip to content

Commit

Permalink
Merge pull request #4553 from yuki0410-dev/feat/usePointerEvent
Browse files Browse the repository at this point in the history
feat : add usePointerEvent props
  • Loading branch information
martijnrusschen authored Mar 4, 2024
2 parents 6852294 + 046c42d commit 42d88b8
Show file tree
Hide file tree
Showing 14 changed files with 178 additions and 55 deletions.
1 change: 1 addition & 0 deletions docs/datepicker.md
Original file line number Diff line number Diff line change
Expand Up @@ -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). |
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,3 +160,4 @@
| `wrapperClassName` | `string` | | |
| `yearDropdownItemNumber` | `number` | | |
| `yearItemNumber` | `number` | `DEFAULT_YEAR_ITEM_NUMBER` | |
| `usePointerEvent` | `bool` | `false` | |
1 change: 1 addition & 0 deletions docs/year.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@
| `setPreSelection` | `func` | | |
| `startDate` | `instanceOfDate` | | |
| `yearItemNumber` | `number` | | |
| `usePointerEvent` | `bool` | | |
2 changes: 2 additions & 0 deletions src/calendar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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}
Expand Down
4 changes: 3 additions & 1 deletion src/day.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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"
Expand Down
3 changes: 3 additions & 0 deletions src/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ export default class DatePicker extends React.Component {
customTimeInput: null,
calendarStartDay: undefined,
toggleCalendarOnIconClick: false,
usePointerEvent: false,
};
}

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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>
Expand Down
11 changes: 8 additions & 3 deletions src/month.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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}
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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)}
Expand Down Expand Up @@ -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"
>
Expand Down
2 changes: 2 additions & 0 deletions src/week.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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}
Expand Down
10 changes: 7 additions & 3 deletions src/year.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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}
>
Expand All @@ -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>
Expand Down
44 changes: 34 additions & 10 deletions test/calendar_test.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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())),
);
});

Expand Down
41 changes: 33 additions & 8 deletions test/datepicker_test.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
31 changes: 21 additions & 10 deletions test/day_test.test.js
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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();
});
});

Expand Down
43 changes: 33 additions & 10 deletions test/month_test.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand Down
Loading

0 comments on commit 42d88b8

Please sign in to comment.