diff --git a/resources/js/common/components/+vendor/BaseDialog.tsx b/resources/js/common/components/+vendor/BaseDialog.tsx index f24478cfc9..a39f9af8d6 100644 --- a/resources/js/common/components/+vendor/BaseDialog.tsx +++ b/resources/js/common/components/+vendor/BaseDialog.tsx @@ -44,6 +44,55 @@ const BaseDialogContent = React.forwardRef< ) => { const { t } = useTranslation(); + const closeButtonRef = React.useRef(null); + const blockerTimeoutRef = React.useRef | null>(null); + + /** + * To band-aid a bug in Safari, we create a synthetic "dialog-hover-blocker" + * element. We need to always be sure to clean that up on unmount so we + * don't leak memory for every dialog we open. + */ + React.useEffect(() => { + return () => { + if (blockerTimeoutRef.current) { + clearTimeout(blockerTimeoutRef.current); + } + const blocker = document.getElementById('dialog-hover-blocker'); + if (blocker?.parentNode) { + blocker.parentNode.removeChild(blocker); + } + }; + }, []); + + /** + * On Safari mobile, tapping the dialog close button triggers a "sticky hover" + * on elements underneath after the dialog closes (such as dropdown menus). + * We handle touch separately by adding a temporary synthetic blocker element to + * absorb the hover state, then programmatically trigger the close. + * + * If we don't do this, closing the dialog on mobile Safari can inadvertently + * trigger elements z-indexed directly underneath the dialog close button. + */ + const handleCloseTouchEnd = (e: React.TouchEvent) => { + e.preventDefault(); // Prevent the synthetic click. + + // Only create one blocker at a time. + if (!document.getElementById('dialog-hover-blocker')) { + const blocker = document.createElement('div'); + blocker.id = 'dialog-hover-blocker'; + blocker.style.cssText = 'position:fixed;inset:0;z-index:9999;'; + document.body.appendChild(blocker); + + blockerTimeoutRef.current = setTimeout(() => { + blocker.remove(); + blockerTimeoutRef.current = null; + }, 300); + } + + // Programmatically trigger the close via Radix. + closeButtonRef.current?.click(); + }; + return ( @@ -65,12 +114,14 @@ const BaseDialogContent = React.forwardRef< {shouldShowCloseButton ? ( {t('Close')}