2026-05-13 13:44:33 +03:30
|
|
|
import { DOCUMENT, NgStyle } from '@angular/common';
|
2026-05-13 10:59:51 +03:30
|
|
|
import {
|
|
|
|
|
Component,
|
|
|
|
|
ElementRef,
|
|
|
|
|
EventEmitter,
|
|
|
|
|
Inject,
|
|
|
|
|
Input,
|
2026-05-13 13:44:33 +03:30
|
|
|
OnChanges,
|
2026-05-13 10:59:51 +03:30
|
|
|
OnDestroy,
|
|
|
|
|
OnInit,
|
|
|
|
|
Output,
|
|
|
|
|
Renderer2,
|
2026-05-13 13:44:33 +03:30
|
|
|
SimpleChanges,
|
2026-05-13 10:59:51 +03:30
|
|
|
} from '@angular/core';
|
2026-05-13 13:44:33 +03:30
|
|
|
import { Button } from 'primeng/button';
|
2026-05-13 10:39:43 +03:30
|
|
|
|
|
|
|
|
@Component({
|
|
|
|
|
selector: 'shared-light-bottomsheet',
|
|
|
|
|
templateUrl: './light-bottomsheet.component.html',
|
2026-05-13 13:44:33 +03:30
|
|
|
imports: [NgStyle, Button],
|
2026-05-13 10:39:43 +03:30
|
|
|
styles: [
|
|
|
|
|
`
|
|
|
|
|
:host {
|
|
|
|
|
position: fixed;
|
|
|
|
|
inset: 0;
|
2026-05-13 13:44:33 +03:30
|
|
|
z-index: 1210;
|
2026-05-13 10:39:43 +03:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.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;
|
2026-05-16 21:17:51 +03:30
|
|
|
max-height: 90vh;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
2026-05-13 10:39:43 +03:30
|
|
|
background: var(--surface-card, #fff);
|
|
|
|
|
border-radius: 12px 12px 0 0;
|
|
|
|
|
box-shadow: none;
|
|
|
|
|
backface-visibility: hidden;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.light-bottomsheet-content {
|
2026-05-16 21:17:51 +03:30
|
|
|
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));
|
2026-05-13 10:39:43 +03:30
|
|
|
-webkit-overflow-scrolling: touch;
|
|
|
|
|
overscroll-behavior: contain;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-16 21:17:51 +03:30
|
|
|
.light-bottomsheet-body {
|
|
|
|
|
flex: 1;
|
|
|
|
|
min-height: 0;
|
|
|
|
|
overflow: auto;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-13 10:39:43 +03:30
|
|
|
.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: {
|
2026-05-13 13:44:33 +03:30
|
|
|
'[attr.role]': '"complementary"',
|
|
|
|
|
'[attr.aria-modal]': 'true',
|
|
|
|
|
'[attr.pfocustrap]': 'true',
|
|
|
|
|
'[attr.data-pc-name]': "'drawer'",
|
|
|
|
|
'[attr.data-pc-section]': "'root'",
|
|
|
|
|
'[attr.aria-hidden]': '!visible',
|
2026-05-13 10:39:43 +03:30
|
|
|
'[style.display]': 'visible ? "block" : "none"',
|
|
|
|
|
'[style.--sheet-transition]': 'transitionOptions',
|
|
|
|
|
},
|
|
|
|
|
})
|
2026-05-13 13:44:33 +03:30
|
|
|
export class SharedLightBottomsheetComponent implements OnInit, OnDestroy, OnChanges {
|
2026-05-13 10:39:43 +03:30
|
|
|
@Input() header = '';
|
|
|
|
|
@Input() visible = false;
|
|
|
|
|
@Output() visibleChange = new EventEmitter<boolean>();
|
|
|
|
|
|
|
|
|
|
@Input() modal = true;
|
|
|
|
|
@Input() closable = true;
|
|
|
|
|
@Input() style: Record<string, string> | undefined;
|
2026-05-16 21:17:51 +03:30
|
|
|
@Input() mobileDrawerHeight = '90vh';
|
2026-05-13 10:39:43 +03:30
|
|
|
@Input() transitionOptions = '130ms cubic-bezier(0.2, 0, 0, 1)';
|
|
|
|
|
|
|
|
|
|
@Output() onHide = new EventEmitter<any>();
|
|
|
|
|
|
2026-05-13 10:59:51 +03:30
|
|
|
private originalParent: Node | null = null;
|
2026-05-13 13:44:33 +03:30
|
|
|
private readonly defaultDrawerZIndex = 1102;
|
2026-05-17 00:27:45 +03:30
|
|
|
private previousBodyOverflow = '';
|
|
|
|
|
private previousBodyTouchAction = '';
|
2026-05-13 10:59:51 +03:30
|
|
|
|
|
|
|
|
constructor(
|
|
|
|
|
private readonly elementRef: ElementRef<HTMLElement>,
|
|
|
|
|
private readonly renderer: Renderer2,
|
2026-05-21 21:35:34 +03:30
|
|
|
@Inject(DOCUMENT) private readonly document: Document
|
2026-05-13 10:59:51 +03:30
|
|
|
) {}
|
|
|
|
|
|
|
|
|
|
ngOnInit() {
|
|
|
|
|
const host = this.elementRef.nativeElement;
|
|
|
|
|
this.originalParent = host.parentNode;
|
|
|
|
|
this.renderer.appendChild(this.document.body, host);
|
2026-05-13 13:44:33 +03:30
|
|
|
this.syncZIndex();
|
2026-05-17 00:27:45 +03:30
|
|
|
this.toggleBodyScrollLock(this.visible);
|
2026-05-13 13:44:33 +03:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ngOnChanges(changes: SimpleChanges) {
|
|
|
|
|
if (changes['visible'] && this.visible) {
|
|
|
|
|
this.syncZIndex();
|
|
|
|
|
}
|
2026-05-17 00:27:45 +03:30
|
|
|
if (changes['visible']) {
|
|
|
|
|
this.toggleBodyScrollLock(this.visible);
|
|
|
|
|
}
|
2026-05-13 10:59:51 +03:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ngOnDestroy() {
|
2026-05-17 00:27:45 +03:30
|
|
|
this.toggleBodyScrollLock(false);
|
2026-05-13 13:44:33 +03:30
|
|
|
this.removeDrawer();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private removeDrawer() {
|
2026-05-13 10:59:51 +03:30
|
|
|
const host = this.elementRef.nativeElement;
|
|
|
|
|
if (this.originalParent) {
|
|
|
|
|
this.renderer.appendChild(this.originalParent, host);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-13 10:39:43 +03:30
|
|
|
onVisibilityChange(nextValue: boolean) {
|
|
|
|
|
this.visible = nextValue;
|
|
|
|
|
this.visibleChange.emit(nextValue);
|
2026-05-17 00:27:45 +03:30
|
|
|
this.toggleBodyScrollLock(nextValue);
|
2026-05-13 13:44:33 +03:30
|
|
|
if (nextValue) {
|
|
|
|
|
this.syncZIndex();
|
|
|
|
|
}
|
2026-05-13 10:39:43 +03:30
|
|
|
if (!nextValue) {
|
|
|
|
|
this.onHide.emit();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onMaskClick() {
|
|
|
|
|
if (this.modal && this.closable) {
|
|
|
|
|
this.onVisibilityChange(false);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-05-13 13:44:33 +03:30
|
|
|
|
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.setStyle(body, 'overflow', this.previousBodyOverflow);
|
|
|
|
|
this.renderer.setStyle(body, 'touch-action', this.previousBodyTouchAction);
|
|
|
|
|
this.previousBodyOverflow = '';
|
|
|
|
|
this.previousBodyTouchAction = '';
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-13 13:44:33 +03:30
|
|
|
private syncZIndex() {
|
|
|
|
|
const host = this.elementRef.nativeElement;
|
|
|
|
|
const siblingDrawers = Array.from(
|
2026-05-21 21:35:34 +03:30
|
|
|
this.document.body.querySelectorAll('.p-drawer')
|
2026-05-13 13:44:33 +03:30
|
|
|
) 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}`);
|
|
|
|
|
}
|
2026-05-13 10:39:43 +03:30
|
|
|
}
|