ng-units
Version:
Angular component library for units of measurement.
554 lines (533 loc) • 18.8 kB
JavaScript
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