adobe / react-spectrum

A collection of libraries and tools that help you build adaptive, accessible, and robust user experiences.
https://react-spectrum.adobe.com
Apache License 2.0
12.61k stars 1.09k forks source link

useSelect popup behavior is broken on Firefox when using a graphic tablet #4753

Open s-h-a-d-o-w opened 1 year ago

s-h-a-d-o-w commented 1 year ago

🐛 Bug Report

Opening/closing the select usually doesn't work on Firefox, when using a Wacom Intuos 4. (Mouse works fine.) Video evidence (Note that towards the end, after clicking a lot of times, the options do briefly pop up): https://www.loom.com/share/39149438ef9f4ed4979234ca4ef3a057?sid=b62924c7-2864-4a16-ac09-b76149055d96

🤔 Expected Behavior

Should behave like a select does, like it does on Chrome.

💻 Code Sample

https://react-spectrum.adobe.com/react-aria/useSelect.html#example

🌍 Your Environment

Software Version(s)
react-spectrum Docs
Browser Firefox 114.0.2
Operating System Windows 11
LFDanLu commented 1 year ago

I tried to reproduce on my Windows machine as well on Firefox 115 but wasn't able to: https://www.loom.com/share/846879293a4c493487d7e22d43e7c6c8. Does this reproduce consistently or only once in while?

s-h-a-d-o-w commented 1 year ago

When trying to reproduce this again just now, I realized that it actually only happens when using my wacom tablet. I changed the title and my OP accordingly.

It's very strange though because I've been using it in combination with Firefox on almost a daily basis for a couple of years now. The only problem I have here and there are custom scrollbars, like in the react.dev API docs. But there, it's consistently broken on both Firefox and Chrome.

snowystinger commented 1 year ago

Could you provide us with the event details from a click with the wacom tablet?

Open this codesandbox https://codesandbox.io/s/dreamy-gates-lkjhn7?file=/src/App.js and use the open in a new Tab button above the preview. Then open dev tools for the console then click on the red box using your wacom tablet

We do some detection around various parameters on the event to determine if we're handling a virtual click from VoiceOver or from a mouse or from touch. I'm wondering if the Wacom tablet is doing something we are accounting for.

Thanks!

LFDanLu commented 1 year ago

When trying to reproduce this again just now, I realized that it actually only happens when using my wacom tablet.

In that case, https://github.com/adobe/react-spectrum/issues/4422 is probably related as well, there is a hacky workaround mentioned in the comments there. Definitely give us the event details as mentioned by @snowystinger above, will help us see if there are any adjustments we can make to our virtual tap detection.

s-h-a-d-o-w commented 1 year ago

Here you are. Clicked 3 times, looks like always the same sequence of events:

onPointerDown 
Object { _reactName: "onPointerDown", _targetInst: null, type: "pointerdown", nativeEvent: pointerdown, target: div.App, currentTarget: null, eventPhase: 3, bubbles: true, cancelable: true, timeStamp: 61966, … }
index.js:27:25
onMouseDown 
Object { _reactName: "onMouseDown", _targetInst: null, type: "mousedown", nativeEvent: mousedown, target: div.App, currentTarget: null, eventPhase: 3, bubbles: true, cancelable: true, timeStamp: 61966, … }
index.js:27:25
onPointerUp 
Object { _reactName: "onPointerUp", _targetInst: null, type: "pointerup", nativeEvent: pointerup, target: div.App, currentTarget: null, eventPhase: 3, bubbles: true, cancelable: true, timeStamp: 62029, … }
index.js:27:25
onMouseUp 
Object { _reactName: "onMouseUp", _targetInst: null, type: "mouseup", nativeEvent: mouseup, target: div.App, currentTarget: null, eventPhase: 3, bubbles: true, cancelable: true, timeStamp: 62029, … }
index.js:27:25
onClick 
Object { _reactName: "onClick", _targetInst: null, type: "click", nativeEvent: click, target: div.App, currentTarget: null, eventPhase: 3, bubbles: true, cancelable: true, timeStamp: 62029, … }
index.js:27:25

onPointerDown 
Object { _reactName: "onPointerDown", _targetInst: null, type: "pointerdown", nativeEvent: pointerdown, target: div.App, currentTarget: null, eventPhase: 3, bubbles: true, cancelable: true, timeStamp: 65544, … }
index.js:27:25
onMouseDown 
Object { _reactName: "onMouseDown", _targetInst: null, type: "mousedown", nativeEvent: mousedown, target: div.App, currentTarget: null, eventPhase: 3, bubbles: true, cancelable: true, timeStamp: 65544, … }
index.js:27:25
onPointerUp 
Object { _reactName: "onPointerUp", _targetInst: null, type: "pointerup", nativeEvent: pointerup, target: div.App, currentTarget: null, eventPhase: 3, bubbles: true, cancelable: true, timeStamp: 65623, … }
index.js:27:25
onMouseUp 
Object { _reactName: "onMouseUp", _targetInst: null, type: "mouseup", nativeEvent: mouseup, target: div.App, currentTarget: null, eventPhase: 3, bubbles: true, cancelable: true, timeStamp: 65623, … }
index.js:27:25
onClick 
Object { _reactName: "onClick", _targetInst: null, type: "click", nativeEvent: click, target: div.App, currentTarget: null, eventPhase: 3, bubbles: true, cancelable: true, timeStamp: 65623, … }
index.js:27:25

onPointerDown 
Object { _reactName: "onPointerDown", _targetInst: null, type: "pointerdown", nativeEvent: pointerdown, target: div.App, currentTarget: null, eventPhase: 3, bubbles: true, cancelable: true, timeStamp: 66357, … }
index.js:27:25
onMouseDown 
Object { _reactName: "onMouseDown", _targetInst: null, type: "mousedown", nativeEvent: mousedown, target: div.App, currentTarget: null, eventPhase: 3, bubbles: true, cancelable: true, timeStamp: 66357, … }
index.js:27:25
onPointerUp 
Object { _reactName: "onPointerUp", _targetInst: null, type: "pointerup", nativeEvent: pointerup, target: div.App, currentTarget: null, eventPhase: 3, bubbles: true, cancelable: true, timeStamp: 66435, … }
index.js:27:25
onMouseUp 
Object { _reactName: "onMouseUp", _targetInst: null, type: "mouseup", nativeEvent: mouseup, target: div.App, currentTarget: null, eventPhase: 3, bubbles: true, cancelable: true, timeStamp: 66435, … }
index.js:27:25
onClick 
Object { _reactName: "onClick", _targetInst: null, type: "click", nativeEvent: click, target: div.App, currentTarget: null, eventPhase: 3, bubbles: true, cancelable: true, timeStamp: 66435, … }
index.js:27:25
s-h-a-d-o-w commented 1 year ago

Since it's only 5 events, I also expanded them and copied the data. I'm not really able to clean it up though (I'm using a tablet for a reason - RSI...), so... it's unfortunately a bit messy:

onPointerDown 
Object { _reactName: "onPointerDown", _targetInst: null, type: "pointerdown", nativeEvent: pointerdown, target: div.App
, currentTarget: null, eventPhase: 3, bubbles: true, cancelable: true, timeStamp: 61966, … }
​
_reactName: "onPointerDown"
​
_targetInst: null
​
altKey: false
​
bubbles: true
​
button: 0
​
buttons: 1
​
cancelable: true
​
clientX: 62
​
clientY: 50
​
ctrlKey: false
​
currentTarget: null
​
defaultPrevented: false
​
detail: 0
​
eventPhase: 3
​
getModifierState: function modifierStateGetter(keyArg)
​
height: 1
​
isDefaultPrevented: function functionThatReturnsFalse()
​
isPrimary: true
​
isPropagationStopped: function functionThatReturnsFalse()
​
isTrusted: true
​
metaKey: false
​
movementX: 0
​
movementY: 0
​
nativeEvent: pointerdown { target: div.App
, buttons: 1, clientX: 62, … }
​
pageX: 62
​
pageY: 50
​
pointerId: 8
​
pointerType: "mouse"
​
pressure: 0.5
​
relatedTarget: null
​
screenX: 833
​
screenY: 323
​
shiftKey: false
​
tangentialPressure: 0
​
target: <div class="App" style="width: 100px; height: 10… border: 1px solid red;">
​
tiltX: 0
​
tiltY: 0
​
timeStamp: 61966
​
twist: 0
​
type: "pointerdown"
​
view: Window https://lkjhn7.csb.app/
​
width: 1
​
<prototype>: Object { preventDefault: preventDefault(), stopPropagation: stopPropagation(), persist: persist()
, … }
​​
constructor: function SyntheticBaseEvent(reactName, reactEventType, targetInst, nativeEvent, nativeEventTarget)​​
isPersistent: function functionThatReturnsTrue()​​
persist: function persist()​​
preventDefault: function preventDefault()​​
stopPropagation: function stopPropagation()​​
<prototype>: Object { … }
index.js:27:25
onMouseDown 
Object { _reactName: "onMouseDown", _targetInst: null, type: "mousedown", nativeEvent: mousedown, target: div.App
, currentTarget: null, eventPhase: 3, bubbles: true, cancelable: true, timeStamp: 61966, … }
​
_reactName: "onMouseDown"
​
_targetInst: null
​
altKey: false
​
bubbles: true
​
button: 0
​
buttons: 1
​
cancelable: true
​
clientX: 62
​
clientY: 50
​
ctrlKey: false
​
currentTarget: null
​
defaultPrevented: false
​
detail: 1
​
eventPhase: 3
​
getModifierState: function modifierStateGetter(keyArg)​
isDefaultPrevented: function functionThatReturnsFalse()​
isPropagationStopped: function functionThatReturnsFalse()
​
isTrusted: true
​
metaKey: false
​
movementX: 0
​
movementY: 0
​
nativeEvent: mousedown { target: div.App
, clientX: 62, clientY: 50, … }
​
pageX: 62
​
pageY: 50
​
relatedTarget: null
​
screenX: 833
​
screenY: 323
​
shiftKey: false
​
target: <div class="App" style="width: 100px; height: 10… border: 1px solid red;">
​
timeStamp: 61966
​
type: "mousedown"
​
view: Window https://lkjhn7.csb.app/
​
<prototype>: Object { preventDefault: preventDefault(), stopPropagation: stopPropagation(), persist: persist()
, … }
index.js:27:25
onPointerUp 
Object { _reactName: "onPointerUp", _targetInst: null, type: "pointerup", nativeEvent: pointerup, target: div.App
, currentTarget: null, eventPhase: 3, bubbles: true, cancelable: true, timeStamp: 62029, … }
​
_reactName: "onPointerUp"
​
_targetInst: null
​
altKey: false
​
bubbles: true
​
button: 0
​
buttons: 0
​
cancelable: true
​
clientX: 62
​
clientY: 50
​
ctrlKey: false
​
currentTarget: null
​
defaultPrevented: false
​
detail: 0
​
eventPhase: 3
​
getModifierState: function modifierStateGetter(keyArg)
​
height: 1
​
isDefaultPrevented: function functionThatReturnsFalse()
​
isPrimary: true
​
isPropagationStopped: function functionThatReturnsFalse()
​
isTrusted: true
​
metaKey: false
​
movementX: 0
​
movementY: 0
​
nativeEvent: pointerup { target: div.App
, buttons: 0, clientX: 62, … }
​
pageX: 62
​
pageY: 50
​
pointerId: 17
​
pointerType: "mouse"
​
pressure: 0
​
relatedTarget: null
​
screenX: 833
​
screenY: 322
​
shiftKey: false
​
tangentialPressure: 0
​
target: <div class="App" style="width: 100px; height: 10… border: 1px solid red;">
​
tiltX: 0
​
tiltY: 0
​
timeStamp: 62029
​
twist: 0
​
type: "pointerup"
​
view: Window https://lkjhn7.csb.app/
​
width: 1
​
<prototype>: Object { preventDefault: preventDefault(), stopPropagation: stopPropagation(), persist: persist()
, … }
index.js:27:25
onMouseUp 
Object { _reactName: "onMouseUp", _targetInst: null, type: "mouseup", nativeEvent: mouseup, target: div.App
, currentTarget: null, eventPhase: 3, bubbles: true, cancelable: true, timeStamp: 62029, … }
​
_reactName: "onMouseUp"
​
_targetInst: null
​
altKey: false
​
bubbles: true
​
button: 0
​
buttons: 0
​
cancelable: true
​
clientX: 62
​
clientY: 50
​
ctrlKey: false
​
currentTarget: null
​
defaultPrevented: false
​
detail: 1
​
eventPhase: 3
​
getModifierState: function modifierStateGetter(keyArg)​
isDefaultPrevented: function functionThatReturnsFalse()​
isPropagationStopped: function functionThatReturnsFalse()
​
isTrusted: true
​
metaKey: false
​
movementX: 0
​
movementY: 0
​
nativeEvent: mouseup { target: div.App
, clientX: 62, clientY: 50, … }
​
pageX: 62
​
pageY: 50
​
relatedTarget: null
​
screenX: 833
​
screenY: 322
​
shiftKey: false
​
target: <div class="App" style="width: 100px; height: 10… border: 1px solid red;">
​
timeStamp: 62029
​
type: "mouseup"
​
view: Window https://lkjhn7.csb.app/
​
<prototype>: Object { preventDefault: preventDefault(), stopPropagation: stopPropagation(), persist: persist()
, … }
index.js:27:25
onClick 
Object { _reactName: "onClick", _targetInst: null, type: "click", nativeEvent: click, target: div.App
, currentTarget: null, eventPhase: 3, bubbles: true, cancelable: true, timeStamp: 62029, … }
​
_reactName: "onClick"
​
_targetInst: null
​
altKey: false
​
bubbles: true
​
button: 0
​
buttons: 0
​
cancelable: true
​
clientX: 62
​
clientY: 50
​
ctrlKey: false
​
currentTarget: null
​
defaultPrevented: false
​
detail: 1
​
eventPhase: 3
​
getModifierState: function modifierStateGetter(keyArg)​
isDefaultPrevented: function functionThatReturnsFalse()​
isPropagationStopped: function functionThatReturnsFalse()
​
isTrusted: true
​
metaKey: false
​
movementX: 0
​
movementY: 0
​
nativeEvent: click { target: div.App
, clientX: 62, clientY: 50, … }
​
pageX: 62
​
pageY: 50
​
relatedTarget: null
​
screenX: 833
​
screenY: 322
​
shiftKey: false
​
target: <div class="App" style="width: 100px; height: 10… border: 1px solid red;">
​
timeStamp: 62029
​
type: "click"
​
view: Window https://lkjhn7.csb.app/
​
<prototype>: Object { preventDefault: preventDefault(), stopPropagation: stopPropagation(), persist: persist(), … }
schurchleycci commented 3 weeks ago

Are there any updates to this issue? We just received a report from a user saying that tapping a Button with a tablet in Firefox is broken for them, which I'm assuming is the same problem as the one here.