Browse Source

fix(admin-ui): Currency input handles currencies without minor units

Fixes #1146
Michael Bromley 4 years ago
parent
commit
fd643b30f7

+ 2 - 2
packages/admin-ui/src/lib/core/src/shared/components/currency-input/currency-input.component.html

@@ -4,8 +4,8 @@
 >
     <input
         type="number"
-        step="0.01"
-        [value]="_decimalValue"
+        [step]="hasFractionPart ? 0.01 : 1"
+        [value]="_inputValue"
         [disabled]="disabled"
         [readonly]="readonly"
         (input)="onInput($event.target.value)"

+ 18 - 0
packages/admin-ui/src/lib/core/src/shared/components/currency-input/currency-input.component.spec.ts

@@ -72,6 +72,24 @@ describe('CurrencyInputComponent', () => {
         expect(fixture.componentInstance.price).toBe(157);
     }));
 
+    describe('currencies without minor units', () => {
+        it('displays JPY without decimal places', fakeAsync(() => {
+            MockDataService.language = LanguageCode.en;
+            const fixture = createAndRunChangeDetection(TestSimpleComponent, 429900, 'JPY');
+            const nativeInput = getNativeInput(fixture);
+            expect(nativeInput.value).toBe('4299');
+        }));
+
+        it('increments JPY with a step of 1', fakeAsync(() => {
+            MockDataService.language = LanguageCode.en;
+            const fixture = createAndRunChangeDetection(TestSimpleComponent, 429900, 'JPY');
+            const nativeInputDebugEl = fixture.debugElement.query(By.css('input[type="number"]'));
+
+            const nativeInput = getNativeInput(fixture);
+            expect(nativeInput.step).toBe('1');
+        }));
+    });
+
     describe('currencyCode display', () => {
         it('displays currency code in correct position (prefix)', fakeAsync(() => {
             MockDataService.language = LanguageCode.en;

+ 42 - 13
packages/admin-ui/src/lib/core/src/shared/components/currency-input/currency-input.component.ts

@@ -4,12 +4,13 @@ import {
     EventEmitter,
     Input,
     OnChanges,
+    OnDestroy,
     OnInit,
     Output,
     SimpleChanges,
 } from '@angular/core';
 import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
-import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
+import { BehaviorSubject, combineLatest, Observable, Subscription } from 'rxjs';
 import { map } from 'rxjs/operators';
 
 import { DataService } from '../../../data/providers/data.service';
@@ -30,7 +31,7 @@ import { DataService } from '../../../data/providers/data.service';
         },
     ],
 })
-export class CurrencyInputComponent implements ControlValueAccessor, OnInit, OnChanges {
+export class CurrencyInputComponent implements ControlValueAccessor, OnInit, OnChanges, OnDestroy {
     @Input() disabled = false;
     @Input() readonly = false;
     @Input() value: number;
@@ -38,10 +39,12 @@ export class CurrencyInputComponent implements ControlValueAccessor, OnInit, OnC
     @Output() valueChange = new EventEmitter();
     prefix$: Observable<string>;
     suffix$: Observable<string>;
+    hasFractionPart = true;
     onChange: (val: any) => void;
     onTouch: () => void;
-    _decimalValue: string;
+    _inputValue: string;
     private currencyCode$ = new BehaviorSubject<string>('');
+    private subscription: Subscription;
 
     constructor(private dataService: DataService, private changeDetectorRef: ChangeDetectorRef) {}
 
@@ -53,11 +56,13 @@ export class CurrencyInputComponent implements ControlValueAccessor, OnInit, OnC
                     return '';
                 }
                 const locale = languageCode.replace(/_/g, '-');
-                const parts = (new Intl.NumberFormat(locale, {
-                    style: 'currency',
-                    currency: currencyCode,
-                    currencyDisplay: 'symbol',
-                }) as any).formatToParts();
+                const parts = (
+                    new Intl.NumberFormat(locale, {
+                        style: 'currency',
+                        currency: currencyCode,
+                        currencyDisplay: 'symbol',
+                    }) as any
+                ).formatToParts();
                 const NaNString = parts.find(p => p.type === 'nan')?.value ?? 'NaN';
                 const localised = new Intl.NumberFormat(locale, {
                     style: 'currency',
@@ -69,6 +74,24 @@ export class CurrencyInputComponent implements ControlValueAccessor, OnInit, OnC
         );
         this.prefix$ = shouldPrefix$.pipe(map(shouldPrefix => (shouldPrefix ? this.currencyCode : '')));
         this.suffix$ = shouldPrefix$.pipe(map(shouldPrefix => (shouldPrefix ? '' : this.currencyCode)));
+
+        this.subscription = combineLatest(languageCode$, this.currencyCode$).subscribe(
+            ([languageCode, currencyCode]) => {
+                if (!currencyCode) {
+                    return '';
+                }
+                const locale = languageCode.replace(/_/g, '-');
+                const parts = (
+                    new Intl.NumberFormat(locale, {
+                        style: 'currency',
+                        currency: currencyCode,
+                        currencyDisplay: 'symbol',
+                    }) as any
+                ).formatToParts(123.45);
+                this.hasFractionPart = !!parts.find(p => p.type === 'fraction');
+                this._inputValue = this.toNumericString(this._inputValue);
+            },
+        );
     }
 
     ngOnChanges(changes: SimpleChanges) {
@@ -80,6 +103,12 @@ export class CurrencyInputComponent implements ControlValueAccessor, OnInit, OnC
         }
     }
 
+    ngOnDestroy() {
+        if (this.subscription) {
+            this.subscription.unsubscribe();
+        }
+    }
+
     registerOnChange(fn: any) {
         this.onChange = fn;
     }
@@ -98,11 +127,11 @@ export class CurrencyInputComponent implements ControlValueAccessor, OnInit, OnC
             this.onChange(integerValue);
         }
         this.valueChange.emit(integerValue);
-        const delta = Math.abs(Number(this._decimalValue) - Number(value));
+        const delta = Math.abs(Number(this._inputValue) - Number(value));
         if (0.009 < delta && delta < 0.011) {
-            this._decimalValue = this.toNumericString(value);
+            this._inputValue = this.toNumericString(value);
         } else {
-            this._decimalValue = value;
+            this._inputValue = value;
         }
     }
 
@@ -115,11 +144,11 @@ export class CurrencyInputComponent implements ControlValueAccessor, OnInit, OnC
     writeValue(value: any): void {
         const numericValue = +value;
         if (!Number.isNaN(numericValue)) {
-            this._decimalValue = this.toNumericString(Math.floor(value) / 100);
+            this._inputValue = this.toNumericString(Math.floor(value) / 100);
         }
     }
 
     private toNumericString(value: number | string): string {
-        return Number(value).toFixed(2);
+        return this.hasFractionPart ? Number(value).toFixed(2) : Number(value).toFixed(0);
     }
 }