diff --git a/frontend/src/Components/Modal/Modal.css b/frontend/src/Components/Modal/Modal.css index b92d26ead8..9b7e359ec3 100644 --- a/frontend/src/Components/Modal/Modal.css +++ b/frontend/src/Components/Modal/Modal.css @@ -30,12 +30,6 @@ overflow: hidden !important; } -.modalOpenIOS { - position: fixed; - right: 0; - left: 0; -} - /* * Sizes */ diff --git a/frontend/src/Components/Modal/Modal.css.d.ts b/frontend/src/Components/Modal/Modal.css.d.ts index f60af3bc86..f51fb9d160 100644 --- a/frontend/src/Components/Modal/Modal.css.d.ts +++ b/frontend/src/Components/Modal/Modal.css.d.ts @@ -10,7 +10,6 @@ interface CssExports { 'modalBackdrop': string; 'modalContainer': string; 'modalOpen': string; - 'modalOpenIOS': string; 'small': string; } export const cssExports: CssExports; diff --git a/frontend/src/Components/Modal/Modal.tsx b/frontend/src/Components/Modal/Modal.tsx index cfc157f93c..8b7c7d12e4 100644 --- a/frontend/src/Components/Modal/Modal.tsx +++ b/frontend/src/Components/Modal/Modal.tsx @@ -12,7 +12,7 @@ import FocusLock from 'react-focus-lock'; import ErrorBoundary from 'Components/Error/ErrorBoundary'; import usePrevious from 'Helpers/Hooks/usePrevious'; import { Size } from 'Helpers/Props/sizes'; -import { isIOS } from 'Utilities/browser'; +import { isMobile } from 'Utilities/browser'; import * as keyCodes from 'Utilities/Constants/keyCodes'; import { setScrollLock } from 'Utilities/scrollLock'; import ModalError from './ModalError'; @@ -29,6 +29,21 @@ function removeFromOpenModals(id: string) { } } +// Mobile scroll lock: block touchmove outside the modal portal. Avoids +// mutating body position/overflow — doing so resets window.scrollY to 0, +// which causes react-virtualized WindowScroller to unmount the row that +// owns the just-opened modal (Discover page regression). +function preventTouchScroll(event: TouchEvent) { + let target = event.target as HTMLElement | null; + while (target) { + if (target.id === 'portal-root') { + return; + } + target = target.parentElement; + } + event.preventDefault(); +} + function findEventTarget(event: TouchEvent | MouseEvent) { if ('changedTouches' in event) { const changedTouches = event.changedTouches; @@ -70,7 +85,6 @@ function Modal({ }: ModalProps) { const backgroundRef = useRef(null); const isBackdropPressed = useRef(false); - const bodyScrollTop = useRef(0); const wasOpen = usePrevious(isOpen); const modalId = useId(); @@ -125,10 +139,14 @@ function Modal({ openModals.push(modalId); if (openModals.length === 1) { - if (isIOS()) { + if (isMobile()) { + // Don't mutate body position/overflow on mobile — it resets + // window.scrollY which makes WindowScroller unmount this row's + // modal. Use a touchmove listener to block scroll instead. setScrollLock(true); - bodyScrollTop.current = document.body.scrollTop; - elementClass(document.body).add(styles.modalOpenIOS); + document.addEventListener('touchmove', preventTouchScroll, { + passive: false, + }); } else { elementClass(document.body).add(styles.modalOpen); } @@ -139,9 +157,8 @@ function Modal({ if (openModals.length === 0) { setScrollLock(false); - if (isIOS()) { - elementClass(document.body).remove(styles.modalOpenIOS); - document.body.scrollTop = bodyScrollTop.current; + if (isMobile()) { + document.removeEventListener('touchmove', preventTouchScroll); } else { elementClass(document.body).remove(styles.modalOpen); }