Phenomenon
On Android Chrome with Samsung keyboard text prediction enabled, typing text next to formatted elements like links or bold text in a contenteditable element causes beforeinput eventโs event.data to contain both the formatted elementโs text and the typed text combined.
Reproduction example
- Open Chrome browser on an Android device (Samsung Galaxy series, etc.).
- Enable text prediction feature in Samsung keyboard.
- Prepare HTML with a link or bold text inside a
contenteditableelement (e.g.,<a href="https://example.com">Link text</a>or<strong>Bold text</strong>). - Position the cursor right next to (after) the formatted element.
- Type text (e.g., โHelloโ).
- Check
beforeinputeventโsevent.datain the browser console.
Observed behavior
When typing text next to formatted elements:
-
beforeinput event:
e.datacontains both formatted elementโs text and typed text combined- Example: If link text is โLink textโ and user types โHelloโ,
e.data === 'LinktextHello' - Difficult to extract only the typed text
-
Result:
- Cannot accurately determine the actual typed text from
event.data - Text extraction logic may fail
- Change tracking systems may record incorrect changes
- Cannot accurately determine the actual typed text from
Expected behavior
event.datashould contain only the typed text- Formatted elementโs text should not be included
- Example: Typing โHelloโ should result in
e.data === 'Hello'
Impact
- Incorrect text extraction: Cannot extract only the typed text from
event.data - Change tracking failure: Change tracking systems record incorrect changes
- Undo/redo problems: Undo/redo stacks may record incorrect text
Browser Comparison
- Android Chrome + Samsung Keyboard (Text Prediction ON): This issue occurs
- Android Chrome + Samsung Keyboard (Text Prediction OFF): Works normally
- Android Chrome + Gboard: Works normally
- Other browsers: Similar issues may occur with other text prediction features
Notes and possible direction for workarounds
- Compare DOM state: When
event.datais unreliable, compare DOM state to determine actual changes - Text extraction logic: Implement logic to extract only the typed text from combined text
- Check adjacent elements: Check and remove adjacent formatted elementโs text
Code example
const editor = document.querySelector('div[contenteditable]');
editor.addEventListener('beforeinput', (e) => {
if (e.data) {
const selection = window.getSelection();
const range = selection?.rangeCount > 0
? selection.getRangeAt(0)
: null;
// Find adjacent formatted element
let container = range?.startContainer;
if (container?.nodeType === Node.TEXT_NODE) {
container = container.parentElement;
}
const link = container?.closest('a');
const bold = container?.closest('b, strong');
const italic = container?.closest('i, em');
const formattedElement = link || bold || italic;
if (formattedElement) {
// Extract actual input text from combined text
const actualInputText = extractActualInputText(
e.data,
formattedElement
);
// Process with actual input text
handleInput(actualInputText, range);
} else {
// No formatted element, use e.data as-is
handleInput(e.data, range);
}
}
});
function extractActualInputText(combinedData, formattedElement) {
if (!formattedElement || !combinedData) {
return combinedData;
}
const formattedText = formattedElement.textContent;
// Check if combined data starts with formatted text
if (combinedData.startsWith(formattedText)) {
return combinedData.slice(formattedText.length);
}
// Check if combined data ends with formatted text
if (combinedData.endsWith(formattedText)) {
return combinedData.slice(0, -formattedText.length);
}
// Fallback: return as-is
return combinedData;
}