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..8f252f24fc8 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,87 +87,79 @@ export class DropdownItem {
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
- -
- {{getOptionGroupLabel(optgroup)||'empty'}}
-
-
-
+
+
+
-
-
-
-
-
-
-
-
- -
-
- {{emptyFilterMessageLabel}}
+
+
+
+
+
+
+
+
+
+ -
+ {{getOptionGroupLabel(optgroup)||'empty'}}
+
+
+
+
-
-
- -
-
- {{emptyMessageLabel}}
+
+
-
-
-
-
+
+
+
+
+
+
+
+ {{emptyFilterMessageLabel}}
+
+
+
+
+
+ {{emptyMessageLabel}}
+
+
+
+
+
+
+
-
-
+
`,
- animations: [
- trigger('overlayAnimation', [
- transition(':enter', [
- style({opacity: 0, transform: 'scaleY(0.8)'}),
- animate('{{showTransitionParams}}')
- ]),
- transition(':leave', [
- animate('{{hideTransitionParams}}', style({ opacity: 0 }))
- ])
- ])
- ],
+
host: {
'class': 'p-element p-inputwrapper',
'[class.p-inputwrapper-filled]': 'filled',
@@ -279,6 +272,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 +304,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 +338,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 +416,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 +596,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 +677,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 +684,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 +718,49 @@ 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 === '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) {
+ 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') {
+ 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 +770,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 +1237,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 +1246,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..a7fc4f67c0d
--- /dev/null
+++ b/src/app/components/overlay/overlay.css
@@ -0,0 +1,37 @@
+.p-overlay-mask {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.p-overlay-responsive {
+ overflow: hidden;
+ position: static;
+ height: fit-content;
+ width: inherit;
+}
+
+.p-overlay-panel-start {
+ align-items: flex-start;
+}
+
+.p-overlay-panel-center {
+ align-items: center;
+}
+
+.p-overlay-panel-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
new file mode 100644
index 00000000000..df3ffb421a7
--- /dev/null
+++ b/src/app/components/overlay/overlay.ts
@@ -0,0 +1,266 @@
+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';
+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: 0 }),
+ animate('{{showTransitionParams}}')
+]);
+
+const hideAnimation = animation([
+ animate('{{hideTransitionParams}}', style({ transform: '{{transform}}', opacity: 0 }))
+]);
+
+@Component({
+ selector: 'p-overlay',
+ template: `
+
+ `,
+ animations: [
+ trigger('overlayAnimation', [
+ transition(':enter', [
+ useAnimation(showAnimation)
+ ]),
+ transition(':leave', [
+ useAnimation(hideAnimation)
+ ])
+ ])
+ ],
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ encapsulation: ViewEncapsulation.None,
+ 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() hideTransitionOptions: string = '.1s linear';
+
+ @Input() container: ElementRef;
+
+ @Input() autoZIndex: boolean;
+
+ @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) {
+ 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)";
+ }
+ }
+
+ get overlayDirection(): string {
+ return this._overlayDirection;
+ }
+
+ @Input() set appendTo(val) {
+ this._appendTo = val;
+ }
+
+ get appendTo(): any {
+ return this._appendTo;
+ }
+
+ @Input() set visible(value: boolean) {
+ this._visible = value;
+ }
+
+ get visible(): boolean {
+ return this._visible;
+ }
+
+ get overlayBreakpoints(): any {
+ return this.config.overlayOptions ? this.config.overlayOptions.breakpoint : null;
+ }
+
+ 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)";
+
+ documentResizeListener: any;
+
+ responsive: boolean;
+
+ 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,
+ target: this.el.nativeElement
+ });
+ }
+
+ onOverlayAnimationStart(event) {
+ this.onAnimationStart.emit(event);
+ }
+
+ onOverlayAnimationEnd(event) {
+ if (event.toState === 'void') {
+ this.destroyOverlay();
+ }
+ this.onAnimationEnd.emit(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);
+ }
+ }
+ }
+ if (!this.contentViewChild.nativeElement.style.minWidth) {
+ this.contentViewChild.nativeElement.style.minWidth = DomHandler.getWidth(this.container) + 'px';
+ }
+ }
+
+ 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);
+ }
+ }
+ }
+ }
+
+ blockScroll() {
+ DomHandler.addClass(this.document.body, 'p-overflow-hidden');
+ }
+
+ unblockScroll() {
+ DomHandler.removeClass(this.document.body, 'p-overflow-hidden');
+ }
+
+ bindDocumentResizeListener() {
+ if (!this.documentResizeListener) {
+ this.documentResizeListener = this.renderer.listen(window, 'resize', this.onWindowResize.bind(this));
+ }
+ }
+
+ unbindDocumentResizeListener() {
+ if (this.documentResizeListener) {
+ this.documentResizeListener();
+ this.documentResizeListener = null;
+ }
+ }
+
+ 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.overlayViewChild.nativeElement) {
+ ZIndexUtils.clear(this.el.nativeElement);
+ this.overlayViewChild = null;
+ }
+
+ this.onOverlayHide.emit({visible: this.visible});
+ }
+
+ ngOnDestroy() {
+ this.destroyOverlay();
+ }
+
+}
+
+@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