diff --git a/hooks/use-on-click-outside.ts b/hooks/use-on-click-outside.ts index c5e3b891..d48ae92f 100644 --- a/hooks/use-on-click-outside.ts +++ b/hooks/use-on-click-outside.ts @@ -1,4 +1,4 @@ -import { useEffect, useRef } from '@wordpress/element'; +import { useRefEffect } from '@wordpress/compose'; /** * useOnClickOutside @@ -7,29 +7,53 @@ import { useEffect, useRef } from '@wordpress/element'; * @returns {object} ref to the target element */ export function useOnClickOutside(onClickOutside: (event: MouseEvent | TouchEvent) => void) { - const ref = useRef(); - useEffect( - () => { + const ref = useRefEffect( + (element) => { + if (!element) { + return; + } const listener = (event: MouseEvent | TouchEvent) => { // Do nothing if clicking ref's element or descendent elements - if (!ref.current || ref.current.contains(event.target as HTMLElement)) { + if (!element || element.contains(event.target as HTMLElement)) { return; } onClickOutside(event); }; - document.addEventListener('mousedown', listener); - document.addEventListener('touchstart', listener); + + const ownerDocument = element.ownerDocument || document; + const isRenderedInsideIframe = ownerDocument !== document; + + const editorCanvasIframe = document.querySelector('[name="editor-canvas"]'); + const editorCanvasDocument = editorCanvasIframe?.contentDocument; + + ownerDocument.addEventListener('mousedown', listener); + ownerDocument.addEventListener('touchstart', listener); + + // If the element is rendered inside an iframe, we need to listen to events on the parent document + // as well to detect clicks outside the iframe. + if (isRenderedInsideIframe) { + document.addEventListener('mousedown', listener); + document.addEventListener('touchstart', listener); + + // If the element is rendered outside the editor canvas iframe, we need to listen to events on the editor canvas + // document as well to detect clicks inside the editor canvas. + } else if (editorCanvasDocument) { + editorCanvasDocument.addEventListener('mousedown', listener); + editorCanvasDocument.addEventListener('touchstart', listener); + } return () => { - document.removeEventListener('mousedown', listener); - document.removeEventListener('touchstart', listener); + ownerDocument.removeEventListener('mousedown', listener); + ownerDocument.removeEventListener('touchstart', listener); + if (isRenderedInsideIframe) { + document.removeEventListener('mousedown', listener); + document.removeEventListener('touchstart', listener); + } else if (editorCanvasDocument) { + editorCanvasDocument.removeEventListener('mousedown', listener); + editorCanvasDocument.removeEventListener('touchstart', listener); + } }; }, - // Add ref and handler to effect dependencies - // It's worth noting that because passed in handler is a new function on every - // render that will cause this effect callback/cleanup to run every render. - // It's not a big deal but to optimize you can wrap handler in useCallback before - // passing it into this hook. - [ref, onClickOutside], + [onClickOutside], ); return ref;