Skip to content

Commit

Permalink
fix(cdk/overlay): not emitting for auxiliar button clicks (#22616)
Browse files Browse the repository at this point in the history
Similar to #21397. Fixes that the `OverlayOutsideClickDispatcher` doesn't emit when the user presses outside using the middle or right mouse button.

(cherry picked from commit 65358a9)
  • Loading branch information
crisbeto authored and mmalerba committed May 7, 2021
1 parent 52b0b8c commit ba89398
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 32 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {TestBed, inject, fakeAsync} from '@angular/core/testing';
import {Component, NgModule} from '@angular/core';
import {dispatchMouseEvent} from '@angular/cdk/testing/private';
import {dispatchFakeEvent, dispatchMouseEvent} from '@angular/cdk/testing/private';
import {OverlayModule, OverlayContainer, Overlay} from '../index';
import {OverlayOutsideClickDispatcher} from './overlay-outside-click-dispatcher';
import {ComponentPortal} from '@angular/cdk/portal';
Expand Down Expand Up @@ -51,9 +51,7 @@ describe('OverlayOutsideClickDispatcher', () => {
overlayTwo.dispose();
});

it(
'should dispatch mouse click events to the attached overlays',
() => {
it('should dispatch mouse click events to the attached overlays', () => {
const overlayOne = overlay.create();
overlayOne.attach(new ComponentPortal(TestComponent));
const overlayTwo = overlay.create();
Expand All @@ -80,27 +78,53 @@ describe('OverlayOutsideClickDispatcher', () => {
overlayTwo.dispose();
});

it(
'should dispatch mouse click events to the attached overlays even when propagation is stopped',
() => {
const overlayRef = overlay.create();
overlayRef.attach(new ComponentPortal(TestComponent));
const spy = jasmine.createSpy('overlay mouse click event spy');
overlayRef.outsidePointerEvents().subscribe(spy);
it('should dispatch auxiliary button click events to the attached overlays', () => {
const overlayOne = overlay.create();
overlayOne.attach(new ComponentPortal(TestComponent));
const overlayTwo = overlay.create();
overlayTwo.attach(new ComponentPortal(TestComponent));

outsideClickDispatcher.add(overlayRef);
const overlayOneSpy = jasmine.createSpy('overlayOne auxiliary click event spy');
const overlayTwoSpy = jasmine.createSpy('overlayTwo auxiliary click event spy');

overlayOne.outsidePointerEvents().subscribe(overlayOneSpy);
overlayTwo.outsidePointerEvents().subscribe(overlayTwoSpy);

outsideClickDispatcher.add(overlayOne);
outsideClickDispatcher.add(overlayTwo);

const button = document.createElement('button');
document.body.appendChild(button);
button.addEventListener('click', event => event.stopPropagation());
button.click();
dispatchFakeEvent(button, 'auxclick');

expect(spy).toHaveBeenCalled();
expect(overlayOneSpy).toHaveBeenCalled();
expect(overlayTwoSpy).toHaveBeenCalled();

button.parentNode!.removeChild(button);
overlayRef.dispose();
overlayOne.dispose();
overlayTwo.dispose();
});

it('should dispatch mouse click events to the attached overlays even when propagation is stopped',
() => {
const overlayRef = overlay.create();
overlayRef.attach(new ComponentPortal(TestComponent));
const spy = jasmine.createSpy('overlay mouse click event spy');
overlayRef.outsidePointerEvents().subscribe(spy);

outsideClickDispatcher.add(overlayRef);

const button = document.createElement('button');
document.body.appendChild(button);
button.addEventListener('click', event => event.stopPropagation());
button.click();

expect(spy).toHaveBeenCalled();

button.parentNode!.removeChild(button);
overlayRef.dispose();
});

it('should dispose of the global click event handler correctly', () => {
const overlayRef = overlay.create();
const body = document.body;
Expand Down Expand Up @@ -191,10 +215,8 @@ describe('OverlayOutsideClickDispatcher', () => {
overlayRef.dispose();
});

it(
'should not throw an error when when closing out related components via the' +
' outsidePointerEvents emitter on background click',
fakeAsync(() => {
it('should not throw an error when when closing out related components via the ' +
'outsidePointerEvents emitter on background click', fakeAsync(() => {
const firstOverlayRef = overlay.create();
firstOverlayRef.attach(new ComponentPortal(TestComponent));
const secondOverlayRef = overlay.create();
Expand Down
26 changes: 14 additions & 12 deletions src/cdk/overlay/dispatchers/overlay-outside-click-dispatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,23 +30,23 @@ export class OverlayOutsideClickDispatcher extends BaseOverlayDispatcher {
add(overlayRef: OverlayReference): void {
super.add(overlayRef);

// tslint:disable: max-line-length
// Safari on iOS does not generate click events for non-interactive
// elements. However, we want to receive a click for any element outside
// the overlay. We can force a "clickable" state by setting
// `cursor: pointer` on the document body.
// See https://developer.mozilla.org/en-US/docs/Web/API/Element/click_event#Safari_Mobile
// and https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html
// tslint:enable: max-line-length
// `cursor: pointer` on the document body. See:
// https://developer.mozilla.org/en-US/docs/Web/API/Element/click_event#Safari_Mobile
// https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html
if (!this._isAttached) {
this._document.body.addEventListener('click', this._clickListener, true);
this._document.body.addEventListener('contextmenu', this._clickListener, true);
const body = this._document.body;
body.addEventListener('click', this._clickListener, true);
body.addEventListener('auxclick', this._clickListener, true);
body.addEventListener('contextmenu', this._clickListener, true);

// click event is not fired on iOS. To make element "clickable" we are
// setting the cursor to pointer
if (this._platform.IOS && !this._cursorStyleIsSet) {
this._cursorOriginalValue = this._document.body.style.cursor;
this._document.body.style.cursor = 'pointer';
this._cursorOriginalValue = body.style.cursor;
body.style.cursor = 'pointer';
this._cursorStyleIsSet = true;
}

Expand All @@ -57,10 +57,12 @@ export class OverlayOutsideClickDispatcher extends BaseOverlayDispatcher {
/** Detaches the global keyboard event listener. */
protected detach() {
if (this._isAttached) {
this._document.body.removeEventListener('click', this._clickListener, true);
this._document.body.removeEventListener('contextmenu', this._clickListener, true);
const body = this._document.body;
body.removeEventListener('click', this._clickListener, true);
body.removeEventListener('auxclick', this._clickListener, true);
body.removeEventListener('contextmenu', this._clickListener, true);
if (this._platform.IOS && this._cursorStyleIsSet) {
this._document.body.style.cursor = this._cursorOriginalValue;
body.style.cursor = this._cursorOriginalValue;
this._cursorStyleIsSet = false;
}
this._isAttached = false;
Expand Down

0 comments on commit ba89398

Please sign in to comment.