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

[datetime]fix (dateRangeInput): DateRangeInput should not close on selection when time inputs receive key presses #3658

Merged
merged 7 commits into from
Jul 18, 2019
35 changes: 30 additions & 5 deletions packages/datetime/src/dateRangeInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import {
Utils,
} from "@blueprintjs/core";

import { DateRange, isDateValid, isDayInRange } from "./common/dateUtils";
import { areSameTime, DateRange, isDateValid, isDayInRange } from "./common/dateUtils";
import * as Errors from "./common/errors";
import { getFormattedDateString, IDateFormatProps } from "./dateFormat";
import { getDefaultMaxDate, getDefaultMinDate, IDatePickerBaseProps } from "./datePickerCore";
Expand Down Expand Up @@ -410,8 +410,9 @@ export class DateRangeInput extends AbstractPureComponent<IDateRangeInputProps,

endHoverString = null;
} else if (this.props.closeOnSelection) {
isOpen = false;
isOpen = this.getIsOpenValueWhenDateChanges(selectedStart, selectedEnd);
isStartInputFocused = false;

if (this.props.timePrecision == null && didSubmitWithEnter) {
// if we submit via click or Tab, the focus will have moved already.
// it we submit with Enter, the focus won't have moved, and setting
Expand Down Expand Up @@ -735,6 +736,29 @@ export class DateRangeInput extends AbstractPureComponent<IDateRangeInputProps,
return isFocused && inputRef !== undefined && document.activeElement !== inputRef;
}

private getIsOpenValueWhenDateChanges = (nextSelectedStart: Date, nextSelectedEnd: Date): boolean => {
if (this.props.closeOnSelection) {
// trivial case when TimePicker is not shown
if (this.props.timePrecision == null) {
return false;
}

const fallbackDate = new Date(new Date().setHours(0, 0, 0, 0));
const [selectedStart, selectedEnd] = this.getSelectedRange([fallbackDate, fallbackDate]);

// case to check if the user has changed TimePicker values
if (
areSameTime(selectedStart, nextSelectedStart) === true &&
areSameTime(selectedEnd, nextSelectedEnd) === true
) {
return false;
}
return true;
}

return true;
};

private getInitialRange = (props = this.props): DateRange => {
const { defaultValue, value } = props;
if (value != null) {
Expand All @@ -746,7 +770,7 @@ export class DateRangeInput extends AbstractPureComponent<IDateRangeInputProps,
}
};

private getSelectedRange = () => {
private getSelectedRange = (fallbackRange?: [Date, Date]) => {
let selectedStart: Date;
let selectedEnd: Date;

Expand All @@ -763,8 +787,9 @@ export class DateRangeInput extends AbstractPureComponent<IDateRangeInputProps,
const doBoundaryDatesOverlap = this.doBoundaryDatesOverlap(selectedStart, Boundary.START);
const dateRange = [selectedStart, doBoundaryDatesOverlap ? undefined : selectedEnd];

return dateRange.map((selectedBound: Date | undefined) => {
return this.isDateValidAndInRange(selectedBound) ? selectedBound : undefined;
return dateRange.map((selectedBound: Date | undefined, index: number) => {
const fallbackDate = fallbackRange != null ? fallbackRange[index] : undefined;
return this.isDateValidAndInRange(selectedBound) ? selectedBound : fallbackDate;
}) as DateRange;
};

Expand Down
54 changes: 49 additions & 5 deletions packages/datetime/test/dateRangeInputTests.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -155,16 +155,50 @@ describe("<DateRangeInput>", () => {
expect(isEndInputFocused(root), "end input focus to be false").to.be.false;
});

after(() => {
it("when timePrecision != null && closeOnSelection=true && <TimePicker /> values is changed popover should not close", () => {
const { root, getDayElement } = wrap(
<DateRangeInput {...DATE_FORMAT} timePrecision={TimePrecision.MINUTE} />,
testsContainerElement,
);

root.setState({ isOpen: true });

getDayElement(1).simulate("click");
getDayElement(10).simulate("click");

root.setState({ isOpen: true });

keyDownOnInput(DateClasses.TIMEPICKER_HOUR, Keys.ARROW_UP);
root.update();
expect(root.find(Popover).prop("isOpen")).to.be.true;
});

it("when timePrecision != null && closeOnSelection=true && end <TimePicker /> values is changed directly (without setting the selectedEnd date) - popover should not close", () => {
const { root } = wrap(
<DateRangeInput {...DATE_FORMAT} timePrecision={TimePrecision.MINUTE} />,
testsContainerElement,
);

root.setState({ isOpen: true });
keyDownOnInput(DateClasses.TIMEPICKER_HOUR, Keys.ARROW_UP);
root.update();
keyDownOnInput(DateClasses.TIMEPICKER_HOUR, Keys.ARROW_UP, 1);
root.update();
expect(root.find(Popover).prop("isOpen")).to.be.true;
});

afterEach(() => {
ReactDOM.unmountComponentAtNode(testsContainerElement);
});

function keyDownOnInput(className: string, key: number) {
TestUtils.Simulate.keyDown(findTimePickerInputElement(className), { which: key });
function keyDownOnInput(className: string, key: number, inputElementIndex: number = 0) {
TestUtils.Simulate.keyDown(findTimePickerInputElement(className, inputElementIndex), { which: key });
}

function findTimePickerInputElement(className: string) {
return document.querySelector(`.${DateClasses.TIMEPICKER_INPUT}.${className}`) as HTMLInputElement;
function findTimePickerInputElement(className: string, inputElementIndex: number = 0) {
return document.querySelectorAll(`.${DateClasses.TIMEPICKER_INPUT}.${className}`)[
inputElementIndex
] as HTMLInputElement;
}
});

Expand Down Expand Up @@ -335,6 +369,16 @@ describe("<DateRangeInput>", () => {
getDayElement(10).simulate("click");
expect(root.state("isOpen")).to.be.false;
});

it("if closeOnSelection=true && timePrecision != null, popover closes when full date range is selected", () => {
const { root, getDayElement } = wrap(
<DateRangeInput {...DATE_FORMAT} timePrecision={TimePrecision.MINUTE} />,
);
root.setState({ isOpen: true });
getDayElement(1).simulate("click");
getDayElement(10).simulate("click");
expect(root.state("isOpen")).to.be.false;
});
});

it("accepts contiguousCalendarMonths prop and passes it to the date range picker", () => {
Expand Down