const ComboBoxWidget = (() => { let tempIdCounter = -1; function createOption(text, value = null) { const option = document.createElement('option'); option.textContent = text; option.value = value ?? (tempIdCounter--); return option; } function sortOptions(selectElement) { const sorted = Array.from(selectElement.options) .sort((a, b) => a.text.localeCompare(b.text)); selectElement.innerHTML = ''; sorted.forEach(option => selectElement.appendChild(option)); } function handleComboAdd(inputId, listId, stateArray, label = 'entry') { const input = document.getElementById(inputId); const value = input.value.trim(); if (!value) { alert(`Please enter a ${label}.`); return; } const select = document.getElementById(listId); const exists = Array.from(select.options).some(opt => opt.textContent === value); if (exists) { alert(`${label.charAt(0).toUpperCase() + label.slice(1)} "${value}" already exists.`); return; } const option = createOption(value); // Already built to handle temp IDs select.add(option); formState[stateArray].push(value); input.value = ''; } function initComboBox(ns, config = {}) { const input = document.querySelector(`#${ns}-input`); const list = document.querySelector(`#${ns}-list`); const addBtn = document.querySelector(`#${ns}-add`); const removeBtn = document.querySelector(`#${ns}-remove`); let currentlyEditing = null; if (!input || !list || !addBtn || !removeBtn) { console.warn(`ComboBoxWidget: Missing elements for namespace '${ns}'`); return; } function updateAddButtonIcon() { const iconEl = addBtn.querySelector('.icon-state'); const iconClass = currentlyEditing ? 'bi-pencil' : 'bi-plus-lg'; iconEl.classList.forEach(cls => { if (cls.startsWith('bi-') && cls !== 'icon-state') { iconEl.classList.remove(cls); } }); iconEl.classList.add(iconClass); } input.addEventListener('input', () => { addBtn.disabled = input.value.trim() === ''; updateAddButtonIcon(); }); list.addEventListener('change', () => { const selected = list.selectedOptions; removeBtn.disabled = selected.length === 0; if (selected.length === 1) { input.value = selected[0].textContent; currentlyEditing = selected[0]; addBtn.disabled = input.value.trim() === ''; } else { input.value = ''; currentlyEditing = null; addBtn.disabled = true; } updateAddButtonIcon(); }); addBtn.addEventListener('click', () => { const newItem = input.value.trim(); if (!newItem) return; if (currentlyEditing) { if (config.onEdit) { config.onEdit(currentlyEditing, newItem); } else { currentlyEditing.textContent = newItem; } currentlyEditing = null; } else { if (config.onAdd) { config.onAdd(newItem, list, createOption); return; // Skip the default logic! } const exists = Array.from(list.options).some(opt => opt.textContent === newItem); if (exists) { alert(`"${newItem}" already exists.`); return; } const option = createOption(newItem); list.appendChild(option); const key = config.stateArray ?? `${ns}s`; // fallback to pluralization if (Array.isArray(formState?.[key])) { formState[key].push({ name: newItem }); } if (config.sort !== false) { sortOptions(list); } } input.value = ''; addBtn.disabled = true; removeBtn.disabled = true; updateAddButtonIcon(); }); removeBtn.addEventListener('click', () => { Array.from(list.selectedOptions).forEach(option => { if (config.onRemove) { config.onRemove(option); } option.remove(); }); currentlyEditing = null; input.value = ''; addBtn.disabled = true; removeBtn.disabled = true; updateAddButtonIcon(); }); } return { initComboBox, createOption, sortOptions, handleComboAdd }; })();