import { getContext, hasContext, setContext } from 'svelte'; import { writable, type Writable } from 'svelte/store'; import { range } from './Utils'; interface Item { id: number; } export const hasSelectionContext = () => hasContext('selection'); export function getSelectionContext() { return getContext>>('selection'); } export function initSelectionContext( typename?: string, toName?: (item: T) => string ) { return setContext>>( 'selection', writable(new ItemSelection(typename, toName)) ); } export class ItemSelection { active = false; typename: string; #toName: (item: T) => string; #view: T[] = []; selectable: (item: T) => boolean = () => true; #ids = new Set(); #masked = new Set(); constructor(typename?: string, toName?: (item: T) => string) { this.typename = typename ?? 'unknown'; this.#toName = toName ?? (() => 'unknown'); } set view(view: T[]) { this.#view = view; this.#updateMasked(); } #indexOf = (id: number) => this.#view.findIndex((v) => v.id === id); update(index: number, shift: boolean) { const id = this.#view[index].id; const selectableRange = (first: number, last: number) => range(first, last) .filter((i) => this.selectable(this.#view[i])) .map((i) => this.#view[i].id); if (shift) { const indices = this.indices; const first = indices.at(0); const last = indices.at(-1); if (first === undefined || last === undefined) { this.#ids.add(id); } else if (index === first || index === last) { this.#ids.clear(); } else if (index > last) { this.#ids = new Set([...this.#ids, ...selectableRange(last, index)]); } else if (index < last) { this.#ids = new Set([...this.#ids, ...selectableRange(index, last)]); } } else { if (this.#ids.has(id)) { this.#ids.delete(id); } else { this.#ids.add(id); } } this.#updateMasked(); return this; } toggle() { this.active = !this.active; if (!this.active) { return this.none(); } return this; } all() { this.#ids = new Set(this.#view.filter(this.selectable).map((i) => i.id)); this.#updateMasked(); return this; } none() { this.#ids.clear(); this.#masked.clear(); return this; } clear() { this.active = false; return this.none(); } contains(id: number) { return this.#masked.has(id); } #updateMasked() { this.#masked = new Set([...this.#ids].filter((i) => this.#indexOf(i) >= 0)); } get ids() { return [...this.#masked]; } get size() { return this.#masked.size; } get indices() { return [...this.#ids].map(this.#indexOf).filter((i) => i >= 0); } get items() { return this.indices.map((i) => this.#view[i]); } get names() { return this.items.map(this.#toName); } }