const ToastConfig = {
containerId: 'toast-container',
positionClasses: 'toast-container position-fixed bottom-0 end-0 p-3',
defaultType: 'info',
defaultTimeout: 3000
};
function updateToastConfig(overrides = {}) {
Object.assign(ToastConfig, overrides);
}
function renderToast({ message, type = ToastConfig.defaultType, timeout = ToastConfig.defaultTimeout }) {
if (!message) {
console.warn('renderToast was called without a message.');
return;
}
// Auto-create the toast container if it doesn't exist
let container = document.getElementById(ToastConfig.containerId);
if (!container) {
container = document.createElement('div');
container.id = ToastConfig.containerId;
container.className = ToastConfig.positionClasses;
document.body.appendChild(container);
}
const toastId = `toast-${Date.now()}`;
const wrapper = document.createElement('div');
wrapper.innerHTML = `
`;
const toastElement = wrapper.firstElementChild;
container.appendChild(toastElement);
const toast = new bootstrap.Toast(toastElement, { delay: timeout });
toast.show();
toastElement.addEventListener('hidden.bs.toast', () => {
toastElement.remove();
// Clean up container if empty
if (container.children.length === 0) {
container.remove();
}
});
}
const ComboBoxWidget = (() => {
let tempIdCounter = 1;
function createTempId(prefix = "temp") {
return `${prefix}-${tempIdCounter++}`;
}
function createOption(text, value = null) {
const option = document.createElement('option');
option.textContent = text;
option.value = value ?? createTempId();
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 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.trim();
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 (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,
createTempId
};
})();