I'm trying to create a mention feature, so I have extended the TextEditingController class for styling the entered text - change the color to blue and replace the entered string to the contact's name.
I'm facing a problem with the cursor when changing the text (setting a different text in TextSpan). If I enter a string with the exact length as the original one, the cursor acts normally, but if I enter a string with a smaller or longer length, the cursor doesn't follow the entered text, but the original one.
Example:
If the original text was @John
and I have replaced it to John Snow
, the cursor is located before S
and not after W
.
@override
TextSpan buildTextSpan({TextStyle style, bool withComposing}) {
var children = <InlineSpan>[];
text.splitMapJoin(
mentionPattern,
onMatch: (Match match) {
children.add(
TextSpan(
text: "Text replacer",
style: style.merge(MainStyle.LinkSmallTextStyle),
),
);
},
onNonMatch: (String text) {
children.add(TextSpan(text: text, style: style));
return '';
},
);
return TextSpan(style: style, children: children);
}
I found this workaround / hack here: https://github.com/flutter/flutter/issues/107432#issuecomment-1580175805
As you said, the problem arises when the length of the strings are different. So the trick is to insert some zero width space characters (\u200b) to reach the same length.
In my case, I'm replacing some text with a WidgetSpan
which gives me more control on the decoration. The WidgetSpan
is always considered to have a length of 1. But if I suffix it with a TextSpan with some \u200b, then the cursor behaves as you would expect.
@override
TextSpan buildTextSpan({required context, style, required withComposing}) {
final children = <InlineSpan>[];
final color = Theme.of(context).primaryColor;
text.splitMapJoin(
regexp,
onMatch: (match) {
final replacement = '...'; // Insert what you want here.
children.add(WidgetSpan(
alignment: PlaceholderAlignment.middle,
baseline: TextBaseline.ideographic,
child: Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
border: Border.all(color: color, width: 1),
),
child: Text(replacement),
),
));
// We do -1 because the WidgetSpan already takes up one character.
children.add(TextSpan(text: '\u200b' * (match[0]!.length - 1)));
return '';
},
onNonMatch: (text) {
children.add(TextSpan(text: text, style: style));
return '';
},
);
return TextSpan(style: style, children: children);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With