Skip to content

fix(plugin-annotation): mark FreeText as editing on input so blur doesn't drop changes#682

Open
seanlim wants to merge 1 commit into
embedpdf:mainfrom
seanlim:fix/freetext-content-not-committed
Open

fix(plugin-annotation): mark FreeText as editing on input so blur doesn't drop changes#682
seanlim wants to merge 1 commit into
embedpdf:mainfrom
seanlim:fix/freetext-content-not-committed

Conversation

@seanlim

@seanlim seanlim commented Jun 24, 2026

Copy link
Copy Markdown

Closes #681

Root cause

In packages/plugin-annotation/src/shared/components/annotations/free-text.tsx (and callout-free-text.tsx), the commit-on-blur is gated by editingRef.current:

const handleBlur = () => {
  if (!editingRef.current) return;        // gate
  editingRef.current = false;
  ...
  annotationProvides.updateAnnotation(pageIndex, annotation.object.id, {
    contents: editorRef.current.innerText.replace(/\u00A0/g, ' '),
  });
};

editingRef.current is only ever armed inside the [isEditing] effect:

useEffect(() => {
  if (isEditing && editorRef.current) {
    editingRef.current = true;
    editor.focus();
    ...
  }
}, [isEditing]);   // runs only when isEditing CHANGES

When the style-panel interaction blurs the editor, handleBlur runs and sets editingRef.current = false. The annotation stays selected, so isEditing never changes and the effect doesn't re-run. Re-focusing the still-contentEditable span does not re-arm the gate (there is no onFocus handler), so the next blur bails out before committing.

Proposed fix

Re-arm the gate whenever the user actually edits the text, on the contentEditable element in both free-text.tsx and callout-free-text.tsx:

<span
  ref={editorRef}
  onBlur={handleBlur}
  onInput={() => {
    editingRef.current =
 true;
  }}
  tabIndex={0}
  ...
>

A single onInput handler is sufficient. A contentEditable only emits input when it's editable and its content actually changes, so "the user typed something" guarantees editingRef.current is armed before the next blur — which is the only case where text would otherwise be lost. (Verified against the repro above; re-arming on onFocus instead/additionally isn't needed, since a focus/blur with no typing has nothing new to commit.)

Testing

Ran the snippet viewer @embedpdf/snippet and added a FreeText annotation, typed text, blurred — text persists.

Re-edited the existing annotation, typed more, blurred — change is saved and the comment sidebar reflects the updated text (this is the path that previously dropped the edit).

output_fix

…sn't drop changes

handleBlur early-returns unless editingRef is set, and the ref was only
raised inside the isEditing effect. After a blur reset the ref, further
typing left it false, so the next blur skipped updateAnnotation and the
typed text was lost. Set editingRef on every onInput so a later blur
always saves the content.

Applies to both FreeText and CalloutFreeText.
@vercel

vercel Bot commented Jun 24, 2026

Copy link
Copy Markdown

@seanlim is attempting to deploy a commit to the OpenBook Team on Vercel.

A member of the Team first needs to authorize it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: FreeText annotation text is silently lost when editor is re-focused without isEditing toggling

1 participant