90 lines
2.9 KiB
TypeScript
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();
|
|
}
|
|
}
|