Skip to content

Commit 90ea7ef

Browse files
committed
feat: OutClick service and directive for sidebar component
1 parent 9c1886c commit 90ea7ef

File tree

5 files changed

+106
-19
lines changed

5 files changed

+106
-19
lines changed

projects/coreui-angular/src/lib/shared/layout/layout.directive.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import {Directive, ElementRef, HostListener, Inject, Input, OnInit, Renderer2} from '@angular/core';
1+
import {Directive, ElementRef, EventEmitter, HostListener, Inject, Input, OnInit, Output, Renderer2} from '@angular/core';
22
import {DOCUMENT} from '@angular/common';
33

44
import { asideMenuCssClasses } from '../classes';
55
import { ClassToggler } from '../toggle-classes';
66
import { SidebarService } from '../../sidebar/sidebar.service';
7+
import { OutClickService } from '../../sidebar/out-click.service';
78

89
/**
910
* Allows the sidebar to be toggled via click.
@@ -164,3 +165,25 @@ export class HtmlAttributesDirective implements OnInit {
164165
this.renderer.setAttribute(this.el.nativeElement, key, value );
165166
}
166167
}
168+
169+
/**
170+
* Detects click outside the element
171+
*/
172+
@Directive({ selector: '[cuiOutClick]' })
173+
export class OutClickDirective {
174+
175+
constructor(
176+
private elementRef: ElementRef,
177+
private outClickService: OutClickService
178+
) {}
179+
180+
@HostListener('document:click', ['$event'])
181+
public onDocumentClick($event: MouseEvent): void {
182+
const targetElement = $event.target as HTMLElement;
183+
184+
// Check if the click was outside the element
185+
if (targetElement && !this.elementRef.nativeElement.contains(targetElement)) {
186+
this.outClickService.onClick({event: $event});
187+
}
188+
}
189+
}

projects/coreui-angular/src/lib/shared/layout/layout.module.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import {
77
SidebarToggleDirective,
88
SidebarMinimizeDirective,
99
SidebarOffCanvasCloseDirective,
10-
HtmlAttributesDirective
10+
HtmlAttributesDirective,
11+
OutClickDirective
1112
} from './layout.directive';
1213
import { ClassToggler } from '../toggle-classes';
1314

@@ -22,7 +23,8 @@ import { ClassToggler } from '../toggle-classes';
2223
SidebarToggleDirective,
2324
SidebarMinimizeDirective,
2425
SidebarOffCanvasCloseDirective,
25-
HtmlAttributesDirective
26+
HtmlAttributesDirective,
27+
OutClickDirective
2628
],
2729
declarations: [
2830
AsideToggleDirective,
@@ -31,7 +33,8 @@ import { ClassToggler } from '../toggle-classes';
3133
SidebarToggleDirective,
3234
SidebarMinimizeDirective,
3335
SidebarOffCanvasCloseDirective,
34-
HtmlAttributesDirective
36+
HtmlAttributesDirective,
37+
OutClickDirective
3538
],
3639
providers: [
3740
ClassToggler
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { Injectable } from '@angular/core';
2+
import {BehaviorSubject} from 'rxjs';
3+
4+
@Injectable({
5+
providedIn: 'root'
6+
})
7+
export class OutClickService {
8+
9+
private outClick = new BehaviorSubject<any>({event: undefined});
10+
outClick$ = this.outClick.asObservable();
11+
12+
constructor() {}
13+
14+
onClick(message: any) {
15+
this.outClick.next(message);
16+
}
17+
}

projects/coreui-angular/src/lib/sidebar/sidebar.component.ts

Lines changed: 55 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Subscription } from 'rxjs';
55
import { sidebarCssClasses, validBreakpoints, checkBreakpoint } from '../shared';
66
import { ClassToggler } from '../shared/toggle-classes';
77
import { SidebarService } from './sidebar.service';
8+
import { OutClickService } from './out-click.service';
89

910
interface SidebarState {
1011
minimized: boolean;
@@ -14,7 +15,7 @@ interface SidebarState {
1415

1516
@Component({
1617
selector: 'cui-sidebar',
17-
template: `<ng-content></ng-content>`,
18+
template: `<ng-content></ng-content>`
1819
})
1920
export class SidebarComponent implements OnInit, OnDestroy {
2021
@Input() compact: boolean;
@@ -24,7 +25,8 @@ export class SidebarComponent implements OnInit, OnDestroy {
2425
@Input() opened: boolean;
2526
@Input() offCanvas: boolean;
2627

27-
private subscription: Subscription;
28+
private stateToggleSubscription: Subscription;
29+
private outClickSubscription: Subscription;
2830
private body: HTMLBodyElement;
2931

3032
state: SidebarState = {
@@ -38,7 +40,8 @@ export class SidebarComponent implements OnInit, OnDestroy {
3840
private renderer: Renderer2,
3941
private hostElement: ElementRef,
4042
private classToggler: ClassToggler,
41-
private sidebarService: SidebarService
43+
private sidebarService: SidebarService,
44+
private outClickService: OutClickService
4245
) {
4346
renderer.addClass(hostElement.nativeElement, 'sidebar');
4447
}
@@ -54,20 +57,15 @@ export class SidebarComponent implements OnInit, OnDestroy {
5457
this.isOpened(this.opened);
5558
this.setState();
5659

57-
this.subscription = this.sidebarService.sidebarState$.subscribe((state) => {
58-
if ('minimize' in state) {
59-
this.minimize(state.minimize);
60-
}
61-
if ('open' in state) {
62-
this.open(state);
63-
}
64-
});
60+
this.stateToggleSubscribe();
61+
this.outClickSubscribe();
6562
}
6663

6764
ngOnDestroy(): void {
6865
this.renderer.removeClass(this.body, 'sidebar-fixed' );
6966

70-
this.subscription.unsubscribe();
67+
this.stateToggleSubscribe(false);
68+
this.outClickSubscribe(false);
7169
}
7270

7371
isCompact(compact: boolean = this.compact): void {
@@ -98,8 +96,7 @@ export class SidebarComponent implements OnInit, OnDestroy {
9896

9997
displayBreakpoint(display: any = this.display): void {
10098
if (display !== false) {
101-
const cssClass = Boolean(display) && checkBreakpoint(display, validBreakpoints) ? `sidebar-${display}-show` : sidebarCssClasses[0];
102-
this.renderer.addClass(this.body, cssClass);
99+
this.sidebarService.toggle({open: true, breakpoint: display});
103100
}
104101
}
105102

@@ -120,14 +117,58 @@ export class SidebarComponent implements OnInit, OnDestroy {
120117
const cssClass = Boolean(state.breakpoint) && checkBreakpoint(state.breakpoint, validBreakpoints) ?
121118
`sidebar-${state.breakpoint}-show` :
122119
sidebarCssClasses[0];
120+
const mobile = cssClass === sidebarCssClasses[0];
123121
const opened = this.body.classList.contains( cssClass );
124122
const open = toggle ? !opened : state.open;
125123
if (open) {
126124
this.renderer.addClass(this.body, cssClass );
127125
} else {
128126
this.renderer.removeClass(this.body, cssClass );
129127
}
128+
if (mobile) {
129+
if (open && (!this.outClickSubscription || this.outClickSubscription.closed)) {
130+
this.outClickSubscribe();
131+
}
132+
if (!open && this.outClickSubscription && !this.outClickSubscription.closed) {
133+
this.outClickSubscribe(false);
134+
}
135+
}
130136
this.state.opened = <boolean>open;
131137
return <boolean>open;
132138
}
139+
140+
private stateToggleSubscribe(subscribe: Boolean = true) {
141+
if (subscribe) {
142+
this.stateToggleSubscription = this.sidebarService.sidebarState$.subscribe((state) => {
143+
if ('minimize' in state) {
144+
this.minimize(state.minimize);
145+
}
146+
if ('open' in state) {
147+
this.open(state);
148+
}
149+
});
150+
} else {
151+
this.stateToggleSubscription.unsubscribe();
152+
}
153+
}
154+
155+
private outClickSubscribe(subscribe: Boolean = true) {
156+
if (subscribe) {
157+
this.outClickSubscription = this.outClickService.outClick$.subscribe(message => {
158+
if (message.event) {
159+
this.hideMobile(message.event);
160+
}
161+
});
162+
} else {
163+
this.outClickSubscription.unsubscribe();
164+
}
165+
}
166+
167+
public hideMobile(e) {
168+
if (this.state.opened) {
169+
if (!e.target.closest('[cuisidebartoggle]')) {
170+
this.sidebarService.toggle({open: false, breakpoint: ''});
171+
}
172+
}
173+
}
133174
}

projects/coreui-angular/src/lib/sidebar/sidebar.module.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@ import { NgModule} from '@angular/core';
33
import { RouterModule } from '@angular/router';
44

55
import { LayoutModule } from '../shared/layout/layout.module';
6+
67
// CoreUI Sidebar Component
78
import { SidebarComponent } from './sidebar.component';
89
import { SidebarFooterComponent } from './sidebar-footer.component';
910
import { SidebarFormComponent } from './sidebar-form.component';
1011
import { SidebarHeaderComponent } from './sidebar-header.component';
1112
import { SidebarMinimizerComponent } from './sidebar-minimizer.component';
1213
import { SidebarService } from './sidebar.service';
14+
import { OutClickService } from './out-click.service';
1315

1416
// CoreUI SidebarNav Component
1517
import { NavDropdownDirective, NavDropdownToggleDirective } from './sidebar-nav.directive';
@@ -26,7 +28,7 @@ import { SidebarNavLabelComponent } from './sidebar-nav/sidebar-nav-label.compon
2628
imports: [
2729
CommonModule,
2830
RouterModule,
29-
LayoutModule
31+
LayoutModule,
3032
],
3133
exports: [
3234
SidebarFooterComponent,
@@ -61,6 +63,7 @@ import { SidebarNavLabelComponent } from './sidebar-nav/sidebar-nav-label.compon
6163
SidebarNavLabelComponent
6264
],
6365
providers: [
66+
OutClickService,
6467
SidebarService,
6568
SidebarNavHelper
6669
]

0 commit comments

Comments
 (0)