From c7d5de7a4f290a2fa42731ab6acb75d88e5ff2ec Mon Sep 17 00:00:00 2001 From: librelad Date: Mon, 6 Jul 2026 22:05:00 +0100 Subject: [PATCH] fix(webui/forms): stop the custom-select enhancer double-wrapping a dropdown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On the app-details Config tab, switching main tabs re-renders the config form (showAppDetail → displayConfigForm runs on every tab switch). A re-render race let the enhancer wrap a that already carries the .custom-select-native class or already sits inside a .custom-select wrapper — in both shouldEnhance() and the constructor. Port-manager selects were unaffected (they regenerate their container each render); this fixes the plain form-select fields. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: librelad --- .../frontend/core/forms/js/custom-select.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) 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));