tippyjs-react
tippyjs-react copied to clipboard
Tippy is ignoring event.preventDefault while trying to combine the use with selection api
So I'm trying to create a medium like highlight with tippy and selection api. The issue i'm facing now is once tippy is being called out by selecting some text, I will lose the selection by interacting with Tippy. I'm trying to add event.preventDefault to the component inside content prop, but it seems like tippy is not responding to it. Is there anyway I can handle the event behavior myself on tippy?
Did you ever figure this out?
I'm facing the same issue, but with vanilla JS Tippy. It happens on Chrome, but not on Firefox. Also, it only happens when interactive is set to true.
My hacky solution so far was [...].
Edit:
I gave this another try and found what seems to be a good enough solution, if still not perfect. Tested on Chrome and Firefox (Desktop), Android Chrome (Mobile). I have never used React, so I don't know how easy it is to implement this on React. I'm sorry about that. I'm using vanilla JS.
The root of the problem:
Chrome doesn't like it when text selection overlaps with a pointer-events enabled Tippy (that is, interactive: true). It will glitch the selection, usually extending the selection all the way down (if the Tippy is appended to body), or to wherever the Tippy is in its parent element.
General solutions:
a) Keep the Tippy out of the way of the text selection. This can be done placing it above the selection and hoping people will not select it backwards (like Medium does). Or you can update the positioning on selection change, like a regular context menu.
This makes it harder for users to select the Tippy, but not impossible. So, it's a partial solution.
b) Set Tippy on "interactive: false" while selecting, true while clicking.
Also a partial solution, because on mobile you cannot (as far as I can tell) set it back to false before the first selection change on already selected text. Pointer down, touch start etc. do not fire when the user clicks the selection or the selection caret. You have to use selection change. In that case, if the first thing the user selects is the Tippy, you get the glitch.
Basic combined implementation:
Keep Tippy 'interactive: false' always. Via css, set "pointer-events" to "auto" on elements inside the Tippy only. Remember to have a border or something so the first selection doesn't get to the "pointer-events: auto" elements too quickly. Update position on selection change.
Clicks work just fine. The selection will usually not get to pointer-enabled elements (the cause of the glitch) before the position is updated. It is still possible to reproduce the glitch, though, if you move the selection fast enough. Probably not a big deal in most use cases, but I decided to combine it with my previous approach.
Extra steps:
-
Assign buttons' pointer-events property to a global CSS variable, something like "var(--btn-pointerevts)" (should also work changing the properties on the elements directly, I suppose, but that's probably slower). Start with the value "none".
-
Change "var(--btn-pointerevts)" to "auto" on: pointerup (if there's selected text, of course), contextmenu (if you're using prevent default on context menu, use it inside the function that handles the CSS change).
-
Change "var(--btn-pointerevts)" back to "none" on: pointerdown, selection change.
This worked really well in tests. In theory, it is still possible to reproduce the glitch on mobile if you start the selection, fire a pointerup event (e.g. long press, select a few words, and lift your finger off the screen) then restart the text selection dragging the selection-boundary caret over the pointer-events enabled elements before the position is updated (no pointerdown event is fired in this case, or any other event except for selection change as far as I can tell, unfortunately, so pointer-events will still be set to auto before the first selection change). You'd have to do it really fast, though.
An alternative approach would be to keep interactive always false (that is, pointer events always none), and try to capture clicks on buttons in some other way. Maybe using an event listener on the parentElement, then comparing click coordinates with the coordinates for the buttons. That would be kind of a hassle, though, so I'll be using my solution unless it proves to be unreliable in some way.
P.S. It's worth mentioning that Medium popup uses weird positioning to avoid this, but isn't always able to solve the problem. In corner cases, you can reproduce this glitch on Medium too. So, I'm betting there's no perfect easy solution for this.