@@ -3,18 +3,20 @@ import { DOCUMENT, isPlatformBrowser } from '@angular/common';
3
3
import {
4
4
booleanAttribute ,
5
5
Component ,
6
+ computed ,
6
7
DestroyRef ,
8
+ effect ,
7
9
ElementRef ,
8
10
EventEmitter ,
9
- HostBinding ,
10
- HostListener ,
11
11
inject ,
12
- Input ,
12
+ input ,
13
13
OnDestroy ,
14
14
OnInit ,
15
- Output ,
15
+ output ,
16
16
PLATFORM_ID ,
17
- Renderer2
17
+ Renderer2 ,
18
+ signal ,
19
+ untracked
18
20
} from '@angular/core' ;
19
21
import { takeUntilDestroyed } from '@angular/core/rxjs-interop' ;
20
22
import { A11yModule } from '@angular/cdk/a11y' ;
@@ -52,7 +54,19 @@ let nextId = 0;
52
54
exportAs : 'cOffcanvas' ,
53
55
imports : [ A11yModule ] ,
54
56
hostDirectives : [ { directive : ThemeDirective , inputs : [ 'dark' ] } ] ,
55
- host : { ngSkipHydration : 'true' , '[attr.inert]' : 'ariaHidden || null' }
57
+ host : {
58
+ ngSkipHydration : 'true' ,
59
+ '[@showHide]' : 'animateTrigger' ,
60
+ '[attr.id]' : 'id()' ,
61
+ '[attr.inert]' : 'ariaHidden() || null' ,
62
+ '[attr.role]' : 'role()' ,
63
+ '[attr.aria-modal]' : 'ariaModal()' ,
64
+ '[attr.tabindex]' : 'tabIndex' ,
65
+ '[class]' : 'hostClasses()' ,
66
+ '(@showHide.start)' : 'animateStart($event)' ,
67
+ '(@showHide.done)' : 'animateDone($event)' ,
68
+ '(document:keydown)' : 'onKeyDownHandler($event)'
69
+ }
56
70
} )
57
71
export class OffcanvasComponent implements OnInit , OnDestroy {
58
72
readonly #document = inject < Document > ( DOCUMENT ) ;
@@ -66,45 +80,47 @@ export class OffcanvasComponent implements OnInit, OnDestroy {
66
80
67
81
/**
68
82
* Apply a backdrop on body while offcanvas is open.
69
- * @type boolean | 'static'
83
+ * @return boolean | 'static'
70
84
* @default true
71
85
*/
72
- @ Input ( ) backdrop : boolean | 'static' = true ;
86
+ readonly backdrop = input < boolean | 'static' > ( true ) ;
73
87
74
88
/**
75
89
* Closes the offcanvas when escape key is pressed [docs]
76
- * @type boolean
90
+ * @return boolean
77
91
* @default true
78
92
*/
79
- @ Input ( { transform : booleanAttribute } ) keyboard = true ;
93
+ readonly keyboard = input ( true , { transform : booleanAttribute } ) ;
80
94
81
95
/**
82
96
* Components placement, there’s no default placement.
83
- * @type {'start' | 'end' | 'top' | 'bottom' }
97
+ * @return {'start' | 'end' | 'top' | 'bottom' }
84
98
* @default 'start'
85
99
*/
86
- @ Input ( ) placement : string | 'start' | 'end' | 'top' | 'bottom' = 'start' ;
100
+ readonly placement = input < string | 'start' | 'end' | 'top' | 'bottom' > ( 'start' ) ;
87
101
88
102
/**
89
103
* Responsive offcanvas property hides content outside the viewport from a specified breakpoint and down.
90
- * @type boolean | 'sm' | 'md' | 'lg' | 'xl' | 'xxl';
104
+ * @return boolean | 'sm' | 'md' | 'lg' | 'xl' | 'xxl';
91
105
* @default true
92
106
* @since 4.3.10
93
107
*/
94
- @Input ( ) responsive ?: boolean | 'sm' | 'md' | 'lg' | 'xl' | 'xxl' = true ;
95
- @Input ( ) id = `offcanvas-${ this . placement } -${ nextId ++ } ` ;
108
+ readonly responsive = input < ( boolean | 'sm' | 'md' | 'lg' | 'xl' | 'xxl' ) | undefined > ( true ) ;
109
+ readonly id = input ( `offcanvas-${ this . placement ( ) } -${ nextId ++ } ` ) ;
110
+
96
111
/**
97
112
* Default role for offcanvas. [docs]
98
- * @type string
113
+ * @return string
99
114
* @default 'dialog'
100
115
*/
101
- @Input ( ) @HostBinding ( 'attr.role' ) role = 'dialog' ;
116
+ readonly role = input < string > ( 'dialog' ) ;
117
+
102
118
/**
103
119
* Set aria-modal html attr for offcanvas. [docs]
104
- * @type boolean
120
+ * @return boolean
105
121
* @default true
106
122
*/
107
- @ Input ( { transform : booleanAttribute } ) @ HostBinding ( 'attr.aria-modal' ) ariaModal = true ;
123
+ readonly ariaModal = input ( true , { transform : booleanAttribute } ) ;
108
124
109
125
#activeBackdrop! : HTMLDivElement ;
110
126
#backdropClickSubscription! : Subscription ;
@@ -113,91 +129,92 @@ export class OffcanvasComponent implements OnInit, OnDestroy {
113
129
114
130
/**
115
131
* Allow body scrolling while offcanvas is visible.
116
- * @type boolean
132
+ * @return boolean
117
133
* @default false
118
134
*/
119
- @ Input ( { transform : booleanAttribute } ) scroll : boolean = false ;
135
+ readonly scroll = input ( false , { transform : booleanAttribute } ) ;
120
136
121
137
/**
122
138
* Toggle the visibility of offcanvas component.
123
- * @type boolean
139
+ * @return boolean
124
140
* @default false
125
141
*/
126
- @Input ( { transform : booleanAttribute } )
127
- set visible ( value : boolean ) {
128
- this . #visible = value ;
129
- if ( this . #visible) {
130
- this . setBackdrop ( this . backdrop ) ;
142
+ readonly visibleInput = input ( false , { transform : booleanAttribute , alias : 'visible' } ) ;
143
+
144
+ readonly visibleInputEffect = effect ( ( ) => {
145
+ const visible = this . visibleInput ( ) ;
146
+ untracked ( ( ) => {
147
+ this . visible . set ( visible ) ;
148
+ } ) ;
149
+ } ) ;
150
+
151
+ readonly visible = signal ( false ) ;
152
+
153
+ readonly visibleEffect = effect ( ( ) => {
154
+ const visible = this . visible ( ) ;
155
+ if ( visible ) {
156
+ this . setBackdrop ( this . backdrop ( ) ) ;
131
157
this . setFocus ( ) ;
132
158
} else {
133
159
this . setBackdrop ( false ) ;
134
160
}
135
- this . layoutChangeSubscribe ( this . #visible) ;
136
- this . visibleChange . emit ( value ) ;
137
- }
138
-
139
- get visible ( ) : boolean {
140
- return this . #visible;
141
- }
142
-
143
- #visible: boolean = false ;
161
+ this . layoutChangeSubscribe ( visible ) ;
162
+ this . visibleChange . emit ( visible ) ;
163
+ } ) ;
144
164
145
165
/**
146
166
* Event triggered on visible change.
147
- * @type EventEmitter<boolean>
167
+ * @return EventEmitter<boolean>
148
168
*/
149
- @ Output ( ) readonly visibleChange : EventEmitter < boolean > = new EventEmitter < boolean > ( ) ;
169
+ readonly visibleChange = output < boolean > ( ) ;
150
170
151
- @HostBinding ( 'class' )
152
- get hostClasses ( ) : any {
171
+ readonly hostClasses = computed ( ( ) => {
172
+ const responsive = this . responsive ( ) ;
173
+ const placement = this . placement ( ) ;
153
174
return {
154
- offcanvas : typeof this . responsive === 'boolean' ,
155
- [ `offcanvas-${ this . responsive } ` ] : typeof this . responsive !== 'boolean' ,
156
- [ `offcanvas-${ this . placement } ` ] : ! ! this . placement ,
175
+ offcanvas : typeof responsive === 'boolean' ,
176
+ [ `offcanvas-${ responsive } ` ] : typeof responsive !== 'boolean' ,
177
+ [ `offcanvas-${ placement } ` ] : ! ! placement ,
157
178
show : this . show
158
- } ;
159
- }
179
+ } as Record < string , boolean > ;
180
+ } ) ;
160
181
161
- // @HostBinding ('attr.aria-hidden')
162
- get ariaHidden ( ) : boolean | null {
163
- return this . visible ? null : true ;
164
- }
182
+ readonly ariaHidden = computed ( ( ) => {
183
+ return this . visible ( ) ? null : true ;
184
+ } ) ;
165
185
166
- @HostBinding ( 'attr.tabindex' )
167
186
get tabIndex ( ) : string | null {
168
187
return '-1' ;
169
188
}
170
189
171
- @HostBinding ( '@showHide' )
172
190
get animateTrigger ( ) : string {
173
- return this . visible ? 'visible' : 'hidden' ;
191
+ return this . visible ( ) ? 'visible' : 'hidden' ;
174
192
}
175
193
176
194
get show ( ) : boolean {
177
- return this . visible && this . #show;
195
+ return this . visible ( ) && this . #show;
178
196
}
179
197
180
198
set show ( value : boolean ) {
181
199
this . #show = value ;
182
200
}
183
201
184
202
get responsiveBreakpoint ( ) : string | false {
185
- if ( typeof this . responsive !== 'string' ) {
203
+ const responsive = this . responsive ( ) ;
204
+ if ( typeof responsive !== 'string' ) {
186
205
return false ;
187
206
}
188
207
const element : Element = this . #document. documentElement ;
189
- const responsiveBreakpoint = this . responsive ;
190
208
const breakpointValue =
191
209
this . #document. defaultView
192
210
?. getComputedStyle ( element )
193
- ?. getPropertyValue ( `--cui-breakpoint-${ responsiveBreakpoint . trim ( ) } ` ) ?? false ;
211
+ ?. getPropertyValue ( `--cui-breakpoint-${ responsive . trim ( ) } ` ) ?? false ;
194
212
return breakpointValue ? `${ parseFloat ( breakpointValue . trim ( ) ) - 0.02 } px` : false ;
195
213
}
196
214
197
- @HostListener ( '@showHide.start' , [ '$event' ] )
198
215
animateStart ( event : AnimationEvent ) {
199
216
if ( event . toState === 'visible' ) {
200
- if ( ! this . scroll ) {
217
+ if ( ! this . scroll ( ) ) {
201
218
this . #backdropService. hideScrollbar ( ) ;
202
219
}
203
220
this . #renderer. addClass ( this . #hostElement. nativeElement , 'showing' ) ;
@@ -206,7 +223,6 @@ export class OffcanvasComponent implements OnInit, OnDestroy {
206
223
}
207
224
}
208
225
209
- @HostListener ( '@showHide.done' , [ '$event' ] )
210
226
animateDone ( event : AnimationEvent ) {
211
227
setTimeout ( ( ) => {
212
228
if ( event . toState === 'visible' ) {
@@ -218,13 +234,12 @@ export class OffcanvasComponent implements OnInit, OnDestroy {
218
234
this . #renderer. removeStyle ( this . #document. body , 'paddingRight' ) ;
219
235
}
220
236
} ) ;
221
- this . show = this . visible ;
237
+ this . show = this . visible ( ) ;
222
238
}
223
239
224
- @HostListener ( 'document:keydown' , [ '$event' ] )
225
240
onKeyDownHandler ( event : KeyboardEvent ) : void {
226
- if ( event . key === 'Escape' && this . keyboard && this . visible && this . backdrop !== 'static' ) {
227
- this . #offcanvasService. toggle ( { show : false , id : this . id } ) ;
241
+ if ( event . key === 'Escape' && this . keyboard ( ) && this . visible ( ) && this . backdrop ( ) !== 'static' ) {
242
+ this . #offcanvasService. toggle ( { show : false , id : this . id ( ) } ) ;
228
243
}
229
244
}
230
245
@@ -237,7 +252,7 @@ export class OffcanvasComponent implements OnInit, OnDestroy {
237
252
}
238
253
239
254
ngOnDestroy ( ) : void {
240
- this . #offcanvasService. toggle ( { show : false , id : this . id } ) ;
255
+ this . #offcanvasService. toggle ( { show : false , id : this . id ( ) } ) ;
241
256
}
242
257
243
258
setFocus ( ) : void {
@@ -248,9 +263,9 @@ export class OffcanvasComponent implements OnInit, OnDestroy {
248
263
249
264
private stateToggleSubscribe ( ) : void {
250
265
this . #offcanvasService. offcanvasState$ . pipe ( takeUntilDestroyed ( this . #destroyRef) ) . subscribe ( ( action ) => {
251
- if ( this === action . offcanvas || this . id === action . id ) {
266
+ if ( this === action . offcanvas || this . id ( ) === action . id ) {
252
267
if ( 'show' in action ) {
253
- this . visible = action ?. show === 'toggle' ? ! this . visible : action . show ;
268
+ this . visible . update ( ( value ) => ( action ?. show === 'toggle' ? ! value : action . show ) ) ;
254
269
}
255
270
}
256
271
} ) ;
@@ -261,14 +276,14 @@ export class OffcanvasComponent implements OnInit, OnDestroy {
261
276
this . #backdropClickSubscription = this . #backdropService. backdropClick$
262
277
. pipe ( takeUntilDestroyed ( this . #destroyRef) )
263
278
. subscribe ( ( clicked ) => {
264
- this . #offcanvasService. toggle ( { show : ! clicked , id : this . id } ) ;
279
+ this . #offcanvasService. toggle ( { show : ! clicked , id : this . id ( ) } ) ;
265
280
} ) ;
266
281
} else {
267
282
this . #backdropClickSubscription?. unsubscribe ( ) ;
268
283
}
269
284
}
270
285
271
- private setBackdrop ( setBackdrop : boolean | 'static' ) : void {
286
+ protected setBackdrop ( setBackdrop : boolean | 'static' ) : void {
272
287
this . #activeBackdrop = ! ! setBackdrop
273
288
? this . #backdropService. setBackdrop ( 'offcanvas' )
274
289
: this . #backdropService. clearBackdrop ( this . #activeBackdrop) ;
@@ -291,7 +306,7 @@ export class OffcanvasComponent implements OnInit, OnDestroy {
291
306
takeUntilDestroyed ( this . #destroyRef)
292
307
)
293
308
. subscribe ( ( breakpointState : BreakpointState ) => {
294
- this . visible = breakpointState . matches ;
309
+ this . visible . set ( breakpointState . matches ) ;
295
310
} ) ;
296
311
} else {
297
312
this . #layoutChangeSubscription?. unsubscribe ( ) ;
0 commit comments