ngx-kit 8.4.1 → 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 CHANGED
@@ -1,15 +1,33 @@
1
- # ngx-kit/dialog
1
+ # `ngx-kit/dialog`
2
2
 
3
- مودال/دیالوگ که کاملاً روی `ngx-kit/overlay` سوار شده - نه `<dialog>` بومی (پشتیبانی مرورگرها)، نه یک استک DOM جدا برای هر دیالوگ. تمام رفتار مشترک (بک‌دراپ، وسط‌چین کردن، فوکوس‌تِرپ، بستن با Escape/کلیک بیرون، قفل اسکرول صفحه، رندر در top layer با Popover API) از `OverlayService` می‌آید؛ این ماژول فقط لایه‌ی «دیالوگ» رو روش اضافه می‌کنه: کانفیگ، تزریق داده، هدر/بادی/فوتر، و یک دیالوگ تایید/اعلان آماده.
3
+ A standalone, signal-based dialog library for Angular, built entirely on top of [`ngx-kit/overlay`](../overlay).
4
4
 
5
- هر بیلدینگ‌بلاک (پنل، دایرکتیوهای هدر/فوتر/بادی، دیالوگ تایید) کاملاً **standalone** و **signal-based** است - بدون NgModule، بدون `ChangeDetectorRef.detectChanges()` دستی، سازگار با zoneless.
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
- با API استاتیک (بدون نیاز به inject دستی):
43
+ Inject `NgxDialogService` and call `open()` with a standalone component and an optional configuration object.
26
44
 
27
45
  ```ts
28
- import { Dialog } from 'ngx-kit/dialog';
46
+ import { Component, inject } from '@angular/core';
47
+ import { NgxDialogService } from 'ngx-kit/dialog';
29
48
 
30
- const ref = Dialog.open<{ saved: boolean }>(EditUserComponent, {
31
- data: { userId: 42 },
32
- size: 'md',
33
- header: { enable: true, title: 'ویرایش کاربر' },
34
- });
49
+ @Component({
50
+ standalone: true,
51
+ // ...
52
+ })
53
+ export class UsersComponent {
54
+ private readonly dialog = inject(NgxDialogService);
35
55
 
36
- ref.afterClosed.subscribe((result) => console.log('نتیجه:', result));
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
- یا با تزریق مستقیم سرویس (توصیه‌شده برای SSR - پایین‌تر ببینید):
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
- ```ts
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
- @Component({ standalone: true, ... })
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 ref = inject<NgxDialogRef<{ saved: boolean }>>(DIALOG_REF);
52
- data = inject<{ userId: number }>(DIALOG_DATA);
105
+ private readonly dialogRef =
106
+ inject<NgxDialogRef<EditUserResult>>(DIALOG_REF);
53
107
 
54
- save() {
55
- this.ref.close({ saved: true });
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
- ### دایرکتیوهای layout داخل کامپوننت محتوا
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>عنوان سفارشی</div>
64
- <div *ngxDialogBody>محتوای اصلی، اسکرول‌پذیر</div>
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()">ذخیره</button>
134
+ <button type="button" (click)="save()">Save</button>
67
135
  </div>
68
136
  ```
69
137
 
70
- یا به‌جای نوشتن این‌ها دستی، از هدر/فوتر خودکار استفاده کنید (بخش API رو ببینید) - اما هر دو رو با هم استفاده نکنید.
71
-
72
- ## API
73
-
74
- ### `NgxDialogService.open<R, DataType>(component, config?)` / `Dialog.open(...)`
75
-
76
- | فیلد `config` | نوع | پیش‌فرض | توضیح |
77
- | -------------------------------------------------- | ------------------------------------------ | ------------------------------------- | --------------------------------------------------------------------------------- |
78
- | `data` | `DataType` | `{}` | از طریق `DIALOG_DATA` به کامپوننت محتوا می‌رسه |
79
- | `size` | `'sm' \| 'md' \| 'lg' \| 'xl' \| 'full'` | - | یک پریست عرض/ماکس-عرض |
80
- | `width` / `minWidth` / `maxWidth` | `string` | - | اندازه‌ی دستی (روی پریست `size` اولویت داره) |
81
- | `height` / `minHeight` / `maxHeight` | `string` | - | |
82
- | `panelClass` / `backdropClass` | `string \| string[]` | `'ngx-kit'` / `'ngx-dialog-backdrop'` | کلاس اضافه روی پنل/بک‌دراپ |
83
- | `header.enable` / `.title` / `.showCloseButton` | - | `false` | هدر خودکار (اگه خودتون `*ngxDialogHeader` بذارید، این رو فعال نکنید) |
84
- | `footer.enable` / `.closeText` / `.align` | - | `false` | فوتر خودکار با یک دکمه‌ی بستن |
85
- | `closeOnOutsideClick` | `boolean` | `false` | کلیک بیرون از پنل ببندتش |
86
- | `closeOnEscape` | `boolean` | `true` | Escape ببندتش |
87
- | `disableClose` | `boolean` | `false` | معادل ست کردن هر دوی بالا روی `false` - فقط با کد قابل بسته‌شدنه |
88
- | `beforeClose` | `() => boolean` | - | گارد سینک قبل از بسته‌شدن با Escape/کلیک بیرون (نه برای `dialogRef.close()` دستی) |
89
- | `lockBodyScroll` | `boolean` | `true` | جلوگیری از اسکرول صفحه‌ی پشت دیالوگ |
90
- | `autoFocus` / `restoreFocus` | `boolean` | `true` | |
91
- | `usePopover` | `boolean` | `true` | رندر با Popover API (fallback خودکار در مرورگرهای قدیمی) |
92
- | `role` | `'dialog' \| 'alertdialog'` | `'dialog'` | |
93
- | `ariaLabel` / `ariaLabelledby` / `ariaDescribedby` | `string` | - | |
94
- | `animation` | `'zoom' \| 'fade' \| 'slide-up' \| 'none'` | `'zoom'` | |
95
- | `injector` | `Injector` | - | Injector والد برای کامپوننت محتوا |
96
- `NgxDialogService` محیط تزریق خودش را به Overlay منتقل می‌کند؛ بنابراین providerهای ثبت‌شده در مسیر lazy یا محیطی که سرویس از آن resolve شده، برای محتوای دیالوگ نیز در دسترس هستند و نیازی به پاس‌دادن `ViewContainerRef` از سمت فراخواننده نیست.
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
- | `close(result?)` | دیالوگ رو می‌بنده؛ ایمن در برابر فراخوانی تکراری |
103
- | `afterClosed` | `Observable<R \| undefined>` - دقیقاً یک‌بار، بعد از حذف کامل از DOM |
104
- | `closing` / `closed` | سیگنال‌های `boolean` برای وضعیت لحظه‌ای |
105
- | `beforeClose` | همون گارد بالا؛ می‌تونید بعد از باز شدن هم عوضش کنید |
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
- ### `NgxDialogService`
246
+ `ngx-kit/dialog` integrates with the theming and directionality behavior provided by `ngx-kit/overlay`.
108
247
 
109
- | عضو | توضیح |
110
- | --------------------------- | ------------------------------------------------ |
111
- | `open(component, config?)` | دیالوگ رو باز می‌کنه، `NgxDialogRef` برمی‌گردونه |
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
- ## نکته‌ی SSR
252
+ ## Architecture
118
253
 
119
- `Dialog.open(...)` یک نمونه‌ی استاتیک/global نگه می‌داره که برای یک اپ مرورکری معمولی کاملاً امنه. اگه از Angular SSR استفاده می‌کنید، این نمونه‌ی استاتیک بین درخواست‌های هم‌زمان سرور مشترکه - به‌جای API استاتیک، مستقیماً `NgxDialogService` رو inject کنید (که per-injector/per-request امنه). `provideNgxDialog()` عمداً روی سرور این نمونه‌ی استاتیک رو ست نمی‌کنه تا این باگ به‌جای رخ‌دادن خاموش، فوراً با خطای «service not initialized» مشخص بشه.
254
+ `ngx-kit/dialog` is a dialog-specific layer built on the shared overlay infrastructure.
120
255
 
121
- ## دارک‌مود و RTL
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
- خودکار پشتیبانی می‌شه (`light-dark()` + جهت از طریق `DirectionService` که `ngx-kit/overlay` براش استفاده می‌کنه).
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.appendChild(backdrop);
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: options.injector,
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.appendChild(backdrop);
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.className = OVERLAY_HOST_CLASSNAME;
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 ROOT element, not the inner panel. This puts the
1390
- // whole overlay (backdrop + panel) in the top layer as a single unit,
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) {