From dd6ad31ab98cb8b42760d30cad150cbd9b8066cf Mon Sep 17 00:00:00 2001 From: Maurice Date: Fri, 11 Sep 2026 23:30:38 +0200 Subject: [PATCH] fix(roadtrip): keep day-ending labels draggable after icon changes --- .../components/Map/dayBoundaryDrag.test.ts | 26 ++++++++++++++++++- client/src/components/Map/dayBoundaryDrag.ts | 3 ++- client/src/components/Map/nightPauseMarker.ts | 2 +- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/client/src/components/Map/dayBoundaryDrag.test.ts b/client/src/components/Map/dayBoundaryDrag.test.ts index 11a37d291..a2e598518 100644 --- a/client/src/components/Map/dayBoundaryDrag.test.ts +++ b/client/src/components/Map/dayBoundaryDrag.test.ts @@ -2,8 +2,9 @@ import { expect, it, vi } from 'vitest' import { bindDayBoundaryDrag } from './dayBoundaryDrag' import type { RoadtripStop } from '../Roadtrip/useRoadtripRoutes' -function setup() { +function setup(withHandle = false) { const el = document.createElement('span') + if (withHandle) el.innerHTML = '1' document.body.append(el) const save = vi.fn(async () => true), setPosition = vi.fn(), unlock = vi.fn() const stop = (assignmentId: number, lng: number) => ({ assignmentId, lat: 0, lng }) as RoadtripStop @@ -14,6 +15,29 @@ function setup() { return { el, save, setPosition, unlock, dispose, pointer } } +it('keeps dragging and keyboard controls after Leaflet replaces the label contents', async () => { + const s = setup(true) + for (const kind of ['route', 'place', 'route']) { + const label = s.el.firstElementChild! + s.pointer(label, 'pointerdown', 500) + s.pointer(document, 'pointermove', 990) + s.pointer(document, 'pointerup', 990) + await Promise.resolve() + s.el.innerHTML = `1` + } + expect(s.save).toHaveBeenCalledTimes(3) + expect(s.unlock).toHaveBeenCalledTimes(3) + s.el.firstElementChild!.dispatchEvent(new KeyboardEvent('keydown', { key: 'Delete', bubbles: true })) + expect(s.save).toHaveBeenLastCalledWith(1, null) + await Promise.resolve() + s.dispose() + s.pointer(s.el.firstElementChild!, 'pointerdown', 500) + s.pointer(document, 'pointermove', 990) + s.pointer(document, 'pointerup', 990) + expect(s.save).toHaveBeenCalledTimes(4) + s.el.remove() +}) + it('saves the snapped visit once on release and restores map dragging', async () => { const s = setup() s.pointer(s.el, 'pointerdown', 500) diff --git a/client/src/components/Map/dayBoundaryDrag.ts b/client/src/components/Map/dayBoundaryDrag.ts index 69dddd788..9b2359dfb 100644 --- a/client/src/components/Map/dayBoundaryDrag.ts +++ b/client/src/components/Map/dayBoundaryDrag.ts @@ -16,7 +16,8 @@ interface DragMap { export function bindDayBoundaryDrag(el: HTMLElement, via: RouteVia, controls: DayBoundaryControls, map: DragMap) { const pause = via.nightPause! - const handle = el.querySelector('[data-night-pause]') ?? el + // Leaflet replaces the label contents when its icon changes, but keeps this root. + const handle = el handle.style.cursor = 'grab' handle.style.touchAction = 'none' handle.tabIndex = 0 diff --git a/client/src/components/Map/nightPauseMarker.ts b/client/src/components/Map/nightPauseMarker.ts index 65ca98092..880fa6b4c 100644 --- a/client/src/components/Map/nightPauseMarker.ts +++ b/client/src/components/Map/nightPauseMarker.ts @@ -13,5 +13,5 @@ export function nightPauseMarker(via: RouteVia): string { const stem = pause.atPlace ? 'left:20px;top:-7px;width:10px;height:1px;transform:rotate(-35deg);transform-origin:left center;' : 'left:-.5px;top:-14px;width:1px;height:14px;' - return `${moon}${escapeHtml(String(pause.day))}` + return `${moon}${escapeHtml(String(pause.day))}` }