Skip to content

Dragging selection handles behave incorrectly when text lines have varying heights #188871

Description

@Renzo-Olivares

Steps to reproduce

  1. Run the provided code sample (below) on any target platform.
  2. Long-press on the text field to select a word on "Huge Line".
  3. Grab the end selection handle and attempt to drag it downwards by a short distance (e.g., 40 pixels) to select "small line 1" and "small line 2".
  4. Alternatively, place the cursor at the end of "Huge Line" and attempt to drag the cursor handle.

Expected results

  1. Selection handles should render precisely at the bottom edge of the caret or text boundary.
  2. Dragging the selection handle down should expand the selection to include the small lines as the finger crosses them.
  3. Dragging the cursor handle should be responsive across its entire height.

Jetpack compose:

screen-20260701-111613-1782929749425.mp4

Actual results

  1. Stuck Drag Selection: Dragging the selection handle down by 40px (which visually crosses multiple 10px-tall lines) does not expand the selection at all. The selection remains stuck on "Huge Line" because the gesture logic assumes every line has the height of the default style's preferredLineHeight (~80px).
  2. Incorrect Handle Position: Selection handles render in incorrect vertical positions (floating too high inside tall lines or too low below small lines) because handle offsets assume a uniform line height.
  3. Grab-Target Sizing Issue (iOS/Cupertino): When the selection is collapsed, the invisible touch container for the cursor handle is built with a height matching only the small default preferredLineHeight (~20px) instead of the actual tall line's height (~90px), meaning the user can only grab the bottom edge of the tall cursor.
screen-20260701-112005-1782929980500.mp4

Code sample

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('Varying Line Heights Bug Repro')),
        body: const Center(
          child: Padding(
            padding: EdgeInsets.all(16.0),
            child: VaryingLineHeightsTextField(),
          ),
        ),
      ),
    );
  }
}

class VaryingLineHeightsTextField extends StatefulWidget {
  const VaryingLineHeightsTextField({super.key});

  @override
  State<VaryingLineHeightsTextField> createState() =>
      _VaryingLineHeightsTextFieldState();
}

class _VaryingLineHeightsTextFieldState
    extends State<VaryingLineHeightsTextField> {
  late final TextEditingController _controller;

  @override
  void initState() {
    super.initState();
    _controller = VaryingTextInputController(
      text: 'Huge Line 1\nsmall line 2\nsmall line 3\nsmall line 4\n',
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return TextField(
      controller: _controller,
      maxLines: null,
      style: const TextStyle(
        fontSize: 80,
        color: Colors.black,
      ), // default/fallback preferredLineHeight will be large (~80)
    );
  }
}

class VaryingTextInputController extends TextEditingController {
  VaryingTextInputController({super.text});

  @override
  TextSpan buildTextSpan({
    required BuildContext context,
    TextStyle? style,
    required bool withComposing,
  }) {
    return const TextSpan(
      style: TextStyle(fontSize: 80, color: Colors.black),
      children: <InlineSpan>[
        TextSpan(text: 'Huge Line\n'),
        TextSpan(text: 'small line 1\n', style: TextStyle(fontSize: 10)),
        TextSpan(text: 'small line 2\n', style: TextStyle(fontSize: 10)),
        TextSpan(text: 'small line 3\n', style: TextStyle(fontSize: 10)),
      ],
    );
  }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Important issues not at the top of the work lista: text inputEntering text in a text field or keyboard related problemsteam-text-inputOwned by Text Input teamtriaged-text-inputTriaged by Text Input team

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions