Semantic-Org / Semantic-UI-React

The official Semantic-UI-React integration
https://react.semantic-ui.com
MIT License
13.23k stars 4.05k forks source link

Dropdown's do not close as expected when mounted in Shadow DOM #4184

Open sawka opened 3 years ago

sawka commented 3 years ago

Bug Report

While creating an app using web components (and Shadow DOM), I came across this issue where Dropdowns do not close after selecting an item. I debugged the problem, and tracked the root cause of the Dropdown not closing to the doesNodeContainClick function. Because event.target is the "host" node, not the shadow dom node, it returns false when it should return true.

The problem is in the file: Semantic-UI-React/src/lib/doesNodeContainClick.js , line 21:

return node.contains(e.target)

There is a quick fix for this. Instead of using e.target, we can use e.composedPath() - https://developer.mozilla.org/en-US/docs/Web/API/Event/composedPath . Iterate through composed path and check to see if you get a match with node:

let cpath = e.composedPath() for (let i=0; i<cpath.length; i++) { if (node == cpath[i]) { return true; } } return false;

Steps

Create a Dropdown in the Shadow DOM. Click the dropdown Select an Item

Expected Result

After the item is selected, the dropdown should close.

Actual Result

The dropdown did not close (it stays open).

Version

2.0.3

Testcase

https://codesandbox.io/s/semantic-ui-react-forked-y493p?file=/index.js

welcome[bot] commented 3 years ago

👋 Thanks for opening your first issue here! If you're reporting a 🐞 bug, please make sure you've completed all the fields in the issue template so we can best help.

We get a lot of issues on this repo, so please be patient and we will get back to you as soon as we can.

sawka commented 3 years ago

Sorry about the edits, I accidentally submitted the issue before I was done writing it. The reason why this call to doesNodeContainClick has the bug (where other calls do not) is because it is getting a DOMEvent, not a synthetic event. AFAIK It is getting called from src/modules/Dropdown/Dropdown.js line 394 from a real document click handler (not a react click handler).

The reason why the dropdown stays open is because the bubbled click closes the dropdown (because doesNodeContainClick returns false). Then the real Dropdown onClick handler fires and re-opens the dropdown.

luijar commented 1 year ago

Also seeing this issue. It's unfortunate, as most other components work inside a Shadow DOM root. We noticed that if you blur the underlying HTML element after the onChange fires (by calling setTimeout), it works (although it's an ugly workaround).

neutraali commented 1 year ago

Also seeing this issue when dealing with Web Components, but in a different fashion. When clicking on an item, the dropdown closes without resolving any listeners inside (for example onChange). Setting closeOnBlur={true} seems to fix the problem, but obviously creates another problem in its place.

@luijar Could you paste / show your workaround?

EDIT 12.05.23: It looks like you can work around this by putting setTimeout / requestAnimationFrame -calls inside event handlers, like onClose for example. Not ideal, but it works.

EDIT 25.09.23: We've now started getting the problem in the "original fashion" where the Dropdown doesn't close when clicking on the same item and/or trying to use the "close" -icon (inside Web Components).

luijar commented 11 months ago

Hey @neutraali so sorry didn't see this earlier. Why I did was I ended up wrapping over callbacks passed to the onChange prop. That wrapper would call said callback and then just blur the root node (which would be the shadow root) e.target.getRootNode().host.blur(); This last line would force the drop down to close when a user picked any item.