diff --git a/plugin-designs/cities-internet/connectivity-map.vue b/plugin-designs/cities-internet/connectivity-map.vue index df2bab879..d206886c8 100644 --- a/plugin-designs/cities-internet/connectivity-map.vue +++ b/plugin-designs/cities-internet/connectivity-map.vue @@ -106,6 +106,27 @@ function announceInfoPending(pending) { // A MapInfo that mounts after this map asked into the void announces itself; answer it again. const reannounceInfoPending = () => { if (!mapRef.value && !err.value) announceInfoPending(true) } watch(err, (e) => { if (e) announceInfoPending(false) }) + +// ─── On a phone the control column starts BELOW the folded legend ───────────────────────────────── +// The ⓘ is the column's top control, and its one-time pointer runs sideways from it across the map's +// top band, which on a phone is this legend's. MapToolbar's phone rule puts the column at top:72px, +// inside that band, so the pointer lay over the legend's title and buttons. The column now starts 8px +// below the FOLDED legend's measured bottom, whatever its height in each language. Measured only while +// folded, and before the toolbar exists, so opening the legend never moves the controls (MapInfo places +// its pointer when the control appears and would not follow a later move). Desktop is unchanged. +const legendEl = ref(null) +const toolbarTop = ref(0) +let legendRO = null +function measureLegend() { + const el = legendEl.value + if (!el || !narrow.value || legendOpen.value) return + const b = el.offsetTop + el.offsetHeight + if (b > 0) toolbarTop.value = b + 8 +} +function watchLegend() { + try { legendRO = new ResizeObserver(measureLegend); if (legendEl.value) legendRO.observe(legendEl.value) } catch (_) { legendRO = null } + measureLegend() +} // India by state (data/tables/internet-by-adm1.json, NFHS-5): the one country with sub-national data. const adm1 = shallowRef(null) // { meta, byArea: { [adm1]: { name, women: {total,urban,rural}, men: {…} } } } const adm1Sex = ref('women') // which half of the survey the fill shows; both are in the sheet @@ -427,6 +448,7 @@ let unmounted = false // set on unmount; onMounted's awaits check it before bu onMounted(async () => { announceInfoPending(true) window.addEventListener('dxmaps:mapinfo-host', reannounceInfoPending) + watchLegend() try { await ensureEngineScript(ciProvider) } catch (_) { /* fall through to the notice below */ } // THE ENGINE THE PAGE ASKED FOR, OR NONE. This read `engineGlobal(ciProvider) || window.mapboxgl`: // engineGlobal already answers for Mapbox, so the fallback only fired when MapLibre was asked for and @@ -504,6 +526,7 @@ onMounted(async () => { }) onBeforeUnmount(() => { if (typeof window !== 'undefined') window.removeEventListener('dxmaps:mapinfo-host', reannounceInfoPending) + try { legendRO?.disconnect() } catch (_) {} if (!mapRef.value) announceInfoPending(false) if (typeof window !== 'undefined') window.removeEventListener('resize', syncNarrow) // ★ RELEASE THE TEST HOOKS. Each pins whatever it closes over — a map instance pins its @@ -558,6 +581,7 @@ useShadowStyles( // again — a geometric guarantee, not a tuned z-index. // LOGICAL, not physical: the control rail sits at inset-inline-end, so in Arabic it moves to the // LEFT; a legend pinned left:8px then sat under the zoom buttons (measured in the ar gallery tab). + '@media(max-width:699px){.gm .map-toolbar.map-toolbar{top:var(--gm-toolbar-top,72px)}}' + '.gm-legend{position:absolute;top:8px;inset-inline-start:8px;width:var(--map-legend-width,380px);' + 'max-width:calc(100% - 16px);box-sizing:border-box;z-index:4;padding:8px 10px;' + 'border-radius:10px;background:rgba(255,255,255,.94);color:#0f1419;' + @@ -620,7 +644,7 @@ useShadowStyles(