Skip to content

Commit a762acd

Browse files
[DSC-1874] Improve related-items popover open/close behaviour
1 parent 640dc02 commit a762acd

3 files changed

Lines changed: 128 additions & 5 deletions

File tree

src/app/shared/metadata-link-view/metadata-link-view.component.html

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,12 @@
33
[ngTemplateOutletContext]="{metadataView: metadataView}"></ng-container>
44
</div>
55
<ng-template class="d-flex" #linkToAuthority let-metadataView="metadataView">
6-
<span [ngbPopover]="popContent"
7-
[closeDelay]="500"
8-
[openDelay]="1000"
6+
<span [dsStickyPopover]="popContent"
7+
[openDelay]="100"
98
[animation]="true"
10-
[autoClose]="'outside'"
9+
[autoClose]="true"
1110
container="body"
12-
triggers="mouseenter:mouseleave">
11+
triggers="mouseenter">
1312
<a rel="noopener noreferrer" data-test="linkToAuthority"
1413
[routerLink]="['/items/' + metadataView.authority]">
1514
<span dsEntityIcon
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import {
2+
ElementRef,
3+
Directive,
4+
Input,
5+
TemplateRef,
6+
Renderer2,
7+
OnInit,
8+
OnDestroy,
9+
Injector,
10+
ViewContainerRef,
11+
NgZone,
12+
ChangeDetectorRef,
13+
ApplicationRef, Inject
14+
} from '@angular/core';
15+
import {NgbPopover, NgbPopoverConfig} from '@ng-bootstrap/ng-bootstrap';
16+
import {DOCUMENT} from '@angular/common';
17+
import {NavigationStart, Router} from '@angular/router';
18+
import {Subscription} from 'rxjs';
19+
20+
/**
21+
* Directive to create a sticky popover using NgbPopover.
22+
* The popover remains open when the mouse is over its content and closes when the mouse leaves.
23+
*/
24+
@Directive({
25+
selector: '[dsStickyPopover]'
26+
})
27+
export class StickyPopoverDirective extends NgbPopover implements OnInit, OnDestroy {
28+
/** Template for the sticky popover content */
29+
@Input() dsStickyPopover: TemplateRef<any>;
30+
31+
/** Subscriptions to manage router events */
32+
subs: Subscription[] = [];
33+
34+
/** Flag to determine if the popover can be closed */
35+
private canClosePopover: boolean;
36+
37+
/** Reference to the element the directive is applied to */
38+
private readonly _elRef;
39+
40+
/** Renderer to listen to and manipulate DOM elements */
41+
private readonly _render;
42+
43+
constructor(
44+
_elementRef: ElementRef<HTMLElement>,
45+
_renderer: Renderer2, injector: Injector,
46+
viewContainerRef: ViewContainerRef,
47+
config: NgbPopoverConfig,
48+
_ngZone: NgZone,
49+
@Inject(DOCUMENT) _document: Document,
50+
_changeDetector: ChangeDetectorRef,
51+
applicationRef: ApplicationRef,
52+
private router: Router
53+
) {
54+
super(_elementRef, _renderer, injector, viewContainerRef, config, _ngZone, document, _changeDetector, applicationRef);
55+
this._elRef = _elementRef;
56+
this._render = _renderer;
57+
this.triggers = 'manual';
58+
this.popoverTitle = 'Permissions';
59+
this.container = 'body';
60+
}
61+
62+
/**
63+
* Sets up event listeners for mouse enter, mouse leave, and click events.
64+
*/
65+
ngOnInit(): void {
66+
super.ngOnInit();
67+
this.ngbPopover = this.dsStickyPopover;
68+
69+
this._render.listen(this._elRef.nativeElement, 'mouseenter', () => {
70+
this.canClosePopover = true;
71+
this.open();
72+
});
73+
74+
this._render.listen(this._elRef.nativeElement, 'mouseleave', () => {
75+
setTimeout(() => {
76+
if (this.canClosePopover) {
77+
this.close();
78+
}
79+
}, 100);
80+
});
81+
82+
this._render.listen(this._elRef.nativeElement, 'click', () => {
83+
this.close();
84+
});
85+
86+
this.subs.push(
87+
this.router.events.subscribe((event) => {
88+
if (event instanceof NavigationStart) {
89+
this.close();
90+
}
91+
})
92+
);
93+
}
94+
95+
/**
96+
* Opens the popover and sets up event listeners for mouse over and mouse out events on the popover.
97+
*/
98+
open() {
99+
super.open();
100+
const popover = window.document.querySelector('.popover');
101+
this._render.listen(popover, 'mouseover', () => {
102+
this.canClosePopover = false;
103+
});
104+
105+
this._render.listen(popover, 'mouseout', () => {
106+
this.canClosePopover = true;
107+
setTimeout(() => {
108+
if (this.canClosePopover) {
109+
this.close();
110+
}
111+
}, 0);
112+
});
113+
}
114+
115+
/**
116+
* Unsubscribes from all subscriptions when the directive is destroyed.
117+
*/
118+
ngOnDestroy() {
119+
super.ngOnDestroy();
120+
this.subs.forEach((sub) => sub.unsubscribe());
121+
}
122+
}

src/app/shared/shared.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,7 @@ import { ThemedDefaultBrowseElementsComponent } from './browse-most-elements/def
357357
import { MetadataLinkViewPopoverComponent } from './metadata-link-view/metadata-link-view-popover/metadata-link-view-popover.component';
358358
import { MetadataLinkViewAvatarPopoverComponent } from './metadata-link-view/metadata-link-view-avatar-popover/metadata-link-view-avatar-popover.component';
359359
import { MetadataLinkViewOrcidComponent } from './metadata-link-view/metadata-link-view-orcid/metadata-link-view-orcid.component';
360+
import {StickyPopoverDirective} from './metadata-link-view/sticky-popover.directive';
360361

361362
const MODULES = [
362363
CommonModule,
@@ -623,6 +624,7 @@ const DIRECTIVES = [
623624
ContextHelpDirective,
624625
EntityIconDirective,
625626
MarkdownDirective,
627+
StickyPopoverDirective
626628
];
627629

628630
@NgModule({

0 commit comments

Comments
 (0)