rapidoc
Version:
RapiDoc - Open API spec viewer with built in console
181 lines (169 loc) • 5.47 kB
text/typescript
import { LitElement, html, css } from "lit";
import { customElement, property } from "lit/decorators.js";
("tag-input")
export default class TagInput extends LitElement {
({ type: String }) placeholder = "";
({
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;
}
`,
];
}
}