diff --git a/super_editor/lib/src/super_textfield/infrastructure/text_scrollview.dart b/super_editor/lib/src/super_textfield/infrastructure/text_scrollview.dart index f867a188d3..dcf6846adb 100644 --- a/super_editor/lib/src/super_textfield/infrastructure/text_scrollview.dart +++ b/super_editor/lib/src/super_textfield/infrastructure/text_scrollview.dart @@ -423,7 +423,11 @@ class _TextScrollViewState extends State child: SingleChildScrollView( key: _textFieldViewportKey, controller: _scrollController, - physics: const NeverScrollableScrollPhysics(), + // For single-line text fields, we do not allow horizontal scrolling, + // therefor we apply NeverScrollableScrollPhysics. For multi-line text + // fields, we pass null to allow the SingleChildScrollView to default + // to the appropriate scroll physics based on the host platform. + physics: isMultiline ? null : const NeverScrollableScrollPhysics(), scrollDirection: isMultiline ? Axis.vertical : Axis.horizontal, child: Padding( padding: widget.padding ?? EdgeInsets.zero, diff --git a/super_editor/test/super_textfield/super_textfield_gesture_scrolling_test.dart b/super_editor/test/super_textfield/super_textfield_gesture_scrolling_test.dart index a03fd15a76..b6fdd598a3 100644 --- a/super_editor/test/super_textfield/super_textfield_gesture_scrolling_test.dart +++ b/super_editor/test/super_textfield/super_textfield_gesture_scrolling_test.dart @@ -249,6 +249,98 @@ void main() { // Ensure the selection didn't change. expect(SuperTextFieldInspector.findSelection(), TextRange.empty); }); + + testWidgetsOnMobile("multi-line is vertically scrollable when text spans more lines than maxLines", (tester) async { + final initialText = "The first line of text in the field\n" + "The second line of text in the field\n" + "The third line of text in the field"; + final controller = AttributedTextEditingController( + text: AttributedText(initialText), + ); + + // Pump the widget tree with a SuperTextField with a maxHeight of 2 lines + // of text, which should overflow considering there are 3 lines of text. + await _pumpTestApp( + tester, + textController: controller, + minLines: 1, + maxLines: 2, + maxHeight: 40, + ); + + // Ensure the text field has not yet scrolled. + var textTop = tester.getTopRight(find.byType(SuperTextField)).dy; + var viewportTop = tester.getTopRight(find.byType(SuperText)).dy; + expect(textTop, moreOrLessEquals(viewportTop)); + + // Scroll down to reveal the last line of text. + await tester.drag(find.byType(SuperTextField), const Offset(0, -1000.0)); + await tester.pumpAndSettle(); + + // Ensure the text field has scrolled to the bottom. + var textBottom = tester.getBottomRight(find.byType(SuperTextField)).dy; + var viewportBottom = tester.getBottomRight(find.byType(SuperText)).dy; + expect(textBottom, moreOrLessEquals(viewportBottom)); + + // Scroll back up to the top of the text field. + await tester.drag(find.byType(SuperTextField), const Offset(0, 1000.0)); + await tester.pumpAndSettle(); + + // Ensure the text field has scrolled back to the top. + textTop = tester.getTopRight(find.byType(SuperTextField)).dy; + viewportTop = tester.getTopRight(find.byType(SuperText)).dy; + expect(textTop, moreOrLessEquals(viewportTop)); + }); + + testWidgetsOnDesktop("multi-line is vertically scrollable when text spans more lines than maxLines", (tester) async { + final initialText = "The first line of text in the field\n" + "The second line of text in the field\n" + "The third line of text in the field"; + final controller = AttributedTextEditingController( + text: AttributedText(initialText), + ); + + // Pump the widget tree with a SuperTextField with a maxHeight of 2 lines + // of text, which should overflow considering there are 3 lines of text. + await _pumpTestApp( + tester, + textController: controller, + minLines: 1, + maxLines: 2, + maxHeight: 40, + ); + + // Ensure the text field has not yet scrolled. + var textTop = tester.getTopRight(find.byType(SuperTextField)).dy; + var viewportTop = tester.getTopRight(find.byType(SuperText)).dy; + expect(textTop, moreOrLessEquals(viewportTop)); + + // Scroll down to reveal the last line of text. + await tester.drag( + find.byType(SuperTextField), + const Offset(0, -1000.0), + kind: PointerDeviceKind.trackpad, + ); + await tester.pumpAndSettle(); + + // Ensure the text field has scrolled to the bottom. + var textBottom = tester.getBottomRight(find.byType(SuperTextField)).dy; + var viewportBottom = tester.getBottomRight(find.byType(SuperText)).dy; + expect(textBottom, moreOrLessEquals(viewportBottom)); + + // Scroll back up to the top of the text field. + await tester.drag( + find.byType(SuperTextField), + const Offset(0, 1000.0), + kind: PointerDeviceKind.trackpad, + ); + await tester.pumpAndSettle(); + + // Ensure the text field has scrolled back to the top. + textTop = tester.getTopRight(find.byType(SuperTextField)).dy; + viewportTop = tester.getTopRight(find.byType(SuperText)).dy; + expect(textTop, moreOrLessEquals(viewportTop)); + }); }); }