"use strict";
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
        return extendStatics(d, b);
    };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
var __assign = (this && this.__assign) || function () {
    __assign = Object.assign || function(t) {
        for (var s, i = 1, n = arguments.length; i < n; i++) {
            s = arguments[i];
            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
                t[p] = s[p];
        }
        return t;
    };
    return __assign.apply(this, arguments);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getAndroidVersion = void 0;
var react_1 = __importDefault(require("react"));
var mask_1 = __importDefault(require("./mask"));
// В эту проверку попадают IE9 и IE10, которые не могут корректно работать с кареткой на событии `input`.
var IS_IE9_10 = typeof window !== 'undefined' && !!window.ActiveXObject;
var IS_ANDROID = typeof window !== 'undefined' && /(android)/i.test(window.navigator.userAgent);
/**
 * Возвращает версию андроида в формате "4.2.1" или false, если не андроид.
 */
function getAndroidVersion() {
    if (!/android/.test(navigator.userAgent.toLowerCase())) {
        return false;
    }
    var userAgent = navigator.userAgent.toLowerCase();
    var match = userAgent.match(/android\s([\d.]*)/);
    return match ? match[1] : false;
}
exports.getAndroidVersion = getAndroidVersion;
// Для IE11 вместо `onChange`, используем событие `onInput`, для правильной работы copy/paste
// Issue на ошибку в React: https://github.com/facebook/react/issues/7211
// Детектим IE11: `Object.hasOwnProperty.call(window, 'ActiveXObject') && !window.ActiveXObject;`
var IS_IE11 = typeof window !== 'undefined' && Object.hasOwnProperty.call(window, 'ActiveXObject') && !window.ActiveXObject;
// Типы операции, которые пользователь может производить с текстовым полем.
var operationType = {
    ADD: 0,
    DELETE: 1,
    REPLACE: 2,
};
/**
 * Возвращает количество не редактируемых символов в строке, в соответствии с указанной маской.
 */
var getSeparatorsAmount = function (str, mask) { return (str.split('').reduce(function (amount, _char, index) {
    if (mask.isEditableIndex(index)) {
        return amount;
    }
    return amount + 1;
}, 0)); };
/**
 * Компонент поля ввода с поддержкой масок.
 * Расширяет стандартный <input /> React-а.
 */
var MaskedInput = /** @class */ (function (_super) {
    __extends(MaskedInput, _super);
    function MaskedInput() {
        var _this = _super !== null && _super.apply(this, arguments) || this;
        _this.value = '';
        _this.caretFixTimeout = null;
        _this.beforeInputSelection = { start: 0, end: 0 };
        _this.handleBeforeInput = function (event) {
            _this.beforeInputSelection = {
                start: _this.input.selectionStart,
                end: _this.input.selectionEnd,
            };
            if (_this.props.onBeforeInput) {
                _this.props.onBeforeInput(event);
            }
        };
        /**
         * Обрабатывает событие «input».
         *
         * @param event The Event object
         */
        _this.handleInput = function (event) {
            var processedEvent = IS_IE9_10 ? event : _this.processInputEvent(event);
            if (_this.props.onInput) {
                _this.props.onInput(processedEvent);
            }
            if (IS_IE11) {
                if (_this.props.onChange) {
                    _this.props.onChange(processedEvent);
                }
            }
        };
        /**
         * Обрабатывает событие «change».
         *
         * @param event The Event object
         */
        _this.handleChange = function (event) {
            if (IS_IE11 || !_this.props.onChange) {
                return;
            }
            var processedInput = IS_IE9_10 ? _this.processInputEvent(event) : event;
            if (_this.props.onChange) {
                _this.props.onChange(processedInput);
            }
        };
        return _this;
    }
    ;
    // eslint-disable-next-line camelcase
    MaskedInput.prototype.UNSAFE_componentWillMount = function () {
        this.setMask(this.props.mask, this.props.formatCharacters, this.props.useWhitespaces);
        this.beforeChangeMask = this.mask;
        this.value = this.mask.format(this.props.value || '');
    };
    // eslint-disable-next-line camelcase
    MaskedInput.prototype.UNSAFE_componentWillReceiveProps = function (nextProps) {
        var reformatValue = false;
        this.beforeChangeMask = this.mask;
        if (this.props.mask !== nextProps.mask || this.props.formatCharacters !== nextProps.formatCharacters) {
            this.setMask(nextProps.mask, nextProps.formatCharacters, nextProps.useWhitespaces);
            reformatValue = true;
        }
        if (reformatValue || this.props.value !== nextProps.value) {
            this.value = this.mask.format(nextProps.value || '');
        }
    };
    MaskedInput.prototype.componentWillUnmount = function () {
        if (this.caretFixTimeout) {
            clearTimeout(this.caretFixTimeout);
            this.caretFixTimeout = null;
        }
    };
    MaskedInput.prototype.render = function () {
        var _this = this;
        var props = __assign({}, this.props);
        delete props.mask;
        delete props.formatCharacters;
        delete props.onProcessInputEvent;
        delete props.useWhitespaces;
        return (react_1.default.createElement("input", __assign({}, props, { ref: function (ref) {
                _this.input = ref;
            }, value: this.value, onBeforeInput: this.handleBeforeInput, onInput: this.handleInput, onChange: this.handleChange })));
    };
    /**
     * Устанавливает фокус на поле ввода.
     */
    MaskedInput.prototype.focus = function () {
        this.input.focus();
    };
    /**
     * Снимает фокус с поля ввода.
     */
    MaskedInput.prototype.blur = function () {
        this.input.blur();
    };
    /**
     * Возвращает ссылку на HTMLElement инпута.
     */
    MaskedInput.prototype.getControl = function () {
        return this.input;
    };
    /**
     * Синхронно обновляет маску на поле ввода.
     *
     * @param newMask Новая маска
     * @param formatCharacters Форматтер маски
     * @param useWhitespaces использовать в маске пробелы
     */
    MaskedInput.prototype.setMask = function (newMask, formatCharacters, useWhitespaces) {
        if (this.maskPattern !== newMask || this.formatCharacters !== formatCharacters) {
            this.mask = new mask_1.default(newMask, formatCharacters, useWhitespaces);
            this.maskPattern = newMask;
            this.formatCharacters = formatCharacters;
        }
    };
    // eslint-disable-next-line complexity
    MaskedInput.prototype.processInputEvent = function (event) {
        if (this.props.onProcessInputEvent) {
            this.props.onProcessInputEvent(event);
        }
        var prevSelection = this.input.selectionStart;
        var newValue = event.target.value;
        var currentValue = this.value;
        var formattedValue = this.mask.format(newValue);
        this.value = formattedValue;
        // eslint-disable-next-line no-param-reassign
        event.target.value = formattedValue;
        // Если изменение поля ввода произошло не в конце ввода,
        // то необходимо починить стандартное поведение Реакта и
        // вернуть каретку к последнему изменению.
        if (prevSelection <= currentValue.length) {
            var newSelection = prevSelection;
            // Определяем тип операции, который был произведен над текстовым полем.
            var opType = newValue.length >= currentValue.length ? operationType.ADD : operationType.DELETE;
            // На пользовательском инпуте было выделение перед операцией,
            // значит могла быть операция или удаления или замены.
            var beforeInputSelectionLength = this.beforeInputSelection.end - this.beforeInputSelection.start;
            if (beforeInputSelectionLength >= 1) {
                if (newValue.length !== currentValue.length - beforeInputSelectionLength) {
                    opType = operationType.REPLACE;
                }
            }
            var beforeChangeSeparatorsAmount = getSeparatorsAmount(currentValue.slice(0, prevSelection), this.beforeChangeMask);
            var afterChangeSeparatorsAmount = getSeparatorsAmount(formattedValue.slice(0, newSelection), this.mask);
            // Двигаем каретку вправо, если слева от каретки добавились не редактируемые символы
            var shouldShiftCaret = beforeChangeSeparatorsAmount < afterChangeSeparatorsAmount
                && (opType === operationType.ADD || opType === operationType.REPLACE)
                && this.beforeChangeMask.isEditableIndex(this.beforeInputSelection.start)
                && this.mask.isEditableIndex(newSelection);
            // Двигаем каретку влево, если слева от каретки добавились не редактируемые символы
            var shouldUnshiftCaret = beforeChangeSeparatorsAmount > afterChangeSeparatorsAmount
                && opType === operationType.DELETE
                && (this.mask.isEditableIndex(newSelection - 1) && newSelection > 0);
            if (shouldUnshiftCaret || shouldShiftCaret) {
                newSelection += (afterChangeSeparatorsAmount - beforeChangeSeparatorsAmount);
            }
            // Для операции добавления и замены, если мы стояли на нередактируемом символе,
            // то добавляем сдвиг до ближайшего редактируемого.
            if (opType === operationType.ADD || opType === operationType.REPLACE) {
                var index = this.beforeInputSelection.start;
                while (!this.beforeChangeMask.isEditableIndex(index) && index < formattedValue.length) {
                    index += 1;
                }
                newSelection += index - this.beforeInputSelection.start;
            }
            // Если вдруг попали на нередактируемый символ маски,
            // то подвигаем курсом до ближайшего редактируемого.
            if (opType === operationType.ADD || opType === operationType.REPLACE) {
                while (!this.mask.isEditableIndex(newSelection) && newSelection < formattedValue.length) {
                    newSelection += 1;
                }
            }
            else if (opType === operationType.DELETE) {
                while (!this.mask.isEditableIndex(newSelection - 1) && newSelection > 0) {
                    newSelection -= 1;
                }
            }
            // Положение каретки с учетом первого и последнего редактируемого символа маски.
            var clampedSection = this.clampSelection(newSelection);
            // Фикс бага смещения каретки в браузере на андроидах Jelly Bean (c 4.1 по 4.3)
            var offsetSection = opType === operationType.ADD
                && IS_ANDROID && parseFloat(getAndroidVersion()) < 4.4 ? 1 : 0;
            this.setInputSelection(clampedSection + offsetSection);
        }
        else if (IS_ANDROID) {
            // Если изменение поля произошло в конце ввода.
            // Android chrome имеет дефект с автоматической установкой каретки
            // при использовании клавиатуры отличной от type="text".
            this.setInputSelectionByTimeout(event.target.selectionStart);
        }
        return event;
    };
    /**
     * Возвращает положение каретки с учетом первого и последнего редактируемого символа маски.
     *
     * @param selection Положение каретки
     */
    MaskedInput.prototype.clampSelection = function (selection) {
        if (selection < this.mask.firstEditableIndex) {
            return this.mask.firstEditableIndex;
        }
        // +1 разрешает каретке становится в конец поля ввода при необходимости.
        if (selection > this.mask.lastEditableIndex + 1) {
            return this.mask.lastEditableIndex + 1;
        }
        return selection;
    };
    /**
     * Устанавливает каретку поля ввода в новую позицию.
     *
     * @param selection Новое положение каретки
     */
    MaskedInput.prototype.setInputSelection = function (selection) {
        this.input.selectionStart = selection;
        this.input.selectionEnd = selection;
        // IE10 не умеет синхронно в событие `change` переставлять каретку.
        // Android chrome имеет дефект с автоматической установкой каретки
        // при использовании клавиатуры отличной от type="text".
        if (IS_IE9_10 || IS_ANDROID) {
            this.setInputSelectionByTimeout(selection);
        }
    };
    /**
     * Устанавливает каретку поля ввода в заданную позицию асинхронно.
     *
     * Во-избежание дефекта с установкой каретки, наблюдаемом в мобильных браузерах, а так же
     * браузерах IE9-10, установка происходит асинхронно, с минимальной задержкой,
     * с помощью [setTimeout] обертки.
     *
     * @param selection Положение каретки
     */
    MaskedInput.prototype.setInputSelectionByTimeout = function (selection) {
        var _this = this;
        if (this.caretFixTimeout) {
            clearTimeout(this.caretFixTimeout);
            this.caretFixTimeout = null;
        }
        this.caretFixTimeout = setTimeout(function () {
            _this.caretFixTimeout = null;
            _this.input.selectionStart = selection;
            _this.input.selectionEnd = selection;
        }, 0);
    };
    return MaskedInput;
}(react_1.default.PureComponent));
exports.default = MaskedInput;

//# sourceMappingURL=masked-input.js.map
