From 457d16e708e9e0c3a5c21e861311574d21435345 Mon Sep 17 00:00:00 2001 From: Doxa Maps Date: Tue, 29 Sep 2026 01:41:33 +0000 Subject: [PATCH] A map's own ? can ask its host for help: HelpButton and useHostedHelp send doxa:help The Experimental maps' explainer lived in a "?" floating at the top centre of the app, which read as a game piece rather than part of the map. The map already has a "?" control. This lets that control answer from the HOST: a click dispatches doxa:help, bubbling and composed, so it crosses every shadow root between the button and the page, and the app answers with the map's text. library/composables/useHostedHelp.js is the one mechanism, for the HelpButton map control and for the maps whose "?" belongs in their own bar. It also decides WHERE the button shows: only under a host that answers (an ancestor, across shadow roots, carrying data-doxa-help), so the same bundle embedded on a partner page shows no dead "?". HelpButton keeps its open-help emit, so doxa-simple-map's own help popup is unchanged; hosted is opt-in. --- .../components/map-controls/HelpButton.vue | 31 ++++++++++---- library/composables/useHostedHelp.js | 41 +++++++++++++++++++ 2 files changed, 65 insertions(+), 7 deletions(-) create mode 100644 library/composables/useHostedHelp.js diff --git a/library/components/map-controls/HelpButton.vue b/library/components/map-controls/HelpButton.vue index 1b21987ba..d0fa91ac6 100644 --- a/library/components/map-controls/HelpButton.vue +++ b/library/components/map-controls/HelpButton.vue @@ -1,30 +1,47 @@ diff --git a/library/composables/useHostedHelp.js b/library/composables/useHostedHelp.js new file mode 100644 index 000000000..904859020 --- /dev/null +++ b/library/composables/useHostedHelp.js @@ -0,0 +1,41 @@ +/** + * useHostedHelp — a map's "?" that its HOST answers. + * + * A map embedded in the Doxa Maps app (doxa-maps-spa) does not carry its own explainer: the app + * holds the what/how text for every Experimental map. So the map's "?" does not open anything + * itself. It dispatches `doxa:help`, bubbling and COMPOSED, which crosses every shadow root between + * the button and the page, and the app answers with that map's text. + * + * WHERE THE BUTTON SHOWS. Only under a host that answers: an ancestor, across shadow roots, carrying + * `data-doxa-help` (the app sets it on the pane of every tab that has text). The same bundle + * embedded on a partner page has no such ancestor, so it shows no "?" rather than a dead one. + * + * Two callers: the library HelpButton (a map control) and the few maps whose "?" belongs in their + * own bar instead (the Wall, the vision card). One mechanism, so the two cannot drift apart. + * + * const { hosted, askForHelp } = useHostedHelp() + * + */ +import { getCurrentInstance, onMounted, ref } from 'vue' + +/** Is `node` inside a host that answers doxa:help? Walks up, stepping out of each shadow root. */ +export function underHelpHost(node) { + for (let n = node; n; n = n.parentNode || n.host) { + if (n.nodeType === 1 && n.hasAttribute('data-doxa-help')) return true + } + return false +} + +/** Ask the host for this map's help, from `el` (the button, so the host can return focus to it). */ +export function askForHelp(el) { + if (el && el.dispatchEvent) el.dispatchEvent(new CustomEvent('doxa:help', { bubbles: true, composed: true })) +} + +export function useHostedHelp() { + const self = getCurrentInstance() + const hosted = ref(false) + // Mounted means connected: a custom element mounts from connectedCallback, so the chain up to the + // host's pane is already in place. $el may be a comment or fragment anchor; it is in the tree too. + onMounted(() => { hosted.value = underHelpHost(self && self.proxy && self.proxy.$el) }) + return { hosted, askForHelp: (el) => askForHelp(el || (self && self.proxy && self.proxy.$el)) } +}