refactor: migrate cart payment modals to shared app-dialog primitive
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
Third attempt, done properly this time - first two were reverted (one stopped cleanly on real conflicts, one botched sequencing and deleted the old focus-trap before finishing the swap). DialogComponent gains closeOnEscape/closeOnBackdropClick (default true, backward-compatible with its 13 other call sites) and ariaLabel (for dialogs with no visible title header). FOCUSABLE_SELECTOR now includes iframe for the bank-payment panel's focus trap. Cart wires closeOnBackdropClick=false on both dialogs (in-flight payment shouldn't cancel on a stray click) and closeOnEscape tied to the bank popup's open state, so Escape closes the nested bank iframe first and falls back to the QR view - matches the original priority exactly. Original geometry (500px QR modal/40px padding, 960x760 bank modal/ 56-16-16 padding, both mobile breakpoints) preserved via :host ::ng-deep overrides scoped per dialog instance - same pattern already used by product-carousel-widget.component.ts. cart.component.ts loses ~90 lines of hand-rolled ViewChild/HostListener/ focus-trap code - app-dialog owns all of it now. Verified live in browser: dialog sizing/padding/aria-label correct at mobile+desktop, backdrop-click confirmed inert, Escape-priority confirmed (bank closes first, then QR), initial focus lands on close button. 83/83 tests pass, tsc/build clean. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
[class.app-dialog-panel--lg]="size() === 'lg'"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
[attr.aria-label]="titleText()"
|
||||
[attr.aria-label]="titleText() ?? ariaLabel()"
|
||||
tabindex="-1"
|
||||
(click)="$event.stopPropagation()"
|
||||
>
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
}
|
||||
|
||||
.app-dialog-panel {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
|
||||
@@ -15,7 +15,7 @@ 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"])';
|
||||
'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), iframe, [tabindex]:not([tabindex="-1"])';
|
||||
|
||||
@Component({
|
||||
selector: 'app-dialog',
|
||||
@@ -28,7 +28,14 @@ const FOCUSABLE_SELECTOR =
|
||||
export class DialogComponent implements OnChanges, AfterViewInit {
|
||||
readonly open = input(false);
|
||||
readonly titleText = input<string | null>(null);
|
||||
// Accessible name for dialogs that skip the visible titled header (a
|
||||
// consumer-rendered close button inside the content instead). Ignored
|
||||
// when titleText is set - the header already supplies the name.
|
||||
readonly ariaLabel = input<string | null>(null);
|
||||
readonly size = input<DialogSize>('md');
|
||||
// Both default true (existing behavior for every current consumer).
|
||||
readonly closeOnEscape = input(true);
|
||||
readonly closeOnBackdropClick = input(true);
|
||||
|
||||
readonly closed = output<void>();
|
||||
|
||||
@@ -60,7 +67,9 @@ export class DialogComponent implements OnChanges, AfterViewInit {
|
||||
return;
|
||||
}
|
||||
if (event.key === 'Escape') {
|
||||
this.requestClose();
|
||||
if (this.closeOnEscape()) {
|
||||
this.requestClose();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (event.key === 'Tab') {
|
||||
@@ -73,7 +82,9 @@ export class DialogComponent implements OnChanges, AfterViewInit {
|
||||
}
|
||||
|
||||
protected handleBackdropClick(): void {
|
||||
this.requestClose();
|
||||
if (this.closeOnBackdropClick()) {
|
||||
this.requestClose();
|
||||
}
|
||||
}
|
||||
|
||||
private focusPanel(): void {
|
||||
|
||||
Reference in New Issue
Block a user