"the element gets the tk to the element itself and does not need to only gatekeep that to one component only — all components in a plugin can share that tk" (Driver, 2026-09-23). Inside ONE element that was already true: ProfileLoader provides 'mapboxToken' and eleven components inject it. The gate is the ELEMENT BOUNDARY — every custom element mounts its own app with its own ProfileLoader, which re-derives the token from its own profile-config, so a composite that nests a real map hands the child nothing. THE EXISTING FALLBACK COULD NOT FIRE. The ladder already read window.MAP_TOKEN. MEASURED: that global IS set on the composite leaf and the child still had no search bar — because the ladder lives in a computed() and window.MAP_TOKEN is a plain global, not a reactive source. The composite leaf FETCHES its token, so the computed resolved '' first, cached, and never re-ran. useSharedMapToken makes it a ref that updates on publish, and assignments to window.MAP_TOKEN are adopted through an accessor, so already-generated pages keep working untouched. Storage lives on window and changes travel as an event: each bundle is its own IIFE, so a module-level ref is NOT shared between a composite and the bundle it nests. THAT WAS NECESSARY AND NOT SUFFICIENT, which only measuring showed. With the token shared and reactive the bar was STILL absent. Real cause: GeocoderComponent's onMounted hit `typeof MapboxGeocoder === 'undefined'` and RETURNED SILENTLY — no throw, no console error, no DOM. MapboxGeocoder is "preloaded via index.html", true for a LEAF and false for a COMPOSITE leaf, which generate-tree classifies as token-free while it goes on to nest a real map. Measured, mapbox-gl-geocoder references per page: research-map leaf 2, research-map-plus-grid leaf 0. So the script is now fetched on demand when a host did not preload it, reusing the exact shape the maplibre path already had (one loader, two callers) and pinned to the same v5.0.0 the leaves preload. It resolves on error as well as load, so a blocked CDN leaves the map usable and the bar absent rather than hanging. This fixes every already-generated page without regenerating any of them. MEASURED on the built tree: leaf mapbox search@56 legend@104 leaf maplibre search@NONE legend@58 (unchanged: maplibre stays bar-free) composite search@56 legend@104 (was NONE) library/ reaches every bundle, so all 22 rebuilt: 22/22, 7/7 pre, 18/18 post, and a browser sweep of five StackLayout consumers plus all seven studio dashboards. |
||
|---|---|---|
| .. | ||
| FullscreenButton.vue | ||
| GeocoderComponent.vue | ||
| HamburgerButton.vue | ||
| HelpButton.vue | ||
| index.js | ||
| LocationButton.vue | ||
| MapControlButton.vue | ||
| MapControlMenu.vue | ||
| MapToolbar.vue | ||
| README.md | ||
| ShareButton.vue | ||
| ThemeToggleButton.vue | ||
| ZoomInButton.vue | ||
| ZoomOutButton.vue | ||
library/components/map-controls/ — toolbar buttons
Each file is a single, focused button or input that mounts inside the map's control area. Replaces
the older monolithic MapControlsComponent.vue.
MapToolbar.vue (container)
├─ ZoomInButton · ZoomOutButton · LocationButton
├─ FullscreenButton · HamburgerButton · ThemeToggleButton
├─ HelpButton · ShareButton
└─ GeocoderComponent (all built on MapControlButton.vue)
- Every button takes a
sizeprop (sm | md | lg) and follows theMapControlButtonshape. - Click handlers go through
inject('mapStore')orgetMap()— never a captured Mapbox instance. - To add one: copy
ZoomInButton.vueand register it inMapToolbar.vue.
Full conventions + catalog: ../../../documentation/architecture/components.md.