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 {
-
+
`,
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();
}
}