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;le.json()).then(e=>{let i=e.data||e;this.buildSuggestions(i),this.abortController=null,t&&this.showSuggestions()}).catch(e=>{e.name!=="AbortError"&&console.error(e)})}getPlaceholder(){let t=this.selectElement.querySelector("option");if(!!t){if(!t.value){let e=t.innerText;return t.remove(),e}return this.selectElement.getAttribute("placeholder")?this.selectElement.getAttribute("placeholder"):this.selectElement.getAttribute("data-placeholder")?this.selectElement.getAttribute("data-placeholder"):""}}configureDropElement(){this.dropElement.classList.add("dropdown-menu"),this.dropElement.classList.add("p-0"),this.dropElement.style.maxHeight="280px",this.dropElement.style.overflowY="auto",this.dropElement.addEventListener("mouseenter",t=>{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": "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",
"names": []
}