first step in refactoring autocomplete: combining input and options

Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
This commit is contained in:
2026-03-16 23:11:16 +01:00
parent e14e37fc9d
commit ef71cf3b20
2 changed files with 57 additions and 44 deletions

View File

@@ -2,59 +2,72 @@
import { t } from '../translations.svelte.js' import { t } from '../translations.svelte.js'
import { tick } from "svelte"; import { tick } from "svelte";
let { let {
getCandidates = async text => { conole.log('no handler for getCandidates('+text+')'); return {};}, getCandidates = dummyGetCandidates,
onSelect = text => [] onCommit = dummyOnCommit,
onSelect = dummyOnSelect,
} = $props(); } = $props();
const ignore = ['Escape','Tab','ArrowUp','ArrowLeft','ArrowRight'] const ignore = ['Escape','Tab','ArrowUp','ArrowLeft','ArrowRight'];
let options = $state({}); let candidate = $state({ display : '' });
let text = $state('') let candidates = $derived(getCandidates(candidate.display));
async function ondblclick(evt){
const select = evt.target; async function dummyGetCandidates(text){
const key = select.value; console.warn(`getCandidates(${text}) not overridden!`);
text = options[key]; if (!text) return [];
let result = {}; return [
result[key] = text; {
options = {}; display : 'candidate 1',
text = ''; explanation : 'candidates need to have a display field'
onSelect(result); },
{
display : 'candidate 2',
additional : 'other fields are optional',
more : 'and may be domain specific'
},
{ display : text }
];
} }
async function onkeyup(evt){ function dummyOnCommit(candidate){
const select = evt.target; // if Enter is pressed on the input field, this method gets called with
const key = evt.key; // either the selected candidate or
// an anonymous object with the entered text in the display field
console.warn('onCommit not overridden!');
}
function dummyOnSelect(candidate){
console.warn(`${candidate.display} selected, but onSelect not overridden!`)
}
async function onkeyup(ev){
const select = ev.target;
const key = ev.key;
if (ignore.includes(key)) return; if (ignore.includes(key)) return;
if (key == 'ArrowDown'){ candidates = await getCandidates(candidate.display);
if (select.selectedIndex == 0) select.selectedIndex=1;
return;
}
if (key == 'Enter'){
ondblclick(evt);
return;
}
if (key == 'Backspace'){
text = text.substring(0,text.length-1)
} else if (key.length<2){
text += evt.key
}
options = await getCandidates(text);
await tick();
for (let o of select.getElementsByTagName('option')) o.selected = false;
} }
</script> </script>
<style> <style>
select{ div { position : relative }
min-width: 200px; select { position : absolute; top: 30px; left: 3px; }
}
</style> </style>
{#if options}
<select size={Object.keys(options).length<2?2:Object.keys(options).length+1} {onkeyup} {ondblclick} width="40"> <div>
<option>{text}</option> <input type="text" bind:value={candidate.display} {onkeyup} />
{#each Object.entries(options) as [val,caption]} {#if candidates && candidates.length > 1}
<option value={val}>{caption}</option> <select multiple>
{#each candidates as candidate (candidate.display)}
<option value={candidate}>{candidate.display}</option>
{/each} {/each}
</select> </select>
{/if} {/if}
</div>
{#if candidates}
<pre>
{JSON.stringify(candidates,null,2)}
</pre>
{/if}
{JSON.stringify(candidate)}

View File

@@ -66,7 +66,7 @@
<tr> <tr>
<td>{t('add_object',{object:t('member')})}</td> <td>{t('add_object',{object:t('member')})}</td>
<td> <td>
<Autocomplete {getCandidates} {onSelect} /> <Autocomplete />
</td> </td>
</tr> </tr>
</tbody> </tbody>