From d03b24e6136aac36d3190358e4021b30ba1235af Mon Sep 17 00:00:00 2001 From: Doxa Maps Date: Wed, 30 Sep 2026 06:15:11 +0000 Subject: [PATCH] =?UTF-8?q?Prayer=20Progress=20carries=20its=20own=20"What?= =?UTF-8?q?=20can=20this=20map=20do=3F",=20so=20the=20=E2=93=98=20works=20?= =?UTF-8?q?wherever=20the=20map=20is=20embedded?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On a partner page or a standalone preset page the ⓘ control rendered nothing, because only the gallery answered it and the ⓘ's text lived in the gallery's settings. Now the map carries its info (plugin-designs/doxa-simple-map/src/mapInfo.js) and a host only adds placement: - MapInfoButton gains an opt-in `standalone` prop. When no host answers and the map carries info, it asks the map for a popup of its own (the 'standalone' event). If a real host appears later, the map's popup is dropped and the host takes over. Never inside the gallery (its panes carry data-doxa-gallery): there the gallery alone decides, its own info:false included. - MapInfo gains `popupOnly` (with trigger 'control'): no floating button, since the map's control is the only button; it answers that control from inside the map, points at it once per page load, and opens the same popup (the bottom sheet on phones). It announces itself as local, so the control can tell it from a real host. - Prayer Progress mounts that popup beside its control column, only when asked. Per preset: the text describes the prayer map, so a map that shows the Prayer tab has info (the full profile and the prayer preset); the engagement-only and adoption-only presets have none and show no ⓘ, as before. A host's profile-config info:false removes the ⓘ. Every other MapInfoButton consumer is unchanged (standalone is off by default). doxa-simple-map grows by 20,590 bytes raw (5,699 gzipped), for compiling MapInfo. Rebuilt doxa-simple-map and doxa-maps-spa, then tree + SRI. --- library/components/info/MapInfo.vue | 11 +- .../components/map-controls/MapInfoButton.vue | 33 ++- .../doxa-simple-map/doxa-simple-map.vue | 13 +- plugin-designs/doxa-simple-map/src/mapInfo.js | 19 ++ public/dx-maps/doxa-maps-spa/doxa-maps-spa.js | 2 +- .../doxa-maps-spa/app/index.html | 2 +- .../doxa-maps-spa/doxa-maps-spa/index.html | 2 +- public/dx-maps/doxa-maps-spa/manifest.json | 10 +- .../doxa-simple-map/doxa-simple-map.js | 190 +++++++++++++----- .../doxa-simple-map/adoption/index.html | 2 +- .../doxa-simple-map/engagement/index.html | 2 +- .../doxa-simple-map/index.html | 2 +- .../doxa-simple-map/prayer/index.html | 2 +- public/dx-maps/manifest.json | 10 +- 14 files changed, 228 insertions(+), 72 deletions(-) create mode 100644 plugin-designs/doxa-simple-map/src/mapInfo.js diff --git a/library/components/info/MapInfo.vue b/library/components/info/MapInfo.vue index 75d080f30..f4e8c1fb1 100644 --- a/library/components/info/MapInfo.vue +++ b/library/components/info/MapInfo.vue @@ -81,6 +81,13 @@ const props = defineProps({ * actually on screen (see "control mode" below). */ trigger: { type: String, default: 'floating', validator: (v) => ['floating', 'control'].includes(v) }, + /** + * With trigger 'control': the popup a MAP mounts for its own control when no host answers it (an + * embed, a standalone page). It never draws its own button, since the map's control is the only + * button; it answers that control from inside the map, points at it once per page load, and opens + * the same popup. It announces itself as local, so the control can tell it from a real host. + */ + popupOnly: { type: Boolean, default: false }, }) // ★ THE PAGE-LOAD POINTER (control mode). The pointer beside the map's own ⓘ shows ONCE PER PAGE LOAD: // the first time that control is on screen in the load. It goes for the rest of the load once the reader @@ -292,7 +299,7 @@ onMounted(() => { timer = window.setInterval(measureControl, 1500) window.addEventListener('resize', onResize) // A control that mounted before we did asked into the void; tell it to ask again. - try { window.dispatchEvent(new CustomEvent('dxmaps:mapinfo-host')) } catch { /* no CustomEvent */ } + try { window.dispatchEvent(new CustomEvent('dxmaps:mapinfo-host', { detail: { local: props.popupOnly } })) } catch { /* no CustomEvent */ } }) onBeforeUnmount(() => { if (pane) { @@ -305,7 +312,7 @@ onBeforeUnmount(() => { window.removeEventListener('resize', onResize) }) watch(open, (v) => { control.value?.setAttribute('aria-expanded', String(v)) }) -const floatingShown = () => props.trigger !== 'control' || (!awaitingControl.value && !controlShown.value) +const floatingShown = () => !props.popupOnly && (props.trigger !== 'control' || (!awaitingControl.value && !controlShown.value)) // The pointer beside the control has been on screen: this page load has pointed once, so a remount (the // gallery rebuilds a heavy tab on every switch back) does not point again. Marked on SCREEN, not on mount: // a tab's MapInfo can mount inside a hidden pane. diff --git a/library/components/map-controls/MapInfoButton.vue b/library/components/map-controls/MapInfoButton.vue index 8f27e81d3..7fcf9d2e5 100644 --- a/library/components/map-controls/MapInfoButton.vue +++ b/library/components/map-controls/MapInfoButton.vue @@ -27,8 +27,13 @@ import MapControlButton from './MapControlButton.vue' const { t } = useI18n() -defineProps({ +const props = defineProps({ isDark: { type: Boolean, default: false }, + // The map carries its own info, so the control may stand ALONE: when no host answers (an embed on a + // partner page, a standalone preset page), it asks its map to mount a popup of its own (the + // 'standalone' event) instead of rendering nothing. Never inside the gallery: there the gallery alone + // decides, its own info:false included. Off by default, so every other consumer is unchanged. + standalone: { type: Boolean, default: false }, // 'round' (default) is the map-control form. 'rounded-square' is the dashboard HEADER form (the ⓘ // sits in a header bar beside Share, not in a map's control column) — forwarded to MapControlButton. shape: { type: String, default: 'round' }, @@ -40,6 +45,7 @@ defineProps({ // QUESTION is asked from the toolbar's own element (provided by MapToolbar as mapToolbar.el), or from this // component's placeholder's parent, and once answered the rendered button announces ITSELF, so the host // measures and focuses the real element. +const emit = defineEmits(['standalone']) const toolbar = inject('mapToolbar', null) const self = getCurrentInstance() const hosted = ref(false) @@ -51,12 +57,30 @@ function announce(from, el) { try { from.dispatchEvent(new CustomEvent('dxmaps:mapinfo-control', { bubbles: true, composed: true, detail })) } catch { /* no CustomEvent */ } return detail.ack === true } +// Inside the gallery (its panes carry data-doxa-gallery), across shadow roots. +function inGallery(el) { + for (let n = el; n; n = n.parentNode || n.host) if (n.nodeType === 1 && n.hasAttribute('data-doxa-gallery')) return true + return false +} +// The map's own popup is up: it answers from the map, so this control is hosted by it. +let local = false function ask() { const from = origin() if (!from) return const was = hosted.value hosted.value = announce(from, node()) if (hosted.value && !was) nextTick(() => { const el = node(); if (el) announce(el, el) }) + // Nobody answered and the map carries its info: ask the map for its own popup. When it mounts, it + // announces itself ('dxmaps:mapinfo-host', detail.local), and the question above is asked again. + if (!hosted.value && props.standalone && !local && !inGallery(from)) { local = true; emit('standalone', true) } +} +// A host appeared. The map's own popup asks us again; any OTHER host (the gallery's) takes over, so the +// map's popup is dropped first: it sits inside the map and would answer before the gallery could. +function onHost(e) { + if (local && !(e && e.detail && e.detail.local)) { + local = false; hosted.value = false; emit('standalone', false); nextTick(ask); return + } + ask() } function openInfo() { const el = node() @@ -65,9 +89,12 @@ function openInfo() { } onMounted(() => { nextTick(ask) - window.addEventListener('dxmaps:mapinfo-host', ask) + window.addEventListener('dxmaps:mapinfo-host', onHost) +}) +onBeforeUnmount(() => { + window.removeEventListener('dxmaps:mapinfo-host', onHost) + if (local) emit('standalone', false) }) -onBeforeUnmount(() => { window.removeEventListener('dxmaps:mapinfo-host', ask) })