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.
This commit is contained in:
parent
06aadaa243
commit
457d16e708
2 changed files with 65 additions and 7 deletions
|
|
@ -1,30 +1,47 @@
|
|||
<!--
|
||||
HelpButton.vue — Opens the "About this map" help modal.
|
||||
HelpButton.vue — the map's "?" control: what this map is and how to use it.
|
||||
|
||||
Service logic extension point: swap the `open-help` emit for an inline
|
||||
tooltip, an external link, a Pinia help-store action, etc.
|
||||
One click, two ways to answer it:
|
||||
- `open-help` (emit): the map shows its own help (doxa-simple-map's popup does).
|
||||
- `doxa:help` (a DOM event, bubbling and COMPOSED; see composables/useHostedHelp.js): a HOST
|
||||
answers for the map. The Doxa Maps app (doxa-maps-spa) answers with the Experimental shelf's
|
||||
what/how text, which is how that explainer left the top of the screen for the map's own control
|
||||
column, next to zoom and fullscreen.
|
||||
|
||||
Props:
|
||||
isDark — theme flag forwarded from parent
|
||||
hosted — show the button ONLY under a host that answers doxa:help. A map whose help lives in its
|
||||
host sets this, so the same bundle embedded on a page with no such host shows no dead "?".
|
||||
|
||||
Emits:
|
||||
open-help — fires on click; parent decides how to display help content
|
||||
-->
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import MapControlButton from './MapControlButton.vue'
|
||||
import { useHostedHelp } from '../../composables/useHostedHelp.js'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
defineProps({
|
||||
isDark: { type: Boolean, default: false }
|
||||
const props = defineProps({
|
||||
isDark: { type: Boolean, default: false },
|
||||
hosted: { type: Boolean, default: false }
|
||||
})
|
||||
|
||||
defineEmits(['open-help'])
|
||||
const emit = defineEmits(['open-help'])
|
||||
|
||||
const { hosted: underHost, askForHelp } = useHostedHelp()
|
||||
const shown = computed(() => !props.hosted || underHost.value)
|
||||
|
||||
function onClick() {
|
||||
emit('open-help')
|
||||
askForHelp()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<MapControlButton data-control="help" :is-dark="isDark" :title="t('buttons.aboutThisMap')" @click="$emit('open-help')">
|
||||
<MapControlButton v-if="shown" data-control="help" :is-dark="isDark" :title="t('buttons.aboutThisMap')" @click="onClick">
|
||||
<span style="font-size:15px;font-weight:700;font-family:serif;line-height:1;">?</span>
|
||||
</MapControlButton>
|
||||
</template>
|
||||
|
|
|
|||
41
library/composables/useHostedHelp.js
Normal file
41
library/composables/useHostedHelp.js
Normal file
|
|
@ -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()
|
||||
* <button v-if="hosted" @click="askForHelp($event.currentTarget)">?</button>
|
||||
*/
|
||||
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)) }
|
||||
}
|
||||
Loading…
Reference in a new issue