Skip to content

Commit

Permalink
fix(autoclose): fix popup automatic closing on touch on iOS (#3004)
Browse files Browse the repository at this point in the history
Fixes #2995
  • Loading branch information
maxokorokov committed Feb 11, 2019
1 parent 083fb0f commit bc489ec
Showing 1 changed file with 20 additions and 14 deletions.
34 changes: 20 additions & 14 deletions src/util/autoclose.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import {Injectable, NgZone, Inject} from '@angular/core';
import {Inject, Injectable, NgZone} from '@angular/core';
import {fromEvent, Observable, race} from 'rxjs';
import {DOCUMENT} from '@angular/common';
import {takeUntil, filter, delay, withLatestFrom, map} from 'rxjs/operators';
import {delay, filter, map, takeUntil, withLatestFrom} from 'rxjs/operators';
import {Key} from './key';

const isHTMLElementContainedIn = (element: HTMLElement, array?: HTMLElement[]) =>
array ? array.some(item => item.contains(element)) : false;

// we'll have to use 'touch' events instead of 'mouse' events on iOS and add a more significant delay
// to avoid re-opening when handling (click) on a toggling element
const iOS = !!navigator.userAgent && /iPad|iPhone|iPod/.test(navigator.userAgent);

@Injectable({providedIn: 'root'})
export class AutoClose {
constructor(private _ngZone: NgZone, @Inject(DOCUMENT) private _document: any) {}
Expand All @@ -18,10 +22,12 @@ export class AutoClose {
if (autoClose) {
this._ngZone.runOutsideAngular(() => {

const shouldCloseOnClick = (event: MouseEvent) => {
const shouldCloseOnClick = (event: MouseEvent | TouchEvent) => {
const element = event.target as HTMLElement;
if (event.button === 2 || isHTMLElementContainedIn(element, ignoreElements)) {
return false;
if (event instanceof MouseEvent) {
if (event.button === 2 || isHTMLElementContainedIn(element, ignoreElements)) {
return false;
}
}
if (autoClose === 'inside') {
return isHTMLElementContainedIn(element, insideElements);
Expand All @@ -39,18 +45,18 @@ export class AutoClose {
filter(e => e.which === Key.Escape));


// we have to pre-calculate 'shouldCloseOnClick' on 'mousedown',
// because on 'mouseup' DOM nodes might be detached
const mouseDowns$ =
fromEvent<MouseEvent>(this._document, 'mousedown').pipe(map(shouldCloseOnClick), takeUntil(closed$));
// we have to pre-calculate 'shouldCloseOnClick' on 'mousedown/touchstart',
// because on 'mouseup/touchend' DOM nodes might be detached
const mouseDowns$ = fromEvent<MouseEvent>(this._document, iOS ? 'touchstart' : 'mousedown')
.pipe(map(shouldCloseOnClick), takeUntil(closed$));

const outsideClicks$ = fromEvent<MouseEvent>(this._document, 'mouseup')
.pipe(
withLatestFrom(mouseDowns$), filter(([_, shouldClose]) => shouldClose), delay(0),
takeUntil(closed$));
const closeableClicks$ = fromEvent<MouseEvent>(this._document, iOS ? 'touchend' : 'mouseup')
.pipe(
withLatestFrom(mouseDowns$), filter(([_, shouldClose]) => shouldClose),
delay(iOS ? 16 : 0), takeUntil(closed$));


race<Event>([escapes$, outsideClicks$]).subscribe(() => this._ngZone.run(close));
race<Event>([escapes$, closeableClicks$]).subscribe(() => this._ngZone.run(close));
});
}
}
Expand Down

0 comments on commit bc489ec

Please sign in to comment.