From b1d85c5735b8080caa4a2a7fb3216391aacce244 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Mon, 10 Aug 2026 09:36:54 +0200 Subject: [PATCH] Reuse currency selector --- .../user-account-settings.component.ts | 11 +++---- .../user-account-settings.html | 2 +- .../currency-selector.component.ts | 29 ++++++++++++++----- .../src/lib/shared/abstract-mat-form-field.ts | 20 ++++++++++++- 4 files changed, 46 insertions(+), 16 deletions(-) diff --git a/apps/client/src/app/components/user-account-settings/user-account-settings.component.ts b/apps/client/src/app/components/user-account-settings/user-account-settings.component.ts index 2743fe50c..32860d44d 100644 --- a/apps/client/src/app/components/user-account-settings/user-account-settings.component.ts +++ b/apps/client/src/app/components/user-account-settings/user-account-settings.component.ts @@ -32,7 +32,6 @@ import { import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { NonNullableFormBuilder, - FormBuilder, FormsModule, ReactiveFormsModule, Validators @@ -80,9 +79,8 @@ import { catchError } from 'rxjs/operators'; }) export class GfUserAccountSettingsComponent implements OnInit { protected readonly appearancePlaceholder = $localize`Auto`; - protected readonly baseCurrency: string; - protected readonly baseCurrencyForm = inject(FormBuilder).group({ - baseCurrency: [''] + protected readonly baseCurrencyForm = inject(NonNullableFormBuilder).group({ + baseCurrency: [{ value: '', disabled: true }] }); protected closeUserAccountMailHref: string; protected readonly currencies: string[] = []; @@ -135,9 +133,8 @@ export class GfUserAccountSettingsComponent implements OnInit { private readonly webAuthnService = inject(WebAuthnService); public constructor() { - const { baseCurrency, currencies } = this.dataService.fetchInfo(); + const { currencies } = this.dataService.fetchInfo(); - this.baseCurrency = baseCurrency; this.currencies = currencies; this.userService.stateChanged @@ -189,7 +186,7 @@ export class GfUserAccountSettingsComponent implements OnInit { ); this.baseCurrencyForm.setValue( - { baseCurrency: this.user.settings.baseCurrency ?? null }, + { baseCurrency: this.user.settings.baseCurrency ?? '' }, { emitEvent: false } ); diff --git a/apps/client/src/app/components/user-account-settings/user-account-settings.html b/apps/client/src/app/components/user-account-settings/user-account-settings.html index 9a5b702f5..40adccca4 100644 --- a/apps/client/src/app/components/user-account-settings/user-account-settings.html +++ b/apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -3,7 +3,7 @@
-
+
Base Currency diff --git a/libs/ui/src/lib/currency-selector/currency-selector.component.ts b/libs/ui/src/lib/currency-selector/currency-selector.component.ts index 01af411e8..400ccdb35 100644 --- a/libs/ui/src/lib/currency-selector/currency-selector.component.ts +++ b/libs/ui/src/lib/currency-selector/currency-selector.component.ts @@ -30,6 +30,7 @@ import { MatAutocomplete, MatAutocompleteModule, MatAutocompleteOrigin, + MatAutocompleteTrigger, MatOption } from '@angular/material/autocomplete'; import { @@ -78,9 +79,13 @@ export class GfCurrencySelectorComponent public filteredCurrencies: string[] = []; public readonly formControlName = input.required(); + private readonly autocompleteTrigger = viewChild.required( + MatAutocompleteTrigger + ); private readonly destroyRef = inject(DestroyRef); private readonly formField = inject(MAT_FORM_FIELD); private readonly input = viewChild.required(MatInput); + private lastSelectedCurrency: string | null = null; public constructor( public override readonly _elementRef: ElementRef, @@ -113,6 +118,8 @@ export class GfCurrencySelectorComponent public override set value(value: string | null) { this.control.setValue(value); super.value = value; + + this.lastSelectedCurrency = value; } public focus() { @@ -170,16 +177,24 @@ export class GfCurrencySelectorComponent } } + public override onBlur() { + // Typing clears the selected currency, so restore the last selection once + // the user leaves the field without picking an option. The panel is still + // open while an option is being clicked, in which case the selection + // itself provides the new value. + if (!super.value && !this.autocompleteTrigger().panelOpen) { + this.value = this.lastSelectedCurrency; + + this.changeDetectorRef.markForCheck(); + } + + super.onBlur(); + } + public onUpdateCurrency({ option }: { option: MatOption }) { super.value = option.value; - } - public setDisabledState(isDisabled: boolean) { - if (isDisabled) { - this.control.disable({ emitEvent: false }); - } else { - this.control.enable({ emitEvent: false }); - } + this.lastSelectedCurrency = option.value; } private filter(value: string) { diff --git a/libs/ui/src/lib/shared/abstract-mat-form-field.ts b/libs/ui/src/lib/shared/abstract-mat-form-field.ts index a6ef16175..2df4e1997 100644 --- a/libs/ui/src/lib/shared/abstract-mat-form-field.ts +++ b/libs/ui/src/lib/shared/abstract-mat-form-field.ts @@ -9,7 +9,12 @@ import { Input, OnDestroy } from '@angular/core'; -import { ControlValueAccessor, NgControl, Validators } from '@angular/forms'; +import { + ControlValueAccessor, + FormControl, + NgControl, + Validators +} from '@angular/forms'; import { MatFormFieldControl } from '@angular/material/form-field'; import { Subject } from 'rxjs'; @@ -23,6 +28,7 @@ export abstract class AbstractMatFormField @HostBinding('attr.aria-describedBy') public describedBy = ''; public readonly autofilled: boolean; + public abstract readonly control: FormControl; public errorState: boolean; public focused = false; public readonly stateChanges = new Subject(); @@ -156,6 +162,18 @@ export abstract class AbstractMatFormField this.describedBy = ids.join(' '); } + public setDisabledState(isDisabled: boolean) { + if (isDisabled) { + this.control.disable({ emitEvent: false }); + } else { + this.control.enable({ emitEvent: false }); + } + + this.disabled = isDisabled; + + this.stateChanges.next(); + } + public writeValue(value: T) { this.value = value; }