ngx-kit 8.4.0 → 8.5.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/dialog/README.md +212 -73
- package/fesm2022/ngx-kit-core.mjs +29 -73
- package/fesm2022/ngx-kit-core.mjs.map +1 -1
- package/fesm2022/ngx-kit-data-table.mjs +12 -8
- package/fesm2022/ngx-kit-data-table.mjs.map +1 -1
- package/fesm2022/ngx-kit-dialog.mjs +10 -85
- package/fesm2022/ngx-kit-dialog.mjs.map +1 -1
- package/fesm2022/ngx-kit-menu.mjs +19 -26
- package/fesm2022/ngx-kit-menu.mjs.map +1 -1
- package/package.json +1 -1
- package/types/ngx-kit-core.d.ts +1 -7
- package/types/ngx-kit-data-table.d.ts +3 -2
- package/types/ngx-kit-dialog.d.ts +9 -42
- package/types/ngx-kit-menu.d.ts +9 -9
package/dialog/README.md
CHANGED
|
@@ -1,15 +1,33 @@
|
|
|
1
|
-
# ngx-kit/dialog
|
|
1
|
+
# `ngx-kit/dialog`
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A standalone, signal-based dialog library for Angular, built entirely on top of [`ngx-kit/overlay`](../overlay).
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
`ngx-kit/dialog` adds a flexible dialog API to the existing overlay infrastructure. It does not rely on the native HTML `<dialog>` element or introduce a separate overlay stack for each dialog. Instead, it reuses `OverlayService` for shared overlay behavior, including backdrop management, positioning, focus trapping, Escape and outside-click handling, scroll locking, and optional top-layer rendering through the Popover API.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
The dialog module focuses on dialog-specific functionality: configuration, typed data injection, dialog lifecycle management, and customizable headers, bodies, and footers.
|
|
8
|
+
|
|
9
|
+
**Key features**
|
|
10
|
+
|
|
11
|
+
- Built on `ngx-kit/overlay` with no separate overlay infrastructure.
|
|
12
|
+
- Standalone components and directives — no `NgModule` required.
|
|
13
|
+
- Signal-based architecture with zoneless change detection compatibility.
|
|
14
|
+
- Strongly typed dialog references, results, and injected data.
|
|
15
|
+
- Configurable dimensions, animations, accessibility attributes, and close behavior.
|
|
16
|
+
- Optional automatic headers and footers, or fully custom dialog layouts.
|
|
17
|
+
- Optional Popover API integration with automatic fallback when unsupported.
|
|
18
|
+
- Dark mode and RTL support.
|
|
19
|
+
- Programmatic control through `NgxDialogService` and `NgxDialogRef`.
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
8
22
|
|
|
9
23
|
```bash
|
|
10
24
|
npm install ngx-kit
|
|
11
25
|
```
|
|
12
26
|
|
|
27
|
+
## Setup
|
|
28
|
+
|
|
29
|
+
Register the dialog providers in your application configuration:
|
|
30
|
+
|
|
13
31
|
```ts
|
|
14
32
|
// app.config.ts
|
|
15
33
|
import { ApplicationConfig } from '@angular/core';
|
|
@@ -20,104 +38,225 @@ export const appConfig: ApplicationConfig = {
|
|
|
20
38
|
};
|
|
21
39
|
```
|
|
22
40
|
|
|
23
|
-
##
|
|
41
|
+
## Opening a Dialog
|
|
24
42
|
|
|
25
|
-
|
|
43
|
+
Inject `NgxDialogService` and call `open()` with a standalone component and an optional configuration object.
|
|
26
44
|
|
|
27
45
|
```ts
|
|
28
|
-
import {
|
|
46
|
+
import { Component, inject } from '@angular/core';
|
|
47
|
+
import { NgxDialogService } from 'ngx-kit/dialog';
|
|
29
48
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
49
|
+
@Component({
|
|
50
|
+
standalone: true,
|
|
51
|
+
// ...
|
|
52
|
+
})
|
|
53
|
+
export class UsersComponent {
|
|
54
|
+
private readonly dialog = inject(NgxDialogService);
|
|
35
55
|
|
|
36
|
-
|
|
56
|
+
editUser(): void {
|
|
57
|
+
this.dialog
|
|
58
|
+
.open(EditUserComponent, {
|
|
59
|
+
data: { userId: 42 },
|
|
60
|
+
size: 'md',
|
|
61
|
+
header: {
|
|
62
|
+
enable: true,
|
|
63
|
+
title: 'Edit user',
|
|
64
|
+
showCloseButton: true,
|
|
65
|
+
},
|
|
66
|
+
})
|
|
67
|
+
.afterClosed
|
|
68
|
+
.subscribe((result) => {
|
|
69
|
+
console.log('Dialog result:', result);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
37
73
|
```
|
|
38
74
|
|
|
39
|
-
|
|
75
|
+
No `ViewContainerRef` is required at the call site. The dialog service delegates overlay creation to `ngx-kit/overlay` and manages the dialog's lifecycle.
|
|
40
76
|
|
|
41
|
-
|
|
42
|
-
private dialog = inject(NgxDialogService);
|
|
43
|
-
this.dialog.open(EditUserComponent, { data: { userId: 42 } });
|
|
44
|
-
```
|
|
77
|
+
## Accessing Dialog Data and Results
|
|
45
78
|
|
|
46
|
-
|
|
79
|
+
Use `DIALOG_DATA` to access the data passed to `open()`, and `DIALOG_REF` to control the dialog from within its content component.
|
|
47
80
|
|
|
48
81
|
```ts
|
|
49
|
-
|
|
82
|
+
import { Component, inject } from '@angular/core';
|
|
83
|
+
import {
|
|
84
|
+
DIALOG_DATA,
|
|
85
|
+
DIALOG_REF,
|
|
86
|
+
NgxDialogRef,
|
|
87
|
+
} from 'ngx-kit/dialog';
|
|
88
|
+
|
|
89
|
+
interface EditUserData {
|
|
90
|
+
userId: number;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
interface EditUserResult {
|
|
94
|
+
saved: boolean;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
@Component({
|
|
98
|
+
standalone: true,
|
|
99
|
+
template: `
|
|
100
|
+
<p>Editing user {{ data.userId }}</p>
|
|
101
|
+
<button type="button" (click)="save()">Save</button>
|
|
102
|
+
`,
|
|
103
|
+
})
|
|
50
104
|
export class EditUserComponent {
|
|
51
|
-
private
|
|
52
|
-
|
|
105
|
+
private readonly dialogRef =
|
|
106
|
+
inject<NgxDialogRef<EditUserResult>>(DIALOG_REF);
|
|
53
107
|
|
|
54
|
-
|
|
55
|
-
|
|
108
|
+
readonly data = inject<EditUserData>(DIALOG_DATA);
|
|
109
|
+
|
|
110
|
+
save(): void {
|
|
111
|
+
this.dialogRef.close({ saved: true });
|
|
56
112
|
}
|
|
57
113
|
}
|
|
58
114
|
```
|
|
59
115
|
|
|
60
|
-
|
|
116
|
+
The result is emitted through `afterClosed` when the dialog has finished closing and has been removed from the DOM.
|
|
117
|
+
|
|
118
|
+
## Dialog Layout
|
|
119
|
+
|
|
120
|
+
Dialog content can define its own header, body, and footer using the provided structural directives or layout components. Choose one approach for a given section; do not combine a custom section with its corresponding automatic section.
|
|
121
|
+
|
|
122
|
+
### Structural directives
|
|
61
123
|
|
|
62
124
|
```html
|
|
63
|
-
<div *ngxDialogHeader
|
|
64
|
-
|
|
125
|
+
<div *ngxDialogHeader>
|
|
126
|
+
Edit user
|
|
127
|
+
</div>
|
|
128
|
+
|
|
129
|
+
<div *ngxDialogBody>
|
|
130
|
+
<!-- Main content -->
|
|
131
|
+
</div>
|
|
132
|
+
|
|
65
133
|
<div *ngxDialogFooter align="end">
|
|
66
|
-
<button (click)="save()"
|
|
134
|
+
<button type="button" (click)="save()">Save</button>
|
|
67
135
|
</div>
|
|
68
136
|
```
|
|
69
137
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
138
|
+
Import the directives into the standalone component that uses them.
|
|
139
|
+
|
|
140
|
+
### Layout components
|
|
141
|
+
|
|
142
|
+
Alternatively, use the dedicated layout components:
|
|
143
|
+
|
|
144
|
+
```html
|
|
145
|
+
<ngx-dialog-header>
|
|
146
|
+
Edit user
|
|
147
|
+
</ngx-dialog-header>
|
|
148
|
+
|
|
149
|
+
<ngx-dialog-body>
|
|
150
|
+
<!-- Main content -->
|
|
151
|
+
</ngx-dialog-body>
|
|
152
|
+
|
|
153
|
+
<ngx-dialog-footer align="end">
|
|
154
|
+
<button type="button" (click)="save()">Save</button>
|
|
155
|
+
</ngx-dialog-footer>
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
These layout options let you customize the dialog structure while keeping the overlay behavior managed by `ngx-kit/overlay`.
|
|
159
|
+
|
|
160
|
+
## Automatic Header and Footer
|
|
161
|
+
|
|
162
|
+
For common dialog layouts, enable the built-in header and footer through the dialog configuration. This avoids manually defining those sections.
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
this.dialog.open(EditUserComponent, {
|
|
166
|
+
header: {
|
|
167
|
+
enable: true,
|
|
168
|
+
title: 'Edit user',
|
|
169
|
+
showCloseButton: true,
|
|
170
|
+
},
|
|
171
|
+
footer: {
|
|
172
|
+
enable: true,
|
|
173
|
+
closeText: 'Close',
|
|
174
|
+
align: 'end',
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Do not enable an automatic section if the content component already provides that section using a custom directive or layout component.
|
|
180
|
+
|
|
181
|
+
## API Reference
|
|
182
|
+
|
|
183
|
+
### `NgxDialogService.open<R, DataType>(component, config?)`
|
|
184
|
+
|
|
185
|
+
Opens a dialog and returns an `NgxDialogRef<R>`.
|
|
186
|
+
|
|
187
|
+
| Option | Type | Default | Description |
|
|
188
|
+
|---|---|---|---|
|
|
189
|
+
| `data` | `DataType` | `{}` | Data injected into the content component through `DIALOG_DATA`. |
|
|
190
|
+
| `size` | `'sm' \| 'md' \| 'lg' \| 'xl' \| 'full'` | — | Preset dialog size. |
|
|
191
|
+
| `width` | `string` | — | Explicit width; takes precedence over the preset size. |
|
|
192
|
+
| `minWidth` | `string` | — | Minimum dialog width. |
|
|
193
|
+
| `maxWidth` | `string` | — | Maximum dialog width. |
|
|
194
|
+
| `height` | `string` | — | Explicit dialog height. |
|
|
195
|
+
| `minHeight` | `string` | — | Minimum dialog height. |
|
|
196
|
+
| `maxHeight` | `string` | — | Maximum dialog height. |
|
|
197
|
+
| `panelClass` | `string \| string[]` | `'ngx-kit'` | Additional CSS class or classes for the dialog panel. |
|
|
198
|
+
| `backdropClass` | `string \| string[]` | `'ngx-dialog-backdrop'` | Additional CSS class or classes for the backdrop. |
|
|
199
|
+
| `header.enable` | `boolean` | `false` | Enables the automatic header. |
|
|
200
|
+
| `header.title` | `string` | — | Title displayed in the automatic header. |
|
|
201
|
+
| `header.showCloseButton` | `boolean` | — | Shows a close button in the automatic header. |
|
|
202
|
+
| `footer.enable` | `boolean` | `false` | Enables the automatic footer. |
|
|
203
|
+
| `footer.closeText` | `string` | — | Text for the automatic footer's close button. |
|
|
204
|
+
| `footer.align` | — | — | Alignment of the automatic footer content. |
|
|
205
|
+
| `closeOnOutsideClick` | `boolean` | `false` | Closes the dialog when the backdrop is clicked. |
|
|
206
|
+
| `closeOnEscape` | `boolean` | `true` | Closes the dialog when Escape is pressed. |
|
|
207
|
+
| `disableClose` | `boolean` | `false` | Disables automatic closing through Escape and outside clicks. |
|
|
208
|
+
| `beforeClose` | `() => boolean` | — | Synchronous guard for Escape and outside-click closing. Return `false` to prevent closing. |
|
|
209
|
+
| `autoFocus` | `boolean` | `true` | Enables automatic focus when the dialog opens. |
|
|
210
|
+
| `restoreFocus` | `boolean` | `true` | Restores focus to the previously focused element when the dialog closes. |
|
|
211
|
+
| `usePopover` | `boolean` | `true` | Enables Popover API integration when supported. |
|
|
212
|
+
| `role` | `'dialog' \| 'alertdialog'` | `'dialog'` | Accessibility role for the dialog. |
|
|
213
|
+
| `ariaLabel` | `string` | — | Accessible label for the dialog. |
|
|
214
|
+
| `ariaLabelledby` | `string` | — | ID of the element that labels the dialog. |
|
|
215
|
+
| `ariaDescribedby` | `string` | — | ID of the element that describes the dialog. |
|
|
216
|
+
| `animation` | `'zoom' \| 'fade' \| 'slide-up' \| 'none'` | `'zoom'` | Dialog opening and closing animation. |
|
|
217
|
+
| `injector` | `Injector` | — | Optional parent injector for the dialog content component. |
|
|
218
|
+
|
|
219
|
+
**Closing behavior:** `disableClose: true` disables both Escape and outside-click closing. `beforeClose` applies to those automatic close triggers, not to explicit calls to `dialogRef.close()`.
|
|
97
220
|
|
|
98
221
|
### `NgxDialogRef<R>`
|
|
99
222
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
|
103
|
-
|
|
104
|
-
| `
|
|
105
|
-
| `
|
|
223
|
+
The reference returned by `open()` provides programmatic control over the dialog.
|
|
224
|
+
|
|
225
|
+
| Member | Description |
|
|
226
|
+
|---|---|
|
|
227
|
+
| `close(result?)` | Closes the dialog and optionally supplies a result. Repeated calls are safely ignored. |
|
|
228
|
+
| `afterClosed` | An `Observable<R \| undefined>` that emits once after the dialog has been removed from the DOM. |
|
|
229
|
+
| `closing` | Read-only signal indicating whether the dialog is closing. |
|
|
230
|
+
| `closed` | Read-only signal indicating whether the dialog has closed. |
|
|
231
|
+
| `beforeClose` | The close guard, which can also be updated after the dialog opens. |
|
|
232
|
+
|
|
233
|
+
### Additional service methods
|
|
234
|
+
|
|
235
|
+
| Member | Description |
|
|
236
|
+
|---|---|
|
|
237
|
+
| `open(component, config?)` | Opens a component inside a dialog. |
|
|
238
|
+
| `confirm(options)` | Opens a confirmation dialog and returns an `Observable<boolean>`. |
|
|
239
|
+
| `alert(options)` | Opens an alert dialog and returns an `Observable<void>`. |
|
|
240
|
+
| `closeAll()` | Closes all currently open dialogs. |
|
|
241
|
+
| `openDialogs` | Read-only signal containing the currently open dialogs. |
|
|
242
|
+
| `openCount` | Read-only signal containing the number of currently open dialogs. |
|
|
243
|
+
|
|
244
|
+
## Theming, Dark Mode, and RTL
|
|
106
245
|
|
|
107
|
-
|
|
246
|
+
`ngx-kit/dialog` integrates with the theming and directionality behavior provided by `ngx-kit/overlay`.
|
|
108
247
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
| `confirm(options)` | `Observable<boolean>` |
|
|
113
|
-
| `alert(options)` | `Observable<void>` |
|
|
114
|
-
| `closeAll()` | همهی دیالوگهای باز رو میبنده |
|
|
115
|
-
| `openDialogs` / `openCount` | سیگنالهای ریدانلی برای دیالوگهای باز فعلی |
|
|
248
|
+
- **Dark mode:** Uses the `light-dark()` CSS function where applicable.
|
|
249
|
+
- **RTL and LTR:** Respects the direction provided by the overlay's `DirectionService`.
|
|
250
|
+
- **Overlay behavior:** Backdrop handling, positioning, focus management, scroll locking, and optional Popover API integration remain the responsibility of `OverlayService`.
|
|
116
251
|
|
|
117
|
-
##
|
|
252
|
+
## Architecture
|
|
118
253
|
|
|
119
|
-
`
|
|
254
|
+
`ngx-kit/dialog` is a dialog-specific layer built on the shared overlay infrastructure.
|
|
120
255
|
|
|
121
|
-
|
|
256
|
+
- **`NgxDialogService`** — creates and manages dialogs, configuration, and injected data.
|
|
257
|
+
- **`NgxDialogRef`** — exposes the dialog lifecycle, close operation, and result stream.
|
|
258
|
+
- **`DIALOG_DATA` and `DIALOG_REF`** — provide access to dialog data and controls from the content component.
|
|
259
|
+
- **Dialog layout components and directives** — define the header, body, and footer.
|
|
260
|
+
- **`ngx-kit/overlay`** — owns shared overlay behavior and rendering.
|
|
122
261
|
|
|
123
|
-
|
|
262
|
+
This separation keeps dialog-specific functionality independent from the underlying positioning and overlay lifecycle implementation, while allowing both packages to evolve consistently.
|
|
@@ -1217,7 +1217,6 @@ class OverlayService {
|
|
|
1217
1217
|
overlayStack = [];
|
|
1218
1218
|
globalListenersAttached = false;
|
|
1219
1219
|
layoutRaf = null;
|
|
1220
|
-
bodyScrollLockCount = 0;
|
|
1221
1220
|
previousBodyOverflow = null;
|
|
1222
1221
|
previousBodyPaddingRight = null;
|
|
1223
1222
|
get isBrowser() {
|
|
@@ -1269,9 +1268,9 @@ class OverlayService {
|
|
|
1269
1268
|
// ---------------------------------------------------------------------------
|
|
1270
1269
|
createComponentInstance(options, component, viewContainerRef) {
|
|
1271
1270
|
const host = this.createHostElement(options);
|
|
1272
|
-
const backdrop = this.createBackdropElement(options);
|
|
1273
1271
|
const element = this.createOverlayElement(options);
|
|
1274
|
-
host.
|
|
1272
|
+
// The full-viewport host also acts as the backdrop. Keeping the panel as
|
|
1273
|
+
// a separate element preserves its intrinsic size and placement geometry.
|
|
1275
1274
|
host.appendChild(element);
|
|
1276
1275
|
let componentRef;
|
|
1277
1276
|
let attachedToAppRef = false;
|
|
@@ -1288,7 +1287,7 @@ class OverlayService {
|
|
|
1288
1287
|
const elementInjector = options.injector ?? Injector.NULL;
|
|
1289
1288
|
componentRef = createComponent(component, {
|
|
1290
1289
|
environmentInjector: options.environmentInjector ?? this.environmentInjector,
|
|
1291
|
-
elementInjector
|
|
1290
|
+
elementInjector,
|
|
1292
1291
|
});
|
|
1293
1292
|
this.applicationRef.attachView(componentRef.hostView);
|
|
1294
1293
|
attachedToAppRef = true;
|
|
@@ -1296,7 +1295,7 @@ class OverlayService {
|
|
|
1296
1295
|
element.appendChild(componentRef.location.nativeElement);
|
|
1297
1296
|
const instance = {
|
|
1298
1297
|
host,
|
|
1299
|
-
backdrop,
|
|
1298
|
+
backdrop: host,
|
|
1300
1299
|
element,
|
|
1301
1300
|
anchor: options.anchor,
|
|
1302
1301
|
point: options.point,
|
|
@@ -1307,29 +1306,29 @@ class OverlayService {
|
|
|
1307
1306
|
canClose: options.canClose,
|
|
1308
1307
|
autoFocus: options.autoFocus ?? true,
|
|
1309
1308
|
restoreFocus: options.restoreFocus ?? true,
|
|
1310
|
-
lockBodyScroll: options.lockBodyScroll ?? false,
|
|
1311
1309
|
componentRef,
|
|
1312
1310
|
attachedToAppRef,
|
|
1313
1311
|
previousActiveElement: this.getActiveElement(),
|
|
1314
1312
|
rafId: null,
|
|
1315
1313
|
focusTimeoutId: null,
|
|
1316
1314
|
cleanup: () => { },
|
|
1315
|
+
resizeObserver: null,
|
|
1317
1316
|
};
|
|
1318
1317
|
instance.cleanup = () => this.destroyInstance(instance);
|
|
1318
|
+
instance.resizeObserver = this.observeOverlayResize(instance);
|
|
1319
1319
|
return instance;
|
|
1320
1320
|
}
|
|
1321
1321
|
createTemplateInstance(options, template, appRef) {
|
|
1322
1322
|
const host = this.createHostElement(options);
|
|
1323
|
-
const backdrop = this.createBackdropElement(options);
|
|
1324
1323
|
const element = this.createOverlayElement(options);
|
|
1325
|
-
host.
|
|
1324
|
+
// As in component overlays, the host itself receives backdrop clicks.
|
|
1326
1325
|
host.appendChild(element);
|
|
1327
1326
|
const view = template.createEmbeddedView({});
|
|
1328
1327
|
appRef.attachView(view);
|
|
1329
1328
|
element.append(...view.rootNodes);
|
|
1330
1329
|
const instance = {
|
|
1331
1330
|
host,
|
|
1332
|
-
backdrop,
|
|
1331
|
+
backdrop: host,
|
|
1333
1332
|
element,
|
|
1334
1333
|
anchor: options.anchor,
|
|
1335
1334
|
point: options.point,
|
|
@@ -1340,15 +1339,16 @@ class OverlayService {
|
|
|
1340
1339
|
canClose: options.canClose,
|
|
1341
1340
|
autoFocus: options.autoFocus ?? true,
|
|
1342
1341
|
restoreFocus: options.restoreFocus ?? true,
|
|
1343
|
-
lockBodyScroll: options.lockBodyScroll ?? false,
|
|
1344
1342
|
embeddedView: view,
|
|
1345
1343
|
appRef,
|
|
1346
1344
|
previousActiveElement: this.getActiveElement(),
|
|
1347
1345
|
rafId: null,
|
|
1348
1346
|
focusTimeoutId: null,
|
|
1349
1347
|
cleanup: () => { },
|
|
1348
|
+
resizeObserver: null,
|
|
1350
1349
|
};
|
|
1351
1350
|
instance.cleanup = () => this.destroyInstance(instance);
|
|
1351
|
+
instance.resizeObserver = this.observeOverlayResize(instance);
|
|
1352
1352
|
return instance;
|
|
1353
1353
|
}
|
|
1354
1354
|
pushInstance(instance) {
|
|
@@ -1356,9 +1356,6 @@ class OverlayService {
|
|
|
1356
1356
|
// Only matters as a fallback for browsers without the Popover API -
|
|
1357
1357
|
// real popovers stack by top-layer show order regardless of z-index.
|
|
1358
1358
|
instance.host.style.zIndex = String(BASE_Z_INDEX + this.overlayStack.length);
|
|
1359
|
-
if (instance.lockBodyScroll) {
|
|
1360
|
-
this.lockBodyScroll();
|
|
1361
|
-
}
|
|
1362
1359
|
}
|
|
1363
1360
|
// ---------------------------------------------------------------------------
|
|
1364
1361
|
// DOM
|
|
@@ -1366,7 +1363,10 @@ class OverlayService {
|
|
|
1366
1363
|
createHostElement(options) {
|
|
1367
1364
|
const usePopover = this.resolveUsePopover(options);
|
|
1368
1365
|
const host = this.document.createElement('div');
|
|
1369
|
-
host
|
|
1366
|
+
// One full-viewport element now serves as both the top-layer host and
|
|
1367
|
+
// backdrop. The separately measured panel below it is intentionally kept.
|
|
1368
|
+
host.className = `${OVERLAY_HOST_CLASSNAME} ngx-ui-overlay-backdrop`;
|
|
1369
|
+
this.addClasses(host, options.backdropClass);
|
|
1370
1370
|
host.setAttribute('data-ngx-overlay', '');
|
|
1371
1371
|
host.style.position = 'fixed';
|
|
1372
1372
|
host.style.inset = '0';
|
|
@@ -1386,8 +1386,9 @@ class OverlayService {
|
|
|
1386
1386
|
host.style.color = 'inherit';
|
|
1387
1387
|
host.style.zIndex = String(BASE_Z_INDEX);
|
|
1388
1388
|
host.style.pointerEvents = 'auto';
|
|
1389
|
-
// Popover goes on the
|
|
1390
|
-
//
|
|
1389
|
+
// Popover goes on the full-viewport host/backdrop, not the inner panel.
|
|
1390
|
+
// This puts the backdrop and separately positioned panel in the top layer
|
|
1391
|
+
// as a single unit,
|
|
1391
1392
|
// so stacking "just works" above everything on the page - including
|
|
1392
1393
|
// ancestors with `overflow: hidden`, `transform`, or their own z-index
|
|
1393
1394
|
// stacking contexts, which regular `position: fixed` cannot escape.
|
|
@@ -1398,16 +1399,6 @@ class OverlayService {
|
|
|
1398
1399
|
}
|
|
1399
1400
|
return host;
|
|
1400
1401
|
}
|
|
1401
|
-
createBackdropElement(options) {
|
|
1402
|
-
const backdrop = this.document.createElement('div');
|
|
1403
|
-
backdrop.className = 'ngx-ui-overlay-backdrop';
|
|
1404
|
-
backdrop.style.position = 'absolute';
|
|
1405
|
-
backdrop.style.inset = '0';
|
|
1406
|
-
backdrop.style.pointerEvents = 'auto';
|
|
1407
|
-
backdrop.style.background = 'transparent';
|
|
1408
|
-
this.addClasses(backdrop, options.backdropClass);
|
|
1409
|
-
return backdrop;
|
|
1410
|
-
}
|
|
1411
1402
|
createOverlayElement(options) {
|
|
1412
1403
|
const element = this.document.createElement('div');
|
|
1413
1404
|
element.className = DIALOG_OVERLAY_CLASSNAME;
|
|
@@ -1496,9 +1487,6 @@ class OverlayService {
|
|
|
1496
1487
|
}
|
|
1497
1488
|
this.hideHostPopover(instance);
|
|
1498
1489
|
instance.host.remove();
|
|
1499
|
-
if (instance.lockBodyScroll) {
|
|
1500
|
-
this.unlockBodyScroll();
|
|
1501
|
-
}
|
|
1502
1490
|
instance.onClosed?.();
|
|
1503
1491
|
// Only tear down the shared/global listeners once *every* overlay is
|
|
1504
1492
|
// closed - previously this ran unconditionally on every close, which
|
|
@@ -1510,6 +1498,18 @@ class OverlayService {
|
|
|
1510
1498
|
if (instance.restoreFocus) {
|
|
1511
1499
|
this.restoreFocus(instance.previousActiveElement);
|
|
1512
1500
|
}
|
|
1501
|
+
instance.resizeObserver?.disconnect();
|
|
1502
|
+
instance.resizeObserver = null;
|
|
1503
|
+
}
|
|
1504
|
+
observeOverlayResize(instance) {
|
|
1505
|
+
if (typeof ResizeObserver === 'undefined') {
|
|
1506
|
+
return null;
|
|
1507
|
+
}
|
|
1508
|
+
const observer = new ResizeObserver(() => {
|
|
1509
|
+
this.scheduleReposition();
|
|
1510
|
+
});
|
|
1511
|
+
observer.observe(instance.element);
|
|
1512
|
+
return observer;
|
|
1513
1513
|
}
|
|
1514
1514
|
// ---------------------------------------------------------------------------
|
|
1515
1515
|
// popover helpers
|
|
@@ -1548,41 +1548,6 @@ class OverlayService {
|
|
|
1548
1548
|
typeof HTMLElement.prototype.hidePopover === 'function');
|
|
1549
1549
|
}
|
|
1550
1550
|
// ---------------------------------------------------------------------------
|
|
1551
|
-
// body scroll lock
|
|
1552
|
-
// ---------------------------------------------------------------------------
|
|
1553
|
-
lockBodyScroll() {
|
|
1554
|
-
if (!this.isBrowser) {
|
|
1555
|
-
return;
|
|
1556
|
-
}
|
|
1557
|
-
this.bodyScrollLockCount++;
|
|
1558
|
-
if (this.bodyScrollLockCount > 1) {
|
|
1559
|
-
return;
|
|
1560
|
-
}
|
|
1561
|
-
const body = this.document.body;
|
|
1562
|
-
this.previousBodyOverflow = body.style.overflow;
|
|
1563
|
-
this.previousBodyPaddingRight = body.style.paddingRight;
|
|
1564
|
-
const scrollbarWidth = window.innerWidth - this.document.documentElement.clientWidth;
|
|
1565
|
-
body.style.overflow = 'hidden';
|
|
1566
|
-
if (scrollbarWidth > 0) {
|
|
1567
|
-
const currentPaddingRight = parseFloat(window.getComputedStyle(body).paddingRight) || 0;
|
|
1568
|
-
body.style.paddingRight = `${currentPaddingRight + scrollbarWidth}px`;
|
|
1569
|
-
}
|
|
1570
|
-
}
|
|
1571
|
-
unlockBodyScroll() {
|
|
1572
|
-
if (this.bodyScrollLockCount === 0) {
|
|
1573
|
-
return;
|
|
1574
|
-
}
|
|
1575
|
-
this.bodyScrollLockCount--;
|
|
1576
|
-
if (this.bodyScrollLockCount > 0) {
|
|
1577
|
-
return;
|
|
1578
|
-
}
|
|
1579
|
-
const body = this.document.body;
|
|
1580
|
-
body.style.overflow = this.previousBodyOverflow ?? '';
|
|
1581
|
-
body.style.paddingRight = this.previousBodyPaddingRight ?? '';
|
|
1582
|
-
this.previousBodyOverflow = null;
|
|
1583
|
-
this.previousBodyPaddingRight = null;
|
|
1584
|
-
}
|
|
1585
|
-
// ---------------------------------------------------------------------------
|
|
1586
1551
|
// global listeners
|
|
1587
1552
|
// ---------------------------------------------------------------------------
|
|
1588
1553
|
attachGlobalListeners() {
|
|
@@ -1597,7 +1562,6 @@ class OverlayService {
|
|
|
1597
1562
|
this.document.addEventListener('keydown', this.onDocumentKeydown);
|
|
1598
1563
|
this.document.addEventListener('click', this.onDocumentClick);
|
|
1599
1564
|
this.document.addEventListener('contextmenu', this.onDocumentContextMenu);
|
|
1600
|
-
this.document.addEventListener('scroll', this.onDocumentScroll, true);
|
|
1601
1565
|
window.addEventListener('resize', this.onWindowResize);
|
|
1602
1566
|
}, 0);
|
|
1603
1567
|
}
|
|
@@ -1609,7 +1573,6 @@ class OverlayService {
|
|
|
1609
1573
|
this.document.removeEventListener('keydown', this.onDocumentKeydown);
|
|
1610
1574
|
this.document.removeEventListener('click', this.onDocumentClick);
|
|
1611
1575
|
this.document.removeEventListener('contextmenu', this.onDocumentContextMenu);
|
|
1612
|
-
this.document.removeEventListener('scroll', this.onDocumentScroll, true);
|
|
1613
1576
|
window.removeEventListener('resize', this.onWindowResize);
|
|
1614
1577
|
}
|
|
1615
1578
|
onDocumentKeydown = (event) => {
|
|
@@ -1677,9 +1640,6 @@ class OverlayService {
|
|
|
1677
1640
|
}
|
|
1678
1641
|
instance.cleanup();
|
|
1679
1642
|
};
|
|
1680
|
-
onDocumentScroll = () => {
|
|
1681
|
-
this.scheduleReposition();
|
|
1682
|
-
};
|
|
1683
1643
|
onWindowResize = () => {
|
|
1684
1644
|
this.scheduleReposition();
|
|
1685
1645
|
};
|
|
@@ -1802,10 +1762,6 @@ class OverlayService {
|
|
|
1802
1762
|
overlay.style.right = 'auto';
|
|
1803
1763
|
overlay.style.bottom = 'auto';
|
|
1804
1764
|
overlay.style.visibility = 'visible';
|
|
1805
|
-
// NOTE: showPopover() is intentionally NOT called here. It already
|
|
1806
|
-
// happened once in renderHost(); calling it again on every reposition
|
|
1807
|
-
// (scroll/resize) throws InvalidStateError since the popover is
|
|
1808
|
-
// already open.
|
|
1809
1765
|
}
|
|
1810
1766
|
getAnchorRect(instance) {
|
|
1811
1767
|
if (instance.point) {
|