Skip to content

Commit 8585427

Browse files
manucorporatadamdbradley
authored andcommitted
fix(menu): open/close race condition
fixes #7629 #8001
1 parent 6506cd5 commit 8585427

File tree

4 files changed

+107
-93
lines changed

4 files changed

+107
-93
lines changed

src/components/menu/menu-gestures.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export class MenuContentGesture extends SlideEdgeGesture {
2424

2525
canStart(ev: any): boolean {
2626
let menu = this.menu;
27-
if (!menu.enabled || !menu.swipeEnabled) {
27+
if (!menu.canSwipe()) {
2828
return false;
2929
}
3030
if (menu.isOpen) {
@@ -36,15 +36,23 @@ export class MenuContentGesture extends SlideEdgeGesture {
3636
}
3737

3838
// Set CSS, then wait one frame for it to apply before sliding starts
39-
onSlideBeforeStart(slide: SlideData, ev: any) {
39+
onSlideBeforeStart(ev: any) {
4040
console.debug('menu gesture, onSlideBeforeStart', this.menu.side);
4141
this.menu.swipeStart();
4242
}
4343

4444
onSlide(slide: SlideData, ev: any) {
4545
let z = (this.menu.side === 'right' ? slide.min : slide.max);
4646
let stepValue = (slide.distance / z);
47-
console.debug('menu gesture, onSlide', this.menu.side, 'distance', slide.distance, 'min', slide.min, 'max', slide.max, 'z', z, 'stepValue', stepValue);
47+
48+
console.debug(
49+
'menu gesture, onSlide', this.menu.side,
50+
'distance', slide.distance,
51+
'min', slide.min,
52+
'max', slide.max,
53+
'z', z,
54+
'stepValue', stepValue);
55+
4856
ev.preventDefault();
4957
this.menu.swipeProgress(stepValue);
5058
}
@@ -70,28 +78,25 @@ export class MenuContentGesture extends SlideEdgeGesture {
7078
'shouldCompleteLeft', shouldCompleteLeft,
7179
'shouldCompleteRight', shouldCompleteRight,
7280
'currentStepValue', currentStepValue);
81+
7382
this.menu.swipeEnd(shouldCompleteLeft, shouldCompleteRight, currentStepValue);
7483
}
7584

7685
getElementStartPos(slide: SlideData, ev: any) {
7786
if (this.menu.side === 'right') {
78-
// right menu
7987
return this.menu.isOpen ? slide.min : slide.max;
8088
}
81-
8289
// left menu
8390
return this.menu.isOpen ? slide.max : slide.min;
8491
}
8592

8693
getSlideBoundaries(): {min: number, max: number} {
8794
if (this.menu.side === 'right') {
88-
// right menu
8995
return {
9096
min: -this.menu.width(),
9197
max: 0
9298
};
9399
}
94-
95100
// left menu
96101
return {
97102
min: 0,

src/components/menu/menu.ts

Lines changed: 66 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -186,16 +186,17 @@ import { GestureController } from '../../gestures/gesture-controller';
186186
encapsulation: ViewEncapsulation.None,
187187
})
188188
export class Menu {
189-
private _preventTime: number = 0;
190189
private _cntEle: HTMLElement;
191190
private _cntGesture: MenuContentGesture;
192191
private _type: MenuType;
193192
private _resizeUnreg: Function;
194193
private _isEnabled: boolean = true;
195194
private _isSwipeEnabled: boolean = true;
195+
private _isAnimating: boolean = false;
196196
private _isPers: boolean = false;
197197
private _init: boolean = false;
198198

199+
199200
/**
200201
* @private
201202
*/
@@ -378,21 +379,20 @@ export class Menu {
378379
* @private
379380
*/
380381
private _setListeners() {
381-
let self = this;
382-
383-
if (self._init) {
384-
// only listen/unlisten if the menu has initialized
382+
if (!this._init) {
383+
return;
384+
}
385385

386-
if (self._isEnabled && self._isSwipeEnabled && !self._cntGesture.isListening) {
387-
// should listen, but is not currently listening
388-
console.debug('menu, gesture listen', self.side);
389-
self._cntGesture.listen();
386+
// only listen/unlisten if the menu has initialized
387+
if (this._isEnabled && this._isSwipeEnabled && !this._cntGesture.isListening) {
388+
// should listen, but is not currently listening
389+
console.debug('menu, gesture listen', this.side);
390+
this._cntGesture.listen();
390391

391-
} else if (self._cntGesture.isListening && (!self._isEnabled || !self._isSwipeEnabled)) {
392-
// should not listen, but is currently listening
393-
console.debug('menu, gesture unlisten', self.side);
394-
self._cntGesture.unlisten();
395-
}
392+
} else if (this._cntGesture.isListening && (!this._isEnabled || !this._isSwipeEnabled)) {
393+
// should not listen, but is currently listening
394+
console.debug('menu, gesture unlisten', this.side);
395+
this._cntGesture.unlisten();
396396
}
397397
}
398398

@@ -416,7 +416,7 @@ export class Menu {
416416
setOpen(shouldOpen: boolean, animated: boolean = true): Promise<boolean> {
417417
// _isPrevented is used to prevent unwanted opening/closing after swiping open/close
418418
// or swiping open the menu while pressing down on the MenuToggle button
419-
if ((shouldOpen && this.isOpen) || this._isPrevented()) {
419+
if ((shouldOpen && this.isOpen) || !this._isEnabled || this._isAnimating) {
420420
return Promise.resolve(this.isOpen);
421421
}
422422

@@ -430,12 +430,20 @@ export class Menu {
430430
});
431431
}
432432

433+
434+
/**
435+
* @private
436+
*/
437+
canSwipe(): boolean {
438+
return this._isEnabled && this._isSwipeEnabled && !this._isAnimating;
439+
}
440+
433441
/**
434442
* @private
435443
*/
436444
swipeStart() {
437445
// user started swiping the menu open/close
438-
if (this._isEnabled && this._isSwipeEnabled && !this._isPrevented()) {
446+
if (this.canSwipe()) {
439447
this._before();
440448
this._getType().setProgressStart(this.isOpen);
441449
}
@@ -446,84 +454,67 @@ export class Menu {
446454
*/
447455
swipeProgress(stepValue: number) {
448456
// user actively dragging the menu
449-
if (this._isEnabled && this._isSwipeEnabled) {
450-
this._prevent();
451-
this._getType().setProgessStep(stepValue);
452-
this.ionDrag.emit(stepValue);
457+
if (!this._isAnimating) {
458+
return;
453459
}
460+
this._getType().setProgessStep(stepValue);
461+
this.ionDrag.emit(stepValue);
454462
}
455463

456464
/**
457465
* @private
458466
*/
459467
swipeEnd(shouldCompleteLeft: boolean, shouldCompleteRight: boolean, stepValue: number) {
468+
if (!this._isAnimating) {
469+
return;
470+
}
460471
// user has finished dragging the menu
461-
if (this._isEnabled && this._isSwipeEnabled) {
462-
this._prevent();
463-
464-
let opening = !this.isOpen;
465-
let shouldComplete = false;
466-
if (opening) {
467-
shouldComplete = (this.side === 'right') ? shouldCompleteLeft : shouldCompleteRight;
468-
} else {
469-
shouldComplete = (this.side === 'right') ? shouldCompleteRight : shouldCompleteLeft;
470-
}
471-
472-
this._getType().setProgressEnd(shouldComplete, stepValue, (isOpen: boolean) => {
473-
console.debug('menu, swipeEnd', this.side);
474-
this._after(isOpen);
475-
});
472+
let opening = !this.isOpen;
473+
let shouldComplete = false;
474+
if (opening) {
475+
shouldComplete = (this.side === 'right') ? shouldCompleteLeft : shouldCompleteRight;
476+
} else {
477+
shouldComplete = (this.side === 'right') ? shouldCompleteRight : shouldCompleteLeft;
476478
}
479+
480+
this._getType().setProgressEnd(shouldComplete, stepValue, (isOpen: boolean) => {
481+
console.debug('menu, swipeEnd', this.side);
482+
this._after(isOpen);
483+
});
477484
}
478485

479486
private _before() {
480487
// this places the menu into the correct location before it animates in
481488
// this css class doesn't actually kick off any animations
482-
if (this._isEnabled) {
483-
this.getNativeElement().classList.add('show-menu');
484-
this.getBackdropElement().classList.add('show-backdrop');
485-
486-
this._prevent();
487-
this._keyboard.close();
488-
}
489+
this.getNativeElement().classList.add('show-menu');
490+
this.getBackdropElement().classList.add('show-backdrop');
491+
this._keyboard.close();
492+
this._isAnimating = true;
489493
}
490494

491495
private _after(isOpen: boolean) {
492496
// keep opening/closing the menu disabled for a touch more yet
493497
// only add listeners/css if it's enabled and isOpen
494498
// and only remove listeners/css if it's not open
495499
// emit opened/closed events
496-
if ((this._isEnabled && isOpen) || !isOpen) {
497-
this._prevent();
498-
499-
this.isOpen = isOpen;
500+
this.isOpen = isOpen;
501+
this._isAnimating = false;
500502

501-
(<any>this._cntEle.classList)[isOpen ? 'add' : 'remove']('menu-content-open');
503+
(<any>this._cntEle.classList)[isOpen ? 'add' : 'remove']('menu-content-open');
502504

503-
this._cntEle.removeEventListener('click', this.onContentClick);
505+
this._cntEle.removeEventListener('click', this.onContentClick);
504506

505-
if (isOpen) {
506-
this._cntEle.addEventListener('click', this.onContentClick);
507-
this.ionOpen.emit(true);
507+
if (isOpen) {
508+
this._cntEle.addEventListener('click', this.onContentClick);
509+
this.ionOpen.emit(true);
508510

509-
} else {
510-
this.getNativeElement().classList.remove('show-menu');
511-
this.getBackdropElement().classList.remove('show-backdrop');
512-
this.ionClose.emit(true);
513-
}
511+
} else {
512+
this.getNativeElement().classList.remove('show-menu');
513+
this.getBackdropElement().classList.remove('show-backdrop');
514+
this.ionClose.emit(true);
514515
}
515516
}
516517

517-
private _prevent() {
518-
// used to prevent unwanted opening/closing after swiping open/close
519-
// or swiping open the menu while pressing down on the MenuToggle
520-
this._preventTime = Date.now() + 20;
521-
}
522-
523-
private _isPrevented() {
524-
return this._preventTime > Date.now();
525-
}
526-
527518
/**
528519
* @private
529520
*/
@@ -564,6 +555,9 @@ export class Menu {
564555
.map(m => m.enabled = false);
565556
}
566557

558+
// TODO
559+
// what happens if menu is disabled while swipping?
560+
567561
return this;
568562
}
569563

@@ -572,6 +566,8 @@ export class Menu {
572566
*/
573567
swipeEnable(shouldEnable: boolean): Menu {
574568
this.swipeEnabled = shouldEnable;
569+
// TODO
570+
// what happens if menu swipe is disabled while swipping?
575571
return this;
576572
}
577573

@@ -619,7 +615,11 @@ export class Menu {
619615
this._cntGesture && this._cntGesture.destroy();
620616
this._type && this._type.destroy();
621617
this._resizeUnreg && this._resizeUnreg();
618+
619+
this._cntGesture = null;
620+
this._type = null;
622621
this._cntEle = null;
622+
this._resizeUnreg = null;
623623
}
624624

625625
}

src/gestures/slide-gesture.ts

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -33,23 +33,32 @@ export class SlideGesture extends PanGesture {
3333
}
3434

3535
onDragStart(ev: any) {
36-
this.slide = {};
37-
this.onSlideBeforeStart(this.slide, ev);
36+
this.onSlideBeforeStart(ev);
37+
let coord = <any>pointerCoord(ev);
38+
let pos = coord[this.direction];
3839

40+
this.slide = {
41+
min: 0,
42+
max: 0,
43+
pointerStartPos: pos,
44+
pos: pos,
45+
timestamp: Date.now(),
46+
elementStartPos: 0,
47+
started: true,
48+
delta: 0,
49+
distance: 0,
50+
velocity: 0,
51+
};
3952
let {min, max} = this.getSlideBoundaries(this.slide, ev);
40-
let coord = <any>pointerCoord(ev);
4153
this.slide.min = min;
4254
this.slide.max = max;
4355
this.slide.elementStartPos = this.getElementStartPos(this.slide, ev);
44-
this.slide.pos = this.slide.pointerStartPos = coord[this.direction];
45-
this.slide.timestamp = Date.now();
46-
this.slide.started = true;
47-
this.slide.velocity = 0;
56+
4857
this.onSlideStart(this.slide, ev);
4958
}
5059

5160
onDragMove(ev: any) {
52-
let slide = this.slide;
61+
let slide: SlideData = this.slide;
5362
let coord = <any>pointerCoord(ev);
5463
let newPos = coord[this.direction];
5564
let newTimestamp = Date.now();
@@ -74,7 +83,7 @@ export class SlideGesture extends PanGesture {
7483
this.slide = null;
7584
}
7685

77-
onSlideBeforeStart(slide?: SlideData, ev?: any): void {}
86+
onSlideBeforeStart(ev?: any): void {}
7887
onSlideStart(slide?: SlideData, ev?: any): void {}
7988
onSlide(slide?: SlideData, ev?: any): void {}
8089
onSlideEnd(slide?: SlideData, ev?: any): void {}
@@ -84,14 +93,14 @@ export class SlideGesture extends PanGesture {
8493
* @private
8594
*/
8695
export interface SlideData {
87-
min?: number;
88-
max?: number;
89-
distance?: number;
90-
delta?: number;
91-
started?: boolean;
92-
pos?: any;
93-
timestamp?: number;
94-
pointerStartPos?: number;
95-
elementStartPos?: number;
96-
velocity?: number;
96+
min: number;
97+
max: number;
98+
distance: number;
99+
delta: number;
100+
started: boolean;
101+
pos: any;
102+
timestamp: number;
103+
pointerStartPos: number;
104+
elementStartPos: number;
105+
velocity: number;
97106
}

src/navigation/swipe-back.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export class SwipeBackGesture extends SlideEdgeGesture {
3333
}
3434

3535

36-
onSlideBeforeStart(slideData: SlideData, ev: any) {
36+
onSlideBeforeStart(ev: any) {
3737
console.debug('swipeBack, onSlideBeforeStart', ev.type);
3838
this._nav.swipeBackStart();
3939
}

0 commit comments

Comments
 (0)