Phenomenon
When composing text with Korean IME in a contenteditable element, moving focus to another textbox causes the composition to not properly terminate.
Reproduction example
- Focus the first contenteditable element.
- Activate Korean IME.
- Start composing a character (e.g., ‘나’ = ㅎ + ㄴ).
- Before composition completes (after typing initial consonant ㅎ), click the second textbox or move focus using a button.
Observed behavior
- compositionend event fires:
compositionendfires in the first field, but the composition is not fully cleaned up. - Partial jamo remains: The jamo ‘ㅎ’ remains in the first field.
- Second field focus fails: Focus does not move to the second field, or if it does, the composition state does not continue properly.
- Input continues: Continuing to type in the new field behaves as if the previous composition completed, but the first field’s state is not cleaned up.
Expected behavior
- Composition should fully terminate and the final character (‘나’) should be committed to the first field.
- Focus should smoothly move to the second field.
Notes and possible direction for workarounds
- Detect compositionend: Check if
compositionendevent has fired before moving focus. - setTimeout delay: Add a small delay (100-200ms) to focus movement to give the IME time to clean up the composition.
- Composition state flag: Use
isComposingflag to prevent focus movement during composition. - UI feedback: Disable buttons that move focus to other fields during composition, or provide visual feedback.
Code example
let isComposing = false;
const editor1 = document.querySelector('div[contenteditable]:nth-child(1)');
const editor2 = document.querySelector('div[contenteditable]:nth-child(2)');
// Track composition state
editor1.addEventListener('compositionstart', () => {
isComposing = true;
});
editor1.addEventListener('compositionend', () => {
isComposing = false;
// Move focus after composition completes
setTimeout(() => {
editor2.focus();
}, 100);
});
// Safely move focus on button click
document.querySelector('button').addEventListener('click', () => {
if (!isComposing) {
editor2.focus();
} else {
alert('IME input in progress. Please wait for completion.');
}
});