@@ -15,7 +15,9 @@ import {
15
15
import { DOCUMENT , isPlatformBrowser } from '@angular/common' ;
16
16
import { animate , AnimationEvent , state , style , transition , trigger } from '@angular/animations' ;
17
17
import { BooleanInput , coerceBooleanProperty } from '@angular/cdk/coercion' ;
18
+ import { BreakpointObserver , BreakpointState } from '@angular/cdk/layout' ;
18
19
import { Subscription } from 'rxjs' ;
20
+ import { filter } from 'rxjs/operators' ;
19
21
20
22
import { BackdropService } from '../../backdrop/backdrop.service' ;
21
23
import { OffcanvasService } from '../offcanvas.service' ;
@@ -55,7 +57,8 @@ export class OffcanvasComponent implements OnInit, OnDestroy {
55
57
private renderer : Renderer2 ,
56
58
private hostElement : ElementRef ,
57
59
private offcanvasService : OffcanvasService ,
58
- private backdropService : BackdropService
60
+ private backdropService : BackdropService ,
61
+ private breakpointObserver : BreakpointObserver
59
62
) { }
60
63
61
64
/**
@@ -79,20 +82,29 @@ export class OffcanvasComponent implements OnInit, OnDestroy {
79
82
*/
80
83
@Input ( ) placement : string | 'start' | 'end' | 'top' | 'bottom' = 'start' ;
81
84
85
+ /**
86
+ * Responsive offcanvas property hides content outside the viewport from a specified breakpoint and down.
87
+ * @type boolean | 'sm' | 'md' | 'lg' | 'xl' | 'xxl';
88
+ * @default true
89
+ * @since 4.3.10
90
+ */
91
+ @Input ( ) responsive ?: boolean | 'sm' | 'md' | 'lg' | 'xl' | 'xxl' = true ;
92
+
82
93
/**
83
94
* Allow body scrolling while offcanvas is visible.
84
95
* @type boolean
96
+ * @default false
85
97
*/
86
98
@Input ( )
87
99
set scroll ( value : boolean ) {
88
- this . _scroll = coerceBooleanProperty ( value ) ;
100
+ this . #scroll = coerceBooleanProperty ( value ) ;
89
101
}
90
102
91
103
get scroll ( ) {
92
- return this . _scroll ;
104
+ return this . #scroll ;
93
105
}
94
106
95
- private _scroll = false ;
107
+ #scroll = false ;
96
108
97
109
@Input ( ) id = `offcanvas-${ this . placement } -${ nextId ++ } ` ;
98
110
@@ -117,37 +129,40 @@ export class OffcanvasComponent implements OnInit, OnDestroy {
117
129
*/
118
130
@Input ( )
119
131
set visible ( value : boolean ) {
120
- this . _visible = coerceBooleanProperty ( value ) ;
121
- if ( this . _visible ) {
132
+ this . #visible = coerceBooleanProperty ( value ) ;
133
+ if ( this . #visible ) {
122
134
this . setBackdrop ( this . backdrop ) ;
123
135
this . setFocus ( ) ;
124
136
} else {
125
137
this . setBackdrop ( false ) ;
126
138
}
139
+ this . layoutChangeSubscribe ( this . #visible) ;
127
140
this . visibleChange . emit ( value ) ;
128
141
}
129
142
130
143
get visible ( ) : boolean {
131
- return this . _visible ;
144
+ return this . #visible ;
132
145
}
133
146
134
- private _visible : boolean = false ;
147
+ #visible : boolean = false ;
135
148
136
149
/**
137
150
* Event triggered on visible change.
138
151
*/
139
- @Output ( ) visibleChange = new EventEmitter < boolean > ( ) ;
152
+ @Output ( ) readonly visibleChange = new EventEmitter < boolean > ( ) ;
140
153
141
- private activeBackdrop ! : HTMLDivElement ;
142
- private scrollbarWidth ! : string ;
154
+ # activeBackdrop! : HTMLDivElement ;
155
+ # scrollbarWidth! : string ;
143
156
144
- private stateToggleSubscription ! : Subscription ;
145
- private backdropClickSubscription ! : Subscription ;
157
+ #stateToggleSubscription! : Subscription ;
158
+ #backdropClickSubscription! : Subscription ;
159
+ #layoutChangeSubscription! : Subscription ;
146
160
147
161
@HostBinding ( 'class' )
148
162
get hostClasses ( ) : any {
149
163
return {
150
- offcanvas : true ,
164
+ offcanvas : typeof this . responsive === 'boolean' ,
165
+ [ `offcanvas-${ this . responsive } ` ] : typeof this . responsive !== 'boolean' ,
151
166
[ `offcanvas-${ this . placement } ` ] : ! ! this . placement ,
152
167
show : this . show
153
168
} ;
@@ -169,18 +184,18 @@ export class OffcanvasComponent implements OnInit, OnDestroy {
169
184
}
170
185
171
186
get show ( ) : boolean {
172
- return this . visible && this . _show ;
187
+ return this . visible && this . #show ;
173
188
}
174
189
175
190
set show ( value : boolean ) {
176
- this . _show = value ;
191
+ this . #show = value ;
177
192
}
178
193
179
- private _show = false ;
194
+ #show = false ;
180
195
181
196
@HostListener ( '@showHide.start' , [ '$event' ] )
182
197
animateStart ( event : AnimationEvent ) {
183
- const scrollbarWidth = this . scrollbarWidth ;
198
+ const scrollbarWidth = this . # scrollbarWidth;
184
199
if ( event . toState === 'visible' ) {
185
200
if ( ! this . scroll ) {
186
201
this . renderer . setStyle ( this . document . body , 'overflow' , 'hidden' ) ;
@@ -220,7 +235,7 @@ export class OffcanvasComponent implements OnInit, OnDestroy {
220
235
}
221
236
222
237
ngOnInit ( ) : void {
223
- this . scrollbarWidth = this . backdropService . scrollbarWidth ;
238
+ this . # scrollbarWidth = this . backdropService . scrollbarWidth ;
224
239
this . stateToggleSubscribe ( ) ;
225
240
// hotfix to avoid end offcanvas flicker on first render
226
241
this . renderer . setStyle ( this . hostElement . nativeElement , 'display' , 'flex' ) ;
@@ -233,7 +248,7 @@ export class OffcanvasComponent implements OnInit, OnDestroy {
233
248
234
249
private stateToggleSubscribe ( subscribe : boolean = true ) : void {
235
250
if ( subscribe ) {
236
- this . stateToggleSubscription =
251
+ this . # stateToggleSubscription =
237
252
this . offcanvasService . offcanvasState$ . subscribe ( ( action ) => {
238
253
if ( this === action . offcanvas || this . id === action . id ) {
239
254
if ( 'show' in action ) {
@@ -243,25 +258,25 @@ export class OffcanvasComponent implements OnInit, OnDestroy {
243
258
}
244
259
} ) ;
245
260
} else {
246
- this . stateToggleSubscription . unsubscribe ( ) ;
261
+ this . # stateToggleSubscription. unsubscribe ( ) ;
247
262
}
248
263
}
249
264
250
265
private backdropClickSubscribe ( subscribe : boolean = true ) : void {
251
266
if ( subscribe ) {
252
- this . backdropClickSubscription =
267
+ this . # backdropClickSubscription =
253
268
this . backdropService . backdropClick$ . subscribe ( ( clicked ) => {
254
269
this . offcanvasService . toggle ( { show : ! clicked , id : this . id } ) ;
255
270
} ) ;
256
271
} else {
257
- this . backdropClickSubscription ?. unsubscribe ( ) ;
272
+ this . # backdropClickSubscription?. unsubscribe ( ) ;
258
273
}
259
274
}
260
275
261
276
private setBackdrop ( setBackdrop : boolean | 'static' ) : void {
262
- this . scrollbarWidth = this . backdropService . scrollbarWidth ;
263
- this . activeBackdrop = ! ! setBackdrop ? this . backdropService . setBackdrop ( 'offcanvas' )
264
- : this . backdropService . clearBackdrop ( this . activeBackdrop ) ;
277
+ this . # scrollbarWidth = this . backdropService . scrollbarWidth ;
278
+ this . # activeBackdrop = ! ! setBackdrop ? this . backdropService . setBackdrop ( 'offcanvas' )
279
+ : this . backdropService . clearBackdrop ( this . # activeBackdrop) ;
265
280
setBackdrop === true ? this . backdropClickSubscribe ( )
266
281
: this . backdropClickSubscribe ( false ) ;
267
282
}
@@ -271,4 +286,40 @@ export class OffcanvasComponent implements OnInit, OnDestroy {
271
286
setTimeout ( ( ) => this . hostElement . nativeElement . focus ( ) ) ;
272
287
}
273
288
}
289
+
290
+ get responsiveBreakpoint ( ) : string | false {
291
+ if ( typeof this . responsive !== 'string' ) {
292
+ return false ;
293
+ }
294
+ const element : Element = this . document . documentElement ;
295
+ const responsiveBreakpoint = this . responsive ;
296
+ const breakpointValue = getComputedStyle ( element ) . getPropertyValue ( `--cui-breakpoint-${ responsiveBreakpoint . trim ( ) } ` ) || false ;
297
+ return breakpointValue ? `${ parseFloat ( breakpointValue . trim ( ) ) - 0.02 } px` : false ;
298
+ }
299
+
300
+ private layoutChangeSubscribe ( subscribe : boolean = true ) : void {
301
+
302
+ if ( subscribe ) {
303
+
304
+ if ( ! this . responsiveBreakpoint ) {
305
+ return ;
306
+ }
307
+
308
+ const responsiveBreakpoint = `(max-width: ${ this . responsiveBreakpoint } )` ;
309
+
310
+ const layoutChanges = this . breakpointObserver . observe ( [ responsiveBreakpoint ] ) ;
311
+
312
+ this . #layoutChangeSubscription = layoutChanges
313
+ . pipe (
314
+ filter ( breakpointState => ! breakpointState . matches )
315
+ )
316
+ . subscribe (
317
+ ( breakpointState : BreakpointState ) => {
318
+ this . visible = breakpointState . matches ;
319
+ }
320
+ ) ;
321
+ } else {
322
+ this . #layoutChangeSubscription?. unsubscribe ( ) ;
323
+ }
324
+ }
274
325
}
0 commit comments