From dbefbf154ea785c657b58dc1573e2fc16cae6b07 Mon Sep 17 00:00:00 2001 From: Ethan James Date: Tue, 19 Nov 2024 16:19:34 -0800 Subject: [PATCH 1/5] getBoundingClientRect selection method --- src/device/selection.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/device/selection.ts b/src/device/selection.ts index 659fdbbb6e..461a541d03 100644 --- a/src/device/selection.ts +++ b/src/device/selection.ts @@ -440,3 +440,14 @@ export const html = () => { const currentHtml = div.innerHTML return currentHtml } + +/** Returns the bounding rectangle for the current browser selection */ +export const getBoundingClientRect = () => { + const selection = window.getSelection() + + if (selection && selection.rangeCount) { + return selection.getRangeAt(0).getBoundingClientRect() + } + + return null +} From a896fb9529b1bd380225f8d9e7354c12cdfba310 Mon Sep 17 00:00:00 2001 From: Ethan James Date: Thu, 21 Nov 2024 16:05:58 -0800 Subject: [PATCH 2/5] Don't cancel gesture if touch is > 100 pixels away --- src/components/AppComponent.tsx | 4 +-- src/components/MultiGesture.tsx | 4 +-- src/device/selection.ts | 17 ++++++++++- src/e2e/puppeteer/helpers/gesture.ts | 42 ++++++++++++++++++++++++++++ 4 files changed, 62 insertions(+), 5 deletions(-) create mode 100644 src/e2e/puppeteer/helpers/gesture.ts diff --git a/src/components/AppComponent.tsx b/src/components/AppComponent.tsx index 2306275bc4..be2b3976ba 100644 --- a/src/components/AppComponent.tsx +++ b/src/components/AppComponent.tsx @@ -76,8 +76,8 @@ const useDisableLongPressToSelect = () => { } /** Cancel gesture if there is an active text selection, drag, modal, or sidebar. */ -const shouldCancelGesture = (): boolean => - (selection.isActive() && !selection.isCollapsed()) || +const shouldCancelGesture = (x?: number, y?: number): boolean => + (x && y && selection.isNear(x, y)) || store.getState().dragInProgress || !!store.getState().showModal || store.getState().showSidebar diff --git a/src/components/MultiGesture.tsx b/src/components/MultiGesture.tsx index b8c0b52df3..ceabd6a738 100644 --- a/src/components/MultiGesture.tsx +++ b/src/components/MultiGesture.tsx @@ -48,7 +48,7 @@ type MultiGestureProps = PropsWithChildren<{ // related: https://github.com/cybersemics/em/issues/1268 minDistance?: number /** A hook that is called on touchstart if the user is in the gesture zone. If it returns true, the gesture is abandoned. Otherwise scrolling is disabled and a gesture may be entered. */ - shouldCancelGesture?: () => boolean + shouldCancelGesture?: (x?: number, y?: number) => boolean }> const TOOLBAR_HEIGHT = 50 @@ -133,7 +133,7 @@ class MultiGesture extends React.Component { const scrollZoneWidth = viewport.scrollZoneWidth const isInGestureZone = (this.leftHanded ? x > scrollZoneWidth : x < viewport.innerWidth - scrollZoneWidth) && y > TOOLBAR_HEIGHT - if (isInGestureZone && !props.shouldCancelGesture?.()) { + if (isInGestureZone && !props.shouldCancelGesture?.(x, y)) { this.disableScroll = true } else { this.abandon = true diff --git a/src/device/selection.ts b/src/device/selection.ts index 461a541d03..2e634edd33 100644 --- a/src/device/selection.ts +++ b/src/device/selection.ts @@ -441,7 +441,7 @@ export const html = () => { return currentHtml } -/** Returns the bounding rectangle for the current browser selection */ +/** Returns the bounding rectangle for the current browser selection. */ export const getBoundingClientRect = () => { const selection = window.getSelection() @@ -451,3 +451,18 @@ export const getBoundingClientRect = () => { return null } + +/** Returns true if the point is within 100 pixels of the browser selection. */ +export const isNear = (x: number, y: number): boolean => { + if (!isActive() || isCollapsed()) return false + + const rect = getBoundingClientRect() + if (!rect) return false + + const left = rect.left - 100 + const right = rect.right + 100 + const top = rect.top - 100 + const bottom = rect.bottom + 100 + console.log(x, y, left, right, top, bottom) + return x >= left && y >= top && x <= right && y <= bottom +} diff --git a/src/e2e/puppeteer/helpers/gesture.ts b/src/e2e/puppeteer/helpers/gesture.ts new file mode 100644 index 0000000000..67540c2568 --- /dev/null +++ b/src/e2e/puppeteer/helpers/gesture.ts @@ -0,0 +1,42 @@ +import { page } from '../setup' + +/** Draw a gesture on the screen. */ +const gesture = async (points: { x: number; y: number }[], complete: boolean = true) => { + const start = points[0] + + await page.touchscreen.touchStart(start.x, start.y) + + for (let i = 1; i < points.length; i++) await page.touchscreen.touchMove(points[i].x, points[i].y) + + if (complete) await page.touchscreen.touchEnd() +} + +export default gesture + +export const drawHorizontalLineGesture = (y: number = 100) => + gesture( + [ + { x: 100, y }, + { x: 110, y }, + { x: 120, y }, + { x: 130, y }, + { x: 140, y }, + { x: 150, y }, + { x: 160, y }, + { x: 170, y }, + { x: 180, y }, + { x: 190, y }, + { x: 200, y }, + { x: 210, y }, + { x: 220, y }, + { x: 230, y }, + { x: 240, y }, + { x: 250, y }, + { x: 260, y }, + { x: 270, y }, + { x: 280, y }, + { x: 290, y }, + { x: 300, y }, + ], + false, + ) From 0624e59d6067bb5551a087eea4dc184713a0203a Mon Sep 17 00:00:00 2001 From: Ethan James Date: Thu, 21 Nov 2024 16:07:10 -0800 Subject: [PATCH 3/5] Add JSDoc to drawHorizontalLineGesture helper --- src/e2e/puppeteer/helpers/gesture.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/e2e/puppeteer/helpers/gesture.ts b/src/e2e/puppeteer/helpers/gesture.ts index 67540c2568..c9c1d362c6 100644 --- a/src/e2e/puppeteer/helpers/gesture.ts +++ b/src/e2e/puppeteer/helpers/gesture.ts @@ -13,6 +13,7 @@ const gesture = async (points: { x: number; y: number }[], complete: boolean = t export default gesture +/** Draw a horizontal line gesture at a certain y coordinate on the touch screen. */ export const drawHorizontalLineGesture = (y: number = 100) => gesture( [ From 003253fdeb1979e397d8c754600deee9978b6b29 Mon Sep 17 00:00:00 2001 From: Ethan James Date: Thu, 21 Nov 2024 16:07:57 -0800 Subject: [PATCH 4/5] Remove extraneous console.log --- src/device/selection.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/device/selection.ts b/src/device/selection.ts index 2e634edd33..4e4c672f6b 100644 --- a/src/device/selection.ts +++ b/src/device/selection.ts @@ -463,6 +463,6 @@ export const isNear = (x: number, y: number): boolean => { const right = rect.right + 100 const top = rect.top - 100 const bottom = rect.bottom + 100 - console.log(x, y, left, right, top, bottom) + return x >= left && y >= top && x <= right && y <= bottom } From 332c94611c48f89109af6f2bfc4e6add6d6c51d8 Mon Sep 17 00:00:00 2001 From: Ethan James Date: Thu, 21 Nov 2024 16:15:54 -0800 Subject: [PATCH 5/5] Test for drawing a gesture without a selection --- src/e2e/puppeteer/__tests__/gestures.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/e2e/puppeteer/__tests__/gestures.ts diff --git a/src/e2e/puppeteer/__tests__/gestures.ts b/src/e2e/puppeteer/__tests__/gestures.ts new file mode 100644 index 0000000000..d39936f1e1 --- /dev/null +++ b/src/e2e/puppeteer/__tests__/gestures.ts @@ -0,0 +1,24 @@ +import { KnownDevices } from 'puppeteer' +import { drawHorizontalLineGesture } from '../helpers/gesture' +import paste from '../helpers/paste' +import { page } from '../setup' + +vi.setConfig({ testTimeout: 20000, hookTimeout: 20000 }) +const iPhone = KnownDevices['iPhone 15 Pro'] + +describe('gestures', () => { + it.skip('when starting a gesture, the command palette will open', async () => { + const cursor = 'Hello' + const importText = ` + - ${cursor}` + + await page.emulate(iPhone) + await paste(importText) + + await drawHorizontalLineGesture() + + // the command palette should open + const popupValue = await page.$('[data-testid=popup-value]') + expect(popupValue).toBeTruthy() + }) +})