From 0014b5d0822982fc62e61bb91c9a86f9317bd467 Mon Sep 17 00:00:00 2001 From: Nate Woods Date: Mon, 7 Sep 2020 00:43:42 -0500 Subject: [PATCH 1/7] feat(touch): Add basic touch support to tabbar :children_crossing: Helps with https://github.com/jupyterlab/lumino/issues/77 --- packages/dragdrop/src/index.ts | 28 ++++++++++++++++++++++++++++ packages/widgets/src/tabbar.ts | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/packages/dragdrop/src/index.ts b/packages/dragdrop/src/index.ts index ce5481fdb..40be5ecc2 100644 --- a/packages/dragdrop/src/index.ts +++ b/packages/dragdrop/src/index.ts @@ -273,6 +273,14 @@ class Drag implements IDisposable { case 'keydown': this._evtKeyDown(event as KeyboardEvent); break; + case 'touchmove': + let touchMoveEvent = this._convertTouchEvent('mousemove', event as TouchEvent); + this._evtMouseMove(touchMoveEvent); + break; + case 'touchend': + let touchEndEvent = this._convertTouchEvent('mouseup', event as TouchEvent); + this._evtMouseUp(touchEndEvent); + break; default: // Stop all other events during drag-drop. event.preventDefault(); @@ -281,6 +289,20 @@ class Drag implements IDisposable { } } + private _convertTouchEvent(name: string, event: TouchEvent): MouseEvent { + let touches = event.touches; + if (touches.length === 0) touches = event.changedTouches; // touchEnd has no touches :facepalm: + let mouse = new MouseEvent(name, { + button: 0, // why not be a left click :shrug: + clientX: touches[0].clientX, + clientY: touches[0].clientY, + }); + // TODO: bind preventDefault + // TODO: bind stopPropagation + // TODO: bind target + return mouse; + } + /** * Move the drag image element to the specified location. * @@ -377,6 +399,9 @@ class Drag implements IDisposable { document.addEventListener('mouseleave', this, true); document.addEventListener('mouseover', this, true); document.addEventListener('mouseout', this, true); + document.addEventListener('touchstart', this, true); + document.addEventListener('touchmove', this, true); + document.addEventListener('touchend', this, true); document.addEventListener('keydown', this, true); document.addEventListener('keyup', this, true); document.addEventListener('keypress', this, true); @@ -394,6 +419,9 @@ class Drag implements IDisposable { document.removeEventListener('mouseleave', this, true); document.removeEventListener('mouseover', this, true); document.removeEventListener('mouseout', this, true); + document.removeEventListener('touchstart', this, true); + document.removeEventListener('touchmove', this, true); + document.removeEventListener('touchend', this, true); document.removeEventListener('keydown', this, true); document.removeEventListener('keyup', this, true); document.removeEventListener('keypress', this, true); diff --git a/packages/widgets/src/tabbar.ts b/packages/widgets/src/tabbar.ts index f8fc5f0f9..3a4bf3989 100644 --- a/packages/widgets/src/tabbar.ts +++ b/packages/widgets/src/tabbar.ts @@ -526,6 +526,18 @@ class TabBar extends Widget { case 'mouseup': this._evtMouseUp(event as MouseEvent); break; + case 'touchstart': + let touchStartEvent = this._convertTouchEvent('mousedown', event as TouchEvent); + this._evtMouseDown(touchStartEvent); + break; + case 'touchmove': + let touchMoveEvent = this._convertTouchEvent('mousemove', event as TouchEvent); + this._evtMouseMove(touchMoveEvent); + break; + case 'touchend': + let touchEndEvent = this._convertTouchEvent('mouseup', event as TouchEvent); + this._evtMouseUp(touchEndEvent); + break; case 'dblclick': this._evtDblClick(event as MouseEvent); break; @@ -545,6 +557,7 @@ class TabBar extends Widget { protected onBeforeAttach(msg: Message): void { this.node.addEventListener('mousedown', this); this.node.addEventListener('dblclick', this); + this.node.addEventListener('touchstart', this); } /** @@ -553,6 +566,7 @@ class TabBar extends Widget { protected onAfterDetach(msg: Message): void { this.node.removeEventListener('mousedown', this); this.node.removeEventListener('dblclick', this); + this.node.removeEventListener('touchstart', this); this._releaseMouse(); } @@ -650,6 +664,20 @@ class TabBar extends Widget { } } + private _convertTouchEvent(name: string, event: TouchEvent): MouseEvent { + let touches = event.touches; + if (touches.length === 0) touches = event.changedTouches; // touchEnd has no touches :facepalm: + let mouse = new MouseEvent(name, { + button: 0, // why not be a left click :shrug: + clientX: touches[0].clientX, + clientY: touches[0].clientY, + }); + // TODO: bind preventDefault + // TODO: bind stopPropagation + // TODO: bind target + return mouse; + } + /** * Handle the `'mousedown'` event for the tab bar. */ @@ -701,6 +729,7 @@ class TabBar extends Widget { // Add the document mouse up listener. document.addEventListener('mouseup', this, true); + document.addEventListener('touchend', this, true); // Do nothing else if the middle button is clicked. if (event.button === 1) { @@ -716,6 +745,7 @@ class TabBar extends Widget { // Add the extra listeners if the tabs are movable. if (this.tabsMovable) { document.addEventListener('mousemove', this, true); + document.addEventListener('touchmove', this, true); document.addEventListener('keydown', this, true); document.addEventListener('contextmenu', this, true); } @@ -835,7 +865,9 @@ class TabBar extends Widget { // Remove the extra mouse event listeners. document.removeEventListener('mousemove', this, true); + document.removeEventListener('touchmove', this, true); document.removeEventListener('mouseup', this, true); + document.removeEventListener('touchend', this, true); document.removeEventListener('keydown', this, true); document.removeEventListener('contextmenu', this, true); @@ -957,7 +989,9 @@ class TabBar extends Widget { // Remove the extra mouse listeners. document.removeEventListener('mousemove', this, true); + document.removeEventListener('touchmove', this, true); document.removeEventListener('mouseup', this, true); + document.removeEventListener('touchend', this, true); document.removeEventListener('keydown', this, true); document.removeEventListener('contextmenu', this, true); From a2ad28a83f598f3f26046583d8a3ca15c3c48cc4 Mon Sep 17 00:00:00 2001 From: Nate Woods Date: Mon, 7 Sep 2020 16:29:27 -0500 Subject: [PATCH 2/7] refactor(touch): Expose convertTouchToMouseEvent for DRY :recycle: --- packages/dragdrop/src/index.ts | 41 ++++++++++++++++------------------ packages/widgets/src/tabbar.ts | 32 +++++--------------------- 2 files changed, 25 insertions(+), 48 deletions(-) diff --git a/packages/dragdrop/src/index.ts b/packages/dragdrop/src/index.ts index 40be5ecc2..6c04e6f9b 100644 --- a/packages/dragdrop/src/index.ts +++ b/packages/dragdrop/src/index.ts @@ -264,23 +264,19 @@ class Drag implements IDisposable { */ handleEvent(event: Event): void { switch(event.type) { + case 'touchmove': + event = Drag.convertTouchToMouseEvent(event as TouchEvent); case 'mousemove': this._evtMouseMove(event as MouseEvent); break; + case 'touchend': + event = Drag.convertTouchToMouseEvent(event as TouchEvent); case 'mouseup': this._evtMouseUp(event as MouseEvent); break; case 'keydown': this._evtKeyDown(event as KeyboardEvent); break; - case 'touchmove': - let touchMoveEvent = this._convertTouchEvent('mousemove', event as TouchEvent); - this._evtMouseMove(touchMoveEvent); - break; - case 'touchend': - let touchEndEvent = this._convertTouchEvent('mouseup', event as TouchEvent); - this._evtMouseUp(touchEndEvent); - break; default: // Stop all other events during drag-drop. event.preventDefault(); @@ -289,20 +285,6 @@ class Drag implements IDisposable { } } - private _convertTouchEvent(name: string, event: TouchEvent): MouseEvent { - let touches = event.touches; - if (touches.length === 0) touches = event.changedTouches; // touchEnd has no touches :facepalm: - let mouse = new MouseEvent(name, { - button: 0, // why not be a left click :shrug: - clientX: touches[0].clientX, - clientY: touches[0].clientY, - }); - // TODO: bind preventDefault - // TODO: bind stopPropagation - // TODO: bind target - return mouse; - } - /** * Move the drag image element to the specified location. * @@ -756,6 +738,21 @@ namespace Drag { }); } + export + function convertTouchToMouseEvent(touch: TouchEvent): MouseEvent { + let touches = touch.touches; + if (touches.length === 0) touches = touch.changedTouches; // touchEnd has no touches :facepalm: + let mouse = new MouseEvent('fake-mouse', { + button: 0, // why not be a left click :shrug: + clientX: touches[0].clientX, + clientY: touches[0].clientY, + }); + // TODO: bind preventDefault + // TODO: bind stopPropagation + // TODO: bind target + return mouse; + } + /** * The internal id for the active cursor override. */ diff --git a/packages/widgets/src/tabbar.ts b/packages/widgets/src/tabbar.ts index 3a4bf3989..69043ec49 100644 --- a/packages/widgets/src/tabbar.ts +++ b/packages/widgets/src/tabbar.ts @@ -517,27 +517,21 @@ class TabBar extends Widget { */ handleEvent(event: Event): void { switch (event.type) { + case 'touchstart': + event = Drag.convertTouchToMouseEvent(event as TouchEvent); case 'mousedown': this._evtMouseDown(event as MouseEvent); break; + case 'touchmove': + event = Drag.convertTouchToMouseEvent(event as TouchEvent); case 'mousemove': this._evtMouseMove(event as MouseEvent); break; + case 'touchend': + event = Drag.convertTouchToMouseEvent(event as TouchEvent); case 'mouseup': this._evtMouseUp(event as MouseEvent); break; - case 'touchstart': - let touchStartEvent = this._convertTouchEvent('mousedown', event as TouchEvent); - this._evtMouseDown(touchStartEvent); - break; - case 'touchmove': - let touchMoveEvent = this._convertTouchEvent('mousemove', event as TouchEvent); - this._evtMouseMove(touchMoveEvent); - break; - case 'touchend': - let touchEndEvent = this._convertTouchEvent('mouseup', event as TouchEvent); - this._evtMouseUp(touchEndEvent); - break; case 'dblclick': this._evtDblClick(event as MouseEvent); break; @@ -664,20 +658,6 @@ class TabBar extends Widget { } } - private _convertTouchEvent(name: string, event: TouchEvent): MouseEvent { - let touches = event.touches; - if (touches.length === 0) touches = event.changedTouches; // touchEnd has no touches :facepalm: - let mouse = new MouseEvent(name, { - button: 0, // why not be a left click :shrug: - clientX: touches[0].clientX, - clientY: touches[0].clientY, - }); - // TODO: bind preventDefault - // TODO: bind stopPropagation - // TODO: bind target - return mouse; - } - /** * Handle the `'mousedown'` event for the tab bar. */ From 04c630cdf6045518a9d7aec7a5a1944f67e71d6f Mon Sep 17 00:00:00 2001 From: Nate Woods Date: Mon, 7 Sep 2020 17:05:26 -0500 Subject: [PATCH 3/7] feat(touch): Add touch resize handlers :point_up: --- packages/dragdrop/src/index.ts | 14 +++++++++++--- packages/widgets/src/dockpanel.ts | 12 ++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/packages/dragdrop/src/index.ts b/packages/dragdrop/src/index.ts index 6c04e6f9b..20b16b6a0 100644 --- a/packages/dragdrop/src/index.ts +++ b/packages/dragdrop/src/index.ts @@ -738,6 +738,9 @@ namespace Drag { }); } + /** + * Converts a TouchEvent to a MouseEvent to simplify mobile touch event handling. + */ export function convertTouchToMouseEvent(touch: TouchEvent): MouseEvent { let touches = touch.touches; @@ -747,9 +750,14 @@ namespace Drag { clientX: touches[0].clientX, clientY: touches[0].clientY, }); - // TODO: bind preventDefault - // TODO: bind stopPropagation - // TODO: bind target + + // Forcefully add mouse event properties. + (mouse as any).preventDefault = touch.preventDefault; + (mouse as any).stopPropagation = touch.stopPropagation; + + // target is supposed to be readOnly, set it with js hackery + Object.defineProperty(mouse, 'target', {value: touch.target, writable: false}); + return mouse; } diff --git a/packages/widgets/src/dockpanel.ts b/packages/widgets/src/dockpanel.ts index 593000d81..74d0514b4 100644 --- a/packages/widgets/src/dockpanel.ts +++ b/packages/widgets/src/dockpanel.ts @@ -403,12 +403,18 @@ class DockPanel extends Widget { case 'lm-drop': this._evtDrop(event as IDragEvent); break; + case 'touchstart': + event = Drag.convertTouchToMouseEvent(event as TouchEvent); case 'mousedown': this._evtMouseDown(event as MouseEvent); break; + case 'touchmove': + event = Drag.convertTouchToMouseEvent(event as TouchEvent); case 'mousemove': this._evtMouseMove(event as MouseEvent); break; + case 'touchend': + event = Drag.convertTouchToMouseEvent(event as TouchEvent); case 'mouseup': this._evtMouseUp(event as MouseEvent); break; @@ -431,6 +437,7 @@ class DockPanel extends Widget { this.node.addEventListener('lm-dragover', this); this.node.addEventListener('lm-drop', this); this.node.addEventListener('mousedown', this); + this.node.addEventListener('touchstart', this); } /** @@ -442,6 +449,7 @@ class DockPanel extends Widget { this.node.removeEventListener('lm-dragover', this); this.node.removeEventListener('lm-drop', this); this.node.removeEventListener('mousedown', this); + this.node.removeEventListener('touchstart', this); this._releaseMouse(); } @@ -667,7 +675,9 @@ class DockPanel extends Widget { // Add the extra document listeners. document.addEventListener('keydown', this, true); document.addEventListener('mouseup', this, true); + document.addEventListener('touchend', this, true); document.addEventListener('mousemove', this, true); + document.addEventListener('touchmove', this, true); document.addEventListener('contextmenu', this, true); // Compute the offset deltas for the handle press. @@ -740,7 +750,9 @@ class DockPanel extends Widget { // Remove the extra document listeners. document.removeEventListener('keydown', this, true); document.removeEventListener('mouseup', this, true); + document.removeEventListener('touchend', this, true); document.removeEventListener('mousemove', this, true); + document.removeEventListener('touchmove', this, true); document.removeEventListener('contextmenu', this, true); } From c5b8e688018fdeeee4afba2e12a79dc34046be28 Mon Sep 17 00:00:00 2001 From: Nate Woods Date: Tue, 8 Sep 2020 00:11:52 -0500 Subject: [PATCH 4/7] test(touch): verify touch to mouse conversion :white_check_mark: --- packages/dragdrop/tests/src/index.spec.ts | 26 +++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/packages/dragdrop/tests/src/index.spec.ts b/packages/dragdrop/tests/src/index.spec.ts index 9adaa69bd..b50fdb171 100644 --- a/packages/dragdrop/tests/src/index.spec.ts +++ b/packages/dragdrop/tests/src/index.spec.ts @@ -585,6 +585,32 @@ describe('@lumino/dragdrop', () => { }); + describe('.convertTouchToMouseEvent()', () => { + + it('should produce a mouse event from a touch event', () => { + let touch = generate('touchstart') as TouchEvent; + (touch as any).touches = [{clientX: 1, clientY: 2}]; + let mouse = Drag.convertTouchToMouseEvent(touch); + expect(mouse.button).to.equal(0); + expect(mouse.clientX).to.equal(1); + expect(mouse.clientY).to.equal(2); + expect(mouse.preventDefault).to.equal(touch.preventDefault); + expect(mouse.stopPropagation).to.equal(touch.stopPropagation); + expect(mouse.target).to.equal(touch.target); + }); + + it('should produce valid clientX/Y on touchend events', () => { + let touch = generate('touchstart') as TouchEvent; + (touch as any).touches = []; + (touch as any).changedTouches = [{clientX: 1, clientY: 2}]; + let mouse = Drag.convertTouchToMouseEvent(touch); + expect(mouse.button).to.equal(0); + expect(mouse.clientX).to.equal(1); + expect(mouse.clientY).to.equal(2); + }); + + }); + }); }); From 2432f5698a41a295173fdeac48ae8565473810fd Mon Sep 17 00:00:00 2001 From: Nate Woods Date: Sun, 26 Sep 2021 02:16:42 -0500 Subject: [PATCH 5/7] remove eslint exceptions --- packages/dragdrop/src/index.ts | 10 ++++------ packages/widgets/src/dockpanel.ts | 15 ++++++--------- packages/widgets/src/tabbar.ts | 15 ++++++--------- 3 files changed, 16 insertions(+), 24 deletions(-) diff --git a/packages/dragdrop/src/index.ts b/packages/dragdrop/src/index.ts index 1b83e184e..81e6ad2a2 100644 --- a/packages/dragdrop/src/index.ts +++ b/packages/dragdrop/src/index.ts @@ -258,16 +258,14 @@ export class Drag implements IDisposable { handleEvent(event: Event): void { switch (event.type) { case 'touchmove': - /* eslint no-fallthrough: "off" */ - event = Drag.convertTouchToMouseEvent(event as TouchEvent); - // caution: break is omitted intentionally + this._evtMouseMove(Drag.convertTouchToMouseEvent(event as TouchEvent)); + break; case 'mousemove': this._evtMouseMove(event as MouseEvent); break; case 'touchend': - /* eslint no-fallthrough: "off" */ - event = Drag.convertTouchToMouseEvent(event as TouchEvent); - // caution: break is omitted intentionally + this._evtMouseUp(Drag.convertTouchToMouseEvent(event as TouchEvent)); + break; case 'mouseup': this._evtMouseUp(event as MouseEvent); break; diff --git a/packages/widgets/src/dockpanel.ts b/packages/widgets/src/dockpanel.ts index bbf6e7fea..c4cdd25a0 100644 --- a/packages/widgets/src/dockpanel.ts +++ b/packages/widgets/src/dockpanel.ts @@ -429,23 +429,20 @@ export class DockPanel extends Widget { this._evtDrop(event as IDragEvent); break; case 'touchstart': - /* eslint no-fallthrough: "off" */ - event = Drag.convertTouchToMouseEvent(event as TouchEvent); - // caution: break is omitted intentionally + this._evtMouseDown(Drag.convertTouchToMouseEvent(event as TouchEvent)); + break; case 'mousedown': this._evtMouseDown(event as MouseEvent); break; case 'touchmove': - /* eslint no-fallthrough: "off" */ - event = Drag.convertTouchToMouseEvent(event as TouchEvent); - // caution: break is omitted intentionally + this._evtMouseMove(Drag.convertTouchToMouseEvent(event as TouchEvent)); + break; case 'mousemove': this._evtMouseMove(event as MouseEvent); break; case 'touchend': - /* eslint no-fallthrough: "off" */ - event = Drag.convertTouchToMouseEvent(event as TouchEvent); - // caution: break is omitted intentionally + this._evtMouseUp(Drag.convertTouchToMouseEvent(event as TouchEvent)); + break; case 'mouseup': this._evtMouseUp(event as MouseEvent); break; diff --git a/packages/widgets/src/tabbar.ts b/packages/widgets/src/tabbar.ts index 5bef2cb5e..57febcc37 100644 --- a/packages/widgets/src/tabbar.ts +++ b/packages/widgets/src/tabbar.ts @@ -581,23 +581,20 @@ export class TabBar extends Widget { handleEvent(event: Event): void { switch (event.type) { case 'touchstart': - /* eslint no-fallthrough: "off" */ - event = Drag.convertTouchToMouseEvent(event as TouchEvent); - // caution: break is omitted intentionally + this._evtMouseDown(Drag.convertTouchToMouseEvent(event as TouchEvent)); + break; case 'mousedown': this._evtMouseDown(event as MouseEvent); break; case 'touchmove': - /* eslint no-fallthrough: "off" */ - event = Drag.convertTouchToMouseEvent(event as TouchEvent); - // caution: break is omitted intentionally + this._evtMouseMove(Drag.convertTouchToMouseEvent(event as TouchEvent)); + break; case 'mousemove': this._evtMouseMove(event as MouseEvent); break; case 'touchend': - /* eslint no-fallthrough: "off" */ - event = Drag.convertTouchToMouseEvent(event as TouchEvent); - // caution: break is omitted intentionally + this._evtMouseUp(Drag.convertTouchToMouseEvent(event as TouchEvent)); + break; case 'mouseup': this._evtMouseUp(event as MouseEvent); break; From 51355199ec7872a2bee493d5d8da15e4912c962a Mon Sep 17 00:00:00 2001 From: Nate Woods Date: Sun, 26 Sep 2021 10:54:10 -0500 Subject: [PATCH 6/7] Use PointerEvents instead of TouchEvents Recommended by: @afshin on #123 Found native browser drag/drop inadvertently firing the pointercancel event. Disabled with css `pointer-events: none` on tabs. Details: https://javascript.info/pointer-events#event-pointercancel --- packages/dragdrop/src/index.ts | 73 ++++++----------------- packages/dragdrop/tests/src/index.spec.ts | 24 -------- packages/widgets/src/dockpanel.ts | 33 +++------- packages/widgets/src/tabbar.ts | 39 ++++-------- packages/widgets/style/tabbar.css | 1 + 5 files changed, 39 insertions(+), 131 deletions(-) diff --git a/packages/dragdrop/src/index.ts b/packages/dragdrop/src/index.ts index 81e6ad2a2..757aa7adc 100644 --- a/packages/dragdrop/src/index.ts +++ b/packages/dragdrop/src/index.ts @@ -154,7 +154,7 @@ export class Drag implements IDisposable { // If there is a current target, dispatch a drag leave event. if (this._currentTarget) { - let event = Private.createMouseEvent('mouseup', -1, -1); + let event = Private.createMouseEvent('pointerup', -1, -1); Private.dispatchDragLeave(this, this._currentTarget, null, event); } @@ -238,7 +238,7 @@ export class Drag implements IDisposable { }); // Trigger a fake move event to kick off the drag operation. - let event = Private.createMouseEvent('mousemove', clientX, clientY); + let event = Private.createMouseEvent('pointermove', clientX, clientY); document.dispatchEvent(event); // Return the pending promise for the drag operation. @@ -257,16 +257,10 @@ export class Drag implements IDisposable { */ handleEvent(event: Event): void { switch (event.type) { - case 'touchmove': - this._evtMouseMove(Drag.convertTouchToMouseEvent(event as TouchEvent)); - break; - case 'mousemove': + case 'pointermove': this._evtMouseMove(event as MouseEvent); break; - case 'touchend': - this._evtMouseUp(Drag.convertTouchToMouseEvent(event as TouchEvent)); - break; - case 'mouseup': + case 'pointerup': this._evtMouseUp(event as MouseEvent); break; case 'keydown': @@ -369,16 +363,13 @@ export class Drag implements IDisposable { * Add the document event listeners for the drag object. */ private _addListeners(): void { - document.addEventListener('mousedown', this, true); - document.addEventListener('mousemove', this, true); - document.addEventListener('mouseup', this, true); - document.addEventListener('mouseenter', this, true); - document.addEventListener('mouseleave', this, true); - document.addEventListener('mouseover', this, true); - document.addEventListener('mouseout', this, true); - document.addEventListener('touchstart', this, true); - document.addEventListener('touchmove', this, true); - document.addEventListener('touchend', this, true); + document.addEventListener('pointerdown', this, true); + document.addEventListener('pointermove', this, true); + document.addEventListener('pointerup', this, true); + document.addEventListener('pointerenter', this, true); + document.addEventListener('pointerleave', this, true); + document.addEventListener('pointerover', this, true); + document.addEventListener('pointerout', this, true); document.addEventListener('keydown', this, true); document.addEventListener('keyup', this, true); document.addEventListener('keypress', this, true); @@ -389,16 +380,13 @@ export class Drag implements IDisposable { * Remove the document event listeners for the drag object. */ private _removeListeners(): void { - document.removeEventListener('mousedown', this, true); - document.removeEventListener('mousemove', this, true); - document.removeEventListener('mouseup', this, true); - document.removeEventListener('mouseenter', this, true); - document.removeEventListener('mouseleave', this, true); - document.removeEventListener('mouseover', this, true); - document.removeEventListener('mouseout', this, true); - document.removeEventListener('touchstart', this, true); - document.removeEventListener('touchmove', this, true); - document.removeEventListener('touchend', this, true); + document.removeEventListener('pointerdown', this, true); + document.removeEventListener('pointermove', this, true); + document.removeEventListener('pointerup', this, true); + document.removeEventListener('pointerenter', this, true); + document.removeEventListener('pointerleave', this, true); + document.removeEventListener('pointerover', this, true); + document.removeEventListener('pointerout', this, true); document.removeEventListener('keydown', this, true); document.removeEventListener('keyup', this, true); document.removeEventListener('keypress', this, true); @@ -729,31 +717,6 @@ export namespace Drag { }); } - /** - * Converts a TouchEvent to a MouseEvent to simplify mobile touch event handling. - */ - export function convertTouchToMouseEvent(touch: TouchEvent): MouseEvent { - let touches = touch.touches; - if (touches.length === 0) touches = touch.changedTouches; // touchEnd has no touches :facepalm: - let mouse = new MouseEvent('fake-mouse', { - button: 0, // why not be a left click :shrug: - clientX: touches[0].clientX, - clientY: touches[0].clientY - }); - - // Forcefully add mouse event properties. - (mouse as any).preventDefault = touch.preventDefault; - (mouse as any).stopPropagation = touch.stopPropagation; - - // target is supposed to be readOnly, set it with js hackery - Object.defineProperty(mouse, 'target', { - value: touch.target, - writable: false - }); - - return mouse; - } - /** * The internal id for the active cursor override. */ diff --git a/packages/dragdrop/tests/src/index.spec.ts b/packages/dragdrop/tests/src/index.spec.ts index 5f58b3369..6513a0591 100644 --- a/packages/dragdrop/tests/src/index.spec.ts +++ b/packages/dragdrop/tests/src/index.spec.ts @@ -613,29 +613,5 @@ describe('@lumino/dragdrop', () => { document.body.removeChild(div); }); }); - - describe('.convertTouchToMouseEvent()', () => { - it('should produce a mouse event from a touch event', () => { - let touch = generate('touchstart') as TouchEvent; - (touch as any).touches = [{ clientX: 1, clientY: 2 }]; - let mouse = Drag.convertTouchToMouseEvent(touch); - expect(mouse.button).to.equal(0); - expect(mouse.clientX).to.equal(1); - expect(mouse.clientY).to.equal(2); - expect(mouse.preventDefault).to.equal(touch.preventDefault); - expect(mouse.stopPropagation).to.equal(touch.stopPropagation); - expect(mouse.target).to.equal(touch.target); - }); - - it('should produce valid clientX/Y on touchend events', () => { - let touch = generate('touchstart') as TouchEvent; - (touch as any).touches = []; - (touch as any).changedTouches = [{ clientX: 1, clientY: 2 }]; - let mouse = Drag.convertTouchToMouseEvent(touch); - expect(mouse.button).to.equal(0); - expect(mouse.clientX).to.equal(1); - expect(mouse.clientY).to.equal(2); - }); - }); }); }); diff --git a/packages/widgets/src/dockpanel.ts b/packages/widgets/src/dockpanel.ts index c4cdd25a0..0b392d2b0 100644 --- a/packages/widgets/src/dockpanel.ts +++ b/packages/widgets/src/dockpanel.ts @@ -428,22 +428,13 @@ export class DockPanel extends Widget { case 'lm-drop': this._evtDrop(event as IDragEvent); break; - case 'touchstart': - this._evtMouseDown(Drag.convertTouchToMouseEvent(event as TouchEvent)); - break; - case 'mousedown': + case 'pointerdown': this._evtMouseDown(event as MouseEvent); break; - case 'touchmove': - this._evtMouseMove(Drag.convertTouchToMouseEvent(event as TouchEvent)); - break; - case 'mousemove': + case 'pointermove': this._evtMouseMove(event as MouseEvent); break; - case 'touchend': - this._evtMouseUp(Drag.convertTouchToMouseEvent(event as TouchEvent)); - break; - case 'mouseup': + case 'pointerup': this._evtMouseUp(event as MouseEvent); break; case 'keydown': @@ -464,8 +455,7 @@ export class DockPanel extends Widget { this.node.addEventListener('lm-dragleave', this); this.node.addEventListener('lm-dragover', this); this.node.addEventListener('lm-drop', this); - this.node.addEventListener('mousedown', this); - this.node.addEventListener('touchstart', this); + this.node.addEventListener('pointerdown', this); } /** @@ -476,8 +466,7 @@ export class DockPanel extends Widget { this.node.removeEventListener('lm-dragleave', this); this.node.removeEventListener('lm-dragover', this); this.node.removeEventListener('lm-drop', this); - this.node.removeEventListener('mousedown', this); - this.node.removeEventListener('touchstart', this); + this.node.removeEventListener('pointerdown', this); this._releaseMouse(); } @@ -705,10 +694,8 @@ export class DockPanel extends Widget { // Add the extra document listeners. document.addEventListener('keydown', this, true); - document.addEventListener('mouseup', this, true); - document.addEventListener('touchend', this, true); - document.addEventListener('mousemove', this, true); - document.addEventListener('touchmove', this, true); + document.addEventListener('pointerup', this, true); + document.addEventListener('pointermove', this, true); document.addEventListener('contextmenu', this, true); // Compute the offset deltas for the handle press. @@ -780,10 +767,8 @@ export class DockPanel extends Widget { // Remove the extra document listeners. document.removeEventListener('keydown', this, true); - document.removeEventListener('mouseup', this, true); - document.removeEventListener('touchend', this, true); - document.removeEventListener('mousemove', this, true); - document.removeEventListener('touchmove', this, true); + document.removeEventListener('pointerup', this, true); + document.removeEventListener('pointermove', this, true); document.removeEventListener('contextmenu', this, true); } diff --git a/packages/widgets/src/tabbar.ts b/packages/widgets/src/tabbar.ts index 57febcc37..6f32382e8 100644 --- a/packages/widgets/src/tabbar.ts +++ b/packages/widgets/src/tabbar.ts @@ -580,22 +580,13 @@ export class TabBar extends Widget { */ handleEvent(event: Event): void { switch (event.type) { - case 'touchstart': - this._evtMouseDown(Drag.convertTouchToMouseEvent(event as TouchEvent)); - break; - case 'mousedown': + case 'pointerdown': this._evtMouseDown(event as MouseEvent); break; - case 'touchmove': - this._evtMouseMove(Drag.convertTouchToMouseEvent(event as TouchEvent)); - break; - case 'mousemove': + case 'pointermove': this._evtMouseMove(event as MouseEvent); break; - case 'touchend': - this._evtMouseUp(Drag.convertTouchToMouseEvent(event as TouchEvent)); - break; - case 'mouseup': + case 'pointerup': this._evtMouseUp(event as MouseEvent); break; case 'dblclick': @@ -615,18 +606,16 @@ export class TabBar extends Widget { * A message handler invoked on a `'before-attach'` message. */ protected onBeforeAttach(msg: Message): void { - this.node.addEventListener('mousedown', this); + this.node.addEventListener('pointerdown', this); this.node.addEventListener('dblclick', this); - this.node.addEventListener('touchstart', this); } /** * A message handler invoked on an `'after-detach'` message. */ protected onAfterDetach(msg: Message): void { - this.node.removeEventListener('mousedown', this); + this.node.removeEventListener('pointerdown', this); this.node.removeEventListener('dblclick', this); - this.node.removeEventListener('touchstart', this); this._releaseMouse(); } @@ -779,8 +768,7 @@ export class TabBar extends Widget { }; // Add the document mouse up listener. - document.addEventListener('mouseup', this, true); - document.addEventListener('touchend', this, true); + document.addEventListener('pointerup', this, true); // Do nothing else if the middle button or add button is clicked. if (event.button === 1 || addButtonClicked) { @@ -795,8 +783,7 @@ export class TabBar extends Widget { // Add the extra listeners if the tabs are movable. if (this.tabsMovable) { - document.addEventListener('mousemove', this, true); - document.addEventListener('touchmove', this, true); + document.addEventListener('pointermove', this, true); document.addEventListener('keydown', this, true); document.addEventListener('contextmenu', this, true); } @@ -916,10 +903,8 @@ export class TabBar extends Widget { event.stopPropagation(); // Remove the extra mouse event listeners. - document.removeEventListener('mousemove', this, true); - document.removeEventListener('touchmove', this, true); - document.removeEventListener('mouseup', this, true); - document.removeEventListener('touchend', this, true); + document.removeEventListener('pointermove', this, true); + document.removeEventListener('pointerup', this, true); document.removeEventListener('keydown', this, true); document.removeEventListener('contextmenu', this, true); @@ -1051,10 +1036,8 @@ export class TabBar extends Widget { this._dragData = null; // Remove the extra mouse listeners. - document.removeEventListener('mousemove', this, true); - document.removeEventListener('touchmove', this, true); - document.removeEventListener('mouseup', this, true); - document.removeEventListener('touchend', this, true); + document.removeEventListener('pointermove', this, true); + document.removeEventListener('pointerup', this, true); document.removeEventListener('keydown', this, true); document.removeEventListener('contextmenu', this, true); diff --git a/packages/widgets/style/tabbar.css b/packages/widgets/style/tabbar.css index 2e9edaa35..6f64eab47 100644 --- a/packages/widgets/style/tabbar.css +++ b/packages/widgets/style/tabbar.css @@ -62,6 +62,7 @@ flex-direction: row; box-sizing: border-box; overflow: hidden; + touch-action: none; /* Disable native Drag/Drop */ } /* */ From 3754c7b2bdce440d8fdc97d30ee373f952d8f136 Mon Sep 17 00:00:00 2001 From: Nate Woods Date: Sun, 26 Sep 2021 11:40:59 -0500 Subject: [PATCH 7/7] Restoring MouseEvents (marked as deprecated) The simulate-event package does not support pointer events. Looks to be reported here: https://github.com/blakeembrey/simulate-event/issues/3 --- packages/dragdrop/src/index.ts | 20 ++++++++++++++++++++ packages/widgets/src/dockpanel.ts | 15 +++++++++++++++ packages/widgets/src/tabbar.ts | 17 +++++++++++++++++ 3 files changed, 52 insertions(+) diff --git a/packages/dragdrop/src/index.ts b/packages/dragdrop/src/index.ts index 757aa7adc..070c2c452 100644 --- a/packages/dragdrop/src/index.ts +++ b/packages/dragdrop/src/index.ts @@ -257,6 +257,12 @@ export class Drag implements IDisposable { */ handleEvent(event: Event): void { switch (event.type) { + case 'mousemove': // + this._evtMouseMove(event as MouseEvent); + break; + case 'mouseup': // + this._evtMouseUp(event as MouseEvent); + break; case 'pointermove': this._evtMouseMove(event as MouseEvent); break; @@ -363,6 +369,13 @@ export class Drag implements IDisposable { * Add the document event listeners for the drag object. */ private _addListeners(): void { + document.addEventListener('mousedown', this, true); // + document.addEventListener('mousemove', this, true); // + document.addEventListener('mouseup', this, true); // + document.addEventListener('mouseenter', this, true); // + document.addEventListener('mouseleave', this, true); // + document.addEventListener('mouseover', this, true); // + document.addEventListener('mouseout', this, true); // document.addEventListener('pointerdown', this, true); document.addEventListener('pointermove', this, true); document.addEventListener('pointerup', this, true); @@ -380,6 +393,13 @@ export class Drag implements IDisposable { * Remove the document event listeners for the drag object. */ private _removeListeners(): void { + document.removeEventListener('mousedown', this, true); // + document.removeEventListener('mousemove', this, true); // + document.removeEventListener('mouseup', this, true); // + document.removeEventListener('mouseenter', this, true); // + document.removeEventListener('mouseleave', this, true); // + document.removeEventListener('mouseover', this, true); // + document.removeEventListener('mouseout', this, true); // document.removeEventListener('pointerdown', this, true); document.removeEventListener('pointermove', this, true); document.removeEventListener('pointerup', this, true); diff --git a/packages/widgets/src/dockpanel.ts b/packages/widgets/src/dockpanel.ts index 0b392d2b0..03c1fcd88 100644 --- a/packages/widgets/src/dockpanel.ts +++ b/packages/widgets/src/dockpanel.ts @@ -428,6 +428,15 @@ export class DockPanel extends Widget { case 'lm-drop': this._evtDrop(event as IDragEvent); break; + case 'mousedown': // + this._evtMouseDown(event as MouseEvent); + break; + case 'mousemove': // + this._evtMouseMove(event as MouseEvent); + break; + case 'mouseup': // + this._evtMouseUp(event as MouseEvent); + break; case 'pointerdown': this._evtMouseDown(event as MouseEvent); break; @@ -455,6 +464,7 @@ export class DockPanel extends Widget { this.node.addEventListener('lm-dragleave', this); this.node.addEventListener('lm-dragover', this); this.node.addEventListener('lm-drop', this); + this.node.addEventListener('mousedown', this); // this.node.addEventListener('pointerdown', this); } @@ -466,6 +476,7 @@ export class DockPanel extends Widget { this.node.removeEventListener('lm-dragleave', this); this.node.removeEventListener('lm-dragover', this); this.node.removeEventListener('lm-drop', this); + this.node.removeEventListener('mousedown', this); // this.node.removeEventListener('pointerdown', this); this._releaseMouse(); } @@ -694,6 +705,8 @@ export class DockPanel extends Widget { // Add the extra document listeners. document.addEventListener('keydown', this, true); + document.addEventListener('mouseup', this, true); // + document.addEventListener('mousemove', this, true); // document.addEventListener('pointerup', this, true); document.addEventListener('pointermove', this, true); document.addEventListener('contextmenu', this, true); @@ -767,6 +780,8 @@ export class DockPanel extends Widget { // Remove the extra document listeners. document.removeEventListener('keydown', this, true); + document.removeEventListener('mouseup', this, true); // + document.removeEventListener('mousemove', this, true); // document.removeEventListener('pointerup', this, true); document.removeEventListener('pointermove', this, true); document.removeEventListener('contextmenu', this, true); diff --git a/packages/widgets/src/tabbar.ts b/packages/widgets/src/tabbar.ts index 6f32382e8..72a9a5e15 100644 --- a/packages/widgets/src/tabbar.ts +++ b/packages/widgets/src/tabbar.ts @@ -580,6 +580,15 @@ export class TabBar extends Widget { */ handleEvent(event: Event): void { switch (event.type) { + case 'mousedown': // + this._evtMouseDown(event as MouseEvent); + break; + case 'mousemove': // + this._evtMouseMove(event as MouseEvent); + break; + case 'mouseup': // + this._evtMouseUp(event as MouseEvent); + break; case 'pointerdown': this._evtMouseDown(event as MouseEvent); break; @@ -606,6 +615,7 @@ export class TabBar extends Widget { * A message handler invoked on a `'before-attach'` message. */ protected onBeforeAttach(msg: Message): void { + this.node.addEventListener('mousedown', this); // this.node.addEventListener('pointerdown', this); this.node.addEventListener('dblclick', this); } @@ -614,6 +624,7 @@ export class TabBar extends Widget { * A message handler invoked on an `'after-detach'` message. */ protected onAfterDetach(msg: Message): void { + this.node.removeEventListener('mousedown', this); // this.node.removeEventListener('pointerdown', this); this.node.removeEventListener('dblclick', this); this._releaseMouse(); @@ -768,6 +779,7 @@ export class TabBar extends Widget { }; // Add the document mouse up listener. + document.addEventListener('mouseup', this, true); // document.addEventListener('pointerup', this, true); // Do nothing else if the middle button or add button is clicked. @@ -783,6 +795,7 @@ export class TabBar extends Widget { // Add the extra listeners if the tabs are movable. if (this.tabsMovable) { + document.addEventListener('mousemove', this, true); // document.addEventListener('pointermove', this, true); document.addEventListener('keydown', this, true); document.addEventListener('contextmenu', this, true); @@ -903,6 +916,8 @@ export class TabBar extends Widget { event.stopPropagation(); // Remove the extra mouse event listeners. + document.removeEventListener('mousemove', this, true); // + document.removeEventListener('mouseup', this, true); // document.removeEventListener('pointermove', this, true); document.removeEventListener('pointerup', this, true); document.removeEventListener('keydown', this, true); @@ -1036,6 +1051,8 @@ export class TabBar extends Widget { this._dragData = null; // Remove the extra mouse listeners. + document.removeEventListener('mousemove', this, true); // + document.removeEventListener('mouseup', this, true); // document.removeEventListener('pointermove', this, true); document.removeEventListener('pointerup', this, true); document.removeEventListener('keydown', this, true);