doxa-maps/library/composables/usePplrInstance.js
doxa-maps d5ba21fa76 doxa-maps — portable bundler for embeddable JAMstack maps
Standalone repo that builds embeddable JAMstack map bundles (web components),
composes them into a lazy-loaded SPA via layouts/, and optionally wraps a
Capacitor Android/iOS app — one build, choose your output/CDN. Diátaxis docs.

History reset to a single clean commit; no PII, IPs, or internal terms.
2026-09-10 01:53:07 +00:00

94 lines
3 KiB
JavaScript

/**
* usePplrInstance.js — per-map "instance store" mediator.
*
* Ported verbatim from the upstream PPLR module
* per QA building-round-1 R4 decision: take their legend and instance pattern
* word-for-word, integrate into research-mfe.
*
* Implements the Mediator Pattern: each map cluster (one map + one geocoder +
* one legend) gets its own scoped store. Components never call each other
* directly — they read/write the store and watch its fields.
*
* The research-mfe ALSO has a Pinia mapStore. The SemanticTreeLegend's
* internal code reads/writes this instance; research-map.vue bridges
* instance.selection ↔ mapStore.selected{Family,Language,Dialect} so the
* existing applyDimFilter + geocoder paths keep working unchanged.
*/
import { ref, provide, inject } from 'vue'
const KEY = 'pplrInstance'
/**
* Create a new instance store for one map.
* @param {string} instanceId Stable id used for debugging/keying.
* @returns {object} reactive store
*/
export function createPplrInstance(instanceId) {
return {
instanceId,
// ── Selection (drives map filter + legend highlight) ────────────────────
/** Currently selected tree node, or null. Shape: { id, label, kind, depth, filter, color, ...extras } */
selection: ref(null),
/** Active legend tab index (0-based — but Tab 1 = Generation 1 in user-facing labels) */
activeTab: ref(0),
/** Search-mode flag: true when geocoder text ≥ 2 chars */
isSearching: ref(false),
// ── Geocoder text (read by legend so it can dim itself, etc.) ────────────
geocoderText: ref(''),
// ── Pin-detail panel (clicked pin replaces legend with detail) ──────────
/** Currently clicked pin properties, or null */
pinDetail: ref(null),
// ── Chrome theme — driven by the active basemap (light-v11 → 'light',
// anything else → 'dark'). Legend/geocoder/detail-card read this and
// apply [data-theme] CSS overrides so chrome matches the map underneath.
theme: ref('dark'),
}
}
/**
* Provide the instance to descendants.
* Call this in the profile (parent) component.
*/
export function provideInstance(instance) {
provide(KEY, instance)
}
/**
* Inject the instance in a child component.
* Returns null if no instance was provided (component used standalone).
*/
export function useInstance() {
return inject(KEY, null)
}
/**
* Convenience: select a node, sync legend tab to its depth.
*/
export function selectNode(instance, node) {
if (!instance) return
if (!node) {
instance.selection.value = null
return
}
instance.selection.value = node
if (typeof node.depth === 'number') {
instance.activeTab.value = node.depth
}
}
/**
* Convenience: clear all (selection + geocoder text + pin detail).
*/
export function clearAll(instance) {
if (!instance) return
instance.selection.value = null
instance.geocoderText.value = ''
instance.pinDetail.value = null
}