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.
192 lines
6.5 KiB
JavaScript
192 lines
6.5 KiB
JavaScript
// useMapSelection.js - Map Selection Composable
|
|
// Handles region/family selection and visibility updates
|
|
// Vue 3 Composition API - ES Module with NPM packages
|
|
|
|
// Import Vue functions from NPM package
|
|
import { ref, computed } from 'vue';
|
|
|
|
// Import zoom utilities
|
|
import { getCircleRadiusInterpolation } from '../constants/zoom.js';
|
|
|
|
/**
|
|
* useMapSelection composable - manages map selection state and visibility
|
|
*
|
|
* @param {Object} options - Configuration options
|
|
* @param {Function} options.getMap - Function that returns the map instance
|
|
* @param {string} options.mapId - Unique map identifier for logging
|
|
* @param {Object} options.mapStore - Pinia mapStore for shared state
|
|
* @param {Function} options.addFamilyConnectionLines - Function to add connection lines
|
|
* @param {Function} options.removeFamilyConnectionLines - Function to remove connection lines
|
|
* @param {Function} options.getRegionIsoMap - Returns region-slug → array of iso_3166_1_alpha_3 codes
|
|
*
|
|
* @returns {Object} Selection management functions and state
|
|
*/
|
|
export function useMapSelection(options = {}) {
|
|
const {
|
|
getMap = () => null,
|
|
mapId = 'unknown',
|
|
mapStore = null,
|
|
addFamilyConnectionLines = () => {},
|
|
removeFamilyConnectionLines = () => {},
|
|
// regionIsoMap is built in useMapLayers.addRegionsLayer (region slug →
|
|
// array of alpha-3 ISO codes). The regions-fill features come from the
|
|
// country-boundaries-v1 tileset which exposes iso_3166_1_alpha_3 (NOT a
|
|
// `region` property), so selection highlighting must match on ISO.
|
|
getRegionIsoMap = () => ({})
|
|
} = options;
|
|
|
|
// Local state for color scheme
|
|
const colorScheme = ref('doxa-regions');
|
|
|
|
// Computed from mapStore if available
|
|
const selectedRegion = computed(() => mapStore?.selectedRegion || null);
|
|
const selectedFamily = computed(() => mapStore?.selectedFamily || null);
|
|
|
|
/**
|
|
* Update region layer visibility based on selection
|
|
* Highlights selected region, fades others
|
|
*/
|
|
function updateRegionVisibility() {
|
|
const map = getMap();
|
|
if (!map || !map.getLayer('regions-fill')) {
|
|
return;
|
|
}
|
|
|
|
const region = selectedRegion.value;
|
|
|
|
if (region) {
|
|
// Resolve the selected region's ISO alpha-3 codes. selectedRegion may
|
|
// arrive as a display label ('Asia') while regionIsoMap is keyed by
|
|
// slug ('asia'); normalize (lowercase, strip non-alpha) for a robust
|
|
// match without re-keying the map.
|
|
const rMap = getRegionIsoMap() || {};
|
|
const norm = (s) => String(s).toLowerCase().replace(/[^a-z]/g, '');
|
|
let isos = rMap[region] || [];
|
|
if (!isos.length) {
|
|
const target = norm(region);
|
|
for (const [k, v] of Object.entries(rMap)) {
|
|
if (norm(k) === target) { isos = v; break; }
|
|
}
|
|
}
|
|
|
|
// Highlight selected region (0.85, vibrant), fade others (0.15, dim).
|
|
// Match on iso_3166_1_alpha_3 — the country-boundaries-v1 tileset has
|
|
// no `region` property.
|
|
map.setPaintProperty('regions-fill', 'fill-opacity', [
|
|
'case',
|
|
['in', ['get', 'iso_3166_1_alpha_3'], ['literal', isos]],
|
|
0.85,
|
|
0.15
|
|
]);
|
|
} else {
|
|
// Deselect → restore the vibrant base for all regions.
|
|
const opacity = colorScheme.value === 'none' ? 0.2 : 0.7;
|
|
map.setPaintProperty('regions-fill', 'fill-opacity', opacity);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update pin opacity based on region selection
|
|
* Dims pins outside selected region
|
|
*/
|
|
function updatePinOpacityByRegion() {
|
|
const map = getMap();
|
|
if (!map || !map.getLayer('language-family-pins')) {
|
|
return;
|
|
}
|
|
|
|
const region = selectedRegion.value;
|
|
|
|
// If no region selected, show all pins at normal opacity
|
|
if (!region) {
|
|
map.setPaintProperty('language-family-pins', 'circle-opacity', 0.9);
|
|
return;
|
|
}
|
|
|
|
// Dim pins based on PHYSICAL region location (not cluster membership)
|
|
map.setPaintProperty('language-family-pins', 'circle-opacity', [
|
|
'case',
|
|
['==', ['get', 'doxaRegion'], region],
|
|
0.9, // In selected region: bright
|
|
0.2 // Outside selected region: dimmed
|
|
]);
|
|
|
|
}
|
|
|
|
/**
|
|
* Update language family layer visibility based on selection
|
|
* Highlights selected family, fades others
|
|
*
|
|
* @param {Array} peopleGroups - People groups data (for connection lines)
|
|
*/
|
|
function updateLanguageFamilyVisibility(peopleGroups = []) {
|
|
const map = getMap();
|
|
if (!map || !map.getLayer('language-family-pins')) {
|
|
return;
|
|
}
|
|
|
|
const family = selectedFamily.value;
|
|
|
|
if (family) {
|
|
// Highlight selected family, fade others
|
|
map.setPaintProperty('language-family-pins', 'circle-opacity', [
|
|
'case',
|
|
['==', ['get', 'languageFamily'], family],
|
|
1,
|
|
0.15
|
|
]);
|
|
|
|
// Keep standard radius (no size change on selection)
|
|
// NOTE: Cannot use zoom expressions inside case statements in Mapbox
|
|
|
|
// Add connecting lines for selected family (tapestry effect)
|
|
addFamilyConnectionLines(family, peopleGroups);
|
|
|
|
} else {
|
|
// Show all at normal opacity and size
|
|
map.setPaintProperty('language-family-pins', 'circle-opacity', 0.9);
|
|
map.setPaintProperty('language-family-pins', 'circle-radius',
|
|
getCircleRadiusInterpolation('standard')
|
|
);
|
|
|
|
// Remove connection lines
|
|
removeFamilyConnectionLines();
|
|
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set the color scheme for region display
|
|
*/
|
|
function setColorScheme(scheme) {
|
|
colorScheme.value = scheme;
|
|
}
|
|
|
|
/**
|
|
* Clear all selections via mapStore
|
|
*/
|
|
function clearSelections() {
|
|
if (mapStore) {
|
|
mapStore.selectRegion(null);
|
|
mapStore.selectFamily(null);
|
|
}
|
|
}
|
|
|
|
return {
|
|
// State
|
|
selectedRegion,
|
|
selectedFamily,
|
|
colorScheme,
|
|
|
|
// Visibility updates
|
|
updateRegionVisibility,
|
|
updatePinOpacityByRegion,
|
|
updateLanguageFamilyVisibility,
|
|
|
|
// Configuration
|
|
setColorScheme,
|
|
clearSelections
|
|
};
|
|
}
|
|
|
|
// ES Module export
|