From e33baad901367b2aa5fda6ba468dd4dfa5137485 Mon Sep 17 00:00:00 2001 From: gnezim Date: Thu, 30 Apr 2026 18:53:03 +0300 Subject: [PATCH] Fix first city click handling on flights map --- .../components/FlightsMapStartPage.scss | 3 +- .../flights-map/components/MapCanvas.test.tsx | 59 +++++++++++++++++-- .../flights-map/components/MapCanvas.tsx | 8 +++ 3 files changed, 65 insertions(+), 5 deletions(-) diff --git a/src/features/flights-map/components/FlightsMapStartPage.scss b/src/features/flights-map/components/FlightsMapStartPage.scss index dc0cf7f2..99174728 100644 --- a/src/features/flights-map/components/FlightsMapStartPage.scss +++ b/src/features/flights-map/components/FlightsMapStartPage.scss @@ -313,6 +313,8 @@ border: none; box-shadow: none; padding: 0; + pointer-events: auto; + cursor: pointer; font-family: 'Inter', 'Roboto', Arial, sans-serif; color: colors.$text-color; font-size: fonts.$font-size-s; @@ -323,7 +325,6 @@ 1px 0 rgba(255, 255, 255, 0.53), 0 1px rgba(255, 255, 255, 0.53), 0 -1px rgba(255, 255, 255, 0.53); - pointer-events: none; &::before { display: none; } } diff --git a/src/features/flights-map/components/MapCanvas.test.tsx b/src/features/flights-map/components/MapCanvas.test.tsx index 2a006aaa..1e5a6e5e 100644 --- a/src/features/flights-map/components/MapCanvas.test.tsx +++ b/src/features/flights-map/components/MapCanvas.test.tsx @@ -13,15 +13,17 @@ import type { IMapMarker } from "../types.js"; interface MockMarker { icon: unknown; tooltipArgs: unknown[] | null; + tooltipHandlers: Record void>>; addTo: ReturnType; on: ReturnType; bindTooltip: ReturnType; + getTooltip: ReturnType; openTooltip: ReturnType; closeTooltip: ReturnType; setIcon: ReturnType; setLatLng: ReturnType; getLatLng: ReturnType; - options: { title?: string }; + options: { title?: string; autoPanOnFocus?: boolean }; } interface MockLayerGroup { @@ -71,10 +73,22 @@ function resetLeafletMockState() { vi.mock("leaflet/dist/leaflet.css", () => ({})); vi.mock("leaflet", () => { - function marker(_latlng: unknown, opts: { icon?: unknown; title?: string } = {}) { + function marker( + _latlng: unknown, + opts: { icon?: unknown; title?: string; autoPanOnFocus?: boolean } = {}, + ) { + const tooltip = { + on: vi.fn((evt: string, fn: (e?: unknown) => void) => { + m.tooltipHandlers[evt] ??= []; + m.tooltipHandlers[evt]!.push(fn); + return tooltip; + }), + }; + const m: MockMarker = { icon: opts.icon, tooltipArgs: null, + tooltipHandlers: {}, addTo: vi.fn((target: { addLayer?: (l: MockMarker) => unknown }) => { target.addLayer?.(m); return m; @@ -84,6 +98,7 @@ vi.mock("leaflet", () => { m.tooltipArgs = args; return m; }), + getTooltip: vi.fn(() => tooltip), openTooltip: vi.fn(() => m), closeTooltip: vi.fn(() => m), setIcon: vi.fn((ic: unknown) => { @@ -92,7 +107,10 @@ vi.mock("leaflet", () => { }), setLatLng: vi.fn(() => m), getLatLng: vi.fn(() => ({ lat: 0, lng: 0 })), - options: opts.title !== undefined ? { title: opts.title } : {}, + options: { + ...(opts.title !== undefined ? { title: opts.title } : {}), + ...(opts.autoPanOnFocus !== undefined ? { autoPanOnFocus: opts.autoPanOnFocus } : {}), + }, }; createdMarkers.push(m); return m; @@ -192,7 +210,11 @@ vi.mock("leaflet", () => { }; } - const L = { marker, layerGroup, map: mapFn, tileLayer, icon, latLng, polyline, popup }; + const DomEvent = { + stop: vi.fn(), + }; + + const L = { marker, layerGroup, map: mapFn, tileLayer, icon, latLng, polyline, popup, DomEvent }; return { default: L, ...L }; }); @@ -254,6 +276,35 @@ describe("MapCanvas — legacy (flat) path", () => { const allMarkers = createdLayerGroups.flatMap((l) => l._markers); expect(allMarkers).toContain(createdMarkers[0]); }); + + it("creates markers with no auto-pan on focus and interactive city labels", () => { + renderCanvas([ + { id: "MOW", lat: 1, lng: 2, style: "blue-small", label: "Москва", tooltipPermanent: true }, + ]); + + const marker = createdMarkers[0]; + expect(marker).toBeTruthy(); + if (!marker) return; + expect(marker.options).toMatchObject({ title: "MOW", autoPanOnFocus: false }); + expect(marker.tooltipArgs?.[1]).toMatchObject({ interactive: true }); + }); + + it("treats city-label click as marker click", () => { + const onMarkerClick = vi.fn(); + renderCanvas( + [{ id: "MOW", lat: 1, lng: 2, style: "blue-small", label: "Москва", tooltipPermanent: true }], + { onMarkerClick }, + ); + + const marker = createdMarkers[0]; + if (!marker) { + throw new Error("expected marker to be created"); + } + const clickHandlers = marker?.tooltipHandlers["click"]; + expect(clickHandlers?.length).toBeGreaterThan(0); + clickHandlers?.[0]?.({} as unknown); + expect(onMarkerClick).toHaveBeenCalledWith("MOW"); + }); }); describe("MapCanvas — categorized: visibility predicate", () => { diff --git a/src/features/flights-map/components/MapCanvas.tsx b/src/features/flights-map/components/MapCanvas.tsx index f4f5720f..e953e5b5 100644 --- a/src/features/flights-map/components/MapCanvas.tsx +++ b/src/features/flights-map/components/MapCanvas.tsx @@ -376,6 +376,7 @@ export const MapCanvas: FC = ({ const marker = L.marker([m.lat, m.lng], { icon: getIcon(iconStyle), title: m.id, + autoPanOnFocus: false, }); if (m.label) { @@ -383,6 +384,13 @@ export const MapCanvas: FC = ({ permanent: m.tooltipPermanent ?? false, direction: "top", className: "city-label", + interactive: true, + }); + + const tooltip = marker.getTooltip(); + tooltip?.on("click", (event: L.LeafletMouseEvent) => { + L.DomEvent.stop(event); + onMarkerClickRef.current?.(m.id); }); }