/** @license Copyright (c) 2024 trading_peter This program is available under Apache License Version 2.0 */ import { LitElement, html, css } from 'lit'; import { DomQuery } from '@tp/helpers/dom-query.js'; import { EventHelpers } from '@tp/helpers/event-helpers.js'; import { closest } from '@tp/helpers/closest.js'; class TpTagInputPopup extends DomQuery(EventHelpers(LitElement)) { static get styles() { return [ css` :host { display: none; position: fixed; border-radius: 2px; background: #ffffff; z-index: 1; left: 0; width: 100%; margin-top: 5px; box-shadow: var(--tp-tag-input-popup-shadow, none); } #list { max-height: 150px; overflow-y: auto; } .item { line-height: 25px; padding: 3px 10px; white-space: nowrap; overflow: hidden; cursor: pointer; text-overflow: ellipsis; } .item[selected] { background: #e4e4e4; } ` ]; } render() { const { selection } = this; return html`
${this.filteredItems.map((item, idx) => html`
${item.label}
`)}
`; } static get is() { return 'era-autocomplete-popup'; } static get properties() { return { /** * The choosen item. */ value: { type: String }, /** * List of items available for auto complete. */ items: { type: Array }, filteredItems: { type: Array }, /** * The filter string to narrow down the items. */ filter: { type: String }, selection: { type: Number }, visible: { type: Boolean } }; } constructor() { super(); this.visible = false; this.filteredItems = []; } get target() { return closest(this, 'tp-tag-input', true); } shouldUpdate(changes) { if (changes.has('filter') || changes.has('items')) { this._filterItemsByTerm(); } return true; } firstUpdated() { this.listen(this.$.list, 'mousedown', '_onItemClick'); } disconnectedCallback() { super.disconnectedCallback(); this.unlisten(this.$.list, 'mousedown', '_onItemClick'); } show() { if (this.visible) { return; } var targetRect = this.target.getBoundingClientRect(); this.style.top = (targetRect.top + targetRect.height) + 'px'; this.style.left = targetRect.left + 'px'; this.style.width = Math.max(100, targetRect.width) + 'px'; this.listen(this.target, 'keydown', '_onHostKeydown'); this.style.display = 'block'; this.visible = true; } hide() { this.style.display = 'none'; this.unlisten(this.target, 'keydown', '_onHostKeydown'); this.visible = false; } _onItemClick(e) { const item = closest(e.composedPath()[0], '.item', true); if (!item) return; this.value = item.item; this.dispatchEvent(new CustomEvent('selection-changed', { detail: this.value, bubbles: true, composed: true })); this.hide(); this.target.focus(); } _filterItemsByTerm() { if (!Array.isArray(this.items)) return; if (!this.filter) { this.filteredItems = this.items; } else { const term = this.filter.toLowerCase(); this.filteredItems = this.items.filter(item => item.label.toLowerCase().includes(term)); } } _onHostKeydown(e) { const count = this.$.list.querySelectorAll('.item').length; let el; switch (e.keyCode) { case 38: // Up this.selection = this.selection - 1 < 0 ? count - 1 : this.selection - 1; el = this.$.list.querySelector('.item[selected]'); this._scrollIntoView(el, this.$.list); e.preventDefault(); e.stopPropagation(); return; case 40: // Down if (isNaN(this.selection) || this.selection === null) { this.selection = 0; } else { this.selection = (this.selection + 1) % count; } el = this.$.list.querySelector('.item[selected]'); this._scrollIntoView(el, this.$.list); e.preventDefault(); e.stopPropagation(); return; case 13: // Enter const item = this.$.list.querySelector('.item[selected]'); if (item) { this.value = item.item; this.dispatchEvent(new CustomEvent('selection-changed', { detail: this.value, bubbles: true, composed: true })); } this.hide(); e.preventDefault(); e.stopPropagation(); return; case 27: // Esc this.hide(); e.preventDefault(); e.stopPropagation(); return; } } _scrollIntoView(element, container) { if (!element) { return; } const containerTop = container.scrollTop; const containerBottom = containerTop + container.offsetHeight; const elTop = element.offsetTop; const elBottom = elTop + element.offsetHeight; if (elTop < containerTop) { container.scrollTop = elTop; } else if (elBottom > containerBottom) { container.scrollTop = elBottom - container.offsetHeight; } } } window.customElements.define('tp-tag-input-popup', TpTagInputPopup);