The popup is built with no explicit anchor, so the engine puts it above the pin when there is room and below when there is not. Measured on the drop-in: a 452px popup on a pin in the lower half of a 780px map fits NEITHER way, so it opens below and the map's overflow:hidden cuts off the Pray and Full Profile buttons. A real click at their centres then lands on the page behind, though a scripted click still navigates -- so the buttons look present and are not reachable. Moving the PIN is what makes an anchor fit, so the map pans rather than the popup changing. Its design is untouched, which is what keeps this a clone; it is what Leaflet does by default. The alternative, a shorter popup, would have been a visible departure and a product decision. The pan is the smallest one that works, and most pins need none: on a 780px map only a narrow band moves at all, by 74px at the worst position rather than a jump. It is animated over 200ms. It resolves at every width this component renders at. The container is max(780, 16/9 of width) at >=768px and twice the width below that, so even at 320px wide there is 640px of container against a 452px popup -- 164px of headroom, and the popup would have to grow by that much before any width could overflow. So no inner scroll and no residual case. It returns 0 rather than panning when the popup is TALLER than its container: a host can give the map a short slot, and there a pan would only move the clipping around. The failure stays what it is today instead of becoming a clipped popup and a camera that jumped. Unmeasurable inputs -- a map mid-load, a zero-height container, an unprojectable point -- also return 0 rather than throwing, and the pan itself is wrapped, because a pan is a nicety and must never break a click. The offset lives in one constant now. The pan arithmetic has to use the same number the popup was built with, and two literals drift. Proven against the shipping function, lifted out of this file by source rather than restated: 16 checks, including every pin position at 320, 390, 768 and 1440px wide -- none left clipped. The fit check is stated independently of the function, because an earlier version asked the function whether it had succeeded, and that hid a dropped offset: the pan was 12px short and its own test 12px lax, so the two agreed while the button still clipped. Mutation-tested four ways -- dropped offset, zeroed offset constant, flipped sign, removed squeeze guard -- each caught. Both consumers rebuilt: country-map +623 B, regions-dashboard +672 B. |
||
|---|---|---|
| .. | ||
| charts | ||
| dashboards | ||
| hints | ||
| legends | ||
| map-controls | ||
| maps | ||
| poster | ||
| qa | ||
| regions | ||
| search | ||
| tables | ||
| BackToTopButton.vue | ||
| LargerScreenNotice.vue | ||
| PeopleGroupDetail.vue | ||
| README.md | ||
| ResearchMapFilterPanel.vue | ||
| ResearchMapSideMenu.vue | ||
| SideMenuDrawer.vue | ||
| ViewSelect.vue | ||
| WARNING.md | ||
library/components/ — shared Vue components
The map UI, built from parameterized pieces every app-profile reuses. Fix one here and every map benefits — never edit a shared component to customize one map.
library/components/
┌───────────────────────────────────────────┐
│ legends/ map-controls/ poster/ │
│ charts/ tables/ │
│ PeopleGroupDetail.vue SideMenuDrawer.vue │
│ ResearchMapFilterPanel.vue …SideMenu.vue │
└───────────────────────────────────────────┘
reused by every app-profile via @map/components/…
legends/,map-controls/,poster/each have their own README.- Every component uses
<script setup>, callsuseShadowStyles(), and reaches stores viainject('mapStore' | 'uiStore' | 'dataStore')— never a direct store import.
Full catalog + conventions:
../../documentation/architecture/components.md ·
per-file roles: ../../documentation/reference.md.