Files
psp_panel/src/app/shared/components/dialog/light-bottomsheet.component.ts
T

207 lines
5.2 KiB
TypeScript
Raw Normal View History

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);
}
.light-bottomsheet-panel {
position: absolute;
right: 0;
bottom: 0;
left: 0;
max-height: 90vh;
display: flex;
flex-direction: column;
background: var(--surface-card, #fff);
border-radius: 12px 12px 0 0;
box-shadow: none;
backface-visibility: hidden;
overflow: hidden;
}
.light-bottomsheet-content {
display: flex;
flex-direction: column;
flex: 1;
min-height: 0;
2026-05-17 00:27:45 +03:30
// padding-bottom: calc(0.75rem + env(safe-area-inset-bottom));
-webkit-overflow-scrolling: touch;
overscroll-behavior: contain;
}
.light-bottomsheet-body {
flex: 1;
min-height: 0;
overflow: auto;
}
.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;
}
`,
],
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<boolean>();
@Input() modal = true;
@Input() closable = true;
@Input() style: Record<string, string> | undefined;
@Input() mobileDrawerHeight = '90vh';
@Input() transitionOptions = '130ms cubic-bezier(0.2, 0, 0, 1)';
@Output() onHide = new EventEmitter<any>();
private originalParent: Node | null = null;
private readonly defaultDrawerZIndex = 1102;
2026-05-17 00:27:45 +03:30
private previousBodyOverflow = '';
private previousBodyTouchAction = '';
constructor(
private readonly elementRef: ElementRef<HTMLElement>,
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();
2026-05-17 00:27:45 +03:30
this.toggleBodyScrollLock(this.visible);
}
ngOnChanges(changes: SimpleChanges) {
if (changes['visible'] && this.visible) {
this.syncZIndex();
}
2026-05-17 00:27:45 +03:30
if (changes['visible']) {
2026-05-17 00:27:45 +03:30
this.toggleBodyScrollLock(this.visible);
}
}
ngOnDestroy() {
2026-05-17 00:27:45 +03:30
this.toggleBodyScrollLock(false);
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);
2026-05-17 00:27:45 +03:30
this.toggleBodyScrollLock(nextValue);
if (nextValue) {
this.syncZIndex();
}
if (!nextValue) {
this.onHide.emit();
}
}
onMaskClick() {
if (this.modal && this.closable) {
this.onVisibilityChange(false);
}
}
2026-05-17 00:27:45 +03:30
private toggleBodyScrollLock(locked: boolean) {
const body = this.document.body;
if (!body) return;
if (locked) {
if (!this.previousBodyOverflow) {
this.previousBodyOverflow = body.style.overflow || '';
}
if (!this.previousBodyTouchAction) {
this.previousBodyTouchAction = body.style.touchAction || '';
}
this.renderer.setStyle(body, 'overflow', 'hidden');
this.renderer.setStyle(body, 'touch-action', 'none');
return;
}
this.renderer.removeStyle(body, 'overflow');
this.renderer.removeStyle(body, 'touch-action');
2026-05-17 00:27:45 +03:30
this.previousBodyOverflow = '';
this.previousBodyTouchAction = '';
}
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}`);
}
}