Enhance filter for plugins and settings

* When no match, hide by default dropdown elements (because nothing to see)
* Case only one plugin matching filter, close dropdown by default
* Case multiple plugins matching after a filter change (select or keyword input), open dropdown with matching plugin to highlight choices
* Case previously no match and now match a plugin, select by default first available plugin in tab
* Add attribute to combobox to avoid focus on dropdown open while filtering
* Update or reset combobox state after filter to avoid conflict with dropdown items
* Refactor FilterSettings
This commit is contained in:
Jordan Blasenhauer 2024-04-17 18:37:59 +02:00
parent 67674a9055
commit 23a88f5104
2 changed files with 73 additions and 25 deletions

View file

@ -225,8 +225,14 @@ class TabsSelect {
}
dropdown.classList.toggle("hidden");
dropdown.classList.toggle("flex");
// Case open, try to focus on combobox input
if (!dropdown.classList.contains("hidden") && combobox) {
// Unless already input focused (avoid conflict with search)
if (
!dropdown.classList.contains("hidden") &&
combobox &&
combobox.getAttribute("data-focus") !== "false"
) {
combobox.focus();
}
@ -288,6 +294,9 @@ class FilterSettings {
this.tabsEls = this.tabContainer.querySelectorAll(
`[data-tab-select-handler]`,
);
this.comboboxEl = this.tabContainer
.querySelector("[data-tab-select-dropdown]")
.querySelector("[data-combobox]");
this.init();
}
@ -300,11 +309,13 @@ class FilterSettings {
});
}
// Update plugin items based on current input
if (this.comboboxEl) {
this.comboboxEl.addEventListener("input", () => {
this.runComboFilter();
});
// Allow to run combobox filter when opening dropdown (because reset and focus on open)
this.comboboxEl.addEventListener("focusin", () => {
this.runComboFilter();
});
@ -352,7 +363,13 @@ class FilterSettings {
}
runFilter() {
// Reset previous state to start fresh
this.resetFilter();
// get current tab, this will be used to show other plugin tab if current is hidden after filter
const tabNameBeforeFilter =
this.tabContainer
?.querySelector("[data-tab-select-dropdown-btn]")
?.getAttribute("data-tab-id") || "";
//get inp format
const inpValue = this.input.value.trim().toLowerCase().replaceAll("_", " ");
@ -612,37 +629,68 @@ class FilterSettings {
// case no tab match
if (isAllHidden) {
// we want to show message "No match"
this.tabContainer
.querySelector("[data-tab-select-dropdown-btn]")
.setAttribute("data-tab-id", "no-match");
return (this.tabContainer.querySelector(
this.tabContainer.querySelector(
"[data-tab-select-dropdown-btn] span",
).textContent = "No match");
).textContent = "No match";
// we want to close dropdown in case open previsouly
this.toggleDropdown(true, true, false);
return;
}
// click first not hidden tab
const currTabEl = this.tabContainer.querySelector(
// case at least one match
const currTabBtn = this.tabContainer.querySelector(
`[data-tab-select-handler='${tabNameBeforeFilter}']`,
);
// case the previous plugin is still visible, set is as active by clicking it again
if (currTabBtn && !currTabBtn.classList.contains("!hidden")) {
currTabBtn.click();
}
// case the previous plugin is hidden, click on the first not hidden tab
if (currTabBtn?.classList?.contains("!hidden") || !currTabBtn) {
firstNotHiddenEl.click();
}
// furthermore, open dropdown so user can see remain plugins in case the first one is not the one he is looking for
// and if more than one plugin available
const hiddenTabsEl = this.tabContainer.querySelectorAll(
`[data-tab-select-handler][class*="!hidden"]`,
);
if (hiddenTabsEl.length < this.tabsEls.length - 1)
this.toggleDropdown(true, false, true);
return;
}
toggleDropdown(
avoidComboFocus = false,
disableOpen = false,
disableClose = false,
) {
const dropdownEl = this.tabContainer.querySelector(
"[data-tab-select-dropdown]",
);
const dropdownBtn = this.tabContainer.querySelector(
"[data-tab-select-dropdown-btn]",
);
const currTabName = currTabEl.getAttribute("data-tab-id");
// case previously no match
if (currTabName === "no-match" && !isAllHidden) {
return firstNotHiddenEl.click();
}
const currTabBtn = this.tabContainer.querySelector(
`[data-tab-select-handler='${currTabName}']`,
);
if (!currTabBtn.classList.contains("!hidden")) {
return currTabBtn.click();
}
if (currTabBtn.classList.contains("!hidden")) {
return firstNotHiddenEl.click();
}
if (this.comboboxEl && avoidComboFocus)
this.comboboxEl.setAttribute("data-focus", "false");
let canClick = true;
// check if can click based on next dropdown state
if (disableClose && !dropdownEl.classList.contains("hidden"))
canClick = false;
if (disableOpen && dropdownEl.classList.contains("hidden"))
canClick = false;
if (canClick) dropdownBtn.click();
// Case avoid focus on combobox, we need to reset here because the focusin event is not triggered
if (this.comboboxEl && avoidComboFocus) this.runComboFilter();
// Reset to default state
if (this.comboboxEl) this.comboboxEl.setAttribute("data-focus", "true");
}
resetFilter() {

View file

@ -22,7 +22,7 @@
role="listbox"
data-tab-select-dropdown
class="hidden z-100 absolute flex-col w-full overflow-hidden overflow-y-auto max-h-[350px]">
<input data-combobox type="text" placeholder="Search plugin" class="cursor-text first border rounded-t border-gray-100 text-left focus:outline outline-none text-sm leading-5.6 ease block w-full appearance-none border-gray-300 dark:border-gray-200 bg-white dark:bg-gray-400 bg-clip-padding px-6 py-1.5 font-normal text-gray-800 transition-all placeholder:text-gray-500 dark:placeholder:text-gray-600">
<input data-focus="true" data-combobox type="text" placeholder="Search plugin" class="cursor-text first border rounded-t border-gray-100 text-left focus:outline outline-none text-sm leading-5.6 ease block w-full appearance-none border-gray-300 dark:border-gray-200 bg-white dark:bg-gray-400 bg-clip-padding px-6 py-1.5 font-normal text-gray-800 transition-all placeholder:text-gray-500 dark:placeholder:text-gray-600">
{% set first_el = "True" %}
{% for plugin in plugins %}
{% if current_endpoint == "services" and plugin["settings"]