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">
import { createEventDispatcher } from "svelte";
import Badge from "components/Badge.svelte";
import TagInput from "./TagInput.svelte";
import { deleteIcon } from "./icons";
export let name: string;
export let input: HTMLInputElement;
export let active: boolean;
export let blink: boolean = false;
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);
}
function checkForActivation(): void {
const selection = window.getSelection()!;
active = selection.isCollapsed;
}
function deleteTag(event: Event): void {
dispatch("tagdelete");
event.stopPropagation();
}
function deactivate() {
active = false;
}
</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
class="d-inline-flex align-items-center tag text-nowrap rounded ps-2 pe-1 me-1"
class:blink
tabindex="-1"
on:click={checkForActivation}
<button
class="d-inline-flex align-items-center tag text-nowrap rounded ps-2 pe-1 me-1"
class:blink
tabindex="-1"
on:click
>
<span>{name}</span>
<Badge class="delete-icon rounded ms-1 mt-1" on:click={deleteTag}
>{@html deleteIcon}</Badge
>
<span>{name}</span>
<Badge class="delete-icon rounded ms-1 mt-1" on:click={deleteTag}
>{@html deleteIcon}</Badge
>
</button>
{/if}
</button>
<style lang="scss">
$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 AddTagBadge from "./AddTagBadge.svelte";
import Tag from "./Tag.svelte";
import TagInput from "./TagInput.svelte";
import TagAutocomplete from "./TagAutocomplete.svelte";
import ButtonToolbar from "components/ButtonToolbar.svelte";
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;
}
function addEmptyTag(): void {
if (tags[tags.length - 1].name.length === 0) {
tags[tags.length - 1].active = true;
async function addEmptyTag(): Promise<void> {
const lastTag = tags[tags.length - 1];
if (lastTag.name.length === 0) {
lastTag.active = true;
return;
}
tags.push(attachId("", true));
const idx = tags.push(attachId("", true));
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 = 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 = tags;
await tick();
tags[index].input?.focus();
}
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);
insertEmptyTagAt(index);
} else {
deactivate(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 {
deactivate(index);
tags.splice(index, 1);
tags = tags;
}
@ -126,6 +139,15 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
await tick();
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>
<StickyBottom>
@ -140,19 +162,27 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
let:destroyAutocomplete
>
{#each tags as tag, index (tag.id)}
<Tag
bind:name={tag.name}
bind:input={tag.input}
bind:active={tag.active}
bind:blink={tag.blink}
on:tagupdate={() => addTagAt(index)}
on:tagadd={() => insertTagAt(index)}
on:tagdelete={() => deleteTagAt(index)}
on:tagjoinprevious={() => joinWithPreviousTag(index)}
on:tagjoinnext={() => joinWithNextTag(index)}
on:tagmoveprevious={() => moveToPreviousTag(index)}
on:tagmovenext={() => moveToNextTag(index)}
/>
{#if tag.active}
<TagInput
bind:name={tag.name}
bind:input={tag.input}
on:blur={() => deactivate(index)}
on:tagupdate={() => addTagAt(index)}
on:tagadd={() => insertTagAt(index)}
on:tagdelete={() => deleteTagAt(index)}
on:tagjoinprevious={() => joinWithPreviousTag(index)}
on:tagjoinnext={() => joinWithNextTag(index)}
on:tagmoveprevious={() => moveToPreviousTag(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}
<div

View file

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

View file

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