mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-22 23:16:46 +00:00
fix(roadtrip): a freshly placed via lost its first drag
Placing one triggers a re-route, the re-route lands about a second later, and the render it causes rebuilt every via handle — which is exactly when somebody reaches for the dot they just made. On the GL renderer that rebuild replaces the DOM element outright, and a pointerdown belongs to the element it happened on, so the drag went to an element that no longer existed. The second attempt worked because by then nothing was pending, and an older via never showed it because nothing was re-routing. Two causes, both identity: the vias arrive as a fresh object on every reload even when the rows came back identical, and `onMoveVia` is a fresh closure on every render of the planner. Both were in the effect's dependencies. The list is now keyed by what a handle is built from — id and position — so it only changes when a via does, and the callbacks are read through a ref. A via that genuinely moved still gets a new list, which is what redraws it where it now is; the test pins both halves, because holding the element still must not mean holding it in the wrong place. Leaflet's markers had the same churn without the same symptom: a fresh `position` array called `setLatLng` on every marker on every render, and a fresh `eventHandlers` object unbound and rebound every listener. Same fix.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { useEffect, useRef, useMemo, useState, createElement, useCallback } from 'react'
|
||||
import { makeMarkerDraggable, makePoiDraggable, draggedPoiId } from './markerDrag'
|
||||
import type { RoadtripVia } from '@trek/shared'
|
||||
import { useStableVias } from './viaMarkerState'
|
||||
import { ALT_CASING, ALT_LABEL_TEXT } from '../Roadtrip/alternativeColors'
|
||||
import type { AlternativeOverlay } from '../Roadtrip/alternativeOverlays'
|
||||
import { serviceMarkerHtml, serviceMarkerOuter } from '../Roadtrip/serviceMarker'
|
||||
@@ -678,6 +679,19 @@ export function MapViewGL({
|
||||
* drag is therefore hand-rolled — pointer events on the element, unproject on move.
|
||||
*/
|
||||
const viaCleanupRef = useRef<(() => void)[]>([])
|
||||
/**
|
||||
* The list only changes when a via does, and the callbacks are read through a ref.
|
||||
*
|
||||
* Both matter for the same reason: this effect destroys every handle and builds new DOM
|
||||
* elements. It used to re-run on every render of the planner — the vias arrived as a
|
||||
* fresh object each time and `onMoveVia` as a fresh closure — so the render that landed
|
||||
* when a freshly placed via finished re-routing pulled the element out from under the
|
||||
* pointer, and the first drag went nowhere.
|
||||
*/
|
||||
const vias = useStableVias(roadtripVias)
|
||||
const viaHandlersRef = useRef({ onMoveVia, onRemoveVia })
|
||||
viaHandlersRef.current = { onMoveVia, onRemoveVia }
|
||||
const viasDraggable = !!onMoveVia
|
||||
useEffect(() => {
|
||||
const map = mapRef.current
|
||||
if (!map || !mapReady) return
|
||||
@@ -685,12 +699,12 @@ export function MapViewGL({
|
||||
viaCleanupRef.current = []
|
||||
viaPinsRef.current.forEach(p => p.remove())
|
||||
viaPinsRef.current = []
|
||||
for (const via of Object.values(roadtripVias ?? {}).flat()) {
|
||||
for (const via of vias) {
|
||||
const el = document.createElement('span')
|
||||
el.style.cssText = 'display:block;width:12px;height:12px;border-radius:9999px;background:#0a84ff;border:2.5px solid #fff;box-shadow:0 1px 4px rgba(0,0,0,.45);cursor:grab;touch-action:none'
|
||||
const pin = attachPin(map, gl, pinLayerRef.current, el, via.lng, via.lat)
|
||||
viaPinsRef.current.push(pin)
|
||||
if (!onMoveVia) continue
|
||||
if (!viasDraggable) continue
|
||||
|
||||
let dragging = false
|
||||
// The map starts panning on mousedown/touchstart anywhere on its surface, and a pin
|
||||
@@ -716,9 +730,12 @@ export function MapViewGL({
|
||||
el.style.cursor = 'grab'
|
||||
const rect = map.getContainer().getBoundingClientRect()
|
||||
const at = map.unproject([e.clientX - rect.left, e.clientY - rect.top])
|
||||
onMoveVia(via.day_id, via.id, at.lat, at.lng)
|
||||
viaHandlersRef.current.onMoveVia?.(via.day_id, via.id, at.lat, at.lng)
|
||||
}
|
||||
const onContext = (e: MouseEvent) => {
|
||||
e.preventDefault()
|
||||
viaHandlersRef.current.onRemoveVia?.(via.day_id, via.id)
|
||||
}
|
||||
const onContext = (e: MouseEvent) => { e.preventDefault(); onRemoveVia?.(via.day_id, via.id) }
|
||||
el.addEventListener('pointerdown', onDown)
|
||||
el.addEventListener('pointermove', onMove)
|
||||
el.addEventListener('pointerup', onUp)
|
||||
@@ -738,7 +755,7 @@ export function MapViewGL({
|
||||
el.removeEventListener('dblclick', swallow)
|
||||
})
|
||||
}
|
||||
}, [roadtripVias, mapReady, glProvider, onMoveVia, onRemoveVia])
|
||||
}, [vias, viasDraggable, mapReady, glProvider])
|
||||
|
||||
/**
|
||||
* The offered routes, drawn under the current one.
|
||||
|
||||
Reference in New Issue
Block a user