Merge pull request 'fix(map): results map fullscreen falls back to an overlay on iOS' (#18) from fix/results-map-ios-fullscreen into main
Deploy (staging -> E2E gate -> production) / Build Backend (FastAPI) (push) Successful in 12s
Deploy (staging -> E2E gate -> production) / Build Frontend (Next.js) (push) Successful in 48s
Deploy (staging -> E2E gate -> production) / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 12s
Deploy (staging -> E2E gate -> production) / Deploy to Staging (push) Successful in 1s
Deploy (staging -> E2E gate -> production) / E2E Journeys against Staging (push) Successful in 38s
Deploy (staging -> E2E gate -> production) / Promote to Production (push) Successful in 8s
Deploy (staging -> E2E gate -> production) / Build Backend (FastAPI) (push) Successful in 12s
Deploy (staging -> E2E gate -> production) / Build Frontend (Next.js) (push) Successful in 48s
Deploy (staging -> E2E gate -> production) / Build Pipeline (Meltano + dbt + Airflow) (push) Successful in 12s
Deploy (staging -> E2E gate -> production) / Deploy to Staging (push) Successful in 1s
Deploy (staging -> E2E gate -> production) / E2E Journeys against Staging (push) Successful in 38s
Deploy (staging -> E2E gate -> production) / Promote to Production (push) Successful in 8s
Reviewed-on: #18
This commit was merged in pull request #18.
This commit is contained in:
@@ -75,6 +75,31 @@ test('school hero map opens fullscreen on mobile without the Fullscreen API', as
|
||||
await expect(openMap).toBeVisible();
|
||||
});
|
||||
|
||||
test('results map fullscreen falls back to an overlay on iOS', async ({ page }) => {
|
||||
// Same iOS gap as the hero map: no Element.requestFullscreen, so the results
|
||||
// map's fullscreen button must fall back to a CSS overlay.
|
||||
await page.setViewportSize({ width: 390, height: 844 });
|
||||
await page.addInitScript(() => {
|
||||
// @ts-expect-error deliberate API removal
|
||||
delete Element.prototype.requestFullscreen;
|
||||
});
|
||||
|
||||
await searchByName(page, 'B1 1BB');
|
||||
await expect(schoolLinks(page).first()).toBeVisible({ timeout: 15_000 });
|
||||
|
||||
// Switch to the map view, then open the map fullscreen.
|
||||
await page.getByRole('button', { name: 'Map', exact: true }).click();
|
||||
const openFs = page.getByRole('button', { name: 'View map fullscreen' });
|
||||
await expect(openFs).toBeVisible({ timeout: 15_000 });
|
||||
await openFs.click();
|
||||
|
||||
// The button flips to its exit state once the overlay is up.
|
||||
const exitFs = page.getByRole('button', { name: 'Exit fullscreen' });
|
||||
await expect(exitFs).toBeVisible();
|
||||
await exitFs.click();
|
||||
await expect(openFs).toBeVisible();
|
||||
});
|
||||
|
||||
test('comparing two schools shows both side by side', async ({ page }) => {
|
||||
// Collect two school URNs from search results, then load the share URL
|
||||
await searchByName(page, 'primary');
|
||||
|
||||
@@ -10,6 +10,15 @@
|
||||
height: 100dvh;
|
||||
}
|
||||
|
||||
/* Fallback fullscreen (iOS Safari — no Element.requestFullscreen): the API
|
||||
can't promote the element, so pin it over the page ourselves. Above the
|
||||
comparison toast (3000) and the bottom nav; below modals (9999+). */
|
||||
.mapWrapper.fsFallback {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 5000;
|
||||
}
|
||||
|
||||
.fullscreenBtn {
|
||||
position: absolute;
|
||||
top: 0.625rem;
|
||||
|
||||
@@ -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