Phenomenon
In Safari, when composing text with Korean IME, moving focus to another textbox causes the composition to not properly terminate, and WebKit-specific issues may occur.
Reproduction example
- Focus the first contenteditable element.
- Activate macOS 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
- Jamo transfer phenomenon: The jamo ‘ㅎ’ may appear not only in the first field but also in the second field.
- WebKit focus issues: WebKit tends to maintain focus even after external clicks, so focus may not properly move.
- selection addRange failures: In Safari,
selection.addRange()may not work as intended (related scenario: selection-addrange-safari-fail). - compositionend uncertainty:
compositionendevent fires, but the composition state is not fully cleaned up.
Expected behavior
- Composition should fully terminate and the final character (‘나’) should be committed only to the first field.
- Focus should cleanly move to the second field.
- The composition state in the first field should be completely cleaned up.
Notes and possible direction for workarounds
- Understand WebKit focus quirks: Recognize that Safari/WebKit focus management may differ from other browsers.
- Use forced blur: Explicitly call
editor1.blur()before moving focus to force IME state termination. - Check CSS user-select: Verify that
-webkit-user-select: text;is properly set (related scenario: user-select-breaks-safari). - Selection API caution: Safari may have issues with
selection.addRange(), so consider alternative selection setting methods.
Code example
let isComposing = false;
const editor1 = document.querySelector('div[contenteditable]:nth-child(1)');
const editor2 = document.querySelector('div[contenteditable]:nth-child(2)');
editor1.addEventListener('compositionstart', () => {
isComposing = true;
});
editor1.addEventListener('compositionend', () => {
isComposing = false;
// Safari-specific: force blur to terminate IME state
setTimeout(() => {
editor1.blur();
editor2.focus();
}, 150);
});
// On button click
document.querySelector('button').addEventListener('click', () => {
if (!isComposing) {
editor2.focus();
} else {
alert('IME input in progress. Please wait for completion.');
}
});