From fcb532cdea2fc59736e5d915cfce6fd391b54212 Mon Sep 17 00:00:00 2001 From: Maurice Date: Mon, 13 Jul 2026 22:20:04 +0200 Subject: [PATCH] fix(map): stop MapLibre mouse rotation from reversing near mid-screen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since MapLibre 5's camera rewrite, the right-button rotate handler flips its sign whenever the cursor sits above a mid-screen line it derives by re-projecting the map center. That line drifts with the bearing by a fraction of a pixel, so inside the 100px band around the screen center a steady horizontal drag lands alternately above and below it — every processed movement reverses the previous one and the camera ping-pongs in place instead of rotating (#1545). A real hand crossing the line mid-drag flips the rotation direction outright. maplibre-gl 4.x rotated from plain horizontal movement and had none of this. Passing aroundCenter: false opts the handler out of the around-center mode and restores the 4.x/mapbox-gl behaviour: horizontal drag rotates, vertical drag pitches, in one continuous motion. Applied to all three GL map builds (planner, journey, settings preview); mapbox-gl keeps its options untouched. --- .../src/components/Journey/JourneyMapGL.tsx | 3 ++ client/src/components/Map/MapViewGL.test.tsx | 33 +++++++++++++++++++ client/src/components/Map/MapViewGL.tsx | 5 +++ .../src/components/Settings/MapboxPreview.tsx | 3 ++ 4 files changed, 44 insertions(+) diff --git a/client/src/components/Journey/JourneyMapGL.tsx b/client/src/components/Journey/JourneyMapGL.tsx index 3eb4f1cc7..c57d7c327 100644 --- a/client/src/components/Journey/JourneyMapGL.tsx +++ b/client/src/components/Journey/JourneyMapGL.tsx @@ -362,6 +362,9 @@ const JourneyMapGL = forwardRef(function JourneyMapGL antialias: mapboxQuality, } if (!isMapLibre) mapOptions.projection = mapboxQuality ? 'globe' : 'mercator' + // MapLibre 5's around-center mouse rotate ping-pongs near mid-screen (#1545) + // — see MapViewGL. Keep the plain dx-based rotate everywhere. + if (isMapLibre) mapOptions.aroundCenter = false const map = new gl.Map(mapOptions as any) mapRef.current = map diff --git a/client/src/components/Map/MapViewGL.test.tsx b/client/src/components/Map/MapViewGL.test.tsx index 7b4c7635e..f8786b106 100644 --- a/client/src/components/Map/MapViewGL.test.tsx +++ b/client/src/components/Map/MapViewGL.test.tsx @@ -244,6 +244,39 @@ describe('MapViewGL', () => { expect(mapboxgl.Map).not.toHaveBeenCalled() }) + it('FE-COMP-MAPVIEWGL-014: MapLibre maps disable the around-center mouse rotate (#1545)', async () => { + const mapboxgl = (await import('mapbox-gl')).default + const maplibregl = (await import('maplibre-gl')).default + useSettingsStore.setState({ + settings: { + ...useSettingsStore.getState().settings, + map_provider: 'maplibre-gl', + mapbox_access_token: '', + maplibre_style: 'https://tiles.openfreemap.org/styles/liberty', + }, + } as any) + const places = [buildMapPlace({ id: 1, lat: 48.8584, lng: 2.2945 })] + + render() + await act(async () => {}) + // MapLibre 5's around-center rotate reverses direction at a drifting + // mid-screen line, so the map must opt out of it. + expect((maplibregl.Map as any).mock.calls[0][0]).toMatchObject({ aroundCenter: false }) + + vi.clearAllMocks() + useSettingsStore.setState({ + settings: { + ...useSettingsStore.getState().settings, + map_provider: 'mapbox-gl', + mapbox_access_token: 'pk.test_token', + }, + } as any) + render() + await act(async () => {}) + // mapbox-gl has no such option — it must not receive the stray key. + expect((mapboxgl.Map as any).mock.calls[0][0]).not.toHaveProperty('aroundCenter') + }) + it('FE-COMP-MAPVIEWGL-005: adds the clustered place source + layers so markers group on zoom-out (#1385)', async () => { glMap.on.mockImplementation((event: string, handlerOrLayer: unknown) => { if (event === 'load' && typeof handlerOrLayer === 'function') (handlerOrLayer as () => void)() diff --git a/client/src/components/Map/MapViewGL.tsx b/client/src/components/Map/MapViewGL.tsx index cc79f6e78..50a84f981 100644 --- a/client/src/components/Map/MapViewGL.tsx +++ b/client/src/components/Map/MapViewGL.tsx @@ -291,6 +291,11 @@ export function MapViewGL({ antialias: mapboxQuality, } if (!isMapLibre) mapOptions.projection = mapboxQuality ? 'globe' : 'mercator' + // MapLibre 5's mouse-rotate inverts its sign at a mid-screen line it gets by + // re-projecting the map center — a line that drifts with the bearing, so a + // right-button drag near mid-screen ping-pongs instead of rotating (#1545). + // aroundCenter: false restores the plain dx-based rotate mapbox-gl uses. + if (isMapLibre) mapOptions.aroundCenter = false const map = new gl.Map(mapOptions as any) mapRef.current = map diff --git a/client/src/components/Settings/MapboxPreview.tsx b/client/src/components/Settings/MapboxPreview.tsx index 3f3362280..d3f0a33f3 100644 --- a/client/src/components/Settings/MapboxPreview.tsx +++ b/client/src/components/Settings/MapboxPreview.tsx @@ -43,6 +43,9 @@ export default function GlMapPreview({ provider = 'mapbox-gl', token = '', style antialias: quality, } if (!isMapLibre) mapOptions.projection = quality ? 'globe' : 'mercator' + // MapLibre 5's around-center mouse rotate ping-pongs near mid-screen (#1545) + // — see MapViewGL. Keep the plain dx-based rotate everywhere. + if (isMapLibre) mapOptions.aroundCenter = false const map = new gl.Map(mapOptions as any) mapRef.current = map