Merge Research Gallery nested in an app that owns the address: its (shareable pages P2b)

This commit is contained in:
Doxa Maps 2026-10-03 08:00:35 +00:00
commit 526d1414a7

View file

@ -216,7 +216,7 @@ import { helpFor } from './src/experimentalHelp.js'
import { TAB_INFO } from './src/tabInfo.js'
import MapInfo from '@map/components/info/MapInfo.vue'
import { loadScriptOnce, whenTagDefined } from '@map/utils/embedScript.js'
import { readViewKeys, writeViewKeys, onAddressChange, urlOwnerOf, ownAddress } from '@map/composables/useViewAddress.js'
import { readViewKeys, onAddressChange, urlOwnerOf, ownAddress, claimView, subtabAt, depthOf } from '@map/composables/useViewAddress.js'
// THE HOST PAGE'S LANGUAGE, by the same ladder every nested map already uses (library/i18n
// detectLocale): ?lang= / ?locale= on the page URL, then <html lang>. '' when the page says nothing.
@ -1135,7 +1135,11 @@ export default defineComponent({
// its address: on a partner's page a popstate is the partner's own navigation, and re-reading the URL there
// would snap a host's fixed `tab` attribute back to the default. The owner is read per event: a host may set
// profile-config after mount.
this._offAddress = onAddressChange(({ tab }) => { if (urlOwnerOf(this.cfg)) this.openFromAddress(tab) })
this._offAddress = onAddressChange((k) => {
if (urlOwnerOf(this.cfg)) this.openFromAddress(k.tab)
// nested in an app that records this shell's tab as its `subtab` (Plugin Studio): Back re-opens that one
else if (this._underOwner) this.openFromAddress(subtabAt(location.search, depthOf(this.cfg)))
})
// A nested composite's choice (its open pane, a selected place) reaches the address through this shell, the
// page's one writer, while the page owns its address; a nested element can never switch this shell's tab.
this._offOwn = ownAddress(this.$el, () => urlOwnerOf(this.cfg))
@ -1454,13 +1458,27 @@ export default defineComponent({
if (w >= 0 && !this.isDesktopOnly(want) && !this._honouredTab) this.select(w)
else this.select(i >= 0 && !this.isDesktopOnly(activeId) ? i : this.firstVisibleIndex())
this._honouredTab = true
// NESTED (not the owner): name the settled tab in the owner's address once, as a replace, so a deeper shell's
// claim always has this shell's segment to sit under. Here, not at mount: a link's Experimental tab is only
// honoured once the manifest is in, and a claim at mount would overwrite it with the default.
if (!urlOwnerOf(this.cfg)) this.claimNested(false)
},
// The tab a HOST asked to open first: the `tab` attribute, then profile-config.tab, then ?tab= on
// the page URL (so an iframe src or a shared link can deep-link). '' when nobody asked. The URL is read
// through useViewAddress: the first ?tab= wins, and a value that is not a plain id reads as absent.
requestedTab() {
let q = ''
try { q = readViewKeys(location.search).tab } catch { /* non-browser */ }
// NOT the owner: inside an app that owns the address (Plugin Studio) this shell's tab is that app's `subtab`, at
// this shell's depth (profile-config addressDepth); `tab` is the outer app's own (a Studio row). So the subtab
// segment first when it names one of these tabs, then `tab` when it does (a partner's page, one level), else
// the default.
try {
const k = readViewKeys(location.search), sub = subtabAt(location.search, depthOf(this.cfg))
const mine = (id) => !!id && this.tabs.some(t => t.id === id)
if (urlOwnerOf(this.cfg)) q = k.tab
else if (mine(sub)) { q = sub; this._underOwner = true }
else q = mine(k.tab) ? k.tab : ''
} catch { /* non-browser */ }
return String(this.tab || this.cfg.tab || q || '').trim()
},
// The index to open on mount: the requested tab when it exists and is enabled at this width,
@ -1523,7 +1541,19 @@ export default defineComponent({
onRailSelect(key) {
const before = this.activeKey
this.onSelect(key)
if (this.activeKey !== before) writeViewKeys({ tab: this.activeKey }, { owner: urlOwnerOf(this.cfg), push: true, el: this.$el })
if (this.activeKey === before) return
// The owner writes ?tab=. Not the owner: the pick is announced (claimNested).
const owner = urlOwnerOf(this.cfg)
if (owner) claimView({ tab: this.activeKey }, { owner, el: this.$el, push: true })
else this.claimNested(true)
},
// Not the owner: an outer owner (Plugin Studio) records this shell's tab as its `subtab` segment at this shell's
// depth; on a partner's page nobody listens and nothing is written. Seeing it recorded is how this shell learns
// that Back should follow the address.
claimNested(push) {
const depth = depthOf(this.cfg)
claimView({ tab: this.activeKey }, { owner: '', el: this.$el, push, depth })
try { if (subtabAt(location.search, depth) === this.activeKey) this._underOwner = true } catch { /* no window */ }
},
// Back / Forward: the tab the address names, else the page's configured first tab (profile-config.tab), else the
// device default. Not the `tab` attribute: a host with a router re-points it from this same popstate, and until
@ -1641,6 +1671,8 @@ export default defineComponent({
// #anchor and retitled the host's browser tab "Regions". A child that syncs its state into the
// URL standalone should read this and stand down; children that never write the URL ignore it.
pc.urlSync = false
// ...and how deep they sit below the address owner: a shell nested here records its tab as `subtab` at that depth
pc.addressDepth = urlOwnerOf(this.cfg) ? 1 : depthOf(this.cfg) + 1
// The MAP's own name for its print header (mapPrintTemplate.js): this tab's localized label. A plugin that
// does not print ignores it.
pc.mapTitle = this.tabLabel(t)