1 | import { Component, ViewEncapsulation, Inject, forwardRef, Input, EventEmitter, ChangeDetectionStrategy, ElementRef, Renderer2, NgZone, Output, ViewChild, NgModule } from '@angular/core';
|
2 | import { CommonModule } from '@angular/common';
|
3 | import { DomHandler } from 'primeng/dom';
|
4 | import { RippleModule } from 'primeng/ripple';
|
5 | import { RouterModule } from '@angular/router';
|
6 |
|
7 | class ContextMenuSub {
|
8 | constructor(contextMenu) {
|
9 | this.contextMenu = contextMenu;
|
10 | }
|
11 | get parentActive() {
|
12 | return this._parentActive;
|
13 | }
|
14 | set parentActive(value) {
|
15 | this._parentActive = value;
|
16 | if (!value) {
|
17 | this.activeItem = null;
|
18 | }
|
19 | }
|
20 | onItemMouseEnter(event, item, menuitem) {
|
21 | if (this.hideTimeout) {
|
22 | clearTimeout(this.hideTimeout);
|
23 | this.hideTimeout = null;
|
24 | }
|
25 | if (menuitem.disabled) {
|
26 | return;
|
27 | }
|
28 | this.activeItem = item;
|
29 | let nextElement = item.children[0].nextElementSibling;
|
30 | if (nextElement) {
|
31 | let sublist = nextElement.children[0];
|
32 | sublist.style.zIndex = ++DomHandler.zindex;
|
33 | this.position(sublist, item);
|
34 | }
|
35 | }
|
36 | itemClick(event, item) {
|
37 | if (item.disabled) {
|
38 | event.preventDefault();
|
39 | return;
|
40 | }
|
41 | if (item.command) {
|
42 | item.command({
|
43 | originalEvent: event,
|
44 | item: item
|
45 | });
|
46 | event.preventDefault();
|
47 | }
|
48 | if (item.items)
|
49 | event.preventDefault();
|
50 | else
|
51 | this.contextMenu.hide();
|
52 | }
|
53 | listClick(event) {
|
54 | this.activeItem = null;
|
55 | }
|
56 | position(sublist, item) {
|
57 | this.containerOffset = DomHandler.getOffset(item.parentElement);
|
58 | let viewport = DomHandler.getViewport();
|
59 | let sublistWidth = sublist.offsetParent ? sublist.offsetWidth : DomHandler.getHiddenElementOuterWidth(sublist);
|
60 | let itemOuterWidth = DomHandler.getOuterWidth(item.children[0]);
|
61 | let itemOuterHeight = DomHandler.getOuterHeight(item.children[0]);
|
62 | let sublistHeight = sublist.offsetHeight ? sublist.offsetHeight : DomHandler.getHiddenElementOuterHeight(sublist);
|
63 | if ((parseInt(this.containerOffset.top) + itemOuterHeight + sublistHeight) > (viewport.height - DomHandler.calculateScrollbarHeight())) {
|
64 | sublist.style.removeProperty('top');
|
65 | sublist.style.bottom = '0px';
|
66 | }
|
67 | else {
|
68 | sublist.style.removeProperty('bottom');
|
69 | sublist.style.top = '0px';
|
70 | }
|
71 | if ((parseInt(this.containerOffset.left) + itemOuterWidth + sublistWidth) > (viewport.width - DomHandler.calculateScrollbarWidth())) {
|
72 | sublist.style.left = -sublistWidth + 'px';
|
73 | }
|
74 | else {
|
75 | sublist.style.left = itemOuterWidth + 'px';
|
76 | }
|
77 | }
|
78 | }
|
79 | ContextMenuSub.decorators = [
|
80 | { type: Component, args: [{
|
81 | selector: 'p-contextMenuSub',
|
82 | template: `
|
83 | <ul [ngClass]="{'p-submenu-list':!root}">
|
84 | <ng-template ngFor let-child [ngForOf]="(root ? item : item.items)">
|
85 | <li *ngIf="child.separator" class="p-menu-separator" [ngClass]="{'p-hidden': child.visible === false}" role="separator">
|
86 | <li *ngIf="!child.separator" #item [ngClass]="{'p-menuitem':true,'p-menuitem-active':item==activeItem,'p-hidden': child.visible === false}"
|
87 | (mouseenter)="onItemMouseEnter($event,item,child)" role="none">
|
88 | <a *ngIf="!child.routerLink" [attr.href]="child.url ? child.url : null" [attr.target]="child.target" [attr.title]="child.title" [attr.id]="child.id" [attr.tabindex]="child.disabled ? null : '0'" (click)="itemClick($event, child)"
|
89 | [ngClass]="{'p-menuitem-link':true,'p-disabled':child.disabled}" [ngStyle]="child.style" [class]="child.styleClass" pRipple
|
90 | [attr.aria-haspopup]="item.items != null" [attr.aria-expanded]="item === activeItem">
|
91 | <span class="p-menuitem-icon" *ngIf="child.icon" [ngClass]="child.icon"></span>
|
92 | <span class="p-menuitem-text" *ngIf="child.escape !== false; else htmlLabel">{{child.label}}</span>
|
93 | <ng-template #htmlLabel><span class="p-menuitem-text" [innerHTML]="child.label"></span></ng-template>
|
94 | <span class="p-submenu-icon pi pi-angle-right" *ngIf="child.items"></span>
|
95 | </a>
|
96 | <a *ngIf="child.routerLink" [routerLink]="child.routerLink" [queryParams]="child.queryParams" [routerLinkActive]="'p-menuitem-link-active'" role="menuitem"
|
97 | [routerLinkActiveOptions]="child.routerLinkActiveOptions||{exact:false}" [attr.target]="child.target" [attr.title]="child.title" [attr.id]="child.id" [attr.tabindex]="child.disabled ? null : '0'"
|
98 | (click)="itemClick($event, child)" [ngClass]="{'p-menuitem-link':true,'p-disabled':child.disabled}"
|
99 | [ngStyle]="child.style" [class]="child.styleClass" pRipple
|
100 | [fragment]="child.fragment" [queryParamsHandling]="child.queryParamsHandling" [preserveFragment]="child.preserveFragment" [skipLocationChange]="child.skipLocationChange" [replaceUrl]="child.replaceUrl" [state]="child.state">
|
101 | <span class="p-menuitem-icon" *ngIf="child.icon" [ngClass]="child.icon"></span>
|
102 | <span class="p-menuitem-text" *ngIf="child.escape !== false; else htmlRouteLabel">{{child.label}}</span>
|
103 | <ng-template #htmlRouteLabel><span class="p-menuitem-text" [innerHTML]="child.label"></span></ng-template>
|
104 | <span class="p-submenu-icon pi pi-angle-right" *ngIf="child.items"></span>
|
105 | </a>
|
106 | <p-contextMenuSub [parentActive]="item==activeItem" [item]="child" *ngIf="child.items"></p-contextMenuSub>
|
107 | </li>
|
108 | </ng-template>
|
109 | </ul>
|
110 | `,
|
111 | encapsulation: ViewEncapsulation.None
|
112 | },] }
|
113 | ];
|
114 | ContextMenuSub.ctorParameters = () => [
|
115 | { type: undefined, decorators: [{ type: Inject, args: [forwardRef(() => ContextMenu),] }] }
|
116 | ];
|
117 | ContextMenuSub.propDecorators = {
|
118 | item: [{ type: Input }],
|
119 | root: [{ type: Input }],
|
120 | parentActive: [{ type: Input }]
|
121 | };
|
122 | class ContextMenu {
|
123 | constructor(el, renderer, zone) {
|
124 | this.el = el;
|
125 | this.renderer = renderer;
|
126 | this.zone = zone;
|
127 | this.autoZIndex = true;
|
128 | this.baseZIndex = 0;
|
129 | this.triggerEvent = 'contextmenu';
|
130 | this.onShow = new EventEmitter();
|
131 | this.onHide = new EventEmitter();
|
132 | }
|
133 | ngAfterViewInit() {
|
134 | if (this.global) {
|
135 | const documentTarget = this.el ? this.el.nativeElement.ownerDocument : 'document';
|
136 | this.triggerEventListener = this.renderer.listen(documentTarget, this.triggerEvent, (event) => {
|
137 | this.show(event);
|
138 | event.preventDefault();
|
139 | });
|
140 | }
|
141 | else if (this.target) {
|
142 | this.triggerEventListener = this.renderer.listen(this.target, this.triggerEvent, (event) => {
|
143 | this.show(event);
|
144 | event.preventDefault();
|
145 | event.stopPropagation();
|
146 | });
|
147 | }
|
148 | if (this.appendTo) {
|
149 | if (this.appendTo === 'body')
|
150 | document.body.appendChild(this.containerViewChild.nativeElement);
|
151 | else
|
152 | DomHandler.appendChild(this.containerViewChild.nativeElement, this.appendTo);
|
153 | }
|
154 | }
|
155 | show(event) {
|
156 | this.position(event);
|
157 | this.moveOnTop();
|
158 | this.containerViewChild.nativeElement.style.display = 'block';
|
159 | this.parentActive = true;
|
160 | DomHandler.fadeIn(this.containerViewChild.nativeElement, 250);
|
161 | this.bindGlobalListeners();
|
162 | if (event) {
|
163 | event.preventDefault();
|
164 | }
|
165 | this.onShow.emit();
|
166 | }
|
167 | hide() {
|
168 | this.containerViewChild.nativeElement.style.display = 'none';
|
169 | this.parentActive = false;
|
170 | this.unbindGlobalListeners();
|
171 | this.onHide.emit();
|
172 | }
|
173 | moveOnTop() {
|
174 | if (this.autoZIndex) {
|
175 | this.containerViewChild.nativeElement.style.zIndex = String(this.baseZIndex + (++DomHandler.zindex));
|
176 | }
|
177 | }
|
178 | toggle(event) {
|
179 | if (this.containerViewChild.nativeElement.offsetParent)
|
180 | this.hide();
|
181 | else
|
182 | this.show(event);
|
183 | }
|
184 | position(event) {
|
185 | if (event) {
|
186 | let left = event.pageX + 1;
|
187 | let top = event.pageY + 1;
|
188 | let width = this.containerViewChild.nativeElement.offsetParent ? this.containerViewChild.nativeElement.offsetWidth : DomHandler.getHiddenElementOuterWidth(this.containerViewChild.nativeElement);
|
189 | let height = this.containerViewChild.nativeElement.offsetParent ? this.containerViewChild.nativeElement.offsetHeight : DomHandler.getHiddenElementOuterHeight(this.containerViewChild.nativeElement);
|
190 | let viewport = DomHandler.getViewport();
|
191 |
|
192 | if (left + width - document.body.scrollLeft > viewport.width) {
|
193 | left -= width;
|
194 | }
|
195 |
|
196 | if (top + height - document.body.scrollTop > viewport.height) {
|
197 | top -= height;
|
198 | }
|
199 |
|
200 | if (left < document.body.scrollLeft) {
|
201 | left = document.body.scrollLeft;
|
202 | }
|
203 |
|
204 | if (top < document.body.scrollTop) {
|
205 | top = document.body.scrollTop;
|
206 | }
|
207 | this.containerViewChild.nativeElement.style.left = left + 'px';
|
208 | this.containerViewChild.nativeElement.style.top = top + 'px';
|
209 | }
|
210 | }
|
211 | bindGlobalListeners() {
|
212 | if (!this.documentClickListener) {
|
213 | const documentTarget = this.el ? this.el.nativeElement.ownerDocument : 'document';
|
214 | this.documentClickListener = this.renderer.listen(documentTarget, 'click', (event) => {
|
215 | if (this.containerViewChild.nativeElement.offsetParent && this.isOutsideClicked(event) && event.button !== 2) {
|
216 | this.hide();
|
217 | }
|
218 | });
|
219 | }
|
220 | this.zone.runOutsideAngular(() => {
|
221 | if (!this.windowResizeListener) {
|
222 | this.windowResizeListener = this.onWindowResize.bind(this);
|
223 | window.addEventListener('resize', this.windowResizeListener);
|
224 | }
|
225 | });
|
226 | }
|
227 | unbindGlobalListeners() {
|
228 | if (this.documentClickListener) {
|
229 | this.documentClickListener();
|
230 | this.documentClickListener = null;
|
231 | }
|
232 | if (this.windowResizeListener) {
|
233 | window.removeEventListener('resize', this.windowResizeListener);
|
234 | this.windowResizeListener = null;
|
235 | }
|
236 | }
|
237 | onWindowResize(event) {
|
238 | if (this.containerViewChild.nativeElement.offsetParent) {
|
239 | this.hide();
|
240 | }
|
241 | }
|
242 | isOutsideClicked(event) {
|
243 | return !(this.containerViewChild.nativeElement.isSameNode(event.target) || this.containerViewChild.nativeElement.contains(event.target));
|
244 | }
|
245 | ngOnDestroy() {
|
246 | this.unbindGlobalListeners();
|
247 | if (this.triggerEventListener) {
|
248 | this.triggerEventListener();
|
249 | }
|
250 | if (this.appendTo) {
|
251 | this.el.nativeElement.appendChild(this.containerViewChild.nativeElement);
|
252 | }
|
253 | }
|
254 | }
|
255 | ContextMenu.decorators = [
|
256 | { type: Component, args: [{
|
257 | selector: 'p-contextMenu',
|
258 | template: `
|
259 | <div #container [ngClass]="'p-contextmenu p-component'"
|
260 | [class]="styleClass" [ngStyle]="style">
|
261 | <p-contextMenuSub [item]="model" [parentActive]="parentActive" root="root"></p-contextMenuSub>
|
262 | </div>
|
263 | `,
|
264 | changeDetection: ChangeDetectionStrategy.OnPush,
|
265 | encapsulation: ViewEncapsulation.None,
|
266 | styles: [".p-contextmenu{display:none;position:absolute}.p-contextmenu ul{list-style:none;margin:0;padding:0}.p-contextmenu .p-submenu-list{display:none;min-width:100%;position:absolute;z-index:1}.p-contextmenu .p-menuitem-link{-ms-flex-align:center;align-items:center;cursor:pointer;display:-ms-flexbox;display:flex;overflow:hidden;position:relative;text-decoration:none}.p-contextmenu .p-menuitem-text{line-height:1}.p-contextmenu .p-menuitem{position:relative}.p-contextmenu .p-menuitem-link .p-submenu-icon{margin-left:auto}.p-contextmenu .p-menuitem-active>p-contextmenusub>.p-submenu-list{display:block!important}"]
|
267 | },] }
|
268 | ];
|
269 | ContextMenu.ctorParameters = () => [
|
270 | { type: ElementRef },
|
271 | { type: Renderer2 },
|
272 | { type: NgZone }
|
273 | ];
|
274 | ContextMenu.propDecorators = {
|
275 | model: [{ type: Input }],
|
276 | global: [{ type: Input }],
|
277 | target: [{ type: Input }],
|
278 | style: [{ type: Input }],
|
279 | styleClass: [{ type: Input }],
|
280 | appendTo: [{ type: Input }],
|
281 | autoZIndex: [{ type: Input }],
|
282 | baseZIndex: [{ type: Input }],
|
283 | triggerEvent: [{ type: Input }],
|
284 | onShow: [{ type: Output }],
|
285 | onHide: [{ type: Output }],
|
286 | containerViewChild: [{ type: ViewChild, args: ['container',] }]
|
287 | };
|
288 | class ContextMenuModule {
|
289 | }
|
290 | ContextMenuModule.decorators = [
|
291 | { type: NgModule, args: [{
|
292 | imports: [CommonModule, RouterModule, RippleModule],
|
293 | exports: [ContextMenu, RouterModule],
|
294 | declarations: [ContextMenu, ContextMenuSub]
|
295 | },] }
|
296 | ];
|
297 |
|
298 |
|
299 |
|
300 |
|
301 |
|
302 | export { ContextMenu, ContextMenuModule, ContextMenuSub };
|
303 |
|