Skip to content

Commit

Permalink
fix ensure useOnClickOutside hook correctly detects clicks across i…
Browse files Browse the repository at this point in the history
…framed editor boundaries
  • Loading branch information
fabiankaegy committed Jun 7, 2024
1 parent 2083983 commit eec4b56
Showing 1 changed file with 39 additions and 15 deletions.
54 changes: 39 additions & 15 deletions hooks/use-on-click-outside.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useRef } from '@wordpress/element';
import { useRefEffect } from '@wordpress/compose';

/**
* useOnClickOutside
Expand All @@ -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<HTMLElement>();
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<HTMLIFrameElement>('[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;
Expand Down

0 comments on commit eec4b56

Please sign in to comment.