Clearing the clip is not the same as being clickable. The attribution and the pin hint are painted over the canvas and take pointer events, so a popup that ends fully inside the frame can still have its buttons underneath them. Measured at 390 on a 366x732 map: the pan cleared the clip exactly (cut 0) and a real click on Full Profile still did nothing, because at y~701 the top element at that point was div.maplibregl-ctrl-attrib-inner; on the sibling pin it was span.pin-hint-pill. Both pins panned, both looked fixed, neither was. Neither the popup nor the control is at fault. Attribution is a licence term of the basemap, so raising the popup over it is not available to us. The usable frame is simply shorter than the container, and the fit test has to say so. panPlanFor now takes a bottomInset and fits against containerH - bottomInset. On the measured case the plan changes from -77 (pan up, popup below the pin, buttons under the attribution) to +119 (pan down, popup above the pin, clear of the band) -- the planner flips direction because with the band reserved, fitting above became the smaller move. That is the existing "pick the smaller move" rule doing its job on a corrected frame, not a new rule. The inset is MEASURED from the live controls rather than assumed. A constant would be wrong on the next control we add, at a width where the attribution wraps to two lines, or when the gallery's clearance transform lifts the band. Only controls that intercept pointer events are measured: the corner container is pointer-events:none and spans nearly the full map width at 390, so measuring the container would over-reserve the whole strip. Unchanged where it was already right: a pin whose anchor fits still returns 0 (rega measured pan 0 on those in both arms), a popup taller than the usable frame still refuses rather than jumping the camera, and bottomInset 0 is byte-identical to the previous behaviour. 7 probe cases, with the post-pan fit asserted from the geometry rather than by re-calling the planner -- a self-consistent oracle cannot see an error that shifts both sides. |
||
|---|---|---|
| .. | ||
| api | ||
| charts/whiteboard | ||
| colors | ||
| components | ||
| composables | ||
| constants | ||
| dashboards | ||
| data | ||
| elements | ||
| i18n | ||
| layouts | ||
| lists | ||
| maps | ||
| packet | ||
| packets | ||
| scripts | ||
| stores | ||
| styles | ||
| utils | ||
| vendor/cldr | ||
| index.js | ||
| ProfileLoader.vue | ||
| README.md | ||
library/ (@map) — the shared map template
The other half of the architecture (see ../plugin-designs/README.md):
every map in plugin-designs/<your-map>/ composes the reusable, parameterized pieces that live here.
Fix something here and every map benefits on the next build; break something here and every map
breaks — read components/WARNING.md before touching shared pieces.
| Folder | What lives there |
|---|---|
ProfileLoader.vue |
The root of every bundle: parses profile-config, loads the profile, provides the stores + config |
components/ |
Shared UI — map, legends, controls, popup (PeopleGroupDetail), tables, charts, hints (README) |
composables/ |
Shared logic — map engine (useMapInstance), provider switch, shadow-DOM styles, theming |
stores/ |
Per-instance Pinia stores (ui / map / data) — provided by ProfileLoader, injected by children |
api/ |
Data-source registry (sources.json) + fetch layer — the JAMstack runtime wiring |
colors/ |
Color sets maps opt into via profile-config.colorSet |
i18n/ |
The shared string catalogs (all maps speak the same languages) |
data/ |
Small shared datasets baked at build time (big served data lives in ../data/) |
constants/ |
Shared constants |
scripts/ |
Maintainer pipelines (posters, research) — not part of any bundle |
The rule of thumb (same as WARNING.md): custom code in your app-profile, reusable code here — and things only move from the first to the second once they've earned it.