Skip to content

Commit

Permalink
fix(dropdown): fix dropdown inside click (#4609)
Browse files Browse the repository at this point in the history
fixes #1933
  • Loading branch information
svetoldo4444ka authored and valorkin committed Dec 5, 2018
1 parent 0e806e1 commit 75f7105
Show file tree
Hide file tree
Showing 10 changed files with 323 additions and 297 deletions.
4 changes: 3 additions & 1 deletion demo/src/app/components/+dropdown/demos/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { DemoDropdownAutoCloseComponent } from './autoclose/autoclose';
import { DemoDropdownCustomHtmlComponent } from './custom-html/custom-html';
import { DemoAccessibilityComponent } from './accessibility/accessibility';
import { DemoDropdownByIsOpenPropComponent } from './trigger-by-isopen-property/trigger-by-isopen-property';
import { DemoDropdownInsideClickComponent } from './inside-click/inside-click';

export const DEMO_COMPONENTS = [
DemoDropdownBasicComponent,
Expand All @@ -37,5 +38,6 @@ export const DEMO_COMPONENTS = [
DemoDropdownStateChangeEventComponent,
DemoDropdownAutoCloseComponent,
DemoDropdownCustomHtmlComponent,
DemoAccessibilityComponent
DemoAccessibilityComponent,
DemoDropdownInsideClickComponent
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<div class="btn-group" dropdown [insideClick]="true">
<button dropdownToggle type="button" class="btn btn-primary dropdown-toggle">
Button dropdown <span class="caret"></span>
</button>
<ul *dropdownMenu class="dropdown-menu" role="menu">
<li role="menuitem"><a class="dropdown-item" href="#">Action</a></li>
<li role="menuitem"><a class="dropdown-item" href="#">Another action</a></li>
<li role="menuitem"><a class="dropdown-item" href="#">Something else here</a></li>
<li class="divider dropdown-divider"></li>
<li role="menuitem"><a class="dropdown-item" href="#">Separated link</a>
</li>
</ul>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Component } from '@angular/core';

@Component({
selector: 'demo-dropdown-inside-click',
templateUrl: './inside-click.html'
})
export class DemoDropdownInsideClickComponent {}
10 changes: 10 additions & 0 deletions demo/src/app/components/+dropdown/dropdown-section.list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { DemoDropdownStateChangeEventComponent } from './demos/state-change-even
import { DemoDropdownAutoCloseComponent } from './demos/autoclose/autoclose';
import { DemoDropdownCustomHtmlComponent } from './demos/custom-html/custom-html';
import { DemoAccessibilityComponent } from './demos/accessibility/accessibility';
import { DemoDropdownInsideClickComponent } from './demos/inside-click/inside-click';

import { ContentSection } from '../../docs/models/content-section.model';
import { DemoTopSectionComponent } from '../../docs/demo-section-components/demo-top-section/index';
Expand Down Expand Up @@ -120,6 +121,15 @@ export const demoComponentContent: ContentSection[] = [
to right align the dropdown menu.</p>`,
outlet: DemoDropdownAlignmentComponent
},
{
title: 'Inside click',
anchor: 'inside-click',
component: require('!!raw-loader?lang=typescript!./demos/inside-click/inside-click.ts'),
html: require('!!raw-loader?lang=markup!./demos/inside-click/inside-click.html'),
description: `<p>By default, a dropdown menu closes on document click, even if you clicked on an element inside the dropdown.
Use <code>[insideClick]="true"</code> to allow click inside the dropdown</p>`,
outlet: DemoDropdownInsideClickComponent
},
{
title: 'Nested dropdowns (experimental)',
anchor: 'nested-dropdowns',
Expand Down
9 changes: 7 additions & 2 deletions src/dropdown/bs-dropdown-container.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ export class BsDropdownContainerComponent implements OnDestroy {
private _state: BsDropdownState,
private cd: ChangeDetectorRef,
private _renderer: Renderer2,
_element: ElementRef
private _element: ElementRef
) {
this._subscription = _state.isOpenChange.subscribe((value: boolean) => {
this.isOpen = value;
const dropdown = _element.nativeElement.querySelector('.dropdown-menu');
const dropdown = this._element.nativeElement.querySelector('.dropdown-menu');
if (dropdown && !isBs3()) {
this._renderer.addClass(dropdown, 'show');
if (dropdown.classList.contains('dropdown-menu-right')) {
Expand All @@ -61,6 +61,11 @@ export class BsDropdownContainerComponent implements OnDestroy {
});
}

/** @internal */
_contains(el: Element): boolean {
return this._element.nativeElement.contains(el);
}

ngOnDestroy(): void {
this._subscription.unsubscribe();
}
Expand Down
6 changes: 4 additions & 2 deletions src/dropdown/bs-dropdown-toggle.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
import { Subscription } from 'rxjs';

import { BsDropdownState } from './bs-dropdown.state';
import { BsDropdownDirective } from './bs-dropdown.directive';

@Directive({
selector: '[bsDropdownToggle],[dropdownToggle]',
Expand All @@ -24,7 +25,7 @@ export class BsDropdownToggleDirective implements OnDestroy {

private _subscriptions: Subscription[] = [];

constructor(private _state: BsDropdownState, private _element: ElementRef) {
constructor(private _state: BsDropdownState, private _element: ElementRef, private dropdown: BsDropdownDirective) {
// sync is open value with state
this._subscriptions.push(
this._state.isOpenChange.subscribe(
Expand Down Expand Up @@ -52,7 +53,8 @@ export class BsDropdownToggleDirective implements OnDestroy {
if (
this._state.autoClose &&
event.button !== 2 &&
!this._element.nativeElement.contains(event.target)
!this._element.nativeElement.contains(event.target) &&
!(this.dropdown.insideClick && this.dropdown._contains(event))
) {
this._state.toggleClick.emit(false);
}
Expand Down
19 changes: 15 additions & 4 deletions src/dropdown/bs-dropdown.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ export class BsDropdownDirective implements OnInit, OnDestroy {
return this._state.autoClose;
}

/**
* This attribute indicates that the dropdown shouldn't close on inside click when autoClose is set to true
*/
@Input() insideClick: boolean;

/**
* Disables dropdown toggle and hides dropdown menu if opened
*/
Expand Down Expand Up @@ -120,17 +125,17 @@ export class BsDropdownDirective implements OnInit, OnDestroy {
return !isBs3();
}

// todo: move to component loader
private _isInlineOpen = false;
private _dropdown: ComponentLoader<BsDropdownContainerComponent>;

private get _showInline(): boolean {
return !this.container;
}

private _inlinedMenu: EmbeddedViewRef<BsDropdownMenuDirective>;
// todo: move to component loader
private _isInlineOpen = false;

private _inlinedMenu: EmbeddedViewRef<BsDropdownMenuDirective>;
private _isDisabled: boolean;
private _dropdown: ComponentLoader<BsDropdownContainerComponent>;
private _subscriptions: Subscription[] = [];
private _isInited = false;

Expand Down Expand Up @@ -280,6 +285,12 @@ export class BsDropdownDirective implements OnInit, OnDestroy {
return this.show();
}

/** @internal */
_contains(event: any): boolean {
return this._elementRef.nativeElement.contains(event.target) ||
(this._dropdown.instance && this._dropdown.instance._contains(event.target));
}

ngOnDestroy(): void {
// clean up subscriptions and destroy dropdown
for (const sub of this._subscriptions) {
Expand Down
2 changes: 1 addition & 1 deletion src/spec/accordion.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ describe('Component: Accordion', () => {

it('should have the appropriate heading', () => {
const titles = Array.from(
element.querySelectorAll('.panel-heading .accordion-toggle div')
element.querySelectorAll('.panel-heading .accordion-toggle button')
);
titles.forEach((title: HTMLElement, idx: number) => {
const expectedTitle = `Panel ${idx + 1}`;
Expand Down
45 changes: 45 additions & 0 deletions src/spec/bs-dropdown-container.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { BsDropdownContainerComponent, BsDropdownModule, BsDropdownState } from '../dropdown';
import { Subject } from 'rxjs';
import { window } from '../utils';

describe('BsDropdownContainerComponent tests', () => {
let fixture: ComponentFixture<BsDropdownContainerComponent>;
let component: BsDropdownContainerComponent;
/* tslint:disable-next-line:no-inferred-empty-object-type */
const stateSubject = new Subject();
let fakeService;

beforeEach(() => {
fakeService = {
isOpenChange: stateSubject.asObservable()
};
TestBed.configureTestingModule({
imports: [BsDropdownModule.forRoot()],
providers: [{ provide: BsDropdownState, useValue: fakeService }]
});
});

beforeEach(() => {
fixture = TestBed.createComponent(BsDropdownContainerComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should not be null', () => {
expect(component).not.toBeNull();
});

it('should be call isOpenChange method', () => {
const tempVal = window.__theme;
window.__theme = 'bs4';
const spy = spyOn((component as any).cd, 'detectChanges');

stateSubject.next(true);
fixture.detectChanges();

expect(spy).toHaveBeenCalled();
window.__theme = tempVal;
});
});
Loading

0 comments on commit 75f7105

Please sign in to comment.