From 9e10814840067d97581402be8ae1b0f267548597 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Mon, 12 Jun 2023 21:36:24 +0100 Subject: [PATCH 1/2] Patch 1 (#278) * Touch support for split view * Attempting to make TS happy * Making typescript happy round two https://stackoverflow.com/questions/54688147/react-typescript-event-type-for-both-interfaces-mouseevent-and-touchevent * make TS Happy * Update splitview.ts --------- Co-authored-by: Ray Foss --- .../dockview-core/src/splitview/splitview.ts | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/packages/dockview-core/src/splitview/splitview.ts b/packages/dockview-core/src/splitview/splitview.ts index fd68a4a29..a39c989e3 100644 --- a/packages/dockview-core/src/splitview/splitview.ts +++ b/packages/dockview-core/src/splitview/splitview.ts @@ -393,7 +393,8 @@ export class Splitview { const sash = document.createElement('div'); sash.className = 'sash'; - const onStart = (event: MouseEvent) => { + const onStart = (nativeEvent: MouseEvent | TouchEvent) => { + const event = nativeEvent instanceof TouchEvent ? nativeEvent.touches[0] : nativeEvent; for (const item of this.viewItems) { item.enabled = false; } @@ -488,11 +489,12 @@ export class Splitview { } // - const mousemove = (mousemoveEvent: MouseEvent) => { + const mousemove = (nativeMoveEvent: MouseEvent | TouchEvent) => { + const moveEvent = nativeMoveEvent instanceof TouchEvent ? nativeMoveEvent.touches[0] : nativeMoveEvent; const current = this._orientation === Orientation.HORIZONTAL - ? mousemoveEvent.clientX - : mousemoveEvent.clientY; + ? moveEvent.clientX + : moveEvent.clientY; const delta = current - start; this.resize( @@ -523,15 +525,24 @@ export class Splitview { document.removeEventListener('mousemove', mousemove); document.removeEventListener('mouseup', end); + document.removeEventListener("touchmove", mousemove); + document.removeEventListener("touchend", end); + document.removeEventListener("touchcancel", end); this._onDidSashEnd.fire(undefined); + return true // Consume, otherwise Monaco complains }; document.addEventListener('mousemove', mousemove); document.addEventListener('mouseup', end); + document.addEventListener("touchmove", mousemove); + document.addEventListener("touchend", end); + document.addEventListener("touchcancel", end); + return true // consume pull to refresh gesture }; sash.addEventListener('mousedown', onStart); + sash.addEventListener("touchstart", onStart); const sashItem: ISashItem = { container: sash, From 01544a995338037aca4858d968a3a9890260f1b5 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Mon, 12 Jun 2023 21:41:46 +0100 Subject: [PATCH 2/2] feat: touch support refactor and test - add tests - refactor methods to have a clear seperation between mouse and touch logic --- .../src/__tests__/splitview/splitview.spec.ts | 86 +++++++++++++++++++ .../dockview-core/src/splitview/splitview.ts | 57 ++++++++---- 2 files changed, 124 insertions(+), 19 deletions(-) diff --git a/packages/dockview-core/src/__tests__/splitview/splitview.spec.ts b/packages/dockview-core/src/__tests__/splitview/splitview.spec.ts index 7d14653d2..a93d7daf4 100644 --- a/packages/dockview-core/src/__tests__/splitview/splitview.spec.ts +++ b/packages/dockview-core/src/__tests__/splitview/splitview.spec.ts @@ -84,6 +84,8 @@ describe('splitview', () => { beforeEach(() => { container = document.createElement('div'); container.className = 'container'; + + jest.clearAllMocks(); }); test('vertical splitview', () => { @@ -610,6 +612,12 @@ describe('splitview', () => { splitview.addView(view1); splitview.addView(view2); + const addEventListenerSpy = jest.spyOn(document, 'addEventListener'); + const removeEventListenerSpy = jest.spyOn( + document, + 'removeEventListener' + ); + const sashElement = container .getElementsByClassName('sash') .item(0) as HTMLElement; @@ -623,6 +631,8 @@ describe('splitview', () => { // start the drag event fireEvent.mouseDown(sashElement, { clientX: 50, clientY: 100 }); + expect(addEventListenerSpy).toBeCalledTimes(5); + // during a sash drag the views should have pointer-events disabled expect(view1.element.parentElement!.style.pointerEvents).toBe('none'); expect(view2.element.parentElement!.style.pointerEvents).toBe('none'); @@ -638,6 +648,8 @@ describe('splitview', () => { // end the drag event fireEvent.mouseUp(document); + expect(removeEventListenerSpy).toBeCalledTimes(5); + // expect pointer-eventes on views to be restored expect(view1.element.parentElement!.style.pointerEvents).toBe(''); expect(view2.element.parentElement!.style.pointerEvents).toBe(''); @@ -645,5 +657,79 @@ describe('splitview', () => { fireEvent.mouseMove(document, { clientX: 100, clientY: 100 }); // expect no additional resizes expect([view1.size, view2.size]).toEqual([225, 175]); + // expect no additional document listeners + expect(addEventListenerSpy).toBeCalledTimes(5); + expect(removeEventListenerSpy).toBeCalledTimes(5); + }); + + test('dnd: touch events to move sash', () => { + const splitview = new Splitview(container, { + orientation: Orientation.HORIZONTAL, + proportionalLayout: false, + }); + splitview.layout(400, 500); + + const view1 = new Testview(0, 1000); + const view2 = new Testview(0, 1000); + + splitview.addView(view1); + splitview.addView(view2); + + const addEventListenerSpy = jest.spyOn(document, 'addEventListener'); + const removeEventListenerSpy = jest.spyOn( + document, + 'removeEventListener' + ); + + const sashElement = container + .getElementsByClassName('sash') + .item(0) as HTMLElement; + + // validate the expected state before drag + expect([view1.size, view2.size]).toEqual([200, 200]); + expect(sashElement).toBeTruthy(); + expect(view1.element.parentElement!.style.pointerEvents).toBe(''); + expect(view2.element.parentElement!.style.pointerEvents).toBe(''); + + // start the drag event + fireEvent.touchStart(sashElement, { + touches: [{ clientX: 50, clientY: 100 }], + }); + + expect(addEventListenerSpy).toBeCalledTimes(5); + + // during a sash drag the views should have pointer-events disabled + expect(view1.element.parentElement!.style.pointerEvents).toBe('none'); + expect(view2.element.parentElement!.style.pointerEvents).toBe('none'); + + // expect a delta move of 70 - 50 = 20 + fireEvent.touchMove(document, { + touches: [{ clientX: 70, clientY: 110 }], + }); + expect([view1.size, view2.size]).toEqual([220, 180]); + + // expect a delta move of 75 - 70 = 5 + fireEvent.touchMove(document, { + touches: [{ clientX: 75, clientY: 110 }], + }); + expect([view1.size, view2.size]).toEqual([225, 175]); + + // end the drag event + fireEvent.touchEnd(document); + + expect(removeEventListenerSpy).toBeCalledTimes(5); + + // expect pointer-eventes on views to be restored + expect(view1.element.parentElement!.style.pointerEvents).toBe(''); + expect(view2.element.parentElement!.style.pointerEvents).toBe(''); + + fireEvent.touchMove(document, { + touches: [{ clientX: 100, clientY: 100 }], + }); + // expect no additional resizes + expect([view1.size, view2.size]).toEqual([225, 175]); + // expect no additional document listeners + expect(addEventListenerSpy).toBeCalledTimes(5); + expect(removeEventListenerSpy).toBeCalledTimes(5); }); }); diff --git a/packages/dockview-core/src/splitview/splitview.ts b/packages/dockview-core/src/splitview/splitview.ts index a39c989e3..98dcce6a8 100644 --- a/packages/dockview-core/src/splitview/splitview.ts +++ b/packages/dockview-core/src/splitview/splitview.ts @@ -393,8 +393,17 @@ export class Splitview { const sash = document.createElement('div'); sash.className = 'sash'; - const onStart = (nativeEvent: MouseEvent | TouchEvent) => { - const event = nativeEvent instanceof TouchEvent ? nativeEvent.touches[0] : nativeEvent; + const onTouchStart = (event: TouchEvent) => { + event.preventDefault(); + const touch = event.touches[0]; + onStart(touch); + }; + + const onMouseDown = (event: MouseEvent) => { + onStart(event); + }; + + const onStart = (event: { clientX: number; clientY: number }) => { for (const item of this.viewItems) { item.enabled = false; } @@ -487,14 +496,25 @@ export class Splitview { size: snappedViewItem.size, }; } - // - const mousemove = (nativeMoveEvent: MouseEvent | TouchEvent) => { - const moveEvent = nativeMoveEvent instanceof TouchEvent ? nativeMoveEvent.touches[0] : nativeMoveEvent; + const onMouseMove = (event: MouseEvent) => { + reposition(event); + }; + + const onTouchMove = (event: TouchEvent) => { + event.preventDefault(); + const touch = event.touches[0]; + reposition(touch); + }; + + const reposition = (event: { + clientX: number; + clientY: number; + }) => { const current = this._orientation === Orientation.HORIZONTAL - ? moveEvent.clientX - : moveEvent.clientY; + ? event.clientX + : event.clientY; const delta = current - start; this.resize( @@ -523,31 +543,30 @@ export class Splitview { this.saveProportions(); - document.removeEventListener('mousemove', mousemove); + document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', end); - document.removeEventListener("touchmove", mousemove); - document.removeEventListener("touchend", end); - document.removeEventListener("touchcancel", end); + document.removeEventListener('touchmove', onTouchMove); + document.removeEventListener('touchend', end); + document.removeEventListener('touchcancel', end); this._onDidSashEnd.fire(undefined); - return true // Consume, otherwise Monaco complains }; - document.addEventListener('mousemove', mousemove); + document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', end); - document.addEventListener("touchmove", mousemove); - document.addEventListener("touchend", end); - document.addEventListener("touchcancel", end); - return true // consume pull to refresh gesture + document.addEventListener('touchmove', onTouchMove); + document.addEventListener('touchend', end); + document.addEventListener('touchcancel', end); }; - sash.addEventListener('mousedown', onStart); - sash.addEventListener("touchstart", onStart); + sash.addEventListener('mousedown', onMouseDown); + sash.addEventListener('touchstart', onTouchStart); const sashItem: ISashItem = { container: sash, disposable: () => { sash.removeEventListener('mousedown', onStart); + sash.removeEventListener('touchstart', onTouchStart); this.sashContainer.removeChild(sash); }, };