Files

148 lines
5.1 KiB
TypeScript

/** Grid selection helpers extracted from GridView_impl.js (P3-B-03). */
export function getSelectedIdSet(gridContainer: any): Set<string> {
const selected = new Set<string>();
if (!gridContainer) return selected;
try {
const raw = gridContainer.dataset?.mjrSelectedAssetIds;
if (raw) {
const parsed = JSON.parse(raw);
if (Array.isArray(parsed)) {
for (const id of parsed) {
if (id == null) continue;
selected.add(String(id));
}
}
return selected;
}
} catch (e: any) {
console.warn("[MJR] selection state parse error:", e);
}
try {
const id = gridContainer.dataset?.mjrSelectedAssetId;
if (id) selected.add(String(id));
} catch (e: any) {
console.debug?.(e);
}
return selected;
}
export function setSelectedIdsDataset(gridContainer: any, selectedSet: Set<string> | unknown, activeId: string = ""): any[] {
const list = Array.from((selectedSet as any) || []);
const active = activeId ? String(activeId) : list[0] ? String(list[0]) : "";
try {
if (list.length) {
gridContainer.dataset.mjrSelectedAssetIds = JSON.stringify(list);
if (active) gridContainer.dataset.mjrSelectedAssetId = String(active);
else delete gridContainer.dataset.mjrSelectedAssetId;
} else {
delete gridContainer.dataset.mjrSelectedAssetIds;
delete gridContainer.dataset.mjrSelectedAssetId;
}
} catch (e: any) {
console.debug?.(e);
}
return list;
}
export function syncSelectionClasses(gridContainer: any, selectedIds: any, getRenderedCards: (c: any) => any[]): void {
if (!gridContainer) return;
const set =
selectedIds instanceof Set
? selectedIds
: new Set(Array.from(selectedIds || []).map(String));
try {
const cards = typeof getRenderedCards === "function" ? getRenderedCards(gridContainer) : [];
for (const card of cards) {
const id = card?.dataset?.mjrAssetId;
if (id && set.has(String(id))) {
card.classList.add("is-selected");
card.setAttribute("aria-selected", "true");
} else {
card.classList.remove("is-selected");
card.setAttribute("aria-selected", "false");
}
}
} catch (e: any) {
console.debug?.(e);
}
}
export function setSelectionIds(
gridContainer: any,
selectedIds: any,
{ activeId = "" }: { activeId?: string } = {},
getRenderedCards: (c: any) => any[],
): string[] {
if (!gridContainer) return [];
const set = new Set<string>(
Array.from(selectedIds || [])
.map(String)
.filter(Boolean),
);
const list = setSelectedIdsDataset(gridContainer, set, activeId);
syncSelectionClasses(gridContainer, set, getRenderedCards);
const selectionDetail = { selectedIds: list, activeId: activeId || list[0] || "" };
try {
gridContainer.dispatchEvent?.(
new CustomEvent("mjr:selection-changed", { detail: selectionDetail }),
);
} catch (e: any) {
console.debug?.(e);
}
// Also dispatch on window so floating overlays (MFV) can subscribe globally.
try {
window.dispatchEvent(new CustomEvent("mjr:selection-changed", { detail: selectionDetail }));
} catch (e: any) {
console.debug?.(e);
}
return list;
}
export function reconcileSelectionIds(
gridContainer: any,
visibleAssetIds: any,
{ activeId = "", emitWhenUnchanged = false }: { activeId?: string; emitWhenUnchanged?: boolean } = {},
getRenderedCards: (c: any) => any[],
): { selectedIds: string[]; pruned: number; activeId: string } {
if (!gridContainer) return { selectedIds: [], pruned: 0, activeId: "" };
const visibleSet = new Set(
Array.from(visibleAssetIds || [])
.map(String)
.filter(Boolean),
);
const current = getSelectedIdSet(gridContainer);
const next = new Set<string>();
let pruned = 0;
for (const id of current) {
if (visibleSet.has(String(id))) next.add(String(id));
else pruned += 1;
}
const nextActiveId =
activeId && next.has(String(activeId))
? String(activeId)
: next.values().next().value || "";
if (pruned === 0 && !emitWhenUnchanged) {
syncSelectionClasses(gridContainer, next, getRenderedCards);
setSelectedIdsDataset(gridContainer, next, nextActiveId);
return { selectedIds: Array.from(next), pruned: 0, activeId: nextActiveId };
}
const selectedIds = setSelectionIds(
gridContainer,
next,
{ activeId: nextActiveId },
getRenderedCards,
);
return { selectedIds, pruned, activeId: nextActiveId };
}
export function safeEscapeId(value: any): string {
try {
return CSS?.escape
? CSS.escape(String(value))
: String(value).replace(/([!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~])/g, "\\$1");
} catch {
return String(value).replace(/([!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~])/g, "\\$1");
}
}