UNPKG

irisrad-ui

Version:

UI elements developered for IRIS R&D Group Inc

231 lines (201 loc) 7.54 kB
let debounceTimeout; let searchTerm = ""; let onListBlurred; let onKeyDown; let onLabelClicked; let onListItemClicked; export default class GroupedSelect { constructor(element) { this.selectEle = element; this.optionObjects = getFormattedOptions( element.querySelector("ul").childNodes ); this.customSelectContainer = element.querySelector( ".iris-custom-select-container" ); this.customSelectLabel = element.querySelector(".iris-custom-select-label"); this.optionList = element.querySelector(".iris-custom-select-list"); this.registerEvents(); setupCustomElement(this); } /** * get the selected default option */ get selectedOption() { const target = this.optionObjects.find((option) => option.selected); if (target) { return target; } else { const firstOption = this.optionObjects[0]; firstOption.selected = true; firstOption.element.classList.add("selected"); if (this?.selectEle.onChange) { this.selectEle.onChange(firstOption.value); } return firstOption; } } /** * get the index of the selected option */ get selectedOptionIndex() { return this.optionObjects.indexOf(this.selectedOption); } setSelectedValue(value) { // find previously selected option; const prevSelectedOption = this.selectedOption; // set its selected state as false prevSelectedOption.selected = false; // remove selected css style from the option // this.optionList // .querySelector(`[data-value="${prevSelectedOption.value}"]`) prevSelectedOption.element.classList.remove("selected"); // find the reference of the selected option const newSelectedOption = this.optionObjects.find((option) => { return option.value === value; }); // set its selected state as true newSelectedOption.selected = true; // set the label's innerText with the one of the selected option this.customSelectLabel.innerText = newSelectedOption.label; // add stelected css style on the selected option newSelectedOption.element.classList.add("selected"); // scroll to the selected option if necessary newSelectedOption.element.scrollIntoView({ block: "center" }); // call the onchange callback function which is passed to the original element (select) if (value !== prevSelectedOption.value && this?.selectEle.onChange) { this.selectEle.onChange(value); } } registerEvents() { onListBlurred = handlOnListBlurred.bind(this); onKeyDown = handlOnKeyDown.bind(this); onLabelClicked = handleOnLabelClicked.bind(this); onListItemClicked = handleListItemClick.bind(this); this.optionObjects.forEach((option) => { option.element.addEventListener("click", onListItemClicked); }); this.customSelectContainer.addEventListener("blur", onListBlurred); this.customSelectContainer.addEventListener("keydown", onKeyDown); this.customSelectContainer.addEventListener("click", onLabelClicked); } unregisterListeners() { this.customSelectContainer.removeEventListener("blur", onListBlurred); this.customSelectContainer.removeEventListener("keydown", onKeyDown); this.customSelectContainer.removeEventListener("click", onLabelClicked); this.optionObjects.forEach((option) => { option.element.removeEventListener("click", onListItemClicked); }); } } function handlOnKeyDown(event) { switch (event.code) { case "Space": this.optionList.classList.toggle("show"); break; case "ArrowUp": { const prevOption = this.optionObjects[this.selectedOptionIndex - 1]; if (prevOption) { this.setSelectedValue(prevOption.value); } break; } case "ArrowDown": { const nextOption = this.optionObjects[this.selectedOptionIndex + 1]; if (nextOption) { this.setSelectedValue(nextOption.value); } break; } case "Enter": this.optionList.classList.toggle("show"); break; case "Escape": this.optionList.classList.remove("show"); break; default: { clearTimeout(debounceTimeout); searchTerm += event.key; debounceTimeout = setTimeout(() => { searchTerm = ""; }, 500); const searchedOption = this.optionObjects.find((option) => { return option.label.toLowerCase().startsWith(searchTerm); }); if (searchedOption) { this.setSelectedValue(searchedOption.value); } } } } function handlOnListBlurred() { this.optionList.classList.remove("show"); } // click listener for each list option function handleListItemClick(event) { event.stopPropagation(); const optionValue = event.target.getAttribute("value"); this.setSelectedValue(optionValue); this.optionList.classList.remove("show"); } function handleOnLabelClicked() { this.optionList.classList.toggle("show"); this.selectedOption.element.scrollIntoView({ block: "center" }); } function setupCustomElement(select) { const customSelectContainer = select.customSelectContainer; const customSelectLabel = select.customSelectLabel; customSelectContainer.tabIndex = 0; customSelectLabel.innerText = select?.selectedOption?.label || ""; } // /** // * @description given a nodelist of html default options, generate an array of objects which containes the following properties: // * value: option's value of property [value] // * label: the html innerText of the option element // * selected: the boolean value of property [selected] // * element: the option node itself // * // * @param {Array} optionElements an array of default html option elements (NodeList) // * @returns an array of objecs, in the shape of {value: string, label: string, selected: boolean, element: the_option_node_itself} // */ // function getFormattedOptions(optionElements) { // debugger; // return [...optionElements].map((optionElement) => { // optionElement.classList.add("iris-custom-select-option"); // return { // value: optionElement.getAttribute("value"), // label: optionElement.innerText, // selected: optionElement.selected, // element: optionElement, // }; // }); // } /** * @description given a nodelist of html default options, generate an array of objects which containes the following properties: * value: option's value of property [value] * label: the html innerText of the option element * selected: the boolean value of property [selected] * element: the option node itself * * @param {Array} optionElements an array of default html option elements (NodeList) * @returns an array of objecs, in the shape of {value: string, label: string, selected: boolean, element: the_option_node_itself} */ function getFormattedOptions(divs) { const allOptions = []; [...divs].map((div) => { [...div.querySelector("ul").childNodes].map((option) => { option.classList.add("iris-custom-select-option"); if (option.selected) { console.log(`selected`); option.classList.add("selected"); } allOptions.push({ value: option.getAttribute("value"), label: option.innerText, selected: option.selected, element: option, }); }); }); return allOptions; }