UNPKG

ng-units

Version:

Angular component library for units of measurement.

554 lines (533 loc) 18.8 kB
import * as i0 from '@angular/core'; import { Injectable, Pipe, forwardRef, Directive, Input, HostListener, EventEmitter, Component, Output, InjectionToken, NgModule } from '@angular/core'; import * as i2 from '@angular/common'; import { CommonModule } from '@angular/common'; import { Subject } from 'rxjs'; import { filter } from 'rxjs/operators'; import { NG_VALUE_ACCESSOR } from '@angular/forms'; class SimpleUnit { symbol; factor; offset; constructor(symbol, factor, offset = 0) { this.symbol = symbol; this.factor = factor; this.offset = offset; } fromBase(value) { return value * this.factor - this.offset; } toBase(value) { return (value + this.offset) / this.factor; } } const QuantityFormatters = { 'default': function (value) { const abs = Math.abs(value); let text; if (abs && (abs >= 1e5 || abs <= 1e-2)) { text = removeZeroDigits(value.toExponential(3)); } else { if (abs > 1) { text = value.toFixed(2); } else { text = value.toPrecision(3); } text = Number(text).toString(); } return text; }, 'currency': function (value) { return value.toFixed(2); } }; function removeZeroDigits(text) { return text.replace(/\.?0+e/, 'e'); } const QuantityParsers = { 'default': function (value) { if (typeof value === 'string') { const str = value.replace(',', '.'); return str === '' ? null : Number(str); } return value; } }; class Quantity { name; unit; units = []; formatter; parser; constructor(definition) { if (definition) { this.name = definition.name; for (const symbol of Object.keys(definition.units)) { const def = definition.units[symbol]; this.units.push(new SimpleUnit(symbol, def[0], def[1])); } this.unit = this.units[0]; const formatterName = definition.formatter || 'default'; const parserName = definition.parser || 'default'; this.formatter = QuantityFormatters[formatterName]; this.parser = QuantityParsers[parserName]; } } fromBase(value, unit) { const n = this.parser(value); return isNumeric(n) ? this.getUnit(unit).fromBase(n) : null; } toBase(value, unit) { const n = this.parser(value); return isNumeric(n) ? this.getUnit(unit).toBase(n) : null; } getUnit(unit) { return this.findUnit(unit) || this.unit; } selectUnit(unit) { const u = this.findUnit(unit); if (u) { this.unit = u; } } findUnit(unit) { if (!unit) { return; } const id = typeof unit === 'string' ? unit : unit.symbol; const units = this.units; for (let i = 0, e = units.length; i !== e; i++) { if (units[i].symbol === id) { return units[i]; } } } /** * Returns a string containing formatted number and unit symbol (optional). * @param value string or number */ print(value, addUnitSymbol, unit) { const number = this.parser(value); if (typeof number !== 'number') { return ''; } const result = this.formatter(number); return addUnitSymbol ? (result + ' ' + this.getUnit(unit).symbol) : result; } } function isNumeric(value) { return typeof value === 'number' && !isNaN(value); } function defaultPrint(value) { const num = QuantityParsers['default'](value); return typeof num === 'number' ? QuantityFormatters['default'](num) : ''; } class QuantityMessage { quantity; } class SystemOfUnits { quantities = []; quantityChange = new Subject(); changes$ = this.quantityChange.asObservable(); add(...quantities) { this.quantities.push(...quantities); } get(quantityName) { const quantity = this.quantities.find(q => q.name === quantityName); return quantity; } selectUnit(quantity, unit) { quantity.selectUnit(unit); this.broadcast(quantity); } /** * @depracted since 11.0.0. Use changes$ instead. */ changes() { return this.changes$; } /** * @depracted since 11.0.0. Use changes$ instead. */ subscribe(quantity, callback) { return this.quantityChange.pipe(filter(m => m.quantity === quantity)) .subscribe(callback); } broadcast(quantity) { this.quantityChange.next({ quantity }); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.2", ngImport: i0, type: SystemOfUnits, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.1.2", ngImport: i0, type: SystemOfUnits }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.2", ngImport: i0, type: SystemOfUnits, decorators: [{ type: Injectable }] }); class QuantityPipe { system; constructor(system) { this.system = system; } transform(value, quantity, addSymbolOrUnit, addSymbol) { if (typeof quantity === 'string') { quantity = this.system.get(quantity); } let unit; if (typeof addSymbolOrUnit === 'boolean') { addSymbol = addSymbolOrUnit; } else { unit = addSymbolOrUnit; } if (value === null || value === undefined) { if (!quantity || !addSymbol) { return ''; } if (unit) { return quantity.findUnit(unit)?.symbol; } return quantity.unit.symbol; } return quantity ? quantity.print(quantity.fromBase(value, unit), addSymbol, unit) : defaultPrint(value); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.2", ngImport: i0, type: QuantityPipe, deps: [{ token: SystemOfUnits }], target: i0.ɵɵFactoryTarget.Pipe }); static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "16.1.2", ngImport: i0, type: QuantityPipe, name: "ngQuantity", pure: false }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.2", ngImport: i0, type: QuantityPipe, decorators: [{ type: Pipe, args: [{ name: 'ngQuantity', pure: false }] }], ctorParameters: function () { return [{ type: SystemOfUnits }]; } }); const CONTROL_VALUE_ACCESSOR = { name: 'ngQuantityValueAccessor', provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => QuantityDirective), multi: true }; class QuantityDirective { elementRef; system; quantityAttr; ngUnit; quantity; inputElement; onTouch; onModelChange; currentModelValue; subscription; constructor(elementRef, system) { this.elementRef = elementRef; this.system = system; } ngOnInit() { this.inputElement = this.getInputElement(); this.initQuantity(); } initQuantity() { this.unsubscribe(); this.quantity = typeof this.quantityAttr === 'string' ? this.system.get(this.quantityAttr) : this.quantityAttr; this.subscribe(); this.updateUnit(); } subscribe() { if (this.quantity) { this.subscription = this.system.changes$.subscribe(m => { if (m.quantity === this.quantity) { this.updateUnit(); } }); } } updateUnit() { this.updateView(this.currentModelValue); } unsubscribe() { if (this.subscription) { this.subscription.unsubscribe(); } } ngOnDestroy() { this.unsubscribe(); } ngOnChanges(changes) { const change = changes['quantityAttr']; if (change && !change.isFirstChange()) { this.initQuantity(); } } registerOnTouched(fn) { this.onTouch = fn; } registerOnChange(fn) { this.onModelChange = fn; } // Parser: View to Model onControlInput() { const rawValue = this.inputElement.value; const modelValue = this.quantity ? this.quantity.toBase(rawValue, this.ngUnit) : rawValue; this.currentModelValue = modelValue; if (this.onTouch) { this.onTouch(); } if (this.onModelChange) { this.onModelChange(modelValue); } } // Formatter: Model to View writeValue(rawValue) { this.currentModelValue = rawValue; this.updateView(rawValue); } updateView(modelValue) { if (!this.quantity) { this.inputElement.value = defaultPrint(modelValue); return; } const converted = this.quantity.fromBase(modelValue, this.ngUnit); if (converted !== null && converted !== undefined) { this.inputElement.value = this.quantity.print(converted, false); } else { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion this.inputElement.value = null; } } getInputElement() { let input; const element = this.elementRef.nativeElement; if (element.tagName === 'INPUT') { input = element; } else { input = element.querySelector('input'); } if (!input) { throw new Error('ngQuantity only allowed on inputs or elements containing inputs.'); } return input; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.2", ngImport: i0, type: QuantityDirective, deps: [{ token: i0.ElementRef }, { token: SystemOfUnits }], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.1.2", type: QuantityDirective, selector: "[ngQuantity]", inputs: { quantityAttr: ["ngQuantity", "quantityAttr"], ngUnit: "ngUnit" }, host: { listeners: { "input": "onControlInput($event)" } }, providers: [ CONTROL_VALUE_ACCESSOR ], usesOnChanges: true, ngImport: i0 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.2", ngImport: i0, type: QuantityDirective, decorators: [{ type: Directive, args: [{ // tslint:disable-next-line:directive-selector selector: '[ngQuantity]', providers: [ CONTROL_VALUE_ACCESSOR ] }] }], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: SystemOfUnits }]; }, propDecorators: { quantityAttr: [{ type: Input, args: ['ngQuantity'] }], ngUnit: [{ type: Input }], onControlInput: [{ type: HostListener, args: ['input', ['$event']] }] } }); function systemOfUnitsFactory(config) { const system = new SystemOfUnits(); if (config && config.quantities) { system.add(...config.quantities.map(q => new Quantity(q))); } return system; } class UnitSelectComponent { system; // tslint:disable-next-line:no-input-rename set quantityAttr(value) { this.initQuantity(value); setTimeout(() => this.selectUnit(), 0); if (this.subscription) { this.subscription.unsubscribe(); } this.subscription = this.system.changes$.subscribe((msg) => { if (msg.quantity === this.quantity) { this.selectUnit(); } }); } changeUnit = new EventEmitter(); quantity; currentUnit; select; subscription; constructor(elementRef, system) { this.system = system; this.select = elementRef.nativeElement; } ngAfterViewInit() { this.selectUnit(); } ngOnDestroy() { this.subscription?.unsubscribe(); } initQuantity(quantity) { this.quantity = typeof quantity === 'string' ? this.system.get(quantity) : quantity; } selectUnit() { const quantity = this.quantity; this.currentUnit = quantity ? quantity.unit : null; this.select.selectedIndex = (this.currentUnit && quantity) ? quantity.units.indexOf(this.currentUnit) : -1; } change() { if (!this.quantity) { return; } const index = this.select.selectedIndex; const unit = this.quantity.units[index]; this.currentUnit = unit; this.system.selectUnit(this.quantity, unit); this.changeUnit.emit(unit); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.2", ngImport: i0, type: UnitSelectComponent, deps: [{ token: i0.ElementRef }, { token: SystemOfUnits }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.1.2", type: UnitSelectComponent, selector: "[ngUnitSelect]", inputs: { quantityAttr: ["ngUnitSelect", "quantityAttr"] }, outputs: { changeUnit: "changeUnit" }, host: { listeners: { "change": "change($event)" } }, ngImport: i0, template: `<option *ngFor="let u of quantity?.units">{{u.symbol}}</option>`, isInline: true, dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.2", ngImport: i0, type: UnitSelectComponent, decorators: [{ type: Component, args: [{ // eslint-disable-next-line @angular-eslint/component-selector selector: '[ngUnitSelect]', template: `<option *ngFor="let u of quantity?.units">{{u.symbol}}</option>`, }] }], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: SystemOfUnits }]; }, propDecorators: { quantityAttr: [{ type: Input, args: ['ngUnitSelect'] }], changeUnit: [{ type: Output }], change: [{ type: HostListener, args: ['change', ['$event']] }] } }); const SYSTEM_OF_UNITS_CONFIGURATION = new InjectionToken('SYSTEM_OF_UNITS_CONFIGURATION'); class NgUnitsModule { static forRoot(config) { return { ngModule: NgUnitsModule, providers: [ { provide: SYSTEM_OF_UNITS_CONFIGURATION, useValue: config || {} }, { provide: SystemOfUnits, useFactory: systemOfUnitsFactory, deps: [SYSTEM_OF_UNITS_CONFIGURATION] } ] }; } static forChild() { return { ngModule: NgUnitsModule, providers: [] }; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.2", ngImport: i0, type: NgUnitsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "16.1.2", ngImport: i0, type: NgUnitsModule, declarations: [QuantityPipe, QuantityDirective, UnitSelectComponent], imports: [CommonModule], exports: [QuantityPipe, QuantityDirective, UnitSelectComponent] }); static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "16.1.2", ngImport: i0, type: NgUnitsModule, imports: [CommonModule] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.2", ngImport: i0, type: NgUnitsModule, decorators: [{ type: NgModule, args: [{ imports: [ CommonModule ], declarations: [ QuantityPipe, QuantityDirective, UnitSelectComponent ], exports: [ QuantityPipe, QuantityDirective, UnitSelectComponent ] }] }] }); const area = { name: 'Area', units: { 'm²': [1], 'cm²': [10000], 'mm³': [1000000], }, }; const frequency = { name: 'Frequency', units: { 'Hz': [1], 'kHz': [1e-3], 'MHz': [1e-6], 'rpm': [60] }, }; const length = { name: 'Length', units: { 'm': [1], 'cm': [100], 'mm': [1000], 'in': [1000 / 25.4], 'ft': [1000 / 12 / 25.4], }, }; const mass = { name: 'Mass', units: { 'g': [1e3], 'kg': [1], 't': [1e-3], }, }; const pressure = { name: 'Pressure', units: { 'Pa': [1], 'bar': [1e-5], 'mbar': [1e-2], 'Torr': [760 / 101325], 'mTorr': [760000 / 101325], 'psi': [0.0254 * 0.0254 / 0.453592 / 9.80665], 'inHg': [760 / 101325 / 25.4], }, }; const temperature = { name: 'Temperature', units: { 'K': [1], '°C': [1, 273.15], '°F': [1.8, (273.15 * 1.8) - 32], }, }; const time = { name: 'Time', units: { 'ms': [1000], 's': [1], 'min': [1 / 60], 'h': [1 / 3600], }, }; const volume = { name: 'Volume', units: { 'm³': [1], 'cm³': [1000000], 'mm³': [1000000000], }, }; /** * Generated bundle index. Do not edit. */ export { NgUnitsModule, Quantity, QuantityDirective, QuantityFormatters, QuantityMessage, QuantityParsers, QuantityPipe, SYSTEM_OF_UNITS_CONFIGURATION, SimpleUnit, SystemOfUnits, UnitSelectComponent, area, defaultPrint, frequency, length, mass, pressure, systemOfUnitsFactory, temperature, time, volume }; //# sourceMappingURL=ng-units.mjs.map