181 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			181 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| import { IgniteElement } from "../ignite-html/ignite-element.js";
 | |
| import { IgniteTemplate, list, div, input, button, h4, span } from "../ignite-html/ignite-template.js";
 | |
| import { Chip } from "./chip.js";
 | |
| import { Popper } from "./popper.js";
 | |
| 
 | |
| class ChipList extends IgniteElement {
 | |
|     constructor() {
 | |
|         super();
 | |
|     }
 | |
| 
 | |
|     get styles() {
 | |
|         return `
 | |
|             mt-chip-list {
 | |
|                 display: flex;
 | |
|                 flex-direction: row;
 | |
|                 flex-wrap: wrap;
 | |
|                 gap: 12px;
 | |
|                 border: solid 0.13rem transparent;
 | |
|                 border-radius: 0.3rem;
 | |
|                 padding: 0.2rem;
 | |
|             }
 | |
| 
 | |
|             mt-chip-list:hover {
 | |
|                 cursor: pointer;
 | |
|             }
 | |
| 
 | |
|             mt-chip-list.editing {
 | |
|                 border: solid 0.13rem #ced4da;
 | |
|                 border-radius: 0.3rem;
 | |
|             }
 | |
| 
 | |
|             mt-chip-list > .input-container {
 | |
|                 display: flex;
 | |
|                 flex-direction: column;
 | |
|                 align-items: center;
 | |
|                 justify-content: center;
 | |
|             }
 | |
| 
 | |
|             mt-chip-list > .input-container > .input {
 | |
|                 min-width: 1em;
 | |
|                 outline: none;
 | |
|             }
 | |
| 
 | |
|             mt-chip-list .search-result:hover {
 | |
|                 background-color: #e0e0e0;
 | |
|                 border-radius: 0.3em;
 | |
|             }
 | |
|         `;
 | |
|     }
 | |
| 
 | |
|     get properties() {
 | |
|         return {
 | |
|             items: null,
 | |
|             editing: false,
 | |
|             input: null,
 | |
|             searchBox: null,
 | |
|             searching: false,
 | |
|             searchResults: [
 | |
|                 { id: 502, corporation: "Starbucks", content: "Starbucks #1, Spokane WA" },
 | |
|                 { id: 503, corporation: "Starbucks", content: "Starbucks #2, Spokane WA" }
 | |
|             ],
 | |
|             searchEmpty: "No results found.",
 | |
|             searchFooter: null,
 | |
|             blurTimeout: null,
 | |
|             documentListener: null
 | |
|         };
 | |
|     }
 | |
| 
 | |
|     render() {
 | |
|         return this.template
 | |
|             .style("position", "relative")
 | |
|             .class(this.editing, value => { return value ? "editing" : null })
 | |
|             .child(
 | |
|                 new list(this.items, (item) => {
 | |
|                     return new Chip()
 | |
|                         .id(item.id)
 | |
|                         .property("onDelete", () => { this.items = this.items.filter(needle => needle != item) })
 | |
|                         .child(item.content);
 | |
|                 }),
 | |
|                 new div()
 | |
|                     .class("input-container")
 | |
|                     .child(
 | |
|                         new div()
 | |
|                             .class("input")
 | |
|                             .attribute("contenteditable", "true")
 | |
|                             .hide(this.editing, value => { return !value; })
 | |
|                             .ref(this.input)
 | |
|                             .onEnter((e) => {
 | |
|                                 e.preventDefault();
 | |
|                                 //Add a new item to the chip list.
 | |
|                                 this.items.push({ content: this.input.textContent.trim() });
 | |
|                                 this.input.innerHTML = "";
 | |
|                             })
 | |
|                             .onBackspace((e) => {
 | |
|                                 //If the backspace key is pressed and there is no content, try to remove the last item from the list.
 | |
|                                 if (this.input.textContent.length == 0 || (this.input.textContent.length == 1 && this.input.textContent[0] == " ")) {
 | |
|                                     e.preventDefault();
 | |
|                                     this.items.pop();
 | |
|                                 }
 | |
|                             })
 | |
|                             .on("keydown", () => {
 | |
|                                 //If we are not searching and a key was pressed, open the search box.
 | |
|                                 if (!this.searching) {
 | |
|                                     this.searching = true;
 | |
|                                 }
 | |
|                             })
 | |
|                             .onBlur((e) => {
 | |
|                                 //Force the input to not lose focus if we are editing.
 | |
|                                 if (this.editing) {
 | |
|                                     e.target.focus();
 | |
|                                 }
 | |
|                             })
 | |
|                     ),
 | |
|                 new Popper()
 | |
|                     .property("show", this.searching)
 | |
|                     .child(
 | |
|                         new div()
 | |
|                             .class("shadow d-flex flex-column justify-content-center p-2")
 | |
|                             .style("background-color", "#fff")
 | |
|                             .child(
 | |
|                                 new span(this.searchEmpty).class("mt-2").hide(this.searchResults, value => { return value != null && value.length > 0; }),
 | |
|                                 new list(this.searchResults, item => {
 | |
|                                     return new div(item.content).class("search-result p-2").onClick(() => this.searchResultClick(item));
 | |
|                                 }),
 | |
|                                 `<hr />`,
 | |
|                                 //new button().class("btn btn-primary text-white").child("Add missing location")
 | |
|                                 this.searchFooter
 | |
|                             )
 | |
|                     ),
 | |
|             )
 | |
|             .onClick((e) => {
 | |
|                 e.stopPropagation(); //Stop this from bubbling to the document click.
 | |
|                 this.focus(); //Focus our element if we are not already.
 | |
|             })
 | |
|     }
 | |
| 
 | |
|     ready() {
 | |
|         //Add a listener to the document click to blur our element.
 | |
|         this.documentListener = () => this.blur();
 | |
|         window.document.addEventListener("click", this.documentListener);
 | |
| 
 | |
|     }
 | |
| 
 | |
|     cleanup() {
 | |
|         window.document.removeEventListener("click", this.documentListener);
 | |
|     }
 | |
| 
 | |
|     focus() {
 | |
|         if (!this.editing) {
 | |
|             this.editing = true;
 | |
|             this.input.focus();
 | |
|             this.input.textContent = " ";
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     blur() {
 | |
|         if (this.editing) {
 | |
|             this.editing = false;
 | |
|             this.searching = false;
 | |
|             this.input.blur();
 | |
|             this.input.innerHTML = "";
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     searchResultClick(item) {
 | |
|         console.log("Search item was clicked:", item);
 | |
|         this.items.push({ content: item.content });
 | |
|     }
 | |
| }
 | |
| 
 | |
| class ChipListTemplate extends IgniteTemplate {
 | |
|     constructor(...children) {
 | |
|         super("mt-chip-list", children);
 | |
|     }
 | |
| }
 | |
| 
 | |
| customElements.define("mt-chip-list", ChipList);
 | |
| 
 | |
| export {
 | |
|     ChipListTemplate as ChipList
 | |
| } |