1+ import { animate , AnimationEvent , state , style , transition , trigger } from '@angular/animations' ;
2+ import { DOCUMENT } from '@angular/common' ;
13import {
4+ AfterViewInit ,
5+ booleanAttribute ,
26 Component ,
7+ DestroyRef ,
8+ effect ,
39 ElementRef ,
410 EventEmitter ,
511 HostBinding ,
612 HostListener ,
13+ inject ,
714 Inject ,
815 Input ,
916 OnDestroy ,
1017 OnInit ,
1118 Output ,
1219 Renderer2 ,
13- ViewChild
20+ signal ,
21+ ViewChild ,
22+ WritableSignal
1423} 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' ;
2026
2127import { ModalService } from '../modal.service' ;
2228import { BackdropService } from '../../backdrop/backdrop.service' ;
@@ -39,18 +45,18 @@ import { ModalDialogComponent } from '../modal-dialog/modal-dialog.component';
3945 // display: 'none'
4046 } )
4147 ) ,
42- transition ( 'visible <=> *' , [ animate ( '300ms ' ) ] )
48+ transition ( 'visible <=> *' , [ animate ( '150ms ' ) ] )
4349 ] )
4450 ] ,
4551 templateUrl : './modal.component.html' ,
4652 exportAs : 'cModal' ,
4753 standalone : true ,
4854 imports : [ ModalDialogComponent , ModalContentComponent , A11yModule ]
4955} )
50- export class ModalComponent implements OnInit , OnDestroy {
56+ export class ModalComponent implements OnInit , OnDestroy , AfterViewInit {
5157
52- static ngAcceptInputType_scrollable : BooleanInput ;
53- static ngAcceptInputType_visible : BooleanInput ;
58+ #destroyRef = inject ( DestroyRef ) ;
59+ #focusMonitor = inject ( FocusMonitor ) ;
5460
5561 constructor (
5662 @Inject ( DOCUMENT ) private document : Document ,
@@ -83,7 +89,7 @@ export class ModalComponent implements OnInit, OnDestroy {
8389 * @type boolean
8490 * @default true
8591 */
86- @Input ( ) keyboard = true ;
92+ @Input ( { transform : booleanAttribute } ) keyboard = true ;
8793 @Input ( ) id ?: string ;
8894 /**
8995 * Size the component small, large, or extra large.
@@ -92,64 +98,65 @@ export class ModalComponent implements OnInit, OnDestroy {
9298 /**
9399 * Remove animation to create modal that simply appear rather than fade in to view.
94100 */
95- @Input ( ) transition = true ;
101+ @Input ( { transform : booleanAttribute } ) transition = true ;
96102 /**
97103 * Default role for modal. [docs]
98104 * @type string
99105 * @default 'dialog'
100106 */
101107 @Input ( ) @HostBinding ( 'attr.role' ) role = 'dialog' ;
108+
102109 /**
103110 * Set aria-modal html attr for modal. [docs]
104111 * @type boolean
105- * @default true
112+ * @default null
106113 */
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 ;
108124
109125 /**
110126 * Create a scrollable modal that allows scrolling the modal body.
111127 * @type boolean
112128 */
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 ;
123130
124131 /**
125132 * Toggle the visibility of modal component.
126133 * @type boolean
127134 */
128- @Input ( )
135+ @Input ( { transform : booleanAttribute } )
129136 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 ) ;
136142 }
137143 }
138144
139145 get visible ( ) : boolean {
140- return this . _visible ;
146+ return this . #visible ( ) ;
141147 }
142148
143- private _visible ! : boolean ;
149+ #visible: WritableSignal < boolean > = signal ( false ) ;
144150
145151 /**
146152 * Event triggered on modal dismiss.
147153 */
148154 @Output ( ) visibleChange = new EventEmitter < boolean > ( ) ;
149155
150156 @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 ;
153160
154161 // private inBoundingClientRect!: boolean;
155162
@@ -189,10 +196,8 @@ export class ModalComponent implements OnInit, OnDestroy {
189196
190197 @HostListener ( '@showHide.start' , [ '$event' ] )
191198 animateStart ( event : AnimationEvent ) {
192- const scrollbarWidth = this . backdropService . scrollbarWidth ;
193199 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 ( ) ;
196201 this . renderer . setStyle ( this . hostElement . nativeElement , 'display' , 'block' ) ;
197202 } else {
198203 if ( ! this . transition ) {
@@ -206,8 +211,6 @@ export class ModalComponent implements OnInit, OnDestroy {
206211 setTimeout ( ( ) => {
207212 if ( event . toState === 'hidden' ) {
208213 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' ) ;
211214 }
212215 } ) ;
213216 this . show = this . visible ;
@@ -255,14 +258,23 @@ export class ModalComponent implements OnInit, OnDestroy {
255258 this . stateToggleSubscribe ( ) ;
256259 }
257260
261+ #afterViewInit = signal ( false ) ;
262+
263+ ngAfterViewInit ( ) : void {
264+ this . #afterViewInit. set ( true ) ;
265+ }
266+
258267 ngOnDestroy ( ) : void {
259268 this . modalService . toggle ( { show : false , modal : this } ) ;
260- this . stateToggleSubscribe ( false ) ;
269+ this . #afterViewInit . set ( false ) ;
261270 }
262271
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 (
266278 ( action ) => {
267279 if ( this === action . modal || this . id === action . id ) {
268280 if ( 'show' in action ) {
@@ -275,17 +287,10 @@ export class ModalComponent implements OnInit, OnDestroy {
275287 }
276288 }
277289 ) ;
278- } else {
279- this . stateToggleSubscription ?. unsubscribe ( ) ;
280- }
281290 }
282291
283292 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) ;
289294 }
290295
291296 private setBodyStyles ( open : boolean ) : void {
0 commit comments