1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 | import classNames from "classnames";
|
23 | import * as React from "react";
|
24 | import DayPicker from "react-day-picker";
|
25 | import { AbstractPureComponent2, Boundary, Classes, DISPLAYNAME_PREFIX, InputGroup, Intent, Keys, Popover, Position, refHandler, setRef, } from "@blueprintjs/core";
|
26 | import { areSameTime, isDateValid, isDayInRange } from "./common/dateUtils";
|
27 | import * as Errors from "./common/errors";
|
28 | import { getFormattedDateString } from "./dateFormat";
|
29 | import { getDefaultMaxDate, getDefaultMinDate } from "./datePickerCore";
|
30 | import { DateRangePicker } from "./dateRangePicker";
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 | export 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 |
|
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 |
|
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 |
|
128 |
|
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 |
|
145 |
|
146 | handleDateRangePickerChange = (selectedRange, didSubmitWithEnter = false) => {
|
147 |
|
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 |
|
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 |
|
170 | startHoverString = null;
|
171 | }
|
172 | else if (selectedEnd == null) {
|
173 |
|
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 |
|
190 |
|
191 |
|
192 | isEndInputFocused = true;
|
193 | }
|
194 | else {
|
195 | isEndInputFocused = false;
|
196 | boundaryToModify = Boundary.END;
|
197 | }
|
198 | }
|
199 | else if (this.state.lastFocusedField === Boundary.START) {
|
200 |
|
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 |
|
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 |
|
245 | if (!this.state.isOpen) {
|
246 | return;
|
247 | }
|
248 | if (hoveredRange == null) {
|
249 |
|
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 |
|
275 |
|
276 |
|
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 |
|
318 |
|
319 |
|
320 | handleInputKeyDown = (e) => {
|
321 |
|
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 |
|
327 |
|
328 |
|
329 |
|
330 | const wasStartFieldFocused = this.state.lastFocusedField === Boundary.START;
|
331 | const wasEndFieldFocused = this.state.lastFocusedField === Boundary.END;
|
332 |
|
333 | if (isTabPressed) {
|
334 | let isEndInputFocused;
|
335 | let isStartInputFocused;
|
336 | let isOpen = true;
|
337 | if (wasStartFieldFocused && !isShiftPressed) {
|
338 | isStartInputFocused = false;
|
339 | isEndInputFocused = true;
|
340 |
|
341 |
|
342 | e.preventDefault();
|
343 | }
|
344 | else if (wasEndFieldFocused && isShiftPressed) {
|
345 | isStartInputFocused = true;
|
346 | isEndInputFocused = false;
|
347 | e.preventDefault();
|
348 | }
|
349 | else {
|
350 |
|
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 |
|
372 | return;
|
373 | }
|
374 | };
|
375 | handleInputMouseDown = () => {
|
376 |
|
377 |
|
378 |
|
379 | this.setState({ wasLastFocusChangeDueToHover: false });
|
380 | };
|
381 | handleInputClick = (e) => {
|
382 |
|
383 |
|
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 |
|
390 |
|
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 |
|
445 |
|
446 |
|
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 |
|
458 |
|
459 |
|
460 |
|
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 |
|
478 | nextState = { ...nextState, [keys.inputString]: inputString, [keys.hoverString]: null };
|
479 | }
|
480 | this.setState(nextState);
|
481 | };
|
482 |
|
483 |
|
484 | handlePopoverClose = (event) => {
|
485 | this.setState({ isOpen: false });
|
486 | this.props.popoverProps.onClose?.(event);
|
487 | };
|
488 |
|
489 |
|
490 | shouldFocusInputRef(isFocused, inputRef) {
|
491 | return isFocused && inputRef !== undefined && document.activeElement !== inputRef;
|
492 | }
|
493 | getIsOpenValueWhenDateChanges = (nextSelectedStart, nextSelectedEnd) => {
|
494 | if (this.props.closeOnSelection) {
|
495 |
|
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 |
|
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 |
|
533 |
|
534 |
|
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 |
|
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 |
|
649 |
|
650 |
|
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 |
|
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 |
|
676 |
|
677 | getFormattedMinMaxDateString(props, propName) {
|
678 | const date = props[propName];
|
679 | const defaultDate = DateRangeInput.defaultProps[propName];
|
680 |
|
681 |
|
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 |
|
\ | No newline at end of file |