From 7442456d85bbe816ee01cc4cfccf32b765165e6c Mon Sep 17 00:00:00 2001 From: Taha <98838967+Taha-Hassan-Git@users.noreply.github.com> Date: Sat, 27 Apr 2024 12:14:23 +0100 Subject: [PATCH] Make coarse pointer check dynamic (#3572) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a device has both a touch screen and a mouse, pointer: coarse evaluates to false and then doesn't change even if the user begins to use the touch screen. In this PR pointer: coarse is replaced with any-pointer: coarse, which checks if a touchscreen input is available. Event listeners for touchstart and mousemove update isCoursePointer dynamically. So if the user begins to move the mouse it changes to pointer: fine, if they then touch the screen the pointer is returned to coarse. https://github.com/tldraw/tldraw/assets/98838967/fb86bb44-ec11-4161-bb2f-0e8c3ee83eb6 ### Change Type - [ ] `sdk` — Changes the tldraw SDK - [x] `dotcom` — Changes the tldraw.com web app - [ ] `docs` — Changes to the documentation, examples, or templates. - [ ] `vs code` — Changes to the vscode plugin - [ ] `internal` — Does not affect user-facing stuff - [ ] `bugfix` — Bug fix - [ ] `feature` — New feature - [x] `improvement` — Improving existing features - [ ] `chore` — Updating dependencies, other boring stuff - [ ] `galaxy brain` — Architectural changes - [ ] `tests` — Changes to any test code - [ ] `tools` — Changes to infrastructure, CI, internal scripts, debugging tools, etc. - [ ] `dunno` — I don't know ### Test Plan 1. Load tldraw on a device with both coarse and fine pointer inputs avaiable (e.g. an ipad with a keyboard and trackpad) 2. Switch between using the mouse and touch screen. 3. Handles on shapes should update dynamically. ### Release Notes - Add a brief release note for your PR here. --------- Co-authored-by: Mime Čuvalo Co-authored-by: Steve Ruiz --- .../editor/src/lib/hooks/useCoarsePointer.ts | 71 +++++++++++++++---- 1 file changed, 56 insertions(+), 15 deletions(-) diff --git a/packages/editor/src/lib/hooks/useCoarsePointer.ts b/packages/editor/src/lib/hooks/useCoarsePointer.ts index f51c8cf94..a0c279d98 100644 --- a/packages/editor/src/lib/hooks/useCoarsePointer.ts +++ b/packages/editor/src/lib/hooks/useCoarsePointer.ts @@ -4,27 +4,68 @@ import { useEditor } from './useEditor' /** @internal */ export function useCoarsePointer() { const editor = useEditor() + useEffect(() => { + // We'll track our own state for the pointer type + let isCoarse = editor.getInstanceState().isCoarsePointer + + // 1. + // We'll use touch events / mouse events to detect coarse pointer. + + // When the user touches the screen, we assume they have a coarse pointer + const handleTouchStart = () => { + if (isCoarse) return + isCoarse = true + editor.updateInstanceState({ isCoarsePointer: true }) + } + + // When the user moves the mouse, we assume they have a fine pointer + const handleMouseMove = () => { + if (!isCoarse) return + isCoarse = false + editor.updateInstanceState({ isCoarsePointer: false }) + } + + // Set up the listeners for touch and mouse events + window.addEventListener('touchstart', handleTouchStart) + window.addEventListener('mousemove', handleMouseMove) + + // 2. + // We can also use the media query to detect / set the initial pointer type + // and update the state if the pointer type changes. + + // We want the touch / mouse events to run even if the browser does not + // support matchMedia. We'll have to handle the media query changes + // conditionally in the code below. + const mql = window.matchMedia && window.matchMedia('(any-pointer: coarse)') + // This is a workaround for a Firefox bug where we don't correctly // detect coarse VS fine pointer. For now, let's assume that you have a fine // pointer if you're on Firefox on desktop. - if ( - editor.environment.isFirefox && - !editor.environment.isAndroid && - !editor.environment.isIos - ) { - editor.updateInstanceState({ isCoarsePointer: false }) - return + const isForcedFinePointer = + editor.environment.isFirefox && !editor.environment.isAndroid && !editor.environment.isIos + + const handleMediaQueryChange = () => { + const next = isForcedFinePointer ? false : mql.matches // get the value from the media query + if (isCoarse !== next) return // bail if the value hasn't changed + isCoarse = next // update the local value + editor.updateInstanceState({ isCoarsePointer: next }) // update the value in state } - if (window.matchMedia) { - const mql = window.matchMedia('(pointer: coarse)') - const handler = () => { - editor.updateInstanceState({ isCoarsePointer: !!mql.matches }) - } - handler() + + if (mql) { + // set up the listener + mql.addEventListener('change', handleMediaQueryChange) + + // and run the handler once to set the initial value + handleMediaQueryChange() + } + + return () => { + window.removeEventListener('touchstart', handleTouchStart) + window.removeEventListener('mousemove', handleMouseMove) + if (mql) { - mql.addEventListener('change', handler) - return () => mql.removeEventListener('change', handler) + mql.removeEventListener('change', handleMediaQueryChange) } } }, [editor])