"use strict";
/*
 * Copyright 2023 Palantir Technologies, Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.DateRangeInput3 = void 0;
var tslib_1 = require("tslib");
var classnames_1 = tslib_1.__importDefault(require("classnames"));
var date_fns_1 = require("date-fns");
var React = tslib_1.__importStar(require("react"));
var core_1 = require("@blueprintjs/core");
var datetime_1 = require("@blueprintjs/datetime");
var classes_1 = require("../../classes");
var dateFnsFormatUtils_1 = require("../../common/dateFnsFormatUtils");
var dateFnsLocaleProps_1 = require("../../common/dateFnsLocaleProps");
var dateRangePicker3_1 = require("../date-range-picker3/dateRangePicker3");
var dateFnsLocalizedComponent_1 = require("../dateFnsLocalizedComponent");
var dateRangeInputUilts_1 = require("./dateRangeInputUilts");
/**
 * Date range input (v3) component.
 *
 * @see https://blueprintjs.com/docs/#datetime2/date-range-input3
 */
var DateRangeInput3 = /** @class */ (function (_super) {
    tslib_1.__extends(DateRangeInput3, _super);
    function DateRangeInput3(props) {
        var _a, _b;
        var _this = _super.call(this, props) || this;
        _this.startInputElement = null;
        _this.endInputElement = null;
        _this.handleStartInputRef = (0, core_1.refHandler)(_this, "startInputElement", (_a = _this.props.startInputProps) === null || _a === void 0 ? void 0 : _a.inputRef);
        _this.handleEndInputRef = (0, core_1.refHandler)(_this, "endInputElement", (_b = _this.props.endInputProps) === null || _b === void 0 ? void 0 : _b.inputRef);
        // We use the renderTarget API to flatten the rendered DOM.
        _this.renderTarget = 
        // N.B. pull out `isOpen` so that it's not forwarded to the DOM.
        function (_a) {
            var _b;
            var isOpen = _a.isOpen, targetProps = tslib_1.__rest(_a, ["isOpen"]);
            var _c = _this.props, fill = _c.fill, _d = _c.popoverProps, popoverProps = _d === void 0 ? {} : _d;
            var _f = popoverProps.targetTagName, targetTagName = _f === void 0 ? "div" : _f;
            return React.createElement(targetTagName, tslib_1.__assign(tslib_1.__assign({}, targetProps), { className: (0, classnames_1.default)(core_1.Classes.CONTROL_GROUP, targetProps.className, (_b = {},
                    _b[core_1.Classes.FILL] = fill,
                    _b)) }), _this.renderInputGroup(core_1.Boundary.START), _this.renderInputGroup(core_1.Boundary.END));
        };
        _this.renderInputGroup = function (boundary) {
            var _a;
            var inputProps = _this.getInputProps(boundary);
            var handleInputEvent = boundary === core_1.Boundary.START ? _this.handleStartInputEvent : _this.handleEndInputEvent;
            return (React.createElement(core_1.InputGroup, tslib_1.__assign({ autoComplete: "off", disabled: (_a = inputProps === null || inputProps === void 0 ? void 0 : inputProps.disabled) !== null && _a !== void 0 ? _a : _this.props.disabled, fill: _this.props.fill }, inputProps, { intent: _this.isInputInErrorState(boundary) ? core_1.Intent.DANGER : inputProps === null || inputProps === void 0 ? void 0 : 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) })));
        };
        // Callbacks - DateRangePicker3
        // ===========================
        _this.handleDateRangePickerChange = function (selectedRange, didSubmitWithEnter) {
            var _a, _b;
            if (didSubmitWithEnter === void 0) { didSubmitWithEnter = false; }
            // ignore mouse events in the date-range picker if the popover is animating closed.
            if (!_this.state.isOpen) {
                return;
            }
            var selectedStart = selectedRange[0], selectedEnd = selectedRange[1];
            var isOpen = true;
            var isStartInputFocused;
            var isEndInputFocused;
            var startHoverString;
            var endHoverString;
            var boundaryToModify;
            if (selectedStart == null) {
                // focus the start field by default or if only an end date is specified
                if (_this.props.timePrecision == null) {
                    isStartInputFocused = true;
                    isEndInputFocused = false;
                }
                else {
                    isStartInputFocused = false;
                    isEndInputFocused = false;
                    boundaryToModify = core_1.Boundary.START;
                }
                // for clarity, hide the hover string until the mouse moves over a different date
                startHoverString = null;
            }
            else if (selectedEnd == null) {
                // focus the end field if a start date is specified
                if (_this.props.timePrecision == null) {
                    isStartInputFocused = false;
                    isEndInputFocused = true;
                }
                else {
                    isStartInputFocused = false;
                    isEndInputFocused = false;
                    boundaryToModify = core_1.Boundary.END;
                }
                endHoverString = null;
            }
            else if (_this.props.closeOnSelection) {
                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
                    // the flag to false won't have an effect anyway, so leave it true.
                    isEndInputFocused = true;
                }
                else {
                    isEndInputFocused = false;
                    boundaryToModify = core_1.Boundary.END;
                }
            }
            else if (_this.state.lastFocusedField === core_1.Boundary.START) {
                // keep the start field focused
                if (_this.props.timePrecision == null) {
                    isStartInputFocused = true;
                    isEndInputFocused = false;
                }
                else {
                    isStartInputFocused = false;
                    isEndInputFocused = false;
                    boundaryToModify = core_1.Boundary.START;
                }
            }
            else if (_this.props.timePrecision == null) {
                // keep the end field focused
                isStartInputFocused = false;
                isEndInputFocused = true;
            }
            else {
                isStartInputFocused = false;
                isEndInputFocused = false;
                boundaryToModify = core_1.Boundary.END;
            }
            var baseStateChange = {
                boundaryToModify: boundaryToModify,
                endHoverString: endHoverString,
                endInputString: _this.formatDate(selectedEnd),
                isEndInputFocused: isEndInputFocused,
                isOpen: isOpen,
                isStartInputFocused: isStartInputFocused,
                selectedShortcutIndex: -1,
                startHoverString: startHoverString,
                startInputString: _this.formatDate(selectedStart),
                wasLastFocusChangeDueToHover: false,
            };
            if (_this.isControlled()) {
                _this.setState(baseStateChange);
            }
            else {
                _this.setState(tslib_1.__assign(tslib_1.__assign({}, baseStateChange), { selectedEnd: selectedEnd, selectedStart: selectedStart }));
            }
            (_b = (_a = _this.props).onChange) === null || _b === void 0 ? void 0 : _b.call(_a, selectedRange);
        };
        _this.handleShortcutChange = function (_, selectedShortcutIndex) {
            _this.setState({ selectedShortcutIndex: selectedShortcutIndex });
        };
        _this.handleDateRangePickerHoverChange = function (hoveredRange, _hoveredDay, hoveredBoundary) {
            // ignore mouse events in the date-range picker if the popover is animating closed.
            if (!_this.state.isOpen) {
                return;
            }
            if (hoveredRange == null) {
                // undo whatever focus changes we made while hovering over various calendar dates
                var isEndInputFocused = _this.state.boundaryToModify === core_1.Boundary.END;
                _this.setState({
                    endHoverString: null,
                    isEndInputFocused: isEndInputFocused,
                    isStartInputFocused: !isEndInputFocused,
                    lastFocusedField: _this.state.boundaryToModify,
                    startHoverString: null,
                });
            }
            else {
                var hoveredStart = hoveredRange[0], hoveredEnd = hoveredRange[1];
                var isStartInputFocused = hoveredBoundary != null ? hoveredBoundary === core_1.Boundary.START : _this.state.isStartInputFocused;
                var isEndInputFocused = hoveredBoundary != null ? hoveredBoundary === core_1.Boundary.END : _this.state.isEndInputFocused;
                _this.setState({
                    endHoverString: _this.formatDate(hoveredEnd),
                    isEndInputFocused: isEndInputFocused,
                    isStartInputFocused: isStartInputFocused,
                    lastFocusedField: isStartInputFocused ? core_1.Boundary.START : core_1.Boundary.END,
                    shouldSelectAfterUpdate: _this.props.selectAllOnFocus,
                    startHoverString: _this.formatDate(hoveredStart),
                    wasLastFocusChangeDueToHover: true,
                });
            }
        };
        // Callbacks - Input
        // =================
        // instantiate these two functions once so we don't have to for each callback on each render.
        _this.handleStartInputEvent = function (e) {
            _this.handleInputEvent(e, core_1.Boundary.START);
        };
        _this.handleEndInputEvent = function (e) {
            _this.handleInputEvent(e, core_1.Boundary.END);
        };
        _this.handleInputEvent = function (e, boundary) {
            var _a, _b, _c, _d, _f, _g;
            var inputProps = _this.getInputProps(boundary);
            switch (e.type) {
                case "blur":
                    _this.handleInputBlur(e, boundary);
                    (_a = inputProps === null || inputProps === void 0 ? void 0 : inputProps.onBlur) === null || _a === void 0 ? void 0 : _a.call(inputProps, e);
                    break;
                case "change":
                    _this.handleInputChange(e, boundary);
                    (_b = inputProps === null || inputProps === void 0 ? void 0 : inputProps.onChange) === null || _b === void 0 ? void 0 : _b.call(inputProps, e);
                    break;
                case "click":
                    e = e;
                    _this.handleInputClick(e);
                    (_c = inputProps === null || inputProps === void 0 ? void 0 : inputProps.onClick) === null || _c === void 0 ? void 0 : _c.call(inputProps, e);
                    break;
                case "focus":
                    _this.handleInputFocus(e, boundary);
                    (_d = inputProps === null || inputProps === void 0 ? void 0 : inputProps.onFocus) === null || _d === void 0 ? void 0 : _d.call(inputProps, e);
                    break;
                case "keydown":
                    e = e;
                    _this.handleInputKeyDown(e, boundary);
                    (_f = inputProps === null || inputProps === void 0 ? void 0 : inputProps.onKeyDown) === null || _f === void 0 ? void 0 : _f.call(inputProps, e);
                    break;
                case "mousedown":
                    e = e;
                    _this.handleInputMouseDown();
                    (_g = inputProps === null || inputProps === void 0 ? void 0 : inputProps.onMouseDown) === null || _g === void 0 ? void 0 : _g.call(inputProps, e);
                    break;
                default:
                    break;
            }
        };
        // add a keydown listener to persistently change focus when tabbing:
        // - if focused in start field, Tab moves focus to end field
        // - if focused in end field, Shift+Tab moves focus to start field
        _this.handleInputKeyDown = function (e, boundary) {
            var _a, _b;
            var isArrowKeyPresssed = e.key === "ArrowUp" || e.key === "ArrowDown" || e.key === "ArrowLeft" || e.key === "ArrowRight";
            var isTabPressed = e.key === "Tab";
            var isEnterPressed = e.key === "Enter";
            var isEscapeKeyPressed = e.key === "Escape";
            var isShiftPressed = e.shiftKey;
            var _c = _this.state, selectedStart = _c.selectedStart, selectedEnd = _c.selectedEnd;
            if (isArrowKeyPresssed) {
                _this.handleInputArrowKeyDown(e, boundary);
                return;
            }
            if (isEscapeKeyPressed) {
                (_a = _this.startInputElement) === null || _a === void 0 ? void 0 : _a.blur();
                (_b = _this.endInputElement) === null || _b === void 0 ? void 0 : _b.blur();
                _this.setState({ isEndInputFocused: false, isOpen: false, isStartInputFocused: false });
                return;
            }
            // order of JS events is our enemy here. when tabbing between fields,
            // this handler will fire in the middle of a focus exchange when no
            // field is currently focused. we work around this by referring to the
            // most recently focused field, rather than the currently focused field.
            var wasStartFieldFocused = _this.state.lastFocusedField === core_1.Boundary.START;
            var wasEndFieldFocused = _this.state.lastFocusedField === core_1.Boundary.END;
            // move focus to the other field
            if (isTabPressed) {
                var isEndInputFocused = void 0;
                var isStartInputFocused = void 0;
                var isOpen = true;
                if (wasStartFieldFocused && !isShiftPressed) {
                    isStartInputFocused = false;
                    isEndInputFocused = true;
                    // prevent the default focus-change behavior to avoid race conditions;
                    // we'll handle the focus change ourselves in componentDidUpdate.
                    e.preventDefault();
                }
                else if (wasEndFieldFocused && isShiftPressed) {
                    isStartInputFocused = true;
                    isEndInputFocused = false;
                    e.preventDefault();
                }
                else {
                    // don't prevent default here, otherwise Tab won't do anything.
                    isStartInputFocused = false;
                    isEndInputFocused = false;
                    isOpen = false;
                }
                _this.setState({
                    isEndInputFocused: isEndInputFocused,
                    isOpen: isOpen,
                    isStartInputFocused: isStartInputFocused,
                    wasLastFocusChangeDueToHover: false,
                });
            }
            else if (wasStartFieldFocused && isEnterPressed) {
                var nextStartDate = _this.parseDate(_this.state.startInputString);
                _this.handleDateRangePickerChange([nextStartDate, selectedEnd], true);
            }
            else if (wasEndFieldFocused && isEnterPressed) {
                var nextEndDate = _this.parseDate(_this.state.endInputString);
                _this.handleDateRangePickerChange([selectedStart, nextEndDate], true);
            }
            else {
                // let the default keystroke happen without side effects
                return;
            }
        };
        _this.handleInputArrowKeyDown = function (e, boundary) {
            var _a;
            var _b, _c, _d;
            var _f = _this.props, minDate = _f.minDate, maxDate = _f.maxDate;
            var isOpen = _this.state.isOpen;
            var inputElement = boundary === core_1.Boundary.START ? _this.startInputElement : _this.endInputElement;
            if (!isOpen || !(0, dateRangeInputUilts_1.isEntireInputSelected)(inputElement)) {
                return;
            }
            var shiftedDate = (_b = _this.getNextDateForArrowKeyNavigation(e.key, boundary)) !== null && _b !== void 0 ? _b : _this.getDefaultDateForArrowKeyNavigation(e.key, boundary);
            if (shiftedDate == null) {
                return;
            }
            // We've commited to moving the selection, prevent the default arrow key interactions
            e.preventDefault();
            var clampedDate = (0, dateRangeInputUilts_1.clampDate)(shiftedDate, minDate, maxDate);
            var keys = _this.getStateKeysAndValuesForBoundary(boundary).keys;
            var nextState = (_a = {},
                _a[keys.inputString] = _this.formatDate(clampedDate),
                _a.selectedShortcutIndex = -1,
                _a.shouldSelectAfterUpdate = true,
                _a);
            if (!_this.isControlled()) {
                nextState[keys.selectedValue] = clampedDate;
            }
            (_d = (_c = _this.props).onChange) === null || _d === void 0 ? void 0 : _d.call(_c, _this.getDateRangeForCallback(clampedDate, boundary));
            _this.setState(nextState);
        };
        _this.handleInputMouseDown = function () {
            // clicking in the field constitutes an explicit focus change. we update
            // the flag on "mousedown" instead of on "click", because it needs to be
            // set before onFocus is called ("click" triggers after "focus").
            _this.setState({ wasLastFocusChangeDueToHover: false });
        };
        _this.handleInputClick = function (e) {
            // unless we stop propagation on this event, a click within an input
            // will close the popover almost as soon as it opens.
            e.stopPropagation();
        };
        _this.handleInputFocus = function (_e, boundary) {
            var _a;
            var _b = _this.getStateKeysAndValuesForBoundary(boundary), keys = _b.keys, values = _b.values;
            var isValueControlled = _this.isControlled();
            // We may be reacting to a programmatic focus triggered by componentDidUpdate() at a point when
            // values.selectedValue may not have been updated yet in controlled mode, so we must use values.controlledValue
            // in that case.
            var inputString = formatDateString(isValueControlled ? values.controlledValue : values.selectedValue, _this.props, _this.state.locale, true);
            // change the boundary only if the user explicitly focused in the field.
            // focus changes from hovering don't count; they're just temporary.
            var boundaryToModify = _this.state.wasLastFocusChangeDueToHover ? _this.state.boundaryToModify : boundary;
            _this.setState((_a = {},
                _a[keys.inputString] = inputString,
                _a[keys.isInputFocused] = true,
                _a.boundaryToModify = boundaryToModify,
                _a.isOpen = true,
                _a.lastFocusedField = boundary,
                _a.shouldSelectAfterUpdate = _this.props.selectAllOnFocus,
                _a.wasLastFocusChangeDueToHover = false,
                _a));
        };
        _this.handleInputBlur = function (_e, boundary) {
            var _a, _b, _c, _d;
            var _f, _g;
            var _h = _this.getStateKeysAndValuesForBoundary(boundary), keys = _h.keys, values = _h.values;
            var maybeNextDate = _this.parseDate(values.inputString);
            var isValueControlled = _this.isControlled();
            var nextState = (_a = {},
                _a[keys.isInputFocused] = false,
                _a.shouldSelectAfterUpdate = false,
                _a);
            if (_this.isInputEmpty(values.inputString)) {
                if (isValueControlled) {
                    nextState = tslib_1.__assign(tslib_1.__assign({}, nextState), (_b = {}, _b[keys.inputString] = formatDateString(values.controlledValue, _this.props, _this.state.locale), _b));
                }
                else {
                    nextState = tslib_1.__assign(tslib_1.__assign({}, nextState), (_c = {}, _c[keys.inputString] = null, _c[keys.selectedValue] = null, _c));
                }
            }
            else if (!_this.isNextDateRangeValid(maybeNextDate, boundary)) {
                if (!isValueControlled) {
                    nextState = tslib_1.__assign(tslib_1.__assign({}, nextState), (_d = {}, _d[keys.inputString] = null, _d[keys.selectedValue] = maybeNextDate, _d));
                }
                (_g = (_f = _this.props).onError) === null || _g === void 0 ? void 0 : _g.call(_f, _this.getDateRangeForCallback(maybeNextDate, boundary));
            }
            _this.setState(nextState);
        };
        _this.handleInputChange = function (e, boundary) {
            var _a, _b, _c, _d, _f;
            var _g, _h, _j, _k;
            var inputString = e.target.value;
            var keys = _this.getStateKeysAndValuesForBoundary(boundary).keys;
            var maybeNextDate = _this.parseDate(inputString);
            var isValueControlled = _this.isControlled();
            var nextState = { shouldSelectAfterUpdate: false };
            if (inputString.length === 0) {
                // this case will be relevant when we start showing the hovered range in the input
                // fields. goal is to show an empty field for clarity until the mouse moves over a
                // different date.
                var baseState = tslib_1.__assign(tslib_1.__assign({}, nextState), (_a = {}, _a[keys.inputString] = "", _a));
                if (isValueControlled) {
                    nextState = baseState;
                }
                else {
                    nextState = tslib_1.__assign(tslib_1.__assign({}, baseState), (_b = {}, _b[keys.selectedValue] = null, _b));
                }
                (_h = (_g = _this.props).onChange) === null || _h === void 0 ? void 0 : _h.call(_g, _this.getDateRangeForCallback(null, boundary));
            }
            else if (_this.isDateValidAndInRange(maybeNextDate)) {
                // note that error cases that depend on both fields (e.g. overlapping dates) should fall
                // through into this block so that the UI can update immediately, possibly with an error
                // message on the other field.
                // also, clear the hover string to ensure the most recent keystroke appears.
                var baseState = tslib_1.__assign(tslib_1.__assign({}, nextState), (_c = {}, _c[keys.hoverString] = null, _c[keys.inputString] = inputString, _c));
                if (isValueControlled) {
                    nextState = baseState;
                }
                else {
                    nextState = tslib_1.__assign(tslib_1.__assign({}, baseState), (_d = {}, _d[keys.selectedValue] = maybeNextDate, _d));
                }
                if (_this.isNextDateRangeValid(maybeNextDate, boundary)) {
                    (_k = (_j = _this.props).onChange) === null || _k === void 0 ? void 0 : _k.call(_j, _this.getDateRangeForCallback(maybeNextDate, boundary));
                }
            }
            else {
                // again, clear the hover string to ensure the most recent keystroke appears
                nextState = tslib_1.__assign(tslib_1.__assign({}, nextState), (_f = {}, _f[keys.inputString] = inputString, _f[keys.hoverString] = null, _f));
            }
            _this.setState(nextState);
        };
        // Callbacks - Popover
        // ===================
        _this.handlePopoverClose = function (event) {
            var _a, _b;
            _this.setState({ isOpen: false });
            (_b = (_a = _this.props.popoverProps) === null || _a === void 0 ? void 0 : _a.onClose) === null || _b === void 0 ? void 0 : _b.call(_a, event);
        };
        _this.getIsOpenValueWhenDateChanges = function (nextSelectedStart, nextSelectedEnd) {
            if (_this.props.closeOnSelection) {
                // trivial case when TimePicker is not shown
                if (_this.props.timePrecision == null) {
                    return false;
                }
                var fallbackDate = (0, dateRangeInputUilts_1.getTodayAtMidnight)();
                var _a = _this.getSelectedRange([fallbackDate, fallbackDate]), selectedStart = _a[0], selectedEnd = _a[1];
                // case to check if the user has changed TimePicker values
                if (datetime_1.DateUtils.isSameTime(selectedStart, nextSelectedStart) &&
                    datetime_1.DateUtils.isSameTime(selectedEnd, nextSelectedEnd)) {
                    return false;
                }
                return true;
            }
            return true;
        };
        _this.getInitialRange = function (props) {
            if (props === void 0) { props = _this.props; }
            var defaultValue = props.defaultValue, value = props.value;
            if (value != null) {
                return value;
            }
            else if (defaultValue != null) {
                return defaultValue;
            }
            else {
                return [null, null];
            }
        };
        _this.getSelectedRange = function (fallbackRange) {
            var _a;
            var selectedStart;
            var selectedEnd;
            if (_this.isControlled()) {
                _a = _this.props.value, selectedStart = _a[0], selectedEnd = _a[1];
            }
            else {
                selectedStart = _this.state.selectedStart;
                selectedEnd = _this.state.selectedEnd;
            }
            // this helper function checks if the provided boundary date *would* overlap the selected
            // other boundary date. providing the already-selected start date simply tells us if we're
            // currently in an overlapping state.
            var doBoundaryDatesOverlap = _this.doBoundaryDatesOverlap(selectedStart, core_1.Boundary.START);
            var dateRange = [selectedStart, doBoundaryDatesOverlap ? undefined : selectedEnd];
            return dateRange.map(function (selectedBound, index) {
                var fallbackDate = fallbackRange != null ? fallbackRange[index] : undefined;
                return _this.isDateValidAndInRange(selectedBound !== null && selectedBound !== void 0 ? selectedBound : null) ? selectedBound : fallbackDate;
            });
        };
        _this.getInputDisplayString = function (boundary) {
            var values = _this.getStateKeysAndValuesForBoundary(boundary).values;
            var isInputFocused = values.isInputFocused, inputString = values.inputString, selectedValue = values.selectedValue, hoverString = values.hoverString;
            if (hoverString != null) {
                return hoverString;
            }
            else if (isInputFocused) {
                return inputString == null ? "" : inputString;
            }
            else if (selectedValue == null) {
                return "";
            }
            else if (_this.doesEndBoundaryOverlapStartBoundary(selectedValue, boundary)) {
                return _this.props.overlappingDatesMessage;
            }
            else {
                return formatDateString(selectedValue, _this.props, _this.state.locale);
            }
        };
        _this.getInputPlaceholderString = function (boundary) {
            var isStartBoundary = boundary === core_1.Boundary.START;
            var isEndBoundary = boundary === core_1.Boundary.END;
            var inputProps = _this.getInputProps(boundary);
            var isInputFocused = _this.getStateKeysAndValuesForBoundary(boundary).values.isInputFocused;
            // use the custom placeholder text for the input, if providied
            if ((inputProps === null || inputProps === void 0 ? void 0 : inputProps.placeholder) != null) {
                return inputProps.placeholder;
            }
            else if (isStartBoundary) {
                return isInputFocused ? _this.state.formattedMinDateString : "Start date";
            }
            else if (isEndBoundary) {
                return isInputFocused ? _this.state.formattedMaxDateString : "End date";
            }
            else {
                return "";
            }
        };
        _this.getInputProps = function (boundary) {
            return boundary === core_1.Boundary.START ? _this.props.startInputProps : _this.props.endInputProps;
        };
        _this.getInputRef = function (boundary) {
            return boundary === core_1.Boundary.START ? _this.handleStartInputRef : _this.handleEndInputRef;
        };
        _this.getStateKeysAndValuesForBoundary = function (boundary) {
            var controlledRange = _this.props.value;
            if (boundary === core_1.Boundary.START) {
                return {
                    keys: {
                        hoverString: "startHoverString",
                        inputString: "startInputString",
                        isInputFocused: "isStartInputFocused",
                        selectedValue: "selectedStart",
                    },
                    values: {
                        controlledValue: controlledRange != null ? controlledRange[0] : undefined,
                        hoverString: _this.state.startHoverString,
                        inputString: _this.state.startInputString,
                        isInputFocused: _this.state.isStartInputFocused,
                        selectedValue: _this.state.selectedStart,
                    },
                };
            }
            else {
                return {
                    keys: {
                        hoverString: "endHoverString",
                        inputString: "endInputString",
                        isInputFocused: "isEndInputFocused",
                        selectedValue: "selectedEnd",
                    },
                    values: {
                        controlledValue: controlledRange != null ? controlledRange[1] : undefined,
                        hoverString: _this.state.endHoverString,
                        inputString: _this.state.endInputString,
                        isInputFocused: _this.state.isEndInputFocused,
                        selectedValue: _this.state.selectedEnd,
                    },
                };
            }
        };
        _this.getDateRangeForCallback = function (currDate, currBoundary) {
            var otherBoundary = _this.getOtherBoundary(currBoundary);
            var otherDate = _this.getStateKeysAndValuesForBoundary(otherBoundary).values.selectedValue;
            return currBoundary === core_1.Boundary.START ? [currDate, otherDate] : [otherDate, currDate];
        };
        _this.getOtherBoundary = function (boundary) {
            return boundary === core_1.Boundary.START ? core_1.Boundary.END : core_1.Boundary.START;
        };
        _this.doBoundaryDatesOverlap = function (date, boundary) {
            var allowSingleDayRange = _this.props.allowSingleDayRange;
            var otherBoundary = _this.getOtherBoundary(boundary);
            var otherBoundaryDate = _this.getStateKeysAndValuesForBoundary(otherBoundary).values.selectedValue;
            if (date == null || otherBoundaryDate == null) {
                return false;
            }
            if (boundary === core_1.Boundary.START) {
                var isAfter = date > otherBoundaryDate;
                return isAfter || (!allowSingleDayRange && (0, date_fns_1.isSameDay)(date, otherBoundaryDate));
            }
            else {
                var isBefore = date < otherBoundaryDate;
                return isBefore || (!allowSingleDayRange && (0, date_fns_1.isSameDay)(date, otherBoundaryDate));
            }
        };
        /**
         * Returns true if the provided boundary is an END boundary overlapping the
         * selected start date. (If the boundaries overlap, we consider the END
         * boundary to be erroneous.)
         */
        _this.doesEndBoundaryOverlapStartBoundary = function (boundaryDate, boundary) {
            return boundary === core_1.Boundary.START ? false : _this.doBoundaryDatesOverlap(boundaryDate, boundary);
        };
        _this.isControlled = function () { return _this.props.value !== undefined; };
        _this.isInputEmpty = function (inputString) { return inputString == null || inputString.length === 0; };
        _this.isInputInErrorState = function (boundary) {
            var values = _this.getStateKeysAndValuesForBoundary(boundary).values;
            var isInputFocused = values.isInputFocused, hoverString = values.hoverString, inputString = values.inputString, selectedValue = values.selectedValue;
            if (hoverString != null || _this.isInputEmpty(inputString)) {
                // don't show an error state while we're hovering over a valid date.
                return false;
            }
            var boundaryValue = isInputFocused ? _this.parseDate(inputString) : selectedValue;
            return (boundaryValue != null &&
                (!_this.isDateValidAndInRange(boundaryValue) ||
                    _this.doesEndBoundaryOverlapStartBoundary(boundaryValue, boundary)));
        };
        _this.isDateValidAndInRange = function (date) {
            // min/max dates defined in defaultProps
            return (0, date_fns_1.isValid)(date) && datetime_1.DateUtils.isDayInRange(date, [_this.props.minDate, _this.props.maxDate]);
        };
        // this is a slightly kludgy function, but it saves us a good amount of repeated code between
        // the constructor and componentDidUpdate.
        _this.formatMinMaxDateString = function (props, propName) {
            var _a, _b;
            var date = props[propName];
            // N.B. default values are applied only if a prop is strictly `undefined`
            // See: https://facebook.github.io/react/docs/react-component.html#defaultprops
            var defaultDate = DateRangeInput3.defaultProps[propName];
            // N.B. this.state will be undefined in the constructor, so we need a fallback in that case
            var maybeLocale = ((_b = (_a = _this.state) === null || _a === void 0 ? void 0 : _a.locale) !== null && _b !== void 0 ? _b : typeof props.locale === "string") ? undefined : props.locale;
            return formatDateString(date !== null && date !== void 0 ? date : defaultDate, _this.props, maybeLocale);
        };
        _this.parseDate = function (dateString) {
            var _a;
            if (dateString === undefined ||
                dateString === _this.props.outOfRangeMessage ||
                dateString === _this.props.invalidDateMessage) {
                return null;
            }
            // HACKHACK: this code below is largely copied from the `useDateParser()` hook, which is the preferred
            // implementation that we can migrate to once DateRangeInput3 is a function component.
            var _b = _this.props, dateFnsFormat = _b.dateFnsFormat, localeFromProps = _b.locale, parseDate = _b.parseDate, timePickerProps = _b.timePickerProps, timePrecision = _b.timePrecision;
            var locale = _this.state.locale;
            var newDate = null;
            if (parseDate !== undefined) {
                // user-provided date parser
                newDate = parseDate(dateString, (_a = locale === null || locale === void 0 ? void 0 : locale.code) !== null && _a !== void 0 ? _a : (0, dateFnsLocaleProps_1.getLocaleCodeFromProps)(localeFromProps));
            }
            else {
                // use user-provided date-fns format or one of the default formats inferred from time picker props
                var format = dateFnsFormat !== null && dateFnsFormat !== void 0 ? dateFnsFormat : (0, dateFnsFormatUtils_1.getDefaultDateFnsFormat)({ timePickerProps: timePickerProps, timePrecision: timePrecision });
                newDate = (0, dateFnsFormatUtils_1.getDateFnsParser)(format, locale)(dateString);
            }
            return newDate === false ? (0, dateRangeInputUilts_1.getTodayAtMidnight)() : newDate;
        };
        // called on date hover & selection
        _this.formatDate = function (date) {
            var _a;
            if (!_this.isDateValidAndInRange(date)) {
                return "";
            }
            // HACKHACK: the code below is largely copied from the `useDateFormatter()` hook, which is the preferred
            // implementation that we can migrate to once DateRangeInput3 is a function component.
            var _b = _this.props, dateFnsFormat = _b.dateFnsFormat, formatDate = _b.formatDate, localeFromProps = _b.locale, timePickerProps = _b.timePickerProps, timePrecision = _b.timePrecision;
            var locale = _this.state.locale;
            if (formatDate !== undefined) {
                // user-provided date formatter
                return formatDate(date, (_a = locale === null || locale === void 0 ? void 0 : locale.code) !== null && _a !== void 0 ? _a : (0, dateFnsLocaleProps_1.getLocaleCodeFromProps)(localeFromProps));
            }
            else {
                // use user-provided date-fns format or one of the default formats inferred from time picker props
                var format = dateFnsFormat !== null && dateFnsFormat !== void 0 ? dateFnsFormat : (0, dateFnsFormatUtils_1.getDefaultDateFnsFormat)({ timePickerProps: timePickerProps, timePrecision: timePrecision });
                return (0, dateFnsFormatUtils_1.getDateFnsFormatter)(format, locale)(date);
            }
        };
        var _c = _this.getInitialRange(), selectedStart = _c[0], selectedEnd = _c[1];
        _this.state = {
            formattedMaxDateString: _this.formatMinMaxDateString(props, "maxDate"),
            formattedMinDateString: _this.formatMinMaxDateString(props, "minDate"),
            isEndInputFocused: false,
            isOpen: false,
            isStartInputFocused: false,
            locale: undefined,
            selectedEnd: selectedEnd,
            selectedShortcutIndex: -1,
            selectedStart: selectedStart,
        };
        return _this;
    }
    DateRangeInput3.prototype.componentDidMount = function () {
        return tslib_1.__awaiter(this, void 0, void 0, function () {
            return tslib_1.__generator(this, function (_a) {
                switch (_a.label) {
                    case 0: return [4 /*yield*/, _super.prototype.componentDidMount.call(this)];
                    case 1:
                        _a.sent();
                        return [2 /*return*/];
                }
            });
        });
    };
    DateRangeInput3.prototype.componentDidUpdate = function (prevProps) {
        var _a, _b, _c, _d, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
        return tslib_1.__awaiter(this, void 0, void 0, function () {
            var _r, isStartInputFocused, isEndInputFocused, shouldSelectAfterUpdate, shouldFocusStartInput, shouldFocusEndInput, nextState, _s, selectedStart, selectedEnd, formattedMinDateString, formattedMaxDateString;
            return tslib_1.__generator(this, function (_t) {
                _super.prototype.componentDidUpdate.call(this, prevProps);
                _r = this.state, isStartInputFocused = _r.isStartInputFocused, isEndInputFocused = _r.isEndInputFocused, shouldSelectAfterUpdate = _r.shouldSelectAfterUpdate;
                if (((_a = prevProps.startInputProps) === null || _a === void 0 ? void 0 : _a.inputRef) !== ((_b = this.props.startInputProps) === null || _b === void 0 ? void 0 : _b.inputRef)) {
                    (0, core_1.setRef)((_c = prevProps.startInputProps) === null || _c === void 0 ? void 0 : _c.inputRef, null);
                    this.handleStartInputRef = (0, core_1.refHandler)(this, "startInputElement", (_d = this.props.startInputProps) === null || _d === void 0 ? void 0 : _d.inputRef);
                    (0, core_1.setRef)((_f = this.props.startInputProps) === null || _f === void 0 ? void 0 : _f.inputRef, this.startInputElement);
                }
                if (((_g = prevProps.endInputProps) === null || _g === void 0 ? void 0 : _g.inputRef) !== ((_h = this.props.endInputProps) === null || _h === void 0 ? void 0 : _h.inputRef)) {
                    (0, core_1.setRef)((_j = prevProps.endInputProps) === null || _j === void 0 ? void 0 : _j.inputRef, null);
                    this.handleEndInputRef = (0, core_1.refHandler)(this, "endInputElement", (_k = this.props.endInputProps) === null || _k === void 0 ? void 0 : _k.inputRef);
                    (0, core_1.setRef)((_l = this.props.endInputProps) === null || _l === void 0 ? void 0 : _l.inputRef, this.endInputElement);
                }
                shouldFocusStartInput = this.shouldFocusInputRef(isStartInputFocused, this.startInputElement);
                shouldFocusEndInput = this.shouldFocusInputRef(isEndInputFocused, this.endInputElement);
                if (shouldFocusStartInput) {
                    (_m = this.startInputElement) === null || _m === void 0 ? void 0 : _m.focus();
                }
                else if (shouldFocusEndInput) {
                    (_o = this.endInputElement) === null || _o === void 0 ? void 0 : _o.focus();
                }
                if (isStartInputFocused && shouldSelectAfterUpdate) {
                    (_p = this.startInputElement) === null || _p === void 0 ? void 0 : _p.select();
                }
                else if (isEndInputFocused && shouldSelectAfterUpdate) {
                    (_q = this.endInputElement) === null || _q === void 0 ? void 0 : _q.select();
                }
                nextState = {};
                if (this.props.value !== prevProps.value) {
                    _s = this.getInitialRange(this.props), selectedStart = _s[0], selectedEnd = _s[1];
                    nextState = tslib_1.__assign(tslib_1.__assign({}, nextState), { selectedEnd: selectedEnd, selectedStart: selectedStart });
                }
                // cache the formatted date strings to avoid computing on each render.
                if (this.props.minDate !== prevProps.minDate) {
                    formattedMinDateString = this.formatMinMaxDateString(this.props, "minDate");
                    nextState = tslib_1.__assign(tslib_1.__assign({}, nextState), { formattedMinDateString: formattedMinDateString });
                }
                if (this.props.maxDate !== prevProps.maxDate) {
                    formattedMaxDateString = this.formatMinMaxDateString(this.props, "maxDate");
                    nextState = tslib_1.__assign(tslib_1.__assign({}, nextState), { formattedMaxDateString: formattedMaxDateString });
                }
                this.setState(nextState);
                return [2 /*return*/];
            });
        });
    };
    DateRangeInput3.prototype.render = function () {
        var _a = this.state, locale = _a.locale, selectedShortcutIndex = _a.selectedShortcutIndex;
        var _b = this.props, _c = _b.popoverProps, popoverProps = _c === void 0 ? {} : _c, popoverRef = _b.popoverRef;
        var popoverContent = (React.createElement(dateRangePicker3_1.DateRangePicker3, tslib_1.__assign({}, this.props, { boundaryToModify: this.state.boundaryToModify, locale: locale !== null && locale !== void 0 ? locale : this.props.locale, onChange: this.handleDateRangePickerChange, onHoverChange: this.handleDateRangePickerHoverChange, onShortcutChange: this.handleShortcutChange, selectedShortcutIndex: selectedShortcutIndex, value: this.getSelectedRange() })));
        // allow custom props for the popover and each input group, but pass them in an order that
        // guarantees only some props are overridable.
        return (React.createElement(core_1.Popover, tslib_1.__assign({ isOpen: this.state.isOpen, placement: "bottom-start" }, popoverProps, { autoFocus: false, className: (0, classnames_1.default)(classes_1.Classes.DATE_RANGE_INPUT, popoverProps.className, this.props.className), content: popoverContent, enforceFocus: false, onClose: this.handlePopoverClose, popoverClassName: (0, classnames_1.default)(classes_1.Classes.DATE_RANGE_INPUT_POPOVER, popoverProps.popoverClassName), ref: popoverRef, renderTarget: this.renderTarget })));
    };
    DateRangeInput3.prototype.validateProps = function (props) {
        if (props.value === null) {
            // throw a blocking error here because we don't handle a null value gracefully across this component
            // (it's not allowed by TS under strict null checks anyway)
            throw new Error(datetime_1.Errors.DATERANGEINPUT_NULL_VALUE);
        }
    };
    DateRangeInput3.prototype.getNextDateForArrowKeyNavigation = function (arrowKey, boundary) {
        var allowSingleDayRange = this.props.allowSingleDayRange;
        var _a = this.getSelectedRange(), selectedStart = _a[0], selectedEnd = _a[1];
        var initialDate = boundary === core_1.Boundary.START ? selectedStart : selectedEnd;
        if (initialDate == null) {
            return undefined;
        }
        var relativeDate = (0, dateRangeInputUilts_1.shiftDateByArrowKey)(initialDate, arrowKey);
        // Ensure that we don't move onto a single day range selection if that is disallowed
        var adjustedStart = selectedStart == null || allowSingleDayRange ? selectedStart : (0, dateRangeInputUilts_1.shiftDateByDays)(selectedStart, 1);
        var adjustedEnd = selectedEnd == null || allowSingleDayRange ? selectedEnd : (0, dateRangeInputUilts_1.shiftDateByDays)(selectedEnd, -1);
        return boundary === core_1.Boundary.START
            ? (0, dateRangeInputUilts_1.clampDate)(relativeDate, undefined, adjustedEnd)
            : (0, dateRangeInputUilts_1.clampDate)(relativeDate, adjustedStart, undefined);
    };
    DateRangeInput3.prototype.getDefaultDateForArrowKeyNavigation = function (arrowKey, boundary) {
        var _a = this.getSelectedRange(), selectedStart = _a[0], selectedEnd = _a[1];
        var otherBoundary = boundary === core_1.Boundary.START ? selectedEnd : selectedStart;
        if (otherBoundary == null) {
            return (0, dateRangeInputUilts_1.getTodayAtMidnight)();
        }
        var isForwardArrowKey = arrowKey === "ArrowRight" || arrowKey === "ArrowDown";
        // If the arrow key direction is in the same direction as the boundary, then moving that way will not create an
        // overlapping date range
        if (isForwardArrowKey === (boundary === core_1.Boundary.END)) {
            return (0, dateRangeInputUilts_1.shiftDateByArrowKey)(otherBoundary, arrowKey);
        }
        return undefined;
    };
    // Helpers
    // =======
    DateRangeInput3.prototype.shouldFocusInputRef = function (isFocused, inputRef) {
        return isFocused && inputRef != null && core_1.Utils.getActiveElement(this.startInputElement) !== inputRef;
    };
    DateRangeInput3.prototype.isNextDateRangeValid = function (nextDate, boundary) {
        return this.isDateValidAndInRange(nextDate) && !this.doBoundaryDatesOverlap(nextDate, boundary);
    };
    DateRangeInput3.defaultProps = {
        allowSingleDayRange: false,
        closeOnSelection: true,
        contiguousCalendarMonths: true,
        dayPickerProps: {},
        disabled: false,
        endInputProps: {},
        invalidDateMessage: "Invalid date",
        locale: "en-US",
        maxDate: datetime_1.DatePickerUtils.getDefaultMaxDate(),
        minDate: datetime_1.DatePickerUtils.getDefaultMinDate(),
        outOfRangeMessage: "Out of range",
        overlappingDatesMessage: "Overlapping dates",
        popoverProps: {},
        selectAllOnFocus: false,
        shortcuts: true,
        singleMonthOnly: false,
        startInputProps: {},
    };
    DateRangeInput3.displayName = "".concat(core_1.DISPLAYNAME_PREFIX, ".DateRangeInput3");
    return DateRangeInput3;
}(dateFnsLocalizedComponent_1.DateFnsLocalizedComponent));
exports.DateRangeInput3 = DateRangeInput3;
// called on initial construction, input focus & blur, and the standard input render path
function formatDateString(date, props, locale, ignoreRange) {
    var _a;
    if (ignoreRange === void 0) { ignoreRange = false; }
    var _b = props, invalidDateMessage = _b.invalidDateMessage, maxDate = _b.maxDate, minDate = _b.minDate, outOfRangeMessage = _b.outOfRangeMessage;
    if (date == null) {
        return "";
    }
    else if (!datetime_1.DateUtils.isDateValid(date)) {
        return invalidDateMessage;
    }
    else if (ignoreRange || datetime_1.DateUtils.isDayInRange(date, [minDate, maxDate])) {
        // HACKHACK: the code below is largely copied from the `useDateFormatter()` hook, which is the preferred
        // implementation that we can migrate to once DateRangeInput3 is a function component.
        var dateFnsFormat = props.dateFnsFormat, formatDate = props.formatDate, localeFromProps = props.locale, timePickerProps = props.timePickerProps, timePrecision = props.timePrecision;
        if (formatDate !== undefined) {
            // user-provided date formatter
            return formatDate(date, (_a = locale === null || locale === void 0 ? void 0 : locale.code) !== null && _a !== void 0 ? _a : (0, dateFnsLocaleProps_1.getLocaleCodeFromProps)(localeFromProps));
        }
        else {
            // use user-provided date-fns format or one of the default formats inferred from time picker props
            var format = dateFnsFormat !== null && dateFnsFormat !== void 0 ? dateFnsFormat : (0, dateFnsFormatUtils_1.getDefaultDateFnsFormat)({ timePickerProps: timePickerProps, timePrecision: timePrecision });
            return (0, dateFnsFormatUtils_1.getDateFnsFormatter)(format, locale)(date);
        }
    }
    else {
        return outOfRangeMessage;
    }
}
//# sourceMappingURL=dateRangeInput3.js.map