UNPKG

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