import { Component, ChangeDetectionStrategy, output, input, signal, AfterViewInit, OnDestroy, viewChild, ElementRef, inject, } from '@angular/core'; import { A11yModule } from '@angular/cdk/a11y'; import { ModalStateService } from '../../services/modal-state.service'; @Component({ selector: 'lt-modal', standalone: true, imports: [A11yModule], changeDetection: ChangeDetectionStrategy.OnPush, template: ` `, styles: ` @import '../../../library/main'; /* Keep the component host out of parent flex/grid flow. Parent containers apply spacing to direct children, so relying on the fixed child alone can still let become a layout item when it mounts. */ :host { position: fixed; inset: 0; z-index: 10000; display: block; margin: 0 !important; } section.modal { position: absolute; inset: 0; @include center-child(); padding: var(--large-padding); box-sizing: border-box; background: rgba(255, 248, 248, 0.94); transition: opacity 300ms; opacity: 1; overflow-y: auto; @media (max-width: $mobile-width) { padding: var(--medium-padding); } @media (max-height: $min-height) { align-items: flex-start; } &:not(.active) { opacity: 0; pointer-events: none; } button { margin-top: var(--medium-padding); } } `, }) export class ModalComponent implements AfterViewInit, OnDestroy { readonly labelledBy = input(null); readonly describedBy = input(null); readonly close = output(); // The active signal starts false; AfterViewInit flips it true on next tick // so the 300ms opacity transition fires on entry (0 → 1). readonly active = signal(false); private readonly dialogRef = viewChild>('dialog'); private previousFocus: HTMLElement | null = null; private readonly modalState = inject(ModalStateService); ngAfterViewInit(): void { this.previousFocus = document.activeElement as HTMLElement; // Track open state so towers can be locked while any modal is mounted. this.modalState.open(); // Defer one tick so the opacity transition runs (0 → 1). setTimeout(() => this.active.set(true), 0); } ngOnDestroy(): void { this.modalState.close(); this.previousFocus?.focus(); } onBackdropClick(event: MouseEvent): void { const dialog = this.dialogRef()?.nativeElement; if (dialog && !dialog.contains(event.target as Node)) { this.onClose(); } } onClose(): void { this.close.emit(); } }