UNPKG

@senx/discovery-widgets

Version:

Discovery Widgets Elements

592 lines (591 loc) 23 kB
/* * Copyright 2022-2025 SenX S.A.S. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { h, Host } from "@stencil/core"; import { GTSLib } from "../../../utils/gts.lib"; import { Utils } from "../../../utils/utils"; export class DiscoveryInputChips { constructor() { this.chips = []; this.constrain_input = false; this.fuzzy_search = false; this.chipDelimiters = []; this.disabled = false; this.innerChips = []; this.change_handler_enabled = true; this.autocomplete_debounce = 200; this.autocomplete_highlight = true; this.boundClickHandler = this.handleDocumentClick.bind(this); this.real_input = null; this.real_input_handler = null; } updateChips() { var _a; if (!Utils.deepEqual(this.chips, this.innerChips)) { this.innerChips = ((_a = this.chips) !== null && _a !== void 0 ? _a : []).slice(); this.chipChange.emit(this.innerChips); } } // noinspection JSUnusedGlobalSymbols componentDidLoad() { var _a; this.el.addEventListener('click', () => this.real_input.focus()); this.innerChips = ((_a = this.chips) !== null && _a !== void 0 ? _a : []).slice(); } handleAutocompleteContainerFocus(event) { event.preventDefault(); event.stopImmediatePropagation(); } handleChipClose(event) { void this.deleteChip(parseInt(event.detail)); } handleDocumentClick(event) { var _a; if (((_a = event.path) !== null && _a !== void 0 ? _a : []).includes(this.el)) { return; } this.closeAutoComplete(true); } handleChipClick(event, chip) { this.chipClick.emit({ label: chip.label, event: event, }); } handleInput(event) { var _a; let autocomplete_items = []; this.value = this.real_input.value; const key = event ? (_a = event.data) !== null && _a !== void 0 ? _a : '' : ''; if (this.chipDelimiters.includes(key)) { event.preventDefault(); event.stopImmediatePropagation(); if (!this.constrain_input) return this.createChip(undefined); } if (this.autocomplete_debounce_key) { clearTimeout(this.autocomplete_debounce_key); } this.chipInput.emit(); return new Promise(resolve => { this.autocomplete_debounce_key = setTimeout(() => { (async () => { this.autocomplete_debounce_key = null; const value = this.real_input.value; this.highlighted_autocomplete_index = null; if (this.autocomplete) { autocomplete_items = await this.autocomplete(value); } if (autocomplete_items.length == 0) { this.closeAutoComplete(false); return; } else { this.showAutoComplete(autocomplete_items, value); if (this.constrain_input) { this.highlighted_autocomplete_index = 0; this.highLightSelectedAutoCompleteItem(); } } })().then(() => resolve()).catch(e => console.error(e)); }, this.autocomplete_debounce); }); } async handleBeforeInput(event) { var _a; const input_type = event.inputType; const key = event.data; let autocomplete_items = []; if (input_type === 'deleteContentBackward') { if (this.real_input.selectionStart === 0) { if ((_a = this.innerChips) === null || _a === void 0 ? void 0 : _a.length) await this.deleteChip(this.innerChips.length - 1); } return; } if ((input_type === 'insertLineBreak')) { event.preventDefault(); event.stopImmediatePropagation(); if (this.highlighted_autocomplete_index !== null) { const div = this.autocompleteContainer.childNodes[this.highlighted_autocomplete_index]; return this.handleAutoCompleteItemSelected(div); } else { if (this.autocomplete_select_default) { if (this.autocompleteContainer.childNodes.length) { const div = this.autocompleteContainer.childNodes[0]; return this.handleAutoCompleteItemSelected(div); } } } await this.createChip(undefined); return; } if (this.constrain_input && !!this.autocomplete) { let value = this.real_input.value; value += key; this.highlighted_autocomplete_index = null; if (this.autocomplete) { autocomplete_items = await this.autocomplete(value); } if (autocomplete_items.length == 0) { event.preventDefault(); event.stopImmediatePropagation(); } return; } } handleChange() { if (!this.change_handler_enabled) { return; } } handleKeydown(event) { const key = event.key; let navigating = false; if (key === 'ArrowDown') { if (this.highlighted_autocomplete_index == null) this.highlighted_autocomplete_index = -1; this.highlighted_autocomplete_index++; if (this.highlighted_autocomplete_index > (this.autocompleteContainer.childNodes.length - 1)) this.highlighted_autocomplete_index = this.autocompleteContainer.childNodes.length - 1; navigating = true; } if (key === 'ArrowUp') { if (this.highlighted_autocomplete_index == null) this.highlighted_autocomplete_index = 1; this.highlighted_autocomplete_index--; if (this.highlighted_autocomplete_index < 0) this.highlighted_autocomplete_index = 0; navigating = true; } if (key === 'Escape') { this.real_input.value = ''; this.closeAutoComplete(false); } if (navigating) { this.highLightSelectedAutoCompleteItem(); } } highLightSelectedAutoCompleteItem() { const items = this.autocompleteContainer.children; for (let i = 0; i < items.length; i++) { const item = items.item(i); item.style.backgroundColor = 'var(--chip-input-autocomplete-background-color, white)'; if (this.highlighted_autocomplete_index === i) { item.style.backgroundColor = 'var(--chip-input-autocomplete-hover-background-color, lightblue)'; item.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'start' }); } } } updateCaretPosition() { const selection_start = this.real_input.selectionStart; this.caret_position_tracker.textContent = this.real_input.value.substring(0, selection_start).replace(/\s/g, '\u00a0'); } closeAutoComplete(force) { if (!force && this.show_autocomplete_on_focus) return; if (this.autocomplete_dismiss_target) { this.autocomplete_dismiss_target.removeEventListener('click', this.boundClickHandler); } else { document.removeEventListener('click', this.boundClickHandler); } this.autocompleteContainer.style.display = 'none'; } async deleteChip(index) { var _a; ((_a = this.innerChips) !== null && _a !== void 0 ? _a : []).splice(index, 1); this.innerChips = [...this.innerChips]; this.chipChange.emit(this.innerChips); if (this.show_autocomplete_on_focus && this.autocomplete) { await this.handleInput(undefined); } } async handleAutoCompleteItemSelected(div) { this.change_handler_enabled = false; this.real_input.value = div.dataset.value; await this.createChip(div.autocomplete_data); this.closeAutoComplete(false); this.real_input.blur(); this.real_input.focus(); this.highlighted_autocomplete_index = null; } async handleFocus() { this.updateCaretPosition(); if (this.show_autocomplete_on_focus && !!this.autocomplete) { const autocomplete_items = await this.autocomplete(this.real_input.value); this.showAutoComplete(autocomplete_items, this.real_input.value); } } showAutoComplete(autocomplete_items, highlight_value) { const rect = this.real_input.getBoundingClientRect(); const value = highlight_value; this.autocompleteContainer.style.display = 'block'; this.autocompleteContainer.style.top = `${this.real_input.offsetTop + rect.height + 3}px`; this.autocompleteContainer.style.left = `${this.real_input.offsetLeft}px`; this.autocompleteContainer.innerHTML = ''; autocomplete_items.map((item) => { let label; const div = document.createElement('DIV'); div.addEventListener('focus', (event) => { event.preventDefault(); event.stopImmediatePropagation(); }); div.style.backgroundColor = 'var(--chip-input-autocomplete-background-color, white)'; div.style.borderBottom = '1px solid lightgrey'; div.style.padding = '3px'; div.style.cursor = 'pointer'; if (this.fuzzy_search) { // autocomplete_items is an array of result object label = item.obj.v; div.innerHTML = item.highlight("<span style='font-weight: bold'>", "</span>"); } else { if (typeof item == 'string') { label = item; } else { label = item.label; } const start_index = label.toLowerCase().indexOf(value.toLowerCase()); const prefix = label.substring(0, start_index); const match = label.slice(start_index, start_index + value.length); const postfix = label.slice(start_index + value.length); if (this.autocomplete_highlight) { div.innerHTML = `${prefix}<span style='font-weight: bold'>${match}</span>${postfix}`; } else { div.innerHTML = label; } } div.dataset.value = label; div.onmouseover = () => div.style.backgroundColor = 'var(--chip-input-autocomplete-hover-background-color, lightblue)'; div.onmouseout = () => div.style.backgroundColor = 'var(--chip-input-autocomplete-background-color, white)'; div.onclick = () => void this.handleAutoCompleteItemSelected(div); this.autocompleteContainer.appendChild(div); }); let autocomplete_dismiss_target = this.autocompleteContainer; let element; if (this.autocomplete_dismiss_target) { if (typeof this.autocomplete_dismiss_target == 'string') { element = this.autocompleteContainer.querySelector(this.autocomplete_dismiss_target); } else { element = this.autocomplete_dismiss_target; } } if (element) { autocomplete_dismiss_target = element; } autocomplete_dismiss_target.addEventListener('click', this.boundClickHandler); } async createChip(value) { var _a; if (!value) { value = this.real_input.value; } if (!!this.constrain_input && !await this.containsFn(this.real_input.value)) { return; } if (value.trim() !== '') { this.innerChips = [...(_a = this.innerChips) !== null && _a !== void 0 ? _a : [], value.trim()]; this.change_handler_enabled = false; this.real_input.value = ''; this.change_handler_enabled = true; this.chipCreate.emit(value.trim()); this.chipChange.emit(this.innerChips); } if (this.show_autocomplete_on_focus && this.autocomplete) { this.updateCaretPosition(); await this.handleInput(); } else if (this.autocomplete) { this.closeAutoComplete(false); } } render() { return h(Host, { key: 'b0415a27fc32d80d6bbfb314769700ddf459b2d8' }, h("div", { key: '14eb39345a74a354764dfb8de82aaa984db96598', class: "chip-input-autocomplete-container", onFocus: this.handleAutocompleteContainerFocus.bind(this), ref: e => this.autocompleteContainer = e }), h("div", { key: '933bc3f97ee543d4ea8ff05e3956590e0d2f073f', class: { 'wrapper': true, 'disabled': this.disabled } }, GTSLib.isArray(this.innerChips) ? this.innerChips.map((chip, idx) => h("discovery-input-chips-chip", { onClick: event => this.handleChipClick(event, chip), disabled: this.disabled, label: chip, position: idx.toString(), onRemoveChip: this.handleChipClose.bind(this) })) : '', h("div", { key: '7a947e2ea8dbcf219ea9b9a98caaa62b4f963f3e', class: "caret_position_tracker", ref: el => this.caret_position_tracker = el }), h("input", { key: '184013434b0694a457a0c510f5db68c621fc90dd', class: "real_input", type: "text", ref: e => { if (this.real_input) { this.real_input.removeEventListener('beforeinput', this.real_input_handler); } this.real_input = e; this.real_input_handler = this.handleBeforeInput.bind(this); e.addEventListener('beforeinput', this.real_input_handler); }, onInput: this.handleInput.bind(this), disabled: this.disabled, onChange: this.handleChange.bind(this), onKeyDown: this.handleKeydown.bind(this), onKeyUp: this.updateCaretPosition.bind(this), onClick: this.updateCaretPosition.bind(this), onFocus: this.handleFocus.bind(this) }))); } static get is() { return "discovery-input-chips"; } static get encapsulation() { return "shadow"; } static get originalStyleUrls() { return { "$": ["discovery-input-chips.scss"] }; } static get styleUrls() { return { "$": ["discovery-input-chips.css"] }; } static get properties() { return { "chips": { "type": "unknown", "mutable": false, "complexType": { "original": "string[]", "resolved": "string[]", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "" }, "getter": false, "setter": false, "defaultValue": "[]" }, "autocomplete": { "type": "unknown", "mutable": false, "complexType": { "original": "(_value: string) => Promise<any>", "resolved": "(_value: string) => Promise<any>", "references": { "Promise": { "location": "global", "id": "global::Promise" } } }, "required": false, "optional": false, "docs": { "tags": [], "text": "" }, "getter": false, "setter": false }, "containsFn": { "type": "unknown", "mutable": false, "complexType": { "original": "(_value: string) => Promise<boolean>", "resolved": "(_value: string) => Promise<boolean>", "references": { "Promise": { "location": "global", "id": "global::Promise" } } }, "required": false, "optional": false, "docs": { "tags": [], "text": "" }, "getter": false, "setter": false }, "constrain_input": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "" }, "getter": false, "setter": false, "attribute": "constrain_input", "reflect": false, "defaultValue": "false" }, "fuzzy_search": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "" }, "getter": false, "setter": false, "attribute": "fuzzy_search", "reflect": false, "defaultValue": "false" }, "chipDelimiters": { "type": "unknown", "mutable": false, "complexType": { "original": "string[]", "resolved": "string[]", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "" }, "getter": false, "setter": false, "defaultValue": "[]" }, "value": { "type": "string", "mutable": true, "complexType": { "original": "string", "resolved": "string", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "" }, "getter": false, "setter": false, "attribute": "value", "reflect": false }, "disabled": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "" }, "getter": false, "setter": false, "attribute": "disabled", "reflect": false, "defaultValue": "false" } }; } static get states() { return { "innerChips": {} }; } static get events() { return [{ "method": "chipClick", "name": "chipClick", "bubbles": true, "cancelable": true, "composed": true, "docs": { "tags": [], "text": "" }, "complexType": { "original": "any", "resolved": "any", "references": {} } }, { "method": "chipInput", "name": "chipInput", "bubbles": true, "cancelable": true, "composed": true, "docs": { "tags": [], "text": "" }, "complexType": { "original": "void", "resolved": "void", "references": {} } }, { "method": "chipChange", "name": "chipChange", "bubbles": true, "cancelable": false, "composed": true, "docs": { "tags": [], "text": "" }, "complexType": { "original": "string[]", "resolved": "string[]", "references": {} } }, { "method": "chipCreate", "name": "chipCreate", "bubbles": true, "cancelable": false, "composed": true, "docs": { "tags": [], "text": "" }, "complexType": { "original": "any", "resolved": "any", "references": {} } }]; } static get elementRef() { return "el"; } static get watchers() { return [{ "propName": "chips", "methodName": "updateChips" }]; } static get listeners() { return [{ "name": "document:click", "method": "handleDocumentClick", "target": undefined, "capture": false, "passive": false }]; } } //# sourceMappingURL=discovery-input-chips.js.map