"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.inputNumberProps = void 0;
const vue_1 = require("vue");
const seemly_1 = require("seemly");
const vooks_1 = require("vooks");
const evtd_1 = require("evtd");
const icons_1 = require("../../_internal/icons");
const input_1 = require("../../input");
const _internal_1 = require("../../_internal");
const button_1 = require("../../button");
const _mixins_1 = require("../../_mixins");
const _utils_1 = require("../../_utils");
const styles_1 = require("../styles");
const use_rtl_1 = require("../../_mixins/use-rtl");
const utils_1 = require("./utils");
const input_number_cssr_1 = __importDefault(require("./styles/input-number.cssr"));
const HOLDING_CHANGE_THRESHOLD = 800;
const HOLDING_CHANGE_INTERVAL = 100;
exports.inputNumberProps = Object.assign(Object.assign({}, _mixins_1.useTheme.props), { autofocus: Boolean, loading: {
        type: Boolean,
        default: undefined
    }, placeholder: String, defaultValue: {
        type: Number,
        default: null
    }, value: Number, step: {
        type: [Number, String],
        default: 1
    }, min: [Number, String], max: [Number, String], size: String, disabled: {
        type: Boolean,
        default: undefined
    }, validator: Function, bordered: {
        type: Boolean,
        default: undefined
    }, showButton: {
        type: Boolean,
        default: true
    }, buttonPlacement: {
        type: String,
        default: 'right'
    }, inputProps: Object, readonly: Boolean, clearable: Boolean, keyboard: {
        type: Object,
        default: {}
    }, updateValueOnInput: {
        type: Boolean,
        default: true
    }, round: {
        type: Boolean,
        default: undefined
    }, parse: Function, format: Function, precision: Number, status: String, 'onUpdate:value': [Function, Array], onUpdateValue: [Function, Array], onFocus: [Function, Array], onBlur: [Function, Array], onClear: [Function, Array], 
    // deprecated
    onChange: [Function, Array] });
exports.default = (0, vue_1.defineComponent)({
    name: 'InputNumber',
    props: exports.inputNumberProps,
    setup(props) {
        if (process.env.NODE_ENV !== 'production') {
            (0, vue_1.watchEffect)(() => {
                if (props.onChange !== undefined) {
                    (0, _utils_1.warnOnce)('input-number', '`on-change` is deprecated, please use `on-update:value` instead');
                }
            });
        }
        const { mergedBorderedRef, mergedClsPrefixRef, mergedRtlRef } = (0, _mixins_1.useConfig)(props);
        const themeRef = (0, _mixins_1.useTheme)('InputNumber', '-input-number', input_number_cssr_1.default, styles_1.inputNumberLight, props, mergedClsPrefixRef);
        const { localeRef } = (0, _mixins_1.useLocale)('InputNumber');
        const formItem = (0, _mixins_1.useFormItem)(props);
        const { mergedSizeRef, mergedDisabledRef, mergedStatusRef } = formItem;
        // dom ref
        const inputInstRef = (0, vue_1.ref)(null);
        const minusButtonInstRef = (0, vue_1.ref)(null);
        const addButtonInstRef = (0, vue_1.ref)(null);
        // value
        const uncontrolledValueRef = (0, vue_1.ref)(props.defaultValue);
        const controlledValueRef = (0, vue_1.toRef)(props, 'value');
        const mergedValueRef = (0, vooks_1.useMergedState)(controlledValueRef, uncontrolledValueRef);
        const displayedValueRef = (0, vue_1.ref)('');
        const getPrecision = (value) => {
            const fraction = String(value).split('.')[1];
            return fraction ? fraction.length : 0;
        };
        const getMaxPrecision = (currentValue) => {
            const precisions = [props.min, props.max, props.step, currentValue].map((value) => {
                if (value === undefined)
                    return 0;
                return getPrecision(value);
            });
            return Math.max(...precisions);
        };
        const mergedPlaceholderRef = (0, vooks_1.useMemo)(() => {
            const { placeholder } = props;
            if (placeholder !== undefined)
                return placeholder;
            return localeRef.value.placeholder;
        });
        const mergedStepRef = (0, vooks_1.useMemo)(() => {
            const parsedNumber = (0, utils_1.parseNumber)(props.step);
            if (parsedNumber !== null) {
                return parsedNumber === 0 ? 1 : Math.abs(parsedNumber);
            }
            return 1;
        });
        const mergedMinRef = (0, vooks_1.useMemo)(() => {
            const parsedNumber = (0, utils_1.parseNumber)(props.min);
            if (parsedNumber !== null)
                return parsedNumber;
            else
                return null;
        });
        const mergedMaxRef = (0, vooks_1.useMemo)(() => {
            const parsedNumber = (0, utils_1.parseNumber)(props.max);
            if (parsedNumber !== null)
                return parsedNumber;
            else
                return null;
        });
        const deriveDisplayedValueFromValue = () => {
            const { value: mergedValue } = mergedValueRef;
            if ((0, utils_1.validator)(mergedValue)) {
                const { format: formatProp, precision } = props;
                if (formatProp) {
                    displayedValueRef.value = formatProp(mergedValue);
                }
                else {
                    if (mergedValue === null
                        || precision === undefined
                        // precision overflow
                        || getPrecision(mergedValue) > precision) {
                        displayedValueRef.value = (0, utils_1.format)(mergedValue, undefined);
                    }
                    else {
                        displayedValueRef.value = (0, utils_1.format)(mergedValue, precision);
                    }
                }
            }
            else {
                // null can pass the validator check
                // so mergedValue is a number
                displayedValueRef.value = String(mergedValue);
            }
        };
        deriveDisplayedValueFromValue();
        const doUpdateValue = (value) => {
            const { value: mergedValue } = mergedValueRef;
            if (value === mergedValue) {
                deriveDisplayedValueFromValue();
                return;
            }
            const { 'onUpdate:value': _onUpdateValue, onUpdateValue, onChange } = props;
            const { nTriggerFormInput, nTriggerFormChange } = formItem;
            if (onChange)
                (0, _utils_1.call)(onChange, value);
            if (onUpdateValue)
                (0, _utils_1.call)(onUpdateValue, value);
            if (_onUpdateValue)
                (0, _utils_1.call)(_onUpdateValue, value);
            uncontrolledValueRef.value = value;
            nTriggerFormInput();
            nTriggerFormChange();
        };
        const deriveValueFromDisplayedValue = ({ offset, doUpdateIfValid, fixPrecision, isInputing }) => {
            const { value: displayedValue } = displayedValueRef;
            if (isInputing && (0, utils_1.isWipValue)(displayedValue)) {
                return false;
            }
            const parsedValue = (props.parse || utils_1.parse)(displayedValue);
            if (parsedValue === null) {
                if (doUpdateIfValid)
                    doUpdateValue(null);
                return null;
            }
            if ((0, utils_1.validator)(parsedValue)) {
                const currentPrecision = getPrecision(parsedValue);
                const { precision } = props;
                if (precision !== undefined
                    && precision < currentPrecision
                    && !fixPrecision) {
                    return false;
                }
                let nextValue = Number.parseFloat((parsedValue + offset).toFixed(precision !== null && precision !== void 0 ? precision : getMaxPrecision(parsedValue)));
                if ((0, utils_1.validator)(nextValue)) {
                    const { value: mergedMax } = mergedMaxRef;
                    const { value: mergedMin } = mergedMinRef;
                    if (mergedMax !== null && nextValue > mergedMax) {
                        if (!doUpdateIfValid || isInputing)
                            return false;
                        // if doUpdateIfValid=true, we try to make it a valid value
                        nextValue = mergedMax;
                    }
                    if (mergedMin !== null && nextValue < mergedMin) {
                        if (!doUpdateIfValid || isInputing)
                            return false;
                        // if doUpdateIfValid=true, we try to make it a valid value
                        nextValue = mergedMin;
                    }
                    if (props.validator && !props.validator(nextValue))
                        return false;
                    if (doUpdateIfValid)
                        doUpdateValue(nextValue);
                    return nextValue;
                }
            }
            return false;
        };
        const displayedValueInvalidRef = (0, vooks_1.useMemo)(() => {
            const derivedValue = deriveValueFromDisplayedValue({
                offset: 0,
                doUpdateIfValid: false,
                isInputing: false,
                fixPrecision: false
            });
            return derivedValue === false;
        });
        const minusableRef = (0, vooks_1.useMemo)(() => {
            const { value: mergedValue } = mergedValueRef;
            if (props.validator && mergedValue === null) {
                return false;
            }
            const { value: mergedStep } = mergedStepRef;
            const derivedNextValue = deriveValueFromDisplayedValue({
                offset: -mergedStep,
                doUpdateIfValid: false,
                isInputing: false,
                fixPrecision: false
            });
            return derivedNextValue !== false;
        });
        const addableRef = (0, vooks_1.useMemo)(() => {
            const { value: mergedValue } = mergedValueRef;
            if (props.validator && mergedValue === null) {
                return false;
            }
            const { value: mergedStep } = mergedStepRef;
            const derivedNextValue = deriveValueFromDisplayedValue({
                offset: +mergedStep,
                doUpdateIfValid: false,
                isInputing: false,
                fixPrecision: false
            });
            return derivedNextValue !== false;
        });
        function doFocus(e) {
            const { onFocus } = props;
            const { nTriggerFormFocus } = formItem;
            if (onFocus)
                (0, _utils_1.call)(onFocus, e);
            nTriggerFormFocus();
        }
        function doBlur(e) {
            var _a, _b;
            if (e.target === ((_a = inputInstRef.value) === null || _a === void 0 ? void 0 : _a.wrapperElRef)) {
                // hit input wrapper
                // which means not activated
                return;
            }
            const value = deriveValueFromDisplayedValue({
                offset: 0,
                doUpdateIfValid: true,
                isInputing: false,
                fixPrecision: true
            });
            // If valid, update event has been emitted
            // make sure e.target.value is correct in blur callback
            if (value !== false) {
                const inputElRef = (_b = inputInstRef.value) === null || _b === void 0 ? void 0 : _b.inputElRef;
                if (inputElRef) {
                    inputElRef.value = String(value || '');
                }
                // If value is not changed, the displayed value may be greater than or
                // less than the current value. The derived value is reformatted so the
                // value is not changed. We can simply derive a new displayed value
                if (mergedValueRef.value === value) {
                    deriveDisplayedValueFromValue();
                }
            }
            else {
                // If not valid, nothing will be emitted, so derive displayed value from
                // origin value
                deriveDisplayedValueFromValue();
            }
            const { onBlur } = props;
            const { nTriggerFormBlur } = formItem;
            if (onBlur)
                (0, _utils_1.call)(onBlur, e);
            nTriggerFormBlur();
            // User may change value in blur callback, we make sure it will be
            // displayed. Sometimes mergedValue won't be viewed as changed
            void (0, vue_1.nextTick)(() => {
                deriveDisplayedValueFromValue();
            });
        }
        function doClear(e) {
            const { onClear } = props;
            if (onClear)
                (0, _utils_1.call)(onClear, e);
        }
        function doAdd() {
            const { value: addable } = addableRef;
            if (!addable) {
                clearAddHoldTimeout();
                return;
            }
            const { value: mergedValue } = mergedValueRef;
            if (mergedValue === null) {
                if (!props.validator) {
                    doUpdateValue(createValidValue());
                }
            }
            else {
                const { value: mergedStep } = mergedStepRef;
                deriveValueFromDisplayedValue({
                    offset: mergedStep,
                    doUpdateIfValid: true,
                    isInputing: false,
                    fixPrecision: true
                });
            }
        }
        function doMinus() {
            const { value: minusable } = minusableRef;
            if (!minusable) {
                clearMinusHoldTimeout();
                return;
            }
            const { value: mergedValue } = mergedValueRef;
            if (mergedValue === null) {
                if (!props.validator) {
                    doUpdateValue(createValidValue());
                }
            }
            else {
                const { value: mergedStep } = mergedStepRef;
                deriveValueFromDisplayedValue({
                    offset: -mergedStep,
                    doUpdateIfValid: true,
                    isInputing: false,
                    fixPrecision: true
                });
            }
        }
        const handleFocus = doFocus;
        const handleBlur = doBlur;
        function createValidValue() {
            if (props.validator)
                return null;
            const { value: mergedMin } = mergedMinRef;
            const { value: mergedMax } = mergedMaxRef;
            if (mergedMin !== null) {
                return Math.max(0, mergedMin);
            }
            else if (mergedMax !== null) {
                return Math.min(0, mergedMax);
            }
            else {
                return 0;
            }
        }
        function handleClear(e) {
            doClear(e);
            doUpdateValue(null);
        }
        function handleMouseDown(e) {
            var _a, _b, _c;
            if ((_a = addButtonInstRef.value) === null || _a === void 0 ? void 0 : _a.$el.contains(e.target)) {
                e.preventDefault();
            }
            if ((_b = minusButtonInstRef.value) === null || _b === void 0 ? void 0 : _b.$el.contains(e.target)) {
                e.preventDefault();
            }
            (_c = inputInstRef.value) === null || _c === void 0 ? void 0 : _c.activate();
        }
        let minusHoldStateIntervalId = null;
        let addHoldStateIntervalId = null;
        let firstMinusMousedownId = null;
        function clearMinusHoldTimeout() {
            if (firstMinusMousedownId) {
                window.clearTimeout(firstMinusMousedownId);
                firstMinusMousedownId = null;
            }
            if (minusHoldStateIntervalId) {
                window.clearInterval(minusHoldStateIntervalId);
                minusHoldStateIntervalId = null;
            }
        }
        let firstAddMousedownId = null;
        function clearAddHoldTimeout() {
            if (firstAddMousedownId) {
                window.clearTimeout(firstAddMousedownId);
                firstAddMousedownId = null;
            }
            if (addHoldStateIntervalId) {
                window.clearInterval(addHoldStateIntervalId);
                addHoldStateIntervalId = null;
            }
        }
        function handleMinusMousedown() {
            clearMinusHoldTimeout();
            firstMinusMousedownId = window.setTimeout(() => {
                minusHoldStateIntervalId = window.setInterval(() => {
                    doMinus();
                }, HOLDING_CHANGE_INTERVAL);
            }, HOLDING_CHANGE_THRESHOLD);
            (0, evtd_1.on)('mouseup', document, clearMinusHoldTimeout, {
                once: true
            });
        }
        function handleAddMousedown() {
            clearAddHoldTimeout();
            firstAddMousedownId = window.setTimeout(() => {
                addHoldStateIntervalId = window.setInterval(() => {
                    doAdd();
                }, HOLDING_CHANGE_INTERVAL);
            }, HOLDING_CHANGE_THRESHOLD);
            (0, evtd_1.on)('mouseup', document, clearAddHoldTimeout, {
                once: true
            });
        }
        const handleAddClick = () => {
            if (addHoldStateIntervalId)
                return;
            doAdd();
        };
        const handleMinusClick = () => {
            if (minusHoldStateIntervalId)
                return;
            doMinus();
        };
        function handleKeyDown(e) {
            var _a, _b;
            if (e.key === 'Enter') {
                if (e.target === ((_a = inputInstRef.value) === null || _a === void 0 ? void 0 : _a.wrapperElRef)) {
                    // hit input wrapper
                    // which means not activated
                    return;
                }
                const value = deriveValueFromDisplayedValue({
                    offset: 0,
                    doUpdateIfValid: true,
                    isInputing: false,
                    fixPrecision: true
                });
                if (value !== false) {
                    (_b = inputInstRef.value) === null || _b === void 0 ? void 0 : _b.deactivate();
                }
            }
            else if (e.key === 'ArrowUp') {
                if (!addableRef.value)
                    return;
                if (props.keyboard.ArrowUp === false)
                    return;
                e.preventDefault();
                const value = deriveValueFromDisplayedValue({
                    offset: 0,
                    doUpdateIfValid: true,
                    isInputing: false,
                    fixPrecision: true
                });
                if (value !== false) {
                    doAdd();
                }
            }
            else if (e.key === 'ArrowDown') {
                if (!minusableRef.value)
                    return;
                if (props.keyboard.ArrowDown === false)
                    return;
                e.preventDefault();
                const value = deriveValueFromDisplayedValue({
                    offset: 0,
                    doUpdateIfValid: true,
                    isInputing: false,
                    fixPrecision: true
                });
                if (value !== false) {
                    doMinus();
                }
            }
        }
        function handleUpdateDisplayedValue(value) {
            displayedValueRef.value = value;
            if (props.updateValueOnInput
                && !props.format
                && !props.parse
                && props.precision === undefined) {
                deriveValueFromDisplayedValue({
                    offset: 0,
                    doUpdateIfValid: true,
                    isInputing: true,
                    fixPrecision: false
                });
            }
        }
        (0, vue_1.watch)(mergedValueRef, () => {
            deriveDisplayedValueFromValue();
        });
        const exposedMethods = {
            focus: () => { var _a; return (_a = inputInstRef.value) === null || _a === void 0 ? void 0 : _a.focus(); },
            blur: () => { var _a; return (_a = inputInstRef.value) === null || _a === void 0 ? void 0 : _a.blur(); },
            select: () => { var _a; return (_a = inputInstRef.value) === null || _a === void 0 ? void 0 : _a.select(); }
        };
        const rtlEnabledRef = (0, use_rtl_1.useRtl)('InputNumber', mergedRtlRef, mergedClsPrefixRef);
        return Object.assign(Object.assign({}, exposedMethods), { rtlEnabled: rtlEnabledRef, inputInstRef,
            minusButtonInstRef,
            addButtonInstRef, mergedClsPrefix: mergedClsPrefixRef, mergedBordered: mergedBorderedRef, uncontrolledValue: uncontrolledValueRef, mergedValue: mergedValueRef, mergedPlaceholder: mergedPlaceholderRef, displayedValueInvalid: displayedValueInvalidRef, mergedSize: mergedSizeRef, mergedDisabled: mergedDisabledRef, displayedValue: displayedValueRef, addable: addableRef, minusable: minusableRef, mergedStatus: mergedStatusRef, handleFocus,
            handleBlur,
            handleClear,
            handleMouseDown,
            handleAddClick,
            handleMinusClick,
            handleAddMousedown,
            handleMinusMousedown,
            handleKeyDown,
            handleUpdateDisplayedValue, 
            // theme
            mergedTheme: themeRef, inputThemeOverrides: {
                paddingSmall: '0 8px 0 10px',
                paddingMedium: '0 8px 0 12px',
                paddingLarge: '0 8px 0 14px'
            }, buttonThemeOverrides: (0, vue_1.computed)(() => {
                const { self: { iconColorDisabled } } = themeRef.value;
                const [r, g, b, a] = (0, seemly_1.rgba)(iconColorDisabled);
                return {
                    textColorTextDisabled: `rgb(${r}, ${g}, ${b})`,
                    opacityDisabled: `${a}`
                };
            }) });
    },
    render() {
        const { mergedClsPrefix, $slots } = this;
        const renderMinusButton = () => {
            return ((0, vue_1.h)(button_1.NxButton, { text: true, disabled: !this.minusable || this.mergedDisabled || this.readonly, focusable: false, theme: this.mergedTheme.peers.Button, themeOverrides: this.mergedTheme.peerOverrides.Button, builtinThemeOverrides: this.buttonThemeOverrides, onClick: this.handleMinusClick, onMousedown: this.handleMinusMousedown, ref: "minusButtonInstRef" }, {
                icon: () => (0, _utils_1.resolveSlot)($slots['minus-icon'], () => [
                    (0, vue_1.h)(_internal_1.NBaseIcon, { clsPrefix: mergedClsPrefix }, {
                        default: () => (0, vue_1.h)(icons_1.RemoveIcon, null)
                    })
                ])
            }));
        };
        const renderAddButton = () => {
            return ((0, vue_1.h)(button_1.NxButton, { text: true, disabled: !this.addable || this.mergedDisabled || this.readonly, focusable: false, theme: this.mergedTheme.peers.Button, themeOverrides: this.mergedTheme.peerOverrides.Button, builtinThemeOverrides: this.buttonThemeOverrides, onClick: this.handleAddClick, onMousedown: this.handleAddMousedown, ref: "addButtonInstRef" }, {
                icon: () => (0, _utils_1.resolveSlot)($slots['add-icon'], () => [
                    (0, vue_1.h)(_internal_1.NBaseIcon, { clsPrefix: mergedClsPrefix }, {
                        default: () => (0, vue_1.h)(icons_1.AddIcon, null)
                    })
                ])
            }));
        };
        return ((0, vue_1.h)("div", { class: [
                `${mergedClsPrefix}-input-number`,
                this.rtlEnabled && `${mergedClsPrefix}-input-number--rtl`
            ] },
            (0, vue_1.h)(input_1.NInput, { ref: "inputInstRef", autofocus: this.autofocus, status: this.mergedStatus, bordered: this.mergedBordered, loading: this.loading, value: this.displayedValue, onUpdateValue: this.handleUpdateDisplayedValue, theme: this.mergedTheme.peers.Input, themeOverrides: this.mergedTheme.peerOverrides.Input, builtinThemeOverrides: this.inputThemeOverrides, size: this.mergedSize, placeholder: this.mergedPlaceholder, disabled: this.mergedDisabled, readonly: this.readonly, round: this.round, textDecoration: this.displayedValueInvalid ? 'line-through' : undefined, onFocus: this.handleFocus, onBlur: this.handleBlur, onKeydown: this.handleKeyDown, onMousedown: this.handleMouseDown, onClear: this.handleClear, clearable: this.clearable, inputProps: this.inputProps, internalLoadingBeforeSuffix: true }, {
                prefix: () => {
                    var _a;
                    return this.showButton && this.buttonPlacement === 'both'
                        ? [
                            renderMinusButton(),
                            (0, _utils_1.resolveWrappedSlot)($slots.prefix, (children) => {
                                if (children) {
                                    return ((0, vue_1.h)("span", { class: `${mergedClsPrefix}-input-number-prefix` }, children));
                                }
                                return null;
                            })
                        ]
                        : (_a = $slots.prefix) === null || _a === void 0 ? void 0 : _a.call($slots);
                },
                suffix: () => {
                    var _a;
                    return this.showButton
                        ? [
                            (0, _utils_1.resolveWrappedSlot)($slots.suffix, (children) => {
                                if (children) {
                                    return ((0, vue_1.h)("span", { class: `${mergedClsPrefix}-input-number-suffix` }, children));
                                }
                                return null;
                            }),
                            this.buttonPlacement === 'right'
                                ? renderMinusButton()
                                : null,
                            renderAddButton()
                        ]
                        : (_a = $slots.suffix) === null || _a === void 0 ? void 0 : _a.call($slots);
                }
            })));
    }
});
