diff --git a/client/src/components/Map/MapView.test.tsx b/client/src/components/Map/MapView.test.tsx
index 9bcf0c5d7..f0fe2149e 100644
--- a/client/src/components/Map/MapView.test.tsx
+++ b/client/src/components/Map/MapView.test.tsx
@@ -916,7 +916,17 @@ describe('MapView live location', () => {
})
describe('MapView bounds fitting', () => {
- it('FE-COMP-MAPVIEW-065: a day-detail fit nudges the map down once the animation ran (#1348)', async () => {
+ /*
+ * The day fit used to compensate for the day panel twice: once through the
+ * padding, and again 300ms later with panBy([0, 150]) (#1348). On a route
+ * running north to south the fit is height-bound, so the northernmost stop
+ * lands exactly on the top padding line and the nudge then pushed it off the
+ * canvas — the map framed everything and then drifted upwards (#1982).
+ *
+ * So this case is the inverse of what it used to assert: the padding does the
+ * work, and nothing moves the camera afterwards.
+ */
+ it('FE-COMP-MAPVIEW-065: a day-detail fit reserves the panel in its padding and then leaves the camera alone (#1348, #1982)', async () => {
const places = [
buildMapPlace({ id: 1, lat: 48, lng: 2 }),
buildMapPlace({ id: 2, lat: 48.2, lng: 2.2 }),
@@ -925,7 +935,24 @@ describe('MapView bounds fitting', () => {
rerender()
expect(mapMock.fitBounds).toHaveBeenCalled()
- await waitFor(() => expect(mapMock.panBy).toHaveBeenCalledWith([0, 150], { animate: true }), { timeout: 2000 })
+ const opts = mapMock.fitBounds.mock.calls.at(-1)?.[1] as { paddingBottomRight?: [number, number] }
+ expect(opts?.paddingBottomRight?.[1]).toBe(280)
+
+ // Long enough that the old 300ms nudge would have fired by now.
+ await new Promise(r => setTimeout(r, 600))
+ expect(mapMock.panBy).not.toHaveBeenCalled()
+ })
+
+ it('FE-COMP-MAPVIEW-065b: reserves only the small margin when no day panel is open (#1982)', () => {
+ const places = [
+ buildMapPlace({ id: 1, lat: 48, lng: 2 }),
+ buildMapPlace({ id: 2, lat: 48.2, lng: 2.2 }),
+ ]
+ const { rerender } = render()
+ rerender()
+
+ const opts = mapMock.fitBounds.mock.calls.at(-1)?.[1] as { paddingBottomRight?: [number, number] }
+ expect(opts?.paddingBottomRight?.[1]).toBe(60)
})
it('FE-COMP-MAPVIEW-066: a fitKey that never changed is not re-fitted', async () => {
diff --git a/client/src/components/Map/MapView.tsx b/client/src/components/Map/MapView.tsx
index eca314267..5c8ca3deb 100644
--- a/client/src/components/Map/MapView.tsx
+++ b/client/src/components/Map/MapView.tsx
@@ -269,7 +269,6 @@ function MapController({ center, zoom }: MapControllerProps) {
// finished computing asynchronously — re-fit once more to include the full route
// polyline, so a route that bulges past its stops stays in view (#1128).
interface BoundsControllerProps {
- hasDayDetail?: boolean
places: Place[]
routeCoords: [number, number][]
fitKey: number
@@ -278,7 +277,7 @@ interface BoundsControllerProps {
framedOnMount?: boolean
}
-function BoundsController({ places, routeCoords, fitKey, paddingOpts, hasDayDetail, framedOnMount = false }: BoundsControllerProps) {
+function BoundsController({ places, routeCoords, fitKey, paddingOpts, framedOnMount = false }: BoundsControllerProps) {
const map = useMap()
const prevFitKey = useRef(-1)
const awaitingRoute = useRef(false)
@@ -289,13 +288,24 @@ function BoundsController({ places, routeCoords, fitKey, paddingOpts, hasDayDeta
try {
const bounds = L.latLngBounds(coords)
if (bounds.isValid()) {
+ /*
+ * The padding already reserves the day panel's height at the bottom
+ * (paddingBox), so the fit puts the day's stops above it. There used to
+ * be a second, manual nudge 300ms later — panBy([0, 150]) — from the
+ * same change that introduced the padding, and the two compensated for
+ * the same panel twice.
+ *
+ * On a route that runs north to south the fit is height-bound, which
+ * puts the northernmost stop exactly on the top padding line; the nudge
+ * then pushed it off the canvas. The map appeared to frame everything
+ * correctly and then drift upwards, which is what the report describes
+ * (#1982). MapViewGL never had the nudge, so this also brings the two
+ * renderers back into agreement.
+ */
map.fitBounds(bounds, { ...paddingOpts, maxZoom: 16, animate: true })
- if (hasDayDetail) {
- setTimeout(() => map.panBy([0, 150], { animate: true }), 300)
- }
}
} catch {}
- }, [map, paddingOpts, hasDayDetail])
+ }, [map, paddingOpts])
// New fitKey (initial trip fit or a day selection): fit to the destinations now
// and arm a one-shot re-fit for when the route arrives.
@@ -827,7 +837,7 @@ export const MapView = memo(function MapView({
/>
- 0 ? dayPlaces : places} routeCoords={dayPlaces.length > 0 ? routeCoords : []} fitKey={fitKey} paddingOpts={paddingOpts} hasDayDetail={hasDayDetail} framedOnMount={initialView.framed} />
+ 0 ? dayPlaces : places} routeCoords={dayPlaces.length > 0 ? routeCoords : []} fitKey={fitKey} paddingOpts={paddingOpts} framedOnMount={initialView.framed} />