Skip to content

Commit 378f6d4

Browse files
committed
Implement sliding window feature to keep focused markers in view
1 parent 09f29df commit 378f6d4

1 file changed

Lines changed: 51 additions & 0 deletions

File tree

pcd-website/src/components/MapView.vue

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ let mapInstance: import('leaflet').Map | null = null;
2323
let leafletRef: typeof import('leaflet') | null = null;
2424
const markerMap = new Map<string, import('leaflet').Marker>();
2525
let openPopupNodeId: string | null = null;
26+
let slidingWindowHandler: ((e: FocusEvent) => void) | null = null;
2627
2728
// --- Tile style config ---
2829
interface TileLayerConfig { url: string; options: Record<string, unknown>; }
@@ -62,6 +63,7 @@ const MAP_STYLES: MapStyle[] = [
6263
];
6364
6465
const STORAGE_KEY = 'pcd-map-style';
66+
const SLIDING_WINDOW_MARGIN = 0.28; // 28% dead zone inset from each edge
6567
let activeTileLayers: import('leaflet').TileLayer[] = [];
6668
let themeTransitionTimer: number | null = null;
6769
@@ -137,6 +139,35 @@ function closeList() {
137139
listOpen.value = false;
138140
}
139141
142+
function panToKeepInView(lat: number, lng: number): void {
143+
if (!mapInstance) return;
144+
if (selectedNode.value !== null) return;
145+
if (openPopupNodeId !== null) return;
146+
147+
const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
148+
const containerPoint = mapInstance.latLngToContainerPoint([lat, lng]);
149+
const size = mapInstance.getSize();
150+
151+
const mx = size.x * SLIDING_WINDOW_MARGIN;
152+
const my = size.y * SLIDING_WINDOW_MARGIN;
153+
154+
let dx = 0;
155+
let dy = 0;
156+
157+
if (containerPoint.x < mx) dx = containerPoint.x - mx;
158+
else if (containerPoint.x > size.x - mx) dx = containerPoint.x - (size.x - mx);
159+
160+
if (containerPoint.y < my) dy = containerPoint.y - my;
161+
else if (containerPoint.y > size.y - my) dy = containerPoint.y - (size.y - my);
162+
163+
if (dx !== 0 || dy !== 0) {
164+
mapInstance.panBy([dx, dy], {
165+
animate: !reduceMotion,
166+
duration: reduceMotion ? 0 : 0.3,
167+
});
168+
}
169+
}
170+
140171
function onNodeSelect(node: Node) {
141172
selectedNode.value = null;
142173
@@ -385,6 +416,22 @@ onMounted(async () => {
385416
});
386417
});
387418
419+
// Sliding window: pan just enough to keep focused markers in the safe zone
420+
slidingWindowHandler = (e: FocusEvent) => {
421+
const target = e.target as HTMLElement;
422+
if (!target.classList.contains('marker-node')) return;
423+
424+
let foundNode: Node | undefined;
425+
markerMap.forEach((marker, id) => {
426+
if (marker.getElement() === target) {
427+
foundNode = props.nodes.find(n => n.id === id);
428+
}
429+
});
430+
431+
if (foundNode) panToKeepInView(foundNode.lat, foundNode.lng);
432+
};
433+
map.getContainer().addEventListener('focusin', slidingWindowHandler);
434+
388435
// Move focus into popup content when it opens
389436
map.on('popupopen', (e) => {
390437
const container = e.popup.getElement();
@@ -442,6 +489,10 @@ onUnmounted(() => {
442489
}
443490
document.documentElement.classList.remove('theme-transition');
444491
document.removeEventListener('keydown', handleKeydown);
492+
if (slidingWindowHandler && mapInstance) {
493+
mapInstance.getContainer().removeEventListener('focusin', slidingWindowHandler);
494+
slidingWindowHandler = null;
495+
}
445496
mapInstance?.remove();
446497
});
447498
</script>

0 commit comments

Comments
 (0)