ng-hub-ui-modal 1.2.4 → 21.0.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/README.md +580 -251
- package/fesm2022/ng-hub-ui-modal.mjs +402 -209
- package/fesm2022/ng-hub-ui-modal.mjs.map +1 -1
- package/package.json +3 -5
- package/types/ng-hub-ui-modal.d.ts +527 -0
- package/esm2022/lib/modal-backdrop.mjs +0 -51
- package/esm2022/lib/modal-config.mjs +0 -32
- package/esm2022/lib/modal-dismiss-reasons.mjs +0 -6
- package/esm2022/lib/modal-ref.mjs +0 -216
- package/esm2022/lib/modal-stack.mjs +0 -321
- package/esm2022/lib/modal-window.mjs +0 -275
- package/esm2022/lib/modal.mjs +0 -59
- package/esm2022/lib/modal.module.mjs +0 -17
- package/esm2022/ng-hub-ui-modal.mjs +0 -5
- package/esm2022/public-api.mjs +0 -5
- package/index.d.ts +0 -5
- package/lib/modal-backdrop.d.ts +0 -13
- package/lib/modal-config.d.ts +0 -175
- package/lib/modal-dismiss-reasons.d.ts +0 -4
- package/lib/modal-ref.d.ts +0 -115
- package/lib/modal-stack.d.ts +0 -62
- package/lib/modal-window.d.ts +0 -39
- package/lib/modal.d.ts +0 -42
- package/lib/modal.module.d.ts +0 -10
- package/ng-hub-ui-modal-1.2.4.tgz +0 -0
- package/public-api.d.ts +0 -1
package/README.md
CHANGED
|
@@ -1,343 +1,672 @@
|
|
|
1
1
|
# ng-hub-ui-modal
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/ng-hub-ui-modal)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://angular.io)
|
|
6
|
+
|
|
7
|
+
> A standalone, fully-featured Angular modal library with flexible content projection, placement support, and full CSS variable theming. No Bootstrap or ng-bootstrap dependency required.
|
|
8
|
+
|
|
9
|
+
> **⚠️ WARNING: BREAKING CHANGES IN VERSION 21.0.0**
|
|
10
|
+
> If you are upgrading from `1.x.x` to `21.x.x` and you have overridden the `.modal` or `.modal-dialog` CSS classes in your global stylesheets, please review the [BREAKING_CHANGES.md](./BREAKING_CHANGES.md) document to migrate your styles to the new `hub-modal` BEM classes.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 🧩 Library Family `ng-hub-ui`
|
|
15
|
+
|
|
16
|
+
This library is part of the **ng-hub-ui** ecosystem:
|
|
17
|
+
|
|
18
|
+
- [**ng-hub-ui-accordion**](https://www.npmjs.com/package/ng-hub-ui-accordion)
|
|
19
|
+
- [**ng-hub-ui-avatar**](https://www.npmjs.com/package/ng-hub-ui-avatar)
|
|
20
|
+
- [**ng-hub-ui-board**](https://www.npmjs.com/package/ng-hub-ui-board)
|
|
21
|
+
- [**ng-hub-ui-breadcrumbs**](https://www.npmjs.com/package/ng-hub-ui-breadcrumbs)
|
|
22
|
+
- [**ng-hub-ui-calendar**](https://www.npmjs.com/package/ng-hub-ui-calendar)
|
|
23
|
+
- [**➡️ ng-hub-ui-modal**](https://www.npmjs.com/package/ng-hub-ui-modal) ← _you are here_
|
|
24
|
+
- [**ng-hub-ui-paginable**](https://www.npmjs.com/package/ng-hub-ui-paginable)
|
|
25
|
+
- [**ng-hub-ui-portal**](https://www.npmjs.com/package/ng-hub-ui-portal)
|
|
26
|
+
- [**ng-hub-ui-stepper**](https://www.npmjs.com/package/ng-hub-ui-stepper)
|
|
27
|
+
- [**ng-hub-ui-utils**](https://www.npmjs.com/package/ng-hub-ui-utils)
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## 📋 Table of Contents
|
|
32
|
+
|
|
33
|
+
- [Features](#features)
|
|
34
|
+
- [Installation](#installation)
|
|
35
|
+
- [Quick Start](#quick-start)
|
|
36
|
+
- [Examples](#examples)
|
|
37
|
+
- [Open with TemplateRef](#open-with-templateref)
|
|
38
|
+
- [Open with Component](#open-with-component)
|
|
39
|
+
- [Open with String](#open-with-string)
|
|
40
|
+
- [Placement](#placement)
|
|
41
|
+
- [Size and Fullscreen](#size-and-fullscreen)
|
|
42
|
+
- [Scrollable Content](#scrollable-content)
|
|
43
|
+
- [Static Backdrop](#static-backdrop)
|
|
44
|
+
- [Before Dismiss Guard](#before-dismiss-guard)
|
|
45
|
+
- [HubActiveModal in Content Component](#hubactivemodal-in-content-component)
|
|
46
|
+
- [Dismiss and Close Selectors](#dismiss-and-close-selectors)
|
|
47
|
+
- [Multiple Stacked Modals](#multiple-stacked-modals)
|
|
48
|
+
- [Observables: dismissAll and hasOpenModals](#observables-dismissall-and-hasopenmodals)
|
|
49
|
+
- [API Reference](#api-reference)
|
|
50
|
+
- [HubModal Service](#hubmodal-service)
|
|
51
|
+
- [HubModalRef](#hubmodalref)
|
|
52
|
+
- [HubActiveModal](#hubactivemodal-1)
|
|
53
|
+
- [HubModalOptions](#hubmodaloptions)
|
|
54
|
+
- [HubModalUpdatableOptions](#hubmodalupdatableoptions)
|
|
55
|
+
- [HubModalPlacement](#hubmodalplacement-1)
|
|
56
|
+
- [ModalDismissReasons](#modaldismissreasons)
|
|
57
|
+
- [HubModalConfig](#hubmodalconfig)
|
|
58
|
+
- [Styling](#styling)
|
|
59
|
+
- [Contributing](#contributing)
|
|
60
|
+
- [Support & License](#support--license)
|
|
61
|
+
|
|
62
|
+
---
|
|
8
63
|
|
|
9
64
|
## Features
|
|
10
65
|
|
|
11
|
-
- **
|
|
12
|
-
- **
|
|
13
|
-
- **Flexible
|
|
14
|
-
- **
|
|
15
|
-
- **
|
|
16
|
-
- **
|
|
66
|
+
- **Zero external dependencies**: No ng-bootstrap, no Bootstrap JS.
|
|
67
|
+
- **Three content types**: Open modals with a `TemplateRef`, a `Component` class, or a plain `string`.
|
|
68
|
+
- **Flexible content projection**: Use CSS selectors to route content to `header`, `body`, and `footer` slots.
|
|
69
|
+
- **Placement support**: Anchor modals to any viewport edge — `start`, `end`, `top`, `bottom` — or keep them `center`.
|
|
70
|
+
- **Modal stacking**: Open multiple modals; focus management and aria-hidden are handled automatically.
|
|
71
|
+
- **Programmatic dismiss/close guards**: The `beforeDismiss` callback lets you intercept and prevent dismissal.
|
|
72
|
+
- **Full keyboard & backdrop interaction**: ESC key, static backdrop, backdrop click — all configurable.
|
|
73
|
+
- **CSS Variable theming**: Deep customization without overriding internal classes.
|
|
74
|
+
- **BEM class architecture**: All structural classes use the `hub-modal__*` prefix to avoid conflicts.
|
|
75
|
+
- **Lifecycle Observables**: `shown`, `hidden`, `closed`, `dismissed` streams for precise reactive flow.
|
|
76
|
+
- **Global defaults**: Inject `HubModalConfig` to set application-wide defaults.
|
|
77
|
+
|
|
78
|
+
---
|
|
17
79
|
|
|
18
80
|
## Installation
|
|
19
81
|
|
|
20
|
-
```
|
|
82
|
+
```bash
|
|
21
83
|
npm install ng-hub-ui-modal
|
|
22
84
|
```
|
|
23
85
|
|
|
24
|
-
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Quick Start
|
|
89
|
+
|
|
90
|
+
### Standalone (recommended)
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
import { Component, inject, TemplateRef } from '@angular/core';
|
|
94
|
+
import { HubModal } from 'ng-hub-ui-modal';
|
|
95
|
+
|
|
96
|
+
@Component({
|
|
97
|
+
selector: 'app-root',
|
|
98
|
+
standalone: true,
|
|
99
|
+
template: `
|
|
100
|
+
<button (click)="open(tpl)">Open Modal</button>
|
|
101
|
+
|
|
102
|
+
<ng-template #tpl let-close="close">
|
|
103
|
+
<div class="hub-modal__header"><h5>Hello!</h5></div>
|
|
104
|
+
<div class="hub-modal__body">Modal content goes here.</div>
|
|
105
|
+
<div class="hub-modal__footer">
|
|
106
|
+
<button (click)="close('done')">Close</button>
|
|
107
|
+
</div>
|
|
108
|
+
</ng-template>
|
|
109
|
+
`
|
|
110
|
+
})
|
|
111
|
+
export class AppComponent {
|
|
112
|
+
private modal = inject(HubModal);
|
|
113
|
+
|
|
114
|
+
open(tpl: TemplateRef<unknown>) {
|
|
115
|
+
this.modal
|
|
116
|
+
.open(tpl, { headerSelector: '.hub-modal__header', footerSelector: '.hub-modal__footer' })
|
|
117
|
+
.result.catch(() => {});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
```
|
|
25
121
|
|
|
26
|
-
|
|
122
|
+
### NgModule (classic)
|
|
27
123
|
|
|
28
124
|
```typescript
|
|
29
|
-
import {
|
|
125
|
+
import { HubModalModule } from 'ng-hub-ui-modal';
|
|
30
126
|
|
|
31
127
|
@NgModule({
|
|
32
|
-
|
|
33
|
-
// ...
|
|
34
|
-
ModalModule
|
|
35
|
-
]
|
|
128
|
+
imports: [HubModalModule]
|
|
36
129
|
})
|
|
37
130
|
export class AppModule {}
|
|
38
131
|
```
|
|
39
132
|
|
|
40
|
-
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Examples
|
|
136
|
+
|
|
137
|
+
### Open with TemplateRef
|
|
138
|
+
|
|
139
|
+
Open a modal whose content is defined inline as a template.
|
|
140
|
+
The template context exposes `close` and `dismiss` functions.
|
|
41
141
|
|
|
42
142
|
```typescript
|
|
43
|
-
import {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
143
|
+
import { Component, inject, TemplateRef } from '@angular/core';
|
|
144
|
+
import { HubModal } from 'ng-hub-ui-modal';
|
|
145
|
+
|
|
146
|
+
@Component({
|
|
147
|
+
selector: 'app-example',
|
|
148
|
+
standalone: true,
|
|
149
|
+
template: `
|
|
150
|
+
<button (click)="open(tpl)">Open Template Modal</button>
|
|
151
|
+
|
|
152
|
+
<ng-template #tpl let-close="close" let-dismiss="dismiss">
|
|
153
|
+
<div class="hub-modal__body">
|
|
154
|
+
<p>This is a template modal.</p>
|
|
155
|
+
<button (click)="dismiss('cancel')">Cancel</button>
|
|
156
|
+
<button (click)="close('ok')">OK</button>
|
|
157
|
+
</div>
|
|
158
|
+
</ng-template>
|
|
159
|
+
`
|
|
160
|
+
})
|
|
161
|
+
export class TemplateModalComponent {
|
|
162
|
+
private modal = inject(HubModal);
|
|
163
|
+
|
|
164
|
+
open(tpl: TemplateRef<unknown>) {
|
|
165
|
+
this.modal
|
|
166
|
+
.open(tpl)
|
|
167
|
+
.result.then((result) => console.log('Closed with', result))
|
|
168
|
+
.catch((reason) => console.log('Dismissed:', reason));
|
|
169
|
+
}
|
|
57
170
|
}
|
|
58
171
|
```
|
|
59
172
|
|
|
60
|
-
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
### Open with Component
|
|
176
|
+
|
|
177
|
+
Pass any Angular component class to display it inside the modal.
|
|
178
|
+
The component can inject `HubActiveModal` to close or dismiss the modal from within.
|
|
61
179
|
|
|
62
180
|
```typescript
|
|
63
|
-
import { Component } from '@angular/core';
|
|
181
|
+
import { Component, inject } from '@angular/core';
|
|
182
|
+
import { HubModal, HubActiveModal } from 'ng-hub-ui-modal';
|
|
64
183
|
|
|
184
|
+
/** Content component displayed inside the modal */
|
|
65
185
|
@Component({
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
`
|
|
186
|
+
selector: 'app-confirm-dialog',
|
|
187
|
+
standalone: true,
|
|
188
|
+
template: `
|
|
189
|
+
<div class="hub-modal__header"><h5>Confirm action</h5></div>
|
|
190
|
+
<div class="hub-modal__body">Are you sure you want to proceed?</div>
|
|
191
|
+
<div class="hub-modal__footer">
|
|
192
|
+
<button (click)="activeModal.dismiss('no')">Cancel</button>
|
|
193
|
+
<button (click)="activeModal.close(true)">Confirm</button>
|
|
194
|
+
</div>
|
|
195
|
+
`
|
|
77
196
|
})
|
|
78
|
-
export class
|
|
197
|
+
export class ConfirmDialogComponent {
|
|
198
|
+
activeModal = inject(HubActiveModal);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** Host component that opens the modal */
|
|
202
|
+
@Component({ selector: 'app-host', standalone: true, template: `<button (click)="openConfirm()">Delete</button>` })
|
|
203
|
+
export class HostComponent {
|
|
204
|
+
private modal = inject(HubModal);
|
|
205
|
+
|
|
206
|
+
openConfirm() {
|
|
207
|
+
this.modal
|
|
208
|
+
.open(ConfirmDialogComponent, {
|
|
209
|
+
headerSelector: '.hub-modal__header',
|
|
210
|
+
footerSelector: '.hub-modal__footer'
|
|
211
|
+
})
|
|
212
|
+
.result.then((confirmed) => {
|
|
213
|
+
if (confirmed) {
|
|
214
|
+
/* perform deletion */
|
|
215
|
+
}
|
|
216
|
+
})
|
|
217
|
+
.catch(() => {});
|
|
218
|
+
}
|
|
219
|
+
}
|
|
79
220
|
```
|
|
80
221
|
|
|
81
|
-
|
|
222
|
+
---
|
|
82
223
|
|
|
83
|
-
###
|
|
224
|
+
### Open with String
|
|
84
225
|
|
|
85
|
-
|
|
226
|
+
Display a quick text message without any additional component or template.
|
|
86
227
|
|
|
87
|
-
|
|
228
|
+
```typescript
|
|
229
|
+
this.modal.open('This is a simple string modal.');
|
|
230
|
+
```
|
|
88
231
|
|
|
89
|
-
|
|
232
|
+
---
|
|
90
233
|
|
|
91
|
-
|
|
92
|
-
- `component` (`ComponentType<any>`): The component to be displayed in the modal.
|
|
93
|
-
- `options` (`ModalOptions` | *optional*): An object containing the configuration options for the modal.
|
|
234
|
+
### Placement
|
|
94
235
|
|
|
95
|
-
|
|
236
|
+
Anchor the modal to any edge of the viewport using `HubModalPlacement`.
|
|
96
237
|
|
|
97
|
-
|
|
238
|
+
```typescript
|
|
239
|
+
import { HubModal, HubModalPlacement } from 'ng-hub-ui-modal';
|
|
98
240
|
|
|
99
|
-
|
|
241
|
+
// Right side panel
|
|
242
|
+
this.modal.open(MyComponent, { placement: HubModalPlacement.End });
|
|
100
243
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
- `footerSelector` (`string` | *optional*): A CSS selector for the footer section of the modal content. Any elements matching this selector will be projected into the modal footer.
|
|
104
|
-
- `dismissSelector` (`string` | *optional*): A CSS selector for elements that should act as dismiss triggers for the modal. When clicked, these elements will dismiss the modal. Default: `'[data-dismiss="modal"]'`.
|
|
105
|
-
- `data` (`any` | *optional*): An object containing additional data that will be bound to the modal component instance.
|
|
244
|
+
// Bottom sheet
|
|
245
|
+
this.modal.open(MyComponent, { placement: HubModalPlacement.Bottom });
|
|
106
246
|
|
|
107
|
-
|
|
247
|
+
// Left drawer, vertically centered
|
|
248
|
+
this.modal.open(MyComponent, {
|
|
249
|
+
placement: HubModalPlacement.Start,
|
|
250
|
+
centered: true
|
|
251
|
+
});
|
|
252
|
+
```
|
|
108
253
|
|
|
109
|
-
|
|
254
|
+
| Value | Effect |
|
|
255
|
+
| -------------------------- | ----------------------------- |
|
|
256
|
+
| `HubModalPlacement.Center` | Centred in viewport (default) |
|
|
257
|
+
| `HubModalPlacement.Start` | Left-anchored drawer |
|
|
258
|
+
| `HubModalPlacement.End` | Right-anchored drawer |
|
|
259
|
+
| `HubModalPlacement.Top` | Top sheet |
|
|
260
|
+
| `HubModalPlacement.Bottom` | Bottom sheet |
|
|
110
261
|
|
|
111
|
-
|
|
112
|
-
- `reason` (`any` | *optional*): A value that will be passed to the modal's dismissal event.
|
|
113
|
-
- `result`: A promise that resolves when the modal is dismissed, providing the dismissal reason.
|
|
262
|
+
---
|
|
114
263
|
|
|
115
|
-
###
|
|
264
|
+
### Size and Fullscreen
|
|
116
265
|
|
|
117
|
-
|
|
266
|
+
```typescript
|
|
267
|
+
// Predefined sizes
|
|
268
|
+
this.modal.open(MyComponent, { size: 'sm' }); // 'sm' | 'lg' | 'xl'
|
|
269
|
+
|
|
270
|
+
// Always fullscreen
|
|
271
|
+
this.modal.open(MyComponent, { fullscreen: true });
|
|
272
|
+
|
|
273
|
+
// Fullscreen only below 'md' breakpoint
|
|
274
|
+
this.modal.open(MyComponent, { fullscreen: 'md' });
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
---
|
|
118
278
|
|
|
119
|
-
|
|
279
|
+
### Scrollable Content
|
|
280
|
+
|
|
281
|
+
When the modal content overflows, enable internal scrolling.
|
|
120
282
|
|
|
121
283
|
```typescript
|
|
122
|
-
|
|
284
|
+
this.modal.open(LongContentComponent, { scrollable: true });
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
### Static Backdrop
|
|
290
|
+
|
|
291
|
+
Prevent dismissal when clicking outside the modal.
|
|
292
|
+
|
|
293
|
+
```typescript
|
|
294
|
+
this.modal.open(MyComponent, { backdrop: 'static' });
|
|
295
|
+
|
|
296
|
+
// Also disable ESC key
|
|
297
|
+
this.modal.open(MyComponent, { backdrop: 'static', keyboard: false });
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
### Before Dismiss Guard
|
|
303
|
+
|
|
304
|
+
Use `beforeDismiss` to prevent or delay modal closure, e.g. to show a confirmation first.
|
|
305
|
+
|
|
306
|
+
```typescript
|
|
307
|
+
this.modal.open(MyFormComponent, {
|
|
308
|
+
beforeDismiss: () => {
|
|
309
|
+
if (this.formIsDirty) {
|
|
310
|
+
return confirm('You have unsaved changes. Really close?');
|
|
311
|
+
}
|
|
312
|
+
return true;
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
// Async guard using a Promise
|
|
317
|
+
this.modal.open(MyComponent, {
|
|
318
|
+
beforeDismiss: () => this.confirmService.ask('Discard changes?')
|
|
319
|
+
});
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
### HubActiveModal in Content Component
|
|
325
|
+
|
|
326
|
+
Inject `HubActiveModal` into any component used as modal content to control it from within.
|
|
327
|
+
|
|
328
|
+
```typescript
|
|
329
|
+
import { Component, inject } from '@angular/core';
|
|
330
|
+
import { HubActiveModal, HubModalUpdatableOptions } from 'ng-hub-ui-modal';
|
|
123
331
|
|
|
124
332
|
@Component({
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
<div class="modal-body">
|
|
134
|
-
{{ body }}
|
|
135
|
-
</div>
|
|
136
|
-
<div class="modal-footer">
|
|
137
|
-
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
|
|
138
|
-
<button type="button" class="btn btn-primary" (click)="confirm()">Confirm</button>
|
|
139
|
-
</div>
|
|
140
|
-
`
|
|
333
|
+
selector: 'app-my-modal',
|
|
334
|
+
standalone: true,
|
|
335
|
+
template: `
|
|
336
|
+
<div class="hub-modal__body">
|
|
337
|
+
<button (click)="save()">Save</button>
|
|
338
|
+
<button (click)="cancel()">Cancel</button>
|
|
339
|
+
</div>
|
|
340
|
+
`
|
|
141
341
|
})
|
|
142
342
|
export class MyModalComponent {
|
|
143
|
-
|
|
144
|
-
body: string;
|
|
145
|
-
|
|
146
|
-
constructor(@Inject(MODAL_DATA) public data: any) {
|
|
147
|
-
this.title = data.title;
|
|
148
|
-
this.body = data.body;
|
|
149
|
-
}
|
|
343
|
+
activeModal = inject(HubActiveModal);
|
|
150
344
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
345
|
+
save() {
|
|
346
|
+
this.activeModal.close({ saved: true });
|
|
347
|
+
}
|
|
154
348
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
349
|
+
cancel() {
|
|
350
|
+
this.activeModal.dismiss('user_cancelled');
|
|
351
|
+
}
|
|
158
352
|
}
|
|
159
353
|
```
|
|
160
354
|
|
|
161
|
-
|
|
355
|
+
---
|
|
162
356
|
|
|
163
|
-
|
|
357
|
+
### Dismiss and Close Selectors
|
|
164
358
|
|
|
165
|
-
|
|
359
|
+
Automatically bind dismiss/close behaviour to DOM elements inside the modal content using CSS selectors.
|
|
166
360
|
|
|
167
|
-
|
|
361
|
+
```typescript
|
|
362
|
+
this.modal.open(MyComponent, {
|
|
363
|
+
dismissSelector: '[data-dismiss="modal"]',
|
|
364
|
+
closeSelector: '[data-close="modal"]'
|
|
365
|
+
});
|
|
366
|
+
```
|
|
168
367
|
|
|
169
368
|
```html
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
<div class="modal-header">
|
|
174
|
-
<!-- Header content projected here -->
|
|
175
|
-
</div>
|
|
176
|
-
<div class="modal-body">
|
|
177
|
-
<!-- Body content projected here -->
|
|
178
|
-
</div>
|
|
179
|
-
<div class="modal-footer">
|
|
180
|
-
<!-- Footer content projected here -->
|
|
181
|
-
</div>
|
|
182
|
-
</div>
|
|
183
|
-
</div>
|
|
184
|
-
</div>
|
|
369
|
+
<!-- Inside MyComponent template -->
|
|
370
|
+
<button data-dismiss="modal">Cancel</button>
|
|
371
|
+
<button data-close="modal">OK</button>
|
|
185
372
|
```
|
|
186
373
|
|
|
187
|
-
|
|
374
|
+
---
|
|
188
375
|
|
|
189
|
-
|
|
376
|
+
### Multiple Stacked Modals
|
|
190
377
|
|
|
191
|
-
|
|
378
|
+
Open modals from within a modal — the stack is managed automatically and focus is trapped to the topmost one.
|
|
192
379
|
|
|
193
380
|
```typescript
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
@Component({
|
|
198
|
-
selector: 'app-example',
|
|
199
|
-
template: `
|
|
200
|
-
<button (click)="openModal()">Open Modal</button>
|
|
201
|
-
`
|
|
202
|
-
})
|
|
203
|
-
export class ExampleComponent {
|
|
204
|
-
constructor(private modalService: ModalService) {}
|
|
381
|
+
@Component({ ... })
|
|
382
|
+
export class ParentModalComponent {
|
|
383
|
+
private modal = inject(HubModal);
|
|
205
384
|
|
|
206
|
-
|
|
207
|
-
|
|
385
|
+
openNested() {
|
|
386
|
+
this.modal.open(ChildModalComponent);
|
|
208
387
|
}
|
|
209
388
|
}
|
|
210
|
-
|
|
211
|
-
@Component({
|
|
212
|
-
selector: 'app-basic-modal',
|
|
213
|
-
template: `
|
|
214
|
-
<div class="modal-header">
|
|
215
|
-
<h4 class="modal-title">Basic Modal</h4>
|
|
216
|
-
</div>
|
|
217
|
-
<div class="modal-body">
|
|
218
|
-
This is a basic modal example.
|
|
219
|
-
</div>
|
|
220
|
-
<div class="modal-footer">
|
|
221
|
-
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
|
222
|
-
</div>
|
|
223
|
-
`
|
|
224
|
-
})
|
|
225
|
-
export class BasicModalComponent {}
|
|
226
389
|
```
|
|
227
390
|
|
|
228
|
-
|
|
391
|
+
---
|
|
392
|
+
|
|
393
|
+
### Observables: dismissAll and hasOpenModals
|
|
394
|
+
|
|
395
|
+
Use the service methods to interact with the entire modal stack.
|
|
229
396
|
|
|
230
397
|
```typescript
|
|
231
|
-
import {
|
|
232
|
-
import { ModalService, MODAL_DATA } from 'ng-hub-ui-modal';
|
|
398
|
+
import { HubModal } from 'ng-hub-ui-modal';
|
|
233
399
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
template: `
|
|
237
|
-
<button (click)="openModal()">Open Modal</button>
|
|
238
|
-
`
|
|
239
|
-
})
|
|
240
|
-
export class ExampleComponent {
|
|
241
|
-
constructor(private modalService: ModalService) {}
|
|
400
|
+
export class AppComponent {
|
|
401
|
+
private modal = inject(HubModal);
|
|
242
402
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
403
|
+
closeAll() {
|
|
404
|
+
this.modal.dismissAll('route_change');
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
get anyOpen(): boolean {
|
|
408
|
+
return this.modal.hasOpenModals();
|
|
409
|
+
}
|
|
248
410
|
}
|
|
411
|
+
```
|
|
249
412
|
|
|
250
|
-
|
|
251
|
-
selector: 'app-data-modal',
|
|
252
|
-
template: `
|
|
253
|
-
<div class="modal-header">
|
|
254
|
-
<h4 class="modal-title">Modal with Data</h4>
|
|
255
|
-
</div>
|
|
256
|
-
<div class="modal-body">
|
|
257
|
-
Hello, {{ name }}!
|
|
258
|
-
</div>
|
|
259
|
-
<div class="modal-footer">
|
|
260
|
-
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
|
261
|
-
</div>
|
|
262
|
-
`
|
|
263
|
-
})
|
|
264
|
-
export class DataModalComponent {
|
|
265
|
-
name: string;
|
|
413
|
+
Listen to `activeInstances` for reactive updates:
|
|
266
414
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
}
|
|
415
|
+
```typescript
|
|
416
|
+
this.modal.activeInstances.subscribe((refs) => {
|
|
417
|
+
console.log(`${refs.length} modals open`);
|
|
418
|
+
});
|
|
271
419
|
```
|
|
272
420
|
|
|
273
|
-
|
|
421
|
+
---
|
|
422
|
+
|
|
423
|
+
## API Reference
|
|
424
|
+
|
|
425
|
+
### HubModal Service
|
|
426
|
+
|
|
427
|
+
The main entry point for opening and managing modals.
|
|
428
|
+
|
|
429
|
+
| Method | Signature | Description |
|
|
430
|
+
| ----------------- | -------------------------------------- | ----------------------------------------------------- |
|
|
431
|
+
| `open` | `open(content, options?): HubModalRef` | Opens a new modal with the given content and options. |
|
|
432
|
+
| `dismissAll` | `dismissAll(reason?): void` | Dismisses all currently open modals. |
|
|
433
|
+
| `hasOpenModals` | `hasOpenModals(): boolean` | Returns `true` if at least one modal is open. |
|
|
434
|
+
| `activeInstances` | `EventEmitter<HubModalRef[]>` | Emits whenever the stack of open modals changes. |
|
|
435
|
+
|
|
436
|
+
---
|
|
437
|
+
|
|
438
|
+
### HubModalRef
|
|
439
|
+
|
|
440
|
+
A reference to an open modal returned by `HubModal.open()`.
|
|
441
|
+
|
|
442
|
+
| Member | Type | Description |
|
|
443
|
+
| ------------------- | ------------------ | ----------------------------------------------------------- |
|
|
444
|
+
| `result` | `Promise<any>` | Resolves on `close()`, rejects on `dismiss()`. |
|
|
445
|
+
| `componentInstance` | `T \| void` | Instance of the content component (if used). |
|
|
446
|
+
| `close(result?)` | `void` | Closes the modal and resolves `result`. |
|
|
447
|
+
| `dismiss(reason?)` | `void` | Dismisses the modal and rejects `result`. |
|
|
448
|
+
| `update(options)` | `void` | Updates modal options after opening. |
|
|
449
|
+
| `closed` | `Observable<any>` | Emits when the modal is closed via `close()`. |
|
|
450
|
+
| `dismissed` | `Observable<any>` | Emits when dismissed via `dismiss()` or user interaction. |
|
|
451
|
+
| `shown` | `Observable<void>` | Emits once the open animation finishes. |
|
|
452
|
+
| `hidden` | `Observable<void>` | Emits once the close animation finishes and DOM is removed. |
|
|
453
|
+
|
|
454
|
+
---
|
|
455
|
+
|
|
456
|
+
### HubActiveModal
|
|
457
|
+
|
|
458
|
+
Inject into your content component to control the modal from within.
|
|
459
|
+
|
|
460
|
+
| Method | Description |
|
|
461
|
+
| ------------------ | ---------------------------------------------------- |
|
|
462
|
+
| `close(result?)` | Closes the modal with an optional result. |
|
|
463
|
+
| `dismiss(reason?)` | Dismisses the modal with an optional reason. |
|
|
464
|
+
| `update(options)` | Updates live options (same as `HubModalRef.update`). |
|
|
465
|
+
|
|
466
|
+
---
|
|
467
|
+
|
|
468
|
+
### HubModalOptions
|
|
469
|
+
|
|
470
|
+
All options accepted by `HubModal.open()`.
|
|
471
|
+
|
|
472
|
+
| Option | Type | Default | Description |
|
|
473
|
+
| ------------------ | ------------------------------------------------------------ | ------------------------ | ----------------------------------------------------------- |
|
|
474
|
+
| `animation` | `boolean` | `true` | Enables fade in/out transitions. |
|
|
475
|
+
| `ariaLabelledBy` | `string` | — | ID of the element that labels the modal. |
|
|
476
|
+
| `ariaDescribedBy` | `string` | — | ID of the element that describes the modal. |
|
|
477
|
+
| `backdrop` | `boolean \| 'static'` | `true` | `false` = no backdrop, `'static'` = click does not close. |
|
|
478
|
+
| `beforeDismiss` | `() => boolean \| Promise<boolean>` | — | Guard called before dismissal. Return `false` to cancel. |
|
|
479
|
+
| `centered` | `boolean` | `false` | Centers modal on the cross-axis for side placements. |
|
|
480
|
+
| `placement` | `HubModalPlacement` | `Center` | Viewport anchor for the modal. |
|
|
481
|
+
| `container` | `string \| HTMLElement` | `body` | CSS selector or element to which modals are appended. |
|
|
482
|
+
| `fullscreen` | `boolean \| 'sm' \| 'md' \| 'lg' \| 'xl' \| 'xxl' \| string` | `false` | Fullscreen always or below a specific breakpoint. |
|
|
483
|
+
| `injector` | `Injector` | — | Custom injector for content component dependencies. |
|
|
484
|
+
| `keyboard` | `boolean` | `true` | Whether ESC key dismisses the modal. |
|
|
485
|
+
| `scrollable` | `boolean` | `false` | Makes the modal body scroll internally. |
|
|
486
|
+
| `size` | `'sm' \| 'lg' \| 'xl' \| string` | — | Controls the width of the modal dialog. |
|
|
487
|
+
| `windowClass` | `string` | — | Extra class added to the `hub-modal` host element. |
|
|
488
|
+
| `modalDialogClass` | `string` | — | Extra class added to the `hub-modal__dialog` element. |
|
|
489
|
+
| `backdropClass` | `string` | — | Extra class added to the `hub-modal__backdrop` element. |
|
|
490
|
+
| `headerSelector` | `string` | — | CSS selector for nodes to project into the header slot. |
|
|
491
|
+
| `footerSelector` | `string` | — | CSS selector for nodes to project into the footer slot. |
|
|
492
|
+
| `dismissSelector` | `string` | `[data-dismiss="modal"]` | Selector for elements that auto-dismiss the modal on click. |
|
|
493
|
+
| `closeSelector` | `string` | `[data-close="modal"]` | Selector for elements that auto-close the modal on click. |
|
|
494
|
+
| `data` | `any` | — | Arbitrary data bound to the content component instance. |
|
|
495
|
+
|
|
496
|
+
---
|
|
497
|
+
|
|
498
|
+
### HubModalUpdatableOptions
|
|
499
|
+
|
|
500
|
+
A subset of `HubModalOptions` that can be updated on an already-open modal via `HubModalRef.update()`.
|
|
501
|
+
|
|
502
|
+
`ariaLabelledBy`, `ariaDescribedBy`, `centered`, `placement`, `fullscreen`, `backdropClass`, `size`, `windowClass`, `modalDialogClass`.
|
|
503
|
+
|
|
504
|
+
---
|
|
505
|
+
|
|
506
|
+
### HubModalPlacement
|
|
274
507
|
|
|
275
508
|
```typescript
|
|
276
|
-
import {
|
|
277
|
-
|
|
509
|
+
import { HubModalPlacement } from 'ng-hub-ui-modal';
|
|
510
|
+
```
|
|
278
511
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
bodySelector: '[modalBody]',
|
|
305
|
-
footerSelector: '[modalFooter]'
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
}
|
|
512
|
+
| Value | CSS class applied | Description |
|
|
513
|
+
| -------------------------- | ----------------------------- | ---------------------------------------- |
|
|
514
|
+
| `HubModalPlacement.Center` | _(none)_ | Modal centred in the viewport (default). |
|
|
515
|
+
| `HubModalPlacement.Start` | `hub-modal--placement-start` | Left edge anchor. |
|
|
516
|
+
| `HubModalPlacement.End` | `hub-modal--placement-end` | Right edge anchor. |
|
|
517
|
+
| `HubModalPlacement.Top` | `hub-modal--placement-top` | Top edge anchor. |
|
|
518
|
+
| `HubModalPlacement.Bottom` | `hub-modal--placement-bottom` | Bottom edge anchor. |
|
|
519
|
+
|
|
520
|
+
---
|
|
521
|
+
|
|
522
|
+
### ModalDismissReasons
|
|
523
|
+
|
|
524
|
+
Built-in dismiss reason constants.
|
|
525
|
+
|
|
526
|
+
```typescript
|
|
527
|
+
import { ModalDismissReasons } from 'ng-hub-ui-modal';
|
|
528
|
+
|
|
529
|
+
modalRef.dismissed.subscribe((reason) => {
|
|
530
|
+
if (reason === ModalDismissReasons.ESC) {
|
|
531
|
+
/* ESC key */
|
|
532
|
+
}
|
|
533
|
+
if (reason === ModalDismissReasons.BACKDROP_CLICK) {
|
|
534
|
+
/* backdrop */
|
|
535
|
+
}
|
|
536
|
+
});
|
|
309
537
|
```
|
|
310
538
|
|
|
311
|
-
|
|
539
|
+
---
|
|
540
|
+
|
|
541
|
+
### HubModalConfig
|
|
542
|
+
|
|
543
|
+
Inject `HubModalConfig` to provide application-wide default options.
|
|
312
544
|
|
|
313
545
|
```typescript
|
|
314
|
-
import {
|
|
315
|
-
|
|
546
|
+
import { HubModalConfig } from 'ng-hub-ui-modal';
|
|
547
|
+
|
|
548
|
+
@Injectable({ providedIn: 'root' })
|
|
549
|
+
export class AppModalDefaults {
|
|
550
|
+
constructor(config: HubModalConfig) {
|
|
551
|
+
config.animation = true;
|
|
552
|
+
config.keyboard = false;
|
|
553
|
+
config.backdrop = 'static';
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
```
|
|
316
557
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
558
|
+
---
|
|
559
|
+
|
|
560
|
+
## Styling
|
|
561
|
+
|
|
562
|
+
The library publishes a self-contained stylesheet. Import it once in your application:
|
|
563
|
+
|
|
564
|
+
```scss
|
|
565
|
+
@import 'ng-hub-ui-modal/src/lib/modal.scss';
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
### CSS Variables
|
|
569
|
+
|
|
570
|
+
All visual aspects are controlled via `--hub-modal-*` tokens.
|
|
571
|
+
Full reference: [docs/css-variables-reference.md](./docs/css-variables-reference.md)
|
|
572
|
+
|
|
573
|
+
**Quick reference (most common tokens):**
|
|
574
|
+
|
|
575
|
+
| Variable | Default | Description |
|
|
576
|
+
| ------------------------------ | ------------------ | ------------------------- |
|
|
577
|
+
| `--hub-modal-max-width` | `500px` | Max dialog width |
|
|
578
|
+
| `--hub-modal-border-radius` | `0.5rem` | Dialog corner radius |
|
|
579
|
+
| `--hub-modal-bg` | system surface | Background color |
|
|
580
|
+
| `--hub-modal-color` | system text | Text color |
|
|
581
|
+
| `--hub-modal-header-padding-x` | `1rem` | Header horizontal padding |
|
|
582
|
+
| `--hub-modal-body-padding-x` | `1rem` | Body horizontal padding |
|
|
583
|
+
| `--hub-modal-backdrop-opacity` | `0.5` | Backdrop opacity |
|
|
584
|
+
| `--hub-modal-transition` | `0.2s ease-in-out` | Animation speed |
|
|
585
|
+
|
|
586
|
+
### Customization Example
|
|
587
|
+
|
|
588
|
+
```scss
|
|
589
|
+
/* Override at the host element level */
|
|
590
|
+
hub-modal-window {
|
|
591
|
+
--hub-modal-max-width: 720px;
|
|
592
|
+
--hub-modal-border-radius: 1rem;
|
|
593
|
+
--hub-modal-backdrop-opacity: 0.7;
|
|
342
594
|
}
|
|
343
|
-
```
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
### Bootstrap Integration (optional)
|
|
598
|
+
|
|
599
|
+
```scss
|
|
600
|
+
hub-modal-window {
|
|
601
|
+
--hub-modal-bg: var(--bs-body-bg);
|
|
602
|
+
--hub-modal-color: var(--bs-body-color);
|
|
603
|
+
--hub-modal-border-color: var(--bs-border-color);
|
|
604
|
+
}
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
### BEM Class Reference
|
|
608
|
+
|
|
609
|
+
| Class | Element |
|
|
610
|
+
| -------------------------------- | --------------------- |
|
|
611
|
+
| `.hub-modal` | Modal window host |
|
|
612
|
+
| `.hub-modal__backdrop` | Backdrop overlay |
|
|
613
|
+
| `.hub-modal__dialog` | Dialog container |
|
|
614
|
+
| `.hub-modal__content` | Content wrapper |
|
|
615
|
+
| `.hub-modal__header` | Header region |
|
|
616
|
+
| `.hub-modal__body` | Body region |
|
|
617
|
+
| `.hub-modal__footer` | Footer region |
|
|
618
|
+
| `.hub-modal__close` | Built-in close button |
|
|
619
|
+
| `.hub-modal--placement-{value}` | Placement modifier |
|
|
620
|
+
| `.hub-modal__dialog--centered` | Vertical centering |
|
|
621
|
+
| `.hub-modal__dialog--scrollable` | Scrollable body |
|
|
622
|
+
| `.hub-modal__dialog--fullscreen` | Fullscreen modifier |
|
|
623
|
+
|
|
624
|
+
---
|
|
625
|
+
|
|
626
|
+
## Contributing
|
|
627
|
+
|
|
628
|
+
### Development Setup
|
|
629
|
+
|
|
630
|
+
```bash
|
|
631
|
+
git clone https://github.com/carlos-morcillo/ng-hub-ui-modal.git
|
|
632
|
+
cd ng-hub-ui-modal
|
|
633
|
+
npm install
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
Build the library in watch mode:
|
|
637
|
+
|
|
638
|
+
```bash
|
|
639
|
+
ng build modal --watch
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
Serve the demo application:
|
|
643
|
+
|
|
644
|
+
```bash
|
|
645
|
+
ng serve
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
### Testing
|
|
649
|
+
|
|
650
|
+
```bash
|
|
651
|
+
ng test modal
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
### Commit Guidelines
|
|
655
|
+
|
|
656
|
+
Commits follow the [Conventional Commits](https://www.conventionalcommits.org/) format:
|
|
657
|
+
|
|
658
|
+
```
|
|
659
|
+
feat(modal): add new placement option
|
|
660
|
+
fix(modal): correct backdrop z-index
|
|
661
|
+
docs(modal): update CSS variable table
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
---
|
|
665
|
+
|
|
666
|
+
## Support & License
|
|
667
|
+
|
|
668
|
+
If this library saves you time, consider supporting further development:
|
|
669
|
+
|
|
670
|
+
☕ [Buy me a coffee](https://www.buymeacoffee.com/carlosmorcillo)
|
|
671
|
+
|
|
672
|
+
**MIT License** — © [Carlos Morcillo](https://github.com/carlos-morcillo)
|