From acbf2e396758c65c0c54e7cda7c2046ce4e99604 Mon Sep 17 00:00:00 2001 From: Thomas Date: Mon, 13 Dec 2021 16:57:44 +0100 Subject: [PATCH] server side support --- demo.html | 8 +++ demo.json | 18 +++++++ package.json | 2 +- readme.md | 14 ++++++ tags.js | 128 +++++++++++++++++++++++++++++++++++------------- tags.min.js | 2 +- tags.min.js.map | 4 +- 7 files changed, 137 insertions(+), 39 deletions(-) create mode 100644 demo.json diff --git a/demo.html b/demo.html index fa22867..a974339 100644 --- a/demo.html +++ b/demo.html @@ -87,6 +87,14 @@

Demo

Please select a valid tag.
+
+
+ + +
+
diff --git a/demo.json b/demo.json new file mode 100644 index 0000000..23b705f --- /dev/null +++ b/demo.json @@ -0,0 +1,18 @@ +[ + { + "value": "server1", + "label": "Server 1" + }, + { + "value": "server2", + "label": "Server Orange", + "data": { + "badgeStyle": "warning", + "badgeClass": "text-dark" + } + }, + { + "value": "server3", + "label": "Server 3" + } +] diff --git a/package.json b/package.json index 1eca50b..9004744 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bootstrap5-tags", - "version": "1.2.4", + "version": "1.3.0", "description": "Replace select[multiple] with nices badges", "main": "tags", "scripts": { diff --git a/readme.md b/readme.md index b0aaae0..b271803 100644 --- a/readme.md +++ b/readme.md @@ -58,6 +58,20 @@ Use attribute `data-suggestions-threshold` to determine how many characters need *NOTE: don't forget the [] if you need multiple values!* +## Server side support + +You can also use options provided by the server. This script expects a json response that is an array or an object with the data key containing an array. + +Simply set `data-server` where your endpoint is located. It should provide an array of value/label objects. The suggestions will be populated upon init +except if `data-live-server` is set, in which case, it will be populated on type. A ?query= parameter is passed along with the current value of the searchInput. + +```html + + +``` + ## Accessibility You can set accessibility labels when passing options: diff --git a/tags.js b/tags.js index 969e50a..7d62aa0 100644 --- a/tags.js +++ b/tags.js @@ -26,12 +26,14 @@ class Tags { this.placeholder = this.getPlaceholder(); this.allowNew = selectElement.dataset.allowNew ? true : false; this.showAllSuggestions = selectElement.dataset.showAllSuggestions ? true : false; - this.badgeStyle = selectElement.dataset.badgeStyle ?? "primary"; + this.badgeStyle = selectElement.dataset.badgeStyle || "primary"; this.allowClear = selectElement.dataset.allowClear ? true : false; + this.server = selectElement.dataset.server || false; + this.liveServer = selectElement.dataset.liveServer ? true : false; this.suggestionsThreshold = selectElement.dataset.suggestionsThreshold ? parseInt(selectElement.dataset.suggestionsThreshold) : 1; this.keyboardNavigation = false; - this.clearLabel = opts.clearLabel ?? "Clear"; - this.searchLabel = opts.searchLabel ?? "Type a value"; + this.clearLabel = opts.clearLabel || "Clear"; + this.searchLabel = opts.searchLabel || "Type a value"; // Create elements this.holderElement = document.createElement("div"); // this is the one holding the fake input and the dropmenu @@ -50,7 +52,18 @@ class Tags { this.configureHolderElement(); this.configureDropElement(); this.configureContainerElement(); - this.buildSuggestions(); + + if (this.server && !this.liveServer) { + this.loadFromServer(); + } else { + let suggestions = Array.from(this.selectElement.querySelectorAll("option")).map((option) => { + return { + value: option.getAttribute("value"), + label: option.innerText, + }; + }); + this.buildSuggestions(suggestions); + } } /** @@ -66,6 +79,32 @@ class Tags { } } + /** + * @param {boolean} show + */ + loadFromServer(show = false) { + if (this.abortController) { + this.abortController.abort(); + } + this.abortController = new AbortController(); + fetch(this.server + "?query=" + encodeURIComponent(this.searchInput.value), { signal: this.abortController.signal }) + .then((r) => r.json()) + .then((suggestions) => { + let data = suggestions.data || suggestions; + this.buildSuggestions(data); + this.abortController = null; + if (show) { + this.showSuggestions(); + } + }) + .catch((e) => { + if (e.name === "AbortError") { + return; + } + console.error(e); + }); + } + /** * @returns {string} */ @@ -139,7 +178,11 @@ class Tags { this.searchInput.addEventListener("input", (event) => { this.adjustWidth(); if (this.searchInput.value.length >= this.suggestionsThreshold) { - this.showSuggestions(); + if (this.liveServer) { + this.loadFromServer(true); + } else { + this.showSuggestions(); + } } else { this.hideSuggestions(); } @@ -161,14 +204,11 @@ class Tags { case "Enter": let selection = this.getActiveSelection(); if (selection) { - this.addItem(selection.innerText, selection.getAttribute(VALUE_ATTRIBUTE)); - this.resetSearchInput(); - this.hideSuggestions(); - this.removeActiveSelection(); + selection.click(); } else { // We use what is typed - if (this.allowNew) { - let res = this.addItem(this.searchInput.value, null, true); + if (this.allowNew && !this.isSelected(this.searchInput.value)) { + let res = this.addItem(this.searchInput.value, null); if (res) { this.resetSearchInput(); this.hideSuggestions(); @@ -274,22 +314,30 @@ class Tags { } /** - * Add suggestions from element + * Add suggestions to the drop element + * @param {array} */ - buildSuggestions() { - let options = this.selectElement.querySelectorAll("option"); - for (let i = 0; i < options.length; i++) { - let opt = options[i]; - if (!opt.getAttribute("value")) { + buildSuggestions(suggestions = null) { + while (this.dropElement.lastChild) { + this.dropElement.removeChild(this.dropElement.lastChild); + } + for (let i = 0; i < suggestions.length; i++) { + let suggestion = suggestions[i]; + if (!suggestion.value) { continue; } let newChild = document.createElement("li"); let newChildLink = document.createElement("a"); newChild.append(newChildLink); newChildLink.classList.add("dropdown-item"); - newChildLink.setAttribute(VALUE_ATTRIBUTE, opt.getAttribute("value")); + newChildLink.setAttribute(VALUE_ATTRIBUTE, suggestion.value); newChildLink.setAttribute("href", "#"); - newChildLink.innerText = opt.innerText; + newChildLink.innerText = suggestion.label; + if (suggestion.data) { + for (const [key, value] of Object.entries(suggestion.data)) { + newChildLink.dataset[key] = value; + } + } this.dropElement.appendChild(newChild); // Hover sets active item @@ -312,7 +360,7 @@ class Tags { }); newChildLink.addEventListener("click", (event) => { event.preventDefault(); - this.addItem(newChildLink.innerText, newChildLink.getAttribute(VALUE_ATTRIBUTE)); + this.addItem(newChildLink.innerText, newChildLink.getAttribute(VALUE_ATTRIBUTE), newChildLink.dataset); this.resetSearchInput(); this.hideSuggestions(); }); @@ -450,28 +498,34 @@ class Tags { return ver; } + /** + * Find if label is already selected + * @param {string} text + * @returns {boolean} + */ + isSelected(text) { + const opt = Array.from(this.selectElement.querySelectorAll("option")).find((el) => el.textContent == text); + if (opt && opt.getAttribute("selected")) { + return true; + } + return false; + } + /** * @param {string} text * @param {string} value - * @param {boolean} checkSelected + * @param {object} data * @return {boolean} */ - addItem(text, value, checkSelected = false) { + addItem(text, value = null, data = {}) { if (!value) { value = text; } const bver = this.getBootstrapVersion(); - - // Find by label and value let opt = this.selectElement.querySelector('option[value="' + value + '"]'); - if (!opt) { - opt = Array.from(this.selectElement.querySelectorAll("option")).find((el) => el.textContent == text); - } - if (checkSelected) { - if (opt && opt.getAttribute("selected")) { - return false; - } + if (opt) { + data = opt.dataset; } // create span @@ -479,11 +533,11 @@ class Tags { let span = document.createElement("span"); let badgeStyle = this.badgeStyle; span.classList.add("badge"); - if (opt && opt.dataset.badgeStyle) { - badgeStyle = opt.dataset.badgeStyle; + if (data.badgeStyle) { + badgeStyle = data.badgeStyle; } - if (opt && opt.dataset.badgeClass) { - span.classList.add(opt.dataset.badgeClass); + if (data.badgeClass) { + span.classList.add(data.badgeClass); } if (bver === 5) { //https://getbootstrap.com/docs/5.1/components/badge/ @@ -524,6 +578,10 @@ class Tags { opt = document.createElement("option"); opt.value = value; opt.innerText = text; + // Pass along data provided + for (const [key, value] of Object.entries(data)) { + opt.dataset[key] = value; + } opt.setAttribute("selected", "selected"); this.selectElement.appendChild(opt); } diff --git a/tags.min.js b/tags.min.js index 8ee95e1..3cb842f 100644 --- a/tags.min.js +++ b/tags.min.js @@ -1,2 +1,2 @@ -var m="is-active",o=["is-active","bg-primary","text-white"],d="data-value",c=class{constructor(t,e={}){this.selectElement=t,this.selectElement.style.display="none",this.placeholder=this.getPlaceholder(),this.allowNew=!!t.dataset.allowNew,this.showAllSuggestions=!!t.dataset.showAllSuggestions,this.badgeStyle=t.dataset.badgeStyle??"primary",this.allowClear=!!t.dataset.allowClear,this.suggestionsThreshold=t.dataset.suggestionsThreshold?parseInt(t.dataset.suggestionsThreshold):1,this.keyboardNavigation=!1,this.clearLabel=e.clearLabel??"Clear",this.searchLabel=e.searchLabel??"Type a value",this.holderElement=document.createElement("div"),this.containerElement=document.createElement("div"),this.dropElement=document.createElement("ul"),this.searchInput=document.createElement("input"),this.holderElement.appendChild(this.containerElement),this.containerElement.appendChild(this.searchInput),this.holderElement.appendChild(this.dropElement),this.selectElement.parentNode.insertBefore(this.holderElement,this.selectElement.nextSibling),this.configureSearchInput(),this.configureHolderElement(),this.configureDropElement(),this.configureContainerElement(),this.buildSuggestions()}static init(t="select[multiple]",e={}){let i=document.querySelectorAll(t);for(let l=0;l{this.keyboardNavigation=!1})}configureHolderElement(){this.holderElement.classList.add("form-control"),this.holderElement.classList.add("dropdown"),this.getBootstrapVersion()===4&&(this.holderElement.style.height="auto")}configureContainerElement(){this.containerElement.addEventListener("click",e=>{this.searchInput.focus()});let t=this.selectElement.querySelectorAll("option[selected]");for(let e=0;e{this.adjustWidth(),this.searchInput.value.length>=this.suggestionsThreshold?this.showSuggestions():this.hideSuggestions()}),this.searchInput.addEventListener("focus",e=>{this.searchInput.value.length>=this.suggestionsThreshold&&this.showSuggestions()}),this.searchInput.addEventListener("focusout",e=>{t.hideSuggestions()}),this.searchInput.addEventListener("keydown",e=>{switch(e.keyCode||e.key){case 13:case"Enter":let l=this.getActiveSelection();l?(this.addItem(l.innerText,l.getAttribute(d)),this.resetSearchInput(),this.hideSuggestions(),this.removeActiveSelection()):this.allowNew&&this.addItem(this.searchInput.value,null,!0)&&(this.resetSearchInput(),this.hideSuggestions()),e.preventDefault();break;case 38:case"ArrowUp":e.preventDefault(),this.keyboardNavigation=!0;let s=this.moveSelectionUp();this.searchInput.value.length==0&&this.dropElement.classList.contains("show")&&!s&&this.hideSuggestions();break;case 40:case"ArrowDown":e.preventDefault(),this.keyboardNavigation=!0,this.moveSelectionDown(),this.searchInput.value.length==0&&!this.dropElement.classList.contains("show")&&this.showSuggestions();break;case 8:case"Backspace":this.searchInput.value.length==0&&(this.removeLastItem(),this.adjustWidth(),this.hideSuggestions());break}})}moveSelectionUp(){let t=this.getActiveSelection();if(t){let e=t.parentNode;do e=e.previousSibling;while(e&&e.style.display=="none");return e?(t.classList.remove(...o),e.querySelector("a").classList.add(...o),e.parentNode.scrollTop=e.offsetTop-e.parentNode.offsetTop,e):null}return null}moveSelectionDown(){let t=this.getActiveSelection();if(t){let e=t.parentNode;do e=e.nextSibling;while(e&&e.style.display=="none");return e?(t.classList.remove(...o),e.querySelector("a").classList.add(...o),e.offsetTop>e.parentNode.offsetHeight-e.offsetHeight&&(e.parentNode.scrollTop+=e.offsetHeight),e):null}return null}adjustWidth(){this.searchInput.value?this.searchInput.size=this.searchInput.value.length+1:this.getSelectedValues().length?(this.searchInput.placeholder="",this.searchInput.size=1):(this.searchInput.size=this.placeholder.length,this.searchInput.placeholder=this.placeholder)}buildSuggestions(){let t=this.selectElement.querySelectorAll("option");for(let e=0;e{this.keyboardNavigation||(this.removeActiveSelection(),l.querySelector("a").classList.add(...o))}),s.addEventListener("mousemove",n=>{this.keyboardNavigation=!1}),s.addEventListener("mousedown",n=>{n.preventDefault()}),s.addEventListener("click",n=>{n.preventDefault(),this.addItem(s.innerText,s.getAttribute(d)),this.resetSearchInput(),this.hideSuggestions()})}}resetSearchInput(){this.searchInput.value="",this.adjustWidth()}getSelectedValues(){let t=this.selectElement.querySelectorAll("option:checked");return Array.from(t).map(e=>e.value)}showSuggestions(){this.dropElement.classList.contains("show")||this.dropElement.classList.add("show"),this.dropElement.style.left=this.searchInput.offsetLeft+"px";let t=this.searchInput.value.toLocaleLowerCase(),e=this.getSelectedValues(),i=this.dropElement.querySelectorAll("li"),l=!1,s=null,n=!1;for(let r=0;rh.textContent==t)),i&&s&&s.getAttribute("selected"))return!1;let n=t,r=document.createElement("span"),a=this.badgeStyle;return r.classList.add("badge"),s&&s.dataset.badgeStyle&&(a=s.dataset.badgeStyle),s&&s.dataset.badgeClass&&r.classList.add(s.dataset.badgeClass),l===5?(r.classList.add("bg-"+a),r.classList.add("me-2")):(r.classList.add("badge-"+a),r.classList.add("mr-2")),r.setAttribute(d,e),this.allowClear&&(n=(l===5?'':'')+n),r.innerHTML=n,this.containerElement.insertBefore(r,this.searchInput),this.allowClear&&r.querySelector("button").addEventListener("click",h=>{h.preventDefault(),h.stopPropagation(),this.removeItem(e),document.activeElement.blur()}),s?s.setAttribute("selected","selected"):(s=document.createElement("option"),s.value=e,s.innerText=t,s.setAttribute("selected","selected"),this.selectElement.appendChild(s)),!0}removeItem(t){let e=this.containerElement.querySelector("span["+d+'="'+t+'"]');if(!e)return;e.remove();let i=this.selectElement.querySelector('option[value="'+t+'"]');i&&i.removeAttribute("selected")}},f=c;export{f as default}; +var m="is-active",a=["is-active","bg-primary","text-white"],d="data-value",u=class{constructor(t,e={}){if(this.selectElement=t,this.selectElement.style.display="none",this.placeholder=this.getPlaceholder(),this.allowNew=!!t.dataset.allowNew,this.showAllSuggestions=!!t.dataset.showAllSuggestions,this.badgeStyle=t.dataset.badgeStyle||"primary",this.allowClear=!!t.dataset.allowClear,this.server=t.dataset.server||!1,this.liveServer=!!t.dataset.liveServer,this.suggestionsThreshold=t.dataset.suggestionsThreshold?parseInt(t.dataset.suggestionsThreshold):1,this.keyboardNavigation=!1,this.clearLabel=e.clearLabel||"Clear",this.searchLabel=e.searchLabel||"Type a value",this.holderElement=document.createElement("div"),this.containerElement=document.createElement("div"),this.dropElement=document.createElement("ul"),this.searchInput=document.createElement("input"),this.holderElement.appendChild(this.containerElement),this.containerElement.appendChild(this.searchInput),this.holderElement.appendChild(this.dropElement),this.selectElement.parentNode.insertBefore(this.holderElement,this.selectElement.nextSibling),this.configureSearchInput(),this.configureHolderElement(),this.configureDropElement(),this.configureContainerElement(),this.server&&!this.liveServer)this.loadFromServer();else{let i=Array.from(this.selectElement.querySelectorAll("option")).map(l=>({value:l.getAttribute("value"),label:l.innerText}));this.buildSuggestions(i)}}static init(t="select[multiple]",e={}){let i=document.querySelectorAll(t);for(let l=0;l{this.keyboardNavigation=!1})}configureHolderElement(){this.holderElement.classList.add("form-control"),this.holderElement.classList.add("dropdown"),this.getBootstrapVersion()===4&&(this.holderElement.style.height="auto")}configureContainerElement(){this.containerElement.addEventListener("click",e=>{this.searchInput.focus()});let t=this.selectElement.querySelectorAll("option[selected]");for(let e=0;e{this.adjustWidth(),this.searchInput.value.length>=this.suggestionsThreshold?this.liveServer?this.loadFromServer(!0):this.showSuggestions():this.hideSuggestions()}),this.searchInput.addEventListener("focus",e=>{this.searchInput.value.length>=this.suggestionsThreshold&&this.showSuggestions()}),this.searchInput.addEventListener("focusout",e=>{t.hideSuggestions()}),this.searchInput.addEventListener("keydown",e=>{switch(e.keyCode||e.key){case 13:case"Enter":let l=this.getActiveSelection();l?l.click():this.allowNew&&!this.isSelected(this.searchInput.value)&&this.addItem(this.searchInput.value,null)&&(this.resetSearchInput(),this.hideSuggestions()),e.preventDefault();break;case 38:case"ArrowUp":e.preventDefault(),this.keyboardNavigation=!0;let s=this.moveSelectionUp();this.searchInput.value.length==0&&this.dropElement.classList.contains("show")&&!s&&this.hideSuggestions();break;case 40:case"ArrowDown":e.preventDefault(),this.keyboardNavigation=!0,this.moveSelectionDown(),this.searchInput.value.length==0&&!this.dropElement.classList.contains("show")&&this.showSuggestions();break;case 8:case"Backspace":this.searchInput.value.length==0&&(this.removeLastItem(),this.adjustWidth(),this.hideSuggestions());break}})}moveSelectionUp(){let t=this.getActiveSelection();if(t){let e=t.parentNode;do e=e.previousSibling;while(e&&e.style.display=="none");return e?(t.classList.remove(...a),e.querySelector("a").classList.add(...a),e.parentNode.scrollTop=e.offsetTop-e.parentNode.offsetTop,e):null}return null}moveSelectionDown(){let t=this.getActiveSelection();if(t){let e=t.parentNode;do e=e.nextSibling;while(e&&e.style.display=="none");return e?(t.classList.remove(...a),e.querySelector("a").classList.add(...a),e.offsetTop>e.parentNode.offsetHeight-e.offsetHeight&&(e.parentNode.scrollTop+=e.offsetHeight),e):null}return null}adjustWidth(){this.searchInput.value?this.searchInput.size=this.searchInput.value.length+1:this.getSelectedValues().length?(this.searchInput.placeholder="",this.searchInput.size=1):(this.searchInput.size=this.placeholder.length,this.searchInput.placeholder=this.placeholder)}buildSuggestions(t=null){for(;this.dropElement.lastChild;)this.dropElement.removeChild(this.dropElement.lastChild);for(let e=0;e{this.keyboardNavigation||(this.removeActiveSelection(),l.querySelector("a").classList.add(...a))}),s.addEventListener("mousemove",r=>{this.keyboardNavigation=!1}),s.addEventListener("mousedown",r=>{r.preventDefault()}),s.addEventListener("click",r=>{r.preventDefault(),this.addItem(s.innerText,s.getAttribute(d),s.dataset),this.resetSearchInput(),this.hideSuggestions()})}}resetSearchInput(){this.searchInput.value="",this.adjustWidth()}getSelectedValues(){let t=this.selectElement.querySelectorAll("option:checked");return Array.from(t).map(e=>e.value)}showSuggestions(){this.dropElement.classList.contains("show")||this.dropElement.classList.add("show"),this.dropElement.style.left=this.searchInput.offsetLeft+"px";let t=this.searchInput.value.toLocaleLowerCase(),e=this.getSelectedValues(),i=this.dropElement.querySelectorAll("li"),l=!1,s=null,r=!1;for(let n=0;ni.textContent==t);return!!(e&&e.getAttribute("selected"))}addItem(t,e=null,i={}){e||(e=t);let l=this.getBootstrapVersion(),s=this.selectElement.querySelector('option[value="'+e+'"]');s&&(i=s.dataset);let r=t,n=document.createElement("span"),o=this.badgeStyle;if(n.classList.add("badge"),i.badgeStyle&&(o=i.badgeStyle),i.badgeClass&&n.classList.add(i.badgeClass),l===5?(n.classList.add("bg-"+o),n.classList.add("me-2")):(n.classList.add("badge-"+o),n.classList.add("mr-2")),n.setAttribute(d,e),this.allowClear&&(r=(l===5?'':'')+r),n.innerHTML=r,this.containerElement.insertBefore(n,this.searchInput),this.allowClear&&n.querySelector("button").addEventListener("click",h=>{h.preventDefault(),h.stopPropagation(),this.removeItem(e),document.activeElement.blur()}),s)s.setAttribute("selected","selected");else{s=document.createElement("option"),s.value=e,s.innerText=t;for(let[h,c]of Object.entries(i))s.dataset[h]=c;s.setAttribute("selected","selected"),this.selectElement.appendChild(s)}return!0}removeItem(t){let e=this.containerElement.querySelector("span["+d+'="'+t+'"]');if(!e)return;e.remove();let i=this.selectElement.querySelector('option[value="'+t+'"]');i&&i.removeAttribute("selected")}},f=u;export{f as default}; //# sourceMappingURL=tags.min.js.map diff --git a/tags.min.js.map b/tags.min.js.map index b401f90..b0f8195 100644 --- a/tags.min.js.map +++ b/tags.min.js.map @@ -1,7 +1,7 @@ { "version": 3, "sources": ["tags.js"], - "sourcesContent": ["/**\r\n * Bootstrap 5 (and 4!) tags\r\n *\r\n * Turns your select[multiple] into nice tags lists\r\n *\r\n * Required Bootstrap 5 styles:\r\n * - badge\r\n * - background-color utility\r\n * - margin-end utility\r\n * - forms\r\n * - dropdown\r\n */\r\n\r\nconst ACTIVE_CLASS = \"is-active\";\r\nconst ACTIVE_CLASSES = [\"is-active\", \"bg-primary\", \"text-white\"];\r\nconst VALUE_ATTRIBUTE = \"data-value\";\r\n\r\nclass Tags {\r\n /**\r\n * @param {HTMLSelectElement} selectElement\r\n * @param {Object} opts\r\n */\r\n constructor(selectElement, opts = {}) {\r\n this.selectElement = selectElement;\r\n this.selectElement.style.display = \"none\";\r\n this.placeholder = this.getPlaceholder();\r\n this.allowNew = selectElement.dataset.allowNew ? true : false;\r\n this.showAllSuggestions = selectElement.dataset.showAllSuggestions ? true : false;\r\n this.badgeStyle = selectElement.dataset.badgeStyle ?? \"primary\";\r\n this.allowClear = selectElement.dataset.allowClear ? true : false;\r\n this.suggestionsThreshold = selectElement.dataset.suggestionsThreshold ? parseInt(selectElement.dataset.suggestionsThreshold) : 1;\r\n this.keyboardNavigation = false;\r\n this.clearLabel = opts.clearLabel ?? \"Clear\";\r\n this.searchLabel = opts.searchLabel ?? \"Type a value\";\r\n\r\n // Create elements\r\n this.holderElement = document.createElement(\"div\"); // this is the one holding the fake input and the dropmenu\r\n this.containerElement = document.createElement(\"div\"); // this is the one for the fake input (labels + input)\r\n this.dropElement = document.createElement(\"ul\");\r\n this.searchInput = document.createElement(\"input\");\r\n\r\n this.holderElement.appendChild(this.containerElement);\r\n this.containerElement.appendChild(this.searchInput);\r\n this.holderElement.appendChild(this.dropElement);\r\n // insert after\r\n this.selectElement.parentNode.insertBefore(this.holderElement, this.selectElement.nextSibling);\r\n\r\n // Configure them\r\n this.configureSearchInput();\r\n this.configureHolderElement();\r\n this.configureDropElement();\r\n this.configureContainerElement();\r\n this.buildSuggestions();\r\n }\r\n\r\n /**\r\n * Attach to all elements matched by the selector\r\n * @param {string} selector\r\n * @param {Object} opts\r\n */\r\n static init(selector = \"select[multiple]\", opts = {}) {\r\n let list = document.querySelectorAll(selector);\r\n for (let i = 0; i < list.length; i++) {\r\n let el = list[i];\r\n let inst = new Tags(el, opts);\r\n }\r\n }\r\n\r\n /**\r\n * @returns {string}\r\n */\r\n getPlaceholder() {\r\n let firstOption = this.selectElement.querySelector(\"option\");\r\n if (!firstOption) {\r\n return;\r\n }\r\n if (!firstOption.value) {\r\n let placeholder = firstOption.innerText;\r\n firstOption.remove();\r\n return placeholder;\r\n }\r\n if (this.selectElement.getAttribute(\"placeholder\")) {\r\n return this.selectElement.getAttribute(\"placeholder\");\r\n }\r\n if (this.selectElement.getAttribute(\"data-placeholder\")) {\r\n return this.selectElement.getAttribute(\"data-placeholder\");\r\n }\r\n return \"\";\r\n }\r\n\r\n configureDropElement() {\r\n this.dropElement.classList.add(\"dropdown-menu\");\r\n this.dropElement.classList.add(\"p-0\");\r\n this.dropElement.style.maxHeight = \"280px\";\r\n this.dropElement.style.overflowY = \"auto\";\r\n\r\n // If the mouse was outside, entering remove keyboard nav mode\r\n this.dropElement.addEventListener(\"mouseenter\", (event) => {\r\n this.keyboardNavigation = false;\r\n });\r\n }\r\n\r\n configureHolderElement() {\r\n this.holderElement.classList.add(\"form-control\");\r\n this.holderElement.classList.add(\"dropdown\");\r\n if (this.getBootstrapVersion() === 4) {\r\n // Prevent fixed height due to form-control\r\n this.holderElement.style.height = \"auto\";\r\n }\r\n }\r\n\r\n configureContainerElement() {\r\n this.containerElement.addEventListener(\"click\", (event) => {\r\n this.searchInput.focus();\r\n });\r\n\r\n // add initial values\r\n let initialValues = this.selectElement.querySelectorAll(\"option[selected]\");\r\n for (let j = 0; j < initialValues.length; j++) {\r\n let initialValue = initialValues[j];\r\n if (!initialValue.value) {\r\n continue;\r\n }\r\n this.addItem(initialValue.innerText, initialValue.value);\r\n }\r\n }\r\n\r\n configureSearchInput() {\r\n let self = this;\r\n this.searchInput.type = \"text\";\r\n this.searchInput.autocomplete = \"off\";\r\n this.searchInput.style.border = 0;\r\n this.searchInput.style.outline = 0;\r\n this.searchInput.style.maxWidth = \"100%\";\r\n this.searchInput.ariaLabel = this.searchLabel;\r\n\r\n this.adjustWidth();\r\n\r\n this.searchInput.addEventListener(\"input\", (event) => {\r\n this.adjustWidth();\r\n if (this.searchInput.value.length >= this.suggestionsThreshold) {\r\n this.showSuggestions();\r\n } else {\r\n this.hideSuggestions();\r\n }\r\n });\r\n this.searchInput.addEventListener(\"focus\", (event) => {\r\n if (this.searchInput.value.length >= this.suggestionsThreshold) {\r\n this.showSuggestions();\r\n }\r\n });\r\n this.searchInput.addEventListener(\"focusout\", (event) => {\r\n self.hideSuggestions();\r\n });\r\n // keypress doesn't send arrow keys\r\n this.searchInput.addEventListener(\"keydown\", (event) => {\r\n // Keycode reference : https://css-tricks.com/snippets/javascript/javascript-keycodes/\r\n let key = event.keyCode || event.key;\r\n switch (key) {\r\n case 13:\r\n case \"Enter\":\r\n let selection = this.getActiveSelection();\r\n if (selection) {\r\n this.addItem(selection.innerText, selection.getAttribute(VALUE_ATTRIBUTE));\r\n this.resetSearchInput();\r\n this.hideSuggestions();\r\n this.removeActiveSelection();\r\n } else {\r\n // We use what is typed\r\n if (this.allowNew) {\r\n let res = this.addItem(this.searchInput.value, null, true);\r\n if (res) {\r\n this.resetSearchInput();\r\n this.hideSuggestions();\r\n }\r\n }\r\n }\r\n event.preventDefault();\r\n break;\r\n case 38:\r\n case \"ArrowUp\":\r\n event.preventDefault();\r\n this.keyboardNavigation = true;\r\n let newSelection = this.moveSelectionUp();\r\n // If we use arrow up without input and there is no new selection, hide suggestions\r\n if (this.searchInput.value.length == 0 && this.dropElement.classList.contains(\"show\") && !newSelection) {\r\n this.hideSuggestions();\r\n }\r\n break;\r\n case 40:\r\n case \"ArrowDown\":\r\n event.preventDefault();\r\n this.keyboardNavigation = true;\r\n this.moveSelectionDown();\r\n // If we use arrow down without input, show suggestions\r\n if (this.searchInput.value.length == 0 && !this.dropElement.classList.contains(\"show\")) {\r\n this.showSuggestions();\r\n }\r\n break;\r\n case 8:\r\n case \"Backspace\":\r\n if (this.searchInput.value.length == 0) {\r\n this.removeLastItem();\r\n this.adjustWidth();\r\n this.hideSuggestions();\r\n }\r\n break;\r\n }\r\n });\r\n }\r\n\r\n /**\r\n * @returns {HTMLElement}\r\n */\r\n moveSelectionUp() {\r\n let active = this.getActiveSelection();\r\n if (active) {\r\n let prev = active.parentNode;\r\n do {\r\n prev = prev.previousSibling;\r\n } while (prev && prev.style.display == \"none\");\r\n if (!prev) {\r\n return null;\r\n }\r\n active.classList.remove(...ACTIVE_CLASSES);\r\n prev.querySelector(\"a\").classList.add(...ACTIVE_CLASSES);\r\n // Don't use scrollIntoView as it scrolls the whole window\r\n prev.parentNode.scrollTop = prev.offsetTop - prev.parentNode.offsetTop;\r\n return prev;\r\n }\r\n return null;\r\n }\r\n\r\n /**\r\n * @returns {HTMLElement}\r\n */\r\n moveSelectionDown() {\r\n let active = this.getActiveSelection();\r\n if (active) {\r\n let next = active.parentNode;\r\n do {\r\n next = next.nextSibling;\r\n } while (next && next.style.display == \"none\");\r\n if (!next) {\r\n return null;\r\n }\r\n active.classList.remove(...ACTIVE_CLASSES);\r\n next.querySelector(\"a\").classList.add(...ACTIVE_CLASSES);\r\n // This is the equivalent of scrollIntoView(false) but only for parent node\r\n if (next.offsetTop > next.parentNode.offsetHeight - next.offsetHeight) {\r\n next.parentNode.scrollTop += next.offsetHeight;\r\n }\r\n return next;\r\n }\r\n return null;\r\n }\r\n\r\n /**\r\n * Adjust the field to fit its content\r\n */\r\n adjustWidth() {\r\n if (this.searchInput.value) {\r\n this.searchInput.size = this.searchInput.value.length + 1;\r\n } else {\r\n // Show the placeholder only if empty\r\n if (this.getSelectedValues().length) {\r\n this.searchInput.placeholder = \"\";\r\n this.searchInput.size = 1;\r\n } else {\r\n this.searchInput.size = this.placeholder.length;\r\n this.searchInput.placeholder = this.placeholder;\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Add suggestions from element\r\n */\r\n buildSuggestions() {\r\n let options = this.selectElement.querySelectorAll(\"option\");\r\n for (let i = 0; i < options.length; i++) {\r\n let opt = options[i];\r\n if (!opt.getAttribute(\"value\")) {\r\n continue;\r\n }\r\n let newChild = document.createElement(\"li\");\r\n let newChildLink = document.createElement(\"a\");\r\n newChild.append(newChildLink);\r\n newChildLink.classList.add(\"dropdown-item\");\r\n newChildLink.setAttribute(VALUE_ATTRIBUTE, opt.getAttribute(\"value\"));\r\n newChildLink.setAttribute(\"href\", \"#\");\r\n newChildLink.innerText = opt.innerText;\r\n this.dropElement.appendChild(newChild);\r\n\r\n // Hover sets active item\r\n newChildLink.addEventListener(\"mouseenter\", (event) => {\r\n // Don't trigger enter if using arrows\r\n if (this.keyboardNavigation) {\r\n return;\r\n }\r\n this.removeActiveSelection();\r\n newChild.querySelector(\"a\").classList.add(...ACTIVE_CLASSES);\r\n });\r\n // Moving the mouse means no longer using keyboard\r\n newChildLink.addEventListener(\"mousemove\", (event) => {\r\n this.keyboardNavigation = false;\r\n });\r\n\r\n newChildLink.addEventListener(\"mousedown\", (event) => {\r\n // Otherwise searchInput would lose focus and close the menu\r\n event.preventDefault();\r\n });\r\n newChildLink.addEventListener(\"click\", (event) => {\r\n event.preventDefault();\r\n this.addItem(newChildLink.innerText, newChildLink.getAttribute(VALUE_ATTRIBUTE));\r\n this.resetSearchInput();\r\n this.hideSuggestions();\r\n });\r\n }\r\n }\r\n\r\n resetSearchInput() {\r\n this.searchInput.value = \"\";\r\n this.adjustWidth();\r\n }\r\n\r\n /**\r\n * @returns {array}\r\n */\r\n getSelectedValues() {\r\n let selected = this.selectElement.querySelectorAll(\"option:checked\");\r\n return Array.from(selected).map((el) => el.value);\r\n }\r\n\r\n /**\r\n * The element create with buildSuggestions\r\n */\r\n showSuggestions() {\r\n if (!this.dropElement.classList.contains(\"show\")) {\r\n this.dropElement.classList.add(\"show\");\r\n }\r\n\r\n // Position next to search input\r\n this.dropElement.style.left = this.searchInput.offsetLeft + \"px\";\r\n\r\n // Get search value\r\n let search = this.searchInput.value.toLocaleLowerCase();\r\n\r\n // Get current values\r\n let values = this.getSelectedValues();\r\n\r\n // Filter the list according to search string\r\n let list = this.dropElement.querySelectorAll(\"li\");\r\n let found = false;\r\n let firstItem = null;\r\n let hasPossibleValues = false;\r\n for (let i = 0; i < list.length; i++) {\r\n let item = list[i];\r\n let text = item.innerText.toLocaleLowerCase();\r\n let link = item.querySelector(\"a\");\r\n\r\n // Remove previous selection\r\n link.classList.remove(...ACTIVE_CLASSES);\r\n\r\n // Hide selected values\r\n if (values.indexOf(link.getAttribute(VALUE_ATTRIBUTE)) != -1) {\r\n item.style.display = \"none\";\r\n continue;\r\n }\r\n\r\n hasPossibleValues = true;\r\n\r\n // Check search length since we can trigger dropdown with arrow\r\n let isMatched = search.length === 0 || text.indexOf(search) !== -1;\r\n if (this.showAllSuggestions || this.suggestionsThreshold === 0 || isMatched) {\r\n item.style.display = \"list-item\";\r\n found = true;\r\n if (!firstItem && isMatched) {\r\n firstItem = item;\r\n }\r\n } else {\r\n item.style.display = \"none\";\r\n }\r\n }\r\n\r\n // Special case if nothing matches\r\n if (!found) {\r\n this.dropElement.classList.remove(\"show\");\r\n }\r\n\r\n // Always select first item\r\n if (firstItem) {\r\n if (this.holderElement.classList.contains(\"is-invalid\")) {\r\n this.holderElement.classList.remove(\"is-invalid\");\r\n }\r\n firstItem.querySelector(\"a\").classList.add(...ACTIVE_CLASSES);\r\n firstItem.parentNode.scrollTop = firstItem.offsetTop - firstItem.parentNode.offsetTop;\r\n } else {\r\n // No item and we don't allow new items => error\r\n if (!this.allowNew && !(search.length === 0 && !hasPossibleValues)) {\r\n this.holderElement.classList.add(\"is-invalid\");\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * The element create with buildSuggestions\r\n */\r\n hideSuggestions() {\r\n if (this.dropElement.classList.contains(\"show\")) {\r\n this.dropElement.classList.remove(\"show\");\r\n }\r\n if (this.holderElement.classList.contains(\"is-invalid\")) {\r\n this.holderElement.classList.remove(\"is-invalid\");\r\n }\r\n }\r\n\r\n /**\r\n * @returns {HTMLElement}\r\n */\r\n getActiveSelection() {\r\n return this.dropElement.querySelector(\"a.\" + ACTIVE_CLASS);\r\n }\r\n\r\n removeActiveSelection() {\r\n let selection = this.getActiveSelection();\r\n if (selection) {\r\n selection.classList.remove(...ACTIVE_CLASSES);\r\n }\r\n }\r\n\r\n removeLastItem() {\r\n let items = this.containerElement.querySelectorAll(\"span\");\r\n if (!items.length) {\r\n return;\r\n }\r\n let lastItem = items[items.length - 1];\r\n this.removeItem(lastItem.getAttribute(VALUE_ATTRIBUTE));\r\n }\r\n\r\n /**\r\n * @returns {Number}\r\n */\r\n getBootstrapVersion() {\r\n let ver = 5;\r\n // If we have jQuery and the tooltip plugin for BS4\r\n if (window.jQuery && $.fn.tooltip != undefined && $.fn.tooltip.Constructor != undefined) {\r\n ver = parseInt($.fn.tooltip.Constructor.VERSION.charAt(0));\r\n }\r\n return ver;\r\n }\r\n\r\n /**\r\n * @param {string} text\r\n * @param {string} value\r\n * @param {boolean} checkSelected\r\n * @return {boolean}\r\n */\r\n addItem(text, value, checkSelected = false) {\r\n if (!value) {\r\n value = text;\r\n }\r\n\r\n const bver = this.getBootstrapVersion();\r\n\r\n // Find by label and value\r\n let opt = this.selectElement.querySelector('option[value=\"' + value + '\"]');\r\n if (!opt) {\r\n opt = Array.from(this.selectElement.querySelectorAll(\"option\")).find((el) => el.textContent == text);\r\n }\r\n if (checkSelected) {\r\n if (opt && opt.getAttribute(\"selected\")) {\r\n return false;\r\n }\r\n }\r\n\r\n // create span\r\n let html = text;\r\n let span = document.createElement(\"span\");\r\n let badgeStyle = this.badgeStyle;\r\n span.classList.add(\"badge\");\r\n if (opt && opt.dataset.badgeStyle) {\r\n badgeStyle = opt.dataset.badgeStyle;\r\n }\r\n if (opt && opt.dataset.badgeClass) {\r\n span.classList.add(opt.dataset.badgeClass);\r\n }\r\n if (bver === 5) {\r\n //https://getbootstrap.com/docs/5.1/components/badge/\r\n span.classList.add(\"bg-\" + badgeStyle);\r\n span.classList.add(\"me-2\");\r\n } else {\r\n // https://getbootstrap.com/docs/4.6/components/badge/\r\n span.classList.add(\"badge-\" + badgeStyle);\r\n span.classList.add(\"mr-2\");\r\n }\r\n span.setAttribute(VALUE_ATTRIBUTE, value);\r\n\r\n if (this.allowClear) {\r\n const btn =\r\n bver === 5\r\n ? ''\r\n : '';\r\n html = btn + html;\r\n }\r\n\r\n span.innerHTML = html;\r\n this.containerElement.insertBefore(span, this.searchInput);\r\n\r\n if (this.allowClear) {\r\n span.querySelector(\"button\").addEventListener(\"click\", (event) => {\r\n event.preventDefault();\r\n event.stopPropagation();\r\n this.removeItem(value);\r\n document.activeElement.blur();\r\n });\r\n }\r\n\r\n // update select\r\n if (opt) {\r\n opt.setAttribute(\"selected\", \"selected\");\r\n } else {\r\n // we need to create a new option\r\n opt = document.createElement(\"option\");\r\n opt.value = value;\r\n opt.innerText = text;\r\n opt.setAttribute(\"selected\", \"selected\");\r\n this.selectElement.appendChild(opt);\r\n }\r\n\r\n return true;\r\n }\r\n\r\n /**\r\n * @param {string} value\r\n */\r\n removeItem(value) {\r\n let item = this.containerElement.querySelector(\"span[\" + VALUE_ATTRIBUTE + '=\"' + value + '\"]');\r\n if (!item) {\r\n return;\r\n }\r\n item.remove();\r\n\r\n // update select\r\n let opt = this.selectElement.querySelector('option[value=\"' + value + '\"]');\r\n if (opt) {\r\n opt.removeAttribute(\"selected\");\r\n }\r\n }\r\n}\r\n\r\nexport default Tags;\r\n"], - "mappings": "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", + "sourcesContent": ["/**\r\n * Bootstrap 5 (and 4!) tags\r\n *\r\n * Turns your select[multiple] into nice tags lists\r\n *\r\n * Required Bootstrap 5 styles:\r\n * - badge\r\n * - background-color utility\r\n * - margin-end utility\r\n * - forms\r\n * - dropdown\r\n */\r\n\r\nconst ACTIVE_CLASS = \"is-active\";\r\nconst ACTIVE_CLASSES = [\"is-active\", \"bg-primary\", \"text-white\"];\r\nconst VALUE_ATTRIBUTE = \"data-value\";\r\n\r\nclass Tags {\r\n /**\r\n * @param {HTMLSelectElement} selectElement\r\n * @param {Object} opts\r\n */\r\n constructor(selectElement, opts = {}) {\r\n this.selectElement = selectElement;\r\n this.selectElement.style.display = \"none\";\r\n this.placeholder = this.getPlaceholder();\r\n this.allowNew = selectElement.dataset.allowNew ? true : false;\r\n this.showAllSuggestions = selectElement.dataset.showAllSuggestions ? true : false;\r\n this.badgeStyle = selectElement.dataset.badgeStyle || \"primary\";\r\n this.allowClear = selectElement.dataset.allowClear ? true : false;\r\n this.server = selectElement.dataset.server || false;\r\n this.liveServer = selectElement.dataset.liveServer ? true : false;\r\n this.suggestionsThreshold = selectElement.dataset.suggestionsThreshold ? parseInt(selectElement.dataset.suggestionsThreshold) : 1;\r\n this.keyboardNavigation = false;\r\n this.clearLabel = opts.clearLabel || \"Clear\";\r\n this.searchLabel = opts.searchLabel || \"Type a value\";\r\n\r\n // Create elements\r\n this.holderElement = document.createElement(\"div\"); // this is the one holding the fake input and the dropmenu\r\n this.containerElement = document.createElement(\"div\"); // this is the one for the fake input (labels + input)\r\n this.dropElement = document.createElement(\"ul\");\r\n this.searchInput = document.createElement(\"input\");\r\n\r\n this.holderElement.appendChild(this.containerElement);\r\n this.containerElement.appendChild(this.searchInput);\r\n this.holderElement.appendChild(this.dropElement);\r\n // insert after\r\n this.selectElement.parentNode.insertBefore(this.holderElement, this.selectElement.nextSibling);\r\n\r\n // Configure them\r\n this.configureSearchInput();\r\n this.configureHolderElement();\r\n this.configureDropElement();\r\n this.configureContainerElement();\r\n\r\n if (this.server && !this.liveServer) {\r\n this.loadFromServer();\r\n } else {\r\n let suggestions = Array.from(this.selectElement.querySelectorAll(\"option\")).map((option) => {\r\n return {\r\n value: option.getAttribute(\"value\"),\r\n label: option.innerText,\r\n };\r\n });\r\n this.buildSuggestions(suggestions);\r\n }\r\n }\r\n\r\n /**\r\n * Attach to all elements matched by the selector\r\n * @param {string} selector\r\n * @param {Object} opts\r\n */\r\n static init(selector = \"select[multiple]\", opts = {}) {\r\n let list = document.querySelectorAll(selector);\r\n for (let i = 0; i < list.length; i++) {\r\n let el = list[i];\r\n let inst = new Tags(el, opts);\r\n }\r\n }\r\n\r\n /**\r\n * @param {boolean} show\r\n */\r\n loadFromServer(show = false) {\r\n if (this.abortController) {\r\n this.abortController.abort();\r\n }\r\n this.abortController = new AbortController();\r\n fetch(this.server + \"?query=\" + encodeURIComponent(this.searchInput.value), { signal: this.abortController.signal })\r\n .then((r) => r.json())\r\n .then((suggestions) => {\r\n let data = suggestions.data || suggestions;\r\n this.buildSuggestions(data);\r\n this.abortController = null;\r\n if (show) {\r\n this.showSuggestions();\r\n }\r\n })\r\n .catch((e) => {\r\n if (e.name === \"AbortError\") {\r\n return;\r\n }\r\n console.error(e);\r\n });\r\n }\r\n\r\n /**\r\n * @returns {string}\r\n */\r\n getPlaceholder() {\r\n let firstOption = this.selectElement.querySelector(\"option\");\r\n if (!firstOption) {\r\n return;\r\n }\r\n if (!firstOption.value) {\r\n let placeholder = firstOption.innerText;\r\n firstOption.remove();\r\n return placeholder;\r\n }\r\n if (this.selectElement.getAttribute(\"placeholder\")) {\r\n return this.selectElement.getAttribute(\"placeholder\");\r\n }\r\n if (this.selectElement.getAttribute(\"data-placeholder\")) {\r\n return this.selectElement.getAttribute(\"data-placeholder\");\r\n }\r\n return \"\";\r\n }\r\n\r\n configureDropElement() {\r\n this.dropElement.classList.add(\"dropdown-menu\");\r\n this.dropElement.classList.add(\"p-0\");\r\n this.dropElement.style.maxHeight = \"280px\";\r\n this.dropElement.style.overflowY = \"auto\";\r\n\r\n // If the mouse was outside, entering remove keyboard nav mode\r\n this.dropElement.addEventListener(\"mouseenter\", (event) => {\r\n this.keyboardNavigation = false;\r\n });\r\n }\r\n\r\n configureHolderElement() {\r\n this.holderElement.classList.add(\"form-control\");\r\n this.holderElement.classList.add(\"dropdown\");\r\n if (this.getBootstrapVersion() === 4) {\r\n // Prevent fixed height due to form-control\r\n this.holderElement.style.height = \"auto\";\r\n }\r\n }\r\n\r\n configureContainerElement() {\r\n this.containerElement.addEventListener(\"click\", (event) => {\r\n this.searchInput.focus();\r\n });\r\n\r\n // add initial values\r\n let initialValues = this.selectElement.querySelectorAll(\"option[selected]\");\r\n for (let j = 0; j < initialValues.length; j++) {\r\n let initialValue = initialValues[j];\r\n if (!initialValue.value) {\r\n continue;\r\n }\r\n this.addItem(initialValue.innerText, initialValue.value);\r\n }\r\n }\r\n\r\n configureSearchInput() {\r\n let self = this;\r\n this.searchInput.type = \"text\";\r\n this.searchInput.autocomplete = \"off\";\r\n this.searchInput.style.border = 0;\r\n this.searchInput.style.outline = 0;\r\n this.searchInput.style.maxWidth = \"100%\";\r\n this.searchInput.ariaLabel = this.searchLabel;\r\n\r\n this.adjustWidth();\r\n\r\n this.searchInput.addEventListener(\"input\", (event) => {\r\n this.adjustWidth();\r\n if (this.searchInput.value.length >= this.suggestionsThreshold) {\r\n if (this.liveServer) {\r\n this.loadFromServer(true);\r\n } else {\r\n this.showSuggestions();\r\n }\r\n } else {\r\n this.hideSuggestions();\r\n }\r\n });\r\n this.searchInput.addEventListener(\"focus\", (event) => {\r\n if (this.searchInput.value.length >= this.suggestionsThreshold) {\r\n this.showSuggestions();\r\n }\r\n });\r\n this.searchInput.addEventListener(\"focusout\", (event) => {\r\n self.hideSuggestions();\r\n });\r\n // keypress doesn't send arrow keys\r\n this.searchInput.addEventListener(\"keydown\", (event) => {\r\n // Keycode reference : https://css-tricks.com/snippets/javascript/javascript-keycodes/\r\n let key = event.keyCode || event.key;\r\n switch (key) {\r\n case 13:\r\n case \"Enter\":\r\n let selection = this.getActiveSelection();\r\n if (selection) {\r\n selection.click();\r\n } else {\r\n // We use what is typed\r\n if (this.allowNew && !this.isSelected(this.searchInput.value)) {\r\n let res = this.addItem(this.searchInput.value, null);\r\n if (res) {\r\n this.resetSearchInput();\r\n this.hideSuggestions();\r\n }\r\n }\r\n }\r\n event.preventDefault();\r\n break;\r\n case 38:\r\n case \"ArrowUp\":\r\n event.preventDefault();\r\n this.keyboardNavigation = true;\r\n let newSelection = this.moveSelectionUp();\r\n // If we use arrow up without input and there is no new selection, hide suggestions\r\n if (this.searchInput.value.length == 0 && this.dropElement.classList.contains(\"show\") && !newSelection) {\r\n this.hideSuggestions();\r\n }\r\n break;\r\n case 40:\r\n case \"ArrowDown\":\r\n event.preventDefault();\r\n this.keyboardNavigation = true;\r\n this.moveSelectionDown();\r\n // If we use arrow down without input, show suggestions\r\n if (this.searchInput.value.length == 0 && !this.dropElement.classList.contains(\"show\")) {\r\n this.showSuggestions();\r\n }\r\n break;\r\n case 8:\r\n case \"Backspace\":\r\n if (this.searchInput.value.length == 0) {\r\n this.removeLastItem();\r\n this.adjustWidth();\r\n this.hideSuggestions();\r\n }\r\n break;\r\n }\r\n });\r\n }\r\n\r\n /**\r\n * @returns {HTMLElement}\r\n */\r\n moveSelectionUp() {\r\n let active = this.getActiveSelection();\r\n if (active) {\r\n let prev = active.parentNode;\r\n do {\r\n prev = prev.previousSibling;\r\n } while (prev && prev.style.display == \"none\");\r\n if (!prev) {\r\n return null;\r\n }\r\n active.classList.remove(...ACTIVE_CLASSES);\r\n prev.querySelector(\"a\").classList.add(...ACTIVE_CLASSES);\r\n // Don't use scrollIntoView as it scrolls the whole window\r\n prev.parentNode.scrollTop = prev.offsetTop - prev.parentNode.offsetTop;\r\n return prev;\r\n }\r\n return null;\r\n }\r\n\r\n /**\r\n * @returns {HTMLElement}\r\n */\r\n moveSelectionDown() {\r\n let active = this.getActiveSelection();\r\n if (active) {\r\n let next = active.parentNode;\r\n do {\r\n next = next.nextSibling;\r\n } while (next && next.style.display == \"none\");\r\n if (!next) {\r\n return null;\r\n }\r\n active.classList.remove(...ACTIVE_CLASSES);\r\n next.querySelector(\"a\").classList.add(...ACTIVE_CLASSES);\r\n // This is the equivalent of scrollIntoView(false) but only for parent node\r\n if (next.offsetTop > next.parentNode.offsetHeight - next.offsetHeight) {\r\n next.parentNode.scrollTop += next.offsetHeight;\r\n }\r\n return next;\r\n }\r\n return null;\r\n }\r\n\r\n /**\r\n * Adjust the field to fit its content\r\n */\r\n adjustWidth() {\r\n if (this.searchInput.value) {\r\n this.searchInput.size = this.searchInput.value.length + 1;\r\n } else {\r\n // Show the placeholder only if empty\r\n if (this.getSelectedValues().length) {\r\n this.searchInput.placeholder = \"\";\r\n this.searchInput.size = 1;\r\n } else {\r\n this.searchInput.size = this.placeholder.length;\r\n this.searchInput.placeholder = this.placeholder;\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Add suggestions to the drop element\r\n * @param {array}\r\n */\r\n buildSuggestions(suggestions = null) {\r\n while (this.dropElement.lastChild) {\r\n this.dropElement.removeChild(this.dropElement.lastChild);\r\n }\r\n for (let i = 0; i < suggestions.length; i++) {\r\n let suggestion = suggestions[i];\r\n if (!suggestion.value) {\r\n continue;\r\n }\r\n let newChild = document.createElement(\"li\");\r\n let newChildLink = document.createElement(\"a\");\r\n newChild.append(newChildLink);\r\n newChildLink.classList.add(\"dropdown-item\");\r\n newChildLink.setAttribute(VALUE_ATTRIBUTE, suggestion.value);\r\n newChildLink.setAttribute(\"href\", \"#\");\r\n newChildLink.innerText = suggestion.label;\r\n if (suggestion.data) {\r\n for (const [key, value] of Object.entries(suggestion.data)) {\r\n newChildLink.dataset[key] = value;\r\n }\r\n }\r\n this.dropElement.appendChild(newChild);\r\n\r\n // Hover sets active item\r\n newChildLink.addEventListener(\"mouseenter\", (event) => {\r\n // Don't trigger enter if using arrows\r\n if (this.keyboardNavigation) {\r\n return;\r\n }\r\n this.removeActiveSelection();\r\n newChild.querySelector(\"a\").classList.add(...ACTIVE_CLASSES);\r\n });\r\n // Moving the mouse means no longer using keyboard\r\n newChildLink.addEventListener(\"mousemove\", (event) => {\r\n this.keyboardNavigation = false;\r\n });\r\n\r\n newChildLink.addEventListener(\"mousedown\", (event) => {\r\n // Otherwise searchInput would lose focus and close the menu\r\n event.preventDefault();\r\n });\r\n newChildLink.addEventListener(\"click\", (event) => {\r\n event.preventDefault();\r\n this.addItem(newChildLink.innerText, newChildLink.getAttribute(VALUE_ATTRIBUTE), newChildLink.dataset);\r\n this.resetSearchInput();\r\n this.hideSuggestions();\r\n });\r\n }\r\n }\r\n\r\n resetSearchInput() {\r\n this.searchInput.value = \"\";\r\n this.adjustWidth();\r\n }\r\n\r\n /**\r\n * @returns {array}\r\n */\r\n getSelectedValues() {\r\n let selected = this.selectElement.querySelectorAll(\"option:checked\");\r\n return Array.from(selected).map((el) => el.value);\r\n }\r\n\r\n /**\r\n * The element create with buildSuggestions\r\n */\r\n showSuggestions() {\r\n if (!this.dropElement.classList.contains(\"show\")) {\r\n this.dropElement.classList.add(\"show\");\r\n }\r\n\r\n // Position next to search input\r\n this.dropElement.style.left = this.searchInput.offsetLeft + \"px\";\r\n\r\n // Get search value\r\n let search = this.searchInput.value.toLocaleLowerCase();\r\n\r\n // Get current values\r\n let values = this.getSelectedValues();\r\n\r\n // Filter the list according to search string\r\n let list = this.dropElement.querySelectorAll(\"li\");\r\n let found = false;\r\n let firstItem = null;\r\n let hasPossibleValues = false;\r\n for (let i = 0; i < list.length; i++) {\r\n let item = list[i];\r\n let text = item.innerText.toLocaleLowerCase();\r\n let link = item.querySelector(\"a\");\r\n\r\n // Remove previous selection\r\n link.classList.remove(...ACTIVE_CLASSES);\r\n\r\n // Hide selected values\r\n if (values.indexOf(link.getAttribute(VALUE_ATTRIBUTE)) != -1) {\r\n item.style.display = \"none\";\r\n continue;\r\n }\r\n\r\n hasPossibleValues = true;\r\n\r\n // Check search length since we can trigger dropdown with arrow\r\n let isMatched = search.length === 0 || text.indexOf(search) !== -1;\r\n if (this.showAllSuggestions || this.suggestionsThreshold === 0 || isMatched) {\r\n item.style.display = \"list-item\";\r\n found = true;\r\n if (!firstItem && isMatched) {\r\n firstItem = item;\r\n }\r\n } else {\r\n item.style.display = \"none\";\r\n }\r\n }\r\n\r\n // Special case if nothing matches\r\n if (!found) {\r\n this.dropElement.classList.remove(\"show\");\r\n }\r\n\r\n // Always select first item\r\n if (firstItem) {\r\n if (this.holderElement.classList.contains(\"is-invalid\")) {\r\n this.holderElement.classList.remove(\"is-invalid\");\r\n }\r\n firstItem.querySelector(\"a\").classList.add(...ACTIVE_CLASSES);\r\n firstItem.parentNode.scrollTop = firstItem.offsetTop - firstItem.parentNode.offsetTop;\r\n } else {\r\n // No item and we don't allow new items => error\r\n if (!this.allowNew && !(search.length === 0 && !hasPossibleValues)) {\r\n this.holderElement.classList.add(\"is-invalid\");\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * The element create with buildSuggestions\r\n */\r\n hideSuggestions() {\r\n if (this.dropElement.classList.contains(\"show\")) {\r\n this.dropElement.classList.remove(\"show\");\r\n }\r\n if (this.holderElement.classList.contains(\"is-invalid\")) {\r\n this.holderElement.classList.remove(\"is-invalid\");\r\n }\r\n }\r\n\r\n /**\r\n * @returns {HTMLElement}\r\n */\r\n getActiveSelection() {\r\n return this.dropElement.querySelector(\"a.\" + ACTIVE_CLASS);\r\n }\r\n\r\n removeActiveSelection() {\r\n let selection = this.getActiveSelection();\r\n if (selection) {\r\n selection.classList.remove(...ACTIVE_CLASSES);\r\n }\r\n }\r\n\r\n removeLastItem() {\r\n let items = this.containerElement.querySelectorAll(\"span\");\r\n if (!items.length) {\r\n return;\r\n }\r\n let lastItem = items[items.length - 1];\r\n this.removeItem(lastItem.getAttribute(VALUE_ATTRIBUTE));\r\n }\r\n\r\n /**\r\n * @returns {Number}\r\n */\r\n getBootstrapVersion() {\r\n let ver = 5;\r\n // If we have jQuery and the tooltip plugin for BS4\r\n if (window.jQuery && $.fn.tooltip != undefined && $.fn.tooltip.Constructor != undefined) {\r\n ver = parseInt($.fn.tooltip.Constructor.VERSION.charAt(0));\r\n }\r\n return ver;\r\n }\r\n\r\n /**\r\n * Find if label is already selected\r\n * @param {string} text\r\n * @returns {boolean}\r\n */\r\n isSelected(text) {\r\n const opt = Array.from(this.selectElement.querySelectorAll(\"option\")).find((el) => el.textContent == text);\r\n if (opt && opt.getAttribute(\"selected\")) {\r\n return true;\r\n }\r\n return false;\r\n }\r\n\r\n /**\r\n * @param {string} text\r\n * @param {string} value\r\n * @param {object} data\r\n * @return {boolean}\r\n */\r\n addItem(text, value = null, data = {}) {\r\n if (!value) {\r\n value = text;\r\n }\r\n\r\n const bver = this.getBootstrapVersion();\r\n let opt = this.selectElement.querySelector('option[value=\"' + value + '\"]');\r\n if (opt) {\r\n data = opt.dataset;\r\n }\r\n\r\n // create span\r\n let html = text;\r\n let span = document.createElement(\"span\");\r\n let badgeStyle = this.badgeStyle;\r\n span.classList.add(\"badge\");\r\n if (data.badgeStyle) {\r\n badgeStyle = data.badgeStyle;\r\n }\r\n if (data.badgeClass) {\r\n span.classList.add(data.badgeClass);\r\n }\r\n if (bver === 5) {\r\n //https://getbootstrap.com/docs/5.1/components/badge/\r\n span.classList.add(\"bg-\" + badgeStyle);\r\n span.classList.add(\"me-2\");\r\n } else {\r\n // https://getbootstrap.com/docs/4.6/components/badge/\r\n span.classList.add(\"badge-\" + badgeStyle);\r\n span.classList.add(\"mr-2\");\r\n }\r\n span.setAttribute(VALUE_ATTRIBUTE, value);\r\n\r\n if (this.allowClear) {\r\n const btn =\r\n bver === 5\r\n ? ''\r\n : '';\r\n html = btn + html;\r\n }\r\n\r\n span.innerHTML = html;\r\n this.containerElement.insertBefore(span, this.searchInput);\r\n\r\n if (this.allowClear) {\r\n span.querySelector(\"button\").addEventListener(\"click\", (event) => {\r\n event.preventDefault();\r\n event.stopPropagation();\r\n this.removeItem(value);\r\n document.activeElement.blur();\r\n });\r\n }\r\n\r\n // update select\r\n if (opt) {\r\n opt.setAttribute(\"selected\", \"selected\");\r\n } else {\r\n // we need to create a new option\r\n opt = document.createElement(\"option\");\r\n opt.value = value;\r\n opt.innerText = text;\r\n // Pass along data provided\r\n for (const [key, value] of Object.entries(data)) {\r\n opt.dataset[key] = value;\r\n }\r\n opt.setAttribute(\"selected\", \"selected\");\r\n this.selectElement.appendChild(opt);\r\n }\r\n\r\n return true;\r\n }\r\n\r\n /**\r\n * @param {string} value\r\n */\r\n removeItem(value) {\r\n let item = this.containerElement.querySelector(\"span[\" + VALUE_ATTRIBUTE + '=\"' + value + '\"]');\r\n if (!item) {\r\n return;\r\n }\r\n item.remove();\r\n\r\n // update select\r\n let opt = this.selectElement.querySelector('option[value=\"' + value + '\"]');\r\n if (opt) {\r\n opt.removeAttribute(\"selected\");\r\n }\r\n }\r\n}\r\n\r\nexport default Tags;\r\n"], + "mappings": "AAaA,GAAM,GAAe,YACf,EAAiB,CAAC,YAAa,aAAc,cAC7C,EAAkB,aAExB,OAAW,CAKT,YAAY,EAAe,EAAO,GAAI,CAiCpC,GAhCA,KAAK,cAAgB,EACrB,KAAK,cAAc,MAAM,QAAU,OACnC,KAAK,YAAc,KAAK,iBACxB,KAAK,SAAW,IAAc,QAAQ,SACtC,KAAK,mBAAqB,IAAc,QAAQ,mBAChD,KAAK,WAAa,EAAc,QAAQ,YAAc,UACtD,KAAK,WAAa,IAAc,QAAQ,WACxC,KAAK,OAAS,EAAc,QAAQ,QAAU,GAC9C,KAAK,WAAa,IAAc,QAAQ,WACxC,KAAK,qBAAuB,EAAc,QAAQ,qBAAuB,SAAS,EAAc,QAAQ,sBAAwB,EAChI,KAAK,mBAAqB,GAC1B,KAAK,WAAa,EAAK,YAAc,QACrC,KAAK,YAAc,EAAK,aAAe,eAGvC,KAAK,cAAgB,SAAS,cAAc,OAC5C,KAAK,iBAAmB,SAAS,cAAc,OAC/C,KAAK,YAAc,SAAS,cAAc,MAC1C,KAAK,YAAc,SAAS,cAAc,SAE1C,KAAK,cAAc,YAAY,KAAK,kBACpC,KAAK,iBAAiB,YAAY,KAAK,aACvC,KAAK,cAAc,YAAY,KAAK,aAEpC,KAAK,cAAc,WAAW,aAAa,KAAK,cAAe,KAAK,cAAc,aAGlF,KAAK,uBACL,KAAK,yBACL,KAAK,uBACL,KAAK,4BAED,KAAK,QAAU,CAAC,KAAK,WACvB,KAAK,qBACA,CACL,GAAI,GAAc,MAAM,KAAK,KAAK,cAAc,iBAAiB,WAAW,IAAI,AAAC,GACxE,EACL,MAAO,EAAO,aAAa,SAC3B,MAAO,EAAO,aAGlB,KAAK,iBAAiB,UASnB,MAAK,EAAW,mBAAoB,EAAO,GAAI,CACpD,GAAI,GAAO,SAAS,iBAAiB,GACrC,OAAS,GAAI,EAAG,EAAI,EAAK,OAAQ,IAAK,CACpC,GAAI,GAAK,EAAK,GACV,EAAO,GAAI,GAAK,EAAI,IAO5B,eAAe,EAAO,GAAO,CAC3B,AAAI,KAAK,iBACP,KAAK,gBAAgB,QAEvB,KAAK,gBAAkB,GAAI,iBAC3B,MAAM,KAAK,OAAS,UAAY,mBAAmB,KAAK,YAAY,OAAQ,CAAE,OAAQ,KAAK,gBAAgB,SACxG,KAAK,AAAC,GAAM,EAAE,QACd,KAAK,AAAC,GAAgB,CACrB,GAAI,GAAO,EAAY,MAAQ,EAC/B,KAAK,iBAAiB,GACtB,KAAK,gBAAkB,KACnB,GACF,KAAK,oBAGR,MAAM,AAAC,GAAM,CACZ,AAAI,EAAE,OAAS,cAGf,QAAQ,MAAM,KAOpB,gBAAiB,CACf,GAAI,GAAc,KAAK,cAAc,cAAc,UACnD,GAAI,EAAC,EAGL,IAAI,CAAC,EAAY,MAAO,CACtB,GAAI,GAAc,EAAY,UAC9B,SAAY,SACL,EAET,MAAI,MAAK,cAAc,aAAa,eAC3B,KAAK,cAAc,aAAa,eAErC,KAAK,cAAc,aAAa,oBAC3B,KAAK,cAAc,aAAa,oBAElC,IAGT,sBAAuB,CACrB,KAAK,YAAY,UAAU,IAAI,iBAC/B,KAAK,YAAY,UAAU,IAAI,OAC/B,KAAK,YAAY,MAAM,UAAY,QACnC,KAAK,YAAY,MAAM,UAAY,OAGnC,KAAK,YAAY,iBAAiB,aAAc,AAAC,GAAU,CACzD,KAAK,mBAAqB,KAI9B,wBAAyB,CACvB,KAAK,cAAc,UAAU,IAAI,gBACjC,KAAK,cAAc,UAAU,IAAI,YAC7B,KAAK,wBAA0B,GAEjC,MAAK,cAAc,MAAM,OAAS,QAItC,2BAA4B,CAC1B,KAAK,iBAAiB,iBAAiB,QAAS,AAAC,GAAU,CACzD,KAAK,YAAY,UAInB,GAAI,GAAgB,KAAK,cAAc,iBAAiB,oBACxD,OAAS,GAAI,EAAG,EAAI,EAAc,OAAQ,IAAK,CAC7C,GAAI,GAAe,EAAc,GACjC,AAAI,CAAC,EAAa,OAGlB,KAAK,QAAQ,EAAa,UAAW,EAAa,QAItD,sBAAuB,CACrB,GAAI,GAAO,KACX,KAAK,YAAY,KAAO,OACxB,KAAK,YAAY,aAAe,MAChC,KAAK,YAAY,MAAM,OAAS,EAChC,KAAK,YAAY,MAAM,QAAU,EACjC,KAAK,YAAY,MAAM,SAAW,OAClC,KAAK,YAAY,UAAY,KAAK,YAElC,KAAK,cAEL,KAAK,YAAY,iBAAiB,QAAS,AAAC,GAAU,CACpD,KAAK,cACL,AAAI,KAAK,YAAY,MAAM,QAAU,KAAK,qBACxC,AAAI,KAAK,WACP,KAAK,eAAe,IAEpB,KAAK,kBAGP,KAAK,oBAGT,KAAK,YAAY,iBAAiB,QAAS,AAAC,GAAU,CACpD,AAAI,KAAK,YAAY,MAAM,QAAU,KAAK,sBACxC,KAAK,oBAGT,KAAK,YAAY,iBAAiB,WAAY,AAAC,GAAU,CACvD,EAAK,oBAGP,KAAK,YAAY,iBAAiB,UAAW,AAAC,GAAU,CAGtD,OADU,EAAM,SAAW,EAAM,SAE1B,QACA,QACH,GAAI,GAAY,KAAK,qBACrB,AAAI,EACF,EAAU,QAGN,KAAK,UAAY,CAAC,KAAK,WAAW,KAAK,YAAY,QAC3C,KAAK,QAAQ,KAAK,YAAY,MAAO,OAE7C,MAAK,mBACL,KAAK,mBAIX,EAAM,iBACN,UACG,QACA,UACH,EAAM,iBACN,KAAK,mBAAqB,GAC1B,GAAI,GAAe,KAAK,kBAExB,AAAI,KAAK,YAAY,MAAM,QAAU,GAAK,KAAK,YAAY,UAAU,SAAS,SAAW,CAAC,GACxF,KAAK,kBAEP,UACG,QACA,YACH,EAAM,iBACN,KAAK,mBAAqB,GAC1B,KAAK,oBAED,KAAK,YAAY,MAAM,QAAU,GAAK,CAAC,KAAK,YAAY,UAAU,SAAS,SAC7E,KAAK,kBAEP,UACG,OACA,YACH,AAAI,KAAK,YAAY,MAAM,QAAU,GACnC,MAAK,iBACL,KAAK,cACL,KAAK,mBAEP,SAQR,iBAAkB,CAChB,GAAI,GAAS,KAAK,qBAClB,GAAI,EAAQ,CACV,GAAI,GAAO,EAAO,WAClB,EACE,GAAO,EAAK,sBACL,GAAQ,EAAK,MAAM,SAAW,QACvC,MAAK,GAGL,GAAO,UAAU,OAAO,GAAG,GAC3B,EAAK,cAAc,KAAK,UAAU,IAAI,GAAG,GAEzC,EAAK,WAAW,UAAY,EAAK,UAAY,EAAK,WAAW,UACtD,GANE,KAQX,MAAO,MAMT,mBAAoB,CAClB,GAAI,GAAS,KAAK,qBAClB,GAAI,EAAQ,CACV,GAAI,GAAO,EAAO,WAClB,EACE,GAAO,EAAK,kBACL,GAAQ,EAAK,MAAM,SAAW,QACvC,MAAK,GAGL,GAAO,UAAU,OAAO,GAAG,GAC3B,EAAK,cAAc,KAAK,UAAU,IAAI,GAAG,GAErC,EAAK,UAAY,EAAK,WAAW,aAAe,EAAK,cACvD,GAAK,WAAW,WAAa,EAAK,cAE7B,GARE,KAUX,MAAO,MAMT,aAAc,CACZ,AAAI,KAAK,YAAY,MACnB,KAAK,YAAY,KAAO,KAAK,YAAY,MAAM,OAAS,EAGxD,AAAI,KAAK,oBAAoB,OAC3B,MAAK,YAAY,YAAc,GAC/B,KAAK,YAAY,KAAO,GAExB,MAAK,YAAY,KAAO,KAAK,YAAY,OACzC,KAAK,YAAY,YAAc,KAAK,aAS1C,iBAAiB,EAAc,KAAM,CACnC,KAAO,KAAK,YAAY,WACtB,KAAK,YAAY,YAAY,KAAK,YAAY,WAEhD,OAAS,GAAI,EAAG,EAAI,EAAY,OAAQ,IAAK,CAC3C,GAAI,GAAa,EAAY,GAC7B,GAAI,CAAC,EAAW,MACd,SAEF,GAAI,GAAW,SAAS,cAAc,MAClC,EAAe,SAAS,cAAc,KAM1C,GALA,EAAS,OAAO,GAChB,EAAa,UAAU,IAAI,iBAC3B,EAAa,aAAa,EAAiB,EAAW,OACtD,EAAa,aAAa,OAAQ,KAClC,EAAa,UAAY,EAAW,MAChC,EAAW,KACb,OAAW,CAAC,EAAK,IAAU,QAAO,QAAQ,EAAW,MACnD,EAAa,QAAQ,GAAO,EAGhC,KAAK,YAAY,YAAY,GAG7B,EAAa,iBAAiB,aAAc,AAAC,GAAU,CAErD,AAAI,KAAK,oBAGT,MAAK,wBACL,EAAS,cAAc,KAAK,UAAU,IAAI,GAAG,MAG/C,EAAa,iBAAiB,YAAa,AAAC,GAAU,CACpD,KAAK,mBAAqB,KAG5B,EAAa,iBAAiB,YAAa,AAAC,GAAU,CAEpD,EAAM,mBAER,EAAa,iBAAiB,QAAS,AAAC,GAAU,CAChD,EAAM,iBACN,KAAK,QAAQ,EAAa,UAAW,EAAa,aAAa,GAAkB,EAAa,SAC9F,KAAK,mBACL,KAAK,qBAKX,kBAAmB,CACjB,KAAK,YAAY,MAAQ,GACzB,KAAK,cAMP,mBAAoB,CAClB,GAAI,GAAW,KAAK,cAAc,iBAAiB,kBACnD,MAAO,OAAM,KAAK,GAAU,IAAI,AAAC,GAAO,EAAG,OAM7C,iBAAkB,CAChB,AAAK,KAAK,YAAY,UAAU,SAAS,SACvC,KAAK,YAAY,UAAU,IAAI,QAIjC,KAAK,YAAY,MAAM,KAAO,KAAK,YAAY,WAAa,KAG5D,GAAI,GAAS,KAAK,YAAY,MAAM,oBAGhC,EAAS,KAAK,oBAGd,EAAO,KAAK,YAAY,iBAAiB,MACzC,EAAQ,GACR,EAAY,KACZ,EAAoB,GACxB,OAAS,GAAI,EAAG,EAAI,EAAK,OAAQ,IAAK,CACpC,GAAI,GAAO,EAAK,GACZ,EAAO,EAAK,UAAU,oBACtB,EAAO,EAAK,cAAc,KAM9B,GAHA,EAAK,UAAU,OAAO,GAAG,GAGrB,EAAO,QAAQ,EAAK,aAAa,KAAqB,GAAI,CAC5D,EAAK,MAAM,QAAU,OACrB,SAGF,EAAoB,GAGpB,GAAI,GAAY,EAAO,SAAW,GAAK,EAAK,QAAQ,KAAY,GAChE,AAAI,KAAK,oBAAsB,KAAK,uBAAyB,GAAK,EAChE,GAAK,MAAM,QAAU,YACrB,EAAQ,GACJ,CAAC,GAAa,GAChB,GAAY,IAGd,EAAK,MAAM,QAAU,OAKzB,AAAK,GACH,KAAK,YAAY,UAAU,OAAO,QAIpC,AAAI,EACE,MAAK,cAAc,UAAU,SAAS,eACxC,KAAK,cAAc,UAAU,OAAO,cAEtC,EAAU,cAAc,KAAK,UAAU,IAAI,GAAG,GAC9C,EAAU,WAAW,UAAY,EAAU,UAAY,EAAU,WAAW,WAGxE,CAAC,KAAK,UAAY,CAAE,GAAO,SAAW,GAAK,CAAC,IAC9C,KAAK,cAAc,UAAU,IAAI,cAQvC,iBAAkB,CAChB,AAAI,KAAK,YAAY,UAAU,SAAS,SACtC,KAAK,YAAY,UAAU,OAAO,QAEhC,KAAK,cAAc,UAAU,SAAS,eACxC,KAAK,cAAc,UAAU,OAAO,cAOxC,oBAAqB,CACnB,MAAO,MAAK,YAAY,cAAc,KAAO,GAG/C,uBAAwB,CACtB,GAAI,GAAY,KAAK,qBACrB,AAAI,GACF,EAAU,UAAU,OAAO,GAAG,GAIlC,gBAAiB,CACf,GAAI,GAAQ,KAAK,iBAAiB,iBAAiB,QACnD,GAAI,CAAC,EAAM,OACT,OAEF,GAAI,GAAW,EAAM,EAAM,OAAS,GACpC,KAAK,WAAW,EAAS,aAAa,IAMxC,qBAAsB,CACpB,GAAI,GAAM,EAEV,MAAI,QAAO,QAAU,EAAE,GAAG,SAAW,MAAa,EAAE,GAAG,QAAQ,aAAe,MAC5E,GAAM,SAAS,EAAE,GAAG,QAAQ,YAAY,QAAQ,OAAO,KAElD,EAQT,WAAW,EAAM,CACf,GAAM,GAAM,MAAM,KAAK,KAAK,cAAc,iBAAiB,WAAW,KAAK,AAAC,GAAO,EAAG,aAAe,GACrG,MAAI,MAAO,EAAI,aAAa,aAY9B,QAAQ,EAAM,EAAQ,KAAM,EAAO,GAAI,CACrC,AAAK,GACH,GAAQ,GAGV,GAAM,GAAO,KAAK,sBACd,EAAM,KAAK,cAAc,cAAc,iBAAmB,EAAQ,MACtE,AAAI,GACF,GAAO,EAAI,SAIb,GAAI,GAAO,EACP,EAAO,SAAS,cAAc,QAC9B,EAAa,KAAK,WAwCtB,GAvCA,EAAK,UAAU,IAAI,SACf,EAAK,YACP,GAAa,EAAK,YAEhB,EAAK,YACP,EAAK,UAAU,IAAI,EAAK,YAE1B,AAAI,IAAS,EAEX,GAAK,UAAU,IAAI,MAAQ,GAC3B,EAAK,UAAU,IAAI,SAGnB,GAAK,UAAU,IAAI,SAAW,GAC9B,EAAK,UAAU,IAAI,SAErB,EAAK,aAAa,EAAiB,GAE/B,KAAK,YAKP,GAAO,AAHL,KAAS,EACL,qGAAuG,KAAK,WAAa,cACzH,8HAAgI,KAAK,WAAa,sDAC3I,GAGf,EAAK,UAAY,EACjB,KAAK,iBAAiB,aAAa,EAAM,KAAK,aAE1C,KAAK,YACP,EAAK,cAAc,UAAU,iBAAiB,QAAS,AAAC,GAAU,CAChE,EAAM,iBACN,EAAM,kBACN,KAAK,WAAW,GAChB,SAAS,cAAc,SAKvB,EACF,EAAI,aAAa,WAAY,gBACxB,CAEL,EAAM,SAAS,cAAc,UAC7B,EAAI,MAAQ,EACZ,EAAI,UAAY,EAEhB,OAAW,CAAC,EAAK,IAAU,QAAO,QAAQ,GACxC,EAAI,QAAQ,GAAO,EAErB,EAAI,aAAa,WAAY,YAC7B,KAAK,cAAc,YAAY,GAGjC,MAAO,GAMT,WAAW,EAAO,CAChB,GAAI,GAAO,KAAK,iBAAiB,cAAc,QAAU,EAAkB,KAAO,EAAQ,MAC1F,GAAI,CAAC,EACH,OAEF,EAAK,SAGL,GAAI,GAAM,KAAK,cAAc,cAAc,iBAAmB,EAAQ,MACtE,AAAI,GACF,EAAI,gBAAgB,cAKnB,EAAQ", "names": [] }