Support for disabling interactive TextField caret and selection (#22924)
Make it possible to disable TextField's default handlers for tap and long press. If enableInteractiveSelection is false then taps no longer move the text caret and long-press no longer selects text and shows the cut/copy/paste menu. Accessibility is similarly limited.
This commit is contained in:
parent
5d7938d6ec
commit
4f4050bf47
@ -88,8 +88,9 @@ class TextField extends StatefulWidget {
|
|||||||
/// characters may be entered, and the error counter and divider will
|
/// characters may be entered, and the error counter and divider will
|
||||||
/// switch to the [decoration.errorStyle] when the limit is exceeded.
|
/// switch to the [decoration.errorStyle] when the limit is exceeded.
|
||||||
///
|
///
|
||||||
/// The [textAlign], [autofocus], [obscureText], and [autocorrect] arguments
|
/// The [textAlign], [autofocus], [obscureText], [autocorrect],
|
||||||
/// must not be null.
|
/// [maxLengthEnforced], [scrollPadding], [maxLines], [maxLength],
|
||||||
|
/// and [enableInteractiveSelection] arguments must not be null.
|
||||||
///
|
///
|
||||||
/// See also:
|
/// See also:
|
||||||
///
|
///
|
||||||
@ -121,6 +122,7 @@ class TextField extends StatefulWidget {
|
|||||||
this.cursorColor,
|
this.cursorColor,
|
||||||
this.keyboardAppearance,
|
this.keyboardAppearance,
|
||||||
this.scrollPadding = const EdgeInsets.all(20.0),
|
this.scrollPadding = const EdgeInsets.all(20.0),
|
||||||
|
this.enableInteractiveSelection = true,
|
||||||
}) : assert(textAlign != null),
|
}) : assert(textAlign != null),
|
||||||
assert(autofocus != null),
|
assert(autofocus != null),
|
||||||
assert(obscureText != null),
|
assert(obscureText != null),
|
||||||
@ -130,6 +132,7 @@ class TextField extends StatefulWidget {
|
|||||||
assert(maxLines == null || maxLines > 0),
|
assert(maxLines == null || maxLines > 0),
|
||||||
assert(maxLength == null || maxLength > 0),
|
assert(maxLength == null || maxLength > 0),
|
||||||
keyboardType = keyboardType ?? (maxLines == 1 ? TextInputType.text : TextInputType.multiline),
|
keyboardType = keyboardType ?? (maxLines == 1 ? TextInputType.text : TextInputType.multiline),
|
||||||
|
assert(enableInteractiveSelection != null),
|
||||||
super(key: key);
|
super(key: key);
|
||||||
|
|
||||||
/// Controls the text being edited.
|
/// Controls the text being edited.
|
||||||
@ -343,6 +346,9 @@ class TextField extends StatefulWidget {
|
|||||||
/// Defaults to EdgeInserts.all(20.0).
|
/// Defaults to EdgeInserts.all(20.0).
|
||||||
final EdgeInsets scrollPadding;
|
final EdgeInsets scrollPadding;
|
||||||
|
|
||||||
|
/// {@macro flutter.widgets.editableText.enableInteractiveSelection}
|
||||||
|
final bool enableInteractiveSelection;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_TextFieldState createState() => _TextFieldState();
|
_TextFieldState createState() => _TextFieldState();
|
||||||
|
|
||||||
@ -487,7 +493,8 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _handleTap() {
|
void _handleTap() {
|
||||||
_renderEditable.handleTap();
|
if (widget.enableInteractiveSelection)
|
||||||
|
_renderEditable.handleTap();
|
||||||
_requestKeyboard();
|
_requestKeyboard();
|
||||||
_confirmCurrentSplash();
|
_confirmCurrentSplash();
|
||||||
}
|
}
|
||||||
@ -497,7 +504,8 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _handleLongPress() {
|
void _handleLongPress() {
|
||||||
_renderEditable.handleLongPress();
|
if (widget.enableInteractiveSelection)
|
||||||
|
_renderEditable.handleLongPress();
|
||||||
_confirmCurrentSplash();
|
_confirmCurrentSplash();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -567,9 +575,11 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
|
|||||||
autocorrect: widget.autocorrect,
|
autocorrect: widget.autocorrect,
|
||||||
maxLines: widget.maxLines,
|
maxLines: widget.maxLines,
|
||||||
selectionColor: themeData.textSelectionColor,
|
selectionColor: themeData.textSelectionColor,
|
||||||
selectionControls: themeData.platform == TargetPlatform.iOS
|
selectionControls: widget.enableInteractiveSelection
|
||||||
? cupertinoTextSelectionControls
|
? (themeData.platform == TargetPlatform.iOS
|
||||||
: materialTextSelectionControls,
|
? cupertinoTextSelectionControls
|
||||||
|
: materialTextSelectionControls)
|
||||||
|
: null,
|
||||||
onChanged: widget.onChanged,
|
onChanged: widget.onChanged,
|
||||||
onEditingComplete: widget.onEditingComplete,
|
onEditingComplete: widget.onEditingComplete,
|
||||||
onSubmitted: widget.onSubmitted,
|
onSubmitted: widget.onSubmitted,
|
||||||
@ -581,6 +591,7 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
|
|||||||
cursorColor: widget.cursorColor ?? Theme.of(context).cursorColor,
|
cursorColor: widget.cursorColor ?? Theme.of(context).cursorColor,
|
||||||
scrollPadding: widget.scrollPadding,
|
scrollPadding: widget.scrollPadding,
|
||||||
keyboardAppearance: keyboardAppearance,
|
keyboardAppearance: keyboardAppearance,
|
||||||
|
enableInteractiveSelection: widget.enableInteractiveSelection,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -74,6 +74,7 @@ class TextFormField extends FormField<String> {
|
|||||||
bool enabled = true,
|
bool enabled = true,
|
||||||
Brightness keyboardAppearance,
|
Brightness keyboardAppearance,
|
||||||
EdgeInsets scrollPadding = const EdgeInsets.all(20.0),
|
EdgeInsets scrollPadding = const EdgeInsets.all(20.0),
|
||||||
|
bool enableInteractiveSelection = true,
|
||||||
}) : assert(initialValue == null || controller == null),
|
}) : assert(initialValue == null || controller == null),
|
||||||
assert(textAlign != null),
|
assert(textAlign != null),
|
||||||
assert(autofocus != null),
|
assert(autofocus != null),
|
||||||
@ -84,6 +85,7 @@ class TextFormField extends FormField<String> {
|
|||||||
assert(scrollPadding != null),
|
assert(scrollPadding != null),
|
||||||
assert(maxLines == null || maxLines > 0),
|
assert(maxLines == null || maxLines > 0),
|
||||||
assert(maxLength == null || maxLength > 0),
|
assert(maxLength == null || maxLength > 0),
|
||||||
|
assert(enableInteractiveSelection != null),
|
||||||
super(
|
super(
|
||||||
key: key,
|
key: key,
|
||||||
initialValue: controller != null ? controller.text : (initialValue ?? ''),
|
initialValue: controller != null ? controller.text : (initialValue ?? ''),
|
||||||
@ -117,6 +119,7 @@ class TextFormField extends FormField<String> {
|
|||||||
enabled: enabled,
|
enabled: enabled,
|
||||||
scrollPadding: scrollPadding,
|
scrollPadding: scrollPadding,
|
||||||
keyboardAppearance: keyboardAppearance,
|
keyboardAppearance: keyboardAppearance,
|
||||||
|
enableInteractiveSelection: enableInteractiveSelection,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -118,6 +118,8 @@ class RenderEditable extends RenderBox {
|
|||||||
///
|
///
|
||||||
/// The [offset] is required and must not be null. You can use [new
|
/// The [offset] is required and must not be null. You can use [new
|
||||||
/// ViewportOffset.zero] if you have no need for scrolling.
|
/// ViewportOffset.zero] if you have no need for scrolling.
|
||||||
|
///
|
||||||
|
/// The [enableInteractiveSelection] argument must not be null.
|
||||||
RenderEditable({
|
RenderEditable({
|
||||||
TextSpan text,
|
TextSpan text,
|
||||||
@required TextDirection textDirection,
|
@required TextDirection textDirection,
|
||||||
@ -137,6 +139,7 @@ class RenderEditable extends RenderBox {
|
|||||||
Locale locale,
|
Locale locale,
|
||||||
double cursorWidth = 1.0,
|
double cursorWidth = 1.0,
|
||||||
Radius cursorRadius,
|
Radius cursorRadius,
|
||||||
|
bool enableInteractiveSelection = true,
|
||||||
@required this.textSelectionDelegate,
|
@required this.textSelectionDelegate,
|
||||||
}) : assert(textAlign != null),
|
}) : assert(textAlign != null),
|
||||||
assert(textDirection != null, 'RenderEditable created without a textDirection.'),
|
assert(textDirection != null, 'RenderEditable created without a textDirection.'),
|
||||||
@ -145,8 +148,9 @@ class RenderEditable extends RenderBox {
|
|||||||
assert(offset != null),
|
assert(offset != null),
|
||||||
assert(ignorePointer != null),
|
assert(ignorePointer != null),
|
||||||
assert(obscureText != null),
|
assert(obscureText != null),
|
||||||
|
assert(enableInteractiveSelection != null),
|
||||||
assert(textSelectionDelegate != null),
|
assert(textSelectionDelegate != null),
|
||||||
_textPainter = TextPainter(
|
_textPainter = TextPainter(
|
||||||
text: text,
|
text: text,
|
||||||
textAlign: textAlign,
|
textAlign: textAlign,
|
||||||
textDirection: textDirection,
|
textDirection: textDirection,
|
||||||
@ -162,6 +166,7 @@ class RenderEditable extends RenderBox {
|
|||||||
_offset = offset,
|
_offset = offset,
|
||||||
_cursorWidth = cursorWidth,
|
_cursorWidth = cursorWidth,
|
||||||
_cursorRadius = cursorRadius,
|
_cursorRadius = cursorRadius,
|
||||||
|
_enableInteractiveSelection = enableInteractiveSelection,
|
||||||
_obscureText = obscureText {
|
_obscureText = obscureText {
|
||||||
assert(_showCursor != null);
|
assert(_showCursor != null);
|
||||||
assert(!_showCursor.value || cursorColor != null);
|
assert(!_showCursor.value || cursorColor != null);
|
||||||
@ -692,6 +697,20 @@ class RenderEditable extends RenderBox {
|
|||||||
markNeedsPaint();
|
markNeedsPaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// If false, [describeSemanticsConfiguration] will not set the
|
||||||
|
/// configuration's cursor motion or set selection callbacks.
|
||||||
|
///
|
||||||
|
/// True by default.
|
||||||
|
bool get enableInteractiveSelection => _enableInteractiveSelection;
|
||||||
|
bool _enableInteractiveSelection;
|
||||||
|
set enableInteractiveSelection(bool value) {
|
||||||
|
if (_enableInteractiveSelection == value)
|
||||||
|
return;
|
||||||
|
_enableInteractiveSelection = value;
|
||||||
|
markNeedsTextLayout();
|
||||||
|
markNeedsSemanticsUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void describeSemanticsConfiguration(SemanticsConfiguration config) {
|
void describeSemanticsConfiguration(SemanticsConfiguration config) {
|
||||||
super.describeSemanticsConfiguration(config);
|
super.describeSemanticsConfiguration(config);
|
||||||
@ -705,10 +724,10 @@ class RenderEditable extends RenderBox {
|
|||||||
..isFocused = hasFocus
|
..isFocused = hasFocus
|
||||||
..isTextField = true;
|
..isTextField = true;
|
||||||
|
|
||||||
if (hasFocus)
|
if (hasFocus && enableInteractiveSelection)
|
||||||
config.onSetSelection = _handleSetSelection;
|
config.onSetSelection = _handleSetSelection;
|
||||||
|
|
||||||
if (_selection?.isValid == true) {
|
if (enableInteractiveSelection && _selection?.isValid == true) {
|
||||||
config.textSelection = _selection;
|
config.textSelection = _selection;
|
||||||
if (_textPainter.getOffsetBefore(_selection.extentOffset) != null) {
|
if (_textPainter.getOffsetBefore(_selection.extentOffset) != null) {
|
||||||
config
|
config
|
||||||
|
@ -183,7 +183,8 @@ class EditableText extends StatefulWidget {
|
|||||||
/// default to [TextInputType.multiline].
|
/// default to [TextInputType.multiline].
|
||||||
///
|
///
|
||||||
/// The [controller], [focusNode], [style], [cursorColor], [textAlign],
|
/// The [controller], [focusNode], [style], [cursorColor], [textAlign],
|
||||||
/// and [rendererIgnoresPointer], arguments must not be null.
|
/// [rendererIgnoresPointer], and [enableInteractiveSelection] arguments must
|
||||||
|
/// not be null.
|
||||||
EditableText({
|
EditableText({
|
||||||
Key key,
|
Key key,
|
||||||
@required this.controller,
|
@required this.controller,
|
||||||
@ -213,6 +214,7 @@ class EditableText extends StatefulWidget {
|
|||||||
this.cursorRadius,
|
this.cursorRadius,
|
||||||
this.scrollPadding = const EdgeInsets.all(20.0),
|
this.scrollPadding = const EdgeInsets.all(20.0),
|
||||||
this.keyboardAppearance = Brightness.light,
|
this.keyboardAppearance = Brightness.light,
|
||||||
|
this.enableInteractiveSelection = true,
|
||||||
}) : assert(controller != null),
|
}) : assert(controller != null),
|
||||||
assert(focusNode != null),
|
assert(focusNode != null),
|
||||||
assert(obscureText != null),
|
assert(obscureText != null),
|
||||||
@ -224,6 +226,7 @@ class EditableText extends StatefulWidget {
|
|||||||
assert(autofocus != null),
|
assert(autofocus != null),
|
||||||
assert(rendererIgnoresPointer != null),
|
assert(rendererIgnoresPointer != null),
|
||||||
assert(scrollPadding != null),
|
assert(scrollPadding != null),
|
||||||
|
assert(enableInteractiveSelection != null),
|
||||||
keyboardType = keyboardType ?? (maxLines == 1 ? TextInputType.text : TextInputType.multiline),
|
keyboardType = keyboardType ?? (maxLines == 1 ? TextInputType.text : TextInputType.multiline),
|
||||||
inputFormatters = maxLines == 1
|
inputFormatters = maxLines == 1
|
||||||
? (
|
? (
|
||||||
@ -399,6 +402,17 @@ class EditableText extends StatefulWidget {
|
|||||||
/// Defaults to EdgeInserts.all(20.0).
|
/// Defaults to EdgeInserts.all(20.0).
|
||||||
final EdgeInsets scrollPadding;
|
final EdgeInsets scrollPadding;
|
||||||
|
|
||||||
|
/// {@template flutter.widgets.editableText.enableInteractiveSelection}
|
||||||
|
/// If true, then long-pressing this TextField will select text and show the
|
||||||
|
/// cut/copy/paste menu, and tapping will move the text caret.
|
||||||
|
///
|
||||||
|
/// True by default.
|
||||||
|
///
|
||||||
|
/// If false, most of the accessibility support for selecting text, copy
|
||||||
|
/// and paste, and moving the caret will be disabled.
|
||||||
|
/// {@endtemplate}
|
||||||
|
final bool enableInteractiveSelection;
|
||||||
|
|
||||||
/// Setting this property to true makes the cursor stop blinking and stay visible on the screen continually.
|
/// Setting this property to true makes the cursor stop blinking and stay visible on the screen continually.
|
||||||
/// This property is most useful for testing purposes.
|
/// This property is most useful for testing purposes.
|
||||||
///
|
///
|
||||||
@ -864,12 +878,30 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
|||||||
_selectionOverlay?.hide();
|
_selectionOverlay?.hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VoidCallback _semanticsOnCopy(TextSelectionControls controls) {
|
||||||
|
return widget.enableInteractiveSelection && _hasFocus && controls?.canCopy(this) == true
|
||||||
|
? () => controls.handleCopy(this)
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
VoidCallback _semanticsOnCut(TextSelectionControls controls) {
|
||||||
|
return widget.enableInteractiveSelection && _hasFocus && controls?.canCut(this) == true
|
||||||
|
? () => controls.handleCut(this)
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
VoidCallback _semanticsOnPaste(TextSelectionControls controls) {
|
||||||
|
return widget.enableInteractiveSelection &&_hasFocus && controls?.canPaste(this) == true
|
||||||
|
? () => controls.handlePaste(this)
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
FocusScope.of(context).reparentIfNeeded(widget.focusNode);
|
FocusScope.of(context).reparentIfNeeded(widget.focusNode);
|
||||||
super.build(context); // See AutomaticKeepAliveClientMixin.
|
super.build(context); // See AutomaticKeepAliveClientMixin.
|
||||||
final TextSelectionControls controls = widget.selectionControls;
|
|
||||||
|
|
||||||
|
final TextSelectionControls controls = widget.selectionControls;
|
||||||
return Scrollable(
|
return Scrollable(
|
||||||
excludeFromSemantics: true,
|
excludeFromSemantics: true,
|
||||||
axisDirection: _isMultiline ? AxisDirection.down : AxisDirection.right,
|
axisDirection: _isMultiline ? AxisDirection.down : AxisDirection.right,
|
||||||
@ -879,9 +911,9 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
|||||||
return CompositedTransformTarget(
|
return CompositedTransformTarget(
|
||||||
link: _layerLink,
|
link: _layerLink,
|
||||||
child: Semantics(
|
child: Semantics(
|
||||||
onCopy: _hasFocus && controls?.canCopy(this) == true ? () => controls.handleCopy(this) : null,
|
onCopy: _semanticsOnCopy(controls),
|
||||||
onCut: _hasFocus && controls?.canCut(this) == true ? () => controls.handleCut(this) : null,
|
onCut: _semanticsOnCut(controls),
|
||||||
onPaste: _hasFocus && controls?.canPaste(this) == true ? () => controls.handlePaste(this) : null,
|
onPaste: _semanticsOnPaste(controls),
|
||||||
child: _Editable(
|
child: _Editable(
|
||||||
key: _editableKey,
|
key: _editableKey,
|
||||||
textSpan: buildTextSpan(),
|
textSpan: buildTextSpan(),
|
||||||
@ -903,6 +935,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
|||||||
rendererIgnoresPointer: widget.rendererIgnoresPointer,
|
rendererIgnoresPointer: widget.rendererIgnoresPointer,
|
||||||
cursorWidth: widget.cursorWidth,
|
cursorWidth: widget.cursorWidth,
|
||||||
cursorRadius: widget.cursorRadius,
|
cursorRadius: widget.cursorRadius,
|
||||||
|
enableInteractiveSelection: widget.enableInteractiveSelection,
|
||||||
textSelectionDelegate: this,
|
textSelectionDelegate: this,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -967,9 +1000,11 @@ class _Editable extends LeafRenderObjectWidget {
|
|||||||
this.rendererIgnoresPointer = false,
|
this.rendererIgnoresPointer = false,
|
||||||
this.cursorWidth,
|
this.cursorWidth,
|
||||||
this.cursorRadius,
|
this.cursorRadius,
|
||||||
|
this.enableInteractiveSelection = true,
|
||||||
this.textSelectionDelegate,
|
this.textSelectionDelegate,
|
||||||
}) : assert(textDirection != null),
|
}) : assert(textDirection != null),
|
||||||
assert(rendererIgnoresPointer != null),
|
assert(rendererIgnoresPointer != null),
|
||||||
|
assert(enableInteractiveSelection != null),
|
||||||
super(key: key);
|
super(key: key);
|
||||||
|
|
||||||
final TextSpan textSpan;
|
final TextSpan textSpan;
|
||||||
@ -991,6 +1026,7 @@ class _Editable extends LeafRenderObjectWidget {
|
|||||||
final bool rendererIgnoresPointer;
|
final bool rendererIgnoresPointer;
|
||||||
final double cursorWidth;
|
final double cursorWidth;
|
||||||
final Radius cursorRadius;
|
final Radius cursorRadius;
|
||||||
|
final bool enableInteractiveSelection;
|
||||||
final TextSelectionDelegate textSelectionDelegate;
|
final TextSelectionDelegate textSelectionDelegate;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -1014,6 +1050,7 @@ class _Editable extends LeafRenderObjectWidget {
|
|||||||
obscureText: obscureText,
|
obscureText: obscureText,
|
||||||
cursorWidth: cursorWidth,
|
cursorWidth: cursorWidth,
|
||||||
cursorRadius: cursorRadius,
|
cursorRadius: cursorRadius,
|
||||||
|
enableInteractiveSelection: enableInteractiveSelection,
|
||||||
textSelectionDelegate: textSelectionDelegate,
|
textSelectionDelegate: textSelectionDelegate,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -404,6 +404,34 @@ void main() {
|
|||||||
expect(controller.selection.extentOffset, tapIndex);
|
expect(controller.selection.extentOffset, tapIndex);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('enableInteractiveSelection = false, tap', (WidgetTester tester) async {
|
||||||
|
final TextEditingController controller = TextEditingController();
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
overlay(
|
||||||
|
child: TextField(
|
||||||
|
controller: controller,
|
||||||
|
enableInteractiveSelection: false,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
expect(controller.selection.baseOffset, -1);
|
||||||
|
expect(controller.selection.extentOffset, -1);
|
||||||
|
|
||||||
|
const String testValue = 'abc def ghi';
|
||||||
|
await tester.enterText(find.byType(TextField), testValue);
|
||||||
|
await skipPastScrollingAnimation(tester);
|
||||||
|
|
||||||
|
// Tap would ordinarily reposition the caret.
|
||||||
|
final int tapIndex = testValue.indexOf('e');
|
||||||
|
final Offset ePos = textOffsetToPosition(tester, tapIndex);
|
||||||
|
await tester.tapAt(ePos);
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
expect(controller.selection.baseOffset, -1);
|
||||||
|
expect(controller.selection.extentOffset, -1);
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('Can long press to select', (WidgetTester tester) async {
|
testWidgets('Can long press to select', (WidgetTester tester) async {
|
||||||
final TextEditingController controller = TextEditingController();
|
final TextEditingController controller = TextEditingController();
|
||||||
|
|
||||||
@ -434,6 +462,37 @@ void main() {
|
|||||||
expect(controller.selection.extentOffset, testValue.indexOf('f')+1);
|
expect(controller.selection.extentOffset, testValue.indexOf('f')+1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('enableInteractiveSelection = false, long-press', (WidgetTester tester) async {
|
||||||
|
final TextEditingController controller = TextEditingController();
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
overlay(
|
||||||
|
child: TextField(
|
||||||
|
controller: controller,
|
||||||
|
enableInteractiveSelection: false,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const String testValue = 'abc def ghi';
|
||||||
|
await tester.enterText(find.byType(TextField), testValue);
|
||||||
|
expect(controller.value.text, testValue);
|
||||||
|
await skipPastScrollingAnimation(tester);
|
||||||
|
|
||||||
|
expect(controller.selection.isCollapsed, true);
|
||||||
|
|
||||||
|
// Long press the 'e' to select 'def'.
|
||||||
|
final Offset ePos = textOffsetToPosition(tester, testValue.indexOf('e'));
|
||||||
|
final TestGesture gesture = await tester.startGesture(ePos, pointer: 7);
|
||||||
|
await tester.pump(const Duration(seconds: 2));
|
||||||
|
await gesture.up();
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
expect(controller.selection.isCollapsed, true);
|
||||||
|
expect(controller.selection.baseOffset, -1);
|
||||||
|
expect(controller.selection.extentOffset, -1);
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('Can drag handles to change selection', (WidgetTester tester) async {
|
testWidgets('Can drag handles to change selection', (WidgetTester tester) async {
|
||||||
final TextEditingController controller = TextEditingController();
|
final TextEditingController controller = TextEditingController();
|
||||||
|
|
||||||
@ -2530,6 +2589,48 @@ void main() {
|
|||||||
semantics.dispose();
|
semantics.dispose();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('TextField semantics, enableInteractiveSelection = false', (WidgetTester tester) async {
|
||||||
|
final SemanticsTester semantics = SemanticsTester(tester);
|
||||||
|
final TextEditingController controller = TextEditingController();
|
||||||
|
final Key key = UniqueKey();
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
overlay(
|
||||||
|
child: TextField(
|
||||||
|
key: key,
|
||||||
|
controller: controller,
|
||||||
|
enableInteractiveSelection: false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.tap(find.byKey(key));
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
expect(semantics, hasSemantics(TestSemantics.root(
|
||||||
|
children: <TestSemantics>[
|
||||||
|
TestSemantics.rootChild(
|
||||||
|
id: 1,
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
actions: <SemanticsAction>[
|
||||||
|
SemanticsAction.tap,
|
||||||
|
// Absent the following because enableInteractiveSelection: false
|
||||||
|
// SemanticsAction.moveCursorBackwardByCharacter,
|
||||||
|
// SemanticsAction.moveCursorBackwardByWord,
|
||||||
|
// SemanticsAction.setSelection,
|
||||||
|
// SemanticsAction.paste,
|
||||||
|
],
|
||||||
|
flags: <SemanticsFlag>[
|
||||||
|
SemanticsFlag.isTextField,
|
||||||
|
SemanticsFlag.isFocused,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
), ignoreTransform: true, ignoreRect: true));
|
||||||
|
|
||||||
|
semantics.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('TextField semantics for selections', (WidgetTester tester) async {
|
testWidgets('TextField semantics for selections', (WidgetTester tester) async {
|
||||||
final SemanticsTester semantics = SemanticsTester(tester);
|
final SemanticsTester semantics = SemanticsTester(tester);
|
||||||
final TextEditingController controller = TextEditingController()
|
final TextEditingController controller = TextEditingController()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user