diff --git a/containers/libreportal/frontend/core/forms/js/custom-select.js b/containers/libreportal/frontend/core/forms/js/custom-select.js index 972c0f3..c678068 100644 --- a/containers/libreportal/frontend/core/forms/js/custom-select.js +++ b/containers/libreportal/frontend/core/forms/js/custom-select.js @@ -23,7 +23,15 @@ class CustomSelect { constructor(selectEl) { - if (selectEl[ENHANCED]) return; + // Guard against double-wrapping. The ENHANCED symbol is per-element, but a + // re-render race (the app config form re-renders on every tab switch) could + // slip a second enhancement through and nest a .custom-select inside another + // — showing the dropdown twice, one atop the other. The class + wrapper + // checks below survive that (they live on the DOM, not a JS symbol), so a + // select that's already enhanced or already inside a wrapper is never wrapped again. + if (selectEl[ENHANCED] + || selectEl.classList.contains('custom-select-native') + || selectEl.closest('.custom-select')) return; selectEl[ENHANCED] = true; this.select = selectEl; this.build(); @@ -300,6 +308,10 @@ function shouldEnhance(el) { if (!(el instanceof HTMLSelectElement)) return false; if (el[ENHANCED]) return false; + // Already enhanced or already inside a wrapper — never nest a second widget + // (see the constructor). DOM-based so it survives a lost ENHANCED symbol. + if (el.classList.contains('custom-select-native')) return false; + if (el.closest('.custom-select')) return false; if (el.multiple) return false; // multi-selects need different UX if (el.hasAttribute('data-no-enhance')) return false; return ENHANCE_CLASSES.some(c => el.classList.contains(c));