ext(affordances): wireGroupAutocomplete via <datalist>

This commit is contained in:
adlee-was-taken
2026-05-01 18:09:33 -04:00
parent 5fbdd30a19
commit e452d8df02
2 changed files with 58 additions and 0 deletions

View File

@@ -0,0 +1,35 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { wireGroupAutocomplete } from '../group-autocomplete';
describe('wireGroupAutocomplete', () => {
let form: HTMLElement;
beforeEach(() => {
// Clean up any datalist from a prior test
document.getElementById('groups-datalist')?.remove();
form = document.createElement('div');
form.innerHTML = `<input id="f-group" type="text" />`;
document.body.appendChild(form);
});
it('attaches datalist with all groups', async () => {
const sendMessage = vi.fn().mockResolvedValue({
ok: true,
data: { groups: ['personal', 'work', 'finance'] },
});
await wireGroupAutocomplete(form, { sendMessage });
const list = document.getElementById('groups-datalist') as HTMLDataListElement | null;
expect(list).not.toBeNull();
const opts = Array.from(list!.querySelectorAll('option')).map((o) => o.value);
expect(opts).toEqual(['personal', 'work', 'finance']);
const input = form.querySelector('#f-group') as HTMLInputElement;
expect(input.getAttribute('list')).toBe('groups-datalist');
});
it('is a no-op if SW returns error', async () => {
const sendMessage = vi.fn().mockResolvedValue({ ok: false, error: 'vault_locked' });
await wireGroupAutocomplete(form, { sendMessage });
const input = form.querySelector('#f-group') as HTMLInputElement;
expect(input.getAttribute('list')).toBeNull();
});
});

View File

@@ -0,0 +1,23 @@
export interface GroupAutocompleteOpts {
sendMessage: (msg: { type: 'list_groups' }) => Promise<{ ok: boolean; data?: { groups: string[] }; error?: string }>;
}
const DATALIST_ID = 'groups-datalist';
export async function wireGroupAutocomplete(form: HTMLElement, opts: GroupAutocompleteOpts): Promise<void> {
const input = form.querySelector<HTMLInputElement>('#f-group');
if (!input) return;
const resp = await opts.sendMessage({ type: 'list_groups' });
if (!resp.ok || !resp.data) return;
// Datalists must live in the document, not nested inside an input. Reuse if
// we've already mounted one this session.
let list = document.getElementById(DATALIST_ID) as HTMLDataListElement | null;
if (!list) {
list = document.createElement('datalist');
list.id = DATALIST_ID;
document.body.appendChild(list);
}
list.innerHTML = resp.data.groups.map((g) => `<option value="${g.replace(/"/g, '&quot;')}"></option>`).join('');
input.setAttribute('list', DATALIST_ID);
}