UNPKG

@progress/kendo-angular-inputs

Version:

Kendo UI for Angular Inputs Package - Everything you need to build professional form functionality (Checkbox, ColorGradient, ColorPalette, ColorPicker, FlatColorPicker, FormField, MaskedTextBox, NumericTextBox, RadioButton, RangeSlider, Slider, Switch, Te

1,307 lines • 50.2 kB
/**-----------------------------------------------------------------------------------------
* Copyright © 2025 Progress Software Corporation. All rights reserved.
* Licensed under commercial license. See LICENSE.md in the project root for more information
*-------------------------------------------------------------------------------------------*/
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Component, ElementRef, EventEmitter, HostBinding, Input, Output, Renderer2, ViewChild, forwardRef, isDevMode, NgZone, ChangeDetectorRef, Injector, ContentChild } from '@angular/core';
import { anyChanged, hasObservers, Keys, guid, KendoInput, SuffixTemplateDirective, PrefixTemplateDirective, setHTMLAttributes, isControlRequired, isObjectPresent, removeHTMLAttributes, parseAttributes, EventsOutsideAngularDirective } from '@progress/kendo-angular-common';
import { areSame, getStylingClasses, requiresZoneOnBlur } from '../common/utils';
import { invokeElementMethod } from '../common/dom-utils';
import { add, toFixedPrecision, limitPrecision } from '../common/math';
import { createMaxValidator } from '../validators/max.validator';
import { createMinValidator } from '../validators/min.validator';
import { NG_VALUE_ACCESSOR, NG_VALIDATORS, NgControl } from '@angular/forms';
import { IntlService } from '@progress/kendo-angular-intl';
import { L10N_PREFIX, LocalizationService } from '@progress/kendo-angular-l10n';
import { validatePackage } from '@progress/kendo-licensing';
import { packageMetadata } from '../package-metadata';
import { MIN_DOC_LINK, MAX_DOC_LINK, POINT, INITIAL_SPIN_DELAY, SPIN_DELAY, EXPONENT_REGEX } from './constants';
import { defined, noop, numericRegex, isNumber, pad, decimalPart, getDeltaFromMouseWheel, getCaretPosition, extractSignificantNumericChars, isRightClick } from './utils';
import { ArrowDirection } from './arrow-direction';
import { mobileOS } from '@progress/kendo-common';
import { caretAltUpIcon, caretAltDownIcon } from '@progress/kendo-svg-icons';
import { InputSeparatorComponent } from '../shared/input-separator.component';
import { NgIf, NgTemplateOutlet } from '@angular/common';
import { SharedInputEventsDirective } from '../shared/shared-events.directive';
import { LocalizedNumericTextBoxMessagesDirective } from './localization/localized-numerictextbox-messages.directive';
import { IconWrapperComponent } from '@progress/kendo-angular-icons';
import * as i0 from "@angular/core";
import * as i1 from "@progress/kendo-angular-intl";
import * as i2 from "@progress/kendo-angular-l10n";
const PARSABLE_OPTIONS = ['min', 'max', 'step', 'decimals'];
const PARSABLE_DEFAULTS = {
    decimals: null,
    max: null,
    min: null,
    step: 1
};
const FOCUSED = 'k-focus';
const DEFAULT_SIZE = 'medium';
const DEFAULT_ROUNDED = 'medium';
const DEFAULT_FILL_MODE = 'solid';
/**
 * Represents the [Kendo UI NumericTextBox component for Angular]({% slug overview_numerictextbox %}).
 */
export class NumericTextBoxComponent {
    intl;
    renderer;
    localizationService;
    injector;
    ngZone;
    changeDetector;
    hostElement;
    /**
     * @hidden
     */
    focusableId = `k-${guid()}`;
    /**
     * Determines whether the NumericTextBox is disabled ([see example]({% slug disabled_numerictextbox %})). To learn how to disable the component in reactive forms, refer to the article on [Forms Support](slug:formssupport_numerictextbox#toc-managing-the-numerictextbox-disabled-state-in-reactive-forms).
     */
    disabled = false;
    /**
     * Determines whether the NumericTextBox is in its read-only state ([see example]({% slug readonly_numerictextbox %})).
     *
     * @default false
     */
    readonly = false;
    /**
     * Sets the title of the `input` element of the NumericTextBox.
     */
    title = '';
    /**
     * Specifies whether the value will be auto-corrected based on the minimum and maximum values
     * ([see example]({% slug precision_numerictextbox %})).
     */
    autoCorrect = false;
    /**
     * Specifies the number format which is used when the NumericTextBox is not focused
     * ([see example]({% slug formats_numerictextbox %})).
     * If `format` is set to `null` or `undefined`, the default format will be used.
     */
    get format() {
        const format = this._format;
        return format !== null && format !== undefined ? format : 'n2';
    }
    set format(value) {
        this._format = value;
    }
    /**
     * Specifies the greatest value that is valid
     * ([see example]({% slug precision_numerictextbox %}#toc-value-ranges)).
     */
    max;
    /**
     * Specifies the smallest value that is valid
     * ([see example]({% slug precision_numerictextbox %}#toc-value-ranges)).
     */
    min;
    /**
     * Specifies the number of decimals that the user can enter when the input is focused
     * ([see example]({% slug precision_numerictextbox %})).
     */
    decimals = null;
    /**
     * Specifies the input placeholder.
     */
    placeholder;
    /**
     * Specifies the value that is used to increment or decrement the component value
     * ([see example]({% slug predefinedsteps_numerictextbox %})).
     */
    step = 1;
    /**
     * Specifies whether the **Up** and **Down** spin buttons will be rendered
     * ([see example]({% slug spinbuttons_numerictextbox %})).
     */
    spinners = true;
    /**
     * Determines whether the built-in minimum or maximum validators are enforced when a form is validated.
     *
     * > The 4.2.0 Angular version introduces the `min` and `max` validation directives. As a result, even if you set `rangeValidation`
     * to `false`, the built-in Angular validators will be executed.
     */
    rangeValidation = true;
    /**
     * Specifies the [tabindex](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex) of the component.
     */
    tabindex = 0;
    /**
     * @hidden
     */
    set tabIndex(tabIndex) {
        this.tabindex = tabIndex;
    }
    get tabIndex() {
        return this.tabindex;
    }
    /**
     * Determines whether the value of the NumericTextBox will be changed via scrolling. Defaults to `true`.
     *
     * @default true
     */
    changeValueOnScroll = true;
    /**
     * Determines whether the whole value will be selected when the NumericTextBox is clicked. Defaults to `true`.
     */
    selectOnFocus = true;
    /**
     * Specifies the value of the NumericTextBox
     * ([see example]({% slug formats_numerictextbox %})).
     */
    value = null;
    /**
     * Specifies the maximum number of characters the end user can type or paste in the input.
     * The locale-specific decimal separator and negative sign (`-`) are included in the length of the value when present.
     * The `maxlength` restriction is not applied to the length of the formatted value when the component is not focused.
     */
    maxlength;
    /**
     * The size property specifies padding of the NumericTextBox internal input element
     * ([see example]({% slug appearance_numerictextbox %}#toc-size)).
     * The possible values are:
     * * `small`
     * * `medium` (default)
     * * `large`
     * * `none`
     */
    set size(size) {
        const newSize = size ? size : DEFAULT_SIZE;
        this.handleClasses(newSize, 'size');
        this._size = newSize;
    }
    get size() {
        return this._size;
    }
    /**
     * The `rounded` property specifies the border radius of the NumericTextBox
     * ([see example](slug:appearance_numerictextbox#toc-roundness)).
     * The possible values are:
     * * `small`
     * * `medium` (default)
     * * `large`
     * * `none`
     */
    set rounded(rounded) {
        const newRounded = rounded ? rounded : DEFAULT_ROUNDED;
        this.handleClasses(newRounded, 'rounded');
        this._rounded = newRounded;
    }
    get rounded() {
        return this._rounded;
    }
    /**
     * The `fillMode` property specifies the background and border styles of the NumericTextBox
     * ([see example](slug:appearance_numerictextbox#toc-fill-mode)).
     * The possible values are:
     * * `flat`
     * * `solid` (default)
     * * `outline`
     * * `none`
     */
    set fillMode(fillMode) {
        const newFillMode = fillMode ? fillMode : DEFAULT_FILL_MODE;
        this.handleClasses(newFillMode, 'fillMode');
        this._fillMode = newFillMode;
    }
    get fillMode() {
        return this._fillMode;
    }
    /**
     * Sets the HTML attributes of the inner focusable input element. Attributes which are essential for certain component functionalities cannot be changed.
     */
    set inputAttributes(attributes) {
        if (isObjectPresent(this.parsedAttributes)) {
            removeHTMLAttributes(this.parsedAttributes, this.renderer, this.numericInput.nativeElement);
        }
        this._inputAttributes = attributes;
        this.parsedAttributes = this.inputAttributes ?
            parseAttributes(this.inputAttributes, this.defaultAttributes) :
            this.inputAttributes;
        this.setInputAttributes();
    }
    get inputAttributes() {
        return this._inputAttributes;
    }
    /**
     * Fires each time the user selects a new value ([see example](slug:events_numerictextbox)).
     */
    valueChange = new EventEmitter();
    /**
     * Fires each time the user focuses the NumericTextBox element ([see example](slug:events_numerictextbox)).
     */
    onFocus = new EventEmitter();
    /**
     * Fires each time the NumericTextBox component gets blurred ([see example](slug:events_numerictextbox)).
     */
    onBlur = new EventEmitter();
    /**
     * Fires each time the user focuses the `input` element.
     */
    inputFocus = new EventEmitter();
    /**
     * Fires each time the `input` element gets blurred.
     */
    inputBlur = new EventEmitter();
    /**
     * @hidden
     */
    numericInput;
    /**
     * @hidden
     */
    suffixTemplate;
    /**
     * @hidden
     */
    prefixTemplate;
    direction;
    /**
     * @hidden
     */
    ArrowDirection = ArrowDirection;
    /**
     * @hidden
     */
    arrowDirection = ArrowDirection.None;
    get disableClass() {
        return this.disabled;
    }
    hostClasses = true;
    /**
     * @hidden
     */
    arrowUpIcon = caretAltUpIcon;
    /**
     * @hidden
     */
    arrowDownIcon = caretAltDownIcon;
    subscriptions;
    inputValue = '';
    spinTimeout;
    isFocused;
    minValidateFn = noop;
    maxValidateFn = noop;
    numericRegex;
    _format = "n2";
    previousSelection;
    pressedKey;
    control;
    isPasted = false;
    mouseDown = false;
    _size = 'medium';
    _rounded = 'medium';
    _fillMode = 'solid';
    ngChange = noop;
    ngTouched = noop;
    ngValidatorChange = noop;
    domEvents = [];
    _inputAttributes;
    parsedAttributes = {};
    get defaultAttributes() {
        return {
            id: this.focusableId,
            disabled: this.disabled ? '' : null,
            readonly: this.readonly ? '' : null,
            tabindex: this.tabIndex,
            placeholder: this.placeholder,
            title: this.title,
            maxlength: this.maxlength,
            'aria-valuemin': this.min,
            'aria-valuemax': this.max,
            'aria-valuenow': this.value,
            required: this.isControlRequired ? '' : null,
            'aria-invalid': this.isControlInvalid
        };
    }
    get mutableAttributes() {
        return {
            autocomplete: 'off',
            autocorrect: 'off',
            role: 'spinbutton'
        };
    }
    constructor(intl, renderer, localizationService, injector, ngZone, changeDetector, hostElement) {
        this.intl = intl;
        this.renderer = renderer;
        this.localizationService = localizationService;
        this.injector = injector;
        this.ngZone = ngZone;
        this.changeDetector = changeDetector;
        this.hostElement = hostElement;
        validatePackage(packageMetadata);
        this.direction = localizationService.rtl ? 'rtl' : 'ltr';
    }
    ngOnInit() {
        this.subscriptions = this.localizationService
            .changes
            .subscribe(({ rtl }) => {
            this.direction = rtl ? 'rtl' : 'ltr';
        });
        this.subscriptions.add(this.intl.changes.subscribe(this.intlChange.bind(this)));
        if (this.hostElement) {
            this.renderer.removeAttribute(this.hostElement.nativeElement, "tabindex");
        }
        this.control = this.injector.get(NgControl, null);
        this.ngZone.runOutsideAngular(() => {
            this.domEvents.push(this.renderer.listen(this.hostElement.nativeElement, 'mousewheel', this.handleWheel.bind(this)));
            this.domEvents.push(this.renderer.listen(this.hostElement.nativeElement, 'DOMMouseScroll', this.handleWheel.bind(this)));
        });
    }
    ngAfterViewInit() {
        const stylingInputs = ['size', 'rounded', 'fillMode'];
        stylingInputs.forEach(input => {
            this.handleClasses(this[input], input);
        });
    }
    /**
     * @hidden
     */
    increasePress = (e) => {
        this.arrowPress(ArrowDirection.Up, e);
    };
    /**
     * @hidden
     */
    decreasePress = (e) => {
        this.arrowPress(ArrowDirection.Down, e);
    };
    /**
     * @hidden
     */
    releaseArrow = () => {
        clearTimeout(this.spinTimeout);
        if (this.arrowDirection !== ArrowDirection.None) {
            this.arrowDirection = ArrowDirection.None;
            this.changeDetector.detectChanges();
        }
    };
    /**
     * @hidden
     */
    ngOnChanges(changes) {
        if (anyChanged(PARSABLE_OPTIONS, changes, false)) {
            this.parseOptions(PARSABLE_OPTIONS.filter(option => changes[option]));
        }
        this.verifySettings();
        if (anyChanged(['min', 'max', 'rangeValidation'], changes, false)) {
            this.minValidateFn = this.rangeValidation ? createMinValidator(this.min) : noop;
            this.maxValidateFn = this.rangeValidation ? createMaxValidator(this.max) : noop;
            this.ngValidatorChange();
        }
        if (anyChanged(['autoCorrect', 'decimals', 'min'], changes)) {
            delete this.numericRegex;
        }
        if (anyChanged(['value', 'format'], changes, false)) {
            this.verifyValue(this.value);
            this.value = this.restrictModelValue(this.value);
            if (!this.focused || (this.intl.parseNumber(this.elementValue) !== this.value)) {
                this.setInputValue();
            }
        }
    }
    /**
     * @hidden
     */
    ngOnDestroy() {
        if (this.subscriptions) {
            this.subscriptions.unsubscribe();
        }
        clearTimeout(this.spinTimeout);
        this.domEvents.forEach(unbindHandler => unbindHandler());
    }
    /**
     * @hidden
     */
    validate(control) {
        return this.minValidateFn(control) || this.maxValidateFn(control);
    }
    /**
     * @hidden
     */
    registerOnValidatorChange(fn) {
        this.ngValidatorChange = fn;
    }
    /**
     * @hidden
     */
    writeValue(value) {
        this.verifyValue(value);
        const restrictedValue = this.restrictModelValue(value);
        this.value = restrictedValue;
        this.setInputValue();
    }
    /**
     * @hidden
     */
    registerOnChange(fn) {
        this.ngChange = fn;
    }
    /**
     * @hidden
     */
    registerOnTouched(fn) {
        this.ngTouched = fn;
    }
    /**
     * @hidden
     * Called when the status of the component changes to or from `disabled`.
     * Depending on the value, it enables or disables the appropriate DOM element.
     *
     * @param isDisabled
     */
    setDisabledState(isDisabled) {
        this.changeDetector.markForCheck();
        this.disabled = isDisabled;
    }
    /**
     * Focuses the NumericTextBox.
     *
     * @example
     * ```ts-no-run
     * _@Component({
     * selector: 'my-app',
     * template: `
     *  <button (click)="numerictextbox.focus()">Focus NumericTextBox</button>
     *  <kendo-numerictextbox #numerictextbox></kendo-numerictextbox>
     * `
     * })
     * class AppComponent { }
     * ```
     */
    focus() {
        invokeElementMethod(this.numericInput, 'focus');
    }
    /**
     * Blurs the NumericTextBox.
     */
    blur() {
        invokeElementMethod(this.numericInput, 'blur');
    }
    /**
     * Notifies the `NumericTextBoxComponent` that the input value should be changed.
     * Can be used to update the input after setting the component properties directly.
     */
    notifyValueChange() {
        this.setInputValue();
    }
    /**
     * @hidden
     */
    handlePaste = () => {
        this.isPasted = true;
    };
    /**
     * @hidden
     */
    handleInput = () => {
        const input = this.numericInput.nativeElement;
        let { selectionStart, selectionEnd, value: inputValue } = input;
        if (this.pressedKey === Keys.NumpadDecimal) {
            inputValue = this.replaceNumpadDotValue();
        }
        if (this.isPasted) {
            inputValue = this.formatInputValue(this.intl.parseNumber(inputValue));
        }
        if (!this.isValid(inputValue)) {
            input.value = this.inputValue;
            this.setSelection(selectionStart - 1, selectionEnd - 1);
            return;
        }
        const parsedValue = this.intl.parseNumber(inputValue);
        let value = this.restrictDecimals(parsedValue);
        if (this.autoCorrect) {
            const limited = this.limitInputValue(value);
            value = limited.value;
            selectionStart = limited.selectionStart;
            selectionEnd = limited.selectionEnd;
        }
        if (parsedValue !== value || this.hasTrailingZeros(inputValue) || !this.focused) {
            this.setInputValue(value);
            this.setSelection(selectionStart, selectionEnd);
        }
        else {
            this.inputValue = inputValue;
        }
        if (this.isPasted) {
            input.value = this.inputValue;
        }
        this.updateValue(value);
        this.previousSelection = null;
        this.isPasted = false;
    };
    /**
     * @hidden
     */
    handleDragEnter = () => {
        if (!this.focused && !this.isDisabled) {
            this.setInputValue(this.value, true);
        }
    };
    /**
     * @hidden
     */
    handleMouseDown = () => {
        this.mouseDown = true;
    };
    /**
     * @hidden
     */
    handleInputFocus = () => {
        if (!this.focused) {
            this.focused = true;
            if (!this.isDisabled) {
                const shouldSelectAll = this.selectOnFocus || !this.mouseDown;
                this.ngZone.runOutsideAngular(() => {
                    setTimeout(() => {
                        if (shouldSelectAll) {
                            this.selectAll();
                        }
                        else {
                            this.selectCaret();
                        }
                    }, 0);
                });
            }
            if (hasObservers(this.onFocus)) {
                this.ngZone.run(() => {
                    this.onFocus.emit();
                });
            }
        }
        this.mouseDown = false;
        if (hasObservers(this.inputFocus)) {
            this.ngZone.run(() => {
                this.inputFocus.emit();
            });
        }
    };
    /**
     * @hidden
     */
    handleFocus() {
        this.ngZone.run(() => {
            if (!this.focused && hasObservers(this.onFocus)) {
                this.onFocus.emit();
            }
            this.focused = true;
        });
    }
    /**
     * @hidden
     */
    handleBlur = () => {
        this.changeDetector.markForCheck();
        this.focused = false;
        //blur is thrown before input when dragging the input text in IE
        if (this.inputValue !== this.elementValue) {
            this.handleInput();
        }
        this.setInputValue();
        if (hasObservers(this.onBlur)) {
            this.ngZone.run(() => {
                this.ngTouched();
                this.onBlur.emit();
            });
        }
    };
    /**
     * @hidden
     */
    handleInputBlur = () => {
        this.changeDetector.markForCheck();
        //blur is thrown before input when dragging the input text in IE
        if (this.inputValue !== this.elementValue) {
            this.handleInput();
        }
        this.setInputValue();
        if (hasObservers(this.inputBlur) || requiresZoneOnBlur(this.control)) {
            this.ngZone.run(() => {
                this.ngTouched();
                this.inputBlur.emit();
            });
        }
    };
    /**
     * @hidden
     */
    handleKeyDown = (e) => {
        if (this.isDisabled) {
            return;
        }
        let step;
        if (e.keyCode === Keys.ArrowDown) {
            step = -1;
        }
        else if (e.keyCode === Keys.ArrowUp) {
            step = 1;
        }
        if (step && this.step) {
            e.preventDefault();
            this.addStep(step);
        }
        const input = this.numericInput.nativeElement;
        this.previousSelection = {
            end: input.selectionEnd,
            start: input.selectionStart
        };
        this.pressedKey = e.keyCode;
    };
    /**
     * @hidden
     */
    handleWheel = (e) => {
        if (this.focused && !this.isDisabled && this.changeValueOnScroll) {
            e.preventDefault();
            const delta = getDeltaFromMouseWheel(e);
            this.addStep(delta);
        }
    };
    /**
     * @hidden
     */
    get incrementTitle() {
        return this.localizationService.get('increment');
    }
    /**
     * @hidden
     */
    get decrementTitle() {
        return this.localizationService.get('decrement');
    }
    /**
     * @hidden
     */
    get isControlInvalid() {
        return this.control && this.control.touched && !this.control.valid;
    }
    /**
     * @hidden
     */
    get isControlRequired() {
        return isControlRequired(this.control?.control);
    }
    /**
     * @hidden
     */
    get focused() {
        return this.isFocused;
    }
    /**
     * @hidden
     */
    set focused(value) {
        if (this.isFocused !== value && this.hostElement) {
            const wrap = this.hostElement.nativeElement;
            if (value) {
                this.renderer.addClass(wrap, FOCUSED);
            }
            else {
                this.renderer.removeClass(wrap, FOCUSED);
            }
            this.isFocused = value;
        }
    }
    get decimalSeparator() {
        const numberSymbols = this.intl.numberSymbols();
        return numberSymbols.decimal;
    }
    get elementValue() {
        return this.numericInput.nativeElement.value;
    }
    set elementValue(value) {
        this.renderer.setProperty(this.numericInput.nativeElement, 'value', value);
    }
    get hasDecimals() {
        return this.decimals !== null && this.decimals >= 0;
    }
    get isDisabled() {
        return this.disabled || this.readonly;
    }
    arrowPress(direction, e) {
        e.preventDefault();
        if (this.isDisabled || isRightClick(e)) {
            return;
        }
        if (!mobileOS) {
            this.focus();
            this.focused = true;
        }
        if (this.arrowDirection !== direction) {
            this.arrowDirection = direction;
            this.changeDetector.detectChanges();
        }
        if (this.step) {
            this.spin(direction, INITIAL_SPIN_DELAY);
        }
        else {
            this.setInputValue();
        }
    }
    updateValue(value) {
        if (!areSame(this.value, value)) {
            this.ngZone.run(() => {
                this.value = value;
                this.ngChange(value);
                this.valueChange.emit(value);
                this.changeDetector.markForCheck();
            });
        }
    }
    replaceNumpadDotValue() {
        let value = this.inputValue || "";
        if (this.previousSelection) {
            const input = this.numericInput.nativeElement;
            const { selectionStart, selectionEnd } = input;
            const { start, end } = this.previousSelection;
            input.value = value = value.substring(0, start) + this.decimalSeparator + value.substring(end);
            this.setSelection(selectionStart, selectionEnd);
        }
        return value;
    }
    isValid(value) {
        if (!this.numericRegex) {
            this.numericRegex = numericRegex({
                autoCorrect: this.autoCorrect,
                decimals: this.decimals,
                min: this.min,
                separator: this.decimalSeparator
            });
        }
        return this.numericRegex.test(value);
    }
    spin(step, timeout) {
        clearTimeout(this.spinTimeout);
        this.spinTimeout = window.setTimeout(() => {
            this.spin(step, SPIN_DELAY);
        }, timeout);
        this.addStep(step);
    }
    addStep(step) {
        let value = add(this.value || 0, this.step * step);
        value = this.limitValue(value);
        value = this.restrictDecimals(value);
        this.setInputValue(value);
        this.updateValue(value);
    }
    setSelection(start, end) {
        if (this.focused) {
            invokeElementMethod(this.numericInput, 'setSelectionRange', start, end);
        }
    }
    limitValue(value) {
        let result = value;
        if (!this.isInRange(value)) {
            if (isNumber(this.max) && value > this.max) {
                result = this.max;
            }
            if (isNumber(this.min) && value < this.min) {
                result = this.min;
            }
        }
        return result;
    }
    limitInputValue(value) {
        const { selectionStart, selectionEnd, value: enteredValue } = this.numericInput.nativeElement;
        let limitedValue = value;
        let selectToEnd = false;
        if (!this.isInRange(value)) {
            const lengthChange = enteredValue.length - String(this.inputValue).length;
            const { min, max } = this;
            const hasMax = isNumber(max);
            const hasMin = isNumber(min);
            let padLimit, replaceNext;
            let correctedValue = value;
            if (selectionStart === 0 && this.inputValue.substr(1) === enteredValue) {
                return {
                    selectionEnd: selectionEnd,
                    selectionStart: selectionStart,
                    value: null
                };
            }
            if (hasMax && value > max) {
                if (value > 0) {
                    replaceNext = true;
                }
                else {
                    padLimit = max;
                }
            }
            else if (hasMin && value < min) {
                if (value > 0) {
                    padLimit = min;
                }
                else {
                    replaceNext = true;
                }
            }
            if (padLimit) {
                const paddedValue = this.tryPadValue(value, padLimit);
                if (paddedValue && decimalPart(value) !== decimalPart(padLimit)) {
                    correctedValue = paddedValue;
                    selectToEnd = true;
                }
            }
            else if (replaceNext) {
                if (this.inputValue && selectionStart !== enteredValue.length) {
                    correctedValue = parseFloat(enteredValue.substr(0, selectionStart) +
                        enteredValue.substr(selectionStart + lengthChange));
                }
            }
            limitedValue = this.limitValue(correctedValue);
            selectToEnd = (selectToEnd || limitedValue !== correctedValue) && this.previousSelection &&
                (this.previousSelection.end - this.previousSelection.start + lengthChange) > 0;
        }
        return {
            selectionEnd: selectToEnd ? String(limitedValue).length : selectionEnd,
            selectionStart: selectionStart,
            value: limitedValue
        };
    }
    tryPadValue(value, limit) {
        const limitLength = String(Math.floor(limit)).length;
        const zeroPadded = pad(value, limitLength);
        const zeroPaddedNext = pad(value, limitLength + 1);
        let result;
        if (this.isInRange(zeroPadded)) {
            result = zeroPadded;
        }
        else if (this.isInRange(zeroPaddedNext)) {
            result = zeroPaddedNext;
        }
        return result;
    }
    isInRange(value) {
        return !isNumber(value) || ((!isNumber(this.min) || this.min <= value) && (!isNumber(this.max) || value <= this.max));
    }
    restrictModelValue(value) {
        let result = this.restrictDecimals(value, true);
        if (this.autoCorrect && this.limitValue(result) !== result) {
            result = null;
        }
        return result;
    }
    restrictDecimals(value, round) {
        let result = value;
        if (value && this.hasDecimals) {
            const decimals = this.decimals;
            const stringValue = String(value);
            if (round || EXPONENT_REGEX.test(stringValue)) {
                result = toFixedPrecision(value, decimals);
            }
            else {
                const parts = stringValue.split(POINT);
                let fraction = parts[1];
                if (fraction && fraction.length > decimals) {
                    fraction = fraction.substr(0, decimals);
                    result = parseFloat(`${parts[0]}${POINT}${fraction}`);
                }
            }
        }
        return result;
    }
    formatInputValue(value) {
        let stringValue = Object.is(value, -0) ? '-0' : String(value);
        const exponentMatch = EXPONENT_REGEX.exec(stringValue);
        if (exponentMatch) {
            stringValue = value.toFixed(limitPrecision(parseInt(exponentMatch[1], 10)));
        }
        return stringValue.replace(POINT, this.decimalSeparator);
    }
    formatValue(value, focused) {
        let formattedValue;
        if (value === null || !defined(value) || value === '') {
            formattedValue = '';
        }
        else if (focused && !this.readonly) {
            formattedValue = this.formatInputValue(value);
        }
        else {
            formattedValue = this.intl.formatNumber(value, this.format);
        }
        return formattedValue;
    }
    setInputValue(value = this.value, focused = this.focused) {
        const formattedValue = this.formatValue(value, focused);
        this.elementValue = formattedValue;
        this.inputValue = formattedValue;
    }
    verifySettings() {
        if (!isDevMode()) {
            return;
        }
        if (this.min !== null && this.max !== null && this.min > this.max) {
            throw new Error(`The max value should be bigger than the min. See ${MIN_DOC_LINK} and ${MAX_DOC_LINK}.`);
        }
    }
    verifyValue(value) {
        if (isDevMode() && value && typeof value !== 'number') {
            throw new Error(`The NumericTextBox component requires value of type Number and ${JSON.stringify(value)} was set.`);
        }
    }
    parseOptions(options) {
        for (let idx = 0; idx < options.length; idx++) {
            const name = options[idx];
            const value = this[name];
            if (typeof value === 'string') {
                const parsed = parseFloat(value);
                const valid = !isNaN(parsed);
                if (isDevMode() && !valid && value !== '') {
                    throw new Error('The NumericTextBox component requires value of type Number or a String representing ' +
                        `a number for the ${name} property and ${JSON.stringify(value)} was set.`);
                }
                this[name] = valid ? parsed : PARSABLE_DEFAULTS[name];
            }
        }
    }
    intlChange() {
        delete this.numericRegex;
        if (this.numericInput && (!this.focused || !this.isValid(this.elementValue))) {
            this.setInputValue();
        }
    }
    hasTrailingZeros(inputValue) {
        if (this.hasDecimals && this.focused) {
            const fraction = inputValue.split(this.decimalSeparator)[1];
            return fraction && fraction.length > this.decimals && fraction.lastIndexOf('0') === fraction.length - 1;
        }
    }
    selectAll() {
        this.setInputValue();
        this.setSelection(0, this.inputValue.length);
    }
    selectCaret() {
        const caretPosition = getCaretPosition(this.numericInput.nativeElement);
        const formattedValue = this.elementValue;
        const partialValue = formattedValue.substring(0, caretPosition);
        this.setInputValue();
        if (partialValue.length) {
            const significantCharsInFormattedValue = extractSignificantNumericChars(partialValue, this.decimalSeparator);
            const adjustedSignificantChars = this.adjustSignificantChars(formattedValue, significantCharsInFormattedValue);
            this.setSelection(adjustedSignificantChars, adjustedSignificantChars);
        }
        else {
            this.setSelection(0, 0);
        }
    }
    numberOfLeadingZeroes(formattedValue) {
        const separatorIndex = formattedValue.indexOf(this.decimalSeparator);
        const matchedLeadingZeroes = formattedValue.match(/^[^1-9]*?(0+)/);
        if (matchedLeadingZeroes) {
            const lengthOfMatch = matchedLeadingZeroes[0].length;
            const lengthOfLeadingZeroesMatch = matchedLeadingZeroes[1].length;
            return lengthOfMatch === separatorIndex ? lengthOfLeadingZeroesMatch - 1 : lengthOfLeadingZeroesMatch;
        }
        return 0;
    }
    adjustSignificantChars(formattedValue, significantChars) {
        const leadingZeroes = this.numberOfLeadingZeroes(formattedValue);
        if (leadingZeroes > 0) {
            return Math.max(0, significantChars - leadingZeroes);
        }
        return significantChars;
    }
    handleClasses(value, input) {
        const elem = this.hostElement.nativeElement;
        const classes = getStylingClasses('input', input, this[input], value);
        if (classes.toRemove) {
            this.renderer.removeClass(elem, classes.toRemove);
        }
        if (classes.toAdd) {
            this.renderer.addClass(elem, classes.toAdd);
        }
    }
    setInputAttributes() {
        const attributesToRender = Object.assign({}, this.mutableAttributes, this.parsedAttributes);
        setHTMLAttributes(attributesToRender, this.renderer, this.numericInput.nativeElement, this.ngZone);
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: NumericTextBoxComponent, deps: [{ token: i1.IntlService }, { token: i0.Renderer2 }, { token: i2.LocalizationService }, { token: i0.Injector }, { token: i0.NgZone }, { token: i0.ChangeDetectorRef }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
    static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: NumericTextBoxComponent, isStandalone: true, selector: "kendo-numerictextbox", inputs: { focusableId: "focusableId", disabled: "disabled", readonly: "readonly", title: "title", autoCorrect: "autoCorrect", format: "format", max: "max", min: "min", decimals: "decimals", placeholder: "placeholder", step: "step", spinners: "spinners", rangeValidation: "rangeValidation", tabindex: "tabindex", tabIndex: "tabIndex", changeValueOnScroll: "changeValueOnScroll", selectOnFocus: "selectOnFocus", value: "value", maxlength: "maxlength", size: "size", rounded: "rounded", fillMode: "fillMode", inputAttributes: "inputAttributes" }, outputs: { valueChange: "valueChange", onFocus: "focus", onBlur: "blur", inputFocus: "inputFocus", inputBlur: "inputBlur" }, host: { properties: { "class.k-readonly": "this.readonly", "attr.dir": "this.direction", "class.k-disabled": "this.disableClass", "class.k-input": "this.hostClasses", "class.k-numerictextbox": "this.hostClasses" } }, providers: [
            LocalizationService,
            { provide: L10N_PREFIX, useValue: 'kendo.numerictextbox' },
            { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NumericTextBoxComponent), multi: true },
            { provide: NG_VALIDATORS, useExisting: forwardRef(() => NumericTextBoxComponent), multi: true },
            { provide: KendoInput, useExisting: forwardRef(() => NumericTextBoxComponent) }
        ], queries: [{ propertyName: "suffixTemplate", first: true, predicate: SuffixTemplateDirective, descendants: true }, { propertyName: "prefixTemplate", first: true, predicate: PrefixTemplateDirective, descendants: true }], viewQueries: [{ propertyName: "numericInput", first: true, predicate: ["numericInput"], descendants: true, static: true }], exportAs: ["kendoNumericTextBox"], usesOnChanges: true, ngImport: i0, template: `
        <ng-container kendoNumericTextBoxLocalizedMessages
            i18n-increment="kendo.numerictextbox.increment|The title for the **Increment** button in the NumericTextBox"
            increment="Increase value"
            i18n-decrement="kendo.numerictextbox.decrement|The title for the **Decrement** button in the NumericTextBox"
            decrement="Decrease value"
        >
        </ng-container>
        <ng-container
            kendoInputSharedEvents
            [hostElement]="hostElement"
            [(isFocused)]="focused"
            (handleBlur)="handleBlur()"
            (onFocus)="handleFocus()"
        >
            <span *ngIf="prefixTemplate" class="k-input-prefix k-input-prefix-horizontal">
                <ng-template [ngTemplateOutlet]="prefixTemplate?.templateRef">
                </ng-template>
            </span>
            <kendo-input-separator *ngIf="prefixTemplate && prefixTemplate.showSeparator"></kendo-input-separator>
            <input #numericInput
                class="k-input-inner"
                role="spinbutton"
                autocomplete="off"
                autocorrect="off"
                [id]="focusableId"
                [attr.aria-valuemin]="min"
                [attr.aria-valuemax]="max"
                [attr.aria-valuenow]="value"
                [attr.title]="title"
                [attr.placeholder]="placeholder"
                [attr.maxLength]="maxlength"
                [tabindex]="tabIndex"
                [disabled]="disabled"
                [readonly]="readonly"
                [attr.aria-invalid]="isControlInvalid"
                [attr.required]="isControlRequired ? '' : null"
                [kendoEventsOutsideAngular]="{
                    mousedown: handleMouseDown,
                    dragenter: handleDragEnter,
                    keydown: handleKeyDown,
                    input: handleInput,
                    focus: handleInputFocus,
                    blur: handleInputBlur,
                    paste: handlePaste
                }"/>
            <kendo-input-separator *ngIf="suffixTemplate && suffixTemplate?.showSeparator"></kendo-input-separator>
            <span *ngIf="suffixTemplate" class="k-input-suffix k-input-suffix-horizontal">
                <ng-template [ngTemplateOutlet]="suffixTemplate?.templateRef">
                </ng-template>
            </span>
            <span
                class="k-input-spinner k-spin-button" *ngIf="spinners"
                [kendoEventsOutsideAngular]="{ mouseup: releaseArrow, mouseleave: releaseArrow }"
            >
            <button
                type="button"
                [kendoEventsOutsideAngular]="{ mousedown: increasePress }"
                [attr.aria-hidden]="true"
                [attr.aria-label]="incrementTitle"
                [title]="incrementTitle"
                class="k-spinner-increase k-button k-button-md k-icon-button k-button-solid k-button-solid-base"
                [class.k-active]="arrowDirection === ArrowDirection.Up"
                tabindex="-1"
            >
                <kendo-icon-wrapper
                    name="caret-alt-up"
                    innerCssClass="k-button-icon"
                    [svgIcon]="arrowUpIcon"
                >
                </kendo-icon-wrapper>
            </button>
            <button
                type="button"
                [kendoEventsOutsideAngular]="{ mousedown: decreasePress }"
                [attr.aria-hidden]="true"
                [attr.aria-label]="decrementTitle"
                [title]="decrementTitle"
                [class.k-active]="arrowDirection === ArrowDirection.Down"
                class="k-spinner-decrease k-button k-button-md k-icon-button k-button-solid k-button-solid-base"
                tabindex="-1"
            >
                <kendo-icon-wrapper
                    name="caret-alt-down"
                    innerCssClass="k-button-icon"
                    [svgIcon]="arrowDownIcon"
                >
                    </kendo-icon-wrapper>
                </button>
            </span>
        </ng-container>
    `, isInline: true, dependencies: [{ kind: "directive", type: LocalizedNumericTextBoxMessagesDirective, selector: "[kendoNumericTextBoxLocalizedMessages]" }, { kind: "directive", type: SharedInputEventsDirective, selector: "[kendoInputSharedEvents]", inputs: ["hostElement", "clearButtonClicked", "isFocused"], outputs: ["isFocusedChange", "onFocus", "handleBlur"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: InputSeparatorComponent, selector: "kendo-input-separator, kendo-textbox-separator", inputs: ["orientation"] }, { kind: "directive", type: EventsOutsideAngularDirective, selector: "[kendoEventsOutsideAngular]", inputs: ["kendoEventsOutsideAngular", "scope"] }, { kind: "component", type: IconWrapperComponent, selector: "kendo-icon-wrapper", inputs: ["name", "svgIcon", "innerCssClass", "customFontClass", "size"], exportAs: ["kendoIconWrapper"] }] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: NumericTextBoxComponent, decorators: [{
            type: Component,
            args: [{
                    exportAs: 'kendoNumericTextBox',
                    providers: [
                        LocalizationService,
                        { provide: L10N_PREFIX, useValue: 'kendo.numerictextbox' },
                        { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NumericTextBoxComponent), multi: true },
                        { provide: NG_VALIDATORS, useExisting: forwardRef(() => NumericTextBoxComponent), multi: true },
                        { provide: KendoInput, useExisting: forwardRef(() => NumericTextBoxComponent) }
                    ],
                    selector: 'kendo-numerictextbox',
                    template: `
        <ng-container kendoNumericTextBoxLocalizedMessages
            i18n-increment="kendo.numerictextbox.increment|The title for the **Increment** button in the NumericTextBox"
            increment="Increase value"
            i18n-decrement="kendo.numerictextbox.decrement|The title for the **Decrement** button in the NumericTextBox"
            decrement="Decrease value"
        >
        </ng-container>
        <ng-container
            kendoInputSharedEvents
            [hostElement]="hostElement"
            [(isFocused)]="focused"
            (handleBlur)="handleBlur()"
            (onFocus)="handleFocus()"
        >
            <span *ngIf="prefixTemplate" class="k-input-prefix k-input-prefix-horizontal">
                <ng-template [ngTemplateOutlet]="prefixTemplate?.templateRef">
                </ng-template>
            </span>
            <kendo-input-separator *ngIf="prefixTemplate && prefixTemplate.showSeparator"></kendo-input-separator>
            <input #numericInput
                class="k-input-inner"
                role="spinbutton"
                autocomplete="off"
                autocorrect="off"
                [id]="focusableId"
                [attr.aria-valuemin]="min"
                [attr.aria-valuemax]="max"
                [attr.aria-valuenow]="value"
                [attr.title]="title"
                [attr.placeholder]="placeholder"
                [attr.maxLength]="maxlength"
                [tabindex]="tabIndex"
                [disabled]="disabled"
                [readonly]="readonly"
                [attr.aria-invalid]="isControlInvalid"
                [attr.required]="isControlRequired ? '' : null"
                [kendoEventsOutsideAngular]="{
                    mousedown: handleMouseDown,
                    dragenter: handleDragEnter,
                    keydown: handleKeyDown,
                    input: handleInput,
                    focus: handleInputFocus,
                    blur: handleInputBlur,
                    paste: handlePaste
                }"/>
            <kendo-input-separator *ngIf="suffixTemplate && suffixTemplate?.showSeparator"></kendo-input-separator>
            <span *ngIf="suffixTemplate" class="k-input-suffix k-input-suffix-horizontal">
                <ng-template [ngTemplateOutlet]="suffixTemplate?.templateRef">
                </ng-template>
            </span>
            <span
                class="k-input-spinner k-spin-button" *ngIf="spinners"
                [kendoEventsOutsideAngular]="{ mouseup: releaseArrow, mouseleave: releaseArrow }"
            >
            <button
                type="button"
                [kendoEventsOutsideAngular]="{ mousedown: increasePress }"
                [attr.aria-hidden]="true"
                [attr.aria-label]="incrementTitle"
                [title]="incrementTitle"
                class="k-spinner-increase k-button k-button-md k-icon-button k-button-solid k-button-solid-base"
                [class.k-active]="arrowDirection === ArrowDirection.Up"
                tabindex="-1"
            >
                <kendo-icon-wrapper
                    name="caret-alt-up"
                    innerCssClass="k-button-icon"
                    [svgIcon]="arrowUpIcon"
                >
                </kendo-icon-wrapper>
            </button>
            <button
                type="button"
                [kendoEventsOutsideAngular]="{ mousedown: decreasePress }"
                [attr.aria-hidden]="true"
                [attr.aria-label]="decrementTitle"
                [title]="decrementTitle"
                [class.k-active]="arrowDirection === ArrowDirection.Down"
                class="k-spinner-decrease k-button k-button-md k-icon-button k-button-solid k-button-solid-base"
                tabindex="-1"
            >
                <kendo-icon-wrapper
                    name="caret-alt-down"
                    innerCssClass="k-button-icon"
                    [svgIcon]="arrowDownIcon"
                >
                    </kendo-icon-wrapper>
                </button>
            </span>
        </ng-container>
    `,
                    standalone: true,
                    imports: [LocalizedNumericTextBoxMessagesDirective, SharedInputEventsDirective, NgIf, NgTemplateOutlet, InputSeparatorComponent, EventsOutsideAngularDirective, IconWrapperComponent]
                }]
        }], ctorParameters: function () { return [{ type: i1.IntlService }, { type: i0.Renderer2 }, { type: i2.LocalizationService }, { type: i0.Injector }, { type: i0.NgZone }, { type: i0.ChangeDetectorRef }, { type: i0.ElementRef }]; }, propDecorators: { focusableId: [{
                type: Input
            }], disabled: [{
                type: Input
            }], readonly: [{
                type: Input
            }, {
                type: HostBinding,
                args: ['class.k-readonly']
            }], title: [{
                type: Input
            }], autoCorrect: [{
                type: Input
            }], format: [{
                type: Input
            }], max: [{
                type: Input
            }], min: [{
                type: Input
            }], decimals: [{
                type: Input
            }], placeholder: [{
                type: Input
            }], step: [{
                type: Input
            }], spinners: [{
                type: Input
            }], rangeValidation: [{
                type: Input
            }], tabindex: [{
                type: Input
            }], tabIndex: [{
                type: Input
            }], changeValueOnScroll: [{
                type: Input
            }], selectOnFocus: [{
                type: Input
            }], value: [{
                type: Input
            }], maxlength: [{
                type: Input
            }], size: [{
                type: Input
            }], rounded: [{
                type: Input
            }], fillMode: [{
                type: Input
            }], inputAttributes: [{
                type: Input
            }], valueChange: [{
                type: Output
            }], onFocus: [{
                type: Output,
                args: ['focus']
            }], onBlur: [{
                type: Output,
                args: ['blur']
            }], inputFocus: [{
                type: Output
            }], inputBlur: [{
                type: Output
            }], numericInput: [{
                type: ViewChild,
                args: ['numericInput', { static: true }]
            }], suffixTemplate: [{
                type: ContentChild,
                args: [SuffixTemplateDirective]
            }], prefixTemplate: [{
                type: ContentChild,
                args: [PrefixTemplateDirective]
            }], direction: [{
                type: HostBinding,
                args: ['attr.dir']
            }], disableClass: [{
                type: HostBinding,
                args: ['class.k-disabled']
            }], hostClasses: [{
                type: HostBinding,
                args: ['class.k-input']
            }, {
                type: HostBinding,
                args: ['class.k-numerictextbox']
            }] } });