mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-22 23:16:46 +00:00
fix(map,mcp): review follow-ups for the issue-fix batch
- mapbox-gl (unlike maplibre) still emits the map contextmenu after a right-button rotate/pitch drag on Windows - guard it with the pressed position so ending a rotate can't open the Add-Place form (#1398) - a long-press whose fire was deduped (or that never yields a click) no longer leaves suppressNextClick armed to swallow a later real tap - MCP_SSE_KEEPALIVE=0 keeps the open-stream-counts-as-activity guarantee: the touch interval survives, only the pings stop (#1414) - swagger-ui-dist ships @scarf/scarf install-time analytics - disabled via scarfSettings in the root package.json, TREK sends no telemetry - Budget wiki currency list: 47 incl. KGS (#1400)
This commit is contained in:
@@ -344,6 +344,52 @@ describe('MapViewGL', () => {
|
||||
expect(onContext.mock.calls[0][0].latlng).toEqual({ lat: 48.8566, lng: 2.3522 })
|
||||
})
|
||||
|
||||
it('FE-COMP-MAPVIEWGL-012: a right-button rotate/pitch drag does not open Add-Place on release (#1398)', async () => {
|
||||
const onContext = vi.fn()
|
||||
render(<MapViewGL places={[]} fitKey={1} onMapContextMenu={onContext} />)
|
||||
await act(async () => {})
|
||||
const handler = glMap.on.mock.calls.find(c => c[0] === 'contextmenu')?.[1] as (e: unknown) => void
|
||||
act(() => {
|
||||
// mapbox-gl (unlike maplibre) still emits contextmenu after a right-drag
|
||||
// on Windows — the movement guard must drop it.
|
||||
glCanvasContainer.dispatchEvent(new MouseEvent('mousedown', { button: 2, clientX: 10, clientY: 10, bubbles: true }))
|
||||
handler({ lngLat: { lat: 1, lng: 2 }, originalEvent: new MouseEvent('contextmenu', { clientX: 140, clientY: 90 }) })
|
||||
})
|
||||
expect(onContext).not.toHaveBeenCalled()
|
||||
// ...while a stationary right-click still fires.
|
||||
act(() => {
|
||||
glCanvasContainer.dispatchEvent(new MouseEvent('mousedown', { button: 2, clientX: 10, clientY: 10, bubbles: true }))
|
||||
handler({ lngLat: { lat: 1, lng: 2 }, originalEvent: new MouseEvent('contextmenu', { clientX: 11, clientY: 10 }) })
|
||||
})
|
||||
expect(onContext).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('FE-COMP-MAPVIEWGL-013: a stale long-press suppression never swallows a later real tap (#1398)', async () => {
|
||||
vi.useFakeTimers()
|
||||
try {
|
||||
const onContext = vi.fn()
|
||||
const onMapClick = vi.fn()
|
||||
render(<MapViewGL places={[]} fitKey={1} onMapContextMenu={onContext} onMapClick={onMapClick} />)
|
||||
await act(async () => {})
|
||||
// Long-press fires (arms the suppression), but no click follows.
|
||||
act(() => {
|
||||
glCanvasContainer.dispatchEvent(touchEvent('touchstart', [{ clientX: 30, clientY: 40 }]))
|
||||
vi.advanceTimersByTime(650)
|
||||
})
|
||||
expect(onContext).toHaveBeenCalledTimes(1)
|
||||
// The NEXT gesture starts fresh: its tap must reach the map click handler.
|
||||
const clickHandler = glMap.on.mock.calls.find(c => c[0] === 'click')?.[1] as (e: unknown) => void
|
||||
act(() => {
|
||||
glCanvasContainer.dispatchEvent(touchEvent('touchstart', [{ clientX: 80, clientY: 90 }]))
|
||||
glCanvasContainer.dispatchEvent(touchEvent('touchend', []))
|
||||
clickHandler({ lngLat: { lat: 3, lng: 4 }, originalEvent: { target: glCanvasContainer } })
|
||||
})
|
||||
expect(onMapClick).toHaveBeenCalledWith({ latlng: { lat: 3, lng: 4 } })
|
||||
} finally {
|
||||
vi.useRealTimers()
|
||||
}
|
||||
})
|
||||
|
||||
it('FE-COMP-MAPVIEWGL-010: middle-click still opens Add-Place (#1398 regression guard)', async () => {
|
||||
const onContext = vi.fn()
|
||||
render(<MapViewGL places={[]} fitKey={1} onMapContextMenu={onContext} />)
|
||||
|
||||
@@ -501,14 +501,25 @@ export function MapViewGL({
|
||||
// does, which is why the OSM map already worked on mobile).
|
||||
const canvas = map.getCanvasContainer()
|
||||
let lastContextFire = 0
|
||||
const fireContext = (lngLat: { lat: number; lng: number }, originalEvent: MouseEvent | TouchEvent) => {
|
||||
const fireContext = (lngLat: { lat: number; lng: number }, originalEvent: MouseEvent | TouchEvent): boolean => {
|
||||
// Android fires a native contextmenu for a long-press on top of our own
|
||||
// timer — dedupe so the form doesn't open twice.
|
||||
if (Date.now() - lastContextFire < 700) return
|
||||
if (Date.now() - lastContextFire < 700) return false
|
||||
lastContextFire = Date.now()
|
||||
onClickRefs.current.context?.({ latlng: { lat: lngLat.lat, lng: lngLat.lng }, originalEvent })
|
||||
return true
|
||||
}
|
||||
// MapLibre swallows the map contextmenu at the end of a right-button
|
||||
// rotate/pitch drag, but mapbox-gl does NOT — and on Windows the DOM
|
||||
// contextmenu arrives after mouseup, so every rotate would end by opening
|
||||
// the Add-Place form. Track the right-button press position and drop a
|
||||
// contextmenu whose pointer travelled like a drag rather than a click.
|
||||
let rightDownAt: { x: number; y: number } | null = null
|
||||
const onAuxDown = (ev: MouseEvent) => {
|
||||
if (ev.button === 2) {
|
||||
rightDownAt = { x: ev.clientX, y: ev.clientY }
|
||||
return
|
||||
}
|
||||
if (ev.button !== 1) return
|
||||
ev.preventDefault()
|
||||
const rect = canvas.getBoundingClientRect()
|
||||
@@ -522,6 +533,9 @@ export function MapViewGL({
|
||||
canvas.addEventListener('mousedown', onAuxDown)
|
||||
canvas.addEventListener('auxclick', onAuxClick)
|
||||
map.on('contextmenu', (e: { lngLat: { lat: number; lng: number }; originalEvent: MouseEvent }) => {
|
||||
const down = rightDownAt
|
||||
rightDownAt = null
|
||||
if (down && Math.hypot(e.originalEvent.clientX - down.x, e.originalEvent.clientY - down.y) > 5) return
|
||||
fireContext(e.lngLat, e.originalEvent)
|
||||
})
|
||||
// Touch long-press: 600 ms hold (Leaflet's tapHold feel) with a 10 px
|
||||
@@ -534,6 +548,10 @@ export function MapViewGL({
|
||||
lpStart = null
|
||||
}
|
||||
const onTouchStart = (ev: TouchEvent) => {
|
||||
// A fresh gesture clears a stale suppression flag: not every long-press
|
||||
// is followed by a click (finger drag after the hold, Android's native
|
||||
// contextmenu path), and the flag must never swallow a later real tap.
|
||||
suppressNextClick = false
|
||||
if (ev.touches.length !== 1) { cancelLongPress(); return }
|
||||
if ((ev.target as HTMLElement).closest('.mapboxgl-marker, .maplibregl-marker')) return
|
||||
const t = ev.touches[0]
|
||||
@@ -544,8 +562,9 @@ export function MapViewGL({
|
||||
const rect = canvas.getBoundingClientRect()
|
||||
const lngLat = map.unproject([lpStart.x - rect.left, lpStart.y - rect.top])
|
||||
lpStart = null
|
||||
suppressNextClick = true
|
||||
fireContext({ lat: lngLat.lat, lng: lngLat.lng }, ev)
|
||||
// Only suppress the tap when OUR fire opened the form — if the native
|
||||
// contextmenu beat us to it (dedupe), no click needs swallowing.
|
||||
if (fireContext({ lat: lngLat.lat, lng: lngLat.lng }, ev)) suppressNextClick = true
|
||||
}, 600)
|
||||
}
|
||||
const onTouchMove = (ev: TouchEvent) => {
|
||||
|
||||
Reference in New Issue
Block a user