refactor: isolate Map Source controls and selection lifecycle

This commit is contained in:
Sameh Khamis committed 2026-09-12 22:25:04 -07:00
1 parent 78199c2ab3
commit 72d531b18f
14 files changed
+405 -72

No files matched your search

+75
View File
@@ -0,0 +1,75 @@
import { renderMapStackChips, syncMapStackChips } from '../mapStackChips.js';
/**
* Own Map Source presentation and selection without constructing map providers.
* The supplied controller remains authoritative for availability and active state.
*/
export function createMapSourceControls({
container, statusElement, controller, subscribe,
claimSelection = () => {}, onStateChanged = () => {}, onError = () => {},
}) {
let destroyed = false;
let generation = 0;
const removers = [];
const bind = (element, type, listener) => {
element.addEventListener(type, listener);
removers.push(() => element.removeEventListener(type, listener));
};
function render(state) {
if (destroyed || !state) return;
syncMapStackChips(container, state.activeId);
if (statusElement) {
const stack = state.activeStack;
statusElement.textContent = state.status === 'switching' ? '...' : (stack?.shortLabel || stack?.label || 'MAP');
statusElement.classList.toggle('warn', !!state.lastError);
}
}
async function select(stackId, { syncShare = true } = {}) {
if (destroyed) return null;
const current = ++generation;
if (syncShare) claimSelection();
const before = controller.getActiveId();
render(controller.getState('switching'));
let state;
try {
state = await controller.setStack(stackId);
} catch (error) {
if (!destroyed && current === generation) {
render(controller.getState());
onError(error?.message || String(error));
}
throw error;
}
if (destroyed || current !== generation) return state;
render(controller.getState());
if (state?.activeId === before && stackId !== before && state?.lastError) onError(state.lastError);
if (syncShare) onStateChanged();
return state;
}
function refresh() {
if (destroyed) return;
for (const remove of removers.splice(0)) remove();
renderMapStackChips(container, controller.getStacks(), {
activeId: controller.getActiveId(),
onSelect: (id) => { void select(id).catch(() => {}); },
bind,
});
render(controller.getState());
}
const unsubscribe = subscribe(() => {
if (destroyed) return;
render(controller.getState());
onStateChanged();
});
refresh();
return {
render, select, refresh,
destroy() {
if (destroyed) return;
destroyed = true;
generation++;
unsubscribe?.();
for (const remove of removers.splice(0)) remove();
},
};
}