Avoid blur-save during mention selection

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
dotta 2026-04-04 14:05:08 -05:00
parent c89349687f
commit fe61e650c2
2 changed files with 135 additions and 12 deletions

View file

@ -19,6 +19,23 @@ const pad = "px-1 -mx-1";
const markdownPad = "px-1";
const AUTOSAVE_DEBOUNCE_MS = 900;
export function queueContainedBlurCommit(container: HTMLDivElement, onCommit: () => void) {
let frameId = requestAnimationFrame(() => {
frameId = requestAnimationFrame(() => {
frameId = 0;
const active = document.activeElement;
if (active instanceof Node && container.contains(active)) return;
onCommit();
});
});
return () => {
if (frameId === 0) return;
cancelAnimationFrame(frameId);
frameId = 0;
};
}
export function InlineEditor({
value,
onSave,
@ -35,6 +52,7 @@ export function InlineEditor({
const inputRef = useRef<HTMLTextAreaElement>(null);
const markdownRef = useRef<MarkdownEditorRef>(null);
const autosaveDebounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const blurCommitFrameRef = useRef<(() => void) | null>(null);
const {
state: autosaveState,
markDirty,
@ -52,6 +70,10 @@ export function InlineEditor({
if (autosaveDebounceRef.current) {
clearTimeout(autosaveDebounceRef.current);
}
if (blurCommitFrameRef.current !== null) {
blurCommitFrameRef.current();
blurCommitFrameRef.current = null;
}
};
}, []);
@ -91,6 +113,30 @@ export function InlineEditor({
}
}, [draft, multiline, onSave, value]);
const cancelPendingBlurCommit = useCallback(() => {
if (blurCommitFrameRef.current === null) return;
blurCommitFrameRef.current();
blurCommitFrameRef.current = null;
}, []);
const scheduleBlurCommit = useCallback((container: HTMLDivElement) => {
cancelPendingBlurCommit();
blurCommitFrameRef.current = queueContainedBlurCommit(container, () => {
blurCommitFrameRef.current = null;
if (autosaveDebounceRef.current) {
clearTimeout(autosaveDebounceRef.current);
}
setMultilineFocused(false);
const trimmed = draft.trim();
if (!trimmed || trimmed === value) {
reset();
void commit();
return;
}
void runSave(() => commit());
});
}, [cancelPendingBlurCommit, commit, draft, reset, runSave, value]);
function handleKeyDown(e: React.KeyboardEvent) {
if (e.key === "Enter" && !multiline) {
e.preventDefault();
@ -146,20 +192,13 @@ export function InlineEditor({
"rounded transition-colors",
multilineFocused ? "bg-transparent" : "hover:bg-accent/20",
)}
onFocusCapture={() => setMultilineFocused(true)}
onFocusCapture={() => {
cancelPendingBlurCommit();
setMultilineFocused(true);
}}
onBlurCapture={(event) => {
if (event.currentTarget.contains(event.relatedTarget as Node | null)) return;
if (autosaveDebounceRef.current) {
clearTimeout(autosaveDebounceRef.current);
}
setMultilineFocused(false);
const trimmed = draft.trim();
if (!trimmed || trimmed === value) {
reset();
void commit();
return;
}
void runSave(() => commit());
scheduleBlurCommit(event.currentTarget);
}}
onKeyDown={handleKeyDown}
>