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:
parent
ac8ea64bd6
commit
c6b6dce11f
2 changed files with 25 additions and 31 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in a new issue