UNPKG

rapidoc

Version:

RapiDoc - Open API spec viewer with built in console

181 lines (169 loc) • 5.47 kB
import { LitElement, html, css } from "lit"; import { customElement, property } from "lit/decorators.js"; @customElement("tag-input") export default class TagInput extends LitElement { @property({ type: String }) placeholder = ""; @property({ type: Array, attribute: "value", converter: { // `value="a,b"` is a comma-separated list, not the JSON Lit's Array converter expects fromAttribute: (value) => (value ? value.split(",").filter((v) => v.trim() !== "") : []), toAttribute: (value) => (Array.isArray(value) ? value.join(",") : value), }, hasChanged(newVal, oldVal) { if (newVal === oldVal) return false; if (!Array.isArray(newVal) || !Array.isArray(oldVal)) return true; if (newVal.length !== oldVal.length) return true; return newVal.some((v, i) => v !== oldVal[i]); }, }) value = []; // For backwards compatibility with static properties inspection in tests static get properties() { return Object.fromEntries(this.elementProperties || []); } render() { let tagItemTmpl = ""; if (Array.isArray(this.value)) { tagItemTmpl = html`${this.value .filter((v) => v !== "" && v !== null && v !== undefined && String(v).trim() !== "") .map((v) => html`<span class="tag">${v}</span>`)}`; } return html`<div class="tags" @click="${(e) => this.focusEditor(e)}"> ${tagItemTmpl} <input type="text" class="editor" @paste="${(e) => this.afterPaste(e)}" @keydown="${(e) => this.afterKeyDown(e)}" @blur="${(e) => this.onBlur(e)}" placeholder="${this.placeholder || ""}" /> </div>`; } focusEditor(e) { if (e.target.classList.contains("tags")) { this.shadowRoot?.querySelector(".editor")?.focus(); } } dispatchEvents() { this.dispatchEvent( new CustomEvent("change", { bubbles: true, composed: true, detail: { value: this.value } }), ); this.dispatchEvent( new CustomEvent("input", { bubbles: true, composed: true, detail: { value: this.value } }), ); } afterPaste(e) { const clipboardData = e.clipboardData || window.clipboardData; const pastedData = clipboardData.getData("Text"); const pastedArray = pastedData ? pastedData.split(",").filter((v) => v.trim() !== "") : ""; if (pastedArray) { if (Array.isArray(this.value)) { this.value = [...this.value, ...pastedArray]; } else { this.value = pastedArray; } this.dispatchEvents(); } e.preventDefault(); } afterKeyDown(e) { if (e.key === "Enter" || e.keyCode === 13) { e.stopPropagation(); e.preventDefault(); const val = e.target.value ? e.target.value.trim() : ""; if (val) { if (Array.isArray(this.value)) { this.value = [...this.value, val]; } else { this.value = [val]; } e.target.value = ""; this.dispatchEvents(); } } else if (e.key === "Backspace" || e.key === "Delete" || e.keyCode === 8 || e.keyCode === 46) { if (!e.target.value || e.target.value.length === 0) { if (Array.isArray(this.value) && this.value.length > 0) { e.preventDefault(); this.value = this.value.slice(0, -1); this.dispatchEvents(); } } } } onBlur(e) { const val = e.target.value ? e.target.value.trim() : ""; if (val) { if (Array.isArray(this.value)) { this.value = [...this.value, val]; } else { this.value = [val]; } e.target.value = ""; this.dispatchEvents(); } } static get styles() { return [ css` .tags { display: flex; flex-wrap: wrap; align-items: center; box-sizing: border-box; /* Same height as the text inputs in a parameter row (see param-list-styles.js) */ min-height: calc(2.75 * var(--font-size-small) + 2px); outline: none; padding: 0; border-radius: var(--radius); border: 1px solid var(--input); cursor: text; overflow: hidden; background: var(--input-background); transition: border-color 0.2s ease, box-shadow 0.2s ease; /* Same focus ring as the text inputs */ &:focus-within { border-color: var(--primary); box-shadow: var(--focus-shadow); } } .tag, .editor { padding: 3px; margin: 2px; } /* Chips: a muted fill with the form-field border, so each one stands apart on a white or dark field */ .tag { border: 1px solid var(--input); background: var(--muted); color: var(--foreground); border-radius: var(--radius); word-break: break-all; font-size: var(--font-size-small); } .tag:hover ~ #cursor { display: block; } .editor { flex: 1; border: 1px solid transparent; color: var(--foreground); min-width: 60px; outline: none; line-height: inherit; font-family: inherit; background: transparent; font-size: calc(var(--font-size-small) + 1px); } .editor::placeholder { color: var(--muted-foreground); opacity: 1; } `, ]; } }