Improved search select and made it mostly functional.
This commit is contained in:
		
							
								
								
									
										197
									
								
								search-select.js
									
									
									
									
									
								
							
							
						
						
									
										197
									
								
								search-select.js
									
									
									
									
									
								
							| @@ -1,6 +1,6 @@ | |||||||
| import { IgniteHtml } from '../ignite-html/ignite-html.js'; | import { IgniteHtml } from '../ignite-html/ignite-html.js'; | ||||||
| import { IgniteElement } from "../ignite-html/ignite-element.js"; | import { IgniteElement } from "../ignite-html/ignite-element.js"; | ||||||
| import { IgniteTemplate, button, ul, slot, span, div, html, converter } from "../ignite-html/ignite-template.js"; | import { IgniteTemplate, button, ul, slot, span, div, i, html, list, converter } from "../ignite-html/ignite-template.js"; | ||||||
| import { IgniteProperty } from "../ignite-html/ignite-html.js"; | import { IgniteProperty } from "../ignite-html/ignite-html.js"; | ||||||
| import { Popper } from "./popper.js"; | import { Popper } from "./popper.js"; | ||||||
|  |  | ||||||
| @@ -9,49 +9,206 @@ class SearchSelect extends IgniteElement { | |||||||
|         super(); |         super(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     get styles() { |  | ||||||
|         return /*css*/` |  | ||||||
|             mt-search-select>div:empty:before { |  | ||||||
|                 content: attr(data-placeholder); |  | ||||||
|                 opacity: 0.5; |  | ||||||
|             } |  | ||||||
|         `; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     get properties() { |     get properties() { | ||||||
|         return { |         return { | ||||||
|             inputElement: null, |             inputElement: null, | ||||||
|             placeholder: null, |             placeholder: null, | ||||||
|             options: null, |             options: null, | ||||||
|             optionsRenderer: null, |             optionConverter: null, | ||||||
|  |             results: null, | ||||||
|             value: null, |             value: null, | ||||||
|             search: null |             valueConverter: null, | ||||||
|  |             searchSelector: null, | ||||||
|  |             searchCallback: null, | ||||||
|  |             searchDelay: 200, | ||||||
|  |             searchMaxHeight: "15em", | ||||||
|  |             searching: false, | ||||||
|  |             documentListener: null | ||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     render() { |     render() { | ||||||
|         return this.template |         return this.template | ||||||
|             .class("w-100 position-relative form-control form-control-lg d-flex flex-row") |             .class("w-100 position-relative form-control form-control-lg d-flex flex-row justify-content-between") | ||||||
|             .attribute("tabindex", "0") |             .attribute("tabindex", "0") | ||||||
|  |             .onFocus(() => this.onFocus()) | ||||||
|             .child( |             .child( | ||||||
|  |                 //Placeholder | ||||||
|                 new div() |                 new div() | ||||||
|  |                     .hide([this.value, this.searching], (value, searching) => value || searching) | ||||||
|  |                     .style("flex", 1) | ||||||
|  |                     .style("opacity", "0.5") | ||||||
|  |                     .style("overflow", "auto") | ||||||
|  |                     .innerText(this.placeholder), | ||||||
|  |  | ||||||
|  |                 //Search input | ||||||
|  |                 new div() | ||||||
|  |                     .show(this.searching) | ||||||
|                     .style("flex", 1) |                     .style("flex", 1) | ||||||
|                     .style("border", "none") |                     .style("border", "none") | ||||||
|                     .style("outline", "none") |                     .style("outline", "none") | ||||||
|                     .style("overflow", "auto") |                     .style("overflow", "auto") | ||||||
|                     .attribute("contenteditable", true) |                     .attribute("contenteditable", true) | ||||||
|                     .ref(this.inputElement), |                     .ref(this.inputElement) | ||||||
|  |                     .onFocus(() => this.onFocus()) | ||||||
|  |                     .on("keydown", (e) => { | ||||||
|  |                         //If the escape key is pressed stop searching until something else happens. | ||||||
|  |                         if (e.key == "Escape") { | ||||||
|  |                             this.searching = false; | ||||||
|  |                             this.inputElement.blur(); | ||||||
|  |                             this.inputElement.textContent = null; | ||||||
|  |                             e.preventDefault(); | ||||||
|  |                             e.stopPropagation(); | ||||||
|  |  | ||||||
|                 new div().child( |                             return; | ||||||
|                     new converter(this.value, this.optionsRenderer) |                         } | ||||||
|  |  | ||||||
|  |                         //Reset the searching and input if we get a tab, since the browser | ||||||
|  |                         //will focus the next avaiable element. | ||||||
|  |                         if (e.key == "Tab") { | ||||||
|  |                             this.searching = false; | ||||||
|  |                             this.inputElement.blur(); | ||||||
|  |                             this.inputElement.textContent = null; | ||||||
|  |                             return; | ||||||
|  |                         } | ||||||
|  |  | ||||||
|  |                         //If we are not searching and a key was pressed, open the search box. | ||||||
|  |                         if (!this.searching && (e.key !== "Backspace" || (e.key == "Backspace" && e.target.textContent.length > 1)) && this.options && this.options.length > 0) { | ||||||
|  |                             this.searching = true; | ||||||
|  |                         } | ||||||
|  |  | ||||||
|  |                         //If we are searching and we have a on search function invoke it. | ||||||
|  |                         if (this.searching) { | ||||||
|  |                             if (this.searchCallback) { | ||||||
|  |                                 clearTimeout(this.searchCallback); | ||||||
|  |                             } | ||||||
|  |  | ||||||
|  |                             this.searchCallback = setTimeout(() => this.search(e.target.textContent.trim()), this.searchDelay); | ||||||
|  |                         } | ||||||
|  |                     }), | ||||||
|  |  | ||||||
|  |                 //Value | ||||||
|  |                 new div().show(this.value).child( | ||||||
|  |                     new converter(this.value, this.valueConverter) | ||||||
|                 ), |                 ), | ||||||
|  |  | ||||||
|                 new button().class("btn btn-none").child("<i class='fa-solid fa-times' />"), |                 //Clear value button | ||||||
|  |                 new button().show(this.value) | ||||||
|  |                     .class("btn btn-none") | ||||||
|  |                     .child(new i().class("fa-solid fa-times")) | ||||||
|  |                     .onClick(() => { | ||||||
|  |                         this.value = null; | ||||||
|  |  | ||||||
|                 new Popper().class("form-control form-control-lg shadow").property("show", true).child( |                         this.dispatchEvent(new Event("change")); | ||||||
|                     "Test" |  | ||||||
|  |                         this.onFocus(); | ||||||
|  |                     }), | ||||||
|  |  | ||||||
|  |                 //Search results popper | ||||||
|  |                 new Popper() | ||||||
|  |                     .class("form-control form-control-lg shadow d-flex flex-column gap-3 overflow-auto p-3") | ||||||
|  |                     .style("max-height", this.searchMaxHeight) | ||||||
|  |                     .property("show", this.searching) | ||||||
|  |                     .child( | ||||||
|  |                         new span().class("text-muted").innerText(this.results, results => results && results.length > 0 ? `Showing ${results.length} ${results.length == 1 ? "result" : "results"}` : "No results"), | ||||||
|  |  | ||||||
|  |                         new list(this.results, option => | ||||||
|  |                             new div() | ||||||
|  |                                 .class("cursor-pointer") | ||||||
|  |                                 .child( | ||||||
|  |                                     new converter(option, this.optionConverter) | ||||||
|                                 ) |                                 ) | ||||||
|             ); |                                 .onClick(e => { | ||||||
|  |                                     e.preventDefault(); | ||||||
|  |                                     e.stopPropagation(); | ||||||
|  |  | ||||||
|  |                                     this.value = option; | ||||||
|  |  | ||||||
|  |                                     this.searching = false; | ||||||
|  |  | ||||||
|  |                                     this.dispatchEvent(new Event("change")); | ||||||
|  |                                 }) | ||||||
|  |                         ) | ||||||
|  |                     ) | ||||||
|  |             ) | ||||||
|  |             .on("keydown", e => { | ||||||
|  |                 if (!this.searching && e.key == "Backspace" && this.value) { | ||||||
|  |                     this.value = null; | ||||||
|  |  | ||||||
|  |                     this.dispatchEvent(new Event("change")); | ||||||
|  |  | ||||||
|  |                     this.onFocus(); | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     ready() { | ||||||
|  |         //Add a listener to the document click to blur our element. | ||||||
|  |         this.documentListener = (e) => this.onBlur(e); | ||||||
|  |         window.document.addEventListener("click", this.documentListener); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     cleanup() { | ||||||
|  |         window.document.removeEventListener("click", this.documentListener); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     onBlur(e) { | ||||||
|  |         //Only blur if we are editing and the target is not ourself or any of our children. | ||||||
|  |         if (this.searching) { | ||||||
|  |             if (e.target != this && !this.contains(e.target)) { | ||||||
|  |                 this.searching = false; | ||||||
|  |  | ||||||
|  |                 this.inputElement.blur(); | ||||||
|  |  | ||||||
|  |                 this.inputElement.textContent = null; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     onFocus() { | ||||||
|  |         if (!this.searching && !this.value) { | ||||||
|  |             this.searching = true; | ||||||
|  |  | ||||||
|  |             this.inputElement.focus(); | ||||||
|  |  | ||||||
|  |             this.inputElement.textContent = null; | ||||||
|  |  | ||||||
|  |             this.search(this.inputElement.textContent.trim()); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     search(text) { | ||||||
|  |         var results = []; | ||||||
|  |  | ||||||
|  |         results = results.concat(this.options); | ||||||
|  |  | ||||||
|  |         if (text && text != "" && text != " ") { | ||||||
|  |             var regex = new RegExp(text, 'i'); | ||||||
|  |  | ||||||
|  |             results = results.filter(result => { | ||||||
|  |                 if (this.searchSelector) { | ||||||
|  |                     var values = this.searchSelector(result); | ||||||
|  |  | ||||||
|  |                     if (values) { | ||||||
|  |                         if (Array.isArray(values)) { | ||||||
|  |                             for (var i = 0; i < values.length; i++) { | ||||||
|  |                                 if (values[i] && values[i].toString().match(regex)) { | ||||||
|  |                                     return true; | ||||||
|  |                                 } | ||||||
|  |                             } | ||||||
|  |                         } else if (values.toString().match(regex)) { | ||||||
|  |                             return true; | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } else if (result && result.toString().match(regex)) { | ||||||
|  |                     return true; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 return false; | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         this.results = results; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user