fix(map): results map fullscreen falls back to an overlay on iOS
PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 9m37s
PR Checks / Backend Smoke (pull_request) Successful in 5s
PR Checks / Build Backend (no push) (pull_request) Successful in 10s
PR Checks / Build Frontend (no push) (pull_request) Successful in 45s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 10s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 1m4s
PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 9m37s
PR Checks / Backend Smoke (pull_request) Successful in 5s
PR Checks / Build Backend (no push) (pull_request) Successful in 10s
PR Checks / Build Frontend (no push) (pull_request) Successful in 45s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 10s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 1m4s
The results-view map's fullscreen button called requestFullscreen(), which iOS Safari doesn't implement (fullscreen is video-only there), so tapping it did nothing on iPhones — the same gap already fixed for the school hero map. When the Fullscreen API is missing or its promise rejects, fall back to a fixed-position overlay (.fsFallback, z-index 5000) driven by state, locking body scroll while open. Leaflet re-measures on window resize, so dispatch a resize when fullscreen toggles (the CSS overlay fires none) or the map would fill only part of the screen. Native fullscreen is unchanged. New e2e journey deletes Element.requestFullscreen on a mobile viewport, opens the results map fullscreen, and asserts the exit control appears then releases; it fails against current production, reproducing the bug. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -33,22 +33,52 @@ interface SchoolMapProps {
|
||||
|
||||
export function SchoolMap({ schools, center, zoom = 13, referencePoint, onMarkerClick, nationalAvgRwm, laAverages }: SchoolMapProps) {
|
||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||
const [isFullscreen, setIsFullscreen] = useState(false);
|
||||
const [nativeFullscreen, setNativeFullscreen] = useState(false);
|
||||
// iOS Safari has no Element.requestFullscreen — fall back to a fixed-position
|
||||
// overlay driven by state instead of the Fullscreen API.
|
||||
const [fallbackFullscreen, setFallbackFullscreen] = useState(false);
|
||||
const isFullscreen = nativeFullscreen || fallbackFullscreen;
|
||||
|
||||
// Sync state with browser fullscreen events (e.g. Escape key)
|
||||
useEffect(() => {
|
||||
const onFsChange = () => setIsFullscreen(!!document.fullscreenElement);
|
||||
const onFsChange = () => setNativeFullscreen(!!document.fullscreenElement);
|
||||
document.addEventListener('fullscreenchange', onFsChange);
|
||||
return () => document.removeEventListener('fullscreenchange', onFsChange);
|
||||
}, []);
|
||||
|
||||
// Lock body scroll while the fallback overlay is up.
|
||||
useEffect(() => {
|
||||
if (!fallbackFullscreen) return;
|
||||
const prev = document.body.style.overflow;
|
||||
document.body.style.overflow = 'hidden';
|
||||
return () => { document.body.style.overflow = prev; };
|
||||
}, [fallbackFullscreen]);
|
||||
|
||||
// Leaflet re-measures on window resize (trackResize). Native fullscreen fires
|
||||
// one; the CSS fallback overlay changes size without a resize event, so nudge
|
||||
// Leaflet after the layout settles or the map fills only part of the screen.
|
||||
useEffect(() => {
|
||||
const id = requestAnimationFrame(() => window.dispatchEvent(new Event('resize')));
|
||||
return () => cancelAnimationFrame(id);
|
||||
}, [isFullscreen]);
|
||||
|
||||
const toggleFullscreen = useCallback(() => {
|
||||
if (!document.fullscreenElement) {
|
||||
wrapperRef.current?.requestFullscreen();
|
||||
} else {
|
||||
document.exitFullscreen();
|
||||
if (document.fullscreenElement) {
|
||||
document.exitFullscreen().catch(() => {});
|
||||
return;
|
||||
}
|
||||
}, []);
|
||||
if (fallbackFullscreen) {
|
||||
setFallbackFullscreen(false);
|
||||
return;
|
||||
}
|
||||
const el = wrapperRef.current;
|
||||
if (!el) return;
|
||||
if (el.requestFullscreen) {
|
||||
el.requestFullscreen().catch(() => setFallbackFullscreen(true));
|
||||
} else {
|
||||
setFallbackFullscreen(true);
|
||||
}
|
||||
}, [fallbackFullscreen]);
|
||||
|
||||
// Calculate center if not provided
|
||||
const mapCenter: [number, number] = center || (() => {
|
||||
@@ -64,7 +94,7 @@ export function SchoolMap({ schools, center, zoom = 13, referencePoint, onMarker
|
||||
})();
|
||||
|
||||
return (
|
||||
<div ref={wrapperRef} className={`${styles.mapWrapper} ${isFullscreen ? styles.fullscreen : ''}`}>
|
||||
<div ref={wrapperRef} className={`${styles.mapWrapper} ${isFullscreen ? styles.fullscreen : ''} ${fallbackFullscreen ? styles.fsFallback : ''}`}>
|
||||
<button
|
||||
className={styles.fullscreenBtn}
|
||||
onClick={toggleFullscreen}
|
||||
|
||||
Reference in New Issue
Block a user