Skip to content

WidgetSpan wrong caret position #107432

@tapeo

Description

@tapeo

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3Issues that are less important to the Flutter projecta: text inputEntering text in a text field or keyboard related problemsa: typographyText rendering, possibly libtxtf: material designflutter/packages/flutter/material repository.found in release: 3.0Found to occur in 3.0found in release: 3.1Found to occur in 3.1frameworkflutter/packages/flutter repository. See also f: labels.has reproducible stepsThe issue has been confirmed reproducible and is ready to work onteam-designOwned by Design Languages teamtriaged-designTriaged by Design Languages team

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions