/** @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'; 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: 5px; padding: 0; display: flex; } tp-input::part(wrap) { border: none; } :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.item)}> `; } 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; } _inputChanged(e) { this._input = e.target.value; console.log('input changed'); 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)) { // Need to clone the item so polymer won't get confused if the user removes duplicated entries. if (Array.isArray(this.value)) { this.push('value', this._clone(item)); } else { this.set('value', [ this._clone(item) ]); } } this._input = ''; this.$.autoCpl.selection = null; this.$.autoCpl.value = null; } } _inputBlur() { if (!this._tryToAddItem()) { this._input = ''; 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.pop('value'); 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 === e.key) { 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(); } } window.customElements.define('tp-tag-input', TpTagInput);