From 056942b33fd24ff6d65fc2ac3bfc6439baffaffc Mon Sep 17 00:00:00 2001 From: doxa-maps-88655c70 <88627970+doxa-maps-88655c70@users.noreply.github.com> Date: Fri, 25 Sep 2026 18:01:49 +0000 Subject: [PATCH] engineFor(map): build markers, popups and poster maps with the map's OWN engine Every generated leaf
preloaded mapbox-gl.js whatever the provider, so code that used the bare `mapboxgl` global always found one, even on a MapLibre map. Removing that peer (it sends every visitor's IP to Mapbox under provider=maplibre) turns each such site into an error. engineFor(map), next to isMapboxMap in useMapProvider.js, asks the map which engine built it: - useSelectedPin: the selected-pin Marker - useMapEvents: the pin-fallback and region Popups - useMapPoster: main already builds the hidden map with the source map's engine (1cbfb3aa); its choice now goes through this one helper instead of a second inline sniff - pplr-data-maps: glFor is now this helper; the geocoder awaits ensureEngineScript first - key-cities: ensureEngineScript('mapbox') before reading the global Measured with every api.mapbox.com tag stripped from the served leaf (no peer at all for key-cities), before -> after: pin select under MapLibre, on cities-internet, country-posters, doxa-research-map and doxa-simple-map: "reading 'Marker'" -> marker drawn; country-posters region popup: none -> popup; PPLR search: no input -> suggestions (both engines); key-cities: "mapbox-gl not loaded" -> map loaded. Mapbox rows unchanged (they passed before and after). The poster UI could not be driven: PosterDialog never opens in country-posters or doxa-research-map (they pass props it does not declare and no modelValue/getMap), reported separately. useMapPoster itself was rendered from a real map with only its engine on the page: MapLibre "mapboxgl global not found" on the pre-1cbfb3aa library -> a 400x300 PNG with 88 colours. --- library/composables/poster/useMapPoster.js | 3 ++- library/composables/useMapEvents.js | 5 +++-- library/composables/useMapProvider.js | 22 +++++++++++++++++++ library/composables/useSelectedPin.js | 5 +++-- plugin-designs/key-cities/cities-map.vue | 6 ++++- .../src/components/MapboxGeocoder.vue | 10 ++++++--- .../pplr-data-maps/src/utils/pplrData.js | 9 ++------ 7 files changed, 44 insertions(+), 16 deletions(-) diff --git a/library/composables/poster/useMapPoster.js b/library/composables/poster/useMapPoster.js index b69f773b6..1ca5e9b75 100644 --- a/library/composables/poster/useMapPoster.js +++ b/library/composables/poster/useMapPoster.js @@ -33,6 +33,7 @@ import { ref, readonly } from 'vue'; import { computeLayout } from './usePosterLayout.js'; +import { engineFor } from '../useMapProvider.js'; const MAX_SINGLE_CANVAS = 4096; // WebGL safe ceiling for most hardware const TILE_PX = 2048; // tile size for stitch path @@ -370,7 +371,7 @@ async function _spawnHiddenMap(sourceMap, w, h, opts = {}) { // eslint-disable-next-line no-undef const W = (typeof window !== 'undefined' ? window : {}); const isMaplibre = !!W.maplibregl && sourceMap instanceof W.maplibregl.Map; - const gl = isMaplibre ? W.maplibregl : W.mapboxgl; + const gl = engineFor(sourceMap); // the one per-instance helper (useMapProvider.js) if (!gl) throw new Error('useMapPoster: ' + (isMaplibre ? 'maplibregl' : 'mapboxgl') + ' global not found'); const styleObj = sourceMap.getStyle(); diff --git a/library/composables/useMapEvents.js b/library/composables/useMapEvents.js index a5cc61f60..6871af20b 100644 --- a/library/composables/useMapEvents.js +++ b/library/composables/useMapEvents.js @@ -5,6 +5,7 @@ // Import Vue functions from NPM package import { ref, inject } from 'vue'; import { useI18n } from 'vue-i18n'; +import { engineFor } from './useMapProvider.js'; /** * useMapEvents composable - manages Mapbox layer event handlers @@ -245,7 +246,7 @@ export function useMapEvents(options = {}) { } // Fallback: show popup (for non-prayer legend types) - new mapboxgl.Popup({ + new (engineFor(map).Popup)({ maxWidth: '320px', className: 'people-group-popup' }) @@ -290,7 +291,7 @@ export function useMapEvents(options = {}) { if (e.features && e.features.length > 0) { const properties = e.features[0].properties; - new mapboxgl.Popup() + new (engineFor(map).Popup)() .setLngLat(e.lngLat) .setHTML(generateRegionPopupContent(properties)) .addTo(map); diff --git a/library/composables/useMapProvider.js b/library/composables/useMapProvider.js index 00cbf3557..f764575cb 100644 --- a/library/composables/useMapProvider.js +++ b/library/composables/useMapProvider.js @@ -61,6 +61,28 @@ export function isMapboxMap(map) { return typeof window !== 'undefined' && !!window.mapboxgl && map instanceof window.mapboxgl.Map } +/** + * The engine namespace that built THIS map: window.maplibregl or window.mapboxgl. Use it for + * everything constructed alongside a map (Marker, Popup, a second Map, controls) instead of + * the bare `mapboxgl` global. + * + * ★ WHY. Every generated leaf used to load mapbox-gl.js statically, whatever the provider, + * so a bare `new mapboxgl.Popup()` always found a global, even on a MapLibre map (where it + * built a MAPBOX popup and attached it to a MapLibre map). Removing that static peer (a visitor-IP + * leak to api.mapbox.com under MapLibre) turns each of those into a ReferenceError on the first + * pin select or click, and the poster's hidden map into 'mapboxgl global not found'. + * Asking the MAP which engine it is removes the dependency on what else the page loaded. + * @param {object} map the live map instance + * @returns {object|undefined} the engine namespace, or undefined when neither is loaded + */ +export function engineFor(map) { + if (typeof window === 'undefined') return undefined + const ml = window.maplibregl, mb = window.mapboxgl + if (ml && ml.Map && map instanceof ml.Map) return ml + if (mb && mb.Map && map instanceof mb.Map) return mb + return mb || ml +} + /** Mapbox-Standard-only lighting paint props, per engine. MapLibre rejects layers whose * paint carries unknown properties (the whole layer is dropped → invisible pins), so * spread this instead of writing '