function scrollToElementInFocus(container) {
const focus = container.querySelector(".focus");
if (!focus) return;
const container_rect = container.getBoundingClientRect();
const focus_rect = focus.getBoundingClientRect();
if (focus_rect.top < container_rect.top) {
container.scrollTop -= (container_rect.top - focus_rect.top);
} else if (focus_rect.bottom > container_rect.bottom) {
container.scrollTop += (focus_rect.bottom - container_rect.bottom);
}
}
function autoSetState(container) {
const totalViewed = container.advancedSelect.optionsElements.length - container.querySelectorAll(".option.hide").length;
if(totalViewed == 0) {
container.advancedSelect.showState("not-found");
} else {
container.advancedSelect.showState("options");
}
}
export default function advancedSelect(input, options, notFoundText) {
const container = document.createElement("div");
container.classList.add("advanced-select");
let optionsList = ``;
for(let optionValue in options) {
optionsList += `<div class="option" data-value="${optionValue}" data-display-value="${options[optionValue]}">${options[optionValue]}</div>`;
}
let html = `
<div class="popup-options-container">
<div class="not-found">${notFoundText}</div>
<div class="options">${optionsList}</div>
</div>
`;
container.innerHTML = html;
const existsOption = (value, options) => {
for(let optionValue in options) {
if(options[optionValue] == value) {
const ret = {};
ret[optionValue] = options[optionValue];
return ret;
}
}
return false;
}
container.advancedSelect = {
isOpened: false,
options: options,
eventsHandlers: {
openList: [],
closeList: [],
selected: [],
changed: [],
},
openList: () => {
container.advancedSelect.isOpened = true;
container.classList.add("a-show");
autoSetState(container);
container.advancedSelect.dispatchEvent("openList");
},
closeList: () => {
container.advancedSelect.isOpened = false;
container.classList.remove("a-show");
autoSetState(container);
container.advancedSelect.dispatchEvent("closeList");
},
showState: stateName => {
if(stateName == "options") {
container.querySelector(".options").classList.add("show");
container.querySelector(".not-found").classList.remove("show");
} else if(stateName == "not-found") {
container.querySelector(".options").classList.remove("show");
container.querySelector(".not-found").classList.add("show");
}
},
optionsElements: container.querySelectorAll(".option"),
value: () => {
const option = existsOption(input.value, options);
return {
inputValue: input.value,
isOption: option ? true : false,
option
};
},
addEventListener: (name, handler) => {
if(typeof container.advancedSelect.eventsHandlers[name] != "undefined") {
return container.advancedSelect.eventsHandlers[name].push(handler);
}
console.error("Advanced Select component.", "addEventListener()", "Invalid event name");
},
dispatchEvent: name => {
if(typeof container.advancedSelect.eventsHandlers[name] == "undefined") {
return console.error("Advanced Select component.", "dispatchEvent()", "Invalid event name");
}
for(let eventHandler of container.advancedSelect.eventsHandlers[name]) {
eventHandler(container);
}
}
};
input.setAttribute("autocomplete", "nope");
input.advancedSelect = {
value: () => container.advancedSelect.value()
}
input.addEventListener("focus", e => {
container.advancedSelect.openList();
});
input.addEventListener("blur", e => {
setTimeout(() => container.advancedSelect.closeList(), 20);
});
input.addEventListener("keydown", e => {
if(e.keyCode == 38) {
// up
const current = container.querySelector(".option.focus");
if(current) {
current.classList.remove("focus");
let prev = current.previousElementSibling;
while (prev) {
if (!prev.classList.contains("hide")) {
break;
}
prev = prev.previousElementSibling;
}
if(!prev) {
prev = container.querySelector(".option:not(.hide)");
}
prev.classList.add("focus");
} else {
container.querySelector(".option:not(.hide):last-child").classList.add("focus");
}
scrollToElementInFocus(container);
} else if(e.keyCode == 40) {
// down
const current = container.querySelector(".option.focus");
if(current) {
current.classList.remove("focus");
let next = current.nextElementSibling;
while (next) {
if (!next.classList.contains("hide")) {
break;
}
next = next.nextElementSibling;
}
if(!next) {
next = container.querySelector(".option:not(.hide)");
}
next.classList.add("focus");
} else {
container.querySelector(".option:not(.hide)").classList.add("focus");
}
scrollToElementInFocus(container);
} else if(e.keyCode == 13) {
let selected = container.querySelector(".option.focus");
if(!selected) return;
input.value = selected.dataset.displayValue;
input.blur();
input.dispatchEvent(new Event("input", { bubbles: true }));
input.dispatchEvent(new Event("change", { bubbles: true }));
container.advancedSelect.dispatchEvent("selected");
}
});
input.addEventListener("input", e => {
const val = e.currentTarget.value.toLowerCase();
if(val == "") {
container.advancedSelect.optionsElements.forEach(i => i.classList.remove("hide"));
} else {
[ ...container.advancedSelect.optionsElements ]
.filter(i => i.dataset.displayValue.toLowerCase().indexOf(val) != -1)
.forEach(i => i.classList.remove("hide"));
[ ...container.advancedSelect.optionsElements ]
.filter(i => i.dataset.displayValue.toLowerCase().indexOf(val) == -1)
.forEach(i => i.classList.add("hide"));
autoSetState(container);
container.querySelector(".option.focus")?.classList.remove("focus");
}
});
input.addEventListener("change", e => {
container.advancedSelect.dispatchEvent("changed");
});
[ ...container.advancedSelect.optionsElements ].forEach(option => {
option.addEventListener("click", e => {
input.value = e.currentTarget.dataset.displayValue;
input.blur();
input.dispatchEvent(new Event("input", { bubbles: true }));
input.dispatchEvent(new Event("change", { bubbles: true }));
container.advancedSelect.dispatchEvent("selected");
});
});
return container;
}