import { DOCUMENT, NgStyle } from '@angular/common'; import { Component, ElementRef, EventEmitter, Inject, Input, OnChanges, OnDestroy, OnInit, Output, Renderer2, SimpleChanges, } from '@angular/core'; import { Button } from 'primeng/button'; @Component({ selector: 'shared-light-bottomsheet', templateUrl: './light-bottomsheet.component.html', imports: [NgStyle, Button], styles: [ ` :host { position: fixed; inset: 0; z-index: 1210; } .light-bottomsheet-mask { position: absolute; inset: 0; background: rgba(15, 23, 42, 0.22); will-change: opacity; animation: sheetFadeIn var(--sheet-transition, 130ms cubic-bezier(0.2, 0, 0, 1)); } .light-bottomsheet-panel { position: absolute; right: 0; bottom: 0; left: 0; max-height: 80svh; background: var(--surface-card, #fff); border-radius: 12px 12px 0 0; box-shadow: none; transform: translate3d(0, 0, 0); will-change: transform; backface-visibility: hidden; animation: sheetSlideUp var(--sheet-transition, 130ms cubic-bezier(0.2, 0, 0, 1)); overflow: hidden; } .light-bottomsheet-content { max-height: 80svh; overflow: auto; padding-bottom: calc(0.75rem + env(safe-area-inset-bottom)); -webkit-overflow-scrolling: touch; overscroll-behavior: contain; } .light-bottomsheet-header { display: flex; align-items: center; justify-content: space-between; gap: 0.75rem; padding: 0.75rem 1rem; } .light-bottomsheet-title { margin: 0; } .light-bottomsheet-close { border: 0; background: transparent; padding: 0.25rem; line-height: 1; font-size: 1.25rem; } @keyframes sheetSlideUp { from { transform: translate3d(0, 100%, 0); } to { transform: translate3d(0, 0, 0); } } @keyframes sheetFadeIn { from { opacity: 0; } to { opacity: 1; } } `, ], host: { '[attr.role]': '"complementary"', '[attr.aria-modal]': 'true', '[attr.pfocustrap]': 'true', '[attr.data-pc-name]': "'drawer'", '[attr.data-pc-section]': "'root'", '[attr.aria-hidden]': '!visible', '[style.display]': 'visible ? "block" : "none"', '[style.--sheet-transition]': 'transitionOptions', }, }) export class SharedLightBottomsheetComponent implements OnInit, OnDestroy, OnChanges { @Input() header = ''; @Input() visible = false; @Output() visibleChange = new EventEmitter(); @Input() modal = true; @Input() closable = true; @Input() style: Record | undefined; @Input() mobileDrawerHeight = '80svh'; @Input() transitionOptions = '130ms cubic-bezier(0.2, 0, 0, 1)'; @Output() onHide = new EventEmitter(); private originalParent: Node | null = null; private readonly defaultDrawerZIndex = 1102; constructor( private readonly elementRef: ElementRef, private readonly renderer: Renderer2, @Inject(DOCUMENT) private readonly document: Document, ) {} ngOnInit() { const host = this.elementRef.nativeElement; this.originalParent = host.parentNode; this.renderer.appendChild(this.document.body, host); this.syncZIndex(); } ngOnChanges(changes: SimpleChanges) { if (changes['visible'] && this.visible) { this.syncZIndex(); } } ngOnDestroy() { this.removeDrawer(); } private removeDrawer() { const host = this.elementRef.nativeElement; if (this.originalParent) { this.renderer.appendChild(this.originalParent, host); } } onVisibilityChange(nextValue: boolean) { this.visible = nextValue; this.visibleChange.emit(nextValue); if (nextValue) { this.syncZIndex(); } if (!nextValue) { this.onHide.emit(); } } onMaskClick() { if (this.modal && this.closable) { this.onVisibilityChange(false); } } private syncZIndex() { const host = this.elementRef.nativeElement; const siblingDrawers = Array.from( this.document.body.querySelectorAll('.p-drawer'), ) as HTMLElement[]; const drawerZIndexes = siblingDrawers .map((drawer) => Number.parseInt(drawer.style.zIndex || '0', 10)) .filter((zIndex) => Number.isFinite(zIndex) && zIndex > 0); const maxDrawerZIndex = drawerZIndexes.length ? Math.max(...drawerZIndexes) : this.defaultDrawerZIndex; this.renderer.setStyle(host, 'z-index', `${maxDrawerZIndex + 1}`); } }