Repository navigation
WidgetSpan wrong caret position #107432
Copy link
Copy link
Open
Labels
P3Issues that are less important to the Flutter projectIssues that are less important to the Flutter projecta: text inputEntering text in a text field or keyboard related problemsEntering text in a text field or keyboard related problemsa: typographyText rendering, possibly libtxtText rendering, possibly libtxtfound in release: 3.0Found to occur in 3.0Found to occur in 3.0found in release: 3.1Found to occur in 3.1Found to occur in 3.1has reproducible stepsThe issue has been confirmed reproducible and is ready to work onThe issue has been confirmed reproducible and is ready to work onp: material_uimaterial_ui package in flutter/packagesmaterial_ui package in flutter/packagespackageflutter/packages repository. See also p: labels.flutter/packages repository. See also p: labels.team-designOwned by Design Languages teamOwned by Design Languages teamtriaged-designTriaged by Design Languages teamTriaged by Design Languages team
Description
Steps to Reproduce
Type the word "widget" inside the text field.
Expected results:
The caret reflects the correct position.
Actual results:
The caret is moved in a wrong position (probably the x - size of the widget span)
Code sample:
import 'package:flutter/material.dart';
void main() {
runApp(const InputExample());
}
class InputExample extends StatefulWidget {
const InputExample({Key? key}) : super(key: key);
@override
State<InputExample> createState() => _InputExampleState();
}
class _InputExampleState extends State<InputExample> {
late final WidgetSpanTextFieldController titleController;
@override
void initState() {
titleController = WidgetSpanTextFieldController();
super.initState();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Input Example'),
),
body: Container(
padding: const EdgeInsets.all(16),
child: _title(context),
),
),
);
}
Widget _title(BuildContext context) {
return TextField(
controller: titleController,
textCapitalization: TextCapitalization.none,
maxLines: null,
keyboardType: TextInputType.multiline,
decoration: const InputDecoration(hintText: "Type here"),
cursorWidth: 5,
cursorColor: Colors.orange,
style: const TextStyle(
color: Colors.black,
fontSize: 20,
fontWeight: FontWeight.w500,
),
);
}
}
class WidgetSpanTextFieldController extends TextEditingController {
WidgetSpanTextFieldController();
@override
TextSpan buildTextSpan({
required BuildContext context,
TextStyle? style,
required bool withComposing,
}) {
TextRange? matchedRange;
if (text.contains("widget")) {
matchedRange = _findMatchedRange(text);
}
if (matchedRange != null) {
return TextSpan(
children: [
_textSpan(matchedRange.textBefore(text)),
_widgetSpan(matchedRange.textInside(text)),
_textSpan(matchedRange.textAfter(text)),
],
style: style,
);
}
return TextSpan(text: text, style: style);
}
TextRange _findMatchedRange(String text) {
final RegExp matchPattern = RegExp('widget', caseSensitive: false);
late TextRange matchedRange;
for (final Match match in matchPattern.allMatches(text)) {
matchedRange = TextRange(start: match.start, end: match.end);
}
return matchedRange;
}
WidgetSpan _widgetSpan(String text) {
return WidgetSpan(
alignment: PlaceholderAlignment.middle,
child: Container(
height: 20,
padding: const EdgeInsets.symmetric(horizontal: 5),
decoration: BoxDecoration(
color: Colors.redAccent,
borderRadius: BorderRadius.circular(3),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
text,
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.w500,
fontSize: 17,
),
),
],
)),
);
}
TextSpan _textSpan(String text) {
return TextSpan(text: text);
}
}
Tested on master and stable channels.
Video:
example.mov
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
P3Issues that are less important to the Flutter projectIssues that are less important to the Flutter projecta: text inputEntering text in a text field or keyboard related problemsEntering text in a text field or keyboard related problemsa: typographyText rendering, possibly libtxtText rendering, possibly libtxtfound in release: 3.0Found to occur in 3.0Found to occur in 3.0found in release: 3.1Found to occur in 3.1Found to occur in 3.1has reproducible stepsThe issue has been confirmed reproducible and is ready to work onThe issue has been confirmed reproducible and is ready to work onp: material_uimaterial_ui package in flutter/packagesmaterial_ui package in flutter/packagespackageflutter/packages repository. See also p: labels.flutter/packages repository. See also p: labels.team-designOwned by Design Languages teamOwned by Design Languages teamtriaged-designTriaged by Design Languages teamTriaged by Design Languages team
Activity
huycozy commentedon Jul 12, 2022
Hi @tapeo, thanks for filing the issue.
This issue is reproducible on the latest
stableandmasterchannels with provided sample code.The caret position displayed wrong after WidgetSpan appears.
Demo
ios.mov
web.mov
flutter doctor -v
[✓] Flutter (Channel stable, 3.0.4, on macOS 12.2.1 21D62 darwin-x64, locale en-VN) • Flutter version 3.0.4 at /Users/huynq/Documents/GitHub/flutter • Upstream repository https://github.com/flutter/flutter.git • Framework revision 85684f9300 (3 days ago), 2022-06-30 13:22:47 -0700 • Engine revision 6ba2af10bb • Dart version 2.17.5 • DevTools version 2.12.2 [✓] Android toolchain - develop for Android devices (Android SDK version 31.0.0) • Android SDK at /Users/huynq/Library/Android/sdk • Platform android-33, build-tools 31.0.0 • ANDROID_HOME = /Users/huynq/Library/Android/sdk • Java binary at: /Applications/Android Studio.app/Contents/jre/Contents/Home/bin/java • Java version OpenJDK Runtime Environment (build 11.0.12+0-b1504.28-7817840) • All Android licenses accepted. [✓] Xcode - develop for iOS and macOS (Xcode 13.3) • Xcode at /Applications/Xcode.app/Contents/Developer • CocoaPods version 1.11.3 [✓] Chrome - develop for the web • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome [!] Android Studio • Android Studio at /Applications/Android Studio Preview.app/Contents • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart ✗ Unable to find bundled Java version. • Try updating or re-installing Android Studio. [✓] Android Studio (version 2021.2) • Android Studio at /Applications/Android Studio.app/Contents • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart • Java version OpenJDK Runtime Environment (build 11.0.12+0-b1504.28-7817840) [✓] IntelliJ IDEA Community Edition (version 2020.3.3) • IntelliJ at /Applications/IntelliJ IDEA CE.app • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart [✓] IntelliJ IDEA Community Edition (version 2022.1.1) • IntelliJ at /Users/huynq/Library/Application Support/JetBrains/Toolbox/apps/IDEA-C/ch-0/221.5591.52/IntelliJ IDEA CE.app • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart [✓] VS Code (version 1.68.1) • VS Code at /Applications/Visual Studio Code.app/Contents • Flutter extension version 3.42.0 [✓] Connected device (2 available) • macOS (desktop) • macos • darwin-x64 • macOS 12.2.1 21D62 darwin-x64 • Chrome (web) • chrome • web-javascript • Google Chrome 103.0.5060.53 [✓] HTTP Host Availability • All required HTTP hosts are available ! Doctor found issues in 1 category.[✓] Flutter (Channel master, 3.1.0-0.0.pre.1609, on macOS 12.2.1 21D62 darwin-x64, locale en-VN) • Flutter version 3.1.0-0.0.pre.1609 on channel master at /Users/huynq/Documents/GitHub/flutter_master • Upstream repository https://github.com/flutter/flutter.git • Framework revision 881b27e836 (35 minutes ago), 2022-07-11 19:23:05 -0700 • Engine revision abe7b2ccc3 • Dart version 2.18.0 (build 2.18.0-271.0.dev) • DevTools version 2.15.0 [✓] Android toolchain - develop for Android devices (Android SDK version 31.0.0) • Android SDK at /Users/huynq/Library/Android/sdk • Platform android-33, build-tools 31.0.0 • ANDROID_HOME = /Users/huynq/Library/Android/sdk • Java binary at: /Applications/Android Studio.app/Contents/jre/Contents/Home/bin/java • Java version OpenJDK Runtime Environment (build 11.0.12+0-b1504.28-7817840) • All Android licenses accepted. [✓] Xcode - develop for iOS and macOS (Xcode 13.3) • Xcode at /Applications/Xcode.app/Contents/Developer • Build 13E113 • CocoaPods version 1.11.3 [✓] Chrome - develop for the web • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome [✓] Android Studio (version 2021.2) • Android Studio at /Applications/Android Studio.app/Contents • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart • Java version OpenJDK Runtime Environment (build 11.0.12+0-b1504.28-7817840) [✓] IntelliJ IDEA Community Edition (version 2020.3.3) • IntelliJ at /Applications/IntelliJ IDEA CE.app • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart [✓] IntelliJ IDEA Community Edition (version 2022.1.1) • IntelliJ at /Users/huynq/Library/Application Support/JetBrains/Toolbox/apps/IDEA-C/ch-0/221.5591.52/IntelliJ IDEA CE.app • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart [✓] VS Code (version 1.68.1) • VS Code at /Applications/Visual Studio Code.app/Contents • Flutter extension version 3.44.0 [✓] Connected device (3 available) • SM T225 (mobile) • R9JT3004VRJ • android-arm64 • Android 11 (API 30) • macOS (desktop) • macos • darwin-x64 • macOS 12.2.1 21D62 darwin-x64 • Chrome (web) • chrome • web-javascript • Google Chrome 103.0.5060.114 [✓] HTTP Host Availability • All required HTTP hosts are available • No issues found!jason-simmons commentedon Jul 15, 2022
The entire
WidgetSpanis treated as a single character by the Flutter engine's text APIs.In the example app, the
TextEditingControllervalue contains the 6-character string "widget". But when theEditableTextcalls the text positioning APIs exposed by theTextSpanwith the embeddedWidgetSpan, those APIs think that the corresponding region only consumes one position in the text. This mismatch will cause problems for the text editor.A while ago there was an attempt to build a generalized API for this use case but it looks like it never landed (#80185)
For now, one potential way to implement this would be overriding the
valuesetter inWidgetSpanTextFieldControllerto replace the "widget" substring in the text with a single placeholder character. The offsets in the value'sTextSelectionwould also need to be adjusted.buildTextSpancan then substitute the placeholder character with aWidgetSpan. The one-to-one mapping between the placeholder's position in theTextEditingValue.textand theWidgetSpan's position in theTextSpanshould allow the text editor to function as intended.kulny commentedon Aug 16, 2022
Is there a way someone could implement this for an arbitrary number of unique WidgetSpans? For example, say you have a list of names that may grow from user input. What would some ideal candidates for the single placeholder text value, and could there be a way to generate unique placeholders for each unique name?
I originally thought you meant extending PlaceholderSpan to replace the substring, then to replace that extended PlaceholderSpan with the intended WidgetSpan. But that's really just confusing the steps. Could you use unicode values as the unique characters? Or is there some way to create some sort of text value replacing object that can carry data which allows creating the intended WidgetSpan?
In the case of unicode (forgive my lack of knowledge) it seems that they would not be able to serve as a proper replacement for a single value of text. So what would be a good example of a single placeholder character that is not likely (or impossible) to be inputted by a user?
Alternatively, would extending WidgetSpan somehow allow changing what the text editing API's expect the position to be to instead be a range?
kulny commentedon Aug 16, 2022
It looks like a (crude) way to work around this is to simply add several WidgetSpans that take up no space until the expected number of characters is matched. I'm not sure how this might affect performance, though.
EDIT: Added sizes to the
SizedBoxso that when a user clicks on theTextFieldwhere theWidgetSpanis placed it will place the cursor at the end of the string which is replaced byWidgetSpanrather than after the first character.13 remaining items
nithsua commentedon Feb 28, 2024
Any update on the issue?
nithsua commentedon Feb 29, 2024
@tapeo Were you able to find a workaround for this
nithsua commentedon Mar 4, 2024
Hey @davie1989,
Were you able to handle changing cursor position and backspaces
davie1989 commentedon Mar 4, 2024
Hello @nithsua ,
Yes. I implemented it in a little different way and it work for me.
lucasjinreal commentedon Mar 4, 2024
davie1989 commentedon Mar 4, 2024
I can send my code after I close from work today. Maybe it would help.
nithsua commentedon Mar 4, 2024
That would be helpful
nithsua commentedon Mar 5, 2024
@davie1989 Hey mate, just checking in. Will you be able to share it?
lucas-goldner commentedon Feb 18, 2025
I have this issue as well, but for some reason it only happens on a certain device (iPhone 15 Pro Max). I haven't figured out, why it only happens on that device... I copied all of their keyboard settings to see if it was related to that, but nothing changed...