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

Interacting with Grammarly extension in a Dialog registers as an outside click #2055

Open
brianlovin opened this issue Mar 29, 2023 · 11 comments

Comments

@brianlovin
Copy link

Bug report

Current Behavior

We have a dialog where users compose new posts. In that composer, when people interact with the Grammarly browser extension, those interactions are registered as outside clicks and attempt to close the dialog.

Expected behavior

Grammarly overlay clicks should not register as outside clicks.

Reproducible example

Unfortunately, I'm having a hard time getting a reproducible example in CodeSandbox, for some reason Grammarly doesn't want to work in that context. Here's a video of the problem (note that we show a "Confirm delete draft" dialog if the user tries to close the composer when they have drafted content, so that confirmation dialog is being triggered when the Dialog is being told to close).

To test in production, you can:

  1. Install the Grammarly Chrome extension
  2. Go to https://app.campsite.design → sign in with Google
  3. Create a new post, type some content into the description field
  4. Interact with the Grammarly extension overlay
Screen.Shot.2023-03-29.at.10.10.45.mp4

Suggested solution

We had the same issue when we were using Headless UI but recently switched to Radix. Here is how the Headless UI team resolved the problem:

tailwindlabs/headlessui#2217

We added better support for shadow roots being valid containers for the Dialog component in tailwindlabs/headlessui#2079. This means that if you have a 3rd party (or 1st party) tool that uses the shadow DOM, then the Dialog used to close when interacting with those items.

This was solved by @thecrypticace in the tailwindlabs/headlessui#2079 PR.

But I found that there is a bug here with one of these:

Clicks from shadow roots inside the panel (keeps dialog open)
Clicks from shadow roots outside the panel (closes the dialog)
There is an issue with the Grammarly extension that injects a shadow root component inside the Dialog.Panel. Yet, clicking on it closes the Dialog.


### Your environment

<!-- Very important for us to help you debug. Please fill this out! -->

| Software         | Name(s) | Version |
| ---------------- | ------- | ------- |
| Radix Package(s) |     @radix-ui/react-dialog    |   1.0.3      |
| React            | n/a     |    18.2.0     |
| Browser          |   Chromium, Safari      |         |
| Assistive tech   |         |         |
| Node             | n/a     |         |
| npm/yarn         |         |         |
| Operating System |    macOS     |         |
@joaom00
Copy link
Contributor

joaom00 commented Mar 29, 2023

In the meantime, you can use this workaround #1280 (comment)

or this https://codesandbox.io/p/sandbox/throbbing-lake-84vkp7 (need to open the preview in a new tab to show the Grammarly)

@brianlovin
Copy link
Author

Perfect, that helps a ton! It's not a bulletproof solution, clicks in these red zones still register as "outside" but at least the inline suggestions will be taken care of 🙇‍♂️

Screen Shot 2023-03-29 at 11 52 23@2x

@joaom00
Copy link
Contributor

joaom00 commented Mar 29, 2023

Perfect, that helps a ton! It's not a bulletproof solution, clicks in these red zones still register as "outside" but at least the inline suggestions will be taken care of 🙇‍♂️

So I think this workaround will work better, no? https://codesandbox.io/p/sandbox/throbbing-lake-84vkp7

@brianlovin
Copy link
Author

brianlovin commented Mar 30, 2023

This works! TypeScript doesn't like me, but I'll see if I can find a way to type this out or use a different target property.

Screen Shot 2023-03-29 at 19 43 54@2x

Edit

Fixes if you cast:

const target = e.target as HTMLElement
const isGrammarly = target && target.hasAttribute('data-grammarly-shadow-root')

@kbrgl
Copy link

kbrgl commented Jun 15, 2023

I can confirm that this issue also occurs using the 1Password extension. When I press the 1Password lock icon at the right of a text field, the dialog is closed.

@tomdaniel-it
Copy link

Any update to this?

@tomdaniel-it
Copy link

Actually, this seems to have been fixed (at least for the Grammarly extension, unsure about 1Password)

@jacobemcken
Copy link

I still experience dialogs being closed in Firefox 116 with the latest Grammarly extension when interacting with the Grammarly extension.

At least Firefox tells me there are no updates for Grammarly. This is the extension I am using:
https://addons.mozilla.org/en-US/firefox/addon/grammarly-1/

@jacobemcken
Copy link

Chromium 111.0.5563.64 with the latest Grammarly extension also has issues (though as opposed to Grammarly in Firefox), I can choose spelling suggestions without it closing the dialog. But clicking on the green circle icon or the red circle with the number of spelling issues still closes the dialog.

@benoitgrelard
Copy link
Collaborator

Related to #1859

@anatolzak
Copy link

Hey @benoitgrelard,

I was able to solve this issue in Melt UI melt-ui/melt-ui#1114.

Radix

Screen.Recording.2024-05-09.at.4.03.52.PM.mov

Melt

Screen.Recording.2024-05-09.at.4.04.29.PM.mov

The issue here is that the DismissableLayer component should allow external JS to stop propagation of an interaction event. In the videos I provided, 1Password actually calls e.stopPropagation() on mouse events but Radix does not currently handle that.

I want to explain what I implemented in Melt and see if it makes sense to you before I create a PR for Radix.

The goal is to allow external JS to intercept any interaction event which should prevent us from calling onDismiss().

The challenge is that JS still triggers various events even if you intercept other events. For example, intercepting pointerdown externally will still not prevent pointerup and click events from triggering. Intercepting a mouse event does not prevent pointer events and click events from triggering.

So the solution I came up with is to keep track of whether any events were intercepted, and only call onDismiss if no events were intercepted.

And here is how I figured out how to check if any event was intercepted.

For each event type (pointerdown, pointerup, mousedown, mouseup, touchstart, touchend, click), I attach 2 event listeners--one for the capturing phase, and the other for the bubbling phase. In the capture phase, I mark the event type as intercepted, and then if the event type is called during the bubbling phase, I mark the event type as not intercepted.

I also only call onDismiss at the end of an interaction to allow intercepting an end-interaction event. Currently Radix calls onDismiss after pointerdown, which would not allow externally intercepting an end-interaction event.

I also debounce the start-interaction event handler to allow other start-interaction event handles to mark an event as non intercepted.

I detailed all of this in the Melt UI PR melt-ui/melt-ui#1114. Let me know if this is something that makes sense to you and I will be happy to create a PR :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants