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

cant use useOutsideClick function #210

Open
tim092003 opened this issue Nov 28, 2024 · 5 comments
Open

cant use useOutsideClick function #210

tim092003 opened this issue Nov 28, 2024 · 5 comments

Comments

@tim092003
Copy link

I have a modal in the web component which should close when you click outside. However, this does not work with the following code, because event.target only returns the web component.

Screenshot 2024-11-28 130352

@bmomberger-bitovi
Copy link
Contributor

We had a similar issue come up in Discord a while back. This wouldn't happen unless using Shadow DOM, but one of the isolation features of Shadow DOM is that it scrubs references to elements within the shadow root when bubbling events up to the light DOM. One strategy you might consider is having your component manage its own modal backdrop that will catch clicks outside of the modal. Also you might attach this handler at the shadow root and use a different handler that looks for clicks outside of the containing custom element attached at the document level. Let me know if you need code examples.

@tim092003
Copy link
Author

@bmomberger-bitovi thanks for the answer, could you give me some code examples.

@bmomberger-bitovi
Copy link
Contributor

To create a modal backdrop, you should only need to do something like this:

<>
  <div
    style={{
      position: "fixed",
      left: 0,
      top: 0,
      width: "100vw",
      height: "100vh",
      backgroundColor: "rgba(32, 32, 32, 0.3)",
      zIndex: -1,  /* z-index may need to be adjusted here or in the other modal items */
    }}
    onClick={() => callback()}
  >
    &nbsp;
  </div>
  // ... the rest of your modal content goes here:
</>

@bmomberger-bitovi
Copy link
Contributor

bmomberger-bitovi commented Dec 10, 2024

Adapting your useOutsideClick effect to support shadow roots (can be used with or without the modal backdrop above)

const useOutsideClickk = (callback) => {
  const ref = useRef();
  useEffect(() => {
    if(ref.current) {
      const element = ref.current;
      const innerHandler = (ev) => {
        if(!element.contains(ev.target) {
          // click was outside the testing element, but within the shadow DOM
          callback();
        }
      };
      let outerHandler;
      const root = element.getRootNode();  //in shadow DOM will return the shadow root, in light DOM will return the document
      root.addEventListener('click', callback, false);
      if (root !== document) {
        const { host } = root;  // surprisingly, this works even in closed shadow mode -- `host` is the element holding the shadow root
        outerHandler = (ev) => {
          if (ev.target !== host) {
            // click originated from outside the component's shadow DOM entirely
            callback();
          }
        };
        document.addEventListener('click', outerHandler, false);
      }
      return () => {
         root.removeEventListener('click', innerHandler, false);
         if (outerHandler) {
            document.removeEventListener('click', outerHandler, false);
         }
      }
    }
  }, [callback]);
  return ref;
};

@bmomberger-bitovi
Copy link
Contributor

The above code assumes that you will only ever attach a component into the light DOM. The outerHandler could be rewriten as a loop if you have shadow DOM nesting inside shadow DOM. You would need to hold references to each attachment point and each handler you make, in an array or Map. You know when to terminate the loop when host is undefined, as document has no host property.

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

No branches or pull requests

2 participants