From a13d2e6ff27d4dc858d9420c3be1c67b19639b62 Mon Sep 17 00:00:00 2001 From: weretere Date: Sun, 19 Apr 2026 19:33:11 -0500 Subject: [PATCH] Fixed: Modal disappearing on mobile Discover page The existing iOS scroll lock applied `position: fixed` to , which reset window.scrollY to 0. react-virtualized's WindowScroller responded by unmounting the row that owned the just-opened modal, so the modal flashed then vanished and the page jumped to top. Replace the body-mutating lock with a non-passive touchmove listener scoped to everything outside #portal-root, so modal content still scrolls but body touch-scroll is blocked without changing scroll state. --- frontend/src/Components/Modal/Modal.css | 6 ---- frontend/src/Components/Modal/Modal.css.d.ts | 1 - frontend/src/Components/Modal/Modal.tsx | 33 +++++++++++++++----- 3 files changed, 25 insertions(+), 15 deletions(-) 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); }