currentTextEditingValue property
The current state of the TextEditingValue held by this client.
Implementation
@override
TextEditingValue? get currentTextEditingValue {
if (_isComposing) {
// On buffer-sync platforms the IME computes deltas against the full
// fragment text. Return the reconstructed full buffer so delta
// application stays consistent and avoids random duplication.
if (_shouldSyncBuffer) {
final fragText = _getCurrentFragmentText() ?? '';
final start = _preeditLocalOffset.clamp(0, fragText.length);
final text = fragText.substring(0, start) + _preeditText + fragText.substring(start);
final composingStart = start;
final composingEnd = start + _preeditText.length;
return TextEditingValue(
text: text,
selection: TextSelection.collapsed(
offset: composingStart + _preeditCaretOffset.clamp(0, _preeditText.length),
),
composing: TextRange(start: composingStart, end: composingEnd),
);
}
return TextEditingValue(
text: _preeditText,
selection: TextSelection.collapsed(offset: _preeditText.length),
composing: _composingRange,
);
}
// Buffer-sync platforms (web, iOS, macOS, Windows): return the actual
// fragment text so the IME computes deltas against the correct content.
if (_shouldSyncBuffer) {
final text = _getCurrentFragmentText();
if (text != null) {
final doc = _document!;
final cursor = doc.cursor;
final isSingleFragSelection =
!cursor.isCollapsed && cursor.anchorId == cursor.focusId;
final offset = _getCursorOffsetInFragment().clamp(0, text.length);
if (_isIOS &&
cursor.isCollapsed &&
offset == 0 &&
!text.startsWith(_emptyFragmentPlaceholder)) {
// See _emptyFragmentPlaceholder: when the cursor sits at the very
// start of a fragment (offset 0), UIKit's Backspace has nothing
// before the cursor to delete and silently swallows the keystroke.
// We prepend a single zero-width placeholder character so Backspace
// always has something to act on. The placeholder never reaches the
// document model — it is intercepted in updateEditingValueWithDeltas
// and translated into a structural backspace (merge with prev node).
return TextEditingValue(
text: '$_emptyFragmentPlaceholder$text',
selection: const TextSelection.collapsed(offset: 1),
composing: TextRange.empty,
);
}
if (isSingleFragSelection) {
return TextEditingValue(
text: text,
selection: TextSelection(
baseOffset: cursor.anchorOffset.clamp(0, text.length),
extentOffset: cursor.focusOffset.clamp(0, text.length),
),
composing: TextRange.empty,
);
}
// Multi-fragment selection: use a full selection in the buffer so
// the platform can delete or replace the entire buffer content,
// which triggers the correct document-level selection
// deletion/replacement.
if (!cursor.isCollapsed) {
return TextEditingValue(
text: text,
selection: TextSelection(
baseOffset: 0,
extentOffset: text.length,
),
composing: TextRange.empty,
);
}
return TextEditingValue(
text: text,
selection: TextSelection.collapsed(offset: offset),
composing: TextRange.empty,
);
}
}
// Other platforms keep an empty buffer (backspace handled via KeyEvent).
return const TextEditingValue();
}