ng-number-input
Version:
Angualr form input that adds group and decimal separators to numbers as you type.
554 lines (547 loc) • 26.5 kB
JavaScript
import * as i0 from '@angular/core';
import { forwardRef, Component, ViewChildren, Input, Pipe, NgModule } from '@angular/core';
import * as i2 from '@angular/forms';
import { NG_VALUE_ACCESSOR, FormsModule } from '@angular/forms';
import { Subscription } from 'rxjs';
import * as i1 from '@angular/common';
import { CommonModule } from '@angular/common';
/* eslint-disable eqeqeq */
class NgNumberInputComponent {
constructor(elementRef) {
this.elementRef = elementRef;
this.mode = 'default'; //['default', 'string', 'lazy']
this.max = Number.MAX_SAFE_INTEGER;
this.min = -Number.MAX_SAFE_INTEGER;
this.locale = ['en-US'];
this.placeholder = '';
this.parseInt = false;
this.useString = false;
this.live = true;
this.fractionSeperator = '.';
this.thousandsSeperator = ',';
this.regex = /[^\d.\-]/g;
this.blur = true;
this.subscription = new Subscription();
this.groupsDelta = 0;
this.hideCaret = false;
}
get value() {
if (this.useString) {
return this.innerValue;
}
return isNaN(this.innerValue) ? null : this.innerValue;
}
set value(v) {
if (v !== this.innerValue) {
const text = v === null || v === void 0 ? void 0 : v.toString();
if (this.useString) {
v = text === null || text === void 0 ? void 0 : text.split(',').join('');
}
else if (v) {
v = this.limiter(text);
}
this.innerValue = v;
}
}
get text() {
return this.text_;
}
set text(v) {
if (v != this.text_) {
this.processInput(v);
}
}
onChange(event) { }
onTouch(event) { }
writeValue(value) {
if (value !== this.innerValue) {
if (this.useString) {
this.processInput(value);
return;
}
this.processInput(value === null || value === void 0 ? void 0 : value.toLocaleString(this.locale[0], this.locale[1]));
}
}
registerOnChange(fn) {
this.onChange = fn;
}
registerOnTouched(fn) {
this.onTouch = fn;
}
setDisabledState(isDisabled) {
this.disabled = isDisabled;
}
ngOnDestroy() {
{
this.subscription.unsubscribe();
}
}
ngAfterViewInit() {
var _a, _b;
this.subscription.add((_b = (_a = this.numberInput) === null || _a === void 0 ? void 0 : _a.changes) === null || _b === void 0 ? void 0 : _b.subscribe(c => {
var _a, _b, _c;
(_c = (_b = (_a = this.numberInput) === null || _a === void 0 ? void 0 : _a.first) === null || _b === void 0 ? void 0 : _b.nativeElement) === null || _c === void 0 ? void 0 : _c.focus();
}));
}
setMode() {
if (!this.mode || typeof (this.mode) != 'string') {
this.mode = 'default';
}
if (this.mode == 'string') {
this.live = true;
this.useString = true;
}
if (this.mode == 'lazy') {
this.live = false;
this.useString = false;
}
if (this.mode == 'default') {
this.live = true;
}
}
seLocaleOptions() {
}
ngOnInit() {
this.setMode();
this.setCustomStyle();
if (!this.live) {
this.useString = false;
}
this.initLocaleAndLimit();
let seperators = Object.assign({}, this.getLocaleSeperators(this.live == false ? 'en-US' : this.locale[0], this.live, this.locale));
this.thousandsSeperator = seperators.group;
this.fractionSeperator = seperators.decimal;
this.minIntegers = seperators.minIntegers;
this.minfractions = seperators.minfractions;
this.regex = seperators.regex;
this.test = this.value;
}
setCustomStyle() {
if (this.customStyle) {
this.customStyle = Object.assign({ height: '100%', width: '100%', border: 'none', outline: 'none', padding: '0px' }, this.customStyle);
}
}
setLocaleOptions(option, value) {
if (!this.locale[1]) {
this.locale[1] = {};
}
this.locale[1][option] = value;
}
initLocaleAndLimit() {
var _a;
if (this.useString || !((_a = this.locale) === null || _a === void 0 ? void 0 : _a.length)) {
this.locale = ['en-US'];
}
if (!this.useString) {
if (!this.locale[1]) {
this.locale[1] = {};
}
if (this.locale && this.locale[1] && this.locale[1].maximumFractionDigits && !this.limitTo) {
this.limitTo = this.locale[1].maximumFractionDigits;
}
if (!this.limitTo || this.limitTo > 3 || this.limitTo < 0) {
this.limitTo = 3;
}
if (this.limitTo && this.locale[1].maximumFractionDigits) {
this.setLocaleOptions('maximumFractionDigits', this.limitTo);
}
}
else {
if (!this.limitTo && this.limitTo !== 0) {
this.limitTo = Number.POSITIVE_INFINITY;
}
}
}
processInput(stringToProcess) {
var _a, _b, _c, _d;
if (!stringToProcess) {
this.setText(this.format ? this.format('') : '');
if (!this.live) {
this.test = new String('');
}
this.value = null;
this.onChange(null);
return;
}
// start processing string
let sanitizedString = this.sanitize(stringToProcess);
this.groupsDelta = (_a = this.trackGroups(stringToProcess, this.text)) === null || _a === void 0 ? void 0 : _a.delta;
if (this.useString) {
return this.processStringInput(sanitizedString);
}
//end processing string
let number = this.limiter(sanitizedString);
if (!sanitizedString || (!number && number !== 0)) {
if (this.format) {
sanitizedString = this.format(sanitizedString);
}
this.setText(sanitizedString);
if (!this.live) {
this.test = new String(sanitizedString);
}
this.value = null;
this.onChange(this.value);
return;
}
if (this.parseInt) {
number = parseInt(sanitizedString);
}
number = this.checkBoundaries(number);
let localeOptions = this.locale[1];
//preserve fraction seperator while user is typing. for '2.' the decimal is stripped during type conversion
if (sanitizedString.includes(this.fractionSeperator) && !this.minfractions && !this.parseInt) {
localeOptions = localeOptions ? Object.assign(Object.assign({}, localeOptions), { minimumFractionDigits: 1 }) : { minimumFractionDigits: 1 };
}
let processedString = Intl.NumberFormat(this.locale[0], localeOptions).format(number);
if (this.format) {
processedString = this.format(processedString);
}
this.setText(processedString);
this.value = number;
this.onChange(this.value);
//need to limit the decimals of unformatted number manullay in !live mode.
if (!this.live) {
let right = sanitizedString.split(this.fractionSeperator)[1];
if ((right === null || right === void 0 ? void 0 : right.length) > this.limitTo) {
right = right === null || right === void 0 ? void 0 : right.substring(0, this.limitTo);
}
let temp = (_b = this.value) === null || _b === void 0 ? void 0 : _b.toString();
if (sanitizedString.includes(this.fractionSeperator)) {
temp = [(_d = (_c = this.value) === null || _c === void 0 ? void 0 : _c.toString()) === null || _d === void 0 ? void 0 : _d.split(this.fractionSeperator)[0], right].join(this.fractionSeperator);
}
this.test = new String(temp);
}
return;
}
trackGroups(currentText, previousText) {
var _a, _b, _c, _d;
const regex = new RegExp(`[^\\${this.thousandsSeperator}]`, 'g');
const countIncurrentText = (_b = (_a = currentText === null || currentText === void 0 ? void 0 : currentText.replace(regex, '')) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0;
const countInpreviousText = (_d = (_c = previousText === null || previousText === void 0 ? void 0 : previousText.replace(regex, '')) === null || _c === void 0 ? void 0 : _c.length) !== null && _d !== void 0 ? _d : 0;
return { delta: countIncurrentText - countInpreviousText };
}
correctCaretForFormat(correctedPostion, minIntegerCorrection) {
var _a;
if (!this.format) {
return correctedPostion;
}
if (!this.value || !this.previousText || this.previousCursorPosition <= ((_a = this.previousText) === null || _a === void 0 ? void 0 : _a.search(/\d/))) {
if (!this.value && this.minIntegers && this.text.includes(this.fractionSeperator)) {
return this.text.indexOf(this.fractionSeperator);
}
return this.text.search(/\d/) + 1;
}
return correctedPostion;
}
correctCaretForMinIntegers(correctedPostion) {
var _a, _b, _c, _d;
let minIntegerCorrection = false;
if (!this.minIntegers || this.minIntegers < 2 || !this.live || this.useString) {
return { minIntegerCorrection, pos: correctedPostion };
}
const fractionSeperator = this.text.indexOf(this.fractionSeperator);
if (this.correction) {
if (fractionSeperator >= 0) {
correctedPostion = this.text.indexOf(this.fractionSeperator);
}
else {
correctedPostion = this.text.length;
}
return { minIntegerCorrection, pos: correctedPostion };
}
if (this.previousCursorPosition == 0) {
if (fractionSeperator && !this.value) {
correctedPostion = fractionSeperator;
return { minIntegerCorrection, pos: correctedPostion };
}
}
let previousFractionSeperator = (_a = this.previousText) === null || _a === void 0 ? void 0 : _a.indexOf(this.fractionSeperator);
if (previousFractionSeperator >= 0 && this.previousCursorPosition <= previousFractionSeperator) {
const valueLeft = (_c = (_b = this.value) === null || _b === void 0 ? void 0 : _b.toString()) === null || _c === void 0 ? void 0 : _c.split(this.fractionSeperator)[0];
const textLeft = (_d = this.previousText.split(this.fractionSeperator)[0]) === null || _d === void 0 ? void 0 : _d.replace(/[^\d]/g, '');
if ((valueLeft === null || valueLeft === void 0 ? void 0 : valueLeft.length) < (textLeft === null || textLeft === void 0 ? void 0 : textLeft.length)) {
minIntegerCorrection = true;
}
if ((valueLeft === null || valueLeft === void 0 ? void 0 : valueLeft.length) < (textLeft === null || textLeft === void 0 ? void 0 : textLeft.length) && this.currentKey !== this.fractionSeperator) {
correctedPostion = this.previousCursorPosition;
}
if (this.currentKey == this.fractionSeperator) {
return { minIntegerCorrection, pos: fractionSeperator + 1 };
}
}
return { minIntegerCorrection, pos: correctedPostion };
}
setText(textToRender) {
var _a, _b, _c;
let initPos;
let pos;
let diff = 0;
pos = (_b = (_a = this.target) === null || _a === void 0 ? void 0 : _a.selectionStart) !== null && _b !== void 0 ? _b : 0;
initPos = pos;
this.hideCaret = true;
if (textToRender !== this.text) {
diff = (textToRender === null || textToRender === void 0 ? void 0 : textToRender.split(this.regex).length) - ((_c = this.text) === null || _c === void 0 ? void 0 : _c.split(this.regex).length);
this.previousText = this.text_;
this.text_ = new String(textToRender);
if (diff) {
pos += diff;
}
else if (this.groupsDelta > 0) {
//user did not erase a group seperator so allow the natrual flow of caret
pos = this.previousCursorPosition;
}
if (this.correction) {
pos = this.text.length;
}
let minIntResponse = this.correctCaretForMinIntegers(pos);
let minIntegerCorrection = (minIntResponse === null || minIntResponse === void 0 ? void 0 : minIntResponse.minIntegerCorrection) ? true : false;
pos = minIntResponse.pos;
pos = this.correctCaretForFormat(pos, minIntegerCorrection);
}
if (pos || pos == 0) {
setTimeout(() => {
var _a;
if (this.correction) {
this.correction = false;
}
this.currentCursorPosition = pos;
if (pos < 0) {
pos = 0;
}
(_a = this.target) === null || _a === void 0 ? void 0 : _a.setSelectionRange(pos, pos, 'none');
this.hideCaret = false;
});
}
else {
this.hideCaret = false;
}
}
limiter(sanitizedText) {
var _a;
sanitizedText = sanitizedText === null || sanitizedText === void 0 ? void 0 : sanitizedText.replace(this.fractionSeperator, '.');
if (sanitizedText.includes('.')) {
sanitizedText = [sanitizedText.split('.')[0], sanitizedText.split('.')[1].substring(0, this.limitTo)].join('.');
}
let m = Number(sanitizedText);
if (m > Number.MAX_SAFE_INTEGER || m < -Number.MAX_SAFE_INTEGER) {
if (this.live) {
return Number(this.sanitize(this.text_).replace(this.fractionSeperator, '.'));
}
else {
let seperators = this.getLocaleSeperators(this.locale[0], this.live, this.locale);
let temp = (_a = this.text_) === null || _a === void 0 ? void 0 : _a.replace(new RegExp(`[^\\d${seperators.group}\\-]`, 'g'), '');
temp = temp.replace(seperators.decimal, '.');
return Number(temp);
}
}
return m;
}
checkForSelection($event) {
var _a, _b, _c;
// mark for correction if text is selected
if (((_a = this.target) === null || _a === void 0 ? void 0 : _a.selectionStart) !== ((_b = this.target) === null || _b === void 0 ? void 0 : _b.selectionEnd)) {
this.correction = true;
}
else {
this.correction = false;
this.previousCursorPosition = (_c = this.target) === null || _c === void 0 ? void 0 : _c.selectionStart;
}
}
onKeyDown(event) {
this.hideCaret = false;
if (event.key) {
this.currentKey = event.key;
}
this.target = event.target;
if (this.target) {
this.checkForSelection(event);
}
}
processDecimals(text) {
const hasPeriod = text.includes(this.fractionSeperator);
const arr = text.split(this.fractionSeperator);
let value = '';
arr.map((m, i) => {
value += m;
if (i === 0 && hasPeriod) {
value += this.fractionSeperator;
}
});
return value;
}
processNegatives(text) {
const isNegative = text.startsWith('-');
const arr = text === null || text === void 0 ? void 0 : text.split('-');
text = arr.join('');
if (isNegative) {
text = `-${text}`;
}
return text;
}
sanitize(value) {
let text = `${value}`.replace(this.regex, '');
text = this.processDecimals(text);
return this.processNegatives(text);
}
checkBoundaries(number) {
var _a, _b, _c, _d, _e, _f;
let useStringText;
let value = this.value;
if (this.useString) {
useStringText = number;
value = Number(value);
number = Number(useStringText);
}
if ((this.max || this.max === 0) && number > this.max) {
number = this.max;
useStringText = this.max.toString();
if (value <= this.max) {
number = value;
useStringText = (_c = (_b = (_a = this.text_) === null || _a === void 0 ? void 0 : _a.split(',')) === null || _b === void 0 ? void 0 : _b.join('')) === null || _c === void 0 ? void 0 : _c.replace(/[^\d.\-]/g, '');
}
}
if ((this.min || this.min === 0) && number < this.min) {
number = this.min;
useStringText = this.min.toString();
if ((value || value == 0) && value >= this.min) {
number = value;
useStringText = (_f = (_e = (_d = this.text_) === null || _d === void 0 ? void 0 : _d.split(',')) === null || _e === void 0 ? void 0 : _e.join('')) === null || _f === void 0 ? void 0 : _f.replace(/[^\d.\-]/g, '');
}
}
return this.useString ? useStringText : number;
}
processStringInput(text) {
if (this.parseInt) {
text = text === null || text === void 0 ? void 0 : text.split(this.fractionSeperator)[0];
}
text = this.checkBoundaries(text);
text = text.replace(/\B(?<!\.\d*)(?=(\d{3})+(?!\d))/g, ',');
let fraction = text.split(this.fractionSeperator)[1];
if (fraction === null || fraction === void 0 ? void 0 : fraction.length) {
let limit = this.limitTo;
text = [text === null || text === void 0 ? void 0 : text.split(this.fractionSeperator)[0], text === null || text === void 0 ? void 0 : text.split(this.fractionSeperator)[1].substring(0, limit)].join(this.fractionSeperator);
}
let stringValue = text.replace(/\B(?<!\.\d*)(?=(\d{3})+(?!\d))/g, ',');
if (this.format) {
text = this.format(text);
}
this.setText(text);
this.value = stringValue;
this.onChange(this.value);
return;
}
getLocaleSeperators(locale, live, localeFromInput) {
const { group, decimal } = new Intl.NumberFormat(locale).formatToParts(1234567.89).reduce((acc, r) => {
if (r.type == 'group' || r.type == 'decimal') {
acc[r.type] = r.value;
}
return acc;
}, {});
const regex = new RegExp(`[^\\d${decimal}\\-]`, 'g');
if (live) {
const { integer, fraction } = new Intl.NumberFormat(...localeFromInput).formatToParts(0).reduce((acc, r) => {
if (r.type == 'integer' || r.type == 'fraction') {
acc[r.type] = r.value;
}
return acc;
}, {});
const minIntegers = integer === null || integer === void 0 ? void 0 : integer.length;
const minfractions = fraction === null || fraction === void 0 ? void 0 : fraction.length;
return { group, decimal, minIntegers, minfractions, regex };
}
return { group, decimal, regex };
}
}
NgNumberInputComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.1.5", ngImport: i0, type: NgNumberInputComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
NgNumberInputComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "12.1.5", type: NgNumberInputComponent, selector: "ng-number-input", inputs: { mode: "mode", max: "max", min: "min", locale: "locale", name: "name", placeholder: "placeholder", customStyle: "customStyle", parseInt: "parseInt", limitTo: "limitTo", format: "format" }, providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => NgNumberInputComponent),
multi: true
}
], viewQueries: [{ propertyName: "numberInput", predicate: ["numberInput"], descendants: true }], ngImport: i0, template: "\r\n<input (keyup)=\"checkForSelection($event)\" (mouseup)=\"checkForSelection($event)\" (focusin)=\"hideCaret=false\" (click)=\"hideCaret=false\" [ngClass]=\"{'hide-caret':hideCaret}\" *ngIf=\"live\" (paste)=\"onKeyDown($event)\" (keydown)=\"onKeyDown($event)\" [disabled]=\"disabled\" class=\"number-input-element\" [style]=\"customStyle\" [placeholder]=\"placeholder\" [name]=\"name\" autocomplete=\"off\" type=\"text\" [(ngModel)]=\"text\" (blur)=\"onTouch($event)\">\r\n\r\n<ng-container *ngIf=\"!live\">\r\n <input #numberInput *ngIf=\"!blur\" (focus)=\"blur=false\" (focusout)=\"blur=true\" [disabled]=\"disabled\" class=\"number-input-element\" [style]=\"customStyle\" [placeholder]=\"placeholder\" [name]=\"name\" autocomplete=\"off\" type=\"text\" [(ngModel)]=\"test\" (ngModelChange)=\"processInput(test)\" (blur)=\"onTouch($event)\">\r\n <input #stringInput *ngIf=\"blur\" (click)=\"$event.preventDefault;blur = false\" (focus)=\"blur=false\" (focusout)=\"blur=false\" [disabled]=\"disabled\" class=\"number-input-element\" [style]=\"customStyle\" [placeholder]=\"placeholder\" [name]=\"name\" autocomplete=\"off\" type=\"text\" [value]=\"text\" (blur)=\"onTouch($event)\">\r\n</ng-container>\r\n\r\n", styles: [".hide-caret {caret-color: transparent !important}"], directives: [{ type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.1.5", ngImport: i0, type: NgNumberInputComponent, decorators: [{
type: Component,
args: [{
selector: 'ng-number-input',
templateUrl: './ng-number-input.component.html',
styles: [`.hide-caret {caret-color: transparent !important}`],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => NgNumberInputComponent),
multi: true
}
]
}]
}], ctorParameters: function () { return [{ type: i0.ElementRef }]; }, propDecorators: { numberInput: [{
type: ViewChildren,
args: ['numberInput']
}], mode: [{
type: Input
}], max: [{
type: Input
}], min: [{
type: Input
}], locale: [{
type: Input
}], name: [{
type: Input
}], placeholder: [{
type: Input
}], customStyle: [{
type: Input
}], parseInt: [{
type: Input
}], limitTo: [{
type: Input
}], format: [{
type: Input
}] } });
class NgNumberInputPipe {
transform(value) {
return value + "RUssia";
}
}
NgNumberInputPipe.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.1.5", ngImport: i0, type: NgNumberInputPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
NgNumberInputPipe.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "12.0.0", version: "12.1.5", ngImport: i0, type: NgNumberInputPipe, name: "numberInput" });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.1.5", ngImport: i0, type: NgNumberInputPipe, decorators: [{
type: Pipe,
args: [{
name: 'numberInput'
}]
}] });
class NgNumberInputModule {
}
NgNumberInputModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "12.1.5", ngImport: i0, type: NgNumberInputModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
NgNumberInputModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "12.1.5", ngImport: i0, type: NgNumberInputModule, declarations: [NgNumberInputComponent,
NgNumberInputPipe], imports: [CommonModule,
FormsModule], exports: [NgNumberInputComponent] });
NgNumberInputModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "12.1.5", ngImport: i0, type: NgNumberInputModule, imports: [[
CommonModule,
FormsModule
]] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "12.1.5", ngImport: i0, type: NgNumberInputModule, decorators: [{
type: NgModule,
args: [{
declarations: [
NgNumberInputComponent,
NgNumberInputPipe
],
imports: [
CommonModule,
FormsModule
],
exports: [
NgNumberInputComponent
]
}]
}] });
/*
* Public API Surface of ng-number-input
*/
/**
* Generated bundle index. Do not edit.
*/
export { NgNumberInputComponent, NgNumberInputModule };
//# sourceMappingURL=ng-number-input.js.map