ng-primitives 0.7.0 → 0.7.2
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/config/dialog.config.d.ts +43 -0
- package/dialog/dialog/dialog-ref.d.ts +40 -0
- package/dialog/dialog/dialog.directive.d.ts +29 -21
- package/dialog/dialog/dialog.service.d.ts +66 -0
- package/dialog/dialog-description/dialog-description.directive.d.ts +19 -0
- package/dialog/dialog-description/dialog-description.token.d.ts +14 -0
- package/dialog/dialog-overlay/dialog-overlay.directive.d.ts +8 -0
- package/dialog/{dialog-panel/dialog-panel.token.d.ts → dialog-overlay/dialog-overlay.token.d.ts} +4 -4
- package/dialog/dialog-title/dialog-title.directive.d.ts +14 -4
- package/dialog/dialog-trigger/dialog-trigger.directive.d.ts +24 -0
- package/dialog/dialog-trigger/dialog-trigger.token.d.ts +14 -0
- package/dialog/index.d.ts +8 -3
- package/esm2022/date-picker/date-picker-row-render/date-picker-row-render.directive.mjs +1 -2
- package/esm2022/dialog/config/dialog.config.mjs +28 -0
- package/esm2022/dialog/dialog/dialog-ref.mjs +44 -0
- package/esm2022/dialog/dialog/dialog.directive.mjs +59 -27
- package/esm2022/dialog/dialog/dialog.service.mjs +202 -0
- package/esm2022/dialog/dialog-description/dialog-description.directive.mjs +46 -0
- package/esm2022/dialog/dialog-description/dialog-description.token.mjs +16 -0
- package/esm2022/dialog/dialog-overlay/dialog-overlay.directive.mjs +35 -0
- package/esm2022/dialog/dialog-overlay/dialog-overlay.token.mjs +16 -0
- package/esm2022/dialog/dialog-title/dialog-title.directive.mjs +22 -7
- package/esm2022/dialog/dialog-trigger/dialog-trigger.directive.mjs +61 -0
- package/esm2022/dialog/dialog-trigger/dialog-trigger.token.mjs +16 -0
- package/esm2022/dialog/index.mjs +8 -3
- package/esm2022/internal/disabled/disabled.mjs +2 -2
- package/esm2022/utils/signals/async.mjs +3 -1
- package/fesm2022/ng-primitives-date-picker.mjs +0 -1
- package/fesm2022/ng-primitives-date-picker.mjs.map +1 -1
- package/fesm2022/ng-primitives-dialog.mjs +495 -46
- package/fesm2022/ng-primitives-dialog.mjs.map +1 -1
- package/fesm2022/ng-primitives-internal.mjs +1 -1
- package/fesm2022/ng-primitives-internal.mjs.map +1 -1
- package/fesm2022/ng-primitives-utils.mjs +2 -0
- package/fesm2022/ng-primitives-utils.mjs.map +1 -1
- package/package.json +7 -7
- package/dialog/dialog-panel/dialog-panel.directive.d.ts +0 -5
- package/esm2022/dialog/dialog-panel/dialog-panel.directive.mjs +0 -24
- package/esm2022/dialog/dialog-panel/dialog-panel.token.mjs +0 -16
|
@@ -1,6 +1,58 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, inject, Directive,
|
|
3
|
-
import { uniqueId } from 'ng-primitives/utils';
|
|
2
|
+
import { InjectionToken, inject, input, Directive, HostListener, isDevMode, Injector, Injectable, ViewContainerRef, ElementRef, booleanAttribute, signal } from '@angular/core';
|
|
3
|
+
import { uniqueId, onChange } from 'ng-primitives/utils';
|
|
4
|
+
import { hasModifierKey } from '@angular/cdk/keycodes';
|
|
5
|
+
import { Subject, defer } from 'rxjs';
|
|
6
|
+
import { FocusMonitor } from '@angular/cdk/a11y';
|
|
7
|
+
import * as i1 from 'ng-primitives/button';
|
|
8
|
+
import { NgpButton } from 'ng-primitives/button';
|
|
9
|
+
import { Overlay, OverlayContainer, OverlayConfig } from '@angular/cdk/overlay';
|
|
10
|
+
import { TemplatePortal } from '@angular/cdk/portal';
|
|
11
|
+
import { startWith } from 'rxjs/operators';
|
|
12
|
+
import * as i1$1 from 'ng-primitives/focus-trap';
|
|
13
|
+
import { NgpFocusTrap } from 'ng-primitives/focus-trap';
|
|
14
|
+
|
|
15
|
+
const defaultDialogConfig = {
|
|
16
|
+
role: 'dialog',
|
|
17
|
+
modal: true,
|
|
18
|
+
closeOnNavigation: true,
|
|
19
|
+
};
|
|
20
|
+
const NgpDialogConfigToken = new InjectionToken('NgpDialogConfigToken');
|
|
21
|
+
/**
|
|
22
|
+
* Provide the default Dialog configuration
|
|
23
|
+
* @param config The Dialog configuration
|
|
24
|
+
* @returns The provider
|
|
25
|
+
*/
|
|
26
|
+
function provideDialogConfig(config) {
|
|
27
|
+
return [
|
|
28
|
+
{
|
|
29
|
+
provide: NgpDialogConfigToken,
|
|
30
|
+
useValue: { ...defaultDialogConfig, ...config },
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Inject the Dialog configuration
|
|
36
|
+
* @returns The global Dialog configuration
|
|
37
|
+
*/
|
|
38
|
+
function injectDialogConfig() {
|
|
39
|
+
return inject(NgpDialogConfigToken, { optional: true }) ?? defaultDialogConfig;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Copyright © 2024 Angular Primitives.
|
|
44
|
+
* https://github.com/ng-primitives/ng-primitives
|
|
45
|
+
*
|
|
46
|
+
* This source code is licensed under the Apache 2.0 license found in the
|
|
47
|
+
* LICENSE file in the root directory of this source tree.
|
|
48
|
+
*/
|
|
49
|
+
const NgpDialogToken = new InjectionToken('NgpDialogToken');
|
|
50
|
+
/**
|
|
51
|
+
* Inject the Dialog directive instance
|
|
52
|
+
*/
|
|
53
|
+
function injectDialog() {
|
|
54
|
+
return inject(NgpDialogToken);
|
|
55
|
+
}
|
|
4
56
|
|
|
5
57
|
/**
|
|
6
58
|
* Copyright © 2024 Angular Primitives.
|
|
@@ -9,12 +61,12 @@ import { uniqueId } from 'ng-primitives/utils';
|
|
|
9
61
|
* This source code is licensed under the Apache 2.0 license found in the
|
|
10
62
|
* LICENSE file in the root directory of this source tree.
|
|
11
63
|
*/
|
|
12
|
-
const
|
|
64
|
+
const NgpDialogDescriptionToken = new InjectionToken('NgpDialogDescriptionToken');
|
|
13
65
|
/**
|
|
14
|
-
* Inject the
|
|
66
|
+
* Inject the DialogDescription directive instance
|
|
15
67
|
*/
|
|
16
|
-
function
|
|
17
|
-
return inject(
|
|
68
|
+
function injectDialogDescription() {
|
|
69
|
+
return inject(NgpDialogDescriptionToken);
|
|
18
70
|
}
|
|
19
71
|
|
|
20
72
|
/**
|
|
@@ -24,19 +76,126 @@ function injectDialogPanel() {
|
|
|
24
76
|
* This source code is licensed under the Apache 2.0 license found in the
|
|
25
77
|
* LICENSE file in the root directory of this source tree.
|
|
26
78
|
*/
|
|
27
|
-
class
|
|
28
|
-
|
|
29
|
-
|
|
79
|
+
class NgpDialogDescription {
|
|
80
|
+
constructor() {
|
|
81
|
+
/** Access the dialog */
|
|
82
|
+
this.dialog = injectDialog();
|
|
83
|
+
/** The id of the descriptions. */
|
|
84
|
+
this.id = input(uniqueId('ngp-dialog-description'));
|
|
85
|
+
onChange(this.id, (id, prevId) => {
|
|
86
|
+
if (prevId) {
|
|
87
|
+
this.dialog.removeDescribedBy(prevId);
|
|
88
|
+
}
|
|
89
|
+
if (id) {
|
|
90
|
+
this.dialog.setDescribedBy(id);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
ngOnDestroy() {
|
|
95
|
+
this.dialog.removeDescribedBy(this.id());
|
|
96
|
+
}
|
|
97
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.1.1", ngImport: i0, type: NgpDialogDescription, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
98
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "18.1.1", type: NgpDialogDescription, isStandalone: true, selector: "[ngpDialogDescription]", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "id": "id()" } }, providers: [{ provide: NgpDialogDescriptionToken, useExisting: NgpDialogDescription }], exportAs: ["ngpDialogDescription"], ngImport: i0 }); }
|
|
30
99
|
}
|
|
31
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.1", ngImport: i0, type:
|
|
100
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.1", ngImport: i0, type: NgpDialogDescription, decorators: [{
|
|
32
101
|
type: Directive,
|
|
33
102
|
args: [{
|
|
34
103
|
standalone: true,
|
|
35
|
-
selector: '[
|
|
36
|
-
exportAs: '
|
|
37
|
-
providers: [{ provide:
|
|
104
|
+
selector: '[ngpDialogDescription]',
|
|
105
|
+
exportAs: 'ngpDialogDescription',
|
|
106
|
+
providers: [{ provide: NgpDialogDescriptionToken, useExisting: NgpDialogDescription }],
|
|
107
|
+
host: {
|
|
108
|
+
'[id]': 'id()',
|
|
109
|
+
},
|
|
38
110
|
}]
|
|
39
|
-
}] });
|
|
111
|
+
}], ctorParameters: () => [] });
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Reference to a dialog opened via the Dialog service.
|
|
115
|
+
*/
|
|
116
|
+
class NgpDialogRef {
|
|
117
|
+
constructor(overlayRef, config) {
|
|
118
|
+
this.overlayRef = overlayRef;
|
|
119
|
+
this.config = config;
|
|
120
|
+
/** Emits when the dialog has been closed. */
|
|
121
|
+
this.closed = new Subject();
|
|
122
|
+
this.keydownEvents = overlayRef.keydownEvents();
|
|
123
|
+
this.outsidePointerEvents = overlayRef.outsidePointerEvents();
|
|
124
|
+
this.id = config.id; // By the time the dialog is created we are guaranteed to have an ID.
|
|
125
|
+
this.keydownEvents.subscribe(event => {
|
|
126
|
+
if (event.key === 'Escape' && !this.disableClose && !hasModifierKey(event)) {
|
|
127
|
+
event.preventDefault();
|
|
128
|
+
this.close('keyboard');
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
this.detachSubscription = overlayRef.detachments().subscribe(() => this.close());
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Close the dialog.
|
|
135
|
+
* @param result Optional result to return to the dialog opener.
|
|
136
|
+
* @param options Additional options to customize the closing behavior.
|
|
137
|
+
*/
|
|
138
|
+
close(focusOrigin) {
|
|
139
|
+
this.overlayRef.dispose();
|
|
140
|
+
this.detachSubscription.unsubscribe();
|
|
141
|
+
this.closed.next(focusOrigin ?? null);
|
|
142
|
+
this.closed.complete();
|
|
143
|
+
}
|
|
144
|
+
/** Updates the position of the dialog based on the current position strategy. */
|
|
145
|
+
updatePosition() {
|
|
146
|
+
this.overlayRef.updatePosition();
|
|
147
|
+
return this;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function injectDialogRef() {
|
|
151
|
+
return inject(NgpDialogRef);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Copyright © 2024 Angular Primitives.
|
|
156
|
+
* https://github.com/ng-primitives/ng-primitives
|
|
157
|
+
*
|
|
158
|
+
* This source code is licensed under the Apache 2.0 license found in the
|
|
159
|
+
* LICENSE file in the root directory of this source tree.
|
|
160
|
+
*/
|
|
161
|
+
const NgpDialogOverlayToken = new InjectionToken('NgpDialogOverlayToken');
|
|
162
|
+
/**
|
|
163
|
+
* Inject the DialogOverlay directive instance
|
|
164
|
+
*/
|
|
165
|
+
function injectDialogOverlay() {
|
|
166
|
+
return inject(NgpDialogOverlayToken);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Copyright © 2024 Angular Primitives.
|
|
171
|
+
* https://github.com/ng-primitives/ng-primitives
|
|
172
|
+
*
|
|
173
|
+
* This source code is licensed under the Apache 2.0 license found in the
|
|
174
|
+
* LICENSE file in the root directory of this source tree.
|
|
175
|
+
*/
|
|
176
|
+
class NgpDialogOverlay {
|
|
177
|
+
constructor() {
|
|
178
|
+
/** Access the dialog ref. */
|
|
179
|
+
this.dialogRef = injectDialogRef();
|
|
180
|
+
}
|
|
181
|
+
close() {
|
|
182
|
+
this.dialogRef.close();
|
|
183
|
+
}
|
|
184
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.1.1", ngImport: i0, type: NgpDialogOverlay, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
185
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.1.1", type: NgpDialogOverlay, isStandalone: true, selector: "[ngpDialogOverlay]", host: { listeners: { "click": "close()" } }, providers: [{ provide: NgpDialogOverlayToken, useExisting: NgpDialogOverlay }], exportAs: ["ngpDialogOverlay"], ngImport: i0 }); }
|
|
186
|
+
}
|
|
187
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.1", ngImport: i0, type: NgpDialogOverlay, decorators: [{
|
|
188
|
+
type: Directive,
|
|
189
|
+
args: [{
|
|
190
|
+
standalone: true,
|
|
191
|
+
selector: '[ngpDialogOverlay]',
|
|
192
|
+
exportAs: 'ngpDialogOverlay',
|
|
193
|
+
providers: [{ provide: NgpDialogOverlayToken, useExisting: NgpDialogOverlay }],
|
|
194
|
+
}]
|
|
195
|
+
}], propDecorators: { close: [{
|
|
196
|
+
type: HostListener,
|
|
197
|
+
args: ['click']
|
|
198
|
+
}] } });
|
|
40
199
|
|
|
41
200
|
/**
|
|
42
201
|
* Copyright © 2024 Angular Primitives.
|
|
@@ -62,13 +221,24 @@ function injectDialogTitle() {
|
|
|
62
221
|
*/
|
|
63
222
|
class NgpDialogTitle {
|
|
64
223
|
constructor() {
|
|
65
|
-
/**
|
|
66
|
-
|
|
67
|
-
|
|
224
|
+
/** Access the dialog. */
|
|
225
|
+
this.dialog = injectDialog();
|
|
226
|
+
/** The id of the title. */
|
|
68
227
|
this.id = input(uniqueId('ngp-dialog-title'));
|
|
228
|
+
onChange(this.id, (id, prevId) => {
|
|
229
|
+
if (prevId) {
|
|
230
|
+
this.dialog.removeLabelledBy(prevId);
|
|
231
|
+
}
|
|
232
|
+
if (id) {
|
|
233
|
+
this.dialog.setLabelledBy(id);
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
ngOnDestroy() {
|
|
238
|
+
this.dialog.removeLabelledBy(this.id());
|
|
69
239
|
}
|
|
70
240
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.1.1", ngImport: i0, type: NgpDialogTitle, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
71
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "18.1.1", type: NgpDialogTitle, isStandalone: true, selector: "[ngpDialogTitle]", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null } }, providers: [{ provide: NgpDialogTitleToken, useExisting: NgpDialogTitle }], exportAs: ["ngpDialogTitle"], ngImport: i0 }); }
|
|
241
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "18.1.1", type: NgpDialogTitle, isStandalone: true, selector: "[ngpDialogTitle]", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "id": "id()" } }, providers: [{ provide: NgpDialogTitleToken, useExisting: NgpDialogTitle }], exportAs: ["ngpDialogTitle"], ngImport: i0 }); }
|
|
72
242
|
}
|
|
73
243
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.1", ngImport: i0, type: NgpDialogTitle, decorators: [{
|
|
74
244
|
type: Directive,
|
|
@@ -77,8 +247,204 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.1", ngImpor
|
|
|
77
247
|
selector: '[ngpDialogTitle]',
|
|
78
248
|
exportAs: 'ngpDialogTitle',
|
|
79
249
|
providers: [{ provide: NgpDialogTitleToken, useExisting: NgpDialogTitle }],
|
|
250
|
+
host: {
|
|
251
|
+
'[id]': 'id()',
|
|
252
|
+
},
|
|
253
|
+
}]
|
|
254
|
+
}], ctorParameters: () => [] });
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Copyright © 2024 Angular Primitives.
|
|
258
|
+
* https://github.com/ng-primitives/ng-primitives
|
|
259
|
+
*
|
|
260
|
+
* This source code is licensed under the Apache 2.0 license found in the
|
|
261
|
+
* LICENSE file in the root directory of this source tree.
|
|
262
|
+
*/
|
|
263
|
+
/**
|
|
264
|
+
* This is based on the Angular CDK Dialog service.
|
|
265
|
+
* https://github.com/angular/components/blob/main/src/cdk/dialog/dialog.ts
|
|
266
|
+
*/
|
|
267
|
+
class NgpDialogManager {
|
|
268
|
+
constructor() {
|
|
269
|
+
this.overlay = inject(Overlay);
|
|
270
|
+
this.defaultOptions = injectDialogConfig();
|
|
271
|
+
this.parentDialogManager = inject(NgpDialogManager, {
|
|
272
|
+
optional: true,
|
|
273
|
+
skipSelf: true,
|
|
274
|
+
});
|
|
275
|
+
this.overlayContainer = inject(OverlayContainer);
|
|
276
|
+
this.scrollStrategy = this.defaultOptions.scrollStrategy ?? this.overlay.scrollStrategies.block();
|
|
277
|
+
this.openDialogsAtThisLevel = [];
|
|
278
|
+
this.afterAllClosedAtThisLevel = new Subject();
|
|
279
|
+
this.afterOpenedAtThisLevel = new Subject();
|
|
280
|
+
this.ariaHiddenElements = new Map();
|
|
281
|
+
/**
|
|
282
|
+
* Stream that emits when all open dialog have finished closing.
|
|
283
|
+
* Will emit on subscribe if there are no open dialogs to begin with.
|
|
284
|
+
*/
|
|
285
|
+
this.afterAllClosed = defer(() => this.openDialogs.length
|
|
286
|
+
? this.getAfterAllClosed()
|
|
287
|
+
: this.getAfterAllClosed().pipe(startWith(undefined)));
|
|
288
|
+
}
|
|
289
|
+
/** Keeps track of the currently-open dialogs. */
|
|
290
|
+
get openDialogs() {
|
|
291
|
+
return this.parentDialogManager
|
|
292
|
+
? this.parentDialogManager.openDialogs
|
|
293
|
+
: this.openDialogsAtThisLevel;
|
|
294
|
+
}
|
|
295
|
+
/** Stream that emits when a dialog has been opened. */
|
|
296
|
+
get afterOpened() {
|
|
297
|
+
return this.parentDialogManager
|
|
298
|
+
? this.parentDialogManager.afterOpened
|
|
299
|
+
: this.afterOpenedAtThisLevel;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Opens a modal dialog containing the given template.
|
|
303
|
+
*/
|
|
304
|
+
open(templateRef, config) {
|
|
305
|
+
const defaults = this.defaultOptions;
|
|
306
|
+
config = { ...defaults, ...config };
|
|
307
|
+
config.id = config.id ?? uniqueId('ngp-dialog');
|
|
308
|
+
if (config.id && this.getDialogById(config.id) && isDevMode()) {
|
|
309
|
+
throw Error(`Dialog with id "${config.id}" exists already. The dialog id must be unique.`);
|
|
310
|
+
}
|
|
311
|
+
const overlayConfig = this.getOverlayConfig(config);
|
|
312
|
+
const overlayRef = this.overlay.create(overlayConfig);
|
|
313
|
+
const dialogRef = new NgpDialogRef(overlayRef, config);
|
|
314
|
+
const injector = this.createInjector(config, dialogRef, undefined);
|
|
315
|
+
const context = {
|
|
316
|
+
$implicit: dialogRef,
|
|
317
|
+
close: dialogRef.close.bind(dialogRef),
|
|
318
|
+
};
|
|
319
|
+
overlayRef.attach(new TemplatePortal(templateRef, config.viewContainerRef, context, injector));
|
|
320
|
+
// If this is the first dialog that we're opening, hide all the non-overlay content.
|
|
321
|
+
if (!this.openDialogs.length) {
|
|
322
|
+
this.hideNonDialogContentFromAssistiveTechnology();
|
|
323
|
+
}
|
|
324
|
+
this.openDialogs.push(dialogRef);
|
|
325
|
+
dialogRef.closed.subscribe(() => this.removeOpenDialog(dialogRef, true));
|
|
326
|
+
this.afterOpened.next(dialogRef);
|
|
327
|
+
return dialogRef;
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Closes all of the currently-open dialogs.
|
|
331
|
+
*/
|
|
332
|
+
closeAll() {
|
|
333
|
+
reverseForEach(this.openDialogs, dialog => dialog.close());
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Finds an open dialog by its id.
|
|
337
|
+
* @param id ID to use when looking up the dialog.
|
|
338
|
+
*/
|
|
339
|
+
getDialogById(id) {
|
|
340
|
+
return this.openDialogs.find(dialog => dialog.id === id);
|
|
341
|
+
}
|
|
342
|
+
ngOnDestroy() {
|
|
343
|
+
// Make one pass over all the dialogs that need to be untracked, but should not be closed. We
|
|
344
|
+
// want to stop tracking the open dialog even if it hasn't been closed, because the tracking
|
|
345
|
+
// determines when `aria-hidden` is removed from elements outside the dialog.
|
|
346
|
+
reverseForEach(this.openDialogsAtThisLevel, dialog => {
|
|
347
|
+
// Check for `false` specifically since we want `undefined` to be interpreted as `true`.
|
|
348
|
+
this.removeOpenDialog(dialog, false);
|
|
349
|
+
});
|
|
350
|
+
// Make a second pass and close the remaining dialogs. We do this second pass in order to
|
|
351
|
+
// correctly dispatch the `afterAllClosed` event in case we have a mixed array of dialogs
|
|
352
|
+
// that should be closed and dialogs that should not.
|
|
353
|
+
reverseForEach(this.openDialogsAtThisLevel, dialog => dialog.close());
|
|
354
|
+
this.afterAllClosedAtThisLevel.complete();
|
|
355
|
+
this.afterOpenedAtThisLevel.complete();
|
|
356
|
+
this.openDialogsAtThisLevel = [];
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Creates an overlay config from a dialog config.
|
|
360
|
+
*/
|
|
361
|
+
getOverlayConfig(config) {
|
|
362
|
+
const state = new OverlayConfig({
|
|
363
|
+
positionStrategy: this.overlay.position().global().centerHorizontally().centerVertically(),
|
|
364
|
+
scrollStrategy: config.scrollStrategy || this.scrollStrategy,
|
|
365
|
+
hasBackdrop: false,
|
|
366
|
+
disposeOnNavigation: config.closeOnNavigation,
|
|
367
|
+
});
|
|
368
|
+
return state;
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Creates a custom injector to be used inside the dialog. This allows a component loaded inside
|
|
372
|
+
* of a dialog to close itself and, optionally, to return a value.
|
|
373
|
+
*/
|
|
374
|
+
createInjector(config, dialogRef, fallbackInjector) {
|
|
375
|
+
const userInjector = config.injector || config.viewContainerRef?.injector;
|
|
376
|
+
const providers = [{ provide: NgpDialogRef, useValue: dialogRef }];
|
|
377
|
+
return Injector.create({ parent: userInjector || fallbackInjector, providers });
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Removes a dialog from the array of open dialogs.
|
|
381
|
+
*/
|
|
382
|
+
removeOpenDialog(dialogRef, emitEvent) {
|
|
383
|
+
const index = this.openDialogs.indexOf(dialogRef);
|
|
384
|
+
if (index > -1) {
|
|
385
|
+
this.openDialogs.splice(index, 1);
|
|
386
|
+
// If all the dialogs were closed, remove/restore the `aria-hidden`
|
|
387
|
+
// to a the siblings and emit to the `afterAllClosed` stream.
|
|
388
|
+
if (!this.openDialogs.length) {
|
|
389
|
+
this.ariaHiddenElements.forEach((previousValue, element) => {
|
|
390
|
+
if (previousValue) {
|
|
391
|
+
element.setAttribute('aria-hidden', previousValue);
|
|
392
|
+
}
|
|
393
|
+
else {
|
|
394
|
+
element.removeAttribute('aria-hidden');
|
|
395
|
+
}
|
|
396
|
+
});
|
|
397
|
+
this.ariaHiddenElements.clear();
|
|
398
|
+
if (emitEvent) {
|
|
399
|
+
this.getAfterAllClosed().next();
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
/** Hides all of the content that isn't an overlay from assistive technology. */
|
|
405
|
+
hideNonDialogContentFromAssistiveTechnology() {
|
|
406
|
+
const overlayContainer = this.overlayContainer.getContainerElement();
|
|
407
|
+
// Ensure that the overlay container is attached to the DOM.
|
|
408
|
+
if (overlayContainer.parentElement) {
|
|
409
|
+
const siblings = overlayContainer.parentElement.children;
|
|
410
|
+
for (let i = siblings.length - 1; i > -1; i--) {
|
|
411
|
+
const sibling = siblings[i];
|
|
412
|
+
if (sibling !== overlayContainer &&
|
|
413
|
+
sibling.nodeName !== 'SCRIPT' &&
|
|
414
|
+
sibling.nodeName !== 'STYLE' &&
|
|
415
|
+
!sibling.hasAttribute('aria-live')) {
|
|
416
|
+
this.ariaHiddenElements.set(sibling, sibling.getAttribute('aria-hidden'));
|
|
417
|
+
sibling.setAttribute('aria-hidden', 'true');
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
getAfterAllClosed() {
|
|
423
|
+
const parent = this.parentDialogManager;
|
|
424
|
+
return parent ? parent.getAfterAllClosed() : this.afterAllClosedAtThisLevel;
|
|
425
|
+
}
|
|
426
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.1.1", ngImport: i0, type: NgpDialogManager, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
427
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.1.1", ngImport: i0, type: NgpDialogManager, providedIn: 'root' }); }
|
|
428
|
+
}
|
|
429
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.1", ngImport: i0, type: NgpDialogManager, decorators: [{
|
|
430
|
+
type: Injectable,
|
|
431
|
+
args: [{
|
|
432
|
+
providedIn: 'root',
|
|
80
433
|
}]
|
|
81
434
|
}] });
|
|
435
|
+
/**
|
|
436
|
+
* Executes a callback against all elements in an array while iterating in reverse.
|
|
437
|
+
* Useful if the array is being modified as it is being iterated.
|
|
438
|
+
*/
|
|
439
|
+
function reverseForEach(items, callback) {
|
|
440
|
+
let i = items.length;
|
|
441
|
+
while (i--) {
|
|
442
|
+
callback(items[i]);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
function injectDialogManager() {
|
|
446
|
+
return inject(NgpDialogManager);
|
|
447
|
+
}
|
|
82
448
|
|
|
83
449
|
/**
|
|
84
450
|
* Copyright © 2024 Angular Primitives.
|
|
@@ -87,40 +453,118 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.1", ngImpor
|
|
|
87
453
|
* This source code is licensed under the Apache 2.0 license found in the
|
|
88
454
|
* LICENSE file in the root directory of this source tree.
|
|
89
455
|
*/
|
|
90
|
-
const
|
|
456
|
+
const NgpDialogTriggerToken = new InjectionToken('NgpDialogTriggerToken');
|
|
91
457
|
/**
|
|
92
|
-
* Inject the
|
|
458
|
+
* Inject the DialogTrigger directive instance
|
|
93
459
|
*/
|
|
94
|
-
function
|
|
95
|
-
return inject(
|
|
460
|
+
function injectDialogTrigger() {
|
|
461
|
+
return inject(NgpDialogTriggerToken);
|
|
96
462
|
}
|
|
97
463
|
|
|
98
|
-
|
|
464
|
+
/**
|
|
465
|
+
* Copyright © 2024 Angular Primitives.
|
|
466
|
+
* https://github.com/ng-primitives/ng-primitives
|
|
467
|
+
*
|
|
468
|
+
* This source code is licensed under the Apache 2.0 license found in the
|
|
469
|
+
* LICENSE file in the root directory of this source tree.
|
|
470
|
+
*/
|
|
471
|
+
class NgpDialogTrigger {
|
|
99
472
|
constructor() {
|
|
100
|
-
/**
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
this.
|
|
104
|
-
/**
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
this.
|
|
108
|
-
|
|
109
|
-
|
|
473
|
+
/** Access the dialog manager. */
|
|
474
|
+
this.dialogManager = inject(NgpDialogManager);
|
|
475
|
+
/** Access the view container ref. */
|
|
476
|
+
this.viewContainerRef = inject(ViewContainerRef);
|
|
477
|
+
/** Access the focus monitor. */
|
|
478
|
+
this.focusMonitor = inject(FocusMonitor);
|
|
479
|
+
/** Access the element ref. */
|
|
480
|
+
this.elementRef = inject(ElementRef);
|
|
481
|
+
/** The template to launch. */
|
|
482
|
+
this.template = input.required({
|
|
483
|
+
alias: 'ngpDialogTrigger',
|
|
110
484
|
});
|
|
111
485
|
/**
|
|
112
|
-
*
|
|
486
|
+
* Store the dialog ref.
|
|
487
|
+
* @internal
|
|
113
488
|
*/
|
|
114
|
-
this.
|
|
489
|
+
this.dialogRef = null;
|
|
490
|
+
}
|
|
491
|
+
launch() {
|
|
492
|
+
this.dialogRef = this.dialogManager.open(this.template(), {
|
|
493
|
+
viewContainerRef: this.viewContainerRef,
|
|
494
|
+
});
|
|
495
|
+
this.dialogRef.closed.subscribe(focusOrigin => {
|
|
496
|
+
this.dialogRef = null;
|
|
497
|
+
// Focus the trigger element after the dialog closes.
|
|
498
|
+
this.focusMonitor.focusVia(this.elementRef.nativeElement, focusOrigin);
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.1.1", ngImport: i0, type: NgpDialogTrigger, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
502
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "18.1.1", type: NgpDialogTrigger, isStandalone: true, selector: "[ngpDialogTrigger]", inputs: { template: { classPropertyName: "template", publicName: "ngpDialogTrigger", isSignal: true, isRequired: true, transformFunction: null } }, host: { listeners: { "click": "launch()" } }, providers: [{ provide: NgpDialogTriggerToken, useExisting: NgpDialogTrigger }], exportAs: ["ngpDialogTrigger"], hostDirectives: [{ directive: i1.NgpButton }], ngImport: i0 }); }
|
|
503
|
+
}
|
|
504
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.1", ngImport: i0, type: NgpDialogTrigger, decorators: [{
|
|
505
|
+
type: Directive,
|
|
506
|
+
args: [{
|
|
507
|
+
standalone: true,
|
|
508
|
+
selector: '[ngpDialogTrigger]',
|
|
509
|
+
exportAs: 'ngpDialogTrigger',
|
|
510
|
+
providers: [{ provide: NgpDialogTriggerToken, useExisting: NgpDialogTrigger }],
|
|
511
|
+
hostDirectives: [NgpButton],
|
|
512
|
+
}]
|
|
513
|
+
}], propDecorators: { launch: [{
|
|
514
|
+
type: HostListener,
|
|
515
|
+
args: ['click']
|
|
516
|
+
}] } });
|
|
517
|
+
|
|
518
|
+
class NgpDialog {
|
|
519
|
+
constructor() {
|
|
520
|
+
this.config = injectDialogConfig();
|
|
521
|
+
/** Access the dialog ref */
|
|
522
|
+
this.dialogRef = injectDialogRef();
|
|
523
|
+
/** The id of the dialog */
|
|
524
|
+
this.id = input(uniqueId('ngp-dialog'));
|
|
525
|
+
/** The dialog role. */
|
|
526
|
+
this.role = input(this.config.role, {
|
|
115
527
|
alias: 'ngpDialogRole',
|
|
116
528
|
});
|
|
117
|
-
/**
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
529
|
+
/** Whether the dialog is a modal. */
|
|
530
|
+
this.modal = input(this.config.modal ?? false, {
|
|
531
|
+
alias: 'ngpDialogModal',
|
|
532
|
+
transform: booleanAttribute,
|
|
533
|
+
});
|
|
534
|
+
/** The labelledby ids */
|
|
535
|
+
this.labelledBy = signal([]);
|
|
536
|
+
/** The describedby ids */
|
|
537
|
+
this.describedBy = signal([]);
|
|
538
|
+
}
|
|
539
|
+
ngOnDestroy() {
|
|
540
|
+
this.close();
|
|
541
|
+
}
|
|
542
|
+
/** Close the dialog. */
|
|
543
|
+
close() {
|
|
544
|
+
this.dialogRef.close();
|
|
545
|
+
}
|
|
546
|
+
/** Stop click events from propagating to the overlay */
|
|
547
|
+
onClick(event) {
|
|
548
|
+
event.stopPropagation();
|
|
549
|
+
}
|
|
550
|
+
/** @internal register a labelledby id */
|
|
551
|
+
setLabelledBy(id) {
|
|
552
|
+
this.labelledBy.update(ids => [...ids, id]);
|
|
553
|
+
}
|
|
554
|
+
/** @internal register a describedby id */
|
|
555
|
+
setDescribedBy(id) {
|
|
556
|
+
this.describedBy.update(ids => [...ids, id]);
|
|
557
|
+
}
|
|
558
|
+
/** @internal remove a labelledby id */
|
|
559
|
+
removeLabelledBy(id) {
|
|
560
|
+
this.labelledBy.update(ids => ids.filter(i => i !== id));
|
|
561
|
+
}
|
|
562
|
+
/** @internal remove a describedby id */
|
|
563
|
+
removeDescribedBy(id) {
|
|
564
|
+
this.describedBy.update(ids => ids.filter(i => i !== id));
|
|
121
565
|
}
|
|
122
566
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.1.1", ngImport: i0, type: NgpDialog, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
123
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.
|
|
567
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "18.1.1", type: NgpDialog, isStandalone: true, selector: "[ngpDialog]", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, role: { classPropertyName: "role", publicName: "ngpDialogRole", isSignal: true, isRequired: false, transformFunction: null }, modal: { classPropertyName: "modal", publicName: "ngpDialogModal", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "tabindex": "-1" }, listeners: { "click": "onClick($event)" }, properties: { "id": "id()", "attr.role": "role()", "attr.aria-modal": "modal()", "attr.aria-labelledby": "labelledBy().join(\" \")", "attr.aria-describedby": "describedBy().join(\" \")" } }, providers: [{ provide: NgpDialogToken, useExisting: NgpDialog }], exportAs: ["ngpDialog"], hostDirectives: [{ directive: i1$1.NgpFocusTrap }], ngImport: i0 }); }
|
|
124
568
|
}
|
|
125
569
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.1", ngImport: i0, type: NgpDialog, decorators: [{
|
|
126
570
|
type: Directive,
|
|
@@ -129,15 +573,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.1", ngImpor
|
|
|
129
573
|
selector: '[ngpDialog]',
|
|
130
574
|
exportAs: 'ngpDialog',
|
|
131
575
|
providers: [{ provide: NgpDialogToken, useExisting: NgpDialog }],
|
|
576
|
+
hostDirectives: [NgpFocusTrap],
|
|
132
577
|
host: {
|
|
133
|
-
|
|
578
|
+
tabindex: '-1',
|
|
134
579
|
'[id]': 'id()',
|
|
135
|
-
'[attr.
|
|
136
|
-
'[attr.
|
|
137
|
-
'[attr.aria-labelledby]': '
|
|
580
|
+
'[attr.role]': 'role()',
|
|
581
|
+
'[attr.aria-modal]': 'modal()',
|
|
582
|
+
'[attr.aria-labelledby]': 'labelledBy().join(" ")',
|
|
583
|
+
'[attr.aria-describedby]': 'describedBy().join(" ")',
|
|
138
584
|
},
|
|
139
585
|
}]
|
|
140
|
-
}]
|
|
586
|
+
}], propDecorators: { onClick: [{
|
|
587
|
+
type: HostListener,
|
|
588
|
+
args: ['click', ['$event']]
|
|
589
|
+
}] } });
|
|
141
590
|
|
|
142
591
|
/**
|
|
143
592
|
* Copyright © 2024 Angular Primitives.
|
|
@@ -151,5 +600,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.1.1", ngImpor
|
|
|
151
600
|
* Generated bundle index. Do not edit.
|
|
152
601
|
*/
|
|
153
602
|
|
|
154
|
-
export { NgpDialog,
|
|
603
|
+
export { NgpDialog, NgpDialogDescription, NgpDialogDescriptionToken, NgpDialogOverlay, NgpDialogOverlayToken, NgpDialogTitle, NgpDialogTitleToken, NgpDialogToken, NgpDialogTrigger, NgpDialogTriggerToken, provideDialogConfig };
|
|
155
604
|
//# sourceMappingURL=ng-primitives-dialog.mjs.map
|