From b8684067173b95e2d39ea955f5e0bd5d2fc2d9ab Mon Sep 17 00:00:00 2001 From: mertsincan Date: Tue, 25 Oct 2022 13:56:23 +0100 Subject: [PATCH 1/4] Refactor #12031 - Component: Responsive Overlay --- src/app/components/api/overlayoptions.ts | 37 ++ src/app/components/api/primengconfig.ts | 12 +- src/app/components/api/public_api.ts | 15 +- src/app/components/dom/domhandler.ts | 32 +- src/app/components/dropdown/dropdown.css | 12 +- src/app/components/dropdown/dropdown.ts | 207 ++++------- src/app/components/overlay/overlay.css | 36 +- src/app/components/overlay/overlay.ts | 439 +++++++++++++++-------- src/app/components/scroller/scroller.ts | 111 +++--- 9 files changed, 534 insertions(+), 367 deletions(-) create mode 100644 src/app/components/api/overlayoptions.ts diff --git a/src/app/components/api/overlayoptions.ts b/src/app/components/api/overlayoptions.ts new file mode 100644 index 00000000000..290efedfde9 --- /dev/null +++ b/src/app/components/api/overlayoptions.ts @@ -0,0 +1,37 @@ +import { AnimationEvent } from '@angular/animations'; + +export interface OverlayOptions { + style?: any; + styleClass?: string; + appendTo?: 'body' | HTMLElement | undefined; + autoZIndex?: boolean; + baseZIndex?: number; + showTransitionOptions?: string; + hideTransitionOptions?: string; + listener?: any; + responsive?: ResponsiveOverlayOptions | undefined; + onShow?: (event?: OverlayOnShowEvent) => void; + onHide?: (event?: OverlayOnHideEvent) => void; + onAnimationStart?: (event?: AnimationEvent) => void; + onAnimationDone?: (event?: AnimationEvent) => void; +} + +export type ResponsiveOverlayDirectionType = 'start' | 'center' | 'end' | undefined; + +export interface ResponsiveOverlayOptions { + style?: any; + styleClass?: string; + breakpoint?: string; + media?: string; + direction?: ResponsiveOverlayDirectionType; +} + +export type OverlayModeType = 'modal' | 'overlay' | undefined; + +export interface OverlayOnShowEvent { + container?: HTMLElement | undefined; + target?: HTMLElement | undefined; + mode?: OverlayModeType; +} + +export interface OverlayOnHideEvent extends OverlayOnShowEvent {} diff --git a/src/app/components/api/primengconfig.ts b/src/app/components/api/primengconfig.ts index c11d1f1c426..f9a57e0be95 100644 --- a/src/app/components/api/primengconfig.ts +++ b/src/app/components/api/primengconfig.ts @@ -1,17 +1,19 @@ import { Injectable } from '@angular/core'; import { Subject } from 'rxjs'; import { FilterMatchMode } from './filtermatchmode'; +import { OverlayOptions } from './overlayoptions'; import { Translation } from './translation'; -interface OverlayOptions { - breakpoint: number; -} - @Injectable({ providedIn: 'root' }) export class PrimeNGConfig { ripple: boolean = false; - overlayOptions: OverlayOptions; + overlayOptions: OverlayOptions = { + responsive: { + breakpoint: '640px', + direction: 'end' + } + }; filterMatchModeOptions = { text: [FilterMatchMode.STARTS_WITH, FilterMatchMode.CONTAINS, FilterMatchMode.NOT_CONTAINS, FilterMatchMode.ENDS_WITH, FilterMatchMode.EQUALS, FilterMatchMode.NOT_EQUALS], diff --git a/src/app/components/api/public_api.ts b/src/app/components/api/public_api.ts index cafcad827d8..62748a30801 100644 --- a/src/app/components/api/public_api.ts +++ b/src/app/components/api/public_api.ts @@ -1,28 +1,29 @@ -export * from './primengconfig'; -export * from './translation'; -export * from './translationkeys'; export * from './blockableui'; -export * from './confirmation'; export * from './confirmaeventtype'; +export * from './confirmation'; export * from './confirmationservice'; +export * from './contextmenuservice'; +export * from './filtermatchmode'; export * from './filtermetadata'; +export * from './filteroperator'; export * from './filterservice'; -export * from './contextmenuservice'; export * from './lazyloadevent'; export * from './megamenuitem'; export * from './menuitem'; export * from './message'; export * from './messageservice'; +export * from './overlayoptions'; export * from './overlayservice'; export * from './primeicons'; -export * from './filtermatchmode'; -export * from './filteroperator'; +export * from './primengconfig'; export * from './selectitem'; export * from './selectitemgroup'; export * from './shared'; export * from './sortevent'; export * from './sortmeta'; export * from './tablestate'; +export * from './translation'; +export * from './translationkeys'; export * from './treedragdropservice'; export * from './treenode'; export * from './treenodedragevent'; diff --git a/src/app/components/dom/domhandler.ts b/src/app/components/dom/domhandler.ts index a818a2c9c76..b1abae9c7ce 100755 --- a/src/app/components/dom/domhandler.ts +++ b/src/app/components/dom/domhandler.ts @@ -82,6 +82,24 @@ export class DomHandler { return -1; } + public static appendOverlay(overlay: any, target: any, appendTo: any = 'self') { + if (appendTo !== 'self' && overlay && target) { + this.appendChild(overlay, target); + } + } + + public static alignOverlay(overlay: any, target: any, appendTo: any = 'self', calculateMinWidth: boolean = true) { + if (overlay && target) { + calculateMinWidth && (overlay.style.minWidth || (overlay.style.minWidth = DomHandler.getOuterWidth(target) + 'px')); + + if (appendTo === 'self') { + this.relativePosition(overlay, target); + } else { + this.absolutePosition(overlay, target); + } + } + } + public static relativePosition(element: any, target: any): void { let elementDimensions = element.offsetParent ? { width: element.offsetWidth, height: element.offsetHeight } : this.getHiddenElementDimensions(element); const targetHeight = target.offsetHeight; @@ -550,7 +568,19 @@ export class DomHandler { } public static isHidden(element: HTMLElement): boolean { - return element.offsetParent === null; + return !element || element.offsetParent === null; + } + + public static isVisible(element: HTMLElement) { + return element && element.offsetParent != null; + } + + public static isExist(element: HTMLElement) { + return element !== null && typeof element !== 'undefined' && element.nodeName && element.parentNode; + } + + public static focus(element: HTMLElement, options?: FocusOptions): void { + element && document.activeElement !== element && element.focus(options); } public static getFocusableElements(element: HTMLElement) { diff --git a/src/app/components/dropdown/dropdown.css b/src/app/components/dropdown/dropdown.css index 5f529c53717..992abffdf93 100755 --- a/src/app/components/dropdown/dropdown.css +++ b/src/app/components/dropdown/dropdown.css @@ -37,16 +37,6 @@ input.p-dropdown-label { cursor: default; } -.p-dropdown .p-dropdown-panel { - min-width: 100%; -} - -.p-dropdown-panel { - position: absolute; - top: 0; - left: 0; -} - .p-dropdown-items-wrapper { overflow: auto; } @@ -85,4 +75,4 @@ input.p-dropdown-label { .p-fluid .p-dropdown .p-dropdown-label { width: 1%; -} \ No newline at end of file +} diff --git a/src/app/components/dropdown/dropdown.ts b/src/app/components/dropdown/dropdown.ts index 43f63ce065d..0ecbdd39477 100755 --- a/src/app/components/dropdown/dropdown.ts +++ b/src/app/components/dropdown/dropdown.ts @@ -1,39 +1,38 @@ +import { AnimationEvent } from '@angular/animations'; +import { CommonModule } from '@angular/common'; import { - NgModule, - Component, - ElementRef, - OnInit, - AfterViewInit, AfterContentInit, AfterViewChecked, - OnDestroy, + AfterViewInit, + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + ContentChildren, + ElementRef, + EventEmitter, + forwardRef, Input, + NgModule, + NgZone, + OnDestroy, + OnInit, Output, - Renderer2, - EventEmitter, - ContentChildren, QueryList, - ViewChild, + Renderer2, TemplateRef, - forwardRef, - ChangeDetectorRef, - NgZone, - ViewRef, - ChangeDetectionStrategy, - ViewEncapsulation + ViewChild, + ViewEncapsulation, + ViewRef } from '@angular/core'; -import { AnimationEvent } from '@angular/animations'; -import { CommonModule } from '@angular/common'; -import { PrimeNGConfig, SelectItem, TranslationKeys } from 'primeng/api'; -import { SharedModule, PrimeTemplate, FilterService } from 'primeng/api'; -import { DomHandler, ConnectedOverlayScrollHandler } from 'primeng/dom'; -import { ObjectUtils, UniqueComponentId } from 'primeng/utils'; -import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms'; -import { TooltipModule } from 'primeng/tooltip'; -import { Scroller, ScrollerModule, ScrollerOptions } from 'primeng/scroller'; -import { RippleModule } from 'primeng/ripple'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { FilterService, OverlayOptions, PrimeNGConfig, PrimeTemplate, SelectItem, SharedModule, TranslationKeys } from 'primeng/api'; import { AutoFocusModule } from 'primeng/autofocus'; -import { Overlay, OverlayModule } from '../overlay/overlay'; +import { DomHandler } from 'primeng/dom'; +import { Overlay, OverlayModule } from 'primeng/overlay'; +import { RippleModule } from 'primeng/ripple'; +import { Scroller, ScrollerModule, ScrollerOptions } from 'primeng/scroller'; +import { TooltipModule } from 'primeng/tooltip'; +import { ObjectUtils, UniqueComponentId } from 'primeng/utils'; export const DROPDOWN_VALUE_ACCESSOR: any = { provide: NG_VALUE_ACCESSOR, @@ -148,7 +147,6 @@ export class DropdownItem { [attr.placeholder]="placeholder" aria-haspopup="listbox" [attr.aria-expanded]="overlayVisible" - (click)="onEditableInputClick()" (input)="onEditableInputChange($event)" (focus)="onEditableInputFocus($event)" (blur)="onInputBlur($event)" @@ -159,14 +157,15 @@ export class DropdownItem {
@@ -348,13 +347,7 @@ export class Dropdown implements OnInit, AfterViewInit, AfterContentInit, AfterV @Input() virtualScrollOptions: ScrollerOptions; - @Input() autoZIndex: boolean = true; - - @Input() baseZIndex: number = 0; - - @Input() showTransitionOptions: string = '.12s cubic-bezier(0, 0, 0.2, 1)'; - - @Input() hideTransitionOptions: string = '.1s linear'; + @Input() overlayOptions: OverlayOptions; @Input() ariaFilterLabel: string; @@ -441,6 +434,46 @@ export class Dropdown implements OnInit, AfterViewInit, AfterContentInit, AfterV console.warn('The itemSize property is deprecated, use virtualScrollItemSize property instead.'); } + /* @deprecated */ + _autoZIndex: boolean; + @Input() get autoZIndex(): boolean { + return this._autoZIndex; + } + set autoZIndex(val: boolean) { + this._autoZIndex = val; + console.warn('The autoZIndex property is deprecated since v14.2.0, use overlayOptions property instead.'); + } + + /* @deprecated */ + _baseZIndex: number; + @Input() get baseZIndex(): number { + return this._baseZIndex; + } + set baseZIndex(val: number) { + this._baseZIndex = val; + console.warn('The baseZIndex property is deprecated since v14.2.0, use overlayOptions property instead.'); + } + + /* @deprecated */ + _showTransitionOptions: string; + @Input() get showTransitionOptions(): string { + return this._showTransitionOptions; + } + set showTransitionOptions(val: string) { + this._showTransitionOptions = val; + console.warn('The showTransitionOptions property is deprecated since v14.2.0, use overlayOptions property instead.'); + } + + /* @deprecated */ + _hideTransitionOptions: boolean; + @Input() get hideTransitionOptions(): boolean { + return this._hideTransitionOptions; + } + set hideTransitionOptions(val: boolean) { + this._hideTransitionOptions = val; + console.warn('The hideTransitionOptions property is deprecated since v14.2.0, use overlayOptions property instead.'); + } + itemsWrapper: HTMLDivElement; itemTemplate: TemplateRef; @@ -481,10 +514,6 @@ export class Dropdown implements OnInit, AfterViewInit, AfterContentInit, AfterV overlayVisible: boolean; - documentClickListener: any; - - scrollHandler: any; - optionsChanged: boolean; panel: HTMLDivElement; @@ -507,12 +536,8 @@ export class Dropdown implements OnInit, AfterViewInit, AfterContentInit, AfterV currentSearchChar: string; - documentResizeListener: any; - preventModelTouched: boolean; - preventDocumentDefault: boolean; - id: string = UniqueComponentId(); labelId: string; @@ -676,7 +701,7 @@ export class Dropdown implements OnInit, AfterViewInit, AfterContentInit, AfterV setTimeout(() => { this.hide(); - }, 150); + }, 1); } selectItem(event, option) { @@ -784,18 +809,10 @@ export class Dropdown implements OnInit, AfterViewInit, AfterContentInit, AfterV return DomHandler.hasClass(event.target, 'p-dropdown-clear-icon') || event.target.isSameNode(this.accessibleViewChild.nativeElement) || (this.editableInputViewChild && event.target.isSameNode(this.editableInputViewChild.nativeElement)); } - isOutsideClicked(event: Event): boolean { - return !(this.el.nativeElement.isSameNode(event.target) || this.el.nativeElement.contains(event.target) || (this.overlayViewChild && this.overlayViewChild.el.nativeElement.contains(event.target))); - } - isEmpty() { return !this.optionsToDisplay || (this.optionsToDisplay && this.optionsToDisplay.length === 0); } - onEditableInputClick() { - this.bindDocumentClickListener(); - } - onEditableInputFocus(event) { this.focused = true; this.hide(); @@ -814,7 +831,6 @@ export class Dropdown implements OnInit, AfterViewInit, AfterContentInit, AfterV show() { this.overlayVisible = true; - this.preventDocumentDefault = true; this.cd.markForCheck(); } @@ -822,10 +838,6 @@ export class Dropdown implements OnInit, AfterViewInit, AfterContentInit, AfterV if (event.toState === 'visible') { this.itemsWrapper = DomHandler.findSingle(this.overlayViewChild.el.nativeElement, this.virtualScroll ? '.p-scroller' : '.p-dropdown-items-wrapper'); this.virtualScroll && this.scroller.setContentEl(this.itemsViewChild.nativeElement); - this.overlayViewChild.appendOverlay(); - this.overlayViewChild.alignOverlay(); - this.bindDocumentClickListener(); - this.bindScrollListener(); if (this.options && this.options.length) { if (this.virtualScroll) { @@ -858,18 +870,14 @@ export class Dropdown implements OnInit, AfterViewInit, AfterContentInit, AfterV } } - restoreOverlayAppend() { - if (this.overlayViewChild && this.appendTo) { - this.el.nativeElement.appendChild(this.overlayViewChild.el.nativeElement); - } - } - hide() { this.overlayVisible = false; if (this.filter && this.resetFilterOnHide) { this.resetFilter(); } + + this.scroller && this.scroller.ngOnDestroy(); this.cd.markForCheck(); } @@ -1257,63 +1265,6 @@ export class Dropdown implements OnInit, AfterViewInit, AfterContentInit, AfterV this.applyFocus(); } - bindDocumentClickListener() { - if (!this.documentClickListener) { - const documentTarget: any = this.el ? this.el.nativeElement.ownerDocument : 'document'; - - this.documentClickListener = this.renderer.listen(documentTarget, 'click', (event) => { - if (!this.preventDocumentDefault && this.isOutsideClicked(event)) { - this.hide(); - this.unbindDocumentClickListener(); - } - this.preventDocumentDefault = false; - }); - } - } - - unbindDocumentClickListener() { - if (this.documentClickListener) { - this.documentClickListener(); - this.documentClickListener = null; - } - } - - bindDocumentResizeListener() { - this.documentResizeListener = this.onWindowResize.bind(this); - window.addEventListener('resize', this.documentResizeListener); - } - - unbindDocumentResizeListener() { - if (this.documentResizeListener) { - window.removeEventListener('resize', this.documentResizeListener); - this.documentResizeListener = null; - } - } - - onWindowResize() { - if (this.overlayVisible && !DomHandler.isTouchDevice()) { - this.hide(); - } - } - - bindScrollListener() { - if (!this.scrollHandler) { - this.scrollHandler = new ConnectedOverlayScrollHandler(this.containerViewChild.nativeElement, (event: any) => { - if (this.overlayVisible) { - this.hide(); - } - }); - } - - this.scrollHandler.bindScrollListener(); - } - - unbindScrollListener() { - if (this.scrollHandler) { - this.scrollHandler.unbindScrollListener(); - } - } - clear(event: Event) { this.value = null; this.onModelChange(this.value); @@ -1327,19 +1278,11 @@ export class Dropdown implements OnInit, AfterViewInit, AfterContentInit, AfterV } onOverlayHide() { - this.unbindDocumentClickListener(); - this.unbindDocumentResizeListener(); - this.unbindScrollListener(); this.itemsWrapper = null; this.onModelTouched(); } ngOnDestroy() { - if (this.scrollHandler) { - this.scrollHandler.destroy(); - this.scrollHandler = null; - } - this.restoreOverlayAppend(); this.onOverlayHide(); } } diff --git a/src/app/components/overlay/overlay.css b/src/app/components/overlay/overlay.css index a7fc4f67c0d..c371df487fd 100644 --- a/src/app/components/overlay/overlay.css +++ b/src/app/components/overlay/overlay.css @@ -1,37 +1,35 @@ -.p-overlay-mask { +.p-overlay { + position: absolute; + top: 0; + left: 0; +} + +.p-overlay-modal { + display: flex; + align-items: center; + justify-content: center; position: fixed; top: 0; left: 0; width: 100%; height: 100%; - display: flex; - justify-content: center; - align-items: center; } -.p-overlay-responsive { - overflow: hidden; +.p-overlay-modal > .p-overlay { position: static; - height: fit-content; - width: inherit; + z-index: 1; + width: 90%; } -.p-overlay-panel-start { +/* Position */ +.p-overlay-start { align-items: flex-start; } -.p-overlay-panel-center { +.p-overlay-center { align-items: center; } -.p-overlay-panel-end { +.p-overlay-end { align-items: flex-end; } - -.p-overlay { - position: absolute; -} - -.p-overlay-panel-static { - position: static; -} \ No newline at end of file diff --git a/src/app/components/overlay/overlay.ts b/src/app/components/overlay/overlay.ts index e0a52dd1179..bef4bc65cb8 100644 --- a/src/app/components/overlay/overlay.ts +++ b/src/app/components/overlay/overlay.ts @@ -1,11 +1,17 @@ -import { NgModule, Component, Input, OnDestroy, Renderer2, ElementRef, ChangeDetectionStrategy, ViewEncapsulation, ViewChild, Inject, Output, EventEmitter, ChangeDetectorRef } from '@angular/core'; +import { animate, animation, AnimationEvent, style, transition, trigger, useAnimation } from '@angular/animations'; import { CommonModule, DOCUMENT } from '@angular/common'; -import { DomHandler } from 'primeng/dom'; -import { SharedModule, PrimeNGConfig, OverlayService } from 'primeng/api'; -import { RippleModule } from 'primeng/ripple'; -import { trigger, style, transition, animate, animation, useAnimation } from '@angular/animations'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, forwardRef, Inject, Input, NgModule, OnDestroy, Output, Renderer2, ViewChild, ViewEncapsulation } from '@angular/core'; +import { NG_VALUE_ACCESSOR } from '@angular/forms'; +import { OverlayOptions, OverlayService, PrimeNGConfig, SharedModule } from 'primeng/api'; +import { ConnectedOverlayScrollHandler, DomHandler } from 'primeng/dom'; import { ZIndexUtils } from 'primeng/utils'; +export const OVERLAY_VALUE_ACCESSOR: any = { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => Overlay), + multi: true +}; + const showAnimation = animation([style({ transform: '{{transform}}', opacity: 0 }), animate('{{showTransitionParams}}')]); const hideAnimation = animation([animate('{{hideTransitionParams}}', style({ transform: '{{transform}}', opacity: 0 }))]); @@ -13,200 +19,345 @@ const hideAnimation = animation([animate('{{hideTransitionParams}}', style({ tra @Component({ selector: 'p-overlay', template: ` -
-
-
- -
+ +
+ +
+
+ +
+
-
+ `, animations: [trigger('overlayAnimation', [transition(':enter', [useAnimation(showAnimation)]), transition(':leave', [useAnimation(hideAnimation)])])], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, + providers: [OVERLAY_VALUE_ACCESSOR], styleUrls: ['./overlay.css'], host: { class: 'p-element' } }) export class Overlay implements OnDestroy { - @Input() baseZIndex: number = 0; - - @Input() showTransitionOptions: string = '.12s cubic-bezier(0, 0, 0.2, 1)'; + @Input() get visible(): any { + return this._visible; + } + set visible(value: any) { + this._visible = value; - @Input() hideTransitionOptions: string = '.1s linear'; + if (this._visible && !this.modalVisible) { + this.modalVisible = true; + } + } - @Input() container: ElementRef; + @Input() get style(): any { + return this._style || (this.modal ? this.overlayResponsiveOptions?.style : this.overlayOptions?.style); + } + set style(value: any) { + this._style = value; + } - @Input() autoZIndex: boolean; + @Input() get styleClass(): string | undefined { + return this._styleClass || (this.modal ? this.overlayResponsiveOptions?.styleClass : this.overlayOptions?.styleClass); + } + set styleClass(value: string) { + this._styleClass = value; + } - @Output() onAnimationStart: EventEmitter = new EventEmitter(); + @Input() get appendTo(): any { + return this._appendTo || this.overlayOptions?.appendTo; + } + set appendTo(value: any) { + this._appendTo = value; + } - @Output() onAnimationEnd: EventEmitter = new EventEmitter(); + @Input() get autoZIndex(): boolean | undefined { + const value = this._autoZIndex || this.overlayOptions?.autoZIndex; + return value === undefined ? true : value; + } + set autoZIndex(value: boolean) { + this._autoZIndex = value; + } - @Output() onOverlayHide: EventEmitter = new EventEmitter(); + @Input() get baseZIndex(): number | undefined { + const value = this._baseZIndex || this.overlayOptions?.baseZIndex; + return value === undefined ? 0 : value; + } + set baseZIndex(value: number) { + this._baseZIndex = value; + } - @ViewChild('overlay') overlayViewChild: ElementRef; + @Input() get showTransitionOptions(): string { + const value = this._showTransitionOptions || this.overlayOptions?.showTransitionOptions; + return value === undefined ? '.12s cubic-bezier(0, 0, 0.2, 1)' : value; + } + set showTransitionOptions(value: string) { + this._showTransitionOptions = value; + } - @ViewChild('content') contentViewChild: ElementRef; - - @ViewChild('mask') maskViewChild: ElementRef; - - @Input() set overlayDirection(value: string) { - if (value && this.viewport.width < this.overlayBreakpoints) { - this._overlayDirection = value; - - switch (value) { - case 'start': - this.transformOptions = 'translate3d(0px, -100%, 0px)'; - break; - case 'end': - this.transformOptions = 'translate3d(0px, 100%, 0px)'; - break; - case 'center': - this.transformOptions = 'scale(0.8)'; - break; - } - } else { - this.transformOptions = 'scaleY(0.8)'; - } + @Input() get hideTransitionOptions(): string { + const value = this._hideTransitionOptions || this.overlayOptions?.hideTransitionOptions; + return value === undefined ? '.1s linear' : value; + } + set hideTransitionOptions(value: string) { + this._hideTransitionOptions = value; } - get overlayDirection(): string { - return this._overlayDirection; + @Input() get listener(): any { + return this._listener || this.overlayOptions?.listener; + } + set listener(value: any) { + this._listener = value; } - @Input() set appendTo(val) { - this._appendTo = val; + @Input() get options(): OverlayOptions | undefined { + return this._options; + } + set options(val: OverlayOptions) { + this._options = val; } - get appendTo(): any { - return this._appendTo; + @Output() onShow: EventEmitter = new EventEmitter(); + + @Output() onHide: EventEmitter = new EventEmitter(); + + @Output() onAnimationStart: EventEmitter = new EventEmitter(); + + @Output() onAnimationDone: EventEmitter = new EventEmitter(); + + @ViewChild('modal') modalViewChild: ElementRef; + + @ViewChild('overlay') overlayViewChild: ElementRef; + + _visible: boolean; + + _style: any; + + _styleClass: string; + + _appendTo: 'body' | HTMLElement | undefined; + + _autoZIndex: boolean; + + _baseZIndex: number; + + _showTransitionOptions: string; + + _hideTransitionOptions: string; + + _listener: any; + + _options: OverlayOptions | undefined; + + modalVisible: boolean; + + scrollHandler: any; + + documentClickListener: any; + + documentResizeListener: any; + + private window: Window; + + protected transformOptions: any = { + default: 'scaleY(0.8)', + start: 'translate3d(0px, -100%, 0px)', + center: 'scale(0.7)', + end: 'translate3d(0px, 100%, 0px)' + }; + + get modal() { + return this.overlayResponsiveOptions && this.window.matchMedia(this.overlayResponsiveOptions.media?.replace('@media', '') || `(max-width: ${this.overlayResponsiveOptions.breakpoint})`).matches; } - @Input() set visible(value: boolean) { - this._visible = value; + get mode() { + return this.modal ? 'modal' : 'overlay'; } - get visible(): boolean { - return this._visible; + get overlayOptions() { + return { ...this.config?.overlayOptions, ...this.options }; // TODO: Improve performance } - get overlayBreakpoints(): any { - return this.config.overlayOptions ? this.config.overlayOptions.breakpoint : null; + get overlayResponsiveOptions() { + return { ...this.config?.overlayOptions?.responsive, ...this.options?.responsive }; // TODO: Improve performance } - get viewport(): any { - this._viewport = DomHandler.getViewport(); - return this._viewport; + get overlayResponsiveDirection() { + return this.overlayResponsiveOptions?.direction; } - _visible: boolean; + get modalEl() { + return this.modalViewChild?.nativeElement; + } - _overlayDirection: string; + get overlayEl() { + return this.overlayViewChild?.nativeElement; + } - _viewport: any; + get containerEl() { + return this.modal ? this.modalEl : this.overlayEl; + } - _overlayBreakpoints: number; + get targetEl() { + return this.el?.nativeElement?.parentElement; + } - _appendTo: any; + constructor(@Inject(DOCUMENT) private document: Document, public el: ElementRef, public renderer: Renderer2, private config: PrimeNGConfig, public overlayService: OverlayService, private cd: ChangeDetectorRef) { + this.window = this.document.defaultView; + } - transformOptions: string = 'scaleY(0.8)'; + show(container?: HTMLElement, isFocus: boolean = false) { + this.visible = true; + this.handleEvents('onShow', { container: container || this.containerEl, target: this.targetEl, mode: this.mode }); - documentResizeListener: any; + isFocus && DomHandler.focus(this.targetEl); + this.modal && DomHandler.addClass(this.document?.body, 'p-overflow-hidden'); + } - responsive: boolean; + hide(container?: HTMLElement, isFocus: boolean = false) { + this.visible = false; + this.handleEvents('onHide', { container: container || this.containerEl, target: this.targetEl, mode: this.mode }); - constructor(@Inject(DOCUMENT) private document: Document, public el: ElementRef, public renderer: Renderer2, private config: PrimeNGConfig, public overlayService: OverlayService, private cd: ChangeDetectorRef) {} + isFocus && DomHandler.focus(this.targetEl); + this.modal && DomHandler.removeClass(this.document?.body, 'p-overflow-hidden'); + } - ngOnInit() { - this.bindDocumentResizeListener(); + alignOverlay() { + DomHandler.alignOverlay(this.overlayEl, this.targetEl, this.appendTo); } onOverlayClick(event: MouseEvent) { this.overlayService.add({ originalEvent: event, - target: this.el.nativeElement + target: this.targetEl }); } - onOverlayAnimationStart(event) { - this.onAnimationStart.emit(event); - } + onOverlayAnimationStart(event: AnimationEvent) { + switch (event.toState) { + case 'visible': + if (this.autoZIndex) { + ZIndexUtils.set('overlay', this.containerEl, this.baseZIndex + this.config?.zIndex[this.mode]); + } + + DomHandler.appendOverlay(this.containerEl, this.appendTo === 'body' ? this.document.body : this.appendTo, this.appendTo); + this.alignOverlay(); + this.bindListeners(); + + break; + + case 'void': + DomHandler.appendOverlay(this.containerEl, this.targetEl, this.appendTo); + this.modal && DomHandler.addClass(this.containerEl, 'p-component-overlay-leave'); + this.unbindListeners(); - onOverlayAnimationEnd(event) { - if (event.toState === 'void') { - this.destroyOverlay(); + break; } - this.onAnimationEnd.emit(event); + + this.handleEvents('onAnimationStart', event); } - appendOverlay() { - if (this.autoZIndex) { - ZIndexUtils.set('modal', this.el.nativeElement, this.baseZIndex + this.config.zIndex.modal); - } - if (this.viewport.width < this.overlayBreakpoints) { - this.responsive = true; - DomHandler.addClass(this.document.body, 'p-overflow-hidden'); - DomHandler.addClass(this.overlayViewChild.nativeElement, 'p-overlay-responsive'); - DomHandler.addClass(this.contentViewChild.nativeElement.firstChild, 'p-overlay-panel-static'); - } else { - this.responsive = false; - if (this.appendTo) { - if (this.appendTo === 'body') { - this.document.body.appendChild(this.overlayViewChild.nativeElement); - } else { - DomHandler.appendChild(this.overlayViewChild.nativeElement, this.appendTo); - } - } + onOverlayAnimationDone(event: AnimationEvent) { + const container = this.containerEl || (this.modal ? event.element.parentElement : event.element); + + switch (event.toState) { + case 'visible': + this.show(container, true); + + break; + + case 'void': + ZIndexUtils.clear(container); + this.modalVisible = false; + + this.hide(container, true); + break; } - if (!this.contentViewChild.nativeElement.style.minWidth) { - this.contentViewChild.nativeElement.style.minWidth = DomHandler.getWidth(this.container) + 'px'; + + this.handleEvents('onAnimationDone', event); + } + + handleEvents(name: string, params: any) { + this[name].emit(params); + this.options && this.options[name] && this.options[name](params); + this.config?.overlayOptions && this.config?.overlayOptions[name] && this.config?.overlayOptions[name](params); + } + + bindListeners() { + this.bindScrollListener(); + this.bindDocumentClickListener(); + this.bindDocumentResizeListener(); + } + + unbindListeners() { + this.unbindScrollListener(); + this.unbindDocumentClickListener(); + this.unbindDocumentResizeListener(); + } + + bindScrollListener() { + if (!this.scrollHandler) { + this.scrollHandler = new ConnectedOverlayScrollHandler(this.targetEl, (event) => { + const valid = this.listener ? this.listener(event, { type: 'scroll', valid: true }) : true; + + valid && this.hide(event, true); + }); } + + this.scrollHandler.bindScrollListener(); } - alignOverlay() { - if (this.overlayViewChild) { - if (this.viewport.width < this.overlayBreakpoints) { - switch (this.overlayDirection) { - case 'start': - DomHandler.addClass(this.maskViewChild.nativeElement, 'p-overlay-panel-start'); - break; - - case 'center': - DomHandler.addClass(this.maskViewChild.nativeElement, 'p-overlay-panel-center'); - break; - - case 'end': - DomHandler.addClass(this.maskViewChild.nativeElement, 'p-overlay-panel-end'); - break; - } - } else { - if (this.appendTo) { - DomHandler.absolutePosition(this.overlayViewChild.nativeElement, this.container); - } else { - DomHandler.relativePosition(this.overlayViewChild.nativeElement, this.container); - } - } + unbindScrollListener() { + if (this.scrollHandler) { + this.scrollHandler.unbindScrollListener(); } } - blockScroll() { - DomHandler.addClass(this.document.body, 'p-overflow-hidden'); + bindDocumentClickListener() { + if (!this.documentClickListener) { + this.documentClickListener = this.renderer.listen(this.document, 'click', (event) => { + const isOutsideClicked = this.targetEl && !(this.targetEl.isSameNode(event.target) || this.targetEl.contains(event.target) || (this.overlayEl && this.overlayEl.contains(event.target))); + const valid = this.listener ? this.listener(event, { type: 'outside', valid: event.which !== 3 && isOutsideClicked }) : isOutsideClicked; + + valid && this.hide(event); + }); + } } - unblockScroll() { - DomHandler.removeClass(this.document.body, 'p-overflow-hidden'); + unbindDocumentClickListener() { + if (this.documentClickListener) { + this.documentClickListener(); + this.documentClickListener = null; + } } bindDocumentResizeListener() { if (!this.documentResizeListener) { - this.documentResizeListener = this.renderer.listen(window, 'resize', this.onWindowResize.bind(this)); + this.documentResizeListener = this.renderer.listen('window', 'resize', (event) => { + const valid = this.listener ? this.listener(event, { type: 'resize', valid: !DomHandler.isTouchDevice() }) : !DomHandler.isTouchDevice(); + + valid && this.hide(event, true); + }); } } @@ -217,28 +368,20 @@ export class Overlay implements OnDestroy { } } - onWindowResize() { - if (this.visible) { - this.visible = false; - this.onOverlayHide.emit({ visible: this.visible }); - this.cd.markForCheck(); - } - } - - destroyOverlay() { - this.unblockScroll(); - this.unbindDocumentResizeListener(); + ngOnDestroy() { + this.hide(this.containerEl, true); - if (this.overlayViewChild && this.overlayViewChild.nativeElement) { - ZIndexUtils.clear(this.el.nativeElement); - this.overlayViewChild = null; + if (this.containerEl) { + DomHandler.appendOverlay(this.containerEl, this.targetEl, this.appendTo); + ZIndexUtils.clear(this.containerEl); } - this.onOverlayHide.emit({ visible: this.visible }); - } + if (this.scrollHandler) { + this.scrollHandler.destroy(); + this.scrollHandler = null; + } - ngOnDestroy() { - this.destroyOverlay(); + this.unbindListeners(); } } diff --git a/src/app/components/scroller/scroller.ts b/src/app/components/scroller/scroller.ts index 0bd48dfd20d..d5ff44846ed 100644 --- a/src/app/components/scroller/scroller.ts +++ b/src/app/components/scroller/scroller.ts @@ -1,27 +1,27 @@ +import { CommonModule } from '@angular/common'; import { - NgModule, - Component, - Input, - ElementRef, - ViewChild, + AfterContentInit, + AfterViewChecked, ChangeDetectionStrategy, - ViewEncapsulation, ChangeDetectorRef, - AfterContentInit, + Component, ContentChildren, - QueryList, - TemplateRef, - Output, + ElementRef, EventEmitter, - SimpleChanges, - OnInit, - AfterViewChecked, + Input, + NgModule, + NgZone, OnDestroy, - NgZone + OnInit, + Output, + QueryList, + SimpleChanges, + TemplateRef, + ViewChild, + ViewEncapsulation } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { DomHandler } from 'primeng/dom'; import { PrimeTemplate } from 'primeng/api'; +import { DomHandler } from 'primeng/dom'; export type ScrollerToType = 'to-start' | 'to-end' | undefined; @@ -389,16 +389,7 @@ export class Scroller implements OnInit, AfterContentInit, AfterViewChecked, OnD return this._columns; } - constructor(private cd: ChangeDetectorRef, private zone: NgZone) { - if (!this._disabled) { - this.zone.runOutsideAngular(() => { - this.windowResizeListener = this.onWindowResize.bind(this); - - window.addEventListener('resize', this.windowResizeListener); - window.addEventListener('orientationchange', this.windowResizeListener); - }); - } - } + constructor(private cd: ChangeDetectorRef, private zone: NgZone) {} ngOnInit() { this.setInitialState(); @@ -416,11 +407,6 @@ export class Scroller implements OnInit, AfterContentInit, AfterViewChecked, OnD } } - if (this.initialized) { - const isChanged = !isLoadingChanged && (simpleChanges.items || simpleChanges.itemSize || simpleChanges.scrollHeight || simpleChanges.scrollWidth); - isChanged && this.init(); - } - if (simpleChanges.orientation) { this.lastScrollPos = this.both ? { top: 0, left: 0 } : 0; } @@ -438,12 +424,18 @@ export class Scroller implements OnInit, AfterContentInit, AfterViewChecked, OnD if (this.lazy && previousValue?.loading !== currentValue?.loading && currentValue?.loading !== this.d_loading) { this.d_loading = currentValue.loading; + isLoadingChanged = true; } if (previousValue?.numToleratedItems !== currentValue?.numToleratedItems && currentValue?.numToleratedItems !== this.d_numToleratedItems) { this.d_numToleratedItems = currentValue.numToleratedItems; } } + + if (this.initialized) { + const isChanged = !isLoadingChanged && (simpleChanges.items || simpleChanges.itemSize || simpleChanges.scrollHeight || simpleChanges.scrollWidth); + isChanged && this.init(); + } } ngAfterContentInit() { @@ -473,24 +465,30 @@ export class Scroller implements OnInit, AfterContentInit, AfterViewChecked, OnD } ngAfterViewInit() { - this.setContentEl(this.contentEl); - this.init(); - - this.defaultWidth = DomHandler.getWidth(this.elementViewChild.nativeElement); - this.defaultHeight = DomHandler.getHeight(this.elementViewChild.nativeElement); - this.initialized = true; + if (DomHandler.isVisible(this.elementViewChild?.nativeElement)) { + this.setInitialState(); + this.setContentEl(this.contentEl); + this.init(); + + this.defaultWidth = DomHandler.getWidth(this.elementViewChild.nativeElement); + this.defaultHeight = DomHandler.getHeight(this.elementViewChild.nativeElement); + this.initialized = true; + } } ngAfterViewChecked() { + if (!this.initialized) { + this.ngAfterViewInit(); + } + this.calculateAutoSize(); } ngOnDestroy() { - if (this.windowResizeListener) { - window.removeEventListener('resize', this.windowResizeListener); - window.removeEventListener('orientationchange', this.windowResizeListener); - this.windowResizeListener = null; - } + this.unbindResizeListener(); + + this.contentEl = null; + this.initialized = false; } init() { @@ -498,6 +496,7 @@ export class Scroller implements OnInit, AfterContentInit, AfterViewChecked, OnD this.setSize(); this.calculateOptions(); this.setSpacerSize(); + this.bindResizeListener(); this.cd.detectChanges(); } @@ -514,6 +513,9 @@ export class Scroller implements OnInit, AfterContentInit, AfterViewChecked, OnD this.lastScrollPos = this.both ? { top: 0, left: 0 } : 0; this.d_loading = this._loading || false; this.d_numToleratedItems = this._numToleratedItems; + this.loaderArr = []; + this.spacerStyle = {}; + this.contentStyle = {}; } getElementRef() { @@ -646,7 +648,9 @@ export class Scroller implements OnInit, AfterContentInit, AfterViewChecked, OnD } if (this._lazy) { - this.handleEvents('onLazyLoad', { first, last }); + Promise.resolve().then(() => { + this.handleEvents('onLazyLoad', { first, last }); + }); } } @@ -850,13 +854,32 @@ export class Scroller implements OnInit, AfterContentInit, AfterViewChecked, OnD } } + bindResizeListener() { + if (!this.windowResizeListener) { + this.zone.runOutsideAngular(() => { + this.windowResizeListener = this.onWindowResize.bind(this); + + window.addEventListener('resize', this.windowResizeListener); + window.addEventListener('orientationchange', this.windowResizeListener); + }); + } + } + + unbindResizeListener() { + if (this.windowResizeListener) { + window.removeEventListener('resize', this.windowResizeListener); + window.removeEventListener('orientationchange', this.windowResizeListener); + this.windowResizeListener = null; + } + } + onWindowResize() { if (this.resizeTimeout) { clearTimeout(this.resizeTimeout); } this.resizeTimeout = setTimeout(() => { - if (this.elementViewChild) { + if (DomHandler.isVisible(this.elementViewChild?.nativeElement)) { const [width, height] = [DomHandler.getWidth(this.elementViewChild.nativeElement), DomHandler.getHeight(this.elementViewChild.nativeElement)]; const [isDiffWidth, isDiffHeight] = [width !== this.defaultWidth, height !== this.defaultHeight]; const reinit = this.both ? isDiffWidth || isDiffHeight : this.horizontal ? isDiffWidth : this.vertical ? isDiffHeight : false; From 5dd2c48d993d638d7c26d8b801fc92a7846a05ac Mon Sep 17 00:00:00 2001 From: mertsincan Date: Tue, 25 Oct 2022 14:39:37 +0100 Subject: [PATCH 2/4] Cosmetics --- src/app/components/api/overlayoptions.ts | 40 ++++++++++++++---------- src/app/components/overlay/overlay.ts | 12 +++---- 2 files changed, 29 insertions(+), 23 deletions(-) diff --git a/src/app/components/api/overlayoptions.ts b/src/app/components/api/overlayoptions.ts index 290efedfde9..0904bd9e743 100644 --- a/src/app/components/api/overlayoptions.ts +++ b/src/app/components/api/overlayoptions.ts @@ -1,23 +1,15 @@ import { AnimationEvent } from '@angular/animations'; -export interface OverlayOptions { - style?: any; - styleClass?: string; - appendTo?: 'body' | HTMLElement | undefined; - autoZIndex?: boolean; - baseZIndex?: number; - showTransitionOptions?: string; - hideTransitionOptions?: string; - listener?: any; - responsive?: ResponsiveOverlayOptions | undefined; - onShow?: (event?: OverlayOnShowEvent) => void; - onHide?: (event?: OverlayOnHideEvent) => void; - onAnimationStart?: (event?: AnimationEvent) => void; - onAnimationDone?: (event?: AnimationEvent) => void; -} +export type OverlayModeType = 'modal' | 'overlay' | undefined; export type ResponsiveOverlayDirectionType = 'start' | 'center' | 'end' | undefined; +export interface OverlayListenerOptions { + type?: 'scroll' | 'outside' | 'resize' | undefined; + mode?: OverlayModeType; + valid?: boolean; +} + export interface ResponsiveOverlayOptions { style?: any; styleClass?: string; @@ -26,8 +18,6 @@ export interface ResponsiveOverlayOptions { direction?: ResponsiveOverlayDirectionType; } -export type OverlayModeType = 'modal' | 'overlay' | undefined; - export interface OverlayOnShowEvent { container?: HTMLElement | undefined; target?: HTMLElement | undefined; @@ -35,3 +25,19 @@ export interface OverlayOnShowEvent { } export interface OverlayOnHideEvent extends OverlayOnShowEvent {} + +export interface OverlayOptions { + style?: any; + styleClass?: string; + appendTo?: 'body' | HTMLElement | undefined; + autoZIndex?: boolean; + baseZIndex?: number; + showTransitionOptions?: string; + hideTransitionOptions?: string; + listener?: (event: Event, options?: OverlayListenerOptions) => boolean | void; + responsive?: ResponsiveOverlayOptions | undefined; + onShow?: (event?: OverlayOnShowEvent) => void; + onHide?: (event?: OverlayOnHideEvent) => void; + onAnimationStart?: (event?: AnimationEvent) => void; + onAnimationDone?: (event?: AnimationEvent) => void; +} diff --git a/src/app/components/overlay/overlay.ts b/src/app/components/overlay/overlay.ts index bef4bc65cb8..48d6bf7fcf6 100644 --- a/src/app/components/overlay/overlay.ts +++ b/src/app/components/overlay/overlay.ts @@ -12,9 +12,9 @@ export const OVERLAY_VALUE_ACCESSOR: any = { multi: true }; -const showAnimation = animation([style({ transform: '{{transform}}', opacity: 0 }), animate('{{showTransitionParams}}')]); +const showOverlayAnimation = animation([style({ transform: '{{transform}}', opacity: 0 }), animate('{{showTransitionParams}}')]); -const hideAnimation = animation([animate('{{hideTransitionParams}}', style({ transform: '{{transform}}', opacity: 0 }))]); +const hideOverlayAnimation = animation([animate('{{hideTransitionParams}}', style({ transform: '{{transform}}', opacity: 0 }))]); @Component({ selector: 'p-overlay', @@ -49,7 +49,7 @@ const hideAnimation = animation([animate('{{hideTransitionParams}}', style({ tra
`, - animations: [trigger('overlayAnimation', [transition(':enter', [useAnimation(showAnimation)]), transition(':leave', [useAnimation(hideAnimation)])])], + animations: [trigger('overlayAnimation', [transition(':enter', [useAnimation(showOverlayAnimation)]), transition(':leave', [useAnimation(hideOverlayAnimation)])])], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, providers: [OVERLAY_VALUE_ACCESSOR], @@ -318,7 +318,7 @@ export class Overlay implements OnDestroy { bindScrollListener() { if (!this.scrollHandler) { this.scrollHandler = new ConnectedOverlayScrollHandler(this.targetEl, (event) => { - const valid = this.listener ? this.listener(event, { type: 'scroll', valid: true }) : true; + const valid = this.listener ? this.listener(event, { type: 'scroll', mode: this.mode, valid: true }) : true; valid && this.hide(event, true); }); @@ -337,7 +337,7 @@ export class Overlay implements OnDestroy { if (!this.documentClickListener) { this.documentClickListener = this.renderer.listen(this.document, 'click', (event) => { const isOutsideClicked = this.targetEl && !(this.targetEl.isSameNode(event.target) || this.targetEl.contains(event.target) || (this.overlayEl && this.overlayEl.contains(event.target))); - const valid = this.listener ? this.listener(event, { type: 'outside', valid: event.which !== 3 && isOutsideClicked }) : isOutsideClicked; + const valid = this.listener ? this.listener(event, { type: 'outside', mode: this.mode, valid: event.which !== 3 && isOutsideClicked }) : isOutsideClicked; valid && this.hide(event); }); @@ -354,7 +354,7 @@ export class Overlay implements OnDestroy { bindDocumentResizeListener() { if (!this.documentResizeListener) { this.documentResizeListener = this.renderer.listen('window', 'resize', (event) => { - const valid = this.listener ? this.listener(event, { type: 'resize', valid: !DomHandler.isTouchDevice() }) : !DomHandler.isTouchDevice(); + const valid = this.listener ? this.listener(event, { type: 'resize', mode: this.mode, valid: !DomHandler.isTouchDevice() }) : !DomHandler.isTouchDevice(); valid && this.hide(event, true); }); From 74bb5ed13c3ff8c57f48f8a458664c3bdc5d32d0 Mon Sep 17 00:00:00 2001 From: mertsincan Date: Wed, 26 Oct 2022 12:58:14 +0100 Subject: [PATCH 3/4] Refactor on scroller component --- src/app/components/scroller/scroller.ts | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/app/components/scroller/scroller.ts b/src/app/components/scroller/scroller.ts index d5ff44846ed..53adaeec180 100644 --- a/src/app/components/scroller/scroller.ts +++ b/src/app/components/scroller/scroller.ts @@ -465,20 +465,12 @@ export class Scroller implements OnInit, AfterContentInit, AfterViewChecked, OnD } ngAfterViewInit() { - if (DomHandler.isVisible(this.elementViewChild?.nativeElement)) { - this.setInitialState(); - this.setContentEl(this.contentEl); - this.init(); - - this.defaultWidth = DomHandler.getWidth(this.elementViewChild.nativeElement); - this.defaultHeight = DomHandler.getHeight(this.elementViewChild.nativeElement); - this.initialized = true; - } + this.viewInit(); } ngAfterViewChecked() { if (!this.initialized) { - this.ngAfterViewInit(); + this.viewInit(); } this.calculateAutoSize(); @@ -491,6 +483,18 @@ export class Scroller implements OnInit, AfterContentInit, AfterViewChecked, OnD this.initialized = false; } + viewInit() { + if (DomHandler.isVisible(this.elementViewChild?.nativeElement)) { + this.setInitialState(); + this.setContentEl(this.contentEl); + this.init(); + + this.defaultWidth = DomHandler.getWidth(this.elementViewChild.nativeElement); + this.defaultHeight = DomHandler.getHeight(this.elementViewChild.nativeElement); + this.initialized = true; + } + } + init() { if (!this._disabled) { this.setSize(); From b47aa0cab1758ae0e621db2887d4c59472d5b4f3 Mon Sep 17 00:00:00 2001 From: mertsincan Date: Wed, 26 Oct 2022 13:13:25 +0100 Subject: [PATCH 4/4] Added mode option --- src/app/components/api/overlayoptions.ts | 1 + src/app/components/overlay/overlay.ts | 33 +++++++++++++++--------- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/app/components/api/overlayoptions.ts b/src/app/components/api/overlayoptions.ts index 0904bd9e743..7776b4affe1 100644 --- a/src/app/components/api/overlayoptions.ts +++ b/src/app/components/api/overlayoptions.ts @@ -27,6 +27,7 @@ export interface OverlayOnShowEvent { export interface OverlayOnHideEvent extends OverlayOnShowEvent {} export interface OverlayOptions { + mode?: OverlayModeType; style?: any; styleClass?: string; appendTo?: 'body' | HTMLElement | undefined; diff --git a/src/app/components/overlay/overlay.ts b/src/app/components/overlay/overlay.ts index 48d6bf7fcf6..71723a5b054 100644 --- a/src/app/components/overlay/overlay.ts +++ b/src/app/components/overlay/overlay.ts @@ -2,7 +2,7 @@ import { animate, animation, AnimationEvent, style, transition, trigger, useAnim import { CommonModule, DOCUMENT } from '@angular/common'; import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, forwardRef, Inject, Input, NgModule, OnDestroy, Output, Renderer2, ViewChild, ViewEncapsulation } from '@angular/core'; import { NG_VALUE_ACCESSOR } from '@angular/forms'; -import { OverlayOptions, OverlayService, PrimeNGConfig, SharedModule } from 'primeng/api'; +import { OverlayModeType, OverlayOptions, OverlayService, PrimeNGConfig, SharedModule } from 'primeng/api'; import { ConnectedOverlayScrollHandler, DomHandler } from 'primeng/dom'; import { ZIndexUtils } from 'primeng/utils'; @@ -59,10 +59,10 @@ const hideOverlayAnimation = animation([animate('{{hideTransitionParams}}', styl } }) export class Overlay implements OnDestroy { - @Input() get visible(): any { + @Input() get visible(): boolean { return this._visible; } - set visible(value: any) { + set visible(value: boolean) { this._visible = value; if (this._visible && !this.modalVisible) { @@ -70,6 +70,13 @@ export class Overlay implements OnDestroy { } } + @Input() get mode(): OverlayModeType | string { + return this._mode || this.overlayOptions?.mode; + } + set mode(value: string) { + this._mode = value; + } + @Input() get style(): any { return this._style || (this.modal ? this.overlayResponsiveOptions?.style : this.overlayOptions?.style); } @@ -151,6 +158,8 @@ export class Overlay implements OnDestroy { _visible: boolean; + _mode: OverlayModeType | string; + _style: any; _styleClass: string; @@ -187,11 +196,11 @@ export class Overlay implements OnDestroy { }; get modal() { - return this.overlayResponsiveOptions && this.window.matchMedia(this.overlayResponsiveOptions.media?.replace('@media', '') || `(max-width: ${this.overlayResponsiveOptions.breakpoint})`).matches; + return this.mode === 'modal' || (this.overlayResponsiveOptions && this.window.matchMedia(this.overlayResponsiveOptions.media?.replace('@media', '') || `(max-width: ${this.overlayResponsiveOptions.breakpoint})`).matches); } - get mode() { - return this.modal ? 'modal' : 'overlay'; + get overlayMode() { + return this.mode || (this.modal ? 'modal' : 'overlay'); } get overlayOptions() { @@ -228,7 +237,7 @@ export class Overlay implements OnDestroy { show(container?: HTMLElement, isFocus: boolean = false) { this.visible = true; - this.handleEvents('onShow', { container: container || this.containerEl, target: this.targetEl, mode: this.mode }); + this.handleEvents('onShow', { container: container || this.containerEl, target: this.targetEl, mode: this.overlayMode }); isFocus && DomHandler.focus(this.targetEl); this.modal && DomHandler.addClass(this.document?.body, 'p-overflow-hidden'); @@ -236,7 +245,7 @@ export class Overlay implements OnDestroy { hide(container?: HTMLElement, isFocus: boolean = false) { this.visible = false; - this.handleEvents('onHide', { container: container || this.containerEl, target: this.targetEl, mode: this.mode }); + this.handleEvents('onHide', { container: container || this.containerEl, target: this.targetEl, mode: this.overlayMode }); isFocus && DomHandler.focus(this.targetEl); this.modal && DomHandler.removeClass(this.document?.body, 'p-overflow-hidden'); @@ -257,7 +266,7 @@ export class Overlay implements OnDestroy { switch (event.toState) { case 'visible': if (this.autoZIndex) { - ZIndexUtils.set('overlay', this.containerEl, this.baseZIndex + this.config?.zIndex[this.mode]); + ZIndexUtils.set(this.overlayMode, this.containerEl, this.baseZIndex + this.config?.zIndex[this.overlayMode]); } DomHandler.appendOverlay(this.containerEl, this.appendTo === 'body' ? this.document.body : this.appendTo, this.appendTo); @@ -318,7 +327,7 @@ export class Overlay implements OnDestroy { bindScrollListener() { if (!this.scrollHandler) { this.scrollHandler = new ConnectedOverlayScrollHandler(this.targetEl, (event) => { - const valid = this.listener ? this.listener(event, { type: 'scroll', mode: this.mode, valid: true }) : true; + const valid = this.listener ? this.listener(event, { type: 'scroll', mode: this.overlayMode, valid: true }) : true; valid && this.hide(event, true); }); @@ -337,7 +346,7 @@ export class Overlay implements OnDestroy { if (!this.documentClickListener) { this.documentClickListener = this.renderer.listen(this.document, 'click', (event) => { const isOutsideClicked = this.targetEl && !(this.targetEl.isSameNode(event.target) || this.targetEl.contains(event.target) || (this.overlayEl && this.overlayEl.contains(event.target))); - const valid = this.listener ? this.listener(event, { type: 'outside', mode: this.mode, valid: event.which !== 3 && isOutsideClicked }) : isOutsideClicked; + const valid = this.listener ? this.listener(event, { type: 'outside', mode: this.overlayMode, valid: event.which !== 3 && isOutsideClicked }) : isOutsideClicked; valid && this.hide(event); }); @@ -354,7 +363,7 @@ export class Overlay implements OnDestroy { bindDocumentResizeListener() { if (!this.documentResizeListener) { this.documentResizeListener = this.renderer.listen('window', 'resize', (event) => { - const valid = this.listener ? this.listener(event, { type: 'resize', mode: this.mode, valid: !DomHandler.isTouchDevice() }) : !DomHandler.isTouchDevice(); + const valid = this.listener ? this.listener(event, { type: 'resize', mode: this.overlayMode, valid: !DomHandler.isTouchDevice() }) : !DomHandler.isTouchDevice(); valid && this.hide(event, true); });