fix ghost text deletion issues

This commit is contained in:
Fabian Freund
2026-05-04 19:01:32 +02:00
parent e945f9928b
commit 334f5e1b35
2 changed files with 45 additions and 25 deletions
@@ -129,6 +129,9 @@ class SearchField extends HookConsumerWidget {
: null,
minLines: minLines,
autofocus: autofocus,
onSuggestionDismiss: () {
suggestion.value = null;
},
decoration: InputDecoration(
border: InputBorder.none,
contentPadding: const EdgeInsetsDirectional.fromSTEB(12, 12, 0, 12),
@@ -50,6 +50,7 @@ class AutoSuggestTextField extends HookWidget {
final bool? enableIMEPersonalizedLearning;
final TapRegionCallback? onTapOutside;
final VoidCallback? onTap;
final VoidCallback? onSuggestionDismiss;
final bool autocorrect;
final GlobalKey? textFieldKey;
@@ -80,6 +81,7 @@ class AutoSuggestTextField extends HookWidget {
this.enableIMEPersonalizedLearning = true,
this.onTapOutside,
this.onTap,
this.onSuggestionDismiss,
this.autocorrect = false,
this.textFieldKey,
});
@@ -96,6 +98,20 @@ class AutoSuggestTextField extends HookWidget {
final effectiveStyle = style ?? Theme.of(context).textTheme.bodyLarge!;
final hasSuggestionNotifier = useValueNotifier(false);
useEffect(() {
hasSuggestionNotifier.value = _suggestionHasMatch();
return null;
}, [suggestion, controller.text]);
final dismissFormatter = useMemoized(
() => _SuggestionDismissFormatter(
hasSuggestionNotifier: hasSuggestionNotifier,
onSuggestionDismiss: onSuggestionDismiss,
),
);
final showSuggestion = useListenableSelector(controller, () {
if (maxLines != 1) {
final lines = getTextFieldLineCount(
@@ -140,30 +156,6 @@ class AutoSuggestTextField extends HookWidget {
() => controller.text,
);
useEffect(() {
TextSelection? lastSelection;
void handleSelectionChange() {
if (lastSelection != controller.selection) {
lastSelection = controller.selection;
if (lastSelection!.start != lastSelection!.end) {
if (_suggestionHasMatch()) {
controller.value = controller.value.copyWith(
text: suggestion,
selection: lastSelection!.expandTo(
TextPosition(offset: suggestion!.length),
),
);
}
}
}
}
controller.addListener(handleSelectionChange);
return () =>
controller.removeListener(handleSelectionChange);
});
if (!_suggestionHasMatch()) {
return const SizedBox.shrink();
}
@@ -230,7 +222,7 @@ class AutoSuggestTextField extends HookWidget {
}
},
),
inputFormatters: inputFormatters,
inputFormatters: [dismissFormatter, ...?inputFormatters],
enabled: enabled,
cursorColor: cursorColor,
enableIMEPersonalizedLearning: enableIMEPersonalizedLearning ?? true,
@@ -241,3 +233,28 @@ class AutoSuggestTextField extends HookWidget {
);
}
}
class _SuggestionDismissFormatter extends TextInputFormatter {
final ValueNotifier<bool> hasSuggestionNotifier;
final VoidCallback? onSuggestionDismiss;
_SuggestionDismissFormatter({
required this.hasSuggestionNotifier,
this.onSuggestionDismiss,
});
@override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue,
TextEditingValue newValue,
) {
if (hasSuggestionNotifier.value &&
newValue.text.length < oldValue.text.length &&
oldValue.selection.isCollapsed &&
oldValue.selection.baseOffset == oldValue.text.length) {
onSuggestionDismiss?.call();
return oldValue;
}
return newValue;
}
}