diff --git a/src/app/app.component.html b/src/app/app.component.html
index 0aad882..9b9f787 100644
--- a/src/app/app.component.html
+++ b/src/app/app.component.html
@@ -1,4 +1,4 @@
-
+
@@ -16,14 +16,14 @@
-@if (isLanguageSelectorOpen()) {
-
+
diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts
index 5ea2d0a..b283189 100644
--- a/src/app/app.component.spec.ts
+++ b/src/app/app.component.spec.ts
@@ -27,7 +27,7 @@ describe('AppComponent', (): void => {
it('should keep the language selector closed on first visit', (): void => {
const el = fixture.nativeElement as HTMLElement;
- expect(el.querySelector('.language-overlay')).toBeNull();
+ expect(el.querySelector('dialog.language-overlay')?.open).toBe(false);
});
it('should preselect English by default', (): void => {
@@ -51,7 +51,6 @@ describe('AppComponent', (): void => {
const comp = component as unknown as {
chooseLanguage(language: 'en' | 'de'): void;
currentLanguageCode(): string;
- isLanguageConfirmed(): boolean;
isLanguageSelectorOpen(): boolean;
};
@@ -59,7 +58,6 @@ describe('AppComponent', (): void => {
fixture.detectChanges();
expect(comp.currentLanguageCode()).toBe('de');
- expect(comp.isLanguageConfirmed()).toBe(true);
expect(comp.isLanguageSelectorOpen()).toBe(false);
expect(localStorage.getItem('portfolio-language')).toBe('de');
});
@@ -71,12 +69,10 @@ describe('AppComponent', (): void => {
restoredFixture.detectChanges();
const restoredComponent = restoredFixture.componentInstance as unknown as {
currentLanguageCode(): string;
- isLanguageConfirmed(): boolean;
isLanguageSelectorOpen(): boolean;
};
expect(restoredComponent.currentLanguageCode()).toBe('de');
- expect(restoredComponent.isLanguageConfirmed()).toBe(true);
expect(restoredComponent.isLanguageSelectorOpen()).toBe(false);
});
@@ -93,4 +89,18 @@ describe('AppComponent', (): void => {
expect(comp.isLanguageSelectorOpen()).toBe(true);
});
+
+ it('should sync the selector state when the dialog closes natively', (): void => {
+ const comp = component as unknown as {
+ reopenLanguageSelector(): void;
+ isLanguageSelectorOpen(): boolean;
+ };
+ comp.reopenLanguageSelector();
+ fixture.detectChanges();
+
+ const dialog = (fixture.nativeElement as HTMLElement).querySelector('dialog');
+ dialog?.dispatchEvent(new Event('close'));
+
+ expect(comp.isLanguageSelectorOpen()).toBe(false);
+ });
});
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index 07ee453..85a3f57 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -1,17 +1,18 @@
-import { DOCUMENT } from '@angular/common';
import {
+ afterRenderEffect,
ChangeDetectionStrategy,
Component,
computed,
- effect,
inject,
signal,
+ viewChild,
} from '@angular/core';
-import type { OnDestroy, OnInit } from '@angular/core';
+import type { ElementRef, OnInit } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import type { LanguageCode } from '../languages/language.types';
import type { LanguageOption } from './app.types';
+import { LanguagePreferenceService } from './services/language-preference.service';
import { ShellLanguageService } from './services/shell-language.service';
@Component({
@@ -21,13 +22,14 @@ import { ShellLanguageService } from './services/shell-language.service';
templateUrl: './app.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
-export class AppComponent implements OnInit, OnDestroy {
- private readonly document = inject(DOCUMENT);
+export class AppComponent implements OnInit {
+ private readonly languagePreferenceService = inject(LanguagePreferenceService);
private readonly shellLanguageService = inject(ShellLanguageService);
+ private readonly languageDialog =
+ viewChild.required>('languageDialog');
protected readonly content = this.shellLanguageService.content;
- protected readonly currentLanguageCode = this.shellLanguageService.languageCode;
- protected readonly isLanguageConfirmed = this.shellLanguageService.isLanguageConfirmed;
+ protected readonly currentLanguageCode = this.languagePreferenceService.languageCode;
protected readonly isLanguageSelectorOpen = signal(false);
protected readonly selectedLanguage = signal('en');
protected readonly languageOptions: readonly LanguageOption[] = [
@@ -41,26 +43,22 @@ export class AppComponent implements OnInit, OnDestroy {
this.getLanguageLabel(this.currentLanguageCode(), this.content()),
);
- constructor() {
- effect((): void => {
- const overflowValue = this.isLanguageSelectorOpen() ? 'hidden' : '';
- this.document.body.style.overflow = overflowValue;
- this.document.documentElement.style.overflow = overflowValue;
- });
- }
+ private readonly syncDialog = afterRenderEffect((): void => {
+ const dialog = this.languageDialog().nativeElement;
+ if (this.isLanguageSelectorOpen() && !dialog.open) {
+ dialog.showModal();
+ } else if (!this.isLanguageSelectorOpen() && dialog.open) {
+ dialog.close();
+ }
+ });
ngOnInit(): void {
- const initialLanguage = this.shellLanguageService.initializeFromStorage();
- this.selectedLanguage.set(initialLanguage);
+ this.selectedLanguage.set(this.languagePreferenceService.initialize());
}
protected chooseLanguage(language: LanguageCode): void {
this.selectedLanguage.set(language);
- this.confirmLanguage();
- }
-
- protected confirmLanguage(): void {
- this.shellLanguageService.confirmLanguage(this.selectedLanguage());
+ this.languagePreferenceService.confirmLanguage(language);
this.isLanguageSelectorOpen.set(false);
}
@@ -69,6 +67,7 @@ export class AppComponent implements OnInit, OnDestroy {
this.isLanguageSelectorOpen.set(true);
}
+ /** Also runs when the dialog closes natively, e.g. via Escape. */
protected closeLanguageSelector(): void {
this.selectedLanguage.set(this.currentLanguageCode());
this.isLanguageSelectorOpen.set(false);
@@ -83,9 +82,4 @@ export class AppComponent implements OnInit, OnDestroy {
protected getLanguageLabel(language: LanguageCode, content = this.dialogContent()): string {
return language === 'de' ? content.app.languageGerman : content.app.languageEnglish;
}
-
- ngOnDestroy(): void {
- this.document.body.style.overflow = '';
- this.document.documentElement.style.overflow = '';
- }
}