doxa-maps/library/components/poster/PosterSectionView.vue
Doxa Maps 21e2200029 Poster sections: one type registry per page; the element takes a columns attribute
- ONE REGISTRY PER PAGE. Every bundle inlines its own copy of posterTypes.js, and <dxm-poster-section> is
  defined by whichever bundle loads first, so with a module-level Map that element saw only its own bundle's
  types: type="upg100-list" rendered "none" when the country-posters bundle had defined it. The registry now
  lives on the page (globalThis.__DXM_POSTER_TYPES__, created once): every copy registers into it and reads
  from it; a name registered twice keeps the first (a dev warning; the built-in country is silent); every
  registration is announced on window (dxm-poster-type-registered), and PosterSectionView looks again when
  its type arrives after it asked.
- COLUMNS (carried from the Poster Sections plugin PR so the element's library lands once): columns="1".."5"
  maps to profile-config.columns and to PosterWall's existing columns prop; absent or out of range is
  today's default, and a reader's saved per-row choice still wins.
- check:poster-type-registry (gates:pre; self-test in gates:post): two independent copies of the module side
  by side, as two bundles on one page; a type registered through one is found through the other; first wins;
  announced. The self-test's per-bundle registry fails.
2026-10-02 08:29:16 +00:00

74 lines
3.9 KiB
Vue

<script setup>
/**
* PosterSectionView.vue — ONE poster section on its own: the profile behind <dxm-poster-section>.
* It reads its settings from profile-config (type, scope, collapsible, collapsed, locale, assetsBase, columns), finds the
* poster TYPE in posterTypes.js, and draws a PosterWall in section-only mode: the same section a wall draws (the
* same PosterSection, the same print actions and overlay), with no wall head around it.
*/
import { computed, inject, ref, watch, onMounted, onBeforeUnmount } from 'vue'
import PosterWall from './PosterWall.vue'
import { posterType, TYPE_REGISTERED_EVENT } from './posterTypes.js'
import { useOptionalT } from '@map/composables/useOptionalT.js'
import { getRuntimeLocale, detectLocale } from '@map/i18n/index.js'
import { getApiBaseUrl } from '@map/api/apiBaseUrl.js'
const profileConfig = inject('profileConfig', ref(null))
const cfg = computed(() => profileConfig.value || {})
const t = useOptionalT('')
const typeName = computed(() => cfg.value.type || 'country')
const adapter = ref(null)
const unknownType = ref(false)
async function resolveType(name) {
const a = await posterType(name)
if (name !== typeName.value) return // the type changed while this one loaded
adapter.value = a; unknownType.value = !a
}
watch(typeName, resolveType, { immediate: true })
// ★ A TYPE MAY ARRIVE AFTER THIS SECTION ASKED: the registry is page-global (posterTypes.js), and another bundle
// that registers the type (upg100's lists, say) can load after the bundle that defined this element. Look again
// when a registration of OUR type is announced.
const onTypeRegistered = (e) => { if (e && e.detail && e.detail.name === typeName.value && !adapter.value) resolveType(typeName.value) }
onMounted(() => { try { window.addEventListener(TYPE_REGISTERED_EVENT, onTypeRegistered) } catch (_) { /* no window */ } })
onBeforeUnmount(() => { try { window.removeEventListener(TYPE_REGISTERED_EVENT, onTypeRegistered) } catch (_) { /* no window */ } })
const lang = () => cfg.value.locale || getRuntimeLocale() || detectLocale() || 'en'
const assetsBase = computed(() => cfg.value.assetsBase || './assets')
const items = computed(() => (adapter.value ? adapter.value.items(cfg.value.scope, lang()) : []))
const title = computed(() => (adapter.value ? adapter.value.title(cfg.value.scope, t) : ''))
const actions = computed(() => (adapter.value
? adapter.value.actions({ assetsBase: () => assetsBase.value, apiBase: () => cfg.value.apiBaseUrl || getApiBaseUrl(), lang, profileConfig, t })
: null))
const collapsible = computed(() => cfg.value.collapsible !== false)
const startCollapsed = computed(() => cfg.value.collapsed === true)
const groupBy = () => title.value
const collapsedBy = () => startCollapsed.value
const instanceId = computed(() => `poster-section-${typeName.value}-${String(cfg.value.scope || 'all')}`)
// Nothing to show still SAYS so (an unknown type or scope never renders as a blank).
const noneLine = computed(() => t('poster.sectionNone', { scope: String(cfg.value.scope || '') }))
</script>
<template>
<p v-if="unknownType || (adapter && !items.length)" class="dxm-ps-none" role="status">{{ noneLine }}</p>
<!-- embedded: an in-flow block the PAGE scrolls (a wall alone is a full-pane scroller), so sections stack on a
page and sit inline under a host's own heading. -->
<PosterWall v-else-if="adapter && actions"
section-only
embedded
:items="items"
:group-by="groupBy"
:collapsible="collapsible"
:collapsed-by="collapsedBy"
:instance-id="instanceId"
:is-dark="cfg.isDark === true"
:columns="cfg.columns || 0"
:assets-base="assetsBase"
:event-name="actions.eventName"
:unit="actions.unit"
:item-count-key="actions.itemCountKey"
:records="actions.records"
:sheet="actions.sheet"
:unsupported="actions.unsupported"
:card-stocks="actions.cardStocks"
:download-prefix="actions.downloadPrefix"
/>
</template>