Issue in Autocomplete inside custom element (web component) using React instant search
We have an implementation of Algolia's React Instant search with Autocomplete. Also, we are converting our React component into a webcomponent (using @r2wc/react-to-web-component). The issue we are facing is when autocomplete panel is open and we click on any item, it closes the panel and does NOT update the search query input with the selected item.
Replicated the issue here: https://stackblitz.com/edit/react-jneg8s. Ignore the stylings, just search for something and click on the search result from the panel (using mouse click).
Root cause: Autocomplete applies some listeners (mouseDown, touchStart) and uses those events to identify if the click (or touch start) is outside of the panel. If so, it treats this as a blur event of autocomplete and closes the panel. This is completely fine, but in our case, because we are using web component (with shadow DOM), the event.target is always the custom element and not the internal target of the custom element. So this custom element is treated as outside click and thus closes the panel and doesn't even update the search input state.
I've also encountered this same issue when trying to use Autocomplete.js inside of a shadow DOM root element. Keyboard selection works as expected, but clicking on an item with the mouse does not fire the onSelect callback as expected.
Reproduction
Here is my similar attempt at a simple reproduction of the onSelect bug with the shadow DOM (I had put together this reproduction before I found this issue with @gs-asohail's similar reproduction): https://codesandbox.io/p/sandbox/intelligent-albattani-n87g2v
- This is a small modification of the default GitHub repositories sandbox example.
- It should show an alert after selecting an item due to the
onSelectcallback added in this fork. - Otherwise, the only real difference is that the Autocomplete element is using elements inside the shadow DOM (and detached mode is disabled to ensure we're only dealing with shadow DOM elements in this simple example).
- If you use this fork and select an item with keyboard navigation, then the
onSelectcallback and alert does popup. - However, if you use your mouse cursor to click on an item, the
onSelectcallback and alert does not popup.
The mouse selection does work if the shadow DOM changes aren't involved, so this does seem specific to the shadow DOM usage plus mouse uage.
Potential culprits
In poking around more, I think I've traced why this mouse behavior is happening with the shadow DOM, but I'm not totally sure about the best way to fix this.
I found basically the same underlying issues as @gs-asohail, with the root cause being some listeners happening outside of the shadow DOM. But if it helps to provide additional context, here is what I've traced through and observed when the elements are inside the shadow DOM and you click on an item to select it:
- The item's
onMouseDownevent does get called, butonClicknever does: https://github.com/algolia/autocomplete/blob/de8a1955e67b8df9a7f29f8114e3c579de86bab1/packages/autocomplete-core/src/getPropGetters.ts#L377-L382 - I think the reason the click doesn't get fired is due to other mousedown handling that ends up closing the results panel before the click event can occurs. This is due to this logic around
isTargetWithinAutocompleteinside theonMouseDownOrTouchStarthandler. The basic issue is thatisTargetWithinAutocompletealways evaluates tofalsebecause theevent.targetis the entire shadow DOM container element, rather than the actual elements inside the shadow DOM, so the comparison can never really succeed as normally expected, so this ends up triggering ablurto close the panel altogether. https://github.com/algolia/autocomplete/blob/de8a1955e67b8df9a7f29f8114e3c579de86bab1/packages/autocomplete-core/src/getPropGetters.ts#L59-L66 - That
onMouseDownOrTouchStartgets called as a result of listeners added with essentially ends up beingwindow.addEventListener, which isn't really compatible with a shadow DOM setup (since the window-level listeners can't observe stuff inside the shadow DOM). These listeners get added to the window via this chain ofgetEnvironmentPropsgetting passed tosetProperties, which in turn callsaddEventListeneragainst the passed inprops.value.core.environmentvalue. However,props.value.core.environmentalways ends up beingwindow, so this is why the event listeners exist outside the shadow DOM. https://github.com/algolia/autocomplete/blob/de8a1955e67b8df9a7f29f8114e3c579de86bab1/packages/autocomplete-js/src/autocomplete.ts#L182-L188 https://github.com/algolia/autocomplete/blob/de8a1955e67b8df9a7f29f8114e3c579de86bab1/packages/autocomplete-js/src/utils/setProperties.ts#L73 https://github.com/algolia/autocomplete/blob/de8a1955e67b8df9a7f29f8114e3c579de86bab1/packages/autocomplete-js/src/getDefaultOptions.ts#L94-L96
So the main issue in this whole chain seems to be that props.value.core.environment will always be window. If it was possible to configure props.value.core.environment so you could instead pass in the shadow root element, then I suspect that may fix this event listener issue, since then I believe the click listeners would be listening on the shadow DOM root elements, which would probably work better. I'm just not 100% sure if there may be other side effects of having props.value.core.environment be a different root element being in other areas, but I think this is at least where the issues revolve around if that helps.
Thanks!