diff --git a/app-profiles/doxa-research-map/research-map.vue b/app-profiles/doxa-research-map/research-map.vue index 81c29752a..c6e0da82e 100644 --- a/app-profiles/doxa-research-map/research-map.vue +++ b/app-profiles/doxa-research-map/research-map.vue @@ -218,7 +218,7 @@ useShadowStyles(` .dg-section-header-item { font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:0.6px;color:#9ca3af;padding:2px 0; } .dsm-dark .dg-section-header-item { color:rgba(243,243,241,0.45); } @media(max-width:767px){ - .mapboxgl-ctrl-top-left { top:10px!important;left:3px!important;right:3px!important;width:calc(100% - 6px)!important;max-width:none!important; } + /* The phone search band (a header strip that holds the pill) is owned by GeocoderComponent. */ /* Search-bar height (exactly 35px, desktop AND mobile) is now owned by the shared GeocoderComponent.vue injection 'geocoder-height-35' — no per-profile @media height hack needed (replaces bug-searchbar-mobile-height). */ diff --git a/app-profiles/doxa-simple-map/doxa-simple-map.vue b/app-profiles/doxa-simple-map/doxa-simple-map.vue index 9c20790c8..a4035b60d 100644 --- a/app-profiles/doxa-simple-map/doxa-simple-map.vue +++ b/app-profiles/doxa-simple-map/doxa-simple-map.vue @@ -120,7 +120,7 @@ useShadowStyles(` mobile) is now owned by the shared GeocoderComponent.vue injection 'geocoder-height-35' — no per-profile @media height hack (replaces bug-searchbar-mobile-height). */ - .mapboxgl-ctrl-top-left { top:10px!important;left:3px!important;right:3px!important;width:calc(100% - 6px)!important;max-width:none!important; } + /* The phone search band (a header strip that holds the pill) is owned by GeocoderComponent. */ } /* ── Dark mode: root class drives all child overrides ── */ diff --git a/library/components/map-controls/GeocoderComponent.vue b/library/components/map-controls/GeocoderComponent.vue index ca14e3d91..9bd704a71 100644 --- a/library/components/map-controls/GeocoderComponent.vue +++ b/library/components/map-controls/GeocoderComponent.vue @@ -136,8 +136,14 @@ useShadowStyles(dualEngine( // MOBILE — go nearly edge-to-edge. Also shared: a phone has no room for a dead // side margin, and a profile that simply forgets this rule (as one did) silently // renders its DESKTOP width on mobile. - + '@media(max-width:767px){.mapboxgl-ctrl-top-left{top:10px!important;left:3px!important;' - + 'right:3px!important;width:calc(100% - 6px)!important;max-width:none!important;}}' + // MOBILE — the band becomes a header strip across the top of the map that HOLDS the pill: + // the strip spans the full width, its padding insets the pill, so the pill reads as sitting + // IN a header rather than floating edge-to-edge. Desktop keeps the bare positioned slot. + + '@media(max-width:767px){.dg-search-band{top:0!important;left:0!important;right:0!important;' + + 'width:100%!important;max-width:none!important;margin:0!important;box-sizing:border-box!important;' + + 'padding:9px 14px!important;background:#fff;box-shadow:0 1px 4px rgba(0,0,0,0.10);}' + + '.dg-search-band--dark{background:#3b463d;box-shadow:0 1px 4px rgba(0,0,0,0.35);}' + + '.dg-search-band .mapboxgl-ctrl,.dg-search-band .maplibregl-ctrl{margin:0!important;float:none!important;}}' // Light (default): rounded white pill + soft shadow. + '.mapboxgl-ctrl-geocoder{border-radius:999px!important;background:#fff;' + 'box-shadow:0 2px 8px rgba(0,0,0,0.15)!important;transition:box-shadow .12s,border-color .12s;}' @@ -653,9 +659,23 @@ onMounted(async () => { } } else { map.addControl(geocoder.value, 'top-left') + tagSearchBand() } }) +// THE SEARCH BAND — the control container the engine creates for the 'top-left' slot is the +// search bar's own row. Tagging it with a class of ours (instead of styling the engine's +// .mapboxgl-ctrl-top-left directly) gives the band one owner and one name, so a profile never +// has to know how the engine names its corners. On phones the band becomes a header strip that +// holds the pill (see the 'geocoder-band' styles); on desktop it stays a bare positioned slot. +function tagSearchBand() { + const el = geocoder.value?.container?.parentElement + if (!el) return + el.classList.add('dg-search-band') + el.classList.toggle('dg-search-band--dark', !!props.isDark) +} +watch(() => props.isDark, () => tagSearchBand()) + // Mapbox Geocoder sets `placeholder` once at construction; it has no public // updater method. Watch the effective placeholder and patch the DOM input // directly so the per-map prop can change as the user switches profile tabs. diff --git a/library/components/map-controls/MapControlMenu.vue b/library/components/map-controls/MapControlMenu.vue index bc224c4e3..383081865 100644 --- a/library/components/map-controls/MapControlMenu.vue +++ b/library/components/map-controls/MapControlMenu.vue @@ -7,8 +7,9 @@ • It renders INSIDE the map area (teleported out of the toolbar into the same shadow root), so it never escapes the map's walls, never uses position:fixed, and scrolls with the map when the host page scrolls. - • Its top edge is the toolbar's top edge and its max height is the toolbar column's - height — every menu of a map shares the same band, whichever button opened it. + • Its top edge is the toolbar's top edge. By default it is as tall as its content needs + (bounded only by the map's bottom edge — no scrolling); `maxHeight` caps it lower and + `matchToolbar` limits it to the toolbar column's height (content then scrolls). • It opens on the toolbar's inner side: to the LEFT of right-hand controls, to the RIGHT of left-hand controls. The side is measured, so a right-to-left page that mirrors the toolbar mirrors the menu as well. @@ -18,8 +19,9 @@ Props: open (Boolean), title (String), isDark (Boolean), width (Number, 340) — preferred width; shrinks to what fits (mobile friendly) - maxHeight (Number|null) — cap below the toolbar column's height (null = the column) - height (Number|null) — a fixed height instead of fitting the content + maxHeight (Number|null) — cap the height (null = fit the content within the map) + height (Number|null) — a fixed height instead of fitting the content + matchToolbar (Boolean) — limit the height to the toolbar column (content scrolls) Emits: close Slot: the menu content (tabs, buttons, text) — chrome (header, close) is provided here. --> @@ -52,6 +54,7 @@ const props = defineProps({ isDark: { type: Boolean, default: false }, maxHeight: { type: Number, default: null }, height: { type: Number, default: null }, + matchToolbar: { type: Boolean, default: false }, }) const emit = defineEmits(['close']) @@ -73,7 +76,8 @@ function place() { const offset = onRight ? (ar.right - tr.left + gap) : (tr.right - ar.left + gap) const width = Math.max(200, Math.min(props.width, ar.width - offset - edge)) const top = Math.max(0, tr.top - ar.top) - let maxHeight = Math.max(120, Math.min(tr.height, ar.height - top - edge)) + let maxHeight = Math.max(120, ar.height - top - edge) // fit the content inside the map + if (props.matchToolbar) maxHeight = Math.max(120, Math.min(maxHeight, tr.height)) if (props.maxHeight) maxHeight = Math.min(maxHeight, props.maxHeight) style.value = { top: `${Math.round(top)}px`, diff --git a/library/components/map-controls/ShareButton.vue b/library/components/map-controls/ShareButton.vue index 3c53eb4ad..b040e4f2a 100644 --- a/library/components/map-controls/ShareButton.vue +++ b/library/components/map-controls/ShareButton.vue @@ -426,7 +426,7 @@ async function copyEmbed() { ref="btnEl" :is-dark="isDark" :active="isOpen" - :title="t('buttons.shareEmbed')" + :title="t('buttons.share')" @click="toggle" > @@ -441,7 +441,7 @@ async function copyEmbed() { - +