From 37f962923dc223b8aa98d65b6b023f83d46a7683 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=87etin?= <69278826+cetincakiroglu@users.noreply.github.com> Date: Thu, 13 Oct 2022 15:09:13 +0300 Subject: [PATCH 1/2] Fixed #12031 - Component: Responsive Overlay --- src/app/components/api/primengconfig.ts | 6 + src/app/components/dropdown/dropdown.css | 2 +- src/app/components/dropdown/dropdown.ts | 153 +++++-------- src/app/components/overlay/ng-package.json | 0 src/app/components/overlay/overlay.css | 28 +++ src/app/components/overlay/overlay.ts | 249 +++++++++++++++++++++ src/app/components/overlay/public_api.ts | 1 + 7 files changed, 336 insertions(+), 103 deletions(-) create mode 100644 src/app/components/overlay/ng-package.json create mode 100644 src/app/components/overlay/overlay.css create mode 100644 src/app/components/overlay/overlay.ts create mode 100644 src/app/components/overlay/public_api.ts diff --git a/src/app/components/api/primengconfig.ts b/src/app/components/api/primengconfig.ts index b4282cb0c7e..c4858748e86 100644 --- a/src/app/components/api/primengconfig.ts +++ b/src/app/components/api/primengconfig.ts @@ -3,11 +3,17 @@ import { Subject } from 'rxjs'; import { FilterMatchMode } from './filtermatchmode'; import { Translation } from './translation'; +interface OverlayOptions { + breakpoint: number; +} + @Injectable({providedIn: 'root'}) export class PrimeNGConfig { ripple: boolean = false; + overlayOptions: OverlayOptions; + filterMatchModeOptions = { text: [ FilterMatchMode.STARTS_WITH, diff --git a/src/app/components/dropdown/dropdown.css b/src/app/components/dropdown/dropdown.css index b0b02d7c3cf..5f529c53717 100755 --- a/src/app/components/dropdown/dropdown.css +++ b/src/app/components/dropdown/dropdown.css @@ -85,4 +85,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 dd56728b40f..c4209c2b8ca 100755 --- a/src/app/components/dropdown/dropdown.ts +++ b/src/app/components/dropdown/dropdown.ts @@ -1,16 +1,17 @@ import {NgModule,Component,ElementRef,OnInit,AfterViewInit,AfterContentInit,AfterViewChecked,OnDestroy,Input,Output,Renderer2,EventEmitter,ContentChildren, QueryList,ViewChild,TemplateRef,forwardRef,ChangeDetectorRef,NgZone,ViewRef,ChangeDetectionStrategy, ViewEncapsulation} from '@angular/core'; -import {trigger,style,transition,animate,AnimationEvent} from '@angular/animations'; +import {trigger,transition,AnimationEvent, query, animateChild} from '@angular/animations'; import {CommonModule} from '@angular/common'; -import {OverlayService, PrimeNGConfig, SelectItem, TranslationKeys} from 'primeng/api'; +import {PrimeNGConfig, SelectItem, TranslationKeys} from 'primeng/api'; import {SharedModule,PrimeTemplate, FilterService} from 'primeng/api'; import {DomHandler, ConnectedOverlayScrollHandler} from 'primeng/dom'; -import {ObjectUtils,UniqueComponentId,ZIndexUtils} from 'primeng/utils'; +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 {AutoFocusModule} from 'primeng/autofocus'; +import {Overlay, OverlayModule} from '../overlay/overlay'; export const DROPDOWN_VALUE_ACCESSOR: any = { provide: NG_VALUE_ACCESSOR, @@ -86,7 +87,7 @@ export class DropdownItem {
-
+
@@ -153,17 +154,13 @@ export class DropdownItem {
-
+ `, animations: [ trigger('overlayAnimation', [ - transition(':enter', [ - style({opacity: 0, transform: 'scaleY(0.8)'}), - animate('{{showTransitionParams}}') - ]), - transition(':leave', [ - animate('{{hideTransitionParams}}', style({ opacity: 0 })) + transition(':enter, :leave', [ + query('@*', animateChild(), {optional: true}) ]) ]) ], @@ -279,6 +276,8 @@ export class Dropdown implements OnInit,AfterViewInit,AfterContentInit,AfterView @Input() autofocusFilter: boolean = true; + @Input() overlayDirection: string = 'end'; + @Output() onChange: EventEmitter = new EventEmitter(); @Output() onFilter: EventEmitter = new EventEmitter(); @@ -309,6 +308,8 @@ export class Dropdown implements OnInit,AfterViewInit,AfterContentInit,AfterView @ViewChild('scroller') scroller: Scroller; + @ViewChild('overlay') overlayViewChild: Overlay; + @ContentChildren(PrimeTemplate) templates: QueryList; private _disabled: boolean; @@ -341,8 +342,6 @@ export class Dropdown implements OnInit,AfterViewInit,AfterContentInit,AfterView console.warn("The itemSize property is deprecated, use virtualScrollItemSize property instead."); } - overlay: HTMLDivElement; - itemsWrapper: HTMLDivElement; itemTemplate: TemplateRef; @@ -421,7 +420,7 @@ export class Dropdown implements OnInit,AfterViewInit,AfterContentInit,AfterView listId: string; - constructor(public el: ElementRef, public renderer: Renderer2, public cd: ChangeDetectorRef, public zone: NgZone, public filterService: FilterService, public config: PrimeNGConfig, public overlayService: OverlayService) {} + constructor(public el: ElementRef, public renderer: Renderer2, public cd: ChangeDetectorRef, public zone: NgZone, public filterService: FilterService, public config: PrimeNGConfig) {} ngAfterContentInit() { this.templates.forEach((item) => { @@ -601,15 +600,15 @@ export class Dropdown implements OnInit,AfterViewInit,AfterContentInit,AfterView this.zone.runOutsideAngular(() => { setTimeout(() => { - this.alignOverlay(); + this.overlayViewChild.alignOverlay(); }, 1); }); } if (this.selectedOptionUpdated && this.itemsWrapper) { - let selectedItem = DomHandler.findSingle(this.overlay, 'li.p-highlight'); + let selectedItem = DomHandler.findSingle(this.overlayViewChild.el.nativeElement, 'li.p-highlight'); if (selectedItem) { - DomHandler.scrollInView(this.itemsWrapper, DomHandler.findSingle(this.overlay, 'li.p-highlight')); + DomHandler.scrollInView(this.itemsWrapper, DomHandler.findSingle(this.overlayViewChild.el.nativeElement, 'li.p-highlight')); } this.selectedOptionUpdated = false; } @@ -682,13 +681,6 @@ export class Dropdown implements OnInit,AfterViewInit,AfterContentInit,AfterView this.cd.detectChanges(); } - onOverlayClick(event) { - this.overlayService.add({ - originalEvent: event, - target: this.el.nativeElement - }); - } - isInputClick(event): boolean { return DomHandler.hasClass(event.target, 'p-dropdown-clear-icon') || event.target.isSameNode(this.accessibleViewChild.nativeElement) || @@ -696,7 +688,7 @@ export class Dropdown implements OnInit,AfterViewInit,AfterContentInit,AfterView } isOutsideClicked(event: Event): boolean { - return !(this.el.nativeElement.isSameNode(event.target) || this.el.nativeElement.contains(event.target) || (this.overlay && this.overlay.contains( event.target))); + return !(this.el.nativeElement.isSameNode(event.target) || this.el.nativeElement.contains(event.target) || (this.overlayViewChild && this.overlayViewChild.el.nativeElement.contains( event.target))); } isEmpty() { @@ -730,78 +722,50 @@ export class Dropdown implements OnInit,AfterViewInit,AfterContentInit,AfterView } onOverlayAnimationStart(event: AnimationEvent) { - switch (event.toState) { - case 'visible': - this.overlay = event.element; - this.itemsWrapper = DomHandler.findSingle(this.overlay, this.virtualScroll ? '.p-scroller' : '.p-dropdown-items-wrapper'); - this.virtualScroll && this.scroller.setContentEl(this.itemsViewChild.nativeElement); - this.appendOverlay(); - if (this.autoZIndex) { - ZIndexUtils.set('overlay', this.overlay, this.baseZIndex + this.config.zIndex.overlay); - } - this.alignOverlay(); - this.bindDocumentClickListener(); - this.bindDocumentResizeListener(); - this.bindScrollListener(); - - if (this.options && this.options.length) { - if (this.virtualScroll) { - const selectedIndex = this.selectedOption ? this.findOptionIndex(this.getOptionValue(this.selectedOption), this.optionsToDisplay) : -1; - if (selectedIndex !== -1) { - this.scroller.scrollToIndex(selectedIndex); - } - } - else { - let selectedListItem = DomHandler.findSingle(this.itemsWrapper, '.p-dropdown-item.p-highlight'); - - if (selectedListItem) { - selectedListItem.scrollIntoView({ block: 'nearest', inline: 'center' }); - } + if (event.toState === null && event.fromState === 'void') { + 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.bindDocumentResizeListener(); + this.bindScrollListener(); + + if (this.options && this.options.length) { + if (this.virtualScroll) { + const selectedIndex = this.selectedOption ? this.findOptionIndex(this.getOptionValue(this.selectedOption), this.optionsToDisplay) : -1; + if (selectedIndex !== -1) { + this.scroller.scrollToIndex(selectedIndex); } } + else { + let selectedListItem = DomHandler.findSingle(this.itemsWrapper, '.p-dropdown-item.p-highlight'); - if (this.filterViewChild && this.filterViewChild.nativeElement) { - this.preventModelTouched = true; - - if (this.autofocusFilter) { - this.filterViewChild.nativeElement.focus(); + if (selectedListItem) { + selectedListItem.scrollIntoView({ block: 'nearest', inline: 'center' }); } } + } - this.onShow.emit(event); - break; + if (this.filterViewChild && this.filterViewChild.nativeElement) { + this.preventModelTouched = true; - case 'void': - this.onOverlayHide(); - this.onHide.emit(event); - break; - } - } + if (this.autofocusFilter) { + this.filterViewChild.nativeElement.focus(); + } + } - onOverlayAnimationEnd(event: AnimationEvent) { - switch (event.toState) { - case 'void': - ZIndexUtils.clear(event.element); - break; + this.onShow.emit(event); } - } - - appendOverlay() { - if (this.appendTo) { - if (this.appendTo === 'body') - document.body.appendChild(this.overlay); - else - DomHandler.appendChild(this.overlay, this.appendTo); - - if (!this.overlay.style.minWidth) { - this.overlay.style.minWidth = DomHandler.getWidth(this.containerViewChild.nativeElement) + 'px'; - } + if (event.toState === 'void' && event.fromState === null) { + this.onOverlayHide(); + this.onHide.emit(event); } } restoreOverlayAppend() { - if (this.overlay && this.appendTo) { - this.el.nativeElement.appendChild(this.overlay); + if (this.overlayViewChild && this.appendTo) { + this.el.nativeElement.appendChild(this.overlayViewChild.el.nativeElement); } } @@ -811,19 +775,9 @@ export class Dropdown implements OnInit,AfterViewInit,AfterContentInit,AfterView if (this.filter && this.resetFilterOnHide) { this.resetFilter(); } - this.cd.markForCheck(); } - alignOverlay() { - if (this.overlay) { - if (this.appendTo) - DomHandler.absolutePosition(this.overlay, this.containerViewChild.nativeElement); - else - DomHandler.relativePosition(this.overlay, this.containerViewChild.nativeElement); - } - } - onInputFocus(event) { this.focused = true; this.onFocus.emit(event); @@ -1288,7 +1242,6 @@ export class Dropdown implements OnInit,AfterViewInit,AfterContentInit,AfterView this.unbindDocumentClickListener(); this.unbindDocumentResizeListener(); this.unbindScrollListener(); - this.overlay = null; this.itemsWrapper = null; this.onModelTouched(); } @@ -1298,19 +1251,15 @@ export class Dropdown implements OnInit,AfterViewInit,AfterContentInit,AfterView this.scrollHandler.destroy(); this.scrollHandler = null; } - - if (this.overlay) { - ZIndexUtils.clear(this.overlay); - } - this.restoreOverlayAppend(); this.onOverlayHide(); } } @NgModule({ - imports: [CommonModule,SharedModule,TooltipModule,RippleModule,ScrollerModule, AutoFocusModule], + imports: [CommonModule,OverlayModule,SharedModule,TooltipModule,RippleModule,ScrollerModule, AutoFocusModule], exports: [Dropdown,SharedModule,ScrollerModule], - declarations: [Dropdown,DropdownItem] + declarations: [Dropdown,DropdownItem], + entryComponents: [Overlay] }) export class DropdownModule { } diff --git a/src/app/components/overlay/ng-package.json b/src/app/components/overlay/ng-package.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/app/components/overlay/overlay.css b/src/app/components/overlay/overlay.css new file mode 100644 index 00000000000..11ab7a7e6c9 --- /dev/null +++ b/src/app/components/overlay/overlay.css @@ -0,0 +1,28 @@ +.p-overlay-mask { + display: flex; + justify-content: center; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; +} + +.p-overlay-responsive { + overflow: hidden; + position: static; + height: fit-content; + width: inherit; +} + +.p-overlay-panel-start { + align-self: flex-start; +} + +.p-overlay-panel-center { + align-self: center; +} + +.p-overlay-panel-end { + align-self: flex-end; +} \ No newline at end of file diff --git a/src/app/components/overlay/overlay.ts b/src/app/components/overlay/overlay.ts new file mode 100644 index 00000000000..c26f2f2bfae --- /dev/null +++ b/src/app/components/overlay/overlay.ts @@ -0,0 +1,249 @@ +import {NgModule,Component,Input,OnDestroy,Renderer2,ElementRef,ChangeDetectionStrategy, ViewEncapsulation, ViewChild, Inject} from '@angular/core'; +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 {ZIndexUtils} from 'primeng/utils'; + +const showAnimation = animation([ + style({ transform: '{{transform}}', opacity: 1 }), + animate('{{showTransitionParams}}') +]); + +const hideAnimation = animation([ + animate('{{hideTransitionParams}}', style({ transform: '{{transform}}', opacity: 0 })) +]); + +@Component({ + selector: 'p-overlay', + template: ` +
+ +
+ + `, + animations: [ + trigger('overlayAnimation', [ + transition('void => visible', [ + useAnimation(showAnimation) + ]), + transition('visible => void', [ + useAnimation(hideAnimation) + ]) + ]) + ], + changeDetection: ChangeDetectionStrategy.OnPush, + encapsulation: ViewEncapsulation.None, + styleUrls: ['./overlay.css'], + host: { + 'class': 'p-element', + '[class.p-overlay-panel]': 'true' + } +}) +export class Overlay implements OnDestroy { + + @Input() baseZIndex: number = 0; + + @Input() showTransitionOptions: string = '.12s cubic-bezier(0, 0, 0.2, 1)'; + + @Input() hideTransitionOptions: string = '.1s linear'; + + @Input() panelClass: string; + + @Input() panelStyleClass: string; + + @Input() overlayAnimation: string; + + @Input() container: ElementRef; + + @Input() autoZIndex: boolean; + + @Input() panelStyle: string; + + get overlayDirection(): string { + return this._overlayDirection; + } + + @Input() set overlayDirection(value: string) { + const viewport = DomHandler.getViewport(); + if (value && 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() set appendTo(val) { + this._appendTo = val; + } + + get appendTo(): any { + return this._appendTo; + } + + get overlayBreakpoints() { + return this.config.overlayOptions ? this.config.overlayOptions.breakpoint : null; + } + + @ViewChild('overlay') overlayViewChild: ElementRef; + + _overlayDirection: string; + + _overlayBreakpoints: number; + + _appendTo: any; + + transformOptions: string = "scaleY(0.8)"; + + mask: HTMLDivElement; + + maskClickListener: Function; + + constructor(@Inject(DOCUMENT) private document: Document, public el: ElementRef, public renderer: Renderer2, private config: PrimeNGConfig, public overlayService: OverlayService) {} + + onOverlayClick(event: MouseEvent) { + this.overlayService.add({ + originalEvent: event, + target: this.el.nativeElement + }); + } + + appendOverlay() { + const viewport = DomHandler.getViewport(); + if (this.autoZIndex) { + ZIndexUtils.set('overlay', this.overlayViewChild.nativeElement, this.baseZIndex + this.config.zIndex.overlay); + } + if (viewport.width < this.overlayBreakpoints) { + this.appendMask(); + DomHandler.addClass(this.document.body, 'p-overflow-hidden'); + DomHandler.addClass(this.overlayViewChild.nativeElement, 'p-overlay-responsive'); + this.mask.appendChild(this.el.nativeElement); + } + else { + if (this.appendTo) { + if (this.appendTo === 'body') { + this.document.body.appendChild(this.el.nativeElement); + } + else { + DomHandler.appendChild(this.el.nativeElement, this.appendTo); + } + } + } + + if (!this.overlayViewChild.nativeElement.style.minWidth) { + this.overlayViewChild.nativeElement.style.minWidth = DomHandler.getWidth(this.container) + 'px'; + } + } + + alignOverlay() { + const viewport = DomHandler.getViewport(); + + if(this.overlayViewChild) { + if (viewport.width < this.overlayBreakpoints) { + switch (this.overlayDirection) { + case 'start': + DomHandler.addClass(this.el.nativeElement, 'p-overlay-panel-start') + break; + + case 'center': + DomHandler.addClass(this.el.nativeElement, 'p-overlay-panel-center') + break; + + case 'end': + DomHandler.addClass(this.el.nativeElement, 'p-overlay-panel-end') + break; + } + } + else { + if (this.appendTo) { + DomHandler.absolutePosition(this.el.nativeElement.firstChild, this.container); + } + else { + DomHandler.relativePosition(this.el.nativeElement.firstChild, this.container); + } + } + } + } + + appendMask() { + if (!this.mask) { + this.mask = this.renderer.createElement('div'); + DomHandler.addMultipleClasses(this.mask, 'p-overlay-mask p-component-overlay-enter'); + ZIndexUtils.set('modal', this.mask, this.baseZIndex + this.config.zIndex.modal); + this.document.body.appendChild(this.mask); + this.blockScroll(); + this.bindMaskClickListener(); + } + } + + removeMask() { + this.unbindMaskClickListener(); + + if (this.mask) { + DomHandler.addClass(this.mask, 'p-component-overlay-leave'); + ZIndexUtils.clear(this.mask); + this.document.body.removeChild(this.mask); + this.mask = null; + this.unblockScroll(); + } + } + + blockScroll() { + DomHandler.addClass(this.document.body, 'p-overflow-hidden'); + } + + unblockScroll() { + DomHandler.removeClass(this.document.body, 'p-overflow-hidden'); + } + + bindMaskClickListener() { + if (!this.maskClickListener) { + this.maskClickListener = this.renderer.listen(this.mask, 'click', () => { + this.removeMask(); + }); + } + } + + unbindMaskClickListener() { + if (this.maskClickListener) { + this.maskClickListener(); + this.maskClickListener = null; + } + } + + ngOnDestroy() { + if (this.maskClickListener) { + this.unbindMaskClickListener(); + } + + if (this.overlayViewChild && this.el.nativeElement) { + ZIndexUtils.clear(this.overlayViewChild.nativeElement); + this.overlayViewChild = null; + } + + if(this.mask) { + this.removeMask(); + } + } + +} + +@NgModule({ + imports: [CommonModule,RippleModule, SharedModule], + exports: [Overlay, SharedModule], + declarations: [Overlay] +}) +export class OverlayModule { } diff --git a/src/app/components/overlay/public_api.ts b/src/app/components/overlay/public_api.ts new file mode 100644 index 00000000000..5d4bcbb5787 --- /dev/null +++ b/src/app/components/overlay/public_api.ts @@ -0,0 +1 @@ +export * from './overlay'; \ No newline at end of file From c990b871f59be3ee7d1435bb5a804d582d982ba5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=87etin?= <69278826+cetincakiroglu@users.noreply.github.com> Date: Tue, 18 Oct 2022 11:25:37 +0300 Subject: [PATCH 2/2] refactor --- src/app/components/dropdown/dropdown.ts | 135 ++++++++--------- src/app/components/overlay/overlay.css | 19 ++- src/app/components/overlay/overlay.ts | 189 +++++++++++++----------- 3 files changed, 182 insertions(+), 161 deletions(-) diff --git a/src/app/components/dropdown/dropdown.ts b/src/app/components/dropdown/dropdown.ts index c4209c2b8ca..8f252f24fc8 100755 --- a/src/app/components/dropdown/dropdown.ts +++ b/src/app/components/dropdown/dropdown.ts @@ -87,83 +87,79 @@ export class DropdownItem {
- - -
- - - - -
- - -
-
-
-
- - - + +
+ +
+ + + + +
+ + +
- - - +
+
+ + + - - - - - - - -
    - - -
  • - {{getOptionGroupLabel(optgroup)||'empty'}} - -
  • - + + + - - - - - - - - -
  • - - {{emptyFilterMessageLabel}} + + + + + + +
      + + +
    • + {{getOptionGroupLabel(optgroup)||'empty'}} + +
    • + +
      - - -
    • - - {{emptyMessageLabel}} + + - -
    • -
    -
    + + + + + +
  • + + {{emptyFilterMessageLabel}} + + +
  • +
  • + + {{emptyMessageLabel}} + + +
  • +
+
+
+
-
`, - animations: [ - trigger('overlayAnimation', [ - transition(':enter, :leave', [ - query('@*', animateChild(), {optional: true}) - ]) - ]) - ], + host: { 'class': 'p-element p-inputwrapper', '[class.p-inputwrapper-filled]': 'filled', @@ -722,13 +718,12 @@ export class Dropdown implements OnInit,AfterViewInit,AfterContentInit,AfterView } onOverlayAnimationStart(event: AnimationEvent) { - if (event.toState === null && event.fromState === 'void') { + 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.bindDocumentResizeListener(); this.bindScrollListener(); if (this.options && this.options.length) { @@ -757,7 +752,7 @@ export class Dropdown implements OnInit,AfterViewInit,AfterContentInit,AfterView this.onShow.emit(event); } - if (event.toState === 'void' && event.fromState === null) { + if (event.toState === 'void') { this.onOverlayHide(); this.onHide.emit(event); } diff --git a/src/app/components/overlay/overlay.css b/src/app/components/overlay/overlay.css index 11ab7a7e6c9..a7fc4f67c0d 100644 --- a/src/app/components/overlay/overlay.css +++ b/src/app/components/overlay/overlay.css @@ -1,11 +1,12 @@ .p-overlay-mask { - display: flex; - justify-content: center; position: fixed; top: 0; left: 0; width: 100%; height: 100%; + display: flex; + justify-content: center; + align-items: center; } .p-overlay-responsive { @@ -16,13 +17,21 @@ } .p-overlay-panel-start { - align-self: flex-start; + align-items: flex-start; } .p-overlay-panel-center { - align-self: center; + align-items: center; } .p-overlay-panel-end { - align-self: flex-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 c26f2f2bfae..df3ffb421a7 100644 --- a/src/app/components/overlay/overlay.ts +++ b/src/app/components/overlay/overlay.ts @@ -1,4 +1,4 @@ -import {NgModule,Component,Input,OnDestroy,Renderer2,ElementRef,ChangeDetectionStrategy, ViewEncapsulation, ViewChild, Inject} from '@angular/core'; +import {NgModule,Component,Input,OnDestroy,Renderer2,ElementRef,ChangeDetectionStrategy, ViewEncapsulation, ViewChild, Inject, Output, EventEmitter, ChangeDetectorRef} from '@angular/core'; import {CommonModule, DOCUMENT} from '@angular/common'; import {DomHandler} from 'primeng/dom'; import {SharedModule, PrimeNGConfig, OverlayService} from 'primeng/api'; @@ -7,7 +7,7 @@ import {trigger,style,transition,animate, animation, useAnimation} from '@angula import {ZIndexUtils} from 'primeng/utils'; const showAnimation = animation([ - style({ transform: '{{transform}}', opacity: 1 }), + style({ transform: '{{transform}}', opacity: 0 }), animate('{{showTransitionParams}}') ]); @@ -18,17 +18,20 @@ const hideAnimation = animation([ @Component({ selector: 'p-overlay', template: ` -
- +
+
+
+ +
+
- `, animations: [ trigger('overlayAnimation', [ - transition('void => visible', [ + transition(':enter', [ useAnimation(showAnimation) ]), - transition('visible => void', [ + transition(':leave', [ useAnimation(hideAnimation) ]) ]) @@ -37,8 +40,7 @@ const hideAnimation = animation([ encapsulation: ViewEncapsulation.None, styleUrls: ['./overlay.css'], host: { - 'class': 'p-element', - '[class.p-overlay-panel]': 'true' + 'class': 'p-element' } }) export class Overlay implements OnDestroy { @@ -48,27 +50,27 @@ export class Overlay implements OnDestroy { @Input() showTransitionOptions: string = '.12s cubic-bezier(0, 0, 0.2, 1)'; @Input() hideTransitionOptions: string = '.1s linear'; - - @Input() panelClass: string; - - @Input() panelStyleClass: string; - - @Input() overlayAnimation: string; @Input() container: ElementRef; @Input() autoZIndex: boolean; - @Input() panelStyle: string; - - get overlayDirection(): string { - return this._overlayDirection; - } + @Output() onAnimationStart: EventEmitter = new EventEmitter(); + + @Output() onAnimationEnd: EventEmitter = new EventEmitter(); + + @Output() onOverlayHide: EventEmitter = new EventEmitter(); + + @ViewChild('overlay') overlayViewChild: ElementRef; + + @ViewChild('content') contentViewChild: ElementRef; + + @ViewChild('mask') maskViewChild: ElementRef; @Input() set overlayDirection(value: string) { - const viewport = DomHandler.getViewport(); - if (value && viewport.width < this.overlayBreakpoints) { + if (value && this.viewport.width < this.overlayBreakpoints) { this._overlayDirection = value; + switch (value) { case 'start': this.transformOptions = "translate3d(0px, -100%, 0px)"; @@ -86,6 +88,10 @@ export class Overlay implements OnDestroy { } } + get overlayDirection(): string { + return this._overlayDirection; + } + @Input() set appendTo(val) { this._appendTo = val; } @@ -93,27 +99,46 @@ export class Overlay implements OnDestroy { get appendTo(): any { return this._appendTo; } + + @Input() set visible(value: boolean) { + this._visible = value; + } + + get visible(): boolean { + return this._visible; + } - get overlayBreakpoints() { + get overlayBreakpoints(): any { return this.config.overlayOptions ? this.config.overlayOptions.breakpoint : null; } - @ViewChild('overlay') overlayViewChild: ElementRef; + get viewport(): any { + this._viewport = DomHandler.getViewport(); + return this._viewport; + } + + _visible: boolean; _overlayDirection: string; + _viewport: any; + _overlayBreakpoints: number; _appendTo: any; transformOptions: string = "scaleY(0.8)"; - mask: HTMLDivElement; + documentResizeListener: any; - maskClickListener: Function; + responsive: boolean; - constructor(@Inject(DOCUMENT) private document: Document, public el: ElementRef, public renderer: Renderer2, private config: PrimeNGConfig, public overlayService: OverlayService) {} + constructor(@Inject(DOCUMENT) private document: Document, public el: ElementRef, public renderer: Renderer2, private config: PrimeNGConfig, public overlayService: OverlayService, private cd: ChangeDetectorRef) {} + ngOnInit() { + this.bindDocumentResizeListener(); + } + onOverlayClick(event: MouseEvent) { this.overlayService.add({ originalEvent: event, @@ -121,86 +146,71 @@ export class Overlay implements OnDestroy { }); } + onOverlayAnimationStart(event) { + this.onAnimationStart.emit(event); + } + + onOverlayAnimationEnd(event) { + if (event.toState === 'void') { + this.destroyOverlay(); + } + this.onAnimationEnd.emit(event); + } + appendOverlay() { - const viewport = DomHandler.getViewport(); if (this.autoZIndex) { - ZIndexUtils.set('overlay', this.overlayViewChild.nativeElement, this.baseZIndex + this.config.zIndex.overlay); + ZIndexUtils.set('modal', this.el.nativeElement, this.baseZIndex + this.config.zIndex.modal); } - if (viewport.width < this.overlayBreakpoints) { - this.appendMask(); + 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'); - this.mask.appendChild(this.el.nativeElement); + 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.el.nativeElement); + this.document.body.appendChild(this.overlayViewChild.nativeElement); } else { - DomHandler.appendChild(this.el.nativeElement, this.appendTo); + DomHandler.appendChild(this.overlayViewChild.nativeElement, this.appendTo); } } } - - if (!this.overlayViewChild.nativeElement.style.minWidth) { - this.overlayViewChild.nativeElement.style.minWidth = DomHandler.getWidth(this.container) + 'px'; + if (!this.contentViewChild.nativeElement.style.minWidth) { + this.contentViewChild.nativeElement.style.minWidth = DomHandler.getWidth(this.container) + 'px'; } } alignOverlay() { - const viewport = DomHandler.getViewport(); - if(this.overlayViewChild) { - if (viewport.width < this.overlayBreakpoints) { + if (this.viewport.width < this.overlayBreakpoints) { switch (this.overlayDirection) { case 'start': - DomHandler.addClass(this.el.nativeElement, 'p-overlay-panel-start') + DomHandler.addClass(this.maskViewChild.nativeElement, 'p-overlay-panel-start') break; case 'center': - DomHandler.addClass(this.el.nativeElement, 'p-overlay-panel-center') + DomHandler.addClass(this.maskViewChild.nativeElement, 'p-overlay-panel-center') break; case 'end': - DomHandler.addClass(this.el.nativeElement, 'p-overlay-panel-end') + DomHandler.addClass(this.maskViewChild.nativeElement, 'p-overlay-panel-end') break; } } else { if (this.appendTo) { - DomHandler.absolutePosition(this.el.nativeElement.firstChild, this.container); + DomHandler.absolutePosition(this.overlayViewChild.nativeElement, this.container); } else { - DomHandler.relativePosition(this.el.nativeElement.firstChild, this.container); + DomHandler.relativePosition(this.overlayViewChild.nativeElement, this.container); } } } } - appendMask() { - if (!this.mask) { - this.mask = this.renderer.createElement('div'); - DomHandler.addMultipleClasses(this.mask, 'p-overlay-mask p-component-overlay-enter'); - ZIndexUtils.set('modal', this.mask, this.baseZIndex + this.config.zIndex.modal); - this.document.body.appendChild(this.mask); - this.blockScroll(); - this.bindMaskClickListener(); - } - } - - removeMask() { - this.unbindMaskClickListener(); - - if (this.mask) { - DomHandler.addClass(this.mask, 'p-component-overlay-leave'); - ZIndexUtils.clear(this.mask); - this.document.body.removeChild(this.mask); - this.mask = null; - this.unblockScroll(); - } - } - blockScroll() { DomHandler.addClass(this.document.body, 'p-overflow-hidden'); } @@ -209,34 +219,41 @@ export class Overlay implements OnDestroy { DomHandler.removeClass(this.document.body, 'p-overflow-hidden'); } - bindMaskClickListener() { - if (!this.maskClickListener) { - this.maskClickListener = this.renderer.listen(this.mask, 'click', () => { - this.removeMask(); - }); + bindDocumentResizeListener() { + if (!this.documentResizeListener) { + this.documentResizeListener = this.renderer.listen(window, 'resize', this.onWindowResize.bind(this)); } } - unbindMaskClickListener() { - if (this.maskClickListener) { - this.maskClickListener(); - this.maskClickListener = null; + unbindDocumentResizeListener() { + if (this.documentResizeListener) { + this.documentResizeListener(); + this.documentResizeListener = null; } } - - ngOnDestroy() { - if (this.maskClickListener) { - this.unbindMaskClickListener(); + + onWindowResize() { + if (this.visible) { + this.visible = false; + this.onOverlayHide.emit({visible: this.visible}); + this.cd.markForCheck(); } + } + + destroyOverlay() { + this.unblockScroll(); + this.unbindDocumentResizeListener(); - if (this.overlayViewChild && this.el.nativeElement) { - ZIndexUtils.clear(this.overlayViewChild.nativeElement); + if (this.overlayViewChild && this.overlayViewChild.nativeElement) { + ZIndexUtils.clear(this.el.nativeElement); this.overlayViewChild = null; } - if(this.mask) { - this.removeMask(); - } + this.onOverlayHide.emit({visible: this.visible}); + } + + ngOnDestroy() { + this.destroyOverlay(); } }