From dea9d9b5a495f6c0686808da820b5b8c0ecf90cf Mon Sep 17 00:00:00 2001 From: Psychotoxical Date: Sun, 19 Apr 2026 18:23:34 +0200 Subject: [PATCH] fix(settings): lyrics-sources DnD survives mode toggle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The psy-drop listener was attached in a useEffect keyed only on setLyricsSources, so when the container was unmounted by the {lyricsMode === 'standard' && …} wrapper (switching to YouLyPlus and back), the listener stayed bound to the old DOM node and drag-to-reorder silently did nothing. Switch the container ref to a callback-ref stored in state, so the effect re-runs on mount/unmount and always listens on the current DOM node. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/pages/Settings.tsx | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx index 7b703d59..c4759587 100644 --- a/src/pages/Settings.tsx +++ b/src/pages/Settings.tsx @@ -2684,7 +2684,9 @@ function LyricsSourcesCustomizer() { const lyricsStaticOnly = useAuthStore(s => s.lyricsStaticOnly); const setLyricsStaticOnly = useAuthStore(s => s.setLyricsStaticOnly); const { isDragging: isPsyDragging } = useDragDrop(); - const containerRef = useRef(null); + // useState (not useRef) so the listener-effect re-runs when the container + // gets unmounted/remounted by the {lyricsMode === 'standard'} wrapper. + const [containerEl, setContainerEl] = useState(null); const [dropTarget, setDropTarget] = useState(null); const dropTargetRef = useRef(null); const sourcesRef = useRef(lyricsSources); @@ -2695,8 +2697,7 @@ function LyricsSourcesCustomizer() { }, [isPsyDragging]); useEffect(() => { - const el = containerRef.current; - if (!el) return; + if (!containerEl) return; const onPsyDrop = (e: Event) => { const detail = (e as CustomEvent).detail; if (!detail?.data) return; @@ -2717,13 +2718,13 @@ function LyricsSourcesCustomizer() { next.splice(insertBefore > fromIdx ? insertBefore - 1 : insertBefore, 0, moved); setLyricsSources(next); }; - el.addEventListener('psy-drop', onPsyDrop); - return () => el.removeEventListener('psy-drop', onPsyDrop); - }, [setLyricsSources]); + containerEl.addEventListener('psy-drop', onPsyDrop); + return () => containerEl.removeEventListener('psy-drop', onPsyDrop); + }, [containerEl, setLyricsSources]); const handleMouseMove = (e: React.MouseEvent) => { - if (!isPsyDragging || !containerRef.current) return; - const rows = containerRef.current.querySelectorAll('[data-lyrics-idx]'); + if (!isPsyDragging || !containerEl) return; + const rows = containerEl.querySelectorAll('[data-lyrics-idx]'); let target: LyricsDropTarget = null; for (const row of rows) { const rect = row.getBoundingClientRect(); @@ -2817,7 +2818,7 @@ function LyricsSourcesCustomizer() { {lyricsMode === 'standard' && ( -
+
{lyricsSources.map((src, i) => { const label = t(LYRICS_SOURCE_LABEL_KEYS[src.id]); const isBefore = isPsyDragging && dropTarget?.idx === i && dropTarget.before;