chore: better focus timning

This commit is contained in:
Nathan
2025-08-27 20:20:19 +08:00
parent cefdd08200
commit bf10df8f61

View File

@@ -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<HTMLDivElement>(null);
const cursorPositionRef = useRef<number>(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 (
<div
@@ -243,14 +252,14 @@ function TitleEditable({
data-placeholder={t('menuAppHeader.defaultNewPageName')}
contentEditable={true}
aria-readonly={false}
autoFocus={true}
autoFocus={autoFocus}
onFocus={() => {
// 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,