Make coarse pointer check dynamic (#3572)

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

<!--  Please select a 'Scope' label ️ -->

- [ ] `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

<!--  Please select a 'Type' label ️ -->

- [ ] `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 <mimecuvalo@gmail.com>
Co-authored-by: Steve Ruiz <steveruizok@gmail.com>
This commit is contained in:
Taha 2024-04-27 12:14:23 +01:00 committed by GitHub
parent 608f0210a0
commit 7442456d85
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -4,27 +4,68 @@ import { useEditor } from './useEditor'
/** @internal */ /** @internal */
export function useCoarsePointer() { export function useCoarsePointer() {
const editor = useEditor() const editor = useEditor()
useEffect(() => { 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 // 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 // detect coarse VS fine pointer. For now, let's assume that you have a fine
// pointer if you're on Firefox on desktop. // pointer if you're on Firefox on desktop.
if ( const isForcedFinePointer =
editor.environment.isFirefox && editor.environment.isFirefox && !editor.environment.isAndroid && !editor.environment.isIos
!editor.environment.isAndroid &&
!editor.environment.isIos const handleMediaQueryChange = () => {
) { const next = isForcedFinePointer ? false : mql.matches // get the value from the media query
editor.updateInstanceState({ isCoarsePointer: false }) if (isCoarse !== next) return // bail if the value hasn't changed
return isCoarse = next // update the local value
editor.updateInstanceState({ isCoarsePointer: next }) // update the value in state
} }
if (window.matchMedia) {
const mql = window.matchMedia('(pointer: coarse)') if (mql) {
const handler = () => { // set up the listener
editor.updateInstanceState({ isCoarsePointer: !!mql.matches }) mql.addEventListener('change', handleMediaQueryChange)
}
handler() // and run the handler once to set the initial value
handleMediaQueryChange()
}
return () => {
window.removeEventListener('touchstart', handleTouchStart)
window.removeEventListener('mousemove', handleMouseMove)
if (mql) { if (mql) {
mql.addEventListener('change', handler) mql.removeEventListener('change', handleMediaQueryChange)
return () => mql.removeEventListener('change', handler)
} }
} }
}, [editor]) }, [editor])