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 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
+ [![NPM Version](https://img.shields.io/npm/v/ng-hub-ui-modal.svg)](https://www.npmjs.com/package/ng-hub-ui-modal)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+ [![Angular](https://img.shields.io/badge/Angular-21-red.svg)](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)