UNPKG

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
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