/** @license Copyright (c) 2024 trading_peter This program is available under Apache License Version 2.0 */ import '@tp/tp-input/tp-input.js'; import '@tp/tp-icon/tp-icon.js'; import './tp-tag-input-popup.js'; import { LitElement, html, css, svg } from 'lit'; import { FormElement } from '@tp/helpers/form-element.js'; import { EventHelpers } from '@tp/helpers/event-helpers.js'; import { DomQuery } from '@tp/helpers/dom-query.js'; import { reach } from '@tp/helpers/reach.js'; import { debounce } from '@tp/helpers/debounce.js'; import { clone } from '@tp/helpers/clone.js'; import { closest } from '@tp/helpers/closest.js'; const mixins = [ FormElement, EventHelpers, DomQuery ]; /* @litElement */ const BaseElement = mixins.reduce((baseClass, mixin) => { return mixin(baseClass); }, LitElement); class TpTagInput extends BaseElement { static get styles() { return [ css` :host { display: block; border-radius: 2px; border: solid 1px #BDBDBD; } :host([invalid]) { border: solid 1px #B71C1C; } [hidden] { display: none; } .wrap { display: flex; flex-direction: row; flex-wrap: wrap; } tp-input { min-height: 15px; outline: none; margin: 0; padding: 0; display: flex; flex: 1; } tp-input::part(wrap) { border: none; flex: 1; } :host(.has-items) tp-input { margin-top: 10px; } .item { display: inline-block; margin: 5px; border-radius: 2px; border: solid 1px #9E9E9E; background: #FFFFFF; font-size: 14px; padding: 3px 5px 3px 3px; margin-right: 5px; display: flex; flex-direction: row; align-items: center; } .item tp-icon { --tp-icon-width: 12px; --tp-icon-height: 12px; margin-right: 2px; } ` ]; } static get icon() { return svg``; } render() { const value = this.value || []; return html`
${value.map(item => html`
${item.label}
`)} this._inputChanged(e)} required>
this._itemSelected(e.detail)}> `; } static get properties() { return { value: { type: Array }, items: { type: Array }, validator: { type: Object }, allowUnknown: { type: Boolean }, allowDuplicates: { type: Boolean }, tooltipRemove: { type: String }, min: { type: Number }, max: { type: Number }, invalid: { type: Boolean }, filter: { type: String }, pattern: { type: String }, triggerKeys: { type: String }, _input: { type: String }, }; } constructor() { super(); this.allowUnknown = false; this.allowDuplicates = false; this.triggerKeys = 'enter'; this.tooltipRemove = 'Remove'; this._input = ''; this._callFilterApiDebounced = debounce(this._callFilterApi.bind(this), 300, true); } firstUpdated() { this.listen(this.$.innerInput, 'keydown', '_inputKeyDown'); this.listen(this.$.input, 'blur', '_inputBlur'); this.listen(this, 'focus', '_onFocus'); } disconnectedCallback() { super.disconnectedCallback(); this.unlisten(this.$.innerInput, 'keydown', '_inputKeyDown'); this.unlisten(this.$.innerInput, 'blur', '_inputBlur'); this.unlisten(this, 'focus', '_onFocus'); } // shouldUpdate(changes) { // if (changes.has('_input')) { // this._inputChanged(); // } // } validate() { const val = Array.isArray(this.value) ? this.value : []; if ((this.required && val.length === 0) || (this.min > 0 && val.length < this.min) || (this.max > 0 && val.length > this.max)) { this.invalid = true; return false; } return true; } _onClick(e) { if (closest(e.composedPath()[0], '.remove-icon', true)) { const item = closest(e.composedPath()[0], '.item', true); if (!item) return; this.value = this.value.filter(i => i.value !== item.item.value); } } _inputChanged(e) { this._input = this.$.input.value; if (this.api && this._input.length > 2) { this._callFilterApiDebounced(); return; } if (this._input.length > 0) { this.$.autoCpl.show(); } else { this.$.autoCpl.hide(); } } async _callFilterApi() { const apiFunc = typeof this.api === 'function' ? this.api : reach(this.api, window); if (typeof apiFunc !== 'function') return; this.items = []; const apiResp = apiFunc({ value: this._input }); const promisedResult = apiResp.completes ? apiResp.completes : apiResp; const result = await promisedResult; this.$.autoCpl.loading = false; if (Array.isArray(result)) { this.items = result; this.$.autoCpl.show(); return; } const resp = result.response; if (resp && resp.statusCode === 200) { this.items = resp.data; this.$.autoCpl.show(); } } _itemSelected(item) { if (item !== null) { if (this.allowDuplicates || !this._isAlreadySelected(item.label)) { if (!Array.isArray(this.value)) { this.value = [ clone(item) ]; } else { this.value = [...this.value, clone(item)]; } } this._clearInput(); this.$.autoCpl.selection = null; this.$.autoCpl.value = null; } } _inputBlur() { if (!this._tryToAddItem()) { this._clearInput(); this.$.autoCpl.selection = null; this.$.autoCpl.value = null; this.$.autoCpl.hide(); } } _prepFilter(filter, api) { if (api) { return ''; // No filter because items come from api called and should already be filtered by a server. } else { return filter; } } _maxLengthReached() { return (this.value || []).length === this.max && this.max > 0; } _inputKeyDown(e) { if (this._keyboardEventMatchesKeys(e, this.triggerKeys)) { e.preventDefault(); this._tryToAddItem(); return; } if (this._keyboardEventMatchesKeys(e, 'backspace')) { if (this._input === '' && this.value) { this.value = this.value.slice(0, -1); return; } } if (this._maxLengthReached() && !this._keyboardEventMatchesKeys(e, 'tab')) { e.preventDefault(); } } _tryToAddItem() { if (this._maxLengthReached()) { return; } if (typeof this.$.autoCpl.selection === 'number') { return; } if (this.allowUnknown) { if (this.$.input.validate()) { var item = this._getItemByLabel(this._input); if (!item) { item = { value: this._getFreeValue(), label: this._input, custom: true }; this.push('items', item); } if (!this.allowDuplicates && this._isAlreadySelected(this._input)) { return false; } this._itemSelected(item); return true; } } else { if (this.$.autoCpl.filteredItems.length > 0 && this.$.autoCpl.visible) { if (this.allowDuplicates || !this._isAlreadySelected(this.$.autoCpl.filteredItems[0].label)) { this._itemSelected(this.$.autoCpl.filteredItems[0]); return true; } return false; } } } _keyboardEventMatchesKeys(e, keys) { const keyList = keys.split(' '); for (const key of keyList) { if (key.toLowerCase() === e.key.toLowerCase()) { return true; } } } _valueChanged() { this.toggleClass('has-items', Array.isArray(this.value) && this.value.length > 0); } _removeItem(e) { this.splice('value', e.model.index, 1)[0]; } _isAlreadySelected(label) { if (!Array.isArray(this.value)) return false; for (let i = 0, li = this.value.length; i < li; ++i) { const item = this.value[i]; if (item.label.toLocaleLowerCase() === label.toLocaleLowerCase()) { return true; } } return false; } _getItemByLabel(label) { for (var i = 0, li = this.items.length; i < li; ++i) { var item = this.items[i]; if (item.label === label) { return item; } } } _getFreeValue() { var newValue = 0; var _this = this; while(!isFree(newValue)) { newValue++; } function isFree() { for (var i = 0, li = _this.items.length; i < li; ++i) { if (_this.items[i].value == newValue) { return false; } } return true; } return newValue; } _onFocus() { this.$.input.focus(); } _clearInput() { this._input = ''; this.$.input.value = ''; } } window.customElements.define('tp-tag-input', TpTagInput);