>>>>>> c5ab9c84a (fix(client): address review feedback for localized number directive)
}
export function getAllActivityTypes(): ActivityType[] {
diff --git a/libs/ui/src/lib/account-balances/account-balances.component.html b/libs/ui/src/lib/account-balances/account-balances.component.html
index 780653c67..d972eea69 100644
--- a/libs/ui/src/lib/account-balances/account-balances.component.html
+++ b/libs/ui/src/lib/account-balances/account-balances.component.html
@@ -51,7 +51,12 @@
-
+
{{ accountCurrency() }}
diff --git a/libs/ui/src/lib/account-balances/account-balances.component.ts b/libs/ui/src/lib/account-balances/account-balances.component.ts
index e27d29516..2742d9fd4 100644
--- a/libs/ui/src/lib/account-balances/account-balances.component.ts
+++ b/libs/ui/src/lib/account-balances/account-balances.component.ts
@@ -42,11 +42,13 @@ import {
} from 'ionicons/icons';
import { get, isNil } from 'lodash';
+import { GfLocalizedNumberDirective } from '../localized-number';
import { GfValueComponent } from '../value';
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
+ GfLocalizedNumberDirective,
GfValueComponent,
IonIcon,
MatButtonModule,
diff --git a/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.component.ts b/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.component.ts
index 1b7b99fa2..525f4a44a 100644
--- a/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.component.ts
+++ b/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.component.ts
@@ -27,6 +27,7 @@ import { addIcons } from 'ionicons';
import { calendarClearOutline, refreshOutline } from 'ionicons/icons';
import { isNil } from 'lodash';
+import { GfLocalizedNumberDirective } from '../../localized-number';
import { HistoricalMarketDataEditorDialogParams } from './interfaces/interfaces';
@Component({
@@ -34,6 +35,7 @@ import { HistoricalMarketDataEditorDialogParams } from './interfaces/interfaces'
host: { class: 'h-100' },
imports: [
FormsModule,
+ GfLocalizedNumberDirective,
IonIcon,
MatButtonModule,
MatDatepickerModule,
@@ -54,7 +56,7 @@ export class GfHistoricalMarketDataEditorDialogComponent implements OnInit {
protected readonly marketPrice = signal(this.data.marketPrice);
private readonly destroyRef = inject(DestroyRef);
- private readonly locale =
+ protected readonly locale =
this.data.user.settings.locale ?? inject(MAT_DATE_LOCALE);
public constructor(
diff --git a/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html b/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html
index 7e8183664..858304eda 100644
--- a/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html
+++ b/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html
@@ -25,9 +25,10 @@
Market Price
diff --git a/libs/ui/src/lib/localized-number/index.ts b/libs/ui/src/lib/localized-number/index.ts
new file mode 100644
index 000000000..ca2e9de13
--- /dev/null
+++ b/libs/ui/src/lib/localized-number/index.ts
@@ -0,0 +1 @@
+export * from './localized-number.directive';
diff --git a/apps/client/src/app/directives/localized-number/localized-number.directive.spec.ts b/libs/ui/src/lib/localized-number/localized-number.directive.spec.ts
similarity index 77%
rename from apps/client/src/app/directives/localized-number/localized-number.directive.spec.ts
rename to libs/ui/src/lib/localized-number/localized-number.directive.spec.ts
index 181398e9e..087520a0d 100644
--- a/apps/client/src/app/directives/localized-number/localized-number.directive.spec.ts
+++ b/libs/ui/src/lib/localized-number/localized-number.directive.spec.ts
@@ -82,7 +82,7 @@ describe('GfLocalizedNumberDirective', () => {
host.control.setValue(1234.5);
fixture.detectChanges();
- expect(input.value).toBe('1234.5');
+ expect(input.value).toBe('1,234.5');
host.control.setValue(null);
fixture.detectChanges();
@@ -90,6 +90,41 @@ describe('GfLocalizedNumberDirective', () => {
expect(input.value).toBe('');
});
+ it('should write and parse correctly for German locale (de-DE)', () => {
+ host.locale = 'de-DE';
+ fixture.detectChanges();
+
+ host.control.setValue(1234.5);
+ fixture.detectChanges();
+
+ expect(input.value).toBe('1.234,5');
+
+ typeValue('1.234,5');
+
+ expect(host.control.value).toBe(1234.5);
+ });
+
+ it('should preserve more than 3 fraction digits on write/parse', () => {
+ host.locale = 'de-DE';
+ fixture.detectChanges();
+
+ host.control.setValue(12.345678);
+ fixture.detectChanges();
+
+ expect(input.value).toBe('12,345678');
+
+ typeValue(input.value);
+
+ expect(host.control.value).toBe(12.345678);
+ });
+
+ it('should write empty string for non-numeric values', () => {
+ host.control.setValue('' as unknown as number);
+ fixture.detectChanges();
+
+ expect(input.value).toBe('');
+ });
+
it('should keep required validation working', () => {
host.locale = 'de-DE';
host.control.setValidators([
diff --git a/apps/client/src/app/directives/localized-number/localized-number.directive.ts b/libs/ui/src/lib/localized-number/localized-number.directive.ts
similarity index 62%
rename from apps/client/src/app/directives/localized-number/localized-number.directive.ts
rename to libs/ui/src/lib/localized-number/localized-number.directive.ts
index cf8a80507..71928c754 100644
--- a/apps/client/src/app/directives/localized-number/localized-number.directive.ts
+++ b/libs/ui/src/lib/localized-number/localized-number.directive.ts
@@ -1,9 +1,18 @@
import { DEFAULT_LOCALE } from '@ghostfolio/common/config';
-import { extractNumberFromString } from '@ghostfolio/common/helper';
+import {
+ extractNumberFromString,
+ formatNumberForLocale
+} from '@ghostfolio/common/helper';
import { DOCUMENT } from '@angular/common';
-import { Directive, ElementRef, inject, input } from '@angular/core';
-import { ControlValueAccessor, NgControl } from '@angular/forms';
+import {
+ Directive,
+ ElementRef,
+ forwardRef,
+ inject,
+ input
+} from '@angular/core';
+import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
@Directive({
host: {
@@ -12,6 +21,13 @@ import { ControlValueAccessor, NgControl } from '@angular/forms';
'[attr.inputmode]': '"decimal"',
'[attr.type]': '"text"'
},
+ providers: [
+ {
+ provide: NG_VALUE_ACCESSOR,
+ useExisting: forwardRef(() => GfLocalizedNumberDirective),
+ multi: true
+ }
+ ],
selector: 'input[gfLocalizedNumber]'
})
export class GfLocalizedNumberDirective implements ControlValueAccessor {
@@ -21,15 +37,6 @@ export class GfLocalizedNumberDirective implements ControlValueAccessor {
private readonly elementRef =
inject>(ElementRef);
- public constructor() {
- const ngControl = inject(NgControl, { optional: true, self: true });
-
- if (ngControl) {
- // Replace DefaultValueAccessor so the FormControl stores a number
- ngControl.valueAccessor = this;
- }
- }
-
public handleBlur() {
this.onTouched();
}
@@ -49,7 +56,8 @@ export class GfLocalizedNumberDirective implements ControlValueAccessor {
// 3. DEFAULT_LOCALE ('en-US') as the final fallback
const localeInput = this.locale();
const documentLang = this.document.documentElement.lang;
- const resolvedLocale = localeInput ?? documentLang ?? DEFAULT_LOCALE;
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+ const resolvedLocale = localeInput || documentLang || DEFAULT_LOCALE;
const parsedNumber = extractNumberFromString({
locale: resolvedLocale,
@@ -76,10 +84,25 @@ export class GfLocalizedNumberDirective implements ControlValueAccessor {
}
public writeValue(value: number | null) {
- this.elementRef.nativeElement.value =
- value === null || value === undefined || Number.isNaN(value)
- ? ''
- : String(value);
+ if (
+ value === null ||
+ value === undefined ||
+ typeof value !== 'number' ||
+ Number.isNaN(value)
+ ) {
+ this.elementRef.nativeElement.value = '';
+ return;
+ }
+
+ const localeInput = this.locale();
+ const documentLang = this.document.documentElement.lang;
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+ const resolvedLocale = localeInput || documentLang || DEFAULT_LOCALE;
+
+ this.elementRef.nativeElement.value = formatNumberForLocale({
+ locale: resolvedLocale,
+ value
+ });
}
private onChange: (value: number | null) => void = () => undefined;
|