@firestitch/popover 18.0.2 → 18.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/class/popover-ref.d.ts +11 -4
- package/app/components/popover/popover.component.d.ts +1 -1
- package/app/directives/popover.directive.d.ts +48 -1
- package/app/helpers/create-overlay-ref.d.ts +6 -2
- package/app/helpers/has-overlay-above.d.ts +10 -0
- package/esm2022/app/class/popover-ref.mjs +11 -2
- package/esm2022/app/components/popover/popover.component.mjs +3 -3
- package/esm2022/app/directives/popover.directive.mjs +158 -6
- package/esm2022/app/helpers/create-overlay-ref.mjs +11 -6
- package/esm2022/app/helpers/has-overlay-above.mjs +24 -0
- package/fesm2022/firestitch-popover.mjs +219 -37
- package/fesm2022/firestitch-popover.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import { OverlayConfig } from '@angular/cdk/overlay';
|
|
2
2
|
import { createPopupPositionStrategy } from './create-position-strategy';
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Hover and click popovers have no backdrop. A panel gets a transparent backdrop, so a click
|
|
5
|
+
* anywhere outside it closes it, and the `fs-popover-panel` class on its pane.
|
|
6
|
+
*/
|
|
7
|
+
export function createOverlayRef(el, overlay, position, panel = false) {
|
|
8
|
+
const overlayConfig = new OverlayConfig({
|
|
5
9
|
positionStrategy: createPopupPositionStrategy(el, overlay, position),
|
|
6
10
|
scrollStrategy: overlay.scrollStrategies.reposition(),
|
|
7
|
-
hasBackdrop:
|
|
8
|
-
|
|
9
|
-
|
|
11
|
+
hasBackdrop: panel,
|
|
12
|
+
backdropClass: panel ? 'cdk-overlay-transparent-backdrop' : undefined,
|
|
13
|
+
panelClass: panel ? 'fs-popover-panel' : undefined,
|
|
14
|
+
});
|
|
10
15
|
return overlay.create(overlayConfig);
|
|
11
16
|
}
|
|
12
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
17
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY3JlYXRlLW92ZXJsYXktcmVmLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL2FwcC9oZWxwZXJzL2NyZWF0ZS1vdmVybGF5LXJlZi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFFQSxPQUFPLEVBQVcsYUFBYSxFQUFjLE1BQU0sc0JBQXNCLENBQUM7QUFHMUUsT0FBTyxFQUFFLDJCQUEyQixFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFHekU7OztHQUdHO0FBQ0gsTUFBTSxVQUFVLGdCQUFnQixDQUM5QixFQUFjLEVBQ2QsT0FBZ0IsRUFDaEIsUUFBa0IsRUFDbEIsS0FBSyxHQUFHLEtBQUs7SUFFYixNQUFNLGFBQWEsR0FBRyxJQUFJLGFBQWEsQ0FBQztRQUN0QyxnQkFBZ0IsRUFBRSwyQkFBMkIsQ0FBQyxFQUFFLEVBQUUsT0FBTyxFQUFFLFFBQVEsQ0FBQztRQUNwRSxjQUFjLEVBQUUsT0FBTyxDQUFDLGdCQUFnQixDQUFDLFVBQVUsRUFBRTtRQUNyRCxXQUFXLEVBQUUsS0FBSztRQUNsQixhQUFhLEVBQUUsS0FBSyxDQUFDLENBQUMsQ0FBQyxrQ0FBa0MsQ0FBQyxDQUFDLENBQUMsU0FBUztRQUNyRSxVQUFVLEVBQUUsS0FBSyxDQUFDLENBQUMsQ0FBQyxrQkFBa0IsQ0FBQyxDQUFDLENBQUMsU0FBUztLQUNuRCxDQUFDLENBQUM7SUFFSCxPQUFPLE9BQU8sQ0FBQyxNQUFNLENBQUMsYUFBYSxDQUFDLENBQUM7QUFDdkMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IEVsZW1lbnRSZWYgfSBmcm9tICdAYW5ndWxhci9jb3JlJztcblxuaW1wb3J0IHsgT3ZlcmxheSwgT3ZlcmxheUNvbmZpZywgT3ZlcmxheVJlZiB9IGZyb20gJ0Bhbmd1bGFyL2Nkay9vdmVybGF5JztcblxuaW1wb3J0IHsgUG9zaXRpb24gfSBmcm9tICcuLy4uL2VudW1zL3Bvc2l0aW9uJztcbmltcG9ydCB7IGNyZWF0ZVBvcHVwUG9zaXRpb25TdHJhdGVneSB9IGZyb20gJy4vY3JlYXRlLXBvc2l0aW9uLXN0cmF0ZWd5JztcblxuXG4vKipcbiAqIEhvdmVyIGFuZCBjbGljayBwb3BvdmVycyBoYXZlIG5vIGJhY2tkcm9wLiBBIHBhbmVsIGdldHMgYSB0cmFuc3BhcmVudCBiYWNrZHJvcCwgc28gYSBjbGlja1xuICogYW55d2hlcmUgb3V0c2lkZSBpdCBjbG9zZXMgaXQsIGFuZCB0aGUgYGZzLXBvcG92ZXItcGFuZWxgIGNsYXNzIG9uIGl0cyBwYW5lLlxuICovXG5leHBvcnQgZnVuY3Rpb24gY3JlYXRlT3ZlcmxheVJlZihcbiAgZWw6IEVsZW1lbnRSZWYsXG4gIG92ZXJsYXk6IE92ZXJsYXksXG4gIHBvc2l0aW9uOiBQb3NpdGlvbixcbiAgcGFuZWwgPSBmYWxzZSxcbik6IE92ZXJsYXlSZWYge1xuICBjb25zdCBvdmVybGF5Q29uZmlnID0gbmV3IE92ZXJsYXlDb25maWcoe1xuICAgIHBvc2l0aW9uU3RyYXRlZ3k6IGNyZWF0ZVBvcHVwUG9zaXRpb25TdHJhdGVneShlbCwgb3ZlcmxheSwgcG9zaXRpb24pLFxuICAgIHNjcm9sbFN0cmF0ZWd5OiBvdmVybGF5LnNjcm9sbFN0cmF0ZWdpZXMucmVwb3NpdGlvbigpLFxuICAgIGhhc0JhY2tkcm9wOiBwYW5lbCxcbiAgICBiYWNrZHJvcENsYXNzOiBwYW5lbCA/ICdjZGstb3ZlcmxheS10cmFuc3BhcmVudC1iYWNrZHJvcCcgOiB1bmRlZmluZWQsXG4gICAgcGFuZWxDbGFzczogcGFuZWwgPyAnZnMtcG9wb3Zlci1wYW5lbCcgOiB1bmRlZmluZWQsXG4gIH0pO1xuXG4gIHJldHVybiBvdmVybGF5LmNyZWF0ZShvdmVybGF5Q29uZmlnKTtcbn1cbiJdfQ==
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* True when an overlay with a backdrop was attached after this one and still shows content:
|
|
3
|
+
* a calendar, a select, a menu or a dialog opened from inside it. Overlays without a backdrop
|
|
4
|
+
* (tooltips, toasts, hover popovers) don't count.
|
|
5
|
+
*
|
|
6
|
+
* CDK inserts each backdrop right before its overlay's host, and later overlays after earlier
|
|
7
|
+
* ones, so everything after this overlay's host was attached later.
|
|
8
|
+
*/
|
|
9
|
+
export function hasOverlayAbove(overlayRef) {
|
|
10
|
+
let element = overlayRef.hostElement?.nextElementSibling;
|
|
11
|
+
while (element) {
|
|
12
|
+
if (isShowingBackdropOverlay(element)) {
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
element = element.nextElementSibling;
|
|
16
|
+
}
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
function isShowingBackdropOverlay(host) {
|
|
20
|
+
const pane = host.querySelector(':scope > .cdk-overlay-pane');
|
|
21
|
+
return !!pane?.childElementCount
|
|
22
|
+
&& !!host.previousElementSibling?.classList.contains('cdk-overlay-backdrop');
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaGFzLW92ZXJsYXktYWJvdmUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvYXBwL2hlbHBlcnMvaGFzLW92ZXJsYXktYWJvdmUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBR0E7Ozs7Ozs7R0FPRztBQUNILE1BQU0sVUFBVSxlQUFlLENBQUMsVUFBc0I7SUFDcEQsSUFBSSxPQUFPLEdBQUcsVUFBVSxDQUFDLFdBQVcsRUFBRSxrQkFBa0IsQ0FBQztJQUV6RCxPQUFPLE9BQU8sRUFBRSxDQUFDO1FBQ2YsSUFBSSx3QkFBd0IsQ0FBQyxPQUFPLENBQUMsRUFBRSxDQUFDO1lBQ3RDLE9BQU8sSUFBSSxDQUFDO1FBQ2QsQ0FBQztRQUVELE9BQU8sR0FBRyxPQUFPLENBQUMsa0JBQWtCLENBQUM7SUFDdkMsQ0FBQztJQUVELE9BQU8sS0FBSyxDQUFDO0FBQ2YsQ0FBQztBQUVELFNBQVMsd0JBQXdCLENBQUMsSUFBYTtJQUM3QyxNQUFNLElBQUksR0FBRyxJQUFJLENBQUMsYUFBYSxDQUFDLDRCQUE0QixDQUFDLENBQUM7SUFFOUQsT0FBTyxDQUFDLENBQUMsSUFBSSxFQUFFLGlCQUFpQjtXQUMzQixDQUFDLENBQUMsSUFBSSxDQUFDLHNCQUFzQixFQUFFLFNBQVMsQ0FBQyxRQUFRLENBQUMsc0JBQXNCLENBQUMsQ0FBQztBQUNqRixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgT3ZlcmxheVJlZiB9IGZyb20gJ0Bhbmd1bGFyL2Nkay9vdmVybGF5JztcblxuXG4vKipcbiAqIFRydWUgd2hlbiBhbiBvdmVybGF5IHdpdGggYSBiYWNrZHJvcCB3YXMgYXR0YWNoZWQgYWZ0ZXIgdGhpcyBvbmUgYW5kIHN0aWxsIHNob3dzIGNvbnRlbnQ6XG4gKiBhIGNhbGVuZGFyLCBhIHNlbGVjdCwgYSBtZW51IG9yIGEgZGlhbG9nIG9wZW5lZCBmcm9tIGluc2lkZSBpdC4gT3ZlcmxheXMgd2l0aG91dCBhIGJhY2tkcm9wXG4gKiAodG9vbHRpcHMsIHRvYXN0cywgaG92ZXIgcG9wb3ZlcnMpIGRvbid0IGNvdW50LlxuICpcbiAqIENESyBpbnNlcnRzIGVhY2ggYmFja2Ryb3AgcmlnaHQgYmVmb3JlIGl0cyBvdmVybGF5J3MgaG9zdCwgYW5kIGxhdGVyIG92ZXJsYXlzIGFmdGVyIGVhcmxpZXJcbiAqIG9uZXMsIHNvIGV2ZXJ5dGhpbmcgYWZ0ZXIgdGhpcyBvdmVybGF5J3MgaG9zdCB3YXMgYXR0YWNoZWQgbGF0ZXIuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBoYXNPdmVybGF5QWJvdmUob3ZlcmxheVJlZjogT3ZlcmxheVJlZik6IGJvb2xlYW4ge1xuICBsZXQgZWxlbWVudCA9IG92ZXJsYXlSZWYuaG9zdEVsZW1lbnQ/Lm5leHRFbGVtZW50U2libGluZztcblxuICB3aGlsZSAoZWxlbWVudCkge1xuICAgIGlmIChpc1Nob3dpbmdCYWNrZHJvcE92ZXJsYXkoZWxlbWVudCkpIHtcbiAgICAgIHJldHVybiB0cnVlO1xuICAgIH1cblxuICAgIGVsZW1lbnQgPSBlbGVtZW50Lm5leHRFbGVtZW50U2libGluZztcbiAgfVxuXG4gIHJldHVybiBmYWxzZTtcbn1cblxuZnVuY3Rpb24gaXNTaG93aW5nQmFja2Ryb3BPdmVybGF5KGhvc3Q6IEVsZW1lbnQpOiBib29sZWFuIHtcbiAgY29uc3QgcGFuZSA9IGhvc3QucXVlcnlTZWxlY3RvcignOnNjb3BlID4gLmNkay1vdmVybGF5LXBhbmUnKTtcblxuICByZXR1cm4gISFwYW5lPy5jaGlsZEVsZW1lbnRDb3VudFxuICAgICYmICEhaG9zdC5wcmV2aW91c0VsZW1lbnRTaWJsaW5nPy5jbGFzc0xpc3QuY29udGFpbnMoJ2Nkay1vdmVybGF5LWJhY2tkcm9wJyk7XG59XG4iXX0=
|
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, ElementRef, NgZone, Renderer2, Component, ChangeDetectionStrategy, ViewChild, Injector, TemplateRef, Injectable, Directive, Input, HostBinding, NgModule } from '@angular/core';
|
|
2
|
+
import { inject, ElementRef, NgZone, Renderer2, Component, ChangeDetectionStrategy, ViewChild, Injector, TemplateRef, Injectable, input, booleanAttribute, output, Directive, Input, HostBinding, NgModule } from '@angular/core';
|
|
3
|
+
import { NgClass, AsyncPipe, DOCUMENT } from '@angular/common';
|
|
3
4
|
import { Router, NavigationEnd } from '@angular/router';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { map, filter, takeUntil, debounceTime, delay, tap, switchMap, mapTo, startWith, distinctUntilChanged, withLatestFrom, finalize } from 'rxjs/operators';
|
|
5
|
+
import { ConfigurableFocusTrapFactory } from '@angular/cdk/a11y';
|
|
6
|
+
import { hasModifierKey } from '@angular/cdk/keycodes';
|
|
7
7
|
import { OverlayConfig, Overlay } from '@angular/cdk/overlay';
|
|
8
8
|
import { BasePortalOutlet, CdkPortalOutlet, TemplatePortal, ComponentPortal } from '@angular/cdk/portal';
|
|
9
|
-
import {
|
|
9
|
+
import { guid } from '@firestitch/common';
|
|
10
|
+
import { BehaviorSubject, Subject, combineLatest, fromEvent, timer, pipe, merge } from 'rxjs';
|
|
11
|
+
import { map, filter, takeUntil, debounceTime, delay, tap, switchMap, mapTo, startWith, distinctUntilChanged, withLatestFrom, finalize, take } from 'rxjs/operators';
|
|
10
12
|
|
|
11
13
|
class FsPopoverRef {
|
|
12
14
|
overlayRef;
|
|
13
15
|
_autoShow$ = new BehaviorSubject(true);
|
|
14
16
|
_autoClose$ = new BehaviorSubject(true);
|
|
15
17
|
_componentLoading$ = new BehaviorSubject(true);
|
|
18
|
+
_closed$ = new Subject();
|
|
16
19
|
_maxWidth;
|
|
17
20
|
_maxHeight;
|
|
18
21
|
_wrapperClass;
|
|
@@ -76,6 +79,13 @@ class FsPopoverRef {
|
|
|
76
79
|
get maxHeight() {
|
|
77
80
|
return this._maxHeight;
|
|
78
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* Emits after each close(). A panel listens to it, so content that calls
|
|
84
|
+
* `popover.close()` closes the panel the same way Escape or an outside click does.
|
|
85
|
+
*/
|
|
86
|
+
get closed$() {
|
|
87
|
+
return this._closed$.asObservable();
|
|
88
|
+
}
|
|
79
89
|
show() {
|
|
80
90
|
setTimeout(() => {
|
|
81
91
|
this._componentLoading$.next(false);
|
|
@@ -87,6 +97,7 @@ class FsPopoverRef {
|
|
|
87
97
|
close() {
|
|
88
98
|
this.overlayRef.detach();
|
|
89
99
|
this._componentLoading$.next(true);
|
|
100
|
+
this._closed$.next();
|
|
90
101
|
}
|
|
91
102
|
updatePosition() {
|
|
92
103
|
this.overlayRef.updatePosition();
|
|
@@ -104,18 +115,6 @@ class FsPopoverRef {
|
|
|
104
115
|
}
|
|
105
116
|
}
|
|
106
117
|
|
|
107
|
-
var Position;
|
|
108
|
-
(function (Position) {
|
|
109
|
-
Position["North"] = "north";
|
|
110
|
-
Position["South"] = "south";
|
|
111
|
-
Position["East"] = "east";
|
|
112
|
-
Position["West"] = "west";
|
|
113
|
-
})(Position || (Position = {}));
|
|
114
|
-
|
|
115
|
-
function pointInRect(x1, y1, x2, y2, px, py) {
|
|
116
|
-
return (px > x1 && px < x2) && (py > y1 && py < y2);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
118
|
class FsPopoverWrapperComponent extends BasePortalOutlet {
|
|
120
119
|
popoverRef = inject(FsPopoverRef);
|
|
121
120
|
_el = inject(ElementRef);
|
|
@@ -237,6 +236,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.7", ngImpor
|
|
|
237
236
|
args: ['spinner']
|
|
238
237
|
}] } });
|
|
239
238
|
|
|
239
|
+
var Position;
|
|
240
|
+
(function (Position) {
|
|
241
|
+
Position["North"] = "north";
|
|
242
|
+
Position["South"] = "south";
|
|
243
|
+
Position["East"] = "east";
|
|
244
|
+
Position["West"] = "west";
|
|
245
|
+
})(Position || (Position = {}));
|
|
246
|
+
|
|
247
|
+
function createInjector(popoverRef, parentInjector) {
|
|
248
|
+
const providers = [
|
|
249
|
+
{ provide: FsPopoverRef, useValue: popoverRef },
|
|
250
|
+
];
|
|
251
|
+
return Injector.create({
|
|
252
|
+
providers,
|
|
253
|
+
parent: parentInjector,
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
240
257
|
const POSITIONS = {
|
|
241
258
|
[Position.North]: {
|
|
242
259
|
originX: 'center',
|
|
@@ -296,30 +313,53 @@ function createBasePopupPositionStrategy(el, overlay) {
|
|
|
296
313
|
.withViewportMargin(4);
|
|
297
314
|
}
|
|
298
315
|
|
|
299
|
-
|
|
300
|
-
|
|
316
|
+
/**
|
|
317
|
+
* Hover and click popovers have no backdrop. A panel gets a transparent backdrop, so a click
|
|
318
|
+
* anywhere outside it closes it, and the `fs-popover-panel` class on its pane.
|
|
319
|
+
*/
|
|
320
|
+
function createOverlayRef(el, overlay, position, panel = false) {
|
|
321
|
+
const overlayConfig = new OverlayConfig({
|
|
301
322
|
positionStrategy: createPopupPositionStrategy(el, overlay, position),
|
|
302
323
|
scrollStrategy: overlay.scrollStrategies.reposition(),
|
|
303
|
-
hasBackdrop:
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
return overlay.create(overlayConfig);
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
function createInjector(popoverRef, parentInjector) {
|
|
310
|
-
const providers = [
|
|
311
|
-
{ provide: FsPopoverRef, useValue: popoverRef },
|
|
312
|
-
];
|
|
313
|
-
return Injector.create({
|
|
314
|
-
providers,
|
|
315
|
-
parent: parentInjector,
|
|
324
|
+
hasBackdrop: panel,
|
|
325
|
+
backdropClass: panel ? 'cdk-overlay-transparent-backdrop' : undefined,
|
|
326
|
+
panelClass: panel ? 'fs-popover-panel' : undefined,
|
|
316
327
|
});
|
|
328
|
+
return overlay.create(overlayConfig);
|
|
317
329
|
}
|
|
318
330
|
|
|
319
331
|
function createTempatePortal(template, popoverRef, data) {
|
|
320
332
|
return new TemplatePortal(template, void 0, { data: data, popover: popoverRef });
|
|
321
333
|
}
|
|
322
334
|
|
|
335
|
+
/**
|
|
336
|
+
* True when an overlay with a backdrop was attached after this one and still shows content:
|
|
337
|
+
* a calendar, a select, a menu or a dialog opened from inside it. Overlays without a backdrop
|
|
338
|
+
* (tooltips, toasts, hover popovers) don't count.
|
|
339
|
+
*
|
|
340
|
+
* CDK inserts each backdrop right before its overlay's host, and later overlays after earlier
|
|
341
|
+
* ones, so everything after this overlay's host was attached later.
|
|
342
|
+
*/
|
|
343
|
+
function hasOverlayAbove(overlayRef) {
|
|
344
|
+
let element = overlayRef.hostElement?.nextElementSibling;
|
|
345
|
+
while (element) {
|
|
346
|
+
if (isShowingBackdropOverlay(element)) {
|
|
347
|
+
return true;
|
|
348
|
+
}
|
|
349
|
+
element = element.nextElementSibling;
|
|
350
|
+
}
|
|
351
|
+
return false;
|
|
352
|
+
}
|
|
353
|
+
function isShowingBackdropOverlay(host) {
|
|
354
|
+
const pane = host.querySelector(':scope > .cdk-overlay-pane');
|
|
355
|
+
return !!pane?.childElementCount
|
|
356
|
+
&& !!host.previousElementSibling?.classList.contains('cdk-overlay-backdrop');
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function pointInRect(x1, y1, x2, y2, px, py) {
|
|
360
|
+
return (px > x1 && px < x2) && (py > y1 && py < y2);
|
|
361
|
+
}
|
|
362
|
+
|
|
323
363
|
class FsPopoverService {
|
|
324
364
|
_injector = inject(Injector);
|
|
325
365
|
_overlay = inject(Overlay);
|
|
@@ -394,6 +434,17 @@ class FsPopoverDirective {
|
|
|
394
434
|
size = 'normal';
|
|
395
435
|
trigger = 'mouseover';
|
|
396
436
|
enabled = true;
|
|
437
|
+
/**
|
|
438
|
+
* Panel mode: a host click or open() opens it; it stays open until close(), an outside
|
|
439
|
+
* click, Escape (only when no later overlay with a backdrop sits above it) or `popover.close()`
|
|
440
|
+
* from the content. Focus is trapped inside and returns on close to the element that had it
|
|
441
|
+
* when the panel opened. `trigger`, `showDelay`, `leaveDelay` and `autoClose` don't apply.
|
|
442
|
+
*/
|
|
443
|
+
panel = input(false, { transform: booleanAttribute });
|
|
444
|
+
/**
|
|
445
|
+
* Emits each time the panel closes.
|
|
446
|
+
*/
|
|
447
|
+
closed = output();
|
|
397
448
|
_initialized = false;
|
|
398
449
|
_popoverRef;
|
|
399
450
|
_wrapperElement;
|
|
@@ -408,6 +459,15 @@ class FsPopoverDirective {
|
|
|
408
459
|
_ngZone = inject(NgZone);
|
|
409
460
|
_router = inject(Router, { optional: true });
|
|
410
461
|
_guid = guid('xxxxxxx');
|
|
462
|
+
_overlay = inject(Overlay);
|
|
463
|
+
_injector = inject(Injector);
|
|
464
|
+
_focusTrapFactory = inject(ConfigurableFocusTrapFactory);
|
|
465
|
+
_document = inject(DOCUMENT);
|
|
466
|
+
_panelOverlayRef;
|
|
467
|
+
_panelFocusTrap;
|
|
468
|
+
_panelOpener;
|
|
469
|
+
_panelTopmostKeydown;
|
|
470
|
+
_panelClosed$ = new Subject();
|
|
411
471
|
constructor() {
|
|
412
472
|
this._mouseEnter$ = fromEvent(this._elRef.nativeElement, 'mouseenter');
|
|
413
473
|
this._mouseMove$ = fromEvent(document, 'mousemove', { passive: true });
|
|
@@ -432,9 +492,33 @@ class FsPopoverDirective {
|
|
|
432
492
|
}
|
|
433
493
|
}
|
|
434
494
|
ngOnDestroy() {
|
|
495
|
+
if (this._panelOverlayRef) {
|
|
496
|
+
this._finishPanel(false);
|
|
497
|
+
}
|
|
435
498
|
this._popoverClosed$.next(null);
|
|
436
499
|
this._destroy$.next(null);
|
|
437
500
|
}
|
|
501
|
+
/**
|
|
502
|
+
* Opens the panel on this element. Panel mode only.
|
|
503
|
+
*/
|
|
504
|
+
open() {
|
|
505
|
+
if (!this.panel() || !this.enabled || !this._initialized || this._panelOverlayRef) {
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
this._ngZone.run(() => this._openPanel());
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Closes the panel, or in hover and click modes the open popover.
|
|
512
|
+
*/
|
|
513
|
+
close() {
|
|
514
|
+
if (!this.panel()) {
|
|
515
|
+
this._closePopover();
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
if (this._panelOverlayRef) {
|
|
519
|
+
this._ngZone.run(() => this._popoverRef.close());
|
|
520
|
+
}
|
|
521
|
+
}
|
|
438
522
|
_closePopover() {
|
|
439
523
|
this._popoverClosed$.next(null);
|
|
440
524
|
this._ngZone.run(() => {
|
|
@@ -460,7 +544,10 @@ class FsPopoverDirective {
|
|
|
460
544
|
theme: this.theme,
|
|
461
545
|
});
|
|
462
546
|
this._ngZone.runOutsideAngular(() => {
|
|
463
|
-
if (this.
|
|
547
|
+
if (this.panel()) {
|
|
548
|
+
this._listenPanelHostClick();
|
|
549
|
+
}
|
|
550
|
+
else if (this.trigger === 'click') {
|
|
464
551
|
this._listenMouseHostClick();
|
|
465
552
|
}
|
|
466
553
|
else {
|
|
@@ -477,7 +564,7 @@ class FsPopoverDirective {
|
|
|
477
564
|
}), pipe(takeUntil(this._destroy$
|
|
478
565
|
.pipe(delay(200)))))
|
|
479
566
|
.subscribe(() => {
|
|
480
|
-
this.
|
|
567
|
+
this.close();
|
|
481
568
|
});
|
|
482
569
|
}
|
|
483
570
|
_listenMouseHostClick() {
|
|
@@ -536,13 +623,108 @@ class FsPopoverDirective {
|
|
|
536
623
|
const pointInPopoverRect = pointInRect(popoverBounds.x, popoverBounds.y, popoverBounds.x + popoverBounds.width, popoverBounds.y + popoverBounds.height, event.x, event.y);
|
|
537
624
|
return !pointInHostRect && !pointInPopoverRect;
|
|
538
625
|
}
|
|
626
|
+
_listenPanelHostClick() {
|
|
627
|
+
fromEvent(this._elRef.nativeElement, 'click')
|
|
628
|
+
.pipe(filter(() => this.enabled), tap((event) => {
|
|
629
|
+
event.stopPropagation();
|
|
630
|
+
this.open();
|
|
631
|
+
}), takeUntil(this._destroy$))
|
|
632
|
+
.subscribe();
|
|
633
|
+
}
|
|
634
|
+
_openPanel() {
|
|
635
|
+
this._panelOpener = this._document.activeElement;
|
|
636
|
+
this._panelOverlayRef = createOverlayRef(this._elRef, this._overlay, this.position, true);
|
|
637
|
+
this._popoverRef.overlayRef = this._panelOverlayRef;
|
|
638
|
+
const wrapperElement = this._attachPanelContent(this._panelOverlayRef);
|
|
639
|
+
this._trapPanelFocus(wrapperElement);
|
|
640
|
+
this._listenPanelClose(this._panelOverlayRef);
|
|
641
|
+
this._listenPanelEscape(this._panelOverlayRef);
|
|
642
|
+
}
|
|
643
|
+
_attachPanelContent(overlayRef) {
|
|
644
|
+
const wrapperRef = overlayRef.attach(new ComponentPortal(FsPopoverWrapperComponent, null, createInjector(this._popoverRef, this._injector)));
|
|
645
|
+
if (this.template) {
|
|
646
|
+
wrapperRef.instance
|
|
647
|
+
.attachTemplatePortal(createTempatePortal(this.template, this._popoverRef, this.data));
|
|
648
|
+
}
|
|
649
|
+
else {
|
|
650
|
+
wrapperRef.instance.setTextualContent(this.text);
|
|
651
|
+
}
|
|
652
|
+
return wrapperRef.location.nativeElement;
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* The configurable trap also pulls focus back when it lands outside the panel, for
|
|
656
|
+
* example on the first Tab after a calendar inside closed and took the focused element
|
|
657
|
+
* with it. Focus inside other overlays (a select, a calendar) is left alone.
|
|
658
|
+
*/
|
|
659
|
+
_trapPanelFocus(wrapperElement) {
|
|
660
|
+
this._panelFocusTrap = this._focusTrapFactory.create(wrapperElement);
|
|
661
|
+
this._panelFocusTrap.focusInitialElementWhenReady()
|
|
662
|
+
.then((focused) => {
|
|
663
|
+
// Nothing inside takes focus (text only): the panel itself becomes the one
|
|
664
|
+
// tab stop, so the opener behind the backdrop doesn't keep focus.
|
|
665
|
+
if (!focused && wrapperElement.isConnected) {
|
|
666
|
+
wrapperElement.setAttribute('tabindex', '0');
|
|
667
|
+
wrapperElement.style.outline = 'none';
|
|
668
|
+
wrapperElement.focus();
|
|
669
|
+
}
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
_listenPanelClose(overlayRef) {
|
|
673
|
+
overlayRef.backdropClick()
|
|
674
|
+
.pipe(tap(() => this.close()), takeUntil(this._panelClosed$))
|
|
675
|
+
.subscribe();
|
|
676
|
+
// Every close (close(), the backdrop, Escape, or `popover.close()` from the
|
|
677
|
+
// content) goes through FsPopoverRef.close(), which detaches and then emits.
|
|
678
|
+
this._popoverRef.closed$
|
|
679
|
+
.pipe(take(1), tap(() => this._finishPanel(true)), takeUntil(this._panelClosed$))
|
|
680
|
+
.subscribe();
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* Escape closes the panel only when no later overlay with a backdrop sits above it, so
|
|
684
|
+
* Escape in a calendar, select or menu opened from the panel closes only that. The check
|
|
685
|
+
* runs in the capture phase, before any handler inside closes its own overlay; fs-datepicker,
|
|
686
|
+
* for one, closes its calendar from the input's keydown and from a document listener.
|
|
687
|
+
*/
|
|
688
|
+
_listenPanelEscape(overlayRef) {
|
|
689
|
+
this._ngZone.runOutsideAngular(() => {
|
|
690
|
+
fromEvent(this._document, 'keydown', { capture: true })
|
|
691
|
+
.pipe(tap((event) => {
|
|
692
|
+
this._panelTopmostKeydown = hasOverlayAbove(overlayRef) ? null : event;
|
|
693
|
+
}), takeUntil(this._panelClosed$))
|
|
694
|
+
.subscribe();
|
|
695
|
+
});
|
|
696
|
+
overlayRef.keydownEvents()
|
|
697
|
+
.pipe(filter((event) => event.key === 'Escape' && !hasModifierKey(event)), filter((event) => event === this._panelTopmostKeydown), tap(() => this.close()), takeUntil(this._panelClosed$))
|
|
698
|
+
.subscribe();
|
|
699
|
+
}
|
|
700
|
+
_finishPanel(emit) {
|
|
701
|
+
const overlayRef = this._panelOverlayRef;
|
|
702
|
+
this._panelOverlayRef = null;
|
|
703
|
+
this._panelTopmostKeydown = null;
|
|
704
|
+
this._panelClosed$.next();
|
|
705
|
+
this._panelFocusTrap?.destroy();
|
|
706
|
+
this._panelFocusTrap = null;
|
|
707
|
+
overlayRef.dispose();
|
|
708
|
+
this._restorePanelFocus();
|
|
709
|
+
if (emit) {
|
|
710
|
+
this.closed.emit();
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
_restorePanelFocus() {
|
|
714
|
+
const opener = this._panelOpener;
|
|
715
|
+
this._panelOpener = null;
|
|
716
|
+
if (opener?.isConnected && typeof opener.focus === 'function') {
|
|
717
|
+
opener.focus();
|
|
718
|
+
}
|
|
719
|
+
}
|
|
539
720
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.7", ngImport: i0, type: FsPopoverDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
540
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "
|
|
721
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "18.2.7", type: FsPopoverDirective, isStandalone: true, selector: "[fsPopover]", inputs: { text: { classPropertyName: "text", publicName: "text", isSignal: false, isRequired: false, transformFunction: null }, template: { classPropertyName: "template", publicName: "template", isSignal: false, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: false, isRequired: false, transformFunction: null }, leaveDelay: { classPropertyName: "leaveDelay", publicName: "leaveDelay", isSignal: false, isRequired: false, transformFunction: null }, showDelay: { classPropertyName: "showDelay", publicName: "showDelay", isSignal: false, isRequired: false, transformFunction: null }, maxWidth: { classPropertyName: "maxWidth", publicName: "maxWidth", isSignal: false, isRequired: false, transformFunction: null }, maxHeight: { classPropertyName: "maxHeight", publicName: "maxHeight", isSignal: false, isRequired: false, transformFunction: null }, wrapperClass: { classPropertyName: "wrapperClass", publicName: "wrapperClass", isSignal: false, isRequired: false, transformFunction: null }, autoShow: { classPropertyName: "autoShow", publicName: "autoShow", isSignal: false, isRequired: false, transformFunction: null }, autoClose: { classPropertyName: "autoClose", publicName: "autoClose", isSignal: false, isRequired: false, transformFunction: null }, loadingDiameter: { classPropertyName: "loadingDiameter", publicName: "loadingDiameter", isSignal: false, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: false, isRequired: false, transformFunction: null }, indication: { classPropertyName: "indication", publicName: "indication", isSignal: false, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: false, isRequired: false, transformFunction: null }, theme: { classPropertyName: "theme", publicName: "theme", isSignal: false, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: false, isRequired: false, transformFunction: null }, trigger: { classPropertyName: "trigger", publicName: "trigger", isSignal: false, isRequired: false, transformFunction: null }, enabled: { classPropertyName: "enabled", publicName: "enabled", isSignal: false, isRequired: false, transformFunction: null }, panel: { classPropertyName: "panel", publicName: "panel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed" }, host: { properties: { "class.indication": "this.indication", "class.fs-popover-enabled": "this.enabled" }, classAttribute: "fs-popover" }, exportAs: ["fsPopover"], usesOnChanges: true, ngImport: i0 });
|
|
541
722
|
}
|
|
542
723
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.7", ngImport: i0, type: FsPopoverDirective, decorators: [{
|
|
543
724
|
type: Directive,
|
|
544
725
|
args: [{
|
|
545
726
|
selector: '[fsPopover]',
|
|
727
|
+
exportAs: 'fsPopover',
|
|
546
728
|
host: {
|
|
547
729
|
'class': 'fs-popover',
|
|
548
730
|
},
|
|
@@ -594,11 +776,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.7", ngImpor
|
|
|
594
776
|
|
|
595
777
|
class FsPopoverComponent extends FsPopoverDirective {
|
|
596
778
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.7", ngImport: i0, type: FsPopoverComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
597
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.7", type: FsPopoverComponent, isStandalone: true, selector: "fs-popover", host: { classAttribute: "fs-popover-component" }, usesInheritance: true, ngImport: i0, template: "<span [ngClass]=\"{'popover-container': enabled}\">\n <ng-content></ng-content>\n</span>\n", dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
779
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.7", type: FsPopoverComponent, isStandalone: true, selector: "fs-popover", host: { classAttribute: "fs-popover-component" }, exportAs: ["fsPopover"], usesInheritance: true, ngImport: i0, template: "<span [ngClass]=\"{'popover-container': enabled}\">\n <ng-content></ng-content>\n</span>\n", dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
598
780
|
}
|
|
599
781
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.7", ngImport: i0, type: FsPopoverComponent, decorators: [{
|
|
600
782
|
type: Component,
|
|
601
|
-
args: [{ selector: 'fs-popover', changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
783
|
+
args: [{ selector: 'fs-popover', exportAs: 'fsPopover', changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
602
784
|
'class': 'fs-popover-component',
|
|
603
785
|
}, standalone: true, imports: [NgClass], template: "<span [ngClass]=\"{'popover-container': enabled}\">\n <ng-content></ng-content>\n</span>\n" }]
|
|
604
786
|
}] });
|