ng-hub-ui-modal 0.0.0-watch
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/LICENSE +21 -0
- package/README.md +672 -0
- package/fesm2022/ng-hub-ui-modal.mjs +1151 -0
- package/fesm2022/ng-hub-ui-modal.mjs.map +1 -0
- package/package.json +48 -0
- package/types/ng-hub-ui-modal.d.ts +527 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Carlos Morcillo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,672 @@
|
|
|
1
|
+
# ng-hub-ui-modal
|
|
2
|
+
|
|
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
|
+
---
|
|
63
|
+
|
|
64
|
+
## Features
|
|
65
|
+
|
|
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
|
+
---
|
|
79
|
+
|
|
80
|
+
## Installation
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
npm install ng-hub-ui-modal
|
|
84
|
+
```
|
|
85
|
+
|
|
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
|
+
```
|
|
121
|
+
|
|
122
|
+
### NgModule (classic)
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
import { HubModalModule } from 'ng-hub-ui-modal';
|
|
126
|
+
|
|
127
|
+
@NgModule({
|
|
128
|
+
imports: [HubModalModule]
|
|
129
|
+
})
|
|
130
|
+
export class AppModule {}
|
|
131
|
+
```
|
|
132
|
+
|
|
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.
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
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
|
+
}
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
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.
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
import { Component, inject } from '@angular/core';
|
|
182
|
+
import { HubModal, HubActiveModal } from 'ng-hub-ui-modal';
|
|
183
|
+
|
|
184
|
+
/** Content component displayed inside the modal */
|
|
185
|
+
@Component({
|
|
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
|
+
`
|
|
196
|
+
})
|
|
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
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
### Open with String
|
|
225
|
+
|
|
226
|
+
Display a quick text message without any additional component or template.
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
this.modal.open('This is a simple string modal.');
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
### Placement
|
|
235
|
+
|
|
236
|
+
Anchor the modal to any edge of the viewport using `HubModalPlacement`.
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
import { HubModal, HubModalPlacement } from 'ng-hub-ui-modal';
|
|
240
|
+
|
|
241
|
+
// Right side panel
|
|
242
|
+
this.modal.open(MyComponent, { placement: HubModalPlacement.End });
|
|
243
|
+
|
|
244
|
+
// Bottom sheet
|
|
245
|
+
this.modal.open(MyComponent, { placement: HubModalPlacement.Bottom });
|
|
246
|
+
|
|
247
|
+
// Left drawer, vertically centered
|
|
248
|
+
this.modal.open(MyComponent, {
|
|
249
|
+
placement: HubModalPlacement.Start,
|
|
250
|
+
centered: true
|
|
251
|
+
});
|
|
252
|
+
```
|
|
253
|
+
|
|
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 |
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
### Size and Fullscreen
|
|
265
|
+
|
|
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
|
+
---
|
|
278
|
+
|
|
279
|
+
### Scrollable Content
|
|
280
|
+
|
|
281
|
+
When the modal content overflows, enable internal scrolling.
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
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';
|
|
331
|
+
|
|
332
|
+
@Component({
|
|
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
|
+
`
|
|
341
|
+
})
|
|
342
|
+
export class MyModalComponent {
|
|
343
|
+
activeModal = inject(HubActiveModal);
|
|
344
|
+
|
|
345
|
+
save() {
|
|
346
|
+
this.activeModal.close({ saved: true });
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
cancel() {
|
|
350
|
+
this.activeModal.dismiss('user_cancelled');
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
---
|
|
356
|
+
|
|
357
|
+
### Dismiss and Close Selectors
|
|
358
|
+
|
|
359
|
+
Automatically bind dismiss/close behaviour to DOM elements inside the modal content using CSS selectors.
|
|
360
|
+
|
|
361
|
+
```typescript
|
|
362
|
+
this.modal.open(MyComponent, {
|
|
363
|
+
dismissSelector: '[data-dismiss="modal"]',
|
|
364
|
+
closeSelector: '[data-close="modal"]'
|
|
365
|
+
});
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
```html
|
|
369
|
+
<!-- Inside MyComponent template -->
|
|
370
|
+
<button data-dismiss="modal">Cancel</button>
|
|
371
|
+
<button data-close="modal">OK</button>
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
---
|
|
375
|
+
|
|
376
|
+
### Multiple Stacked Modals
|
|
377
|
+
|
|
378
|
+
Open modals from within a modal — the stack is managed automatically and focus is trapped to the topmost one.
|
|
379
|
+
|
|
380
|
+
```typescript
|
|
381
|
+
@Component({ ... })
|
|
382
|
+
export class ParentModalComponent {
|
|
383
|
+
private modal = inject(HubModal);
|
|
384
|
+
|
|
385
|
+
openNested() {
|
|
386
|
+
this.modal.open(ChildModalComponent);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
---
|
|
392
|
+
|
|
393
|
+
### Observables: dismissAll and hasOpenModals
|
|
394
|
+
|
|
395
|
+
Use the service methods to interact with the entire modal stack.
|
|
396
|
+
|
|
397
|
+
```typescript
|
|
398
|
+
import { HubModal } from 'ng-hub-ui-modal';
|
|
399
|
+
|
|
400
|
+
export class AppComponent {
|
|
401
|
+
private modal = inject(HubModal);
|
|
402
|
+
|
|
403
|
+
closeAll() {
|
|
404
|
+
this.modal.dismissAll('route_change');
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
get anyOpen(): boolean {
|
|
408
|
+
return this.modal.hasOpenModals();
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
Listen to `activeInstances` for reactive updates:
|
|
414
|
+
|
|
415
|
+
```typescript
|
|
416
|
+
this.modal.activeInstances.subscribe((refs) => {
|
|
417
|
+
console.log(`${refs.length} modals open`);
|
|
418
|
+
});
|
|
419
|
+
```
|
|
420
|
+
|
|
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
|
|
507
|
+
|
|
508
|
+
```typescript
|
|
509
|
+
import { HubModalPlacement } from 'ng-hub-ui-modal';
|
|
510
|
+
```
|
|
511
|
+
|
|
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
|
+
});
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
---
|
|
540
|
+
|
|
541
|
+
### HubModalConfig
|
|
542
|
+
|
|
543
|
+
Inject `HubModalConfig` to provide application-wide default options.
|
|
544
|
+
|
|
545
|
+
```typescript
|
|
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
|
+
```
|
|
557
|
+
|
|
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;
|
|
594
|
+
}
|
|
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)
|