diff --git a/src/components/room/MentionPopover.tsx b/src/components/room/MentionPopover.tsx index 2479f34..9922d8c 100644 --- a/src/components/room/MentionPopover.tsx +++ b/src/components/room/MentionPopover.tsx @@ -223,19 +223,13 @@ export function MentionPopover({ onCategoryEnterRef.current = onCategoryEnter; // Parse mention state - mentionStateRef.current = useMemo( - () => { - const before = inputValue.slice(0, cursorPosition); - const match = before.match(/@([^:@\s<]*)(:([^\s<]*))?$/); - if (!match) return null; - return { category: match[1].toLowerCase(), item: (match[3] ?? '').toLowerCase(), hasColon: match[2] !== undefined }; - }, - // Inline — not used as dependency - inputValue, cursorPosition, - ); - // Force update reference - const [, setTick] = useState(0); - useEffect(() => { setTick(t => t + 1); }, [inputValue, cursorPosition]); + const mentionState = useMemo(() => { + const before = inputValue.slice(0, cursorPosition); + const match = before.match(/@([^:@\s<]*)(:([^\s<]*))?$/); + if (!match) return null; + return { category: match[1].toLowerCase(), item: (match[3] ?? '').toLowerCase(), hasColon: match[2] !== undefined }; + }, [inputValue, cursorPosition]); + mentionStateRef.current = mentionState; visibleSuggestionsRef.current = suggestions; selectedIndexRef.current = selectedIndex; @@ -314,7 +308,7 @@ export function MentionPopover({ useLayoutEffect(() => { if (!containerRef.current) return; - if (!mentionStateRef.current) return; + if (!mentionState) return; const textarea = containerRef.current; const styles = window.getComputedStyle(textarea); @@ -328,8 +322,7 @@ export function MentionPopover({ tempDiv.style.pointerEvents = 'none'; document.body.appendChild(tempDiv); - const ms = mentionStateRef.current; - const pattern = ms.hasColon ? `@${ms.category}:${ms.item}` : `@${ms.category}`; + const pattern = mentionState.hasColon ? `@${mentionState.category}:${mentionState.item}` : `@${mentionState.category}`; tempDiv.textContent = inputValue.slice(0, cursorPosition - pattern.length + (inputValue.slice(0, cursorPosition).lastIndexOf(pattern))); const span = document.createElement('span'); @@ -381,7 +374,7 @@ export function MentionPopover({ // Simple approach: just use the textarea bounds + estimated offset // This is sufficient for most cases - }, [inputValue, cursorPosition, containerRef, mentionStateRef, suggestions.length]); + }, [inputValue, cursorPosition, containerRef, mentionState, suggestions.length]); // ─── Keyboard Navigation ─────────────────────────────────────────────────── @@ -434,18 +427,16 @@ export function MentionPopover({ // Hide when mention state is gone useEffect(() => { - const ms = mentionStateRef.current; - if (!ms) closePopover(); - }, [inputValue, cursorPosition, closePopover]); + if (!mentionState) closePopover(); + }, [inputValue, cursorPosition, closePopover, mentionState]); // Don't render if no valid mention context - const ms = mentionStateRef.current; - if (!ms) return null; + if (!mentionState) return null; - const isLoading = (ms.category === 'repository' && reposLoading) || - (ms.category === 'ai' && aiConfigsLoading); + const isLoading = (mentionState.category === 'repository' && reposLoading) || + (mentionState.category === 'ai' && aiConfigsLoading); - const currentCategory = ms.hasColon ? ms.category : null; + const currentCategory = mentionState.hasColon ? mentionState.category : null; const catConfig = currentCategory ? CATEGORY_CONFIG[currentCategory] : null; return ( @@ -456,12 +447,12 @@ export function MentionPopover({ @
- {ms.hasColon ? ( + {mentionState.hasColon ? ( <> - {ms.category} - {ms.item && (<> + {mentionState.category} + {mentionState.item && (<> / - {ms.item} + {mentionState.item} )} ) : ( @@ -488,7 +479,7 @@ export function MentionPopover({ doInsert(s)} onMouseEnter={() => { setSelectedIndex(i); selectedIndexRef.current = i; }} - searchTerm={ms.item} /> + searchTerm={mentionState.item} />
))}