value property
The current value stored in this notifier.
When the value is replaced with something that is not equal to the old value as evaluated by the equality operator ==, this class notifies its listeners.
Implementation
@override
T get value => _value;
Implementation
@override
set value(TextEditingValue newValue) {
if (newValue == value) {
return;
}
/// Truncates the text to the maximum length.
if (newValue.text.characters.take(_length).string case final truncated when truncated != newValue.text) {
_focused = (truncated.length - 1).clamp(0, _length - 1);
super.value = TextEditingValue(
text: truncated,
selection: .collapsed(offset: truncated.length),
);
return;
}
/// Clamps selection offsets that exceed text length. This happens when dragging selection handles across empty
/// WidgetSpan items — the framework reports offsets based on span positions, not text length.
final length = newValue.text.length;
if (length < newValue.selection.start || length < newValue.selection.end) {
super.value = newValue.copyWith(
selection: TextSelection(
baseOffset: newValue.selection.baseOffset.clamp(0, length),
extentOffset: newValue.selection.extentOffset.clamp(0, length),
),
);
_focused = length;
return;
}
/// Calculates the focused index and caret position. Arrow key events are intercepted and routed via `traverse`
/// to avoid conflicts.
///
/// WidgetSpan offset/affinity calculations are fucked. See https://github.com/flutter/flutter/issues/107432.
/// Tapping the right half of the first item produces <offset: 1, affinity: upstream> while other items produce
/// <offset: N, affinity: downstream>.
_focused = newValue.selection.baseOffset.clamp(0, _length - 1);
if (newValue.selection.isCollapsed) {
/// Corrects the focused index when tapping the right half of the first item.
if (newValue.selection.baseOffset == 1 && newValue.selection.affinity == .upstream) {
_focused = 0;
super.value = newValue;
return;
}
/// Handles replacement/deletion of middle items.
if (newValue.text.length != newValue.selection.baseOffset) {
/// Selects the previous item on deletion.
if (newValue.text.length != text.length) {
super.value = newValue.copyWith(
selection: TextSelection(
baseOffset: max(newValue.selection.baseOffset - 1, 0),
extentOffset: newValue.selection.baseOffset,
),
);
return;
}
/// Selects the middle item at the caret so that backspace deletes it and typing replaces it.
super.value = newValue.copyWith(
selection: TextSelection(
baseOffset: newValue.selection.baseOffset,
extentOffset: newValue.selection.baseOffset + 1,
),
);
return;
}
}
super.value = newValue;
}