autocomplete icon indicating copy to clipboard operation
autocomplete copied to clipboard

Issue in Autocomplete inside custom element (web component) using React instant search

Open gs-asohail opened this issue 2 years ago • 1 comments

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.

gs-asohail avatar Dec 04 '23 07:12 gs-asohail

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 onSelect callback 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 onSelect callback and alert does popup.
  • However, if you use your mouse cursor to click on an item, the onSelect callback 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 onMouseDown event does get called, but onClick never 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 isTargetWithinAutocomplete inside the onMouseDownOrTouchStart handler. The basic issue is that isTargetWithinAutocomplete always evaluates to false because the event.target is 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 a blur to close the panel altogether. https://github.com/algolia/autocomplete/blob/de8a1955e67b8df9a7f29f8114e3c579de86bab1/packages/autocomplete-core/src/getPropGetters.ts#L59-L66
  • That onMouseDownOrTouchStart gets called as a result of listeners added with essentially ends up being window.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 of getEnvironmentProps getting passed to setProperties, which in turn calls addEventListener against the passed in props.value.core.environment value. However, props.value.core.environment always ends up being window, 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!

GUI avatar Aug 02 '25 03:08 GUI