deleteByComposition

How deleteByComposition inputType handles content deletion during IME composition and varies across browsers.

Overview

The deleteByComposition inputType is triggered when content is deleted during IME (Input Method Editor) composition. This typically occurs when the user presses Backspace or Delete while composing text in languages that require composition, such as Chinese, Japanese, and Korean.

Basic Behavior

Scenario: Deleting during Korean IME composition

During composition (typing "한")

HTML:
<p>Hello |</p>
Composition text is shown with underline or highlight

After pressing Backspace during composition

HTML:
<p>Hello |</p>
Composition text deleted, composition may be cancelled

IME Composition Deletion Behavior

⚠️ Critical Issue

During IME composition, pressing Backspace or Delete may cause unexpected behavior:

  • Composition may be cancelled entirely
  • More text than expected may be deleted (entire syllable or word)
  • Composition text may be partially committed before deletion
  • Event order may be unpredictable
  • Behavior varies significantly between browsers and IME implementations

See: deleteContentBackward and deleteContentForward for similar issues.

Browser-Specific Behavior

Chrome/Edge

  • May cancel composition when Backspace is pressed
  • May delete entire composition text at once
  • Event timing may differ from regular deletion

Firefox

  • Similar behavior to Chrome
  • May handle composition cancellation differently

Safari

  • IME composition deletion behavior may differ, especially on macOS
  • May use different event patterns

Editor-Specific Handling

Different editor frameworks handle composition deletion similarly to regular deletion, but need to account for composition state. Here's how major editors implement deleteByComposition:

Slate.js

Composition Deletion Handling

Slate handles composition deletion with composition state awareness:

    import { Editor, Transforms } from 'slate';

let isComposing = false;

element.addEventListener('compositionstart', () => {
  isComposing = true;
});

element.addEventListener('beforeinput', (e) => {
  if (e.inputType === 'deleteByComposition') {
    e.preventDefault();
    
    const { selection } = editor;
    if (selection && !selection.isCollapsed) {
      // Delete selected content
      Transforms.delete(editor);
    } else if (selection) {
      // Delete backward during composition
      Transforms.delete(editor, { reverse: true, unit: 'character' });
    }
  }
});

element.addEventListener('compositionend', () => {
  isComposing = false;
});
  
  • Composition state: Tracks composition state to handle deletion appropriately.
  • Transforms.delete: Uses same deletion method as regular delete operations.
ProseMirror

Composition Deletion Handling

ProseMirror handles composition deletion:

    import { backspace } from 'prosemirror-commands';

view.dom.addEventListener('compositionstart', () => {
  view.composing = true;
});

view.dom.addEventListener('beforeinput', (e) => {
  if (e.inputType === 'deleteByComposition') {
    e.preventDefault();
    const { state, dispatch } = view;
    
    if (state.selection.empty) {
      // Use backspace command during composition
      if (backspace(state, dispatch)) {
        // Handled
      }
    } else {
      // Delete selection
      if (deleteSelection(state, dispatch)) {
        // Handled
      }
    }
  }
});

view.dom.addEventListener('compositionend', () => {
  view.composing = false;
});
  
  • View.composing: Tracks composition state in ProseMirror view.
  • backspace command: Uses backspace command for composition deletion.
Draft.js

Composition Deletion Handling

Draft.js handles composition deletion:

    import { EditorState, Modifier } from 'draft-js';

let isComposing = false;

element.addEventListener('compositionstart', () => {
  isComposing = true;
});

element.addEventListener('beforeinput', (e) => {
  if (e.inputType === 'deleteByComposition') {
    e.preventDefault();
    
    const contentState = editorState.getCurrentContent();
    const selection = editorState.getSelection();
    
    // Delete backward during composition
    let newSelection = selection;
    if (selection.isCollapsed) {
      newSelection = selection.merge({
        anchorOffset: Math.max(0, selection.anchorOffset - 1),
      });
    }
    
    const newContentState = Modifier.removeRange(
      contentState,
      newSelection,
      'backward'
    );
    
    const newState = EditorState.push(
      editorState,
      newContentState,
      'remove-range'
    );
    
    setEditorState(newState);
  }
});

element.addEventListener('compositionend', () => {
  isComposing = false;
});
  
  • Composition tracking: Tracks composition state to handle deletion.
  • Modifier.removeRange: Uses same deletion method as regular delete operations.

Related resources