padloc/packages/app/src/elements/tags-input.ts

198 lines
5.6 KiB
TypeScript

import { localize as $l } from "@padloc/core/lib/locale.js";
import { Vault, Tag } from "@padloc/core/lib/vault.js";
import { app, router } from "../init.js";
import { shared } from "../styles";
import { BaseElement, element, html, property, query } from "./base.js";
import { Input } from "./input";
import "./icon.js";
@element("pl-tags-input")
export class TagsInput extends BaseElement {
@property()
editing: boolean = false;
@property()
vault: Vault | null = null;
@property()
tags: Tag[] = [];
@property()
_showResults: Boolean = false;
@query("pl-input")
private _input: Input;
private _focusTimeout: number = 0;
render() {
const { tags, editing, vault, _showResults } = this;
const { value } = this._input || { value: "" };
const results = app.tags.filter(
t => !this.tags.includes(t) && t !== value && t.toLowerCase().startsWith(value)
);
if (value) {
results.push(value);
}
return html`
${shared}
<style>
:host {
display: block;
background: var(--color-tertiary);
position: relative;
z-index: 1;
overflow: visible;
font-size: var(--font-size-small);
overflow: visible;
}
.wrapper {
flex-wrap: wrap;
overflow: visible;
}
.wrapper > * {
margin-top: 6px;
}
.tags.small .tag {
height: 27px;
line-height: 27px;
}
.results {
padding: 0;
background: rgba(255, 255, 255, 0.5);
border-radius: 8px;
margin-top: 0;
flex-direction: column;
align-items: flex-start;
}
.results .tag {
margin-top: 6px;
}
.input-wrapper {
font-size: var(--font-size-micro);
padding: 0 4px;
height: 27px;
line-height: 27px;
background: #eee;
box-sizing: border-box;
border-radius: 8px;
align-self: stretch;
position: relative;
min-width: 80px;
overflow: hidden;
}
.input-wrapper pl-input {
font-size: inherit;
position: absolute;
height: 100%;
width: 100%;
box-sizing: border-box;
padding-left: 20px;
top: 0;
font-weight: bold;
pointer-events: none;
}
.add-tag {
height: 27px;
overflow: visible;
}
</style>
<div class="tags small wrapper">
<div class="tag highlight tap" @click=${() => this._openVault()} ?disabled=${editing}>
<pl-icon icon="vault"></pl-icon>
<div class="tag-name">${vault}</div>
</div>
${tags.map(
tag => html`
<div class="tag tap" @click=${() => this._tagClicked(tag)}>
<pl-icon icon="tag"></pl-icon>
<div>${tag}</div>
<pl-icon icon="cancel" ?hidden=${!editing}></pl-icon>
</div>
`
)}
<div class="add-tag" ?hidden=${!editing}>
<div class="input-wrapper tap" @click=${() => this._input.focus()}>
<pl-icon icon="add"></pl-icon>
<pl-input
.placeholder=${$l("Add Tag")}
@enter=${() => this._addTag(value)}
@input=${() => this.requestUpdate()}
@focus=${() => this._focusChanged()}
@blur=${() => this._focusChanged()}>
</pl-input>
</div>
<div class="tags tap small results" ?hidden=${!_showResults}>
${results.map(
res => html`
<div class="tag tap" @click=${() => this._addTag(res)}>
<pl-icon icon="tag"></pl-icon>
<div>${res}</div>
</div>
`
)}
</div>
</div>
</div>
`;
}
private _addTag(tag: Tag) {
if (!tag || this.tags.includes(tag)) {
return;
}
this.tags.push(tag);
this._input.value = "";
this._showResults = false;
this.requestUpdate();
}
private _tagClicked(tag: Tag) {
if (this.editing) {
this.tags = this.tags.filter(t => t !== tag);
} else {
app.filter = { tag };
router.go("items");
}
}
private _openVault() {
app.filter = { vault: this.vault };
router.go("items");
}
_focusChanged() {
clearTimeout(this._focusTimeout);
setTimeout(() => (this._showResults = this._input.focused), this._input.focused ? 0 : 200);
}
}