Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't cancel gesture if touch is > 100 pixels away #2607

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/components/AppComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions src/components/MultiGesture.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -133,7 +133,7 @@ class MultiGesture extends React.Component<MultiGestureProps> {
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
Expand Down
26 changes: 26 additions & 0 deletions src/device/selection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -440,3 +440,29 @@ 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
}

/** 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

return x >= left && y >= top && x <= right && y <= bottom
}
24 changes: 24 additions & 0 deletions src/e2e/puppeteer/__tests__/gestures.ts
Original file line number Diff line number Diff line change
@@ -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)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@raineorshine @trevinhofmann I cannot for the life of me get puppeteer to simulate a selection while emulating a mobile browser. I'll give it another try on Monday, but please let me know if you have any experience getting it to work.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for trying! If it's difficult/impossible I'm okay skimping on test coverage here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you try creating a selection with touch events, or by directly accessing the browser selection API?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did try directly accessing the browser selection API, and found that it wouldn't hold onto the selection once it left the page.evaluate block. That might be worth another try, but I didn't have much luck the first time.

As for creating the selection with touch events, I tried to use a physical device to figure out how that would work, and it seemed like the only way to get a selection was to tap and then use the tooltip menu to "Select All". I don't think the tooltip menu appears in the Puppeteer emulator. If you know another, more reliable way to tap to create a a selection, please do let me know.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A double tap selects the word under the touch. I imagine you will run into the same issue though in which the selection is lost after the page.evaluate.

We will have to rely on manual testing for this one 👍

await paste(importText)

await drawHorizontalLineGesture()

// the command palette should open
const popupValue = await page.$('[data-testid=popup-value]')
expect(popupValue).toBeTruthy()
})
})
43 changes: 43 additions & 0 deletions src/e2e/puppeteer/helpers/gesture.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
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

/** Draw a horizontal line gesture at a certain y coordinate on the touch screen. */
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,
)