1
+ import { animate , AnimationEvent , state , style , transition , trigger } from '@angular/animations' ;
2
+ import { DOCUMENT } from '@angular/common' ;
1
3
import {
4
+ AfterViewInit ,
5
+ booleanAttribute ,
2
6
Component ,
7
+ DestroyRef ,
8
+ effect ,
3
9
ElementRef ,
4
10
EventEmitter ,
5
11
HostBinding ,
6
12
HostListener ,
13
+ inject ,
7
14
Inject ,
8
15
Input ,
9
16
OnDestroy ,
10
17
OnInit ,
11
18
Output ,
12
19
Renderer2 ,
13
- ViewChild
20
+ signal ,
21
+ ViewChild ,
22
+ WritableSignal
14
23
} from '@angular/core' ;
15
- import { DOCUMENT } from '@angular/common' ;
16
- import { animate , AnimationEvent , state , style , transition , trigger } from '@angular/animations' ;
17
- import { BooleanInput , coerceBooleanProperty } from '@angular/cdk/coercion' ;
18
- import { A11yModule } from '@angular/cdk/a11y' ;
19
- import { Subscription } from 'rxjs' ;
24
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop' ;
25
+ import { A11yModule , FocusMonitor } from '@angular/cdk/a11y' ;
20
26
21
27
import { ModalService } from '../modal.service' ;
22
28
import { BackdropService } from '../../backdrop/backdrop.service' ;
@@ -39,18 +45,18 @@ import { ModalDialogComponent } from '../modal-dialog/modal-dialog.component';
39
45
// display: 'none'
40
46
} )
41
47
) ,
42
- transition ( 'visible <=> *' , [ animate ( '300ms ' ) ] )
48
+ transition ( 'visible <=> *' , [ animate ( '150ms ' ) ] )
43
49
] )
44
50
] ,
45
51
templateUrl : './modal.component.html' ,
46
52
exportAs : 'cModal' ,
47
53
standalone : true ,
48
54
imports : [ ModalDialogComponent , ModalContentComponent , A11yModule ]
49
55
} )
50
- export class ModalComponent implements OnInit , OnDestroy {
56
+ export class ModalComponent implements OnInit , OnDestroy , AfterViewInit {
51
57
52
- static ngAcceptInputType_scrollable : BooleanInput ;
53
- static ngAcceptInputType_visible : BooleanInput ;
58
+ #destroyRef = inject ( DestroyRef ) ;
59
+ #focusMonitor = inject ( FocusMonitor ) ;
54
60
55
61
constructor (
56
62
@Inject ( DOCUMENT ) private document : Document ,
@@ -83,7 +89,7 @@ export class ModalComponent implements OnInit, OnDestroy {
83
89
* @type boolean
84
90
* @default true
85
91
*/
86
- @Input ( ) keyboard = true ;
92
+ @Input ( { transform : booleanAttribute } ) keyboard = true ;
87
93
@Input ( ) id ?: string ;
88
94
/**
89
95
* Size the component small, large, or extra large.
@@ -92,64 +98,65 @@ export class ModalComponent implements OnInit, OnDestroy {
92
98
/**
93
99
* Remove animation to create modal that simply appear rather than fade in to view.
94
100
*/
95
- @Input ( ) transition = true ;
101
+ @Input ( { transform : booleanAttribute } ) transition = true ;
96
102
/**
97
103
* Default role for modal. [docs]
98
104
* @type string
99
105
* @default 'dialog'
100
106
*/
101
107
@Input ( ) @HostBinding ( 'attr.role' ) role = 'dialog' ;
108
+
102
109
/**
103
110
* Set aria-modal html attr for modal. [docs]
104
111
* @type boolean
105
- * @default true
112
+ * @default null
106
113
*/
107
- @Input ( ) @HostBinding ( 'attr.aria-modal' ) ariaModal = true ;
114
+ @Input ( ) @HostBinding ( 'attr.aria-modal' )
115
+ set ariaModal ( value : boolean | null ) {
116
+ this . #ariaModal = value ;
117
+ }
118
+
119
+ get ariaModal ( ) : boolean | null {
120
+ return this . visible || this . #ariaModal ? true : null ;
121
+ } ;
122
+
123
+ #ariaModal: boolean | null = null ;
108
124
109
125
/**
110
126
* Create a scrollable modal that allows scrolling the modal body.
111
127
* @type boolean
112
128
*/
113
- @Input ( )
114
- set scrollable ( value : boolean ) {
115
- this . _scrollable = coerceBooleanProperty ( value ) ;
116
- }
117
-
118
- get scrollable ( ) : boolean {
119
- return this . _scrollable ;
120
- }
121
-
122
- private _scrollable = false ;
129
+ @Input ( { transform : booleanAttribute } ) scrollable : boolean = false ;
123
130
124
131
/**
125
132
* Toggle the visibility of modal component.
126
133
* @type boolean
127
134
*/
128
- @Input ( )
135
+ @Input ( { transform : booleanAttribute } )
129
136
set visible ( value : boolean ) {
130
- const newValue = coerceBooleanProperty ( value ) ;
131
- if ( this . _visible !== newValue ) {
132
- this . _visible = newValue ;
133
- this . setBackdrop ( this . backdrop !== false && newValue ) ;
134
- this . setBodyStyles ( newValue ) ;
135
- this . visibleChange . emit ( newValue ) ;
137
+ if ( this . #visible( ) !== value ) {
138
+ this . #visible. set ( value ) ;
139
+ this . setBackdrop ( this . backdrop !== false && value ) ;
140
+ this . setBodyStyles ( value ) ;
141
+ this . visibleChange . emit ( value ) ;
136
142
}
137
143
}
138
144
139
145
get visible ( ) : boolean {
140
- return this . _visible ;
146
+ return this . #visible ( ) ;
141
147
}
142
148
143
- private _visible ! : boolean ;
149
+ #visible: WritableSignal < boolean > = signal ( false ) ;
144
150
145
151
/**
146
152
* Event triggered on modal dismiss.
147
153
*/
148
154
@Output ( ) visibleChange = new EventEmitter < boolean > ( ) ;
149
155
150
156
@ViewChild ( ModalContentComponent , { read : ElementRef } ) modalContent ! : ElementRef ;
151
- private activeBackdrop ! : any ;
152
- private stateToggleSubscription ! : Subscription ;
157
+ @ViewChild ( 'modalContentRef' , { read : ElementRef } ) modalContentRef ! : ElementRef ;
158
+
159
+ #activeBackdrop! : any ;
153
160
154
161
// private inBoundingClientRect!: boolean;
155
162
@@ -189,10 +196,8 @@ export class ModalComponent implements OnInit, OnDestroy {
189
196
190
197
@HostListener ( '@showHide.start' , [ '$event' ] )
191
198
animateStart ( event : AnimationEvent ) {
192
- const scrollbarWidth = this . backdropService . scrollbarWidth ;
193
199
if ( event . toState === 'visible' ) {
194
- this . renderer . setStyle ( this . document . body , 'overflow' , 'hidden' ) ;
195
- this . renderer . setStyle ( this . document . body , 'padding-right' , scrollbarWidth ) ;
200
+ this . backdropService . hideScrollbar ( ) ;
196
201
this . renderer . setStyle ( this . hostElement . nativeElement , 'display' , 'block' ) ;
197
202
} else {
198
203
if ( ! this . transition ) {
@@ -206,8 +211,6 @@ export class ModalComponent implements OnInit, OnDestroy {
206
211
setTimeout ( ( ) => {
207
212
if ( event . toState === 'hidden' ) {
208
213
this . renderer . setStyle ( this . hostElement . nativeElement , 'display' , 'none' ) ;
209
- this . renderer . removeStyle ( this . document . body , 'overflow' ) ;
210
- this . renderer . removeStyle ( this . document . body , 'padding-right' ) ;
211
214
}
212
215
} ) ;
213
216
this . show = this . visible ;
@@ -255,14 +258,23 @@ export class ModalComponent implements OnInit, OnDestroy {
255
258
this . stateToggleSubscribe ( ) ;
256
259
}
257
260
261
+ #afterViewInit = signal ( false ) ;
262
+
263
+ ngAfterViewInit ( ) : void {
264
+ this . #afterViewInit. set ( true ) ;
265
+ }
266
+
258
267
ngOnDestroy ( ) : void {
259
268
this . modalService . toggle ( { show : false , modal : this } ) ;
260
- this . stateToggleSubscribe ( false ) ;
269
+ this . #afterViewInit . set ( false ) ;
261
270
}
262
271
263
- private stateToggleSubscribe ( subscribe : boolean = true ) : void {
264
- if ( subscribe ) {
265
- this . stateToggleSubscription = this . modalService . modalState$ . subscribe (
272
+ private stateToggleSubscribe ( ) : void {
273
+ this . modalService . modalState$
274
+ . pipe (
275
+ takeUntilDestroyed ( this . #destroyRef)
276
+ )
277
+ . subscribe (
266
278
( action ) => {
267
279
if ( this === action . modal || this . id === action . id ) {
268
280
if ( 'show' in action ) {
@@ -275,17 +287,10 @@ export class ModalComponent implements OnInit, OnDestroy {
275
287
}
276
288
}
277
289
) ;
278
- } else {
279
- this . stateToggleSubscription ?. unsubscribe ( ) ;
280
- }
281
290
}
282
291
283
292
private setBackdrop ( setBackdrop : boolean ) : void {
284
- if ( setBackdrop ) {
285
- this . activeBackdrop = this . backdropService . setBackdrop ( 'modal' ) ;
286
- } else {
287
- this . activeBackdrop = this . backdropService . clearBackdrop ( this . activeBackdrop ) ;
288
- }
293
+ this . #activeBackdrop = setBackdrop ? this . backdropService . setBackdrop ( 'modal' ) : this . backdropService . clearBackdrop ( this . #activeBackdrop) ;
289
294
}
290
295
291
296
private setBodyStyles ( open : boolean ) : void {
0 commit comments