Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: timepicker on 23 and 25-hour days #4244

Merged
merged 6 commits into from
Sep 15, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
23 changes: 23 additions & 0 deletions src/date_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -818,3 +818,26 @@ export function getYearsPeriod(
const startPeriod = endPeriod - (yearItemNumber - 1);
return { startPeriod, endPeriod };
}

export function getHoursInDay(d) {
const startOfDay = new Date(d.getFullYear(), d.getMonth(), d.getDate());
const startOfTheNextDay = new Date(
d.getFullYear(),
d.getMonth(),
d.getDate(),
24
);

return Math.round((+startOfTheNextDay - +startOfDay) / 3_600_000);
}

export function startOfMinute(d) {
const seconds = d.getSeconds();
const milliseconds = d.getMilliseconds();

return toDate(d.getTime() - seconds * 1000 - milliseconds);
Zarthus marked this conversation as resolved.
Show resolved Hide resolved
}

export function isSameMinute(d1, d2) {
return startOfMinute(d1).getTime() === startOfMinute(d2).getTime();
}
10 changes: 6 additions & 4 deletions src/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -686,10 +686,12 @@ export default class DatePicker extends React.Component {
const selected = this.props.selected
? this.props.selected
: this.getPreSelection();
let changedDate = setTime(selected, {
hour: getHours(time),
minute: getMinutes(time),
});
let changedDate = this.props.selected
? time
: setTime(selected, {
hour: getHours(time),
minute: getMinutes(time),
});

this.setState({
preSelection: changedDate,
Expand Down
46 changes: 18 additions & 28 deletions src/time.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,15 @@ import PropTypes from "prop-types";
import {
getHours,
getMinutes,
setHours,
setMinutes,
newDate,
getStartOfDay,
addMinutes,
formatDate,
isBefore,
isEqual,
isTimeInDisabledRange,
isTimeDisabled,
timesToInjectAfter,
getHoursInDay,
isSameMinute,
} from "./date_utils";

export default class Time extends React.Component {
Expand Down Expand Up @@ -91,20 +89,16 @@ export default class Time extends React.Component {
this.props.onChange(time);
};

isSelectedTime = (time, currH, currM) =>
this.props.selected &&
currH === getHours(time) &&
currM === getMinutes(time);
isSelectedTime = (time) =>
this.props.selected && isSameMinute(this.props.selected, time);

liClasses = (time, currH, currM) => {
liClasses = (time) => {
let classes = [
"react-datepicker__time-list-item",
this.props.timeClassName
? this.props.timeClassName(time, currH, currM)
: undefined,
this.props.timeClassName ? this.props.timeClassName(time) : undefined,
];

if (this.isSelectedTime(time, currH, currM)) {
if (this.isSelectedTime(time)) {
classes.push("react-datepicker__time-list-item--selected");
}

Expand Down Expand Up @@ -160,19 +154,18 @@ export default class Time extends React.Component {
const format = this.props.format ? this.props.format : "p";
const intervals = this.props.intervals;

const base = getStartOfDay(newDate(this.props.selected));
const multiplier = 1440 / intervals;
const activeDate =
this.props.selected || this.props.openToDate || newDate();

const base = getStartOfDay(activeDate);
const sortedInjectTimes =
this.props.injectTimes &&
this.props.injectTimes.sort(function (a, b) {
return a - b;
});

const activeDate =
this.props.selected || this.props.openToDate || newDate();
const currH = getHours(activeDate);
const currM = getMinutes(activeDate);
const activeTime = setHours(setMinutes(base, currM), currH);
const minutesInDay = 60 * getHoursInDay(activeDate);
const multiplier = minutesInDay / intervals;

for (let i = 0; i < multiplier; i++) {
const currentTime = addMinutes(base, i * intervals);
Expand All @@ -192,19 +185,18 @@ export default class Time extends React.Component {

// Determine which time to focus and scroll into view when component mounts
const timeToFocus = times.reduce((prev, time) => {
if (isBefore(time, activeTime) || isEqual(time, activeTime)) {
if (time.getTime() <= activeDate.getTime()) {
return time;
} else {
return prev;
}
return prev;
}, times[0]);

return times.map((time, i) => {
return (
<li
key={i}
onClick={this.handleClick.bind(this, time)}
className={this.liClasses(time, currH, currM)}
className={this.liClasses(time)}
ref={(li) => {
if (time === timeToFocus) {
this.centerLi = li;
Expand All @@ -213,11 +205,9 @@ export default class Time extends React.Component {
onKeyDown={(ev) => {
this.handleOnKeyDown(ev, time);
}}
tabIndex={time === timeToFocus ? "0" : "-1"}
tabIndex={time === timeToFocus ? 0 : -1}
role="option"
aria-selected={
this.isSelectedTime(time, currH, currM) ? "true" : undefined
}
aria-selected={this.isSelectedTime(time) ? "true" : undefined}
>
{formatDate(time, format, this.props.locale)}
</li>
Expand Down
6 changes: 3 additions & 3 deletions test/time_format_test.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ describe("TimeComponent", () => {
var timeListItem = timeComponent.find(
".react-datepicker__time-list-item--selected",
);
expect(timeListItem.at(0).prop("tabIndex")).toBe("0");
expect(timeListItem.at(0).prop("tabIndex")).toBe(0);
});

it("should not add the aria-selected property to a regular item", () => {
Expand Down Expand Up @@ -151,7 +151,7 @@ describe("TimeComponent", () => {
var timeListItem = timeComponent.find(
".react-datepicker__time-list-item",
);
expect(timeListItem.at(0).prop("tabIndex")).toBe("-1");
expect(timeListItem.at(0).prop("tabIndex")).toBe(-1);
});

it("when no selected time, should focus the time closest to the opened time", () => {
Expand All @@ -169,7 +169,7 @@ describe("TimeComponent", () => {
timeListItem
.findWhere((node) => node.type() && node.text() === "09:00")
.prop("tabIndex"),
).toBe("0");
).toBe(0);
});

it("when no selected time, should call calcCenterPosition with centerLi ref, closest to the opened time", () => {
Expand Down
5 changes: 1 addition & 4 deletions test/timepicker_test.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,7 @@ describe("TimePicker", () => {
});

it("should show different colors for times", () => {
const handleTimeColors = (time, currH, currM) => {
if (!Number.isInteger(currH) || !Number.isInteger(currM)) {
return "wrong";
}
const handleTimeColors = (time) => {
return time.getHours() < 12 ? "red" : "green";
};
const timePicker = TestUtils.renderIntoDocument(
Expand Down