Files
shopit-front/src/app/shared/components/modal-shell/modal-shell.component.ts

90 lines
2.9 KiB
TypeScript

import { NgClass } from '@angular/common';
import {
ChangeDetectionStrategy,
Component,
ElementRef,
computed,
input,
output,
viewChild,
} from '@angular/core';
import { ModalPresentation, ModalSize } from '../../../core/services/modal.service';
@Component({
selector: 'app-modal-shell',
imports: [NgClass],
templateUrl: './modal-shell.component.html',
styleUrl: './modal-shell.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ModalShellComponent {
private readonly shell = viewChild.required<ElementRef<HTMLElement>>('shell');
private readonly panel = viewChild.required<ElementRef<HTMLElement>>('panel');
readonly title = input<string | undefined>();
readonly size = input<ModalSize>('md');
readonly presentation = input<ModalPresentation>('dialog');
readonly showCloseButton = input(true);
readonly backdropClick = output<void>();
readonly closeRequested = output<void>();
protected readonly dialogClass = computed(() => {
const size = this.size();
return {
'modal-shell__dialog--qr': size === 'qr',
'modal-sm': size === 'sm',
'modal-lg': size === 'lg',
'modal-xl': size === 'xl',
'modal-shell__dialog--full': size === 'full',
};
});
protected readonly titleId = `modal-title-${Math.random().toString(36).slice(2, 9)}`;
protected get ariaLabelledBy(): string | null {
return this.title() ? this.titleId : null;
}
setVisualViewport(viewport: VisualViewport | null, layoutScrollX = 0, layoutScrollY = 0): void {
const shell = this.shell().nativeElement;
if (!viewport) {
shell.style.removeProperty('--modal-viewport-top');
shell.style.removeProperty('--modal-viewport-left');
shell.style.removeProperty('--modal-viewport-width');
shell.style.removeProperty('--modal-viewport-height');
return;
}
// pageTop/pageLeft are a useful fallback for WebKit versions that update
// offsetTop/offsetLeft one frame late after dismissing a native control.
const top = Math.max(viewport.offsetTop, viewport.pageTop - layoutScrollY);
const left = Math.max(viewport.offsetLeft, viewport.pageLeft - layoutScrollX);
shell.style.setProperty('--modal-viewport-top', `${top}px`);
shell.style.setProperty('--modal-viewport-left', `${left}px`);
shell.style.setProperty('--modal-viewport-width', `${viewport.width}px`);
shell.style.setProperty('--modal-viewport-height', `${viewport.height}px`);
}
focusInitialElement(): void {
const panel = this.panel().nativeElement;
const focusTarget = panel.querySelector<HTMLElement>(
[
'[autofocus]',
'button:not([disabled])',
'[href]',
'input:not([disabled])',
'select:not([disabled])',
'textarea:not([disabled])',
'[tabindex]:not([tabindex="-1"])',
].join(', '),
);
(focusTarget ?? panel).focus();
}
}