Share menu is part of the map: rendered in the map area, placed from its button, top layer on its own; toolbar stays under the legend

This commit is contained in:
swift-river542 2026-09-16 22:30:35 -05:00
parent ac8ea64bd6
commit c6b6dce11f
2 changed files with 25 additions and 31 deletions

View file

@ -40,11 +40,6 @@ useShadowStyles(`
gap: var(--map-toolbar-gap, 8px);
z-index: 10;
}
/* While a control's menu (the share menu) is open the toolbar is lifted above every other
map layer — search pill, legend, sheet — so the menu can never underlap them. */
.map-toolbar.has-open-menu {
z-index: 3000;
}
/* Slim every toolbar control to a fixed 32px (coder 2026-06-24: "buttons can be
smaller"): smaller but still tappable, and uniform so they all align. The icons
inside stay their own fixed px sizes (they don't scale with the button), so they

View file

@ -50,9 +50,6 @@ useShadowStyles(`
/* ---- popover shell (imitates SemanticTreeLegend .stl-inner) ---- */
.share-pop{
position:absolute;
top:0;
right:48px;
width:min(340px, calc(100vw - 80px));
background:#161b22;
border:1px solid #30363d;
border-radius:12px;
@ -367,12 +364,6 @@ useShadowStyles(`
right:52 = MapToolbar right(10) + button-width(36) + 6px breathing room. */
@media(max-width:767px){
.share-pop{
position:fixed;
top:64px;
left:auto;
right:12px;
z-index:3000;
width:min(340px, calc(100vw - 24px));
max-width:none;
max-height:calc(100vh - 96px);
overflow-y:auto;
@ -392,22 +383,25 @@ const props = defineProps({
/* ---- state ---- */
const rootEl = ref(null)
const isOpen = ref(false)
// The toolbar (this button's parent) is lifted above every map layer while the menu is open.
watch(isOpen, async (open) => {
const tb = rootEl.value?.closest('.map-toolbar'); if (tb) tb.classList.toggle('has-open-menu', open)
if (open) { await nextTick(); keepInsideMap() }
})
// Natural placement: top-aligned with the button, to its left; if the menu would run past the
// map's bottom edge it slides up just enough to stay inside (desktop only — phones use a band).
const popShift = ref(0)
function keepInsideMap() {
const pop = rootEl.value?.querySelector('.share-pop'); const host = rootEl.value?.getRootNode()?.host
if (!pop || !host || window.matchMedia('(max-width: 767px)').matches) { popShift.value = 0; return }
popShift.value = 0
const hb = host.getBoundingClientRect(), pr = pop.getBoundingClientRect()
const overflow = pr.bottom - (hb.bottom - 8)
if (overflow > 0) popShift.value = -Math.min(overflow, Math.max(0, pr.top - hb.top - 8))
// The menu is rendered INSIDE the map area (same shadow root, teleported out of the toolbar) and
// positioned from its button's rectangle, so it: stays inside the map's walls, scrolls with the map,
// sits above every other map layer (z 3000) without lifting the toolbar, and keeps its place on resize.
const menuHost = ref(null)
const menuStyle = ref({})
function placeMenu() {
const area = menuHost.value, btn = btnEl.value?.$el || btnEl.value
if (!area || !btn) { menuStyle.value = {}; return }
const ar = area.getBoundingClientRect(), br = btn.getBoundingClientRect()
const phone = ar.width < 600
const width = Math.min(340, phone ? ar.width - 24 : ar.width - 80)
const right = phone ? 12 : Math.max(8, ar.right - br.left + 8)
let top = br.top - ar.top
const pop = popoverEl.value
if (pop) { const h = pop.offsetHeight; if (top + h > ar.height - 8) top = Math.max(8, ar.height - 8 - h) }
menuStyle.value = { position: 'absolute', top: `${Math.round(top)}px`, right: `${Math.round(right)}px`, width: `${Math.round(width)}px` }
}
watch(isOpen, async (open) => { if (open) { await nextTick(); placeMenu(); await nextTick(); placeMenu() } })
function onResize() { if (isOpen.value) placeMenu() }
const popoverEl = ref(null)
const btnEl = ref(null)
const activeTab = ref('link') // 'link' | 'embed'
@ -520,10 +514,13 @@ function onKeyDown(e) {
onMounted(() => {
document.addEventListener('click', onClickOutside, true)
window.addEventListener('resize', onResize)
menuHost.value = rootEl.value?.closest('.dsm-map-area, .rm-map-area, .dm-map-area, [data-map-area]') || null
document.addEventListener('keydown', onKeyDown)
})
onBeforeUnmount(() => {
document.removeEventListener('click', onClickOutside, true)
window.removeEventListener('resize', onResize)
document.removeEventListener('keydown', onKeyDown)
})
</script>
@ -548,13 +545,14 @@ onBeforeUnmount(() => {
</svg>
</MapControlButton>
<!-- =================== Popover =================== -->
<!-- =================== Popover (teleported into the map area) =================== -->
<Teleport :to="menuHost" :disabled="!menuHost">
<div
v-if="isOpen"
ref="popoverEl"
class="share-pop"
:class="{ light: !isDark }"
:style="popShift ? { top: popShift + 'px' } : null"
:style="menuStyle"
>
<!-- Header -->
<div class="share-header">
@ -659,5 +657,6 @@ onBeforeUnmount(() => {
</button>
</div>
</div>
</Teleport>
</div>
</template>