Poster section element: card-action="dashboard" turns each card's sticker into Show dashboard and dispatches dxm-poster-dashboard for the host (absent keeps Show on map)

This commit is contained in:
Doxa Maps 2026-10-02 12:43:50 +00:00
parent f9a28bde47
commit 18d1d389be
14 changed files with 71 additions and 3 deletions

View file

@ -1,7 +1,7 @@
<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, minCardPx, showHeader), finds the
* It reads its settings from profile-config (type, scope, collapsible, collapsed, locale, assetsBase, columns, minCardPx, showHeader, cardAction), 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.
*/
@ -46,6 +46,22 @@ const startCollapsed = computed(() => cfg.value.collapsed === true && showHeader
const groupBy = () => title.value
const collapsedBy = () => startCollapsed.value
const instanceId = computed(() => `poster-section-${typeName.value}-${String(cfg.value.scope || 'all')}`)
// card-action="dashboard": the card's sticker reads "Show dashboard" (the wall's headerAction seam, as Poster Design's
// "Show full details") and a click dispatches dxm-poster-dashboard from this element: bubbling, composed (it leaves the
// shadow root, so e.target is the <dxm-poster-section>) and cancelable. The HOST builds its own URL; the element never does.
// name is the data's English name (the type's items in 'en'), never the card's localised label. The two strings live in
// their own catalogue area (posterCard), referenced only here, so only the bundles that carry this element include it.
const DASHBOARD_EVENT = 'dxm-poster-dashboard'
const headerAction = computed(() => (cfg.value.cardAction === 'dashboard'
? { labelKey: 'posterCard.showDashboard', ariaKey: 'posterCard.showDashboardAria' } : null))
const wallRef = ref(null)
function onHeaderAction(item) {
if (cfg.value.cardAction !== 'dashboard' || !item) return
const en = adapter.value ? adapter.value.items(cfg.value.scope, 'en').find((x) => x.id === item.id) : null
const detail = { type: typeName.value, id: item.id, iso3: item.iso3, name: (en || item).label, scope: cfg.value.scope }
const from = wallRef.value && wallRef.value.$el
if (from) from.dispatchEvent(new CustomEvent(DASHBOARD_EVENT, { bubbles: true, composed: true, cancelable: true, detail }))
}
// 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>
@ -55,11 +71,14 @@ const noneLine = computed(() => t('poster.sectionNone', { scope: String(cfg.valu
<!-- 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"
ref="wallRef"
section-only
embedded
:items="items"
:group-by="groupBy"
:section-heads="showHeader"
:header-action="headerAction"
@header-action="onHeaderAction"
:collapsible="collapsible"
:collapsed-by="collapsedBy"
:instance-id="instanceId"

View file

@ -1,5 +1,5 @@
// <dxm-poster-section>'s attributes → its profile-config. Pure (no Vue), so it is testable on its own.
export const ATTRS = ['type', 'scope', 'collapsible', 'collapsed', 'locale', 'assets-base', 'api-base-url', 'is-dark', 'columns', 'min-card-px', 'show-header']
export const ATTRS = ['type', 'scope', 'collapsible', 'collapsed', 'locale', 'assets-base', 'api-base-url', 'is-dark', 'columns', 'min-card-px', 'show-header', 'card-action']
export const TAG = 'dxm-poster-section'
export function posterSectionConfig(el) {
@ -19,6 +19,9 @@ export function posterSectionConfig(el) {
// show-header="false": no section head, so the HOST's own layout is the header (a key only then: every other host's
// config is unchanged). The head is also the collapse toggle, so a headless section never collapses (PosterSectionView).
if (flag('show-header', true) === false) cfg.showHeader = false
// card-action="dashboard": each card's sticker reads "Show dashboard" and asks the HOST to open its own page
// (PosterSectionView dispatches dxm-poster-dashboard). Only a known value sets a key; absent = "Show on map".
if (a('card-action') === 'dashboard') cfg.cardAction = 'dashboard'
return cfg
}

View file

@ -6,7 +6,9 @@
* Attributes: type (default "country"), scope (a region / continent / list key), collapsible (default on;
* collapsible="false" turns it off), collapsed (start shut), locale, assets-base, api-base-url, is-dark, columns
* (posters per row, 1-5), min-card-px (the narrowest a card gets before a row holds one fewer), show-header (default
* on; show-header="false" hides the section's head so the host's layout is its header).
* on; show-header="false" hides the section's head so the host's layout is its header), card-action ("dashboard": each
* card reads "Show dashboard" and dispatches a bubbling, composed dxm-poster-dashboard { type, id, iso3, name, scope } for
* the host to open its own page; absent = "Show on map").
* They are mapped into the standard element's profile-config, so the element keeps defineMapElement's per-instance
* i18n and store, and the profile (PosterSectionView.vue) reads them like every other profile reads its config.
* Any bundle can call registerPosterSectionElement(); a second call is a no-op.

View file

@ -0,0 +1,4 @@
{
"showDashboard": "عرض لوحة المعلومات",
"showDashboardAria": "عرض لوحة معلومات {label}"
}

View file

@ -0,0 +1,4 @@
{
"showDashboard": "Dashboard anzeigen",
"showDashboardAria": "Dashboard anzeigen: {label}"
}

View file

@ -0,0 +1,4 @@
{
"showDashboard": "Show dashboard",
"showDashboardAria": "Show the {label} dashboard"
}

View file

@ -0,0 +1,4 @@
{
"showDashboard": "Ver el panel",
"showDashboardAria": "Ver el panel de {label}"
}

View file

@ -0,0 +1,4 @@
{
"showDashboard": "Voir le tableau de bord",
"showDashboardAria": "Voir le tableau de bord : {label}"
}

View file

@ -0,0 +1,4 @@
{
"showDashboard": "डैशबोर्ड देखें",
"showDashboardAria": "{label} डैशबोर्ड देखें"
}

View file

@ -0,0 +1,4 @@
{
"showDashboard": "Vedi il cruscotto",
"showDashboardAria": "Vedi il cruscotto di {label}"
}

View file

@ -0,0 +1,4 @@
{
"showDashboard": "Ver o painel",
"showDashboardAria": "Ver o painel: {label}"
}

View file

@ -0,0 +1,4 @@
{
"showDashboard": "Vezi panoul",
"showDashboardAria": "Vezi panoul {label}"
}

View file

@ -0,0 +1,4 @@
{
"showDashboard": "Открыть панель",
"showDashboardAria": "Открыть панель: {label}"
}

View file

@ -0,0 +1,4 @@
{
"showDashboard": "查看仪表板",
"showDashboardAria": "查看 {label} 仪表板"
}