Files
marketplaces/src/app/shared/ui/dialog/dialog.component.ts
sdarbinyan 1163bfd88a fix(storefront): replace hardcoded strings with i18n, neutral empty-state wording
- Route aria-label/alt/title strings (rating, discount, carousel arrows,
  hero slides, dialog close, toast dismiss, QR code, bank payment iframe,
  guest checkout fallback) through the translate pipe/service instead of
  literal English.
- Drop the "Oops!"/"Упс!" apology framing from category/subcategory empty
  states (en/ru/hy) - zero results is not an error.
2026-07-25 23:54:08 +04:00

110 lines
2.7 KiB
TypeScript

import {
AfterViewInit,
ChangeDetectionStrategy,
Component,
ElementRef,
HostListener,
input,
OnChanges,
output,
SimpleChanges,
ViewChild
} from '@angular/core';
import { TranslatePipe } from '../../../i18n/translate.pipe';
export type DialogSize = 'sm' | 'md' | 'lg';
const FOCUSABLE_SELECTOR =
'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
@Component({
selector: 'app-dialog',
standalone: true,
imports: [TranslatePipe],
templateUrl: './dialog.component.html',
styleUrl: './dialog.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class DialogComponent implements OnChanges, AfterViewInit {
readonly open = input(false);
readonly titleText = input<string | null>(null);
readonly size = input<DialogSize>('md');
readonly closed = output<void>();
@ViewChild('panel') private panelRef?: ElementRef<HTMLElement>;
private previouslyFocused: HTMLElement | null = null;
ngOnChanges(changes: SimpleChanges): void {
if (changes['open']) {
if (this.open()) {
this.previouslyFocused = document.activeElement as HTMLElement | null;
queueMicrotask(() => this.focusPanel());
} else {
this.previouslyFocused?.focus();
this.previouslyFocused = null;
}
}
}
ngAfterViewInit(): void {
if (this.open()) {
this.focusPanel();
}
}
@HostListener('document:keydown', ['$event'])
protected handleKeydown(event: KeyboardEvent): void {
if (!this.open()) {
return;
}
if (event.key === 'Escape') {
this.requestClose();
return;
}
if (event.key === 'Tab') {
this.trapFocus(event);
}
}
protected requestClose(): void {
this.closed.emit();
}
protected handleBackdropClick(): void {
this.requestClose();
}
private focusPanel(): void {
const panel = this.panelRef?.nativeElement;
if (!panel) {
return;
}
const focusable = panel.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR);
(focusable[0] ?? panel).focus();
}
private trapFocus(event: KeyboardEvent): void {
const panel = this.panelRef?.nativeElement;
if (!panel) {
return;
}
const focusable = Array.from(panel.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR));
if (focusable.length === 0) {
return;
}
const first = focusable[0];
const last = focusable[focusable.length - 1];
const active = document.activeElement;
if (event.shiftKey && active === first) {
event.preventDefault();
last.focus();
} else if (!event.shiftKey && active === last) {
event.preventDefault();
first.focus();
}
}
}