Merge remote-tracking branch 'origin/development' into poster-design-plugin/b3-land
This commit is contained in:
commit
fd032702d2
7 changed files with 85 additions and 34 deletions
|
|
@ -13,7 +13,7 @@ tab rail nests every production map. It is built to drop onto somebody else's pa
|
|||
|---|---|---|
|
||||
| **Paste** | two lines: `<script>` + `<doxa-maps-spa>` | one `<iframe>` |
|
||||
| **Sizing** | a block in your layout, `85vh` by default | whatever size you give the frame |
|
||||
| **Language** | follows your page (`<html lang>`), or `language="…"` | `?lang=…` on the `src` |
|
||||
| **Language** | follows your page (`<html lang>`), or `language="…"`; readers can switch it in the gallery (see Events) | `?lang=…` on the `src` |
|
||||
| **Open on a map** | `tab="upg-100"` | `?tab=upg-100` |
|
||||
| **Keyless maps** | `provider="maplibre"` | `?provider=maplibre` |
|
||||
| **Your server must send** | CORS on the whole tree | CORS + `frame-ancestors` on the leaf page |
|
||||
|
|
@ -78,6 +78,32 @@ document.querySelector('doxa-maps-spa')
|
|||
.addEventListener('tab-change', (e) => console.log(e.detail[0].id, e.detail[0].label))
|
||||
```
|
||||
|
||||
### The language menu: `doxa-language-request`
|
||||
|
||||
Readers can change the language from the gallery itself: a menu at the end of the phone bar, and at
|
||||
the bottom of the rail on a desktop. Before switching, the gallery asks your page first by dispatching
|
||||
a cancelable `doxa-language-request` event (it bubbles and crosses shadow roots). Its shape is
|
||||
`e.detail.language`, not an array.
|
||||
|
||||
- **Your page owns the language** (it passes `language`, or its URL carries the locale): call
|
||||
`preventDefault()` and change the language your own way. Then set the gallery's `language`, and the
|
||||
gallery follows.
|
||||
|
||||
```js
|
||||
const gallery = document.querySelector('doxa-maps-spa')
|
||||
gallery.addEventListener('doxa-language-request', (e) => {
|
||||
e.preventDefault()
|
||||
switchMySiteTo(e.detail.language) // your router or i18n; then gallery.setAttribute('language', …)
|
||||
})
|
||||
```
|
||||
|
||||
On Nuxt with @nuxtjs/i18n that call is `navigateTo(switchLocalePath(e.detail.language))`;
|
||||
switchLocalePath already keeps the query and hash. If a new locale route re-mounts the page, carry the
|
||||
open tab across: keep the id from `tab-change` (`e.detail[0].id`) in `?tab=`, which the gallery reads.
|
||||
|
||||
- **You do nothing:** the gallery switches itself. The reader's pick then holds even over a `language`
|
||||
you passed, until you change that attribute again. The gallery never writes your page's URL.
|
||||
|
||||
## A good guest on your page
|
||||
|
||||
The gallery is tested to leave its host page as it found it: it does not scroll your page, rewrite
|
||||
|
|
|
|||
|
|
@ -112,6 +112,14 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<!-- THE LANGUAGE MENU OF A HEADER-OFF SHELL: the end of the layout's phone bar, and the bottom of the
|
||||
desktop rail. ShellLanguageMenu first asks the host (a cancelable doxa-language-request). If the
|
||||
host takes it, the host sets `language` and this gallery follows. If not, it emits 'switch' and the
|
||||
gallery switches itself (pickLocale). `current` is uiLocale, so the select always has a matching
|
||||
option. -->
|
||||
<template #phone-end><ShellLanguageMenu :current="uiLocale" @switch="pickLocale" /></template>
|
||||
<template #rail-end="{ collapsed }"><ShellLanguageMenu :current="uiLocale" :collapsed="collapsed" @switch="pickLocale" /></template>
|
||||
|
||||
<!-- EVERY tab mounts its plugin's REAL custom element — there is no iframe mode. The tag is
|
||||
created imperatively into an EMPTY, Vue-unmanaged slot div (:ref, no children) after that
|
||||
bundle's script is injected once, so Vue never fights the custom element's own shadow DOM.
|
||||
|
|
@ -199,6 +207,7 @@
|
|||
<script>
|
||||
import { defineComponent, inject } from 'vue'
|
||||
import VerticalTabsLayout from '@map/layouts/VerticalTabsLayout.vue'
|
||||
import ShellLanguageMenu from '@map/components/ShellLanguageMenu.vue'
|
||||
import BackToTopButton from '@map/components/BackToTopButton.vue'
|
||||
import { SWITCHER_LANGUAGES, loadLocale, saveLocale, getLanguage } from '@map/layouts/app-wrapper-material/languages.js'
|
||||
import { translate, LOCALES, hasKey } from './src/strings.js'
|
||||
|
|
@ -778,7 +787,7 @@ export default defineComponent({
|
|||
// (measured 2026-09-25). .vtl survives today only because it declares display:grid !important.
|
||||
// Everything the layout needs (dir, lang, labels, …) is passed explicitly below.
|
||||
inheritAttrs: false,
|
||||
components: { VerticalTabsLayout, BackToTopButton, MapInfo },
|
||||
components: { VerticalTabsLayout, BackToTopButton, MapInfo, ShellLanguageMenu },
|
||||
// profile-config props the standalone leaf page + gallery hand every element. treeBase lets a
|
||||
// host pin the tree root explicitly; the rest are accepted so the element is a well-behaved
|
||||
// member of the bundler (profile/instanceId/assetsBase/tk/shareUrl/qa are ignored here — the
|
||||
|
|
@ -879,6 +888,11 @@ export default defineComponent({
|
|||
// The host page's own language (detectHostLocale), kept live by a MutationObserver on <html lang>
|
||||
// — a host app that switches language at runtime (Nuxt i18n does) re-drives the whole gallery.
|
||||
hostLang: detectHostLocale(),
|
||||
// A language picked in the shell's menu in THIS session that no host took (see pickLocale). It
|
||||
// outranks the `language` prop it was picked over, and a change to that prop clears it. A saved
|
||||
// choice is never a source: loadLocale falls back to the browser's language, which would
|
||||
// override a partner page's own <html lang>.
|
||||
sessionPick: '',
|
||||
localeOpen: false,
|
||||
switcherLangs: SWITCHER_LANGUAGES,
|
||||
// Curated tabs start from TAB_DEFS and are COMPLETED from the manifest (tag + scriptUrl) as
|
||||
|
|
@ -1027,18 +1041,21 @@ export default defineComponent({
|
|||
showLocaleSwitcher() { return this.showHeader },
|
||||
// The active language's flag/native name for the toolbar toggle button.
|
||||
activeLang() { return getLanguage(this.locale) || { code: this.locale, flag: '🌐', nativeName: this.locale } },
|
||||
// The EFFECTIVE preview language pushed down to every tab. Precedence, most-authoritative first:
|
||||
// 1. `language` prop / profile-config.language|locale|lang — the parent-page (inherited) value.
|
||||
// 2. the manual switcher's data.locale (only meaningful when a header dropdown is present).
|
||||
// Every tab's element receives THIS via profile-config.locale, so the SPA honors one language prop.
|
||||
// 1. `language` prop / profile-config.language|locale|lang — what the host SAYS.
|
||||
// 2. the gallery's own switcher choice — only while that switcher is actually offered.
|
||||
// 3. the host page's language (?lang= / <html lang>, see detectHostLocale).
|
||||
// 4. English.
|
||||
// The language the HOST says: the `language` prop, or profile-config.language|locale|lang.
|
||||
propLocale() {
|
||||
return (this.language || this.cfg.language || this.cfg.locale || this.cfg.lang || '').trim()
|
||||
},
|
||||
// The EFFECTIVE preview language pushed down to every tab (each tab's element receives it via
|
||||
// profile-config.locale). Precedence, most-authoritative first:
|
||||
// 1. a pick from the shell's language menu in THIS session that no host took (sessionPick), which
|
||||
// the watcher below clears as soon as the host's `language` changes;
|
||||
// 2. `language` prop / profile-config.language|locale|lang: what the host SAYS;
|
||||
// 3. the header-on switcher's choice, only while that switcher is actually offered;
|
||||
// 4. the host page's language (?lang= / <html lang>, see detectHostLocale);
|
||||
// 5. English.
|
||||
effectiveLocale() {
|
||||
const fromProp = (this.language || this.cfg.language || this.cfg.locale || this.cfg.lang || '').trim()
|
||||
const fromSwitcher = this.showLocaleSwitcher ? this.locale : ''
|
||||
return (fromProp || fromSwitcher || this.hostLang || 'en').split('-')[0].toLowerCase()
|
||||
return (this.sessionPick || this.propLocale || fromSwitcher || this.hostLang || 'en').split('-')[0].toLowerCase()
|
||||
},
|
||||
// The locale the gallery's OWN words render in: the effective one when it ships, else English.
|
||||
uiLocale() { return LOCALES.includes(this.effectiveLocale) ? this.effectiveLocale : 'en' },
|
||||
|
|
@ -1096,6 +1113,8 @@ export default defineComponent({
|
|||
},
|
||||
watch: {
|
||||
activeIndex() { this.helpOpen = false },
|
||||
// The host changed its language: that is the newest word, so it replaces a menu pick made earlier.
|
||||
propLocale() { this.sessionPick = '' },
|
||||
// ★ A LANGUAGE SWITCH REMOUNTS, IT DOES NOT RE-SET THE ATTRIBUTE. Re-setting the visible tab's
|
||||
// profile-config was the old path, and it relied on every nested map re-reading its whole config
|
||||
// live. Two production maps do not: Big Picture's tStatic strings are resolved once (2/352 changed,
|
||||
|
|
@ -1390,6 +1409,12 @@ export default defineComponent({
|
|||
this.localeOpen = false
|
||||
saveLocale(code)
|
||||
},
|
||||
// The language menu's 'switch': it fires only when no host cancelled doxa-language-request, so the
|
||||
// gallery switches itself, and the pick holds over a `language` prop the host never updates.
|
||||
pickLocale(code) {
|
||||
this.setLocale(code)
|
||||
this.sessionPick = code
|
||||
},
|
||||
// The shared rail emits the selected tab's `key` (its id); map it back to the index the
|
||||
// pane-mount machinery below is keyed on.
|
||||
//
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -67,7 +67,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <doxa-maps-spa> custom element. -->
|
||||
<script src="../../doxa-maps-spa.js" integrity="sha384-eMJnHI1Ed2HY5E2Zx0NpfPeu0upkZUhFg8sffdCZbsz9UP+rnWwQEtSTolFHcQxu" crossorigin="anonymous"></script>
|
||||
<script src="../../doxa-maps-spa.js" integrity="sha384-jJHY8JXkPZd8UQz7gDBNE4n+LX+DsK5MgcafqLChgSL5e1AUz+YaItO8LOX19VzO" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <doxa-maps-spa> custom element. -->
|
||||
<script src="../doxa-maps-spa.js" integrity="sha384-eMJnHI1Ed2HY5E2Zx0NpfPeu0upkZUhFg8sffdCZbsz9UP+rnWwQEtSTolFHcQxu" crossorigin="anonymous"></script>
|
||||
<script src="../doxa-maps-spa.js" integrity="sha384-jJHY8JXkPZd8UQz7gDBNE4n+LX+DsK5MgcafqLChgSL5e1AUz+YaItO8LOX19VzO" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
|
|
@ -361,7 +361,7 @@
|
|||
"bundle": "doxa-maps-spa",
|
||||
"scriptUrl": "./doxa-maps-spa/doxa-maps-spa.js",
|
||||
"output": "dx-maps/doxa-maps-spa/doxa-maps-spa.js",
|
||||
"integrity": "sha384-eMJnHI1Ed2HY5E2Zx0NpfPeu0upkZUhFg8sffdCZbsz9UP+rnWwQEtSTolFHcQxu",
|
||||
"integrity": "sha384-jJHY8JXkPZd8UQz7gDBNE4n+LX+DsK5MgcafqLChgSL5e1AUz+YaItO8LOX19VzO",
|
||||
"elements": [
|
||||
"doxa-maps-spa"
|
||||
],
|
||||
|
|
@ -391,7 +391,7 @@
|
|||
"scriptUrl": "./doxa-maps-spa/doxa-maps-spa.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./doxa-maps-spa/doxa-maps-spa/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./doxa-maps-spa/doxa-maps-spa.js\" integrity=\"sha384-eMJnHI1Ed2HY5E2Zx0NpfPeu0upkZUhFg8sffdCZbsz9UP+rnWwQEtSTolFHcQxu\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<doxa-maps-spa profile-config='{\"profile\":\"doxa-maps-spa\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></doxa-maps-spa>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./doxa-maps-spa/doxa-maps-spa.js\" integrity=\"sha384-jJHY8JXkPZd8UQz7gDBNE4n+LX+DsK5MgcafqLChgSL5e1AUz+YaItO8LOX19VzO\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<doxa-maps-spa profile-config='{\"profile\":\"doxa-maps-spa\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></doxa-maps-spa>"
|
||||
},
|
||||
"instances": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -361,7 +361,7 @@
|
|||
"bundle": "doxa-maps-spa",
|
||||
"scriptUrl": "./doxa-maps-spa/doxa-maps-spa.js",
|
||||
"output": "dx-maps/doxa-maps-spa/doxa-maps-spa.js",
|
||||
"integrity": "sha384-eMJnHI1Ed2HY5E2Zx0NpfPeu0upkZUhFg8sffdCZbsz9UP+rnWwQEtSTolFHcQxu",
|
||||
"integrity": "sha384-jJHY8JXkPZd8UQz7gDBNE4n+LX+DsK5MgcafqLChgSL5e1AUz+YaItO8LOX19VzO",
|
||||
"elements": [
|
||||
"doxa-maps-spa"
|
||||
],
|
||||
|
|
@ -391,7 +391,7 @@
|
|||
"scriptUrl": "./doxa-maps-spa/doxa-maps-spa.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./doxa-maps-spa/doxa-maps-spa/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./doxa-maps-spa/doxa-maps-spa.js\" integrity=\"sha384-eMJnHI1Ed2HY5E2Zx0NpfPeu0upkZUhFg8sffdCZbsz9UP+rnWwQEtSTolFHcQxu\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<doxa-maps-spa profile-config='{\"profile\":\"doxa-maps-spa\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></doxa-maps-spa>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./doxa-maps-spa/doxa-maps-spa.js\" integrity=\"sha384-jJHY8JXkPZd8UQz7gDBNE4n+LX+DsK5MgcafqLChgSL5e1AUz+YaItO8LOX19VzO\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<doxa-maps-spa profile-config='{\"profile\":\"doxa-maps-spa\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></doxa-maps-spa>"
|
||||
},
|
||||
"instances": [
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue