primeng
Version:
PrimeNG is a premium UI library for Angular featuring a rich set of 90+ components, a theme designer, various theme alternatives such as Material, Bootstrap, Tailwind, premium templates and professional support. In addition, it integrates with PrimeBlock,
1,153 lines (1,151 loc) • 82 kB
JavaScript
import { NgTemplateOutlet } from '@angular/common';
import * as i0 from '@angular/core';
import { Injectable, InjectionToken, forwardRef, inject, input, booleanAttribute, numberAttribute, output, contentChild, viewChild, computed, effect, Injector, signal, ViewEncapsulation, ChangeDetectionStrategy, Component, NgModule } from '@angular/core';
import { NG_VALUE_ACCESSOR, NgControl } from '@angular/forms';
import { AngleDown } from '@primeicons/angular/angle-down';
import { AngleUp } from '@primeicons/angular/angle-up';
import { Times } from '@primeicons/angular/times';
import { getSelection } from '@primeuix/utils';
import { SharedModule } from 'primeng/api';
import { AutoFocus } from 'primeng/autofocus';
import { PARENT_INSTANCE } from 'primeng/basecomponent';
import { BaseInput } from 'primeng/baseinput';
import * as i1 from 'primeng/bind';
import { Bind, BindModule } from 'primeng/bind';
import { InputText } from 'primeng/inputtext';
import { style } from '@primeuix/styles/inputnumber';
import { BaseStyle } from 'primeng/base';
export * from 'primeng/types/inputnumber';
const classes = {
root: ({ instance }) => [
'p-inputnumber p-component p-inputwrapper',
{
'p-invalid': instance.invalid(),
'p-inputwrapper-filled': instance.$filled() || instance.allowEmpty() === false,
'p-inputwrapper-focus': instance.focused,
'p-inputnumber-stacked': instance.showButtons() && instance.buttonLayout() === 'stacked',
'p-inputnumber-horizontal': instance.showButtons() && instance.buttonLayout() === 'horizontal',
'p-inputnumber-vertical': instance.showButtons() && instance.buttonLayout() === 'vertical',
'p-inputnumber-fluid': instance.hasFluid
}
],
pcInputText: 'p-inputnumber-input',
clearIcon: 'p-inputnumber-clear-icon',
buttonGroup: 'p-inputnumber-button-group',
incrementButton: ({ instance }) => [
'p-inputnumber-button p-inputnumber-increment-button',
{
'p-disabled': instance.showButtons() && instance.max() != null && instance.maxlength()
}
],
decrementButton: ({ instance }) => [
'p-inputnumber-button p-inputnumber-decrement-button',
{
'p-disabled': instance.showButtons() && instance.min() != null && instance.minlength()
}
]
};
class InputNumberStyle extends BaseStyle {
name = 'inputnumber';
style = style;
classes = classes;
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: InputNumberStyle, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: InputNumberStyle });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: InputNumberStyle, decorators: [{
type: Injectable
}] });
/**
*
* InputNumber is an input component to provide numerical input.
*
* [Live Demo](https://www.primeng.org/inputnumber/)
*
* @module inputnumberstyle
*
*/
var InputNumberClasses;
(function (InputNumberClasses) {
/**
* Class name of the root element
*/
InputNumberClasses["root"] = "p-inputnumber";
/**
* Class name of the input element
*/
InputNumberClasses["pcInputText"] = "p-inputnumber-input";
/**
* Class name of the button group element
*/
InputNumberClasses["buttonGroup"] = "p-inputnumber-button-group";
/**
* Class name of the increment button element
*/
InputNumberClasses["incrementButton"] = "p-inputnumber-increment-button";
/**
* Class name of the decrement button element
*/
InputNumberClasses["decrementButton"] = "p-inputnumber-decrement-button";
/**
* Class name of the clear icon
*/
InputNumberClasses["clearIcon"] = "p-autocomplete-clear-icon";
})(InputNumberClasses || (InputNumberClasses = {}));
const INPUTNUMBER_INSTANCE = new InjectionToken('INPUTNUMBER_INSTANCE');
const INPUTNUMBER_VALUE_ACCESSOR = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => InputNumber),
multi: true
};
/**
* InputNumber is an input component to provide numerical input.
* @group Components
*/
class InputNumber extends BaseInput {
componentName = 'InputNumber';
$pcInputNumber = inject(INPUTNUMBER_INSTANCE, { optional: true, skipSelf: true }) ?? undefined;
_componentStyle = inject(InputNumberStyle);
bindDirectiveInstance = inject(Bind, { self: true });
onAfterViewChecked() {
this.bindDirectiveInstance.setAttrs(this.ptms(['host', 'root']));
}
/**
* Displays spinner buttons.
* @group Props
*/
showButtons = input(false, { ...(ngDevMode ? { debugName: "showButtons" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
/**
* Whether to format the value.
* @group Props
*/
format = input(true, { ...(ngDevMode ? { debugName: "format" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
/**
* Layout of the buttons, valid values are "stacked" (default), "horizontal" and "vertical".
* @group Props
*/
buttonLayout = input('stacked', /* @ts-ignore */
...(ngDevMode ? [{ debugName: "buttonLayout" }] : /* istanbul ignore next */ []));
/**
* Identifier of the focus input to match a label defined for the component.
* @group Props
*/
inputId = input(/* @ts-ignore */
...(ngDevMode ? [undefined, { debugName: "inputId" }] : /* istanbul ignore next */ []));
/**
* Advisory information to display on input.
* @group Props
*/
placeholder = input(/* @ts-ignore */
...(ngDevMode ? [undefined, { debugName: "placeholder" }] : /* istanbul ignore next */ []));
/**
* Specifies tab order of the element.
* @group Props
*/
tabindex = input(undefined, { ...(ngDevMode ? { debugName: "tabindex" } : /* istanbul ignore next */ {}), transform: numberAttribute });
/**
* Title text of the input text.
* @group Props
*/
title = input(/* @ts-ignore */
...(ngDevMode ? [undefined, { debugName: "title" }] : /* istanbul ignore next */ []));
/**
* Specifies one or more IDs in the DOM that labels the input field.
* @group Props
*/
ariaLabelledBy = input(/* @ts-ignore */
...(ngDevMode ? [undefined, { debugName: "ariaLabelledBy" }] : /* istanbul ignore next */ []));
/**
* Specifies one or more IDs in the DOM that describes the input field.
* @group Props
*/
ariaDescribedBy = input(/* @ts-ignore */
...(ngDevMode ? [undefined, { debugName: "ariaDescribedBy" }] : /* istanbul ignore next */ []));
/**
* Used to define a string that labels the input element.
* @group Props
*/
ariaLabel = input(/* @ts-ignore */
...(ngDevMode ? [undefined, { debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
/**
* Used to indicate that user input is required on an element before a form can be submitted.
* @group Props
*/
ariaRequired = input(undefined, { ...(ngDevMode ? { debugName: "ariaRequired" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
/**
* Used to define a string that autocomplete attribute the current element.
* @group Props
*/
autocomplete = input(/* @ts-ignore */
...(ngDevMode ? [undefined, { debugName: "autocomplete" }] : /* istanbul ignore next */ []));
/**
* Style class of the increment button.
* @group Props
*/
incrementButtonClass = input(/* @ts-ignore */
...(ngDevMode ? [undefined, { debugName: "incrementButtonClass" }] : /* istanbul ignore next */ []));
/**
* Style class of the decrement button.
* @group Props
*/
decrementButtonClass = input(/* @ts-ignore */
...(ngDevMode ? [undefined, { debugName: "decrementButtonClass" }] : /* istanbul ignore next */ []));
/**
* Style class of the increment button.
* @group Props
*/
incrementButtonIcon = input(/* @ts-ignore */
...(ngDevMode ? [undefined, { debugName: "incrementButtonIcon" }] : /* istanbul ignore next */ []));
/**
* Style class of the decrement button.
* @group Props
*/
decrementButtonIcon = input(/* @ts-ignore */
...(ngDevMode ? [undefined, { debugName: "decrementButtonIcon" }] : /* istanbul ignore next */ []));
/**
* When present, it specifies that an input field is read-only.
* @group Props
*/
readonly = input(undefined, { ...(ngDevMode ? { debugName: "readonly" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
/**
* Determines whether the input field is empty.
* @group Props
*/
allowEmpty = input(true, { ...(ngDevMode ? { debugName: "allowEmpty" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
/**
* Locale to be used in formatting.
* @group Props
*/
locale = input(/* @ts-ignore */
...(ngDevMode ? [undefined, { debugName: "locale" }] : /* istanbul ignore next */ []));
/**
* The locale matching algorithm to use. Possible values are "lookup" and "best fit"; the default is "best fit". See Locale Negotiation for details.
* @group Props
*/
localeMatcher = input(/* @ts-ignore */
...(ngDevMode ? [undefined, { debugName: "localeMatcher" }] : /* istanbul ignore next */ []));
/**
* Defines the behavior of the component, valid values are "decimal" and "currency".
* @group Props
*/
mode = input('decimal', /* @ts-ignore */
...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
/**
* The currency to use in currency formatting. Possible values are the ISO 4217 currency codes, such as "USD" for the US dollar, "EUR" for the euro, or "CNY" for the Chinese RMB. There is no default value; if the style is "currency", the currency property must be provided.
* @group Props
*/
currency = input(/* @ts-ignore */
...(ngDevMode ? [undefined, { debugName: "currency" }] : /* istanbul ignore next */ []));
/**
* How to display the currency in currency formatting. Possible values are "symbol" to use a localized currency symbol such as €, ü"code" to use the ISO currency code, "name" to use a localized currency name such as "dollar"; the default is "symbol".
* @group Props
*/
currencyDisplay = input(/* @ts-ignore */
...(ngDevMode ? [undefined, { debugName: "currencyDisplay" }] : /* istanbul ignore next */ []));
/**
* Whether to use grouping separators, such as thousands separators or thousand/lakh/crore separators.
* @group Props
*/
useGrouping = input(true, { ...(ngDevMode ? { debugName: "useGrouping" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
/**
* The minimum number of fraction digits to use. Possible values are from 0 to 20; the default for plain number and percent formatting is 0; the default for currency formatting is the number of minor unit digits provided by the ISO 4217 currency code list (2 if the list doesn't provide that information).
* @group Props
*/
minFractionDigits = input(undefined, { ...(ngDevMode ? { debugName: "minFractionDigits" } : /* istanbul ignore next */ {}), transform: (value) => numberAttribute(value, undefined) });
/**
* The maximum number of fraction digits to use. Possible values are from 0 to 20; the default for plain number formatting is the larger of minimumFractionDigits and 3; the default for currency formatting is the larger of minimumFractionDigits and the number of minor unit digits provided by the ISO 4217 currency code list (2 if the list doesn't provide that information).
* @group Props
*/
maxFractionDigits = input(undefined, { ...(ngDevMode ? { debugName: "maxFractionDigits" } : /* istanbul ignore next */ {}), transform: (value) => numberAttribute(value, undefined) });
/**
* Text to display before the value.
* @group Props
*/
prefix = input(/* @ts-ignore */
...(ngDevMode ? [undefined, { debugName: "prefix" }] : /* istanbul ignore next */ []));
/**
* Text to display after the value.
* @group Props
*/
suffix = input(/* @ts-ignore */
...(ngDevMode ? [undefined, { debugName: "suffix" }] : /* istanbul ignore next */ []));
/**
* Inline style of the input field.
* @group Props
*/
inputStyle = input(/* @ts-ignore */
...(ngDevMode ? [undefined, { debugName: "inputStyle" }] : /* istanbul ignore next */ []));
/**
* Style class of the input field.
* @group Props
*/
inputStyleClass = input(/* @ts-ignore */
...(ngDevMode ? [undefined, { debugName: "inputStyleClass" }] : /* istanbul ignore next */ []));
/**
* When enabled, a clear icon is displayed to clear the value.
* @group Props
*/
showClear = input(false, { ...(ngDevMode ? { debugName: "showClear" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
/**
* When present, it specifies that the component should automatically get focus on load.
* @group Props
*/
autofocus = input(undefined, { ...(ngDevMode ? { debugName: "autofocus" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
/**
* Callback to invoke on input.
* @param {InputNumberInputEvent} event - Custom input event.
* @group Emits
*/
onInput = output();
/**
* Callback to invoke when the component receives focus.
* @param {Event} event - Browser event.
* @group Emits
*/
onFocus = output();
/**
* Callback to invoke when the component loses focus.
* @param {Event} event - Browser event.
* @group Emits
*/
onBlur = output();
/**
* Callback to invoke on input key press.
* @param {KeyboardEvent} event - Keyboard event.
* @group Emits
*/
onKeyDown = output();
/**
* Callback to invoke when clear token is clicked.
* @group Emits
*/
onClear = output();
/**
* Custom clear icon template.
* @group Templates
*/
clearIconTemplate = contentChild('clearicon', { ...(ngDevMode ? { debugName: "clearIconTemplate" } : /* istanbul ignore next */ {}), descendants: false });
/**
* Custom increment button icon template.
* @group Templates
*/
incrementButtonIconTemplate = contentChild('incrementbuttonicon', { ...(ngDevMode ? { debugName: "incrementButtonIconTemplate" } : /* istanbul ignore next */ {}), descendants: false });
/**
* Custom decrement button icon template.
* @group Templates
*/
decrementButtonIconTemplate = contentChild('decrementbuttonicon', { ...(ngDevMode ? { debugName: "decrementButtonIconTemplate" } : /* istanbul ignore next */ {}), descendants: false });
input = viewChild.required('input', /* @ts-ignore */
...(ngDevMode ? [{ debugName: "input" }] : /* istanbul ignore next */ []));
requiredAttr = computed(() => (this.required() ? '' : undefined), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "requiredAttr" }] : /* istanbul ignore next */ []));
readonlyAttr = computed(() => (this.readonly() ? '' : undefined), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "readonlyAttr" }] : /* istanbul ignore next */ []));
disabledAttr = computed(() => (this.$disabled() ? '' : undefined), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "disabledAttr" }] : /* istanbul ignore next */ []));
get showClearIcon() {
return this.buttonLayout() !== 'vertical' && this.showClear() && this.value() != null;
}
showStackedButtons = computed(() => this.showButtons() && this.buttonLayout() === 'stacked', /* @ts-ignore */
...(ngDevMode ? [{ debugName: "showStackedButtons" }] : /* istanbul ignore next */ []));
showNonStackedButtons = computed(() => this.showButtons() && this.buttonLayout() !== 'stacked', /* @ts-ignore */
...(ngDevMode ? [{ debugName: "showNonStackedButtons" }] : /* istanbul ignore next */ []));
hasIncrementButtonIcon = computed(() => !!this.incrementButtonIcon(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "hasIncrementButtonIcon" }] : /* istanbul ignore next */ []));
hasDecrementButtonIcon = computed(() => !!this.decrementButtonIcon(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "hasDecrementButtonIcon" }] : /* istanbul ignore next */ []));
parserConfig = computed(() => ({
locale: this.locale(),
localeMatcher: this.localeMatcher(),
mode: this.mode(),
currency: this.currency(),
currencyDisplay: this.currencyDisplay(),
useGrouping: this.useGrouping(),
minFractionDigits: this.minFractionDigits(),
maxFractionDigits: this.maxFractionDigits(),
prefix: this.prefix(),
suffix: this.suffix()
}), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "parserConfig" }] : /* istanbul ignore next */ []));
constructor() {
super();
effect(() => {
this.parserConfig();
this.updateConstructParser();
});
}
_injector = inject(Injector);
value = signal(undefined, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
focused;
initialized;
groupChar = '';
prefixChar = '';
suffixChar = '';
isSpecialChar;
timer = null;
lastValue;
_numeral = /./g;
numberFormat = null;
_decimal = /./g;
_decimalChar = '';
_group = /./g;
_minusSign = /./g;
_currency;
_prefix;
_suffix;
_index = () => undefined;
ngControl = null;
onInit() {
this.ngControl = this._injector.get(NgControl, null, { optional: true });
this.constructParser();
this.initialized = true;
}
getOptions() {
// Validate fraction digits according to Intl.NumberFormat specifications
// Handle potential NaN, Infinity, or invalid values
const validateFractionDigits = (value, min, max) => {
if (value == null || isNaN(value) || !isFinite(value)) {
return undefined;
}
return Math.max(min, Math.min(max, Math.floor(value)));
};
const minFractionDigits = validateFractionDigits(this.minFractionDigits(), 0, 20);
const maxFractionDigits = validateFractionDigits(this.maxFractionDigits(), 0, 100);
// Ensure minFractionDigits <= maxFractionDigits
const validatedMinFractionDigits = minFractionDigits != null && maxFractionDigits != null && minFractionDigits > maxFractionDigits ? maxFractionDigits : minFractionDigits;
return {
localeMatcher: this.localeMatcher(),
style: this.mode(),
currency: this.currency(),
currencyDisplay: this.currencyDisplay(),
useGrouping: this.useGrouping(),
minimumFractionDigits: validatedMinFractionDigits,
maximumFractionDigits: maxFractionDigits
};
}
constructParser() {
const options = this.getOptions();
// Remove any properties with undefined or invalid values to let Intl.NumberFormat use defaults
const cleanOptions = Object.fromEntries(Object.entries(options).filter(([_key, value]) => value !== undefined));
this.numberFormat = new Intl.NumberFormat(this.locale(), cleanOptions);
const numerals = [...new Intl.NumberFormat(this.locale(), { useGrouping: false }).format(9876543210)].reverse();
const index = new Map(numerals.map((d, i) => [d, i]));
this._numeral = new RegExp(`[${numerals.join('')}]`, 'g');
this._group = this.getGroupingExpression();
this._minusSign = this.getMinusSignExpression();
this._currency = this.getCurrencyExpression();
this._decimal = this.getDecimalExpression();
this._decimalChar = this.getDecimalChar();
this._suffix = this.getSuffixExpression();
this._prefix = this.getPrefixExpression();
this._index = (d) => index.get(d);
}
updateConstructParser() {
if (this.initialized) {
this.constructParser();
}
}
escapeRegExp(text) {
return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
}
getDecimalExpression() {
const decimalChar = this.getDecimalChar();
return new RegExp(`[${decimalChar}]`, 'g');
}
getDecimalChar() {
const formatter = new Intl.NumberFormat(this.locale(), { ...this.getOptions(), useGrouping: false });
return formatter
.format(1.1)
.replace(this._currency, '')
.trim()
.replace(this._numeral, '');
}
getGroupingExpression() {
const formatter = new Intl.NumberFormat(this.locale(), { ...this.getOptions(), useGrouping: true });
const groupPart = formatter.formatToParts(1000000).find((part) => part.type === 'group');
this.groupChar = groupPart ? groupPart.value : '';
return new RegExp(`[${this.groupChar}]`, 'g');
}
getMinusSignExpression() {
const formatter = new Intl.NumberFormat(this.locale(), { useGrouping: false });
return new RegExp(`[${formatter.format(-1).trim().replace(this._numeral, '')}]`, 'g');
}
getCurrencyExpression() {
if (this.currency()) {
const formatter = new Intl.NumberFormat(this.locale(), {
style: 'currency',
currency: this.currency(),
currencyDisplay: this.currencyDisplay(),
minimumFractionDigits: 0,
maximumFractionDigits: 0
});
return new RegExp(`[${formatter.format(1).replace(/\s/g, '').replace(this._numeral, '').replace(this._group, '')}]`, 'g');
}
return new RegExp(`[]`, 'g');
}
getPrefixExpression() {
const prefix = this.prefix();
if (prefix) {
this.prefixChar = prefix;
}
else {
const formatter = new Intl.NumberFormat(this.locale(), {
style: this.mode(),
currency: this.currency(),
currencyDisplay: this.currencyDisplay()
});
this.prefixChar = formatter.format(1).split('1')[0];
}
return new RegExp(`${this.escapeRegExp(this.prefixChar || '')}`, 'g');
}
getSuffixExpression() {
const suffix = this.suffix();
if (suffix) {
this.suffixChar = suffix;
}
else {
const formatter = new Intl.NumberFormat(this.locale(), {
style: this.mode(),
currency: this.currency(),
currencyDisplay: this.currencyDisplay(),
minimumFractionDigits: 0,
maximumFractionDigits: 0
});
this.suffixChar = formatter.format(1).split('1')[1];
}
return new RegExp(`${this.escapeRegExp(this.suffixChar || '')}`, 'g');
}
formatValue(value) {
if (value != null) {
if (value === '-') {
// Minus sign
return value;
}
const prefix = this.prefix();
const suffix = this.suffix();
if (this.format()) {
let formatter = new Intl.NumberFormat(this.locale(), this.getOptions());
let formattedValue = formatter.format(value);
if (prefix && value != prefix) {
formattedValue = prefix + formattedValue;
}
if (suffix && value != suffix) {
formattedValue = formattedValue + suffix;
}
return formattedValue;
}
return value.toString();
}
return '';
}
parseValue(text) {
const suffixRegex = this._suffix ? new RegExp(this._suffix, '') : /(?:)/;
const prefixRegex = this._prefix ? new RegExp(this._prefix, '') : /(?:)/;
const currencyRegex = this._currency ? new RegExp(this._currency, '') : /(?:)/;
let filteredText = text
.replace(suffixRegex, '')
.replace(prefixRegex, '')
.trim()
.replace(/\s/g, '')
.replace(currencyRegex, '')
.replace(this._group, '')
.replace(this._minusSign, '-')
.replace(this._decimal, '.')
.replace(this._numeral, this._index);
if (filteredText) {
if (filteredText === '-')
// Minus sign
return filteredText;
let parsedValue = +filteredText;
return isNaN(parsedValue) ? null : parsedValue;
}
return null;
}
repeat(event, interval, dir) {
if (this.readonly()) {
return;
}
let i = interval || 500;
this.clearTimer();
this.timer = setTimeout(() => {
this.repeat(event, 40, dir);
}, i);
this.spin(event, dir);
}
spin(event, dir) {
let step = (this.step() ?? 1) * dir;
let currentValue = this.parseValue(this.input()?.nativeElement.value) || 0;
let newValue = this.validateValue(currentValue + step);
const max = this.maxlength();
if (max && max < this.formatValue(newValue).length) {
return;
}
this.updateInput(newValue, null, 'spin', null);
this.updateModel(event, newValue);
this.handleOnInput(event, currentValue, newValue);
}
clear() {
this.value.set(null);
this.onModelChange(this.value());
this.onClear.emit();
}
onUpButtonMouseDown(event) {
if (event.button === 2) {
this.clearTimer();
return;
}
if (!this.$disabled()) {
this.input()?.nativeElement.focus();
this.repeat(event, null, 1);
event.preventDefault();
}
}
onUpButtonMouseUp() {
if (!this.$disabled()) {
this.clearTimer();
}
}
onUpButtonMouseLeave() {
if (!this.$disabled()) {
this.clearTimer();
}
}
onUpButtonKeyDown(event) {
if (event.keyCode === 32 || event.keyCode === 13) {
this.repeat(event, null, 1);
}
}
onUpButtonKeyUp() {
if (!this.$disabled()) {
this.clearTimer();
}
}
onDownButtonMouseDown(event) {
if (event.button === 2) {
this.clearTimer();
return;
}
if (!this.$disabled()) {
this.input()?.nativeElement.focus();
this.repeat(event, null, -1);
event.preventDefault();
}
}
onDownButtonMouseUp() {
if (!this.$disabled()) {
this.clearTimer();
}
}
onDownButtonMouseLeave() {
if (!this.$disabled()) {
this.clearTimer();
}
}
onDownButtonKeyUp() {
if (!this.$disabled()) {
this.clearTimer();
}
}
onDownButtonKeyDown(event) {
if (event.keyCode === 32 || event.keyCode === 13) {
this.repeat(event, null, -1);
}
}
onUserInput(event) {
if (this.readonly()) {
return;
}
if (this.isSpecialChar) {
event.target.value = this.lastValue;
}
this.isSpecialChar = false;
}
onInputKeyDown(event) {
if (this.readonly()) {
return;
}
this.lastValue = event.target.value;
if (event.shiftKey || event.altKey) {
this.isSpecialChar = true;
return;
}
let selectionStart = event.target.selectionStart;
let selectionEnd = event.target.selectionEnd;
let inputValue = event.target.value;
let newValueStr = null;
if (event.altKey) {
event.preventDefault();
}
switch (event.key) {
case 'ArrowUp':
this.spin(event, 1);
event.preventDefault();
break;
case 'ArrowDown':
this.spin(event, -1);
event.preventDefault();
break;
case 'ArrowLeft':
for (let index = selectionStart; index <= inputValue.length; index++) {
const previousCharIndex = index === 0 ? 0 : index - 1;
if (this.isNumeralChar(inputValue.charAt(previousCharIndex))) {
this.input().nativeElement.setSelectionRange(index, index);
break;
}
}
break;
case 'ArrowRight':
for (let index = selectionEnd; index >= 0; index--) {
if (this.isNumeralChar(inputValue.charAt(index))) {
this.input().nativeElement.setSelectionRange(index, index);
break;
}
}
break;
case 'Tab':
case 'Enter':
newValueStr = this.validateValue(this.parseValue(this.input().nativeElement.value));
this.input().nativeElement.value = this.formatValue(newValueStr);
this.input().nativeElement.setAttribute('aria-valuenow', newValueStr);
this.updateModel(event, newValueStr);
break;
case 'Backspace': {
event.preventDefault();
if (selectionStart === selectionEnd) {
if ((selectionStart == 1 && this.prefix()) || (selectionStart == inputValue.length && this.suffix())) {
break;
}
const deleteChar = inputValue.charAt(selectionStart - 1);
const { decimalCharIndex, decimalCharIndexWithoutPrefix } = this.getDecimalCharIndexes(inputValue);
if (this.isNumeralChar(deleteChar)) {
const decimalLength = this.getDecimalLength(inputValue);
if (this._group.test(deleteChar)) {
this._group.lastIndex = 0;
newValueStr = inputValue.slice(0, selectionStart - 2) + inputValue.slice(selectionStart - 1);
}
else if (this._decimal.test(deleteChar)) {
this._decimal.lastIndex = 0;
if (decimalLength) {
this.input()?.nativeElement.setSelectionRange(selectionStart - 1, selectionStart - 1);
}
else {
newValueStr = inputValue.slice(0, selectionStart - 1) + inputValue.slice(selectionStart);
}
}
else if (decimalCharIndex > 0 && selectionStart > decimalCharIndex) {
const insertedText = this.isDecimalMode() && (this.minFractionDigits() || 0) < decimalLength ? '' : '0';
newValueStr = inputValue.slice(0, selectionStart - 1) + insertedText + inputValue.slice(selectionStart);
}
else if (decimalCharIndexWithoutPrefix === 1) {
newValueStr = inputValue.slice(0, selectionStart - 1) + '0' + inputValue.slice(selectionStart);
newValueStr = this.parseValue(newValueStr) > 0 ? newValueStr : '';
}
else {
newValueStr = inputValue.slice(0, selectionStart - 1) + inputValue.slice(selectionStart);
}
}
else if (this.mode() === 'currency' && this._currency && deleteChar.search(this._currency) != -1) {
newValueStr = inputValue.slice(1);
}
this.updateValue(event, newValueStr, null, 'delete-single');
}
else {
newValueStr = this.deleteRange(inputValue, selectionStart, selectionEnd);
this.updateValue(event, newValueStr, null, 'delete-range');
}
break;
}
case 'Delete':
event.preventDefault();
if (selectionStart === selectionEnd) {
if ((selectionStart == 0 && this.prefix()) || (selectionStart == inputValue.length - 1 && this.suffix())) {
break;
}
const deleteChar = inputValue.charAt(selectionStart);
const { decimalCharIndex, decimalCharIndexWithoutPrefix } = this.getDecimalCharIndexes(inputValue);
if (this.isNumeralChar(deleteChar)) {
const decimalLength = this.getDecimalLength(inputValue);
if (this._group.test(deleteChar)) {
this._group.lastIndex = 0;
newValueStr = inputValue.slice(0, selectionStart) + inputValue.slice(selectionStart + 2);
}
else if (this._decimal.test(deleteChar)) {
this._decimal.lastIndex = 0;
if (decimalLength) {
this.input()?.nativeElement.setSelectionRange(selectionStart + 1, selectionStart + 1);
}
else {
newValueStr = inputValue.slice(0, selectionStart) + inputValue.slice(selectionStart + 1);
}
}
else if (decimalCharIndex > 0 && selectionStart > decimalCharIndex) {
const insertedText = this.isDecimalMode() && (this.minFractionDigits() || 0) < decimalLength ? '' : '0';
newValueStr = inputValue.slice(0, selectionStart) + insertedText + inputValue.slice(selectionStart + 1);
}
else if (decimalCharIndexWithoutPrefix === 1) {
newValueStr = inputValue.slice(0, selectionStart) + '0' + inputValue.slice(selectionStart + 1);
newValueStr = this.parseValue(newValueStr) > 0 ? newValueStr : '';
}
else {
newValueStr = inputValue.slice(0, selectionStart) + inputValue.slice(selectionStart + 1);
}
}
this.updateValue(event, newValueStr, null, 'delete-back-single');
}
else {
newValueStr = this.deleteRange(inputValue, selectionStart, selectionEnd);
this.updateValue(event, newValueStr, null, 'delete-range');
}
break;
case 'Home':
if (this.min()) {
this.updateModel(event, this.min());
event.preventDefault();
}
break;
case 'End':
if (this.max()) {
this.updateModel(event, this.max());
event.preventDefault();
}
break;
default:
break;
}
this.onKeyDown.emit(event);
}
onInputKeyPress(event) {
if (this.readonly()) {
return;
}
let code = event.which || event.keyCode;
let char = String.fromCharCode(code);
let isDecimalSign = this.isDecimalSign(char);
const isMinusSign = this.isMinusSign(char);
if (code != 13) {
event.preventDefault();
}
if (!isDecimalSign && event.code === 'NumpadDecimal') {
isDecimalSign = true;
char = this._decimalChar;
code = char.charCodeAt(0);
}
const { value, selectionStart, selectionEnd } = this.input().nativeElement;
const newValue = this.parseValue(value + char);
const newValueStr = newValue != null ? newValue.toString() : '';
const selectedValue = value.substring(selectionStart, selectionEnd);
const selectedValueParsed = this.parseValue(selectedValue);
const selectedValueStr = selectedValueParsed != null ? selectedValueParsed.toString() : '';
if (selectionStart !== selectionEnd && selectedValueStr.length > 0) {
this.insert(event, char, { isDecimalSign, isMinusSign });
return;
}
const max = this.maxlength();
if (max && newValueStr.length > max) {
return;
}
if ((48 <= code && code <= 57) || isMinusSign || isDecimalSign) {
this.insert(event, char, { isDecimalSign, isMinusSign });
}
}
onPaste(event) {
if (!this.$disabled() && !this.readonly()) {
event.preventDefault();
let data = (event.clipboardData || this.document.defaultView['clipboardData']).getData('Text');
if (this.inputId() === 'integeronly' && /[^\d-]/.test(data)) {
return;
}
if (data) {
if (this.maxlength()) {
data = data.toString().substring(0, this.maxlength());
}
let filteredData = this.parseValue(data);
if (filteredData != null) {
this.insert(event, filteredData.toString());
}
}
}
}
allowMinusSign() {
const min = this.min();
return min == null || min < 0;
}
isMinusSign(char) {
if (this._minusSign.test(char) || char === '-') {
this._minusSign.lastIndex = 0;
return true;
}
return false;
}
isDecimalSign(char) {
if (this._decimal.test(char)) {
this._decimal.lastIndex = 0;
return true;
}
return false;
}
isDecimalMode() {
return this.mode() === 'decimal';
}
getDecimalCharIndexes(val) {
let decimalCharIndex = val.search(this._decimal);
this._decimal.lastIndex = 0;
const filteredVal = val
.replace(this._prefix, '')
.trim()
.replace(/\s/g, '')
.replace(this._currency, '');
const decimalCharIndexWithoutPrefix = filteredVal.search(this._decimal);
this._decimal.lastIndex = 0;
return { decimalCharIndex, decimalCharIndexWithoutPrefix };
}
getCharIndexes(val) {
const decimalCharIndex = val.search(this._decimal);
this._decimal.lastIndex = 0;
const minusCharIndex = val.search(this._minusSign);
this._minusSign.lastIndex = 0;
const suffixCharIndex = val.search(this._suffix);
this._suffix.lastIndex = 0;
const currencyCharIndex = val.search(this._currency);
this._currency.lastIndex = 0;
return { decimalCharIndex, minusCharIndex, suffixCharIndex, currencyCharIndex };
}
insert(event, text, sign = { isDecimalSign: false, isMinusSign: false }) {
const minusCharIndexOnText = text.search(this._minusSign);
this._minusSign.lastIndex = 0;
if (!this.allowMinusSign() && minusCharIndexOnText !== -1) {
return;
}
let selectionStart = this.input()?.nativeElement.selectionStart ?? 0;
let selectionEnd = this.input()?.nativeElement.selectionEnd ?? 0;
let inputValue = this.input()?.nativeElement.value.trim();
const { decimalCharIndex, minusCharIndex, suffixCharIndex, currencyCharIndex } = this.getCharIndexes(inputValue);
let newValueStr;
if (sign.isMinusSign) {
if (selectionStart === 0) {
newValueStr = inputValue;
if (minusCharIndex === -1 || selectionEnd !== 0) {
newValueStr = this.insertText(inputValue, text, 0, selectionEnd);
}
this.updateValue(event, newValueStr, text, 'insert');
}
}
else if (sign.isDecimalSign) {
if (decimalCharIndex > 0 && selectionStart === decimalCharIndex) {
this.updateValue(event, inputValue, text, 'insert');
}
else if (decimalCharIndex > selectionStart && decimalCharIndex < selectionEnd) {
newValueStr = this.insertText(inputValue, text, selectionStart, selectionEnd);
this.updateValue(event, newValueStr, text, 'insert');
}
else if (decimalCharIndex === -1 && this.maxFractionDigits()) {
newValueStr = this.insertText(inputValue, text, selectionStart, selectionEnd);
this.updateValue(event, newValueStr, text, 'insert');
}
}
else {
const maxFractionDigits = this.numberFormat?.resolvedOptions().maximumFractionDigits ?? 0;
const operation = selectionStart !== selectionEnd ? 'range-insert' : 'insert';
if (decimalCharIndex > 0 && selectionStart > decimalCharIndex) {
if (selectionStart + text.length - (decimalCharIndex + 1) <= maxFractionDigits) {
const charIndex = currencyCharIndex >= selectionStart ? currencyCharIndex - 1 : suffixCharIndex >= selectionStart ? suffixCharIndex : inputValue.length;
newValueStr = inputValue.slice(0, selectionStart) + text + inputValue.slice(selectionStart + text.length, charIndex) + inputValue.slice(charIndex);
this.updateValue(event, newValueStr, text, operation);
}
}
else {
newValueStr = this.insertText(inputValue, text, selectionStart, selectionEnd);
this.updateValue(event, newValueStr, text, operation);
}
}
}
insertText(value, text, start, end) {
let textSplit = text === '.' ? text : text.split('.');
if (textSplit.length === 2) {
const decimalCharIndex = value.slice(start, end).search(this._decimal);
this._decimal.lastIndex = 0;
return decimalCharIndex > 0 ? value.slice(0, start) + this.formatValue(text) + value.slice(end) : value || this.formatValue(text);
}
else if (end - start === value.length) {
return this.formatValue(text);
}
else if (start === 0) {
return text + value.slice(end);
}
else if (end === value.length) {
return value.slice(0, start) + text;
}
else {
return value.slice(0, start) + text + value.slice(end);
}
}
deleteRange(value, start, end) {
let newValueStr;
if (end - start === value.length)
newValueStr = '';
else if (start === 0)
newValueStr = value.slice(end);
else if (end === value.length)
newValueStr = value.slice(0, start);
else
newValueStr = value.slice(0, start) + value.slice(end);
return newValueStr;
}
initCursor() {
let selectionStart = this.input()?.nativeElement.selectionStart ?? 0;
let selectionEnd = this.input()?.nativeElement.selectionEnd ?? 0;
let inputValue = this.input()?.nativeElement.value;
let valueLength = inputValue.length;
let index = null;
// remove prefix
let prefixLength = (this.prefixChar || '').length;
inputValue = inputValue.replace(this._prefix, '');
// Will allow selecting whole prefix. But not a part of it.
// Negative values will trigger clauses after this to fix the cursor position.
if (selectionStart === selectionEnd || selectionStart !== 0 || selectionEnd < prefixLength) {
selectionStart -= prefixLength;
}
let char = inputValue.charAt(selectionStart);
if (this.isNumeralChar(char)) {
return selectionStart + prefixLength;
}
//left
let i = selectionStart - 1;
while (i >= 0) {
char = inputValue.charAt(i);
if (this.isNumeralChar(char)) {
index = i + prefixLength;
break;
}
else {
i--;
}
}
if (index !== null) {
this.input()?.nativeElement.setSelectionRange(index + 1, index + 1);
}
else {
i = selectionStart;
while (i < valueLength) {
char = inputValue.charAt(i);
if (this.isNumeralChar(char)) {
index = i + prefixLength;
break;
}
else {
i++;
}
}
if (index !== null) {
this.input()?.nativeElement.setSelectionRange(index, index);
}
}
return index || 0;
}
onInputClick() {
const currentValue = this.input()?.nativeElement.value;
if (!this.readonly() && currentValue !== getSelection()) {
this.initCursor();
}
}
isNumeralChar(char) {
if (char.length === 1 && (this._numeral.test(char) || this._decimal.test(char) || this._group.test(char) || this._minusSign.test(char))) {
this.resetRegex();
return true;
}
return false;
}
resetRegex() {
this._numeral.lastIndex = 0;
this._decimal.lastIndex = 0;
this._group.lastIndex = 0;
this._minusSign.lastIndex = 0;
}
updateValue(event, valueStr, insertedValueStr, operation) {
let currentValue = this.input()?.nativeElement.value;
let newValue = null;
if (valueStr != null) {
newValue = this.parseValue(valueStr);
newValue = !newValue && !this.allowEmpty() ? 0 : newValue;
this.updateInput(newValue, insertedValueStr, operation, valueStr);
this.handleOnInput(event, currentValue, newValue);
}
}
handleOnInput(event, currentValue, newValue) {
if (this.isValueChanged(currentValue, newValue)) {
this.input().nativeElement.value = this.formatValue(newValue);
this.input()?.nativeElement.setAttribute('aria-valuenow', newValue);
this.updateModel(event, newValue);
this.onInput.emit({ originalEvent: event, value: newValue, formattedValue: currentValue });
}
}
isValueChanged(currentValue, newValue) {
if (newValue === null && currentValue !== null) {
return true;
}
if (newValue != null) {
let parsedCurrentValue = typeof currentValue === 'string' ? this.parseValue(currentValue) : currentValue;
return newValue !== parsedCurrentValue;
}
return false;
}
validateValue(value) {
if (value === '-' || value == null) {
return null;
}
const min = this.min();
const max = this.max();
if (min != null && value < min) {
return this.min();
}
if (max != null && value > max) {
return max;
}
return value;
}
updateInput(value, insertedValueStr, operation, valueStr) {
insertedValueStr = insertedValueStr || '';
let inputValue = this.input()?.nativeElement.value;
let newValue = this.formatValue(value);
let currentLength = inputValue.length;
if (newValue !== valueStr) {
newValue = this.concatValues(newValue, valueStr);
}
if (currentLength === 0) {
this.input().nativeElement.value = newValue;
this.input().nativeElement.setSelectionRange(0, 0);
const index = this.initCursor();
const selectionEnd = index + insertedValueStr.length;
this.input().nativeElement.setSelectionRange(selectionEnd, selectionEnd);
}
else {
let selectionStart = this.input().nativeElement.selectionStart ?? 0;
let selectionEnd = this.input().nativeElement.selectionEnd ?? 0;
const maxlength = this.maxlength();
if (maxlength && newValue.length > maxlength) {
newValue = newValue.slice(0, maxlength);
selectionStart = Math.min(selectionStart, maxlength);
selectionEnd = Math.min(selectionEnd, maxlength);
}
if (maxlength && maxlength < newValue.length) {
return;
}
this.input().nativeElement.value = newValue;
let newLength = newValue.length;
if (operation === 'range-insert') {
const startValue = this.parseValue((inputValue || '').slice(0, selectionStart));
const startValueStr = startValue !== null ? startValue.toString() : '';
const startExpr = startValueStr.split('').join(`(${this.groupChar})?`);
const sRegex = new RegExp(startExpr, 'g');
sRegex.test(newValue);
const tExpr = insertedValueStr.split('').join(`(${this.groupChar})?`);
const tRegex = new RegExp(tExpr, 'g');
tRegex.test(newValue.slice(sRegex.lastIndex));
selectionEnd = sRegex.lastIndex + tRegex.lastIndex;
this.input().nativeElement.setSelectionRange(selectionEnd, selectionEnd);
}
else if (newLength === currentLength) {
if (operation === 'insert' || operation === 'delete-back-single')
this.input().nativeElement.setSelectionRange(selectionEnd + 1, selectionEnd + 1);
else if (operation === 'delete-single')
this.input().nativeElement