diff --git a/src/components/view-meta/TitleEditable.tsx b/src/components/view-meta/TitleEditable.tsx index 14980406..75ef158b 100644 --- a/src/components/view-meta/TitleEditable.tsx +++ b/src/components/view-meta/TitleEditable.tsx @@ -52,12 +52,14 @@ function TitleEditable({ onUpdateName, onEnter, onFocus, + autoFocus = true, }: { viewId: string; name: string; onUpdateName: (name: string) => void; onEnter?: (text: string) => void; onFocus?: () => void; + autoFocus?: boolean; }) { const { t } = useTranslation(); @@ -68,7 +70,7 @@ function TitleEditable({ pendingUpdate: null, updateId: null, }); - const [isEditing, setIsEditing] = useState(false); + const [isFocused, setIsFocused] = useState(false); const contentRef = useRef(null); const cursorPositionRef = useRef(0); @@ -135,11 +137,11 @@ function TitleEditable({ newName: name, pendingUpdate: updateStateRef.current.pendingUpdate, lastConfirmedName: updateStateRef.current.lastConfirmedName, - isEditing, + isFocused, }); - // If editing, ignore all remote updates - if (isEditing) { + // If focused, ignore all remote updates + if (isFocused) { return; } @@ -197,7 +199,7 @@ function TitleEditable({ if (contentRef.current) { contentRef.current.textContent = name; } - }, [name, isEditing, smartSendUpdate]); + }, [name, isFocused, smartSendUpdate]); const focusedTextbox = useCallback(() => { const contentBox = contentRef.current; @@ -209,7 +211,7 @@ function TitleEditable({ textbox?.focus(); }, [viewId]); - // Initialize settings + // Initialize content and handle autoFocus useEffect(() => { const contentBox = contentRef.current; @@ -219,15 +221,22 @@ function TitleEditable({ contentBox.textContent = updateStateRef.current.localName; initialEditValueRef.current = updateStateRef.current.localName; - contentBox.focus(); - - // Move cursor to end - if (contentBox.textContent !== '') { - setTimeout(() => { - setCursorPosition(contentBox, contentBox.textContent?.length || 0); - }, 0); + // Ensure focus if autoFocus is true + if (autoFocus) { + // Use requestAnimationFrame for next paint cycle to ensure DOM is fully ready + requestAnimationFrame(() => { + // Double-check the element still exists and is in the document + if (contentBox && document.contains(contentBox)) { + contentBox.focus(); + // Move cursor to end if there's content + if (contentBox.textContent) { + setCursorPosition(contentBox, contentBox.textContent.length); + } + } + }); } - }, []); // Only execute once when component mounts + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); // Only execute once when component mounts - autoFocus is intentionally not in deps return (
{ // Record initial value when starting to edit if (contentRef.current) { initialEditValueRef.current = contentRef.current.textContent || ''; } - setIsEditing(true); + setIsFocused(true); onFocus?.(); }} onBlur={() => { @@ -272,10 +281,10 @@ function TitleEditable({ } } - // Delay setting editing state to avoid issues with rapid focus switching - setTimeout(() => { - setIsEditing(false); - }, 100); + // Use microtask to avoid race conditions + void Promise.resolve().then(() => { + setIsFocused(false); + }); }} onInput={() => { if (!contentRef.current) return; @@ -314,7 +323,7 @@ function TitleEditable({ if (offset >= currentText.length || offset <= 0) { // Cursor at end or position inaccurate, keep all text - setIsEditing(false); + setIsFocused(false); updateStateRef.current = { ...updateStateRef.current, localName: currentText, @@ -327,7 +336,7 @@ function TitleEditable({ const afterText = currentText.slice(offset); contentRef.current.textContent = beforeText; - setIsEditing(false); + setIsFocused(false); updateStateRef.current = { ...updateStateRef.current, localName: beforeText, @@ -343,7 +352,7 @@ function TitleEditable({ // Escape key: complete editing and save current content const currentText = contentRef.current.textContent || ''; - setIsEditing(false); + setIsFocused(false); updateStateRef.current = { ...updateStateRef.current, localName: currentText,