Implement a basic autocomplete style search
This commit is contained in:
@@ -3,6 +3,8 @@
|
|||||||
import { main } from "$wails/models";
|
import { main } from "$wails/models";
|
||||||
import { CreateFood, GetLastPer100 } from "$wails/main/App";
|
import { CreateFood, GetLastPer100 } from "$wails/main/App";
|
||||||
import { foodStore } from "$lib/store/Energy/foodStore";
|
import { foodStore } from "$lib/store/Energy/foodStore";
|
||||||
|
import FoodSearchEntry from "./FoodSearchEntry.svelte";
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
|
||||||
let item: main.Food = {
|
let item: main.Food = {
|
||||||
food: "",
|
food: "",
|
||||||
@@ -20,6 +22,16 @@
|
|||||||
let per100Edited: boolean = false;
|
let per100Edited: boolean = false;
|
||||||
let per100Element: HTMLTableCellElement;
|
let per100Element: HTMLTableCellElement;
|
||||||
let nameElement: HTMLTableCellElement;
|
let nameElement: HTMLTableCellElement;
|
||||||
|
let autocompleteList: HTMLUListElement;
|
||||||
|
let foodSearch: main.Food[] = [];
|
||||||
|
|
||||||
|
// Maybe it would be a good idea to use $ instead of update down there...
|
||||||
|
// Maybe it's a topic for another day
|
||||||
|
$: {
|
||||||
|
if (!name) {
|
||||||
|
foodSearch = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function update(event: KeyboardEvent & { currentTarget: EventTarget & HTMLTableCellElement }) {
|
async function update(event: KeyboardEvent & { currentTarget: EventTarget & HTMLTableCellElement }) {
|
||||||
name = name.trim();
|
name = name.trim();
|
||||||
@@ -27,6 +39,11 @@
|
|||||||
description = description.trim();
|
description = description.trim();
|
||||||
per100 = per100.trim();
|
per100 = per100.trim();
|
||||||
|
|
||||||
|
if (!name) {
|
||||||
|
foodSearch = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!per100Edited && event.currentTarget == per100Element) per100Edited = true;
|
if (!per100Edited && event.currentTarget == per100Element) per100Edited = true;
|
||||||
|
|
||||||
if (event.key == "Enter") {
|
if (event.key == "Enter") {
|
||||||
@@ -50,27 +67,53 @@
|
|||||||
|
|
||||||
foodStore.update((value) => [res.data, ...value]);
|
foodStore.update((value) => [res.data, ...value]);
|
||||||
nameElement.focus();
|
nameElement.focus();
|
||||||
|
foodSearch = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!per100Edited)
|
if (!per100Edited)
|
||||||
GetLastPer100(name.trim()).then((res) => {
|
GetLastPer100(name.trim()).then((res) => {
|
||||||
console.log(res.data);
|
|
||||||
if (res.success && res.data) {
|
if (res.success && res.data) {
|
||||||
console.log(res.data[0].food);
|
foodSearch = res.data;
|
||||||
per100 = res.data[0].per100.toString();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let hiLiteIndex: number | null = null;
|
||||||
|
function navigateList(e: KeyboardEvent) {
|
||||||
|
if (e.key === "ArrowDown" && hiLiteIndex && hiLiteIndex <= foodSearch.length - 1) {
|
||||||
|
hiLiteIndex === null ? (hiLiteIndex = 0) : (hiLiteIndex += 1);
|
||||||
|
} else if (e.key === "ArrowUp" && hiLiteIndex !== null) {
|
||||||
|
hiLiteIndex === 0 ? (hiLiteIndex = foodSearch.length - 1) : (hiLiteIndex -= 1);
|
||||||
|
} else if (e.key === "Enter") {
|
||||||
|
// @ts-ignore ITS NOT NULL YOU ASSHAT
|
||||||
|
// WE CHECKED
|
||||||
|
// ITS NOT
|
||||||
|
setInputVal(foodSearch[hiLiteIndex]);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function setInputVal(val: string) {
|
||||||
|
name = val;
|
||||||
|
hiLiteIndex = null;
|
||||||
|
foodSearch = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$: {
|
||||||
|
if (nameElement && autocompleteList) {
|
||||||
|
const { top, left, height } = nameElement.getBoundingClientRect();
|
||||||
|
autocompleteList.style.top = `${top + height}px`;
|
||||||
|
autocompleteList.style.left = `${left}px`;
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<tr class="border-b border-gray-200 dark:border-gray-700 text-lg font-bold">
|
<tr class="border-b border-gray-200 dark:border-gray-700 text-lg font-bold relative">
|
||||||
<th
|
<th
|
||||||
class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap bg-gray-50 dark:text-white dark:bg-gray-800"
|
class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap bg-gray-50 dark:text-white dark:bg-gray-800"
|
||||||
scope="row"
|
scope="row"
|
||||||
>
|
></th>
|
||||||
</th>
|
|
||||||
<!-- svelte-ignore a11y-autofocus -->
|
|
||||||
<td
|
<td
|
||||||
bind:innerText={name}
|
bind:innerText={name}
|
||||||
class:border-[3px]={!name}
|
class:border-[3px]={!name}
|
||||||
@@ -80,15 +123,13 @@
|
|||||||
autofocus
|
autofocus
|
||||||
on:keydown={update}
|
on:keydown={update}
|
||||||
bind:this={nameElement}
|
bind:this={nameElement}
|
||||||
>
|
/>
|
||||||
</td>
|
|
||||||
<td
|
<td
|
||||||
bind:innerText={description}
|
bind:innerText={description}
|
||||||
class="px-6 py-4 bg-gray-50 dark:bg-gray-800 overflow-hidden"
|
class="px-6 py-4 bg-gray-50 dark:bg-gray-800 overflow-hidden"
|
||||||
contenteditable="true"
|
contenteditable="true"
|
||||||
on:keydown={update}
|
on:keydown={update}
|
||||||
>
|
/>
|
||||||
</td>
|
|
||||||
<td
|
<td
|
||||||
bind:innerText={amount}
|
bind:innerText={amount}
|
||||||
class:border-[3px]={!amount}
|
class:border-[3px]={!amount}
|
||||||
@@ -96,8 +137,7 @@
|
|||||||
class="px-6 py-4 overflow-hidden"
|
class="px-6 py-4 overflow-hidden"
|
||||||
contenteditable="true"
|
contenteditable="true"
|
||||||
on:keydown={update}
|
on:keydown={update}
|
||||||
>
|
/>
|
||||||
</td>
|
|
||||||
<td
|
<td
|
||||||
bind:this={per100Element}
|
bind:this={per100Element}
|
||||||
bind:innerText={per100}
|
bind:innerText={per100}
|
||||||
@@ -106,7 +146,13 @@
|
|||||||
class:border-orange-600={!per100}
|
class:border-orange-600={!per100}
|
||||||
contenteditable="true"
|
contenteditable="true"
|
||||||
on:keydown={update}
|
on:keydown={update}
|
||||||
>
|
/>
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
|
{#if foodSearch.length > 0}
|
||||||
|
<ul bind:this={autocompleteList} class="z-50 fixed top-0 left-0 w-3/12 border border-x-gray-800">
|
||||||
|
{#each foodSearch as f, i}
|
||||||
|
<FoodSearchEntry itemLabel={f.food} highlighted={i === hiLiteIndex} on:click={() => setInputVal(f)} />
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
{/if}
|
||||||
</template>
|
</template>
|
||||||
|
13
frontend/src/lib/components/Energy/FoodSearchEntry.svelte
Normal file
13
frontend/src/lib/components/Energy/FoodSearchEntry.svelte
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<script>
|
||||||
|
export let itemLabel;
|
||||||
|
export let highlighted;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
|
<li
|
||||||
|
class="list-none border-b border-gray-800 z-50 px-2 py-2 cursor-pointer bg-[#1b2636] hover:bg-[#81921f] hover:text-white active:bg-DodgerBlue active:text-white text-lg"
|
||||||
|
class:bg-DodgerBlue={highlighted}
|
||||||
|
class:text-white={highlighted}
|
||||||
|
on:click>
|
||||||
|
{itemLabel}
|
||||||
|
</li>
|
Reference in New Issue
Block a user