UNPKG

31.8 kBJavaScriptView Raw
1/*
2 * Copyright 2017 Palantir Technologies, Inc. All rights reserved.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16/**
17 * @fileoverview This component is DEPRECATED, and the code is frozen.
18 * All changes & bugfixes should be made to DateRangeInput2 in the datetime2
19 * package instead.
20 */
21/* eslint-disable deprecation/deprecation, @blueprintjs/no-deprecated-components */
22import classNames from "classnames";
23import * as React from "react";
24import DayPicker from "react-day-picker";
25import { AbstractPureComponent2, Boundary, Classes, DISPLAYNAME_PREFIX, InputGroup, Intent, Keys, Popover, Position, refHandler, setRef, } from "@blueprintjs/core";
26import { areSameTime, isDateValid, isDayInRange } from "./common/dateUtils";
27import * as Errors from "./common/errors";
28import { getFormattedDateString } from "./dateFormat";
29import { getDefaultMaxDate, getDefaultMinDate } from "./datePickerCore";
30import { DateRangePicker } from "./dateRangePicker";
31/**
32 * Date range input component.
33 *
34 * @see https://blueprintjs.com/docs/#datetime/daterangeinput
35 * @deprecated use { DateRangeInput2 } from "@blueprintjs/datetime2"
36 */
37export class DateRangeInput extends AbstractPureComponent2 {
38 static defaultProps = {
39 allowSingleDayRange: false,
40 closeOnSelection: true,
41 contiguousCalendarMonths: true,
42 dayPickerProps: {},
43 disabled: false,
44 endInputProps: {},
45 invalidDateMessage: "Invalid date",
46 maxDate: getDefaultMaxDate(),
47 minDate: getDefaultMinDate(),
48 outOfRangeMessage: "Out of range",
49 overlappingDatesMessage: "Overlapping dates",
50 popoverProps: {},
51 selectAllOnFocus: false,
52 shortcuts: true,
53 singleMonthOnly: false,
54 startInputProps: {},
55 };
56 static displayName = `${DISPLAYNAME_PREFIX}.DateRangeInput`;
57 startInputElement = null;
58 endInputElement = null;
59 handleStartInputRef = refHandler(this, "startInputElement", this.props.startInputProps?.inputRef);
60 handleEndInputRef = refHandler(this, "endInputElement", this.props.endInputProps?.inputRef);
61 constructor(props) {
62 super(props);
63 this.reset(props);
64 }
65 /**
66 * Public method intended for unit testing only. Do not use in feature work!
67 */
68 reset(props = this.props) {
69 const [selectedStart, selectedEnd] = this.getInitialRange();
70 this.state = {
71 formattedMaxDateString: this.getFormattedMinMaxDateString(props, "maxDate"),
72 formattedMinDateString: this.getFormattedMinMaxDateString(props, "minDate"),
73 isOpen: false,
74 selectedEnd,
75 selectedShortcutIndex: -1,
76 selectedStart,
77 };
78 }
79 componentDidUpdate(prevProps, prevState) {
80 super.componentDidUpdate(prevProps, prevState);
81 const { isStartInputFocused, isEndInputFocused, shouldSelectAfterUpdate } = this.state;
82 if (prevProps.startInputProps?.inputRef !== this.props.startInputProps?.inputRef) {
83 setRef(prevProps.startInputProps?.inputRef, null);
84 this.handleStartInputRef = refHandler(this, "startInputElement", this.props.startInputProps?.inputRef);
85 setRef(this.props.startInputProps?.inputRef, this.startInputElement);
86 }
87 if (prevProps.endInputProps?.inputRef !== this.props.endInputProps?.inputRef) {
88 setRef(prevProps.endInputProps?.inputRef, null);
89 this.handleEndInputRef = refHandler(this, "endInputElement", this.props.endInputProps?.inputRef);
90 setRef(this.props.endInputProps?.inputRef, this.endInputElement);
91 }
92 const shouldFocusStartInput = this.shouldFocusInputRef(isStartInputFocused, this.startInputElement);
93 const shouldFocusEndInput = this.shouldFocusInputRef(isEndInputFocused, this.endInputElement);
94 if (shouldFocusStartInput) {
95 this.startInputElement?.focus();
96 }
97 else if (shouldFocusEndInput) {
98 this.endInputElement?.focus();
99 }
100 if (isStartInputFocused && shouldSelectAfterUpdate) {
101 this.startInputElement?.select();
102 }
103 else if (isEndInputFocused && shouldSelectAfterUpdate) {
104 this.endInputElement?.select();
105 }
106 let nextState = {};
107 if (this.props.value !== prevProps.value) {
108 const [selectedStart, selectedEnd] = this.getInitialRange(this.props);
109 nextState = { ...nextState, selectedStart, selectedEnd };
110 }
111 // cache the formatted date strings to avoid computing on each render.
112 if (this.props.minDate !== prevProps.minDate) {
113 const formattedMinDateString = this.getFormattedMinMaxDateString(this.props, "minDate");
114 nextState = { ...nextState, formattedMinDateString };
115 }
116 if (this.props.maxDate !== prevProps.maxDate) {
117 const formattedMaxDateString = this.getFormattedMinMaxDateString(this.props, "maxDate");
118 nextState = { ...nextState, formattedMaxDateString };
119 }
120 this.setState(nextState);
121 }
122 render() {
123 const { selectedShortcutIndex } = this.state;
124 const { popoverProps = {} } = this.props;
125 const popoverContent = (React.createElement(DateRangePicker, { ...this.props, selectedShortcutIndex: selectedShortcutIndex, boundaryToModify: this.state.boundaryToModify, onChange: this.handleDateRangePickerChange, onShortcutChange: this.handleShortcutChange, onHoverChange: this.handleDateRangePickerHoverChange, value: this.getSelectedRange() }));
126 const popoverClassName = classNames(popoverProps.className, this.props.className);
127 // allow custom props for the popover and each input group, but pass them in an order that
128 // guarantees only some props are overridable.
129 return (React.createElement(Popover, { isOpen: this.state.isOpen, position: Position.BOTTOM_LEFT, ...this.props.popoverProps, autoFocus: false, className: popoverClassName, content: popoverContent, enforceFocus: false, onClose: this.handlePopoverClose },
130 React.createElement("div", { className: Classes.CONTROL_GROUP },
131 this.renderInputGroup(Boundary.START),
132 this.renderInputGroup(Boundary.END))));
133 }
134 validateProps(props) {
135 if (props.value === null) {
136 throw new Error(Errors.DATERANGEINPUT_NULL_VALUE);
137 }
138 }
139 renderInputGroup = (boundary) => {
140 const inputProps = this.getInputProps(boundary);
141 const handleInputEvent = boundary === Boundary.START ? this.handleStartInputEvent : this.handleEndInputEvent;
142 return (React.createElement(InputGroup, { autoComplete: "off", disabled: inputProps.disabled || this.props.disabled, ...inputProps, intent: this.isInputInErrorState(boundary) ? Intent.DANGER : inputProps.intent, inputRef: this.getInputRef(boundary), onBlur: handleInputEvent, onChange: handleInputEvent, onClick: handleInputEvent, onFocus: handleInputEvent, onKeyDown: handleInputEvent, onMouseDown: handleInputEvent, placeholder: this.getInputPlaceholderString(boundary), value: this.getInputDisplayString(boundary) }));
143 };
144 // Callbacks - DateRangePicker
145 // ===========================
146 handleDateRangePickerChange = (selectedRange, didSubmitWithEnter = false) => {
147 // ignore mouse events in the date-range picker if the popover is animating closed.
148 if (!this.state.isOpen) {
149 return;
150 }
151 const [selectedStart, selectedEnd] = selectedRange;
152 let isOpen = true;
153 let isStartInputFocused;
154 let isEndInputFocused;
155 let startHoverString;
156 let endHoverString;
157 let boundaryToModify;
158 if (selectedStart == null) {
159 // focus the start field by default or if only an end date is specified
160 if (this.props.timePrecision == null) {
161 isStartInputFocused = true;
162 isEndInputFocused = false;
163 }
164 else {
165 isStartInputFocused = false;
166 isEndInputFocused = false;
167 boundaryToModify = Boundary.START;
168 }
169 // for clarity, hide the hover string until the mouse moves over a different date
170 startHoverString = null;
171 }
172 else if (selectedEnd == null) {
173 // focus the end field if a start date is specified
174 if (this.props.timePrecision == null) {
175 isStartInputFocused = false;
176 isEndInputFocused = true;
177 }
178 else {
179 isStartInputFocused = false;
180 isEndInputFocused = false;
181 boundaryToModify = Boundary.END;
182 }
183 endHoverString = null;
184 }
185 else if (this.props.closeOnSelection) {
186 isOpen = this.getIsOpenValueWhenDateChanges(selectedStart, selectedEnd);
187 isStartInputFocused = false;
188 if (this.props.timePrecision == null && didSubmitWithEnter) {
189 // if we submit via click or Tab, the focus will have moved already.
190 // it we submit with Enter, the focus won't have moved, and setting
191 // the flag to false won't have an effect anyway, so leave it true.
192 isEndInputFocused = true;
193 }
194 else {
195 isEndInputFocused = false;
196 boundaryToModify = Boundary.END;
197 }
198 }
199 else if (this.state.lastFocusedField === Boundary.START) {
200 // keep the start field focused
201 if (this.props.timePrecision == null) {
202 isStartInputFocused = true;
203 isEndInputFocused = false;
204 }
205 else {
206 isStartInputFocused = false;
207 isEndInputFocused = false;
208 boundaryToModify = Boundary.START;
209 }
210 }
211 else if (this.props.timePrecision == null) {
212 // keep the end field focused
213 isStartInputFocused = false;
214 isEndInputFocused = true;
215 }
216 else {
217 isStartInputFocused = false;
218 isEndInputFocused = false;
219 boundaryToModify = Boundary.END;
220 }
221 const baseStateChange = {
222 boundaryToModify,
223 endHoverString,
224 endInputString: this.formatDate(selectedEnd),
225 isEndInputFocused,
226 isOpen,
227 isStartInputFocused,
228 startHoverString,
229 startInputString: this.formatDate(selectedStart),
230 wasLastFocusChangeDueToHover: false,
231 };
232 if (this.isControlled()) {
233 this.setState(baseStateChange);
234 }
235 else {
236 this.setState({ ...baseStateChange, selectedEnd, selectedStart });
237 }
238 this.props.onChange?.(selectedRange);
239 };
240 handleShortcutChange = (_, selectedShortcutIndex) => {
241 this.setState({ selectedShortcutIndex });
242 };
243 handleDateRangePickerHoverChange = (hoveredRange, _hoveredDay, hoveredBoundary) => {
244 // ignore mouse events in the date-range picker if the popover is animating closed.
245 if (!this.state.isOpen) {
246 return;
247 }
248 if (hoveredRange == null) {
249 // undo whatever focus changes we made while hovering over various calendar dates
250 const isEndInputFocused = this.state.boundaryToModify === Boundary.END;
251 this.setState({
252 endHoverString: null,
253 isEndInputFocused,
254 isStartInputFocused: !isEndInputFocused,
255 lastFocusedField: this.state.boundaryToModify,
256 startHoverString: null,
257 });
258 }
259 else {
260 const [hoveredStart, hoveredEnd] = hoveredRange;
261 const isStartInputFocused = hoveredBoundary != null ? hoveredBoundary === Boundary.START : this.state.isStartInputFocused;
262 const isEndInputFocused = hoveredBoundary != null ? hoveredBoundary === Boundary.END : this.state.isEndInputFocused;
263 this.setState({
264 endHoverString: this.formatDate(hoveredEnd),
265 isEndInputFocused,
266 isStartInputFocused,
267 lastFocusedField: isStartInputFocused ? Boundary.START : Boundary.END,
268 shouldSelectAfterUpdate: this.props.selectAllOnFocus,
269 startHoverString: this.formatDate(hoveredStart),
270 wasLastFocusChangeDueToHover: true,
271 });
272 }
273 };
274 // Callbacks - Input
275 // =================
276 // instantiate these two functions once so we don't have to for each callback on each render.
277 handleStartInputEvent = (e) => {
278 this.handleInputEvent(e, Boundary.START);
279 };
280 handleEndInputEvent = (e) => {
281 this.handleInputEvent(e, Boundary.END);
282 };
283 handleInputEvent = (e, boundary) => {
284 const inputProps = this.getInputProps(boundary);
285 switch (e.type) {
286 case "blur":
287 this.handleInputBlur(e, boundary);
288 inputProps.onBlur?.(e);
289 break;
290 case "change":
291 this.handleInputChange(e, boundary);
292 inputProps.onChange?.(e);
293 break;
294 case "click":
295 e = e;
296 this.handleInputClick(e);
297 inputProps.onClick?.(e);
298 break;
299 case "focus":
300 this.handleInputFocus(e, boundary);
301 inputProps.onFocus?.(e);
302 break;
303 case "keydown":
304 e = e;
305 this.handleInputKeyDown(e);
306 inputProps.onKeyDown?.(e);
307 break;
308 case "mousedown":
309 e = e;
310 this.handleInputMouseDown();
311 inputProps.onMouseDown?.(e);
312 break;
313 default:
314 break;
315 }
316 };
317 // add a keydown listener to persistently change focus when tabbing:
318 // - if focused in start field, Tab moves focus to end field
319 // - if focused in end field, Shift+Tab moves focus to start field
320 handleInputKeyDown = (e) => {
321 // HACKHACK: https://github.com/palantir/blueprint/issues/4165
322 const isTabPressed = e.which === Keys.TAB;
323 const isEnterPressed = e.which === Keys.ENTER;
324 const isShiftPressed = e.shiftKey;
325 const { selectedStart, selectedEnd } = this.state;
326 // order of JS events is our enemy here. when tabbing between fields,
327 // this handler will fire in the middle of a focus exchange when no
328 // field is currently focused. we work around this by referring to the
329 // most recently focused field, rather than the currently focused field.
330 const wasStartFieldFocused = this.state.lastFocusedField === Boundary.START;
331 const wasEndFieldFocused = this.state.lastFocusedField === Boundary.END;
332 // move focus to the other field
333 if (isTabPressed) {
334 let isEndInputFocused;
335 let isStartInputFocused;
336 let isOpen = true;
337 if (wasStartFieldFocused && !isShiftPressed) {
338 isStartInputFocused = false;
339 isEndInputFocused = true;
340 // prevent the default focus-change behavior to avoid race conditions;
341 // we'll handle the focus change ourselves in componentDidUpdate.
342 e.preventDefault();
343 }
344 else if (wasEndFieldFocused && isShiftPressed) {
345 isStartInputFocused = true;
346 isEndInputFocused = false;
347 e.preventDefault();
348 }
349 else {
350 // don't prevent default here, otherwise Tab won't do anything.
351 isStartInputFocused = false;
352 isEndInputFocused = false;
353 isOpen = false;
354 }
355 this.setState({
356 isEndInputFocused,
357 isOpen,
358 isStartInputFocused,
359 wasLastFocusChangeDueToHover: false,
360 });
361 }
362 else if (wasStartFieldFocused && isEnterPressed) {
363 const nextStartDate = this.parseDate(this.state.startInputString);
364 this.handleDateRangePickerChange([nextStartDate, selectedEnd], true);
365 }
366 else if (wasEndFieldFocused && isEnterPressed) {
367 const nextEndDate = this.parseDate(this.state.endInputString);
368 this.handleDateRangePickerChange([selectedStart, nextEndDate], true);
369 }
370 else {
371 // let the default keystroke happen without side effects
372 return;
373 }
374 };
375 handleInputMouseDown = () => {
376 // clicking in the field constitutes an explicit focus change. we update
377 // the flag on "mousedown" instead of on "click", because it needs to be
378 // set before onFocus is called ("click" triggers after "focus").
379 this.setState({ wasLastFocusChangeDueToHover: false });
380 };
381 handleInputClick = (e) => {
382 // unless we stop propagation on this event, a click within an input
383 // will close the popover almost as soon as it opens.
384 e.stopPropagation();
385 };
386 handleInputFocus = (_e, boundary) => {
387 const { keys, values } = this.getStateKeysAndValuesForBoundary(boundary);
388 const inputString = getFormattedDateString(values.selectedValue, this.props, true);
389 // change the boundary only if the user explicitly focused in the field.
390 // focus changes from hovering don't count; they're just temporary.
391 const boundaryToModify = this.state.wasLastFocusChangeDueToHover ? this.state.boundaryToModify : boundary;
392 this.setState({
393 [keys.inputString]: inputString,
394 [keys.isInputFocused]: true,
395 boundaryToModify,
396 isOpen: true,
397 lastFocusedField: boundary,
398 shouldSelectAfterUpdate: this.props.selectAllOnFocus,
399 wasLastFocusChangeDueToHover: false,
400 });
401 };
402 handleInputBlur = (_e, boundary) => {
403 const { keys, values } = this.getStateKeysAndValuesForBoundary(boundary);
404 const maybeNextDate = this.parseDate(values.inputString);
405 const isValueControlled = this.isControlled();
406 let nextState = {
407 [keys.isInputFocused]: false,
408 shouldSelectAfterUpdate: false,
409 };
410 if (this.isInputEmpty(values.inputString)) {
411 if (isValueControlled) {
412 nextState = {
413 ...nextState,
414 [keys.inputString]: getFormattedDateString(values.controlledValue, this.props),
415 };
416 }
417 else {
418 nextState = {
419 ...nextState,
420 [keys.inputString]: null,
421 [keys.selectedValue]: null,
422 };
423 }
424 }
425 else if (!this.isNextDateRangeValid(maybeNextDate, boundary)) {
426 if (!isValueControlled) {
427 nextState = {
428 ...nextState,
429 [keys.inputString]: null,
430 [keys.selectedValue]: maybeNextDate,
431 };
432 }
433 this.props.onError?.(this.getDateRangeForCallback(maybeNextDate, boundary));
434 }
435 this.setState(nextState);
436 };
437 handleInputChange = (e, boundary) => {
438 const inputString = e.target.value;
439 const { keys } = this.getStateKeysAndValuesForBoundary(boundary);
440 const maybeNextDate = this.parseDate(inputString);
441 const isValueControlled = this.isControlled();
442 let nextState = { shouldSelectAfterUpdate: false };
443 if (inputString.length === 0) {
444 // this case will be relevant when we start showing the hovered range in the input
445 // fields. goal is to show an empty field for clarity until the mouse moves over a
446 // different date.
447 const baseState = { ...nextState, [keys.inputString]: "" };
448 if (isValueControlled) {
449 nextState = baseState;
450 }
451 else {
452 nextState = { ...baseState, [keys.selectedValue]: null };
453 }
454 this.props.onChange?.(this.getDateRangeForCallback(null, boundary));
455 }
456 else if (this.isDateValidAndInRange(maybeNextDate)) {
457 // note that error cases that depend on both fields (e.g. overlapping dates) should fall
458 // through into this block so that the UI can update immediately, possibly with an error
459 // message on the other field.
460 // also, clear the hover string to ensure the most recent keystroke appears.
461 const baseState = {
462 ...nextState,
463 [keys.hoverString]: null,
464 [keys.inputString]: inputString,
465 };
466 if (isValueControlled) {
467 nextState = baseState;
468 }
469 else {
470 nextState = { ...baseState, [keys.selectedValue]: maybeNextDate };
471 }
472 if (this.isNextDateRangeValid(maybeNextDate, boundary)) {
473 this.props.onChange?.(this.getDateRangeForCallback(maybeNextDate, boundary));
474 }
475 }
476 else {
477 // again, clear the hover string to ensure the most recent keystroke appears
478 nextState = { ...nextState, [keys.inputString]: inputString, [keys.hoverString]: null };
479 }
480 this.setState(nextState);
481 };
482 // Callbacks - Popover
483 // ===================
484 handlePopoverClose = (event) => {
485 this.setState({ isOpen: false });
486 this.props.popoverProps.onClose?.(event);
487 };
488 // Helpers
489 // =======
490 shouldFocusInputRef(isFocused, inputRef) {
491 return isFocused && inputRef !== undefined && document.activeElement !== inputRef;
492 }
493 getIsOpenValueWhenDateChanges = (nextSelectedStart, nextSelectedEnd) => {
494 if (this.props.closeOnSelection) {
495 // trivial case when TimePicker is not shown
496 if (this.props.timePrecision == null) {
497 return false;
498 }
499 const fallbackDate = new Date(new Date().setHours(0, 0, 0, 0));
500 const [selectedStart, selectedEnd] = this.getSelectedRange([fallbackDate, fallbackDate]);
501 // case to check if the user has changed TimePicker values
502 if (areSameTime(selectedStart, nextSelectedStart) === true &&
503 areSameTime(selectedEnd, nextSelectedEnd) === true) {
504 return false;
505 }
506 return true;
507 }
508 return true;
509 };
510 getInitialRange = (props = this.props) => {
511 const { defaultValue, value } = props;
512 if (value != null) {
513 return value;
514 }
515 else if (defaultValue != null) {
516 return defaultValue;
517 }
518 else {
519 return [null, null];
520 }
521 };
522 getSelectedRange = (fallbackRange) => {
523 let selectedStart;
524 let selectedEnd;
525 if (this.isControlled()) {
526 [selectedStart, selectedEnd] = this.props.value;
527 }
528 else {
529 selectedStart = this.state.selectedStart;
530 selectedEnd = this.state.selectedEnd;
531 }
532 // this helper function checks if the provided boundary date *would* overlap the selected
533 // other boundary date. providing the already-selected start date simply tells us if we're
534 // currently in an overlapping state.
535 const doBoundaryDatesOverlap = this.doBoundaryDatesOverlap(selectedStart, Boundary.START);
536 const dateRange = [selectedStart, doBoundaryDatesOverlap ? undefined : selectedEnd];
537 return dateRange.map((selectedBound, index) => {
538 const fallbackDate = fallbackRange != null ? fallbackRange[index] : undefined;
539 return this.isDateValidAndInRange(selectedBound) ? selectedBound : fallbackDate;
540 });
541 };
542 getInputDisplayString = (boundary) => {
543 const { values } = this.getStateKeysAndValuesForBoundary(boundary);
544 const { isInputFocused, inputString, selectedValue, hoverString } = values;
545 if (hoverString != null) {
546 return hoverString;
547 }
548 else if (isInputFocused) {
549 return inputString == null ? "" : inputString;
550 }
551 else if (selectedValue == null) {
552 return "";
553 }
554 else if (this.doesEndBoundaryOverlapStartBoundary(selectedValue, boundary)) {
555 return this.props.overlappingDatesMessage;
556 }
557 else {
558 return getFormattedDateString(selectedValue, this.props);
559 }
560 };
561 getInputPlaceholderString = (boundary) => {
562 const isStartBoundary = boundary === Boundary.START;
563 const isEndBoundary = boundary === Boundary.END;
564 const inputProps = this.getInputProps(boundary);
565 const { isInputFocused } = this.getStateKeysAndValuesForBoundary(boundary).values;
566 // use the custom placeholder text for the input, if providied
567 if (inputProps.placeholder != null) {
568 return inputProps.placeholder;
569 }
570 else if (isStartBoundary) {
571 return isInputFocused ? this.state.formattedMinDateString : "Start date";
572 }
573 else if (isEndBoundary) {
574 return isInputFocused ? this.state.formattedMaxDateString : "End date";
575 }
576 else {
577 return "";
578 }
579 };
580 getInputProps = (boundary) => {
581 return boundary === Boundary.START ? this.props.startInputProps : this.props.endInputProps;
582 };
583 getInputRef = (boundary) => {
584 return boundary === Boundary.START ? this.handleStartInputRef : this.handleEndInputRef;
585 };
586 getStateKeysAndValuesForBoundary = (boundary) => {
587 const controlledRange = this.props.value;
588 if (boundary === Boundary.START) {
589 return {
590 keys: {
591 hoverString: "startHoverString",
592 inputString: "startInputString",
593 isInputFocused: "isStartInputFocused",
594 selectedValue: "selectedStart",
595 },
596 values: {
597 controlledValue: controlledRange != null ? controlledRange[0] : undefined,
598 hoverString: this.state.startHoverString,
599 inputString: this.state.startInputString,
600 isInputFocused: this.state.isStartInputFocused,
601 selectedValue: this.state.selectedStart,
602 },
603 };
604 }
605 else {
606 return {
607 keys: {
608 hoverString: "endHoverString",
609 inputString: "endInputString",
610 isInputFocused: "isEndInputFocused",
611 selectedValue: "selectedEnd",
612 },
613 values: {
614 controlledValue: controlledRange != null ? controlledRange[1] : undefined,
615 hoverString: this.state.endHoverString,
616 inputString: this.state.endInputString,
617 isInputFocused: this.state.isEndInputFocused,
618 selectedValue: this.state.selectedEnd,
619 },
620 };
621 }
622 };
623 getDateRangeForCallback = (currDate, currBoundary) => {
624 const otherBoundary = this.getOtherBoundary(currBoundary);
625 const otherDate = this.getStateKeysAndValuesForBoundary(otherBoundary).values.selectedValue;
626 return currBoundary === Boundary.START ? [currDate, otherDate] : [otherDate, currDate];
627 };
628 getOtherBoundary = (boundary) => {
629 return boundary === Boundary.START ? Boundary.END : Boundary.START;
630 };
631 doBoundaryDatesOverlap = (date, boundary) => {
632 const { allowSingleDayRange } = this.props;
633 const otherBoundary = this.getOtherBoundary(boundary);
634 const otherBoundaryDate = this.getStateKeysAndValuesForBoundary(otherBoundary).values.selectedValue;
635 if (date == null || otherBoundaryDate == null) {
636 return false;
637 }
638 if (boundary === Boundary.START) {
639 const isAfter = date > otherBoundaryDate;
640 return isAfter || (!allowSingleDayRange && DayPicker.DateUtils.isSameDay(date, otherBoundaryDate));
641 }
642 else {
643 const isBefore = date < otherBoundaryDate;
644 return isBefore || (!allowSingleDayRange && DayPicker.DateUtils.isSameDay(date, otherBoundaryDate));
645 }
646 };
647 /**
648 * Returns true if the provided boundary is an END boundary overlapping the
649 * selected start date. (If the boundaries overlap, we consider the END
650 * boundary to be erroneous.)
651 */
652 doesEndBoundaryOverlapStartBoundary = (boundaryDate, boundary) => {
653 return boundary === Boundary.START ? false : this.doBoundaryDatesOverlap(boundaryDate, boundary);
654 };
655 isControlled = () => this.props.value !== undefined;
656 isInputEmpty = (inputString) => inputString == null || inputString.length === 0;
657 isInputInErrorState = (boundary) => {
658 const values = this.getStateKeysAndValuesForBoundary(boundary).values;
659 const { isInputFocused, hoverString, inputString, selectedValue } = values;
660 if (hoverString != null || this.isInputEmpty(inputString)) {
661 // don't show an error state while we're hovering over a valid date.
662 return false;
663 }
664 const boundaryValue = isInputFocused ? this.parseDate(inputString) : selectedValue;
665 return (boundaryValue != null &&
666 (!this.isDateValidAndInRange(boundaryValue) ||
667 this.doesEndBoundaryOverlapStartBoundary(boundaryValue, boundary)));
668 };
669 isDateValidAndInRange = (date) => {
670 return isDateValid(date) && isDayInRange(date, [this.props.minDate, this.props.maxDate]);
671 };
672 isNextDateRangeValid(nextDate, boundary) {
673 return this.isDateValidAndInRange(nextDate) && !this.doBoundaryDatesOverlap(nextDate, boundary);
674 }
675 // this is a slightly kludgy function, but it saves us a good amount of repeated code between
676 // the constructor and componentDidUpdate.
677 getFormattedMinMaxDateString(props, propName) {
678 const date = props[propName];
679 const defaultDate = DateRangeInput.defaultProps[propName];
680 // default values are applied only if a prop is strictly `undefined`
681 // See: https://facebook.github.io/react/docs/react-component.html#defaultprops
682 return getFormattedDateString(date === undefined ? defaultDate : date, this.props);
683 }
684 parseDate(dateString) {
685 if (dateString === this.props.outOfRangeMessage || dateString === this.props.invalidDateMessage) {
686 return null;
687 }
688 const { locale, parseDate } = this.props;
689 const newDate = parseDate(dateString, locale);
690 return newDate === false ? new Date(undefined) : newDate;
691 }
692 formatDate(date) {
693 if (!this.isDateValidAndInRange(date)) {
694 return "";
695 }
696 const { locale, formatDate } = this.props;
697 return formatDate(date, locale);
698 }
699}
700//# sourceMappingURL=dateRangeInput.js.map
\No newline at end of file