Input two-way binding, first version of an autocomplete Selector

feat/vaults
Tomáš Mládek 2021-12-31 00:46:19 +01:00
parent 091a02d530
commit 337ba75603
No known key found for this signature in database
GPG Key ID: ED21612889E75EC5
3 changed files with 106 additions and 15 deletions

View File

@ -1,17 +1,20 @@
<script lang="ts">
import { createEventDispatcher } from "svelte";
const dispatch = createEventDispatcher();
export let placeholder = "";
export let type = "text";
export let value = "";
let focused = false;
$: dispatch("focusChange", focused);
</script>
<div class="input" class:focused>
<slot name="prefix" />
<input
{type}
{placeholder}
{value}
bind:value
on:input
on:focus={() => (focused = true)}
on:blur={() => (focused = false)}
@ -27,9 +30,9 @@
border: 1px solid var(--foreground-lighter);
border-radius: 4px;
background: var(--background-lighter);
background: var(--background);
transition: box-shadow .25s;
transition: box-shadow 0.25s;
&.focused {
box-shadow: 0 0 2px 3px var(--primary);
}

View File

@ -0,0 +1,91 @@
<script lang="ts">
import { tick } from "svelte";
import Input from "./Input.svelte";
export let value = "";
export let type: "entity" | "attribute" | "value";
let options = [];
async function updateOptions(query: string) {
switch (type) {
case "entity":
throw new Error("unimplemented");
case "attribute":
const req = await fetch("/api/all/attributes");
const allAttributes: string[] = await req.json();
options = allAttributes.filter((attr) =>
attr.toLowerCase().includes(query.toLowerCase())
);
break;
case "value":
throw new Error("unimplemented");
}
}
$: updateOptions(value);
let inputFocused = false;
let hover = false;
$: visible = (inputFocused || hover) && Boolean(options.length);
</script>
<div class="selector">
<Input bind:value on:focusChange={(ev) => (inputFocused = ev.detail)} />
<ul
class="options"
class:visible
on:mouseenter={() => (hover = true)}
on:mouseleave={() => (hover = false)}
>
{#each options as option}
<li
on:click={() => {
value = option;
visible = false;
}}
>
{option}
</li>
{/each}
</ul>
</div>
<style lang="scss">
.selector {
position: relative;
}
.options {
position: absolute;
list-style: none;
margin: 0;
padding: 0;
border: 1px solid var(--foreground-lighter);
width: 100%;
border-radius: 4px;
margin-top: 2px;
background: var(--background);
font-size: smaller;
visibility: hidden;
opacity: 0;
transition: opacity 0.2s;
z-index: 99;
&.visible {
visibility: visible;
opacity: 1;
}
li {
cursor: pointer;
padding: 0.5em;
transition: background-color 0.2s;
&:hover {
background-color: var(--background-lighter);
}
}
}
</style>

View File

@ -8,8 +8,9 @@
import { useParams } from "svelte-navigator";
import type { Writable } from "svelte/store";
import type { UpEntry } from "upend";
import IconButton from "../utils/IconButton.svelte";
import Input from "../utils/Input.svelte";
import IconButton from "../utils/IconButton.svelte";
import Input from "../utils/Input.svelte";
import Selector from "../utils/Selector.svelte";
const dispatch = createEventDispatcher();
const params = useParams();
@ -26,7 +27,7 @@ import Input from "../utils/Input.svelte";
$: showValue = columns.includes("value");
// Editing
let newEntryAttribute = "'";
let newEntryAttribute = "";
let newEntryValue = "";
async function addEntry() {
@ -303,16 +304,12 @@ import Input from "../utils/Input.svelte";
</td>
{#if showAttribute}
<td>
<Input
on:input={(ev) => (newEntryAttribute = ev.target.value)}
/>
<Selector type="attribute" bind:value={newEntryAttribute} />
</td>
{/if}
{#if showValue}
<td>
<Input
on:input={(ev) => (newEntryValue = ev.target.value)}
/>
<Input bind:value={newEntryValue} />
</td>
{/if}
</tr>
@ -328,7 +325,7 @@ import Input from "../utils/Input.svelte";
border-spacing: 0.5em 0;
tr {
tr {
&.left-active {
td:first-child {
background: linear-gradient(