import { getContext, setContext } from 'svelte'; import { SvelteSet } from 'svelte/reactivity'; import { range } from '../Utils'; interface Selectable { id: number; } export function initSelectionContext( typename: string, toName: (item: T) => string, selectable?: (item: T) => boolean ) { return setContext('selection', new ItemSelection(typename, toName, selectable)); } export function getSelectionContext() { return getContext>('selection'); } export class ItemSelection { active = $state(false); view: T[] = $state([]); #ids = $state(new SvelteSet()); #masked = $derived(new SvelteSet([...this.#ids].filter((i) => this.#indexOf(i) >= 0))); typename: string; #toName: (item: T) => string; selectable: (item: T) => boolean; constructor( typename: string, toName: (item: T) => string, selectable: (item: T) => boolean = () => true ) { this.typename = typename; this.#toName = toName; this.selectable = selectable; } #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 SvelteSet([...this.#ids, ...selectableRange(last, index)]); } else if (index < last) { this.#ids = new SvelteSet([...this.#ids, ...selectableRange(index, last)]); } } else { if (this.#ids.has(id)) { this.#ids.delete(id); } else { this.#ids.add(id); } } }; toggle = () => { this.active = !this.active; if (!this.active) { this.none(); } }; all = () => { this.#ids = new SvelteSet(this.view.filter(this.selectable).map((i) => i.id)); }; none = () => { this.#ids.clear(); this.#masked.clear(); }; clear = () => { this.active = false; this.none(); }; contains(id: number) { return this.#masked.has(id); } 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); } }