@@ -15,7 +15,9 @@ import {
1515import { DOCUMENT , isPlatformBrowser } from '@angular/common' ;
1616import { animate , AnimationEvent , state , style , transition , trigger } from '@angular/animations' ;
1717import { BooleanInput , coerceBooleanProperty } from '@angular/cdk/coercion' ;
18+ import { BreakpointObserver , BreakpointState } from '@angular/cdk/layout' ;
1819import { Subscription } from 'rxjs' ;
20+ import { filter } from 'rxjs/operators' ;
1921
2022import { BackdropService } from '../../backdrop/backdrop.service' ;
2123import { OffcanvasService } from '../offcanvas.service' ;
@@ -55,7 +57,8 @@ export class OffcanvasComponent implements OnInit, OnDestroy {
5557 private renderer : Renderer2 ,
5658 private hostElement : ElementRef ,
5759 private offcanvasService : OffcanvasService ,
58- private backdropService : BackdropService
60+ private backdropService : BackdropService ,
61+ private breakpointObserver : BreakpointObserver
5962 ) { }
6063
6164 /**
@@ -79,20 +82,29 @@ export class OffcanvasComponent implements OnInit, OnDestroy {
7982 */
8083 @Input ( ) placement : string | 'start' | 'end' | 'top' | 'bottom' = 'start' ;
8184
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+
8293 /**
8394 * Allow body scrolling while offcanvas is visible.
8495 * @type boolean
96+ * @default false
8597 */
8698 @Input ( )
8799 set scroll ( value : boolean ) {
88- this . _scroll = coerceBooleanProperty ( value ) ;
100+ this . #scroll = coerceBooleanProperty ( value ) ;
89101 }
90102
91103 get scroll ( ) {
92- return this . _scroll ;
104+ return this . #scroll ;
93105 }
94106
95- private _scroll = false ;
107+ #scroll = false ;
96108
97109 @Input ( ) id = `offcanvas-${ this . placement } -${ nextId ++ } ` ;
98110
@@ -117,37 +129,40 @@ export class OffcanvasComponent implements OnInit, OnDestroy {
117129 */
118130 @Input ( )
119131 set visible ( value : boolean ) {
120- this . _visible = coerceBooleanProperty ( value ) ;
121- if ( this . _visible ) {
132+ this . #visible = coerceBooleanProperty ( value ) ;
133+ if ( this . #visible ) {
122134 this . setBackdrop ( this . backdrop ) ;
123135 this . setFocus ( ) ;
124136 } else {
125137 this . setBackdrop ( false ) ;
126138 }
139+ this . layoutChangeSubscribe ( this . #visible) ;
127140 this . visibleChange . emit ( value ) ;
128141 }
129142
130143 get visible ( ) : boolean {
131- return this . _visible ;
144+ return this . #visible ;
132145 }
133146
134- private _visible : boolean = false ;
147+ #visible : boolean = false ;
135148
136149 /**
137150 * Event triggered on visible change.
138151 */
139- @Output ( ) visibleChange = new EventEmitter < boolean > ( ) ;
152+ @Output ( ) readonly visibleChange = new EventEmitter < boolean > ( ) ;
140153
141- private activeBackdrop ! : HTMLDivElement ;
142- private scrollbarWidth ! : string ;
154+ # activeBackdrop! : HTMLDivElement ;
155+ # scrollbarWidth! : string ;
143156
144- private stateToggleSubscription ! : Subscription ;
145- private backdropClickSubscription ! : Subscription ;
157+ #stateToggleSubscription! : Subscription ;
158+ #backdropClickSubscription! : Subscription ;
159+ #layoutChangeSubscription! : Subscription ;
146160
147161 @HostBinding ( 'class' )
148162 get hostClasses ( ) : any {
149163 return {
150- offcanvas : true ,
164+ offcanvas : typeof this . responsive === 'boolean' ,
165+ [ `offcanvas-${ this . responsive } ` ] : typeof this . responsive !== 'boolean' ,
151166 [ `offcanvas-${ this . placement } ` ] : ! ! this . placement ,
152167 show : this . show
153168 } ;
@@ -169,18 +184,18 @@ export class OffcanvasComponent implements OnInit, OnDestroy {
169184 }
170185
171186 get show ( ) : boolean {
172- return this . visible && this . _show ;
187+ return this . visible && this . #show ;
173188 }
174189
175190 set show ( value : boolean ) {
176- this . _show = value ;
191+ this . #show = value ;
177192 }
178193
179- private _show = false ;
194+ #show = false ;
180195
181196 @HostListener ( '@showHide.start' , [ '$event' ] )
182197 animateStart ( event : AnimationEvent ) {
183- const scrollbarWidth = this . scrollbarWidth ;
198+ const scrollbarWidth = this . # scrollbarWidth;
184199 if ( event . toState === 'visible' ) {
185200 if ( ! this . scroll ) {
186201 this . renderer . setStyle ( this . document . body , 'overflow' , 'hidden' ) ;
@@ -220,7 +235,7 @@ export class OffcanvasComponent implements OnInit, OnDestroy {
220235 }
221236
222237 ngOnInit ( ) : void {
223- this . scrollbarWidth = this . backdropService . scrollbarWidth ;
238+ this . # scrollbarWidth = this . backdropService . scrollbarWidth ;
224239 this . stateToggleSubscribe ( ) ;
225240 // hotfix to avoid end offcanvas flicker on first render
226241 this . renderer . setStyle ( this . hostElement . nativeElement , 'display' , 'flex' ) ;
@@ -233,7 +248,7 @@ export class OffcanvasComponent implements OnInit, OnDestroy {
233248
234249 private stateToggleSubscribe ( subscribe : boolean = true ) : void {
235250 if ( subscribe ) {
236- this . stateToggleSubscription =
251+ this . # stateToggleSubscription =
237252 this . offcanvasService . offcanvasState$ . subscribe ( ( action ) => {
238253 if ( this === action . offcanvas || this . id === action . id ) {
239254 if ( 'show' in action ) {
@@ -243,25 +258,25 @@ export class OffcanvasComponent implements OnInit, OnDestroy {
243258 }
244259 } ) ;
245260 } else {
246- this . stateToggleSubscription . unsubscribe ( ) ;
261+ this . # stateToggleSubscription. unsubscribe ( ) ;
247262 }
248263 }
249264
250265 private backdropClickSubscribe ( subscribe : boolean = true ) : void {
251266 if ( subscribe ) {
252- this . backdropClickSubscription =
267+ this . # backdropClickSubscription =
253268 this . backdropService . backdropClick$ . subscribe ( ( clicked ) => {
254269 this . offcanvasService . toggle ( { show : ! clicked , id : this . id } ) ;
255270 } ) ;
256271 } else {
257- this . backdropClickSubscription ?. unsubscribe ( ) ;
272+ this . # backdropClickSubscription?. unsubscribe ( ) ;
258273 }
259274 }
260275
261276 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) ;
265280 setBackdrop === true ? this . backdropClickSubscribe ( )
266281 : this . backdropClickSubscribe ( false ) ;
267282 }
@@ -271,4 +286,40 @@ export class OffcanvasComponent implements OnInit, OnDestroy {
271286 setTimeout ( ( ) => this . hostElement . nativeElement . focus ( ) ) ;
272287 }
273288 }
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+ }
274325}
0 commit comments