ng-primitives 0.113.0 → 0.114.1
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/dialog/index.d.ts +89 -29
- package/fesm2022/ng-primitives-dialog.mjs +190 -85
- package/fesm2022/ng-primitives-dialog.mjs.map +1 -1
- package/fesm2022/ng-primitives-portal.mjs +340 -62
- package/fesm2022/ng-primitives-portal.mjs.map +1 -1
- package/package.json +1 -1
- package/portal/index.d.ts +159 -4
package/dialog/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { ScrollStrategy, OverlayRef } from '@angular/cdk/overlay';
|
|
2
1
|
import * as _angular_core from '@angular/core';
|
|
3
2
|
import { ViewContainerRef, Injector, Provider, OnDestroy, TemplateRef, Type } from '@angular/core';
|
|
3
|
+
import { ScrollStrategy, NgpOverlayRef } from 'ng-primitives/portal';
|
|
4
4
|
import * as i1 from 'ng-primitives/internal';
|
|
5
5
|
import { Subject, Observable } from 'rxjs';
|
|
6
6
|
import { FocusOrigin } from '@angular/cdk/a11y';
|
|
@@ -21,8 +21,6 @@ interface NgpDialogConfig<T = any> {
|
|
|
21
21
|
role?: NgpDialogRole;
|
|
22
22
|
/** Whether this is a modal dialog. Used to set the `aria-modal` attribute. */
|
|
23
23
|
modal?: boolean;
|
|
24
|
-
/** Scroll strategy to be used for the dialog. This determines how the dialog responds to scrolling underneath the panel element. */
|
|
25
|
-
scrollStrategy?: ScrollStrategy;
|
|
26
24
|
/**
|
|
27
25
|
* Whether the dialog should close when the user navigates. This includes both browser history
|
|
28
26
|
* navigation (back/forward) and programmatic route changes (e.g. router.navigate()).
|
|
@@ -30,8 +28,10 @@ interface NgpDialogConfig<T = any> {
|
|
|
30
28
|
closeOnNavigation?: boolean;
|
|
31
29
|
/** Whether the dialog should close when the user presses the escape key. */
|
|
32
30
|
closeOnEscape?: boolean;
|
|
33
|
-
/** Whether the dialog should close when the user
|
|
31
|
+
/** Whether the dialog should close when the user clicks the overlay. */
|
|
34
32
|
closeOnClick?: boolean;
|
|
33
|
+
/** Scroll strategy to be used for the dialog. */
|
|
34
|
+
scrollStrategy?: ScrollStrategy;
|
|
35
35
|
data?: T;
|
|
36
36
|
}
|
|
37
37
|
/**
|
|
@@ -54,12 +54,15 @@ declare class NgpDialogDescription implements OnDestroy {
|
|
|
54
54
|
|
|
55
55
|
declare class NgpDialogOverlay {
|
|
56
56
|
private readonly dialogRef;
|
|
57
|
+
private startedPointerDownOnOverlay;
|
|
57
58
|
/**
|
|
58
59
|
* Whether the dialog should close on backdrop click.
|
|
59
60
|
* @default `true`
|
|
60
61
|
*/
|
|
61
62
|
readonly closeOnClick: _angular_core.InputSignalWithTransform<boolean | undefined, unknown>;
|
|
62
|
-
protected
|
|
63
|
+
protected onPointerDown(event: Event): void;
|
|
64
|
+
protected onClick(event: Event): void;
|
|
65
|
+
protected resetPointerOrigin(): void;
|
|
63
66
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<NgpDialogOverlay, never>;
|
|
64
67
|
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<NgpDialogOverlay, "[ngpDialogOverlay]", ["ngpDialogOverlay"], { "closeOnClick": { "alias": "ngpDialogOverlayCloseOnClick"; "required": false; "isSignal": true; }; }, {}, never, never, true, [{ directive: typeof i1.NgpExitAnimation; inputs: {}; outputs: {}; }]>;
|
|
65
68
|
}
|
|
@@ -75,12 +78,17 @@ declare class NgpDialogTitle implements OnDestroy {
|
|
|
75
78
|
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<NgpDialogTitle, "[ngpDialogTitle]", ["ngpDialogTitle"], { "id": { "alias": "id"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
76
79
|
}
|
|
77
80
|
|
|
81
|
+
/** Minimal portal interface needed by the dialog ref. */
|
|
82
|
+
interface NgpDialogPortalRef {
|
|
83
|
+
getElements(): HTMLElement[];
|
|
84
|
+
detach(immediate?: boolean): Promise<void>;
|
|
85
|
+
}
|
|
78
86
|
/**
|
|
79
87
|
* Reference to a dialog opened via the Dialog service.
|
|
80
88
|
*/
|
|
81
|
-
declare class NgpDialogRef<T = unknown, R = unknown> {
|
|
82
|
-
readonly overlayRef: OverlayRef;
|
|
89
|
+
declare class NgpDialogRef<T = unknown, R = unknown> implements NgpOverlayRef {
|
|
83
90
|
readonly config: NgpDialogConfig<T>;
|
|
91
|
+
private readonly document;
|
|
84
92
|
/** Whether the user is allowed to close the dialog. */
|
|
85
93
|
disableClose: boolean | undefined;
|
|
86
94
|
/** Whether the escape key is allowed to close the dialog. */
|
|
@@ -90,51 +98,97 @@ declare class NgpDialogRef<T = unknown, R = unknown> {
|
|
|
90
98
|
focusOrigin?: FocusOrigin;
|
|
91
99
|
result?: R;
|
|
92
100
|
}>;
|
|
93
|
-
/**
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
readonly
|
|
101
|
+
/**
|
|
102
|
+
* Observable that emits the dialog result when closed.
|
|
103
|
+
*/
|
|
104
|
+
readonly afterClosed: Observable<R | undefined>;
|
|
97
105
|
/** Data passed from the dialog opener. */
|
|
98
106
|
readonly data: T;
|
|
99
107
|
/** Unique ID for the dialog. */
|
|
100
108
|
readonly id: string;
|
|
101
|
-
/** Subscription to external detachments of the dialog. */
|
|
102
|
-
private detachSubscription;
|
|
103
109
|
/** @internal Store the injector */
|
|
104
110
|
injector: Injector | undefined;
|
|
105
111
|
/** Whether the dialog is closing. */
|
|
106
112
|
private closing;
|
|
107
|
-
|
|
113
|
+
/** @internal Portal reference for element access and detach. */
|
|
114
|
+
portal: NgpDialogPortalRef | null;
|
|
115
|
+
/** Emits on keyboard events within the dialog. */
|
|
116
|
+
readonly keydownEvents: Observable<KeyboardEvent>;
|
|
117
|
+
/**
|
|
118
|
+
* Emits pointer events (click, auxclick, contextmenu) that happen outside of the dialog.
|
|
119
|
+
* Fed by the NgpOverlayRegistry with CDK-compatible stacking awareness.
|
|
120
|
+
* @internal
|
|
121
|
+
*/
|
|
122
|
+
readonly outsidePointerEvents$: Subject<MouseEvent>;
|
|
123
|
+
/** Emits on pointer events that happen outside of the dialog. */
|
|
124
|
+
readonly outsidePointerEvents: Observable<MouseEvent>;
|
|
125
|
+
constructor(config: NgpDialogConfig<T>, document: Document);
|
|
126
|
+
/**
|
|
127
|
+
* Updates the position of the dialog. No-op since dialogs are CSS-centered.
|
|
128
|
+
*/
|
|
129
|
+
updatePosition(): this;
|
|
130
|
+
/**
|
|
131
|
+
* NgpOverlayRef implementation — called by the registry for escape-key dismiss.
|
|
132
|
+
*/
|
|
133
|
+
hide(options?: {
|
|
134
|
+
immediate?: boolean;
|
|
135
|
+
origin?: FocusOrigin;
|
|
136
|
+
}): void;
|
|
137
|
+
/**
|
|
138
|
+
* NgpOverlayRef implementation — called by the registry for descendant cascade.
|
|
139
|
+
* Skips exit animations and tears down immediately.
|
|
140
|
+
*/
|
|
141
|
+
hideImmediate(): Promise<void>;
|
|
108
142
|
/**
|
|
109
143
|
* Close the dialog.
|
|
110
144
|
* @param result Optional result to return to the dialog opener.
|
|
111
|
-
* @param
|
|
145
|
+
* @param focusOrigin The origin of the focus event that triggered the close.
|
|
112
146
|
*/
|
|
113
147
|
close(result?: R, focusOrigin?: FocusOrigin): Promise<void>;
|
|
114
|
-
/**
|
|
115
|
-
|
|
148
|
+
/**
|
|
149
|
+
* @deprecated Access dialog methods directly instead (keydownEvents, outsidePointerEvents,
|
|
150
|
+
* updatePosition, close). This shim will be removed in the next major version.
|
|
151
|
+
*/
|
|
152
|
+
get overlayRef(): {
|
|
153
|
+
keydownEvents: () => Observable<KeyboardEvent>;
|
|
154
|
+
outsidePointerEvents: () => Observable<MouseEvent>;
|
|
155
|
+
updatePosition: () => NgpDialogRef<T, R>;
|
|
156
|
+
dispose: () => Promise<void>;
|
|
157
|
+
detachments: () => Observable<{
|
|
158
|
+
focusOrigin?: FocusOrigin;
|
|
159
|
+
result?: R;
|
|
160
|
+
}>;
|
|
161
|
+
overlayElement: HTMLElement | undefined;
|
|
162
|
+
};
|
|
163
|
+
/**
|
|
164
|
+
* Get the portal elements.
|
|
165
|
+
* @internal
|
|
166
|
+
*/
|
|
167
|
+
getElements(): HTMLElement[];
|
|
116
168
|
}
|
|
117
169
|
declare function injectDialogRef<T = unknown, R = unknown>(): NgpDialogRef<T, R>;
|
|
118
170
|
|
|
119
171
|
/**
|
|
120
|
-
*
|
|
121
|
-
*
|
|
172
|
+
* Originally based on Angular CDK Dialog service.
|
|
173
|
+
* Re-implemented to use ng-primitives/portal instead of @angular/cdk/overlay.
|
|
122
174
|
*/
|
|
123
175
|
declare class NgpDialogManager implements OnDestroy {
|
|
124
176
|
private readonly applicationRef;
|
|
177
|
+
private readonly injector;
|
|
125
178
|
private readonly document;
|
|
126
|
-
private readonly overlay;
|
|
127
179
|
private readonly focusMonitor;
|
|
180
|
+
private readonly viewportRuler;
|
|
181
|
+
private readonly registry;
|
|
128
182
|
private readonly defaultOptions;
|
|
129
183
|
private readonly parentDialogManager;
|
|
130
|
-
private readonly overlayContainer;
|
|
131
184
|
private readonly router;
|
|
132
|
-
private readonly scrollStrategy;
|
|
133
185
|
private openDialogsAtThisLevel;
|
|
134
186
|
private readonly afterAllClosedAtThisLevel;
|
|
135
187
|
private readonly afterOpenedAtThisLevel;
|
|
136
188
|
private ariaHiddenElements;
|
|
137
189
|
private routerSubscription;
|
|
190
|
+
/** Scroll blocking strategy — shared across all dialogs. */
|
|
191
|
+
private scrollStrategy;
|
|
138
192
|
/** Keeps track of the currently-open dialogs. */
|
|
139
193
|
get openDialogs(): readonly NgpDialogRef[];
|
|
140
194
|
/** Stream that emits when a dialog has been opened. */
|
|
@@ -169,15 +223,11 @@ declare class NgpDialogManager implements OnDestroy {
|
|
|
169
223
|
getDialogById(id: string): NgpDialogRef | undefined;
|
|
170
224
|
/**
|
|
171
225
|
* Subscribe to router navigation events so that dialogs with `closeOnNavigation`
|
|
172
|
-
* are closed when the user navigates
|
|
173
|
-
*
|
|
226
|
+
* are closed when the user navigates. This handles both browser popstate events
|
|
227
|
+
* and programmatic route changes (e.g. router.navigate()).
|
|
174
228
|
*/
|
|
175
229
|
private subscribeToRouterEvents;
|
|
176
230
|
ngOnDestroy(): void;
|
|
177
|
-
/**
|
|
178
|
-
* Creates an overlay config from a dialog config.
|
|
179
|
-
*/
|
|
180
|
-
private getOverlayConfig;
|
|
181
231
|
/**
|
|
182
232
|
* Creates a custom injector to be used inside the dialog. This allows a component loaded inside
|
|
183
233
|
* of a dialog to close itself and, optionally, to return a value.
|
|
@@ -187,7 +237,17 @@ declare class NgpDialogManager implements OnDestroy {
|
|
|
187
237
|
* Removes a dialog from the array of open dialogs.
|
|
188
238
|
*/
|
|
189
239
|
private removeOpenDialog;
|
|
190
|
-
/**
|
|
240
|
+
/**
|
|
241
|
+
* Enable scroll blocking when the first dialog opens.
|
|
242
|
+
*/
|
|
243
|
+
private enableScrollBlocking;
|
|
244
|
+
/**
|
|
245
|
+
* Disable scroll blocking when the last dialog closes.
|
|
246
|
+
*/
|
|
247
|
+
private disableScrollBlocking;
|
|
248
|
+
/**
|
|
249
|
+
* Hides all of the content that isn't a dialog portal from assistive technology.
|
|
250
|
+
*/
|
|
191
251
|
private hideNonDialogContentFromAssistiveTechnology;
|
|
192
252
|
private getAfterAllClosed;
|
|
193
253
|
static ɵfac: _angular_core.ɵɵFactoryDeclaration<NgpDialogManager, never>;
|
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, inject, input, Directive, booleanAttribute,
|
|
2
|
+
import { InjectionToken, inject, input, Directive, booleanAttribute, ApplicationRef, Injector, ViewContainerRef, isDevMode, Injectable, output, HostListener, signal } from '@angular/core';
|
|
3
3
|
import { uniqueId, onChange } from 'ng-primitives/utils';
|
|
4
4
|
import { createStateToken, createStateProvider, createStateInjector, createState } from 'ng-primitives/state';
|
|
5
5
|
import * as i1 from 'ng-primitives/internal';
|
|
6
6
|
import { NgpExitAnimationManager, NgpExitAnimation } from 'ng-primitives/internal';
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
7
|
+
import { Subject, defer, EMPTY, fromEvent } from 'rxjs';
|
|
8
|
+
import { map, filter, takeUntil, startWith } from 'rxjs/operators';
|
|
9
9
|
import { FocusMonitor } from '@angular/cdk/a11y';
|
|
10
|
-
import {
|
|
11
|
-
import { TemplatePortal, ComponentPortal } from '@angular/cdk/portal';
|
|
10
|
+
import { ViewportRuler } from '@angular/cdk/scrolling';
|
|
12
11
|
import { DOCUMENT } from '@angular/common';
|
|
13
12
|
import { Router, NavigationStart } from '@angular/router';
|
|
14
|
-
import {
|
|
13
|
+
import { NgpOverlayRegistry, createPortal, BlockScrollStrategy } from 'ng-primitives/portal';
|
|
15
14
|
import * as i1$1 from 'ng-primitives/focus-trap';
|
|
16
15
|
import { NgpFocusTrap } from 'ng-primitives/focus-trap';
|
|
17
16
|
|
|
@@ -97,33 +96,74 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImpor
|
|
|
97
96
|
* Reference to a dialog opened via the Dialog service.
|
|
98
97
|
*/
|
|
99
98
|
class NgpDialogRef {
|
|
100
|
-
constructor(
|
|
101
|
-
this.overlayRef = overlayRef;
|
|
99
|
+
constructor(config, document) {
|
|
102
100
|
this.config = config;
|
|
101
|
+
this.document = document;
|
|
103
102
|
/** Emits when the dialog has been closed. */
|
|
104
103
|
this.closed = new Subject();
|
|
104
|
+
/**
|
|
105
|
+
* Observable that emits the dialog result when closed.
|
|
106
|
+
*/
|
|
107
|
+
this.afterClosed = this.closed.pipe(map(event => event.result));
|
|
105
108
|
/** Whether the dialog is closing. */
|
|
106
109
|
this.closing = false;
|
|
110
|
+
/** @internal Portal reference for element access and detach. */
|
|
111
|
+
this.portal = null;
|
|
112
|
+
/**
|
|
113
|
+
* Emits pointer events (click, auxclick, contextmenu) that happen outside of the dialog.
|
|
114
|
+
* Fed by the NgpOverlayRegistry with CDK-compatible stacking awareness.
|
|
115
|
+
* @internal
|
|
116
|
+
*/
|
|
117
|
+
this.outsidePointerEvents$ = new Subject();
|
|
118
|
+
/** Emits on pointer events that happen outside of the dialog. */
|
|
119
|
+
this.outsidePointerEvents = this.outsidePointerEvents$.asObservable();
|
|
107
120
|
this.data = config.data;
|
|
108
|
-
this.keydownEvents = overlayRef.keydownEvents();
|
|
109
|
-
this.outsidePointerEvents = overlayRef.outsidePointerEvents();
|
|
110
121
|
this.id = config.id; // By the time the dialog is created we are guaranteed to have an ID.
|
|
111
122
|
this.closeOnEscape = config.closeOnEscape ?? true;
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
this.close(undefined, 'keyboard');
|
|
119
|
-
}
|
|
123
|
+
// Use defer() so the observable is created on subscribe — by then the portal will be set.
|
|
124
|
+
this.keydownEvents = defer(() => {
|
|
125
|
+
const elements = this.getElements();
|
|
126
|
+
if (!elements.length)
|
|
127
|
+
return EMPTY;
|
|
128
|
+
return fromEvent(this.document, 'keydown').pipe(filter(event => elements.some(el => el.contains(event.target))), takeUntil(this.closed));
|
|
120
129
|
});
|
|
121
|
-
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Updates the position of the dialog. No-op since dialogs are CSS-centered.
|
|
133
|
+
*/
|
|
134
|
+
updatePosition() {
|
|
135
|
+
return this;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* NgpOverlayRef implementation — called by the registry for escape-key dismiss.
|
|
139
|
+
*/
|
|
140
|
+
hide(options) {
|
|
141
|
+
if (this.disableClose) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
this.close(undefined, options?.origin);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* NgpOverlayRef implementation — called by the registry for descendant cascade.
|
|
148
|
+
* Skips exit animations and tears down immediately.
|
|
149
|
+
*/
|
|
150
|
+
async hideImmediate() {
|
|
151
|
+
if (this.closing) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
this.closing = true;
|
|
155
|
+
// Detach the portal immediately — no exit animation
|
|
156
|
+
if (this.portal) {
|
|
157
|
+
await this.portal.detach(true);
|
|
158
|
+
this.portal = null;
|
|
159
|
+
}
|
|
160
|
+
this.closed.next({});
|
|
161
|
+
this.closed.complete();
|
|
122
162
|
}
|
|
123
163
|
/**
|
|
124
164
|
* Close the dialog.
|
|
125
165
|
* @param result Optional result to return to the dialog opener.
|
|
126
|
-
* @param
|
|
166
|
+
* @param focusOrigin The origin of the focus event that triggered the close.
|
|
127
167
|
*/
|
|
128
168
|
async close(result, focusOrigin) {
|
|
129
169
|
// If the dialog is already closed, do nothing.
|
|
@@ -137,15 +177,34 @@ class NgpDialogRef {
|
|
|
137
177
|
if (exitAnimationManager) {
|
|
138
178
|
await exitAnimationManager.exit();
|
|
139
179
|
}
|
|
140
|
-
|
|
141
|
-
this.
|
|
180
|
+
// Detach the portal (immediate since exit animation already ran)
|
|
181
|
+
if (this.portal) {
|
|
182
|
+
await this.portal.detach(true);
|
|
183
|
+
this.portal = null;
|
|
184
|
+
}
|
|
142
185
|
this.closed.next({ focusOrigin, result });
|
|
143
186
|
this.closed.complete();
|
|
144
187
|
}
|
|
145
|
-
/**
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
188
|
+
/**
|
|
189
|
+
* @deprecated Access dialog methods directly instead (keydownEvents, outsidePointerEvents,
|
|
190
|
+
* updatePosition, close). This shim will be removed in the next major version.
|
|
191
|
+
*/
|
|
192
|
+
get overlayRef() {
|
|
193
|
+
return {
|
|
194
|
+
keydownEvents: () => this.keydownEvents,
|
|
195
|
+
outsidePointerEvents: () => this.outsidePointerEvents,
|
|
196
|
+
updatePosition: () => this.updatePosition(),
|
|
197
|
+
dispose: () => this.close(),
|
|
198
|
+
detachments: () => this.closed.asObservable(),
|
|
199
|
+
overlayElement: this.getElements()[0],
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Get the portal elements.
|
|
204
|
+
* @internal
|
|
205
|
+
*/
|
|
206
|
+
getElements() {
|
|
207
|
+
return this.portal?.getElements() ?? [];
|
|
149
208
|
}
|
|
150
209
|
}
|
|
151
210
|
function injectDialogRef() {
|
|
@@ -155,6 +214,7 @@ function injectDialogRef() {
|
|
|
155
214
|
class NgpDialogOverlay {
|
|
156
215
|
constructor() {
|
|
157
216
|
this.dialogRef = injectDialogRef();
|
|
217
|
+
this.startedPointerDownOnOverlay = false;
|
|
158
218
|
/**
|
|
159
219
|
* Whether the dialog should close on backdrop click.
|
|
160
220
|
* @default `true`
|
|
@@ -165,13 +225,24 @@ class NgpDialogOverlay {
|
|
|
165
225
|
transform: booleanAttribute,
|
|
166
226
|
}]));
|
|
167
227
|
}
|
|
168
|
-
|
|
169
|
-
|
|
228
|
+
onPointerDown(event) {
|
|
229
|
+
this.startedPointerDownOnOverlay = event.target === event.currentTarget;
|
|
230
|
+
}
|
|
231
|
+
onClick(event) {
|
|
232
|
+
const shouldClose = this.startedPointerDownOnOverlay &&
|
|
233
|
+
event.target === event.currentTarget &&
|
|
234
|
+
this.closeOnClick() &&
|
|
235
|
+
!this.dialogRef.disableClose;
|
|
236
|
+
this.resetPointerOrigin();
|
|
237
|
+
if (shouldClose) {
|
|
170
238
|
this.dialogRef.close(undefined, 'mouse');
|
|
171
239
|
}
|
|
172
240
|
}
|
|
241
|
+
resetPointerOrigin() {
|
|
242
|
+
this.startedPointerDownOnOverlay = false;
|
|
243
|
+
}
|
|
173
244
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: NgpDialogOverlay, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
174
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.3.9", type: NgpDialogOverlay, isStandalone: true, selector: "[ngpDialogOverlay]", inputs: { closeOnClick: { classPropertyName: "closeOnClick", publicName: "ngpDialogOverlayCloseOnClick", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "click": "
|
|
245
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.3.9", type: NgpDialogOverlay, isStandalone: true, selector: "[ngpDialogOverlay]", inputs: { closeOnClick: { classPropertyName: "closeOnClick", publicName: "ngpDialogOverlayCloseOnClick", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "pointerdown": "onPointerDown($event)", "click": "onClick($event)", "pointercancel": "resetPointerOrigin()" } }, exportAs: ["ngpDialogOverlay"], hostDirectives: [{ directive: i1.NgpExitAnimation }], ngImport: i0 }); }
|
|
175
246
|
}
|
|
176
247
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: NgpDialogOverlay, decorators: [{
|
|
177
248
|
type: Directive,
|
|
@@ -179,11 +250,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImpor
|
|
|
179
250
|
selector: '[ngpDialogOverlay]',
|
|
180
251
|
exportAs: 'ngpDialogOverlay',
|
|
181
252
|
hostDirectives: [NgpExitAnimation],
|
|
253
|
+
host: {
|
|
254
|
+
'(pointerdown)': 'onPointerDown($event)',
|
|
255
|
+
'(click)': 'onClick($event)',
|
|
256
|
+
'(pointercancel)': 'resetPointerOrigin()',
|
|
257
|
+
},
|
|
182
258
|
}]
|
|
183
|
-
}], propDecorators: { closeOnClick: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngpDialogOverlayCloseOnClick", required: false }] }]
|
|
184
|
-
type: HostListener,
|
|
185
|
-
args: ['click']
|
|
186
|
-
}] } });
|
|
259
|
+
}], propDecorators: { closeOnClick: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngpDialogOverlayCloseOnClick", required: false }] }] } });
|
|
187
260
|
|
|
188
261
|
class NgpDialogTitle {
|
|
189
262
|
constructor() {
|
|
@@ -218,27 +291,29 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImpor
|
|
|
218
291
|
}], ctorParameters: () => [], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }] } });
|
|
219
292
|
|
|
220
293
|
/**
|
|
221
|
-
*
|
|
222
|
-
*
|
|
294
|
+
* Originally based on Angular CDK Dialog service.
|
|
295
|
+
* Re-implemented to use ng-primitives/portal instead of @angular/cdk/overlay.
|
|
223
296
|
*/
|
|
224
297
|
class NgpDialogManager {
|
|
225
298
|
constructor() {
|
|
226
299
|
this.applicationRef = inject(ApplicationRef);
|
|
300
|
+
this.injector = inject(Injector);
|
|
227
301
|
this.document = inject(DOCUMENT);
|
|
228
|
-
this.overlay = inject(Overlay);
|
|
229
302
|
this.focusMonitor = inject(FocusMonitor);
|
|
303
|
+
this.viewportRuler = inject(ViewportRuler);
|
|
304
|
+
this.registry = inject(NgpOverlayRegistry);
|
|
230
305
|
this.defaultOptions = injectDialogConfig();
|
|
231
306
|
this.parentDialogManager = inject(NgpDialogManager, {
|
|
232
307
|
optional: true,
|
|
233
308
|
skipSelf: true,
|
|
234
309
|
});
|
|
235
|
-
this.overlayContainer = inject(OverlayContainer);
|
|
236
310
|
this.router = inject(Router, { optional: true });
|
|
237
|
-
this.scrollStrategy = this.defaultOptions.scrollStrategy ?? this.overlay.scrollStrategies.block();
|
|
238
311
|
this.openDialogsAtThisLevel = [];
|
|
239
312
|
this.afterAllClosedAtThisLevel = new Subject();
|
|
240
313
|
this.afterOpenedAtThisLevel = new Subject();
|
|
241
314
|
this.ariaHiddenElements = new Map();
|
|
315
|
+
/** Scroll blocking strategy — shared across all dialogs. */
|
|
316
|
+
this.scrollStrategy = null;
|
|
242
317
|
/**
|
|
243
318
|
* Stream that emits when all open dialog have finished closing.
|
|
244
319
|
* Will emit on subscribe if there are no open dialogs to begin with.
|
|
@@ -275,30 +350,57 @@ class NgpDialogManager {
|
|
|
275
350
|
if (config.id && this.getDialogById(config.id) && isDevMode()) {
|
|
276
351
|
throw Error(`Dialog with id "${config.id}" exists already. The dialog id must be unique.`);
|
|
277
352
|
}
|
|
278
|
-
const
|
|
279
|
-
const
|
|
280
|
-
const dialogRef = new NgpDialogRef(overlayRef, config);
|
|
281
|
-
const injector = this.createInjector(config, dialogRef, undefined);
|
|
353
|
+
const dialogRef = new NgpDialogRef(config, this.document);
|
|
354
|
+
const injector = this.createInjector(config, dialogRef);
|
|
282
355
|
// store the injector in the dialog ref - this is so we can access the exit animation manager
|
|
283
356
|
dialogRef.injector = injector;
|
|
284
357
|
const context = {
|
|
285
358
|
$implicit: dialogRef,
|
|
286
359
|
close: dialogRef.close.bind(dialogRef),
|
|
287
360
|
};
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
// If this is the first dialog that we're opening, hide all the non-overlay content
|
|
361
|
+
// Create the portal using our portal system
|
|
362
|
+
const portal = createPortal(templateRefOrComponentType, config.viewContainerRef, injector, context);
|
|
363
|
+
// Attach the portal to document.body
|
|
364
|
+
portal.attach(this.document.body);
|
|
365
|
+
// Store the portal reference on the dialog ref for element access and cleanup
|
|
366
|
+
dialogRef.portal = portal;
|
|
367
|
+
// If this is the first dialog that we're opening, hide all the non-overlay content
|
|
368
|
+
// and enable scroll blocking.
|
|
295
369
|
if (!this.openDialogs.length) {
|
|
296
|
-
this.hideNonDialogContentFromAssistiveTechnology();
|
|
370
|
+
this.hideNonDialogContentFromAssistiveTechnology(portal.getElements());
|
|
371
|
+
this.enableScrollBlocking(config);
|
|
372
|
+
}
|
|
373
|
+
// Auto-detect parent overlay: if the trigger element lives inside an existing overlay
|
|
374
|
+
// (e.g. a dialog opened from a popover), register as its child so that clicks inside
|
|
375
|
+
// the dialog don't dismiss the parent overlay.
|
|
376
|
+
// Only inherit parentId from other dialogs — non-dialog overlays (menus, popovers)
|
|
377
|
+
// may close after triggering the dialog open, which would cascade-close the dialog.
|
|
378
|
+
let parentId = activeElement instanceof HTMLElement
|
|
379
|
+
? this.registry.findContainingOverlay(activeElement)
|
|
380
|
+
: null;
|
|
381
|
+
if (parentId !== null && !this.openDialogs.some(d => d.id === parentId)) {
|
|
382
|
+
parentId = null;
|
|
297
383
|
}
|
|
384
|
+
// Register with the overlay registry for centralized escape-key routing.
|
|
385
|
+
// outsidePress is false because the NgpDialogOverlay directive handles its own backdrop clicks.
|
|
386
|
+
this.registry.register({
|
|
387
|
+
id: dialogRef.id,
|
|
388
|
+
parentId,
|
|
389
|
+
overlay: dialogRef,
|
|
390
|
+
getElements: () => dialogRef.getElements(),
|
|
391
|
+
triggerElement: activeElement ?? this.document.body,
|
|
392
|
+
dismissPolicy: {
|
|
393
|
+
outsidePress: false,
|
|
394
|
+
escapeKey: config.closeOnEscape ?? true,
|
|
395
|
+
},
|
|
396
|
+
outsidePointerEvents$: dialogRef.outsidePointerEvents$,
|
|
397
|
+
});
|
|
298
398
|
this.openDialogs.push(dialogRef);
|
|
299
399
|
this.afterOpened.next(dialogRef);
|
|
300
400
|
this.subscribeToRouterEvents();
|
|
301
401
|
dialogRef.closed.subscribe(closeResult => {
|
|
402
|
+
// Deregister from the overlay registry
|
|
403
|
+
this.registry.deregister(dialogRef.id);
|
|
302
404
|
this.removeOpenDialog(dialogRef, true);
|
|
303
405
|
// Focus the trigger element after the dialog closes.
|
|
304
406
|
if (activeElement instanceof HTMLElement && this.document.body.contains(activeElement)) {
|
|
@@ -324,8 +426,8 @@ class NgpDialogManager {
|
|
|
324
426
|
}
|
|
325
427
|
/**
|
|
326
428
|
* Subscribe to router navigation events so that dialogs with `closeOnNavigation`
|
|
327
|
-
* are closed when the user navigates
|
|
328
|
-
*
|
|
429
|
+
* are closed when the user navigates. This handles both browser popstate events
|
|
430
|
+
* and programmatic route changes (e.g. router.navigate()).
|
|
329
431
|
*/
|
|
330
432
|
subscribeToRouterEvents() {
|
|
331
433
|
if (this.routerSubscription || !this.router) {
|
|
@@ -361,33 +463,19 @@ class NgpDialogManager {
|
|
|
361
463
|
this.openDialogsAtThisLevel = [];
|
|
362
464
|
this.routerSubscription?.unsubscribe();
|
|
363
465
|
}
|
|
364
|
-
/**
|
|
365
|
-
* Creates an overlay config from a dialog config.
|
|
366
|
-
*/
|
|
367
|
-
getOverlayConfig(config) {
|
|
368
|
-
const state = new OverlayConfig({
|
|
369
|
-
positionStrategy: this.overlay.position().global().centerHorizontally().centerVertically(),
|
|
370
|
-
scrollStrategy: config.scrollStrategy || this.scrollStrategy,
|
|
371
|
-
hasBackdrop: false,
|
|
372
|
-
disposeOnNavigation: config.closeOnNavigation,
|
|
373
|
-
// required for v21 - the CDK launches overlays using the popover api which means any other overlays
|
|
374
|
-
// such as select dropdowns, or tooltips will appear behind the dialog, regardless of z-index
|
|
375
|
-
// this disables the use of popovers
|
|
376
|
-
usePopover: false,
|
|
377
|
-
});
|
|
378
|
-
return state;
|
|
379
|
-
}
|
|
380
466
|
/**
|
|
381
467
|
* Creates a custom injector to be used inside the dialog. This allows a component loaded inside
|
|
382
468
|
* of a dialog to close itself and, optionally, to return a value.
|
|
383
469
|
*/
|
|
384
|
-
createInjector(config, dialogRef
|
|
470
|
+
createInjector(config, dialogRef) {
|
|
385
471
|
const userInjector = config.injector || config.viewContainerRef?.injector;
|
|
386
472
|
const providers = [
|
|
387
473
|
{ provide: NgpDialogRef, useValue: dialogRef },
|
|
388
474
|
{ provide: NgpExitAnimationManager, useClass: NgpExitAnimationManager },
|
|
389
475
|
];
|
|
390
|
-
|
|
476
|
+
// Fall back to the service's own injector (root injector) to ensure
|
|
477
|
+
// ApplicationRef and other platform providers are available.
|
|
478
|
+
return Injector.create({ parent: userInjector || this.injector, providers });
|
|
391
479
|
}
|
|
392
480
|
/**
|
|
393
481
|
* Removes a dialog from the array of open dialogs.
|
|
@@ -408,27 +496,44 @@ class NgpDialogManager {
|
|
|
408
496
|
}
|
|
409
497
|
});
|
|
410
498
|
this.ariaHiddenElements.clear();
|
|
499
|
+
this.disableScrollBlocking();
|
|
411
500
|
if (emitEvent) {
|
|
412
501
|
this.getAfterAllClosed().next();
|
|
413
502
|
}
|
|
414
503
|
}
|
|
415
504
|
}
|
|
416
505
|
}
|
|
417
|
-
/**
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
if (
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
506
|
+
/**
|
|
507
|
+
* Enable scroll blocking when the first dialog opens.
|
|
508
|
+
*/
|
|
509
|
+
enableScrollBlocking(config) {
|
|
510
|
+
if (!this.scrollStrategy) {
|
|
511
|
+
this.scrollStrategy =
|
|
512
|
+
config?.scrollStrategy ?? new BlockScrollStrategy(this.viewportRuler, this.document);
|
|
513
|
+
}
|
|
514
|
+
this.scrollStrategy.enable();
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Disable scroll blocking when the last dialog closes.
|
|
518
|
+
*/
|
|
519
|
+
disableScrollBlocking() {
|
|
520
|
+
this.scrollStrategy?.disable();
|
|
521
|
+
this.scrollStrategy = null;
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Hides all of the content that isn't a dialog portal from assistive technology.
|
|
525
|
+
*/
|
|
526
|
+
hideNonDialogContentFromAssistiveTechnology(portalElements) {
|
|
527
|
+
const body = this.document.body;
|
|
528
|
+
const bodyChildren = body.children;
|
|
529
|
+
for (let i = bodyChildren.length - 1; i > -1; i--) {
|
|
530
|
+
const sibling = bodyChildren[i];
|
|
531
|
+
if (!portalElements.includes(sibling) &&
|
|
532
|
+
sibling.nodeName !== 'SCRIPT' &&
|
|
533
|
+
sibling.nodeName !== 'STYLE' &&
|
|
534
|
+
!sibling.hasAttribute('aria-live')) {
|
|
535
|
+
this.ariaHiddenElements.set(sibling, sibling.getAttribute('aria-hidden'));
|
|
536
|
+
sibling.setAttribute('aria-hidden', 'true');
|
|
432
537
|
}
|
|
433
538
|
}
|
|
434
539
|
}
|