Completely decouple Tag from TagInput

This commit is contained in:
Henrik Giesel 2021-06-26 19:47:35 +02:00
parent ea1e5b5840
commit 52a705e839
4 changed files with 61 additions and 65 deletions

View file

@ -5,12 +5,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<script lang="typescript"> <script lang="typescript">
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte";
import Badge from "components/Badge.svelte"; import Badge from "components/Badge.svelte";
import TagInput from "./TagInput.svelte";
import { deleteIcon } from "./icons"; import { deleteIcon } from "./icons";
export let name: string; export let name: string;
export let input: HTMLInputElement;
export let active: boolean;
export let blink: boolean = false; export let blink: boolean = false;
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
@ -19,53 +16,23 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
setTimeout(() => (blink = false), 300); setTimeout(() => (blink = false), 300);
} }
function checkForActivation(): void {
const selection = window.getSelection()!;
active = selection.isCollapsed;
}
function deleteTag(event: Event): void { function deleteTag(event: Event): void {
dispatch("tagdelete"); dispatch("tagdelete");
event.stopPropagation(); event.stopPropagation();
} }
function deactivate() {
active = false;
}
</script> </script>
{#if active}
<TagInput
bind:name
bind:input
on:focus
on:blur={deactivate}
on:blur
on:keydown
on:tagupdate={deactivate}
on:tagupdate
on:tagdelete={deactivate}
on:tagdelete
on:tagadd
on:tagjoinprevious
on:tagjoinnext
on:tagmoveprevious
on:tagmovenext
on:mount={(event) => event.detail.input.focus()}
/>
{:else}
<button <button
class="d-inline-flex align-items-center tag text-nowrap rounded ps-2 pe-1 me-1" class="d-inline-flex align-items-center tag text-nowrap rounded ps-2 pe-1 me-1"
class:blink class:blink
tabindex="-1" tabindex="-1"
on:click={checkForActivation} on:click
> >
<span>{name}</span> <span>{name}</span>
<Badge class="delete-icon rounded ms-1 mt-1" on:click={deleteTag} <Badge class="delete-icon rounded ms-1 mt-1" on:click={deleteTag}
>{@html deleteIcon}</Badge >{@html deleteIcon}</Badge
> >
</button> </button>
{/if}
<style lang="scss"> <style lang="scss">
$white-translucent: rgba(255, 255, 255, 0.5); $white-translucent: rgba(255, 255, 255, 0.5);

View file

@ -8,6 +8,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import StickyBottom from "components/StickyBottom.svelte"; import StickyBottom from "components/StickyBottom.svelte";
import AddTagBadge from "./AddTagBadge.svelte"; import AddTagBadge from "./AddTagBadge.svelte";
import Tag from "./Tag.svelte"; import Tag from "./Tag.svelte";
import TagInput from "./TagInput.svelte";
import TagAutocomplete from "./TagAutocomplete.svelte"; import TagAutocomplete from "./TagAutocomplete.svelte";
import ButtonToolbar from "components/ButtonToolbar.svelte"; import ButtonToolbar from "components/ButtonToolbar.svelte";
import { attachId, getName } from "./tags"; import { attachId, getName } from "./tags";
@ -27,24 +28,34 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
return index === tags.length - 1; return index === tags.length - 1;
} }
function addEmptyTag(): void { async function addEmptyTag(): Promise<void> {
if (tags[tags.length - 1].name.length === 0) { const lastTag = tags[tags.length - 1];
tags[tags.length - 1].active = true; if (lastTag.name.length === 0) {
lastTag.active = true;
return; return;
} }
tags.push(attachId("", true)); const idx = tags.push(attachId("", true));
tags = tags; tags = tags;
await tick();
tags[idx - 1].input?.focus();
} }
function insertEmptyTagAt(index: number): void { async function insertEmptyTagAt(index: number): Promise<void> {
tags.splice(index, 0, attachId("", true)); tags.splice(index, 0, attachId("", true));
tags = tags; tags = tags;
await tick();
tags[index].input?.focus();
} }
function appendEmptyTagAt(index: number): void { async function appendEmptyTagAt(index: number): Promise<void> {
tags.splice(index + 1, 0, attachId("", true)); tags.splice(index + 1, 0, attachId("", true));
tags = tags; tags = tags;
await tick();
tags[index].input?.focus();
} }
function checkIfContainsNameAt(index: number): boolean { function checkIfContainsNameAt(index: number): boolean {
@ -65,6 +76,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
deleteTagAt(index); deleteTagAt(index);
insertEmptyTagAt(index); insertEmptyTagAt(index);
} else { } else {
deactivate(index);
appendEmptyTagAt(index); appendEmptyTagAt(index);
} }
} }
@ -79,6 +91,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
} }
function deleteTagAt(index: number): void { function deleteTagAt(index: number): void {
deactivate(index);
tags.splice(index, 1); tags.splice(index, 1);
tags = tags; tags = tags;
} }
@ -126,6 +139,15 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
await tick(); await tick();
nextTag.input?.setSelectionRange(0, 0); nextTag.input?.setSelectionRange(0, 0);
} }
function deactivate(index: number): void {
tags[index].active = false;
}
function checkForActivation(index: number): void {
const selection = window.getSelection()!;
tags[index].active = selection.isCollapsed;
}
</script> </script>
<StickyBottom> <StickyBottom>
@ -140,11 +162,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
let:destroyAutocomplete let:destroyAutocomplete
> >
{#each tags as tag, index (tag.id)} {#each tags as tag, index (tag.id)}
<Tag {#if tag.active}
<TagInput
bind:name={tag.name} bind:name={tag.name}
bind:input={tag.input} bind:input={tag.input}
bind:active={tag.active} on:blur={() => deactivate(index)}
bind:blink={tag.blink}
on:tagupdate={() => addTagAt(index)} on:tagupdate={() => addTagAt(index)}
on:tagadd={() => insertTagAt(index)} on:tagadd={() => insertTagAt(index)}
on:tagdelete={() => deleteTagAt(index)} on:tagdelete={() => deleteTagAt(index)}
@ -153,6 +175,14 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
on:tagmoveprevious={() => moveToPreviousTag(index)} on:tagmoveprevious={() => moveToPreviousTag(index)}
on:tagmovenext={() => moveToNextTag(index)} on:tagmovenext={() => moveToNextTag(index)}
/> />
{:else}
<Tag
bind:name={tag.name}
bind:blink={tag.blink}
on:click={() => checkForActivation(index)}
on:tagdelete={() => deleteTagAt(index)}
/>
{/if}
{/each} {/each}
<div <div

View file

@ -145,8 +145,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
name = last; name = last;
} }
} }
onMount(() => dispatch("mount", { input }));
</script> </script>
<label class="ps-2 pe-1" data-value={name}> <label class="ps-2 pe-1" data-value={name}>

View file

@ -18,7 +18,7 @@ export function normalizeTagname(tagname: string): string {
interface Tag { interface Tag {
id: string; id: string;
name: string; name: string;
input?: HTMLInputElement; input: HTMLInputElement;
active: boolean; active: boolean;
blink: boolean; blink: boolean;
} }
@ -27,6 +27,7 @@ export function attachId(name: string, active = false): Tag {
return { return {
id: Math.random().toString(36).substring(2), id: Math.random().toString(36).substring(2),
name, name,
input: null as unknown as HTMLInputElement,
active, active,
blink: false, blink: false,
}; };