diff --git a/frontend/src/routes/+page.svelte b/frontend/src/routes/+page.svelte index 801b2280..af786574 100644 --- a/frontend/src/routes/+page.svelte +++ b/frontend/src/routes/+page.svelte @@ -61,6 +61,11 @@ function handleTextSearch() { searchTimer = setTimeout(() => triggerSearch(), 500); } +function handleImmediateSearch() { + clearTimeout(searchTimer); + triggerSearch(); +} + // Trigger search when tags change let prevTagStr = untrack(() => tagNames.map((t) => t.name).join(',')); $effect(() => { @@ -114,6 +119,7 @@ const showRightColumn = $derived(data.canWrite || (data.incompleteDocs?.length ? initialReceiverName={data.initialValues?.receiverName} isLoading={navigating.to !== null} onSearch={handleTextSearch} + onSearchImmediate={handleImmediateSearch} onfocus={() => (qFocused = true)} onblur={() => (qFocused = false)} /> diff --git a/frontend/src/routes/SearchFilterBar.svelte b/frontend/src/routes/SearchFilterBar.svelte index 9786bec6..9c815bca 100644 --- a/frontend/src/routes/SearchFilterBar.svelte +++ b/frontend/src/routes/SearchFilterBar.svelte @@ -22,6 +22,7 @@ let { initialReceiverName = '', isLoading = false, onSearch, + onSearchImmediate, onfocus, onblur }: { @@ -40,6 +41,7 @@ let { initialReceiverName?: string; isLoading?: boolean; onSearch: () => void; + onSearchImmediate?: () => void; onfocus?: () => void; onblur?: () => void; } = $props(); @@ -162,7 +164,7 @@ $effect(() => { class="rounded px-2 py-0.5 text-xs font-bold tracking-widest uppercase transition-colors {tagOperator === 'AND' ? 'bg-primary text-primary-fg' : 'bg-muted text-ink-2 hover:bg-line'}" onclick={() => { tagOperator = 'AND'; - onSearch(); + (onSearchImmediate ?? onSearch)(); }}>AND diff --git a/frontend/src/routes/SearchFilterBar.svelte.spec.ts b/frontend/src/routes/SearchFilterBar.svelte.spec.ts index 86075e09..065f1721 100644 --- a/frontend/src/routes/SearchFilterBar.svelte.spec.ts +++ b/frontend/src/routes/SearchFilterBar.svelte.spec.ts @@ -103,6 +103,29 @@ describe('SearchFilterBar – AND/OR tag operator toggle', () => { await expect.poll(() => onSearch.mock.calls.length).toBeGreaterThan(0); vi.unstubAllGlobals(); }); + + it('calls onSearchImmediate instead of onSearch when operator is toggled and onSearchImmediate is provided', async () => { + vi.stubGlobal( + 'fetch', + vi.fn().mockResolvedValue({ ok: true, json: vi.fn().mockResolvedValue([]) }) + ); + const onSearch = vi.fn(); + const onSearchImmediate = vi.fn(); + render(SearchFilterBar, { + ...defaultProps, + onSearch, + onSearchImmediate, + sort: 'DATE', + dir: 'desc', + tagNames: [{ name: 'Tag1' }, { name: 'Tag2' }] + }); + await openAdvanced(); + const toggle = page.getByTestId('and-or-toggle'); + await toggle.getByRole('button', { name: 'OR' }).click(); + await expect.poll(() => onSearchImmediate.mock.calls.length).toBeGreaterThan(0); + expect(onSearch).not.toHaveBeenCalled(); + vi.unstubAllGlobals(); + }); }); describe('SearchFilterBar – tagQ live filter', () => {