feat(bulk-edit): add bulkSelection store backed by SvelteSet
Module-singleton live accumulator: selection persists across pagination and route changes within /documents and /enrich. Cleared on successful bulk save or via Alles aufheben. Refs #225 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
53
frontend/src/lib/stores/bulkSelection.svelte.spec.ts
Normal file
53
frontend/src/lib/stores/bulkSelection.svelte.spec.ts
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import { afterEach, describe, expect, it } from 'vitest';
|
||||||
|
import { bulkSelectionStore } from './bulkSelection.svelte';
|
||||||
|
|
||||||
|
describe('bulkSelectionStore', () => {
|
||||||
|
afterEach(() => bulkSelectionStore.clear());
|
||||||
|
|
||||||
|
it('starts empty', () => {
|
||||||
|
expect(bulkSelectionStore.size).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('toggle adds an id when absent', () => {
|
||||||
|
bulkSelectionStore.toggle('a');
|
||||||
|
expect(bulkSelectionStore.has('a')).toBe(true);
|
||||||
|
expect(bulkSelectionStore.size).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('toggle removes an id when present', () => {
|
||||||
|
bulkSelectionStore.add('a');
|
||||||
|
bulkSelectionStore.toggle('a');
|
||||||
|
expect(bulkSelectionStore.has('a')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('add and remove update size', () => {
|
||||||
|
bulkSelectionStore.add('a');
|
||||||
|
bulkSelectionStore.add('b');
|
||||||
|
expect(bulkSelectionStore.size).toBe(2);
|
||||||
|
bulkSelectionStore.remove('a');
|
||||||
|
expect(bulkSelectionStore.size).toBe(1);
|
||||||
|
expect(bulkSelectionStore.has('b')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('add is idempotent', () => {
|
||||||
|
bulkSelectionStore.add('a');
|
||||||
|
bulkSelectionStore.add('a');
|
||||||
|
expect(bulkSelectionStore.size).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('setAll replaces the selection', () => {
|
||||||
|
bulkSelectionStore.add('a');
|
||||||
|
bulkSelectionStore.add('b');
|
||||||
|
bulkSelectionStore.setAll(['c', 'd', 'e']);
|
||||||
|
expect(bulkSelectionStore.size).toBe(3);
|
||||||
|
expect(bulkSelectionStore.has('a')).toBe(false);
|
||||||
|
expect(bulkSelectionStore.has('c')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('clear empties the selection', () => {
|
||||||
|
bulkSelectionStore.add('a');
|
||||||
|
bulkSelectionStore.add('b');
|
||||||
|
bulkSelectionStore.clear();
|
||||||
|
expect(bulkSelectionStore.size).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
36
frontend/src/lib/stores/bulkSelection.svelte.ts
Normal file
36
frontend/src/lib/stores/bulkSelection.svelte.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { SvelteSet } from 'svelte/reactivity';
|
||||||
|
|
||||||
|
// Live accumulator. Selection persists across pagination and route changes
|
||||||
|
// within /documents and /enrich. Cleared on successful bulk save or via
|
||||||
|
// "Alles aufheben". The store is module-singleton — there is only ever one
|
||||||
|
// bulk-edit selection per browser session.
|
||||||
|
const selectedIds = new SvelteSet<string>();
|
||||||
|
|
||||||
|
export const bulkSelectionStore = {
|
||||||
|
get ids(): SvelteSet<string> {
|
||||||
|
return selectedIds;
|
||||||
|
},
|
||||||
|
get size(): number {
|
||||||
|
return selectedIds.size;
|
||||||
|
},
|
||||||
|
has(id: string): boolean {
|
||||||
|
return selectedIds.has(id);
|
||||||
|
},
|
||||||
|
toggle(id: string): void {
|
||||||
|
if (selectedIds.has(id)) selectedIds.delete(id);
|
||||||
|
else selectedIds.add(id);
|
||||||
|
},
|
||||||
|
add(id: string): void {
|
||||||
|
selectedIds.add(id);
|
||||||
|
},
|
||||||
|
remove(id: string): void {
|
||||||
|
selectedIds.delete(id);
|
||||||
|
},
|
||||||
|
setAll(ids: Iterable<string>): void {
|
||||||
|
selectedIds.clear();
|
||||||
|
for (const id of ids) selectedIds.add(id);
|
||||||
|
},
|
||||||
|
clear(): void {
|
||||||
|
selectedIds.clear();
|
||||||
|
}
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user