Research Gallery nested in an app that owns the address: its tab is that app's subtab (shareable pages P2b)

Not the owner: a rail pick announces the tab (claimView); an outer owner such as
Plugin Studio records it as its subtab, and on a partner's page nothing is
written. On load the shell reads subtab when it names one of its tabs, then tab
when it does (a partner's page, one level), else its default. Back follows
subtab only once the shell knows an owner records it (it opened from one, or saw
its announcement recorded), so a partner's own navigation never moves it. As the
owner (its own leaf, DX Life) the code path is unchanged.
This commit is contained in:
Doxa Maps 2026-10-03 07:42:58 +00:00
parent edbd280d4d
commit b26f5d6820

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, writeViewKeys, onAddressChange, urlOwnerOf, ownAddress, claimView } 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(k.subtab)
})
// 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))
@ -1460,7 +1464,15 @@ export default defineComponent({
// 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`;
// `tab` is the outer app's own (a Studio row). So `subtab` 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), mine = (id) => !!id && this.tabs.some(t => t.id === id)
if (urlOwnerOf(this.cfg)) q = k.tab
else if (mine(k.subtab)) { q = k.subtab; 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 +1535,13 @@ 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
const owner = urlOwnerOf(this.cfg)
if (owner) { writeViewKeys({ tab: this.activeKey }, { owner, push: true, el: this.$el }); return }
// Not the owner: announce the pick. An outer owner (Plugin Studio) records it as its `subtab`; on a partner's
// page nobody listens and nothing is written. Seeing it recorded is how this shell learns Back should follow it.
claimView({ tab: this.activeKey }, { el: this.$el })
try { if (readViewKeys(location.search).subtab === 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