diff --git a/public/app.js b/public/app.js
index 62309e5..ecda578 100644
--- a/public/app.js
+++ b/public/app.js
@@ -2,6 +2,11 @@
const fieldGrid = document.querySelector("#fieldGrid");
const variantHeader = document.querySelector("#variantHeader");
const manufacturerSelect = document.querySelector("#manufacturerSelect");
+const manufacturerTrigger = document.querySelector("#manufacturerTrigger");
+const manufacturerMenu = document.querySelector("#manufacturerMenu");
+const manufacturerSearch = document.querySelector("#manufacturerSearch");
+const manufacturerOptions = document.querySelector("#manufacturerOptions");
+const manufacturerValue = document.querySelector("#manufacturerValue");
const languageSelect = document.querySelector("#languageSelect");
const recordIdElement = document.querySelector("#recordId");
const loadProductButton = document.querySelector("#loadProductButton");
@@ -129,6 +134,8 @@ setTableWidth();
await loadManufacturers();
await loadLanguages();
manufacturerSelect.addEventListener("change", () => {
+ syncManufacturerPickerLabel();
+ closeManufacturerPicker();
if (manufacturerSelect.value) {
loadMappingSources();
loadProductForSelectedManufacturer(1);
@@ -138,6 +145,16 @@ manufacturerSelect.addEventListener("change", () => {
clearProductState("Select manufacturer to load data.");
}
});
+manufacturerTrigger.addEventListener("click", toggleManufacturerPicker);
+manufacturerSearch.addEventListener("input", renderManufacturerOptions);
+manufacturerSearch.addEventListener("keydown", (event) => {
+ if (event.key === "Escape")
+ closeManufacturerPicker();
+});
+document.addEventListener("click", (event) => {
+ if (!event.target.closest("#manufacturerPicker"))
+ closeManufacturerPicker();
+});
loadProductButton.disabled = true;
loadProductButton.title = "Load existing product attribute info.";
loadProductButton.addEventListener("click", loadProductInfoForCurrentProduct);
@@ -258,9 +275,59 @@ async function loadManufacturers() {
}
if (data.message)
manufacturerSelect.append(createOption("", data.message));
+ syncManufacturerPickerLabel();
+ renderManufacturerOptions();
}
catch (error) {
manufacturerSelect.append(createOption("", error.message || "Local DB is not configured."));
+ syncManufacturerPickerLabel();
+ renderManufacturerOptions();
+ }
+}
+function toggleManufacturerPicker() {
+ if (manufacturerMenu.hidden) {
+ manufacturerMenu.hidden = false;
+ manufacturerTrigger.setAttribute("aria-expanded", "true");
+ manufacturerSearch.value = "";
+ renderManufacturerOptions();
+ if (manufacturerSelect.options.length > 11)
+ manufacturerSearch.focus();
+ }
+ else {
+ closeManufacturerPicker();
+ }
+}
+function closeManufacturerPicker() {
+ manufacturerMenu.hidden = true;
+ manufacturerTrigger.setAttribute("aria-expanded", "false");
+}
+function syncManufacturerPickerLabel() {
+ const option = manufacturerSelect.selectedOptions[0];
+ manufacturerValue.textContent = option?.textContent || "Select manufacturer";
+}
+function renderManufacturerOptions() {
+ const options = [...manufacturerSelect.options].filter((option) => option.value);
+ const query = manufacturerSearch.value.trim().toLocaleLowerCase();
+ const filtered = options.filter((option) => option.textContent.toLocaleLowerCase().includes(query));
+ const visible = filtered.slice(0, 10);
+ manufacturerOptions.replaceChildren(...visible.map((option) => {
+ const item = document.createElement("button");
+ item.className = "manufacturer-option";
+ item.type = "button";
+ item.setAttribute("role", "option");
+ item.textContent = option.textContent;
+ item.setAttribute("aria-selected", String(option.value === manufacturerSelect.value));
+ item.addEventListener("click", () => {
+ manufacturerSelect.value = option.value;
+ manufacturerSelect.dispatchEvent(new Event("change", { bubbles: true }));
+ });
+ return item;
+ }));
+ if (!visible.length) {
+ const empty = document.createElement("div");
+ empty.className = "manufacturer-empty";
+ empty.textContent = "No manufacturer found.";
+ manufacturerOptions.append(empty);
}
}
function createOption(value, label) {
diff --git a/public/app.ts b/public/app.ts
index b592b56..840d6c2 100644
--- a/public/app.ts
+++ b/public/app.ts
@@ -2,6 +2,11 @@
const fieldGrid = document.querySelector("#fieldGrid");
const variantHeader = document.querySelector("#variantHeader");
const manufacturerSelect = document.querySelector("#manufacturerSelect");
+const manufacturerTrigger = document.querySelector("#manufacturerTrigger");
+const manufacturerMenu = document.querySelector("#manufacturerMenu");
+const manufacturerSearch = document.querySelector("#manufacturerSearch");
+const manufacturerOptions = document.querySelector("#manufacturerOptions");
+const manufacturerValue = document.querySelector("#manufacturerValue");
const languageSelect = document.querySelector("#languageSelect");
const recordIdElement = document.querySelector("#recordId");
const loadProductButton = document.querySelector("#loadProductButton");
@@ -136,6 +141,8 @@ await loadManufacturers();
await loadLanguages();
manufacturerSelect.addEventListener("change", () => {
+ syncManufacturerPickerLabel();
+ closeManufacturerPicker();
if (manufacturerSelect.value) {
loadMappingSources();
loadProductForSelectedManufacturer(1);
@@ -145,6 +152,15 @@ manufacturerSelect.addEventListener("change", () => {
}
});
+manufacturerTrigger.addEventListener("click", toggleManufacturerPicker);
+manufacturerSearch.addEventListener("input", renderManufacturerOptions);
+manufacturerSearch.addEventListener("keydown", (event) => {
+ if (event.key === "Escape") closeManufacturerPicker();
+});
+document.addEventListener("click", (event) => {
+ if (!event.target.closest("#manufacturerPicker")) closeManufacturerPicker();
+});
+
loadProductButton.disabled = true;
loadProductButton.title = "Load existing product attribute info.";
loadProductButton.addEventListener("click", loadProductInfoForCurrentProduct);
@@ -272,8 +288,64 @@ async function loadManufacturers() {
manufacturerSelect.append(createOption(String(manufacturer.id), manufacturer.name));
}
if (data.message) manufacturerSelect.append(createOption("", data.message));
+ syncManufacturerPickerLabel();
+ renderManufacturerOptions();
} catch (error) {
manufacturerSelect.append(createOption("", error.message || "Local DB is not configured."));
+ syncManufacturerPickerLabel();
+ renderManufacturerOptions();
+ }
+}
+
+function toggleManufacturerPicker() {
+ if (manufacturerMenu.hidden) {
+ manufacturerMenu.hidden = false;
+ manufacturerTrigger.setAttribute("aria-expanded", "true");
+ manufacturerSearch.value = "";
+ renderManufacturerOptions();
+ if (manufacturerSelect.options.length > 11) manufacturerSearch.focus();
+ } else {
+ closeManufacturerPicker();
+ }
+}
+
+function closeManufacturerPicker() {
+ manufacturerMenu.hidden = true;
+ manufacturerTrigger.setAttribute("aria-expanded", "false");
+}
+
+function syncManufacturerPickerLabel() {
+ const option = manufacturerSelect.selectedOptions[0];
+ manufacturerValue.textContent = option?.textContent || "Select manufacturer";
+}
+
+function renderManufacturerOptions() {
+ const options = [...manufacturerSelect.options].filter((option) => option.value);
+ const query = manufacturerSearch.value.trim().toLocaleLowerCase();
+ const filtered = options.filter((option) => option.textContent.toLocaleLowerCase().includes(query));
+ const visible = filtered.slice(0, 10);
+
+ manufacturerOptions.replaceChildren(
+ ...visible.map((option) => {
+ const item = document.createElement("button");
+ item.className = "manufacturer-option";
+ item.type = "button";
+ item.setAttribute("role", "option");
+ item.textContent = option.textContent;
+ item.setAttribute("aria-selected", String(option.value === manufacturerSelect.value));
+ item.addEventListener("click", () => {
+ manufacturerSelect.value = option.value;
+ manufacturerSelect.dispatchEvent(new Event("change", { bubbles: true }));
+ });
+ return item;
+ }),
+ );
+
+ if (!visible.length) {
+ const empty = document.createElement("div");
+ empty.className = "manufacturer-empty";
+ empty.textContent = "No manufacturer found.";
+ manufacturerOptions.append(empty);
}
}
diff --git a/public/index.html b/public/index.html
index d38bd99..24f94f4 100644
--- a/public/index.html
+++ b/public/index.html
@@ -60,7 +60,17 @@
-