Handle composition input in PDF text editor (#5192)

## Summary
- track IME composition state in the PDF text editor to avoid
interrupting phonetic input methods
- update text syncing to occur after composition completes and skip
redundant updates mid-composition

## Testing
- npm run lint -- --max-warnings 0


------
[Codex
Task](https://chatgpt.com/codex/tasks/task_b_693744be74148328bd3bda9150de6e56)
This commit is contained in:
Anthony Stirling 2025-12-10 10:11:45 +00:00 committed by GitHub
parent 291e1a392b
commit 6787169583
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 22 additions and 1 deletions

View File

@ -57,7 +57,7 @@ repositories {
allprojects {
group = 'stirling.software'
version = '2.1.1'
version = '2.1.2'
configurations.configureEach {
exclude group: 'commons-logging', module: 'commons-logging'

View File

@ -334,6 +334,7 @@ const PdfTextEditorView = ({ data }: PdfTextEditorViewProps) => {
const containerRef = useRef<HTMLDivElement | null>(null);
const editorRefs = useRef<Map<string, HTMLDivElement>>(new Map());
const caretOffsetsRef = useRef<Map<string, number>>(new Map());
const composingGroupsRef = useRef<Set<string>>(new Set());
const lastSelectedGroupIdRef = useRef<string | null>(null);
const widthOverridesRef = useRef<Map<string, number>>(widthOverrides);
const resizingRef = useRef<{
@ -614,6 +615,18 @@ const PdfTextEditorView = ({ data }: PdfTextEditorViewProps) => {
[editingGroupId, onGroupEdit],
);
const handleCompositionStart = useCallback((groupId: string) => {
composingGroupsRef.current.add(groupId);
}, []);
const handleCompositionEnd = useCallback(
(element: HTMLElement, pageIndex: number, groupId: string) => {
composingGroupsRef.current.delete(groupId);
syncEditorValue(element, pageIndex, groupId);
},
[syncEditorValue],
);
const handleMergeSelection = useCallback(() => {
if (!canMergeSelection) {
return;
@ -2279,6 +2292,10 @@ const selectionToolbarPosition = useMemo(() => {
contentEditable
suppressContentEditableWarning
data-editor-group={group.id}
onCompositionStart={() => handleCompositionStart(group.id)}
onCompositionEnd={(event) =>
handleCompositionEnd(event.currentTarget, group.pageIndex, group.id)
}
onFocus={(event) => {
const primaryFont = fontFamily.split(',')[0]?.replace(/['"]/g, '').trim();
if (primaryFont && typeof document !== 'undefined') {
@ -2300,6 +2317,7 @@ const selectionToolbarPosition = useMemo(() => {
event.stopPropagation();
}}
onBlur={(event) => {
composingGroupsRef.current.delete(group.id);
syncEditorValue(event.currentTarget, group.pageIndex, group.id, {
skipCaretRestore: true,
});
@ -2309,6 +2327,9 @@ const selectionToolbarPosition = useMemo(() => {
setEditingGroupId(null);
}}
onInput={(event) => {
if (composingGroupsRef.current.has(group.id)) {
return;
}
syncEditorValue(event.currentTarget, group.pageIndex, group.id);
}}
style={{