parent
cd351aec2d
commit
09cc1c4cda
@ -4510,7 +4510,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
||||
userUpdateTextEditingValue(value, SelectionChangedCause.keyboard);
|
||||
},
|
||||
shouldChangeUndoStack: (TextEditingValue? oldValue, TextEditingValue newValue) {
|
||||
if (newValue == TextEditingValue.empty) {
|
||||
if (!newValue.selection.isValid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -100,7 +100,19 @@ class UndoHistoryState<T> extends State<UndoHistory<T>> with UndoManagerClient {
|
||||
|
||||
@override
|
||||
void undo() {
|
||||
if (_stack.currentValue == null) {
|
||||
// Returns early if there is not a first value registered in the history.
|
||||
// This is important because, if an undo is received while the initial
|
||||
// value is being pushed (a.k.a when the field gets the focus but the
|
||||
// throttling delay is pending), the initial push should not be canceled.
|
||||
return;
|
||||
}
|
||||
if (_throttleTimer?.isActive ?? false) {
|
||||
_throttleTimer?.cancel(); // Cancel ongoing push, if any.
|
||||
_update(_stack.currentValue);
|
||||
} else {
|
||||
_update(_stack.undo());
|
||||
}
|
||||
_updateState();
|
||||
}
|
||||
|
||||
@ -455,27 +467,17 @@ typedef _Throttled<T> = Timer Function(T currentArg);
|
||||
_Throttled<T> _throttle<T>({
|
||||
required Duration duration,
|
||||
required _Throttleable<T> function,
|
||||
// If true, calls at the start of the timer.
|
||||
bool leadingEdge = false,
|
||||
}) {
|
||||
Timer? timer;
|
||||
bool calledDuringTimer = false;
|
||||
late T arg;
|
||||
|
||||
return (T currentArg) {
|
||||
arg = currentArg;
|
||||
if (timer != null) {
|
||||
calledDuringTimer = true;
|
||||
if (timer != null && timer!.isActive) {
|
||||
return timer!;
|
||||
}
|
||||
if (leadingEdge) {
|
||||
function(arg);
|
||||
}
|
||||
calledDuringTimer = false;
|
||||
timer = Timer(duration, () {
|
||||
if (!leadingEdge || calledDuringTimer) {
|
||||
function(arg);
|
||||
}
|
||||
timer = null;
|
||||
});
|
||||
return timer!;
|
||||
|
@ -13011,6 +13011,58 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async
|
||||
// On web, these keyboard shortcuts are handled by the browser.
|
||||
}, variant: TargetPlatformVariant.only(TargetPlatform.android), skip: kIsWeb); // [intended]
|
||||
|
||||
// Regression test for https://github.com/flutter/flutter/issues/120194.
|
||||
testWidgets('Cursor does not jump after undo', (WidgetTester tester) async {
|
||||
// Initialize the controller with a non empty text.
|
||||
final TextEditingController controller = TextEditingController(text: textA);
|
||||
final FocusNode focusNode = FocusNode();
|
||||
await tester.pumpWidget(boilerplate(controller, focusNode));
|
||||
|
||||
// Focus the field and wait for throttling delay to get the initial
|
||||
// state saved in text editing history.
|
||||
focusNode.requestFocus();
|
||||
await tester.pump();
|
||||
await waitForThrottling(tester);
|
||||
expect(controller.value, textACollapsedAtEnd);
|
||||
|
||||
// Insert some text.
|
||||
await tester.enterText(find.byType(EditableText), textAB);
|
||||
expect(controller.value, textABCollapsedAtEnd);
|
||||
|
||||
// Undo the insertion without waiting for the throttling delay.
|
||||
await sendUndo(tester);
|
||||
expect(controller.value.selection.isValid, true);
|
||||
expect(controller.value, textACollapsedAtEnd);
|
||||
|
||||
// On web, these keyboard shortcuts are handled by the browser.
|
||||
}, variant: TargetPlatformVariant.all(), skip: kIsWeb); // [intended]
|
||||
|
||||
testWidgets('Initial value is recorded when an undo is received just after getting the focus', (WidgetTester tester) async {
|
||||
// Initialize the controller with a non empty text.
|
||||
final TextEditingController controller = TextEditingController(text: textA);
|
||||
final FocusNode focusNode = FocusNode();
|
||||
await tester.pumpWidget(boilerplate(controller, focusNode));
|
||||
|
||||
// Focus the field and do not wait for throttling delay before calling undo.
|
||||
focusNode.requestFocus();
|
||||
await tester.pump();
|
||||
await sendUndo(tester);
|
||||
await waitForThrottling(tester);
|
||||
expect(controller.value, textACollapsedAtEnd);
|
||||
|
||||
// Insert some text.
|
||||
await tester.enterText(find.byType(EditableText), textAB);
|
||||
expect(controller.value, textABCollapsedAtEnd);
|
||||
|
||||
// Undo the insertion.
|
||||
await sendUndo(tester);
|
||||
|
||||
// Initial text should have been recorded and restored.
|
||||
expect(controller.value, textACollapsedAtEnd);
|
||||
|
||||
// On web, these keyboard shortcuts are handled by the browser.
|
||||
}, variant: TargetPlatformVariant.all(), skip: kIsWeb); // [intended]
|
||||
|
||||
testWidgets('Can make changes in the middle of the history', (WidgetTester tester) async {
|
||||
final TextEditingController controller = TextEditingController();
|
||||
final FocusNode focusNode = FocusNode();
|
||||
|
Loading…
x
Reference in New Issue
Block a user