mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
This reverts commit 18ca37542d
.
This commit is contained in:
parent
7ab111ea61
commit
ef5abcc6bf
@ -469,12 +469,6 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
|
||||
FocusNode _focusNode;
|
||||
FocusNode get _effectiveFocusNode => widget.focusNode ?? (_focusNode ??= FocusNode());
|
||||
|
||||
// The selection overlay should only be shown when the user is interacting
|
||||
// through a touch screen (via either a finger or a stylus). A mouse shouldn't
|
||||
// trigger the selection overlay.
|
||||
// For backwards-compatibility, we treat a null kind the same as touch.
|
||||
bool _shouldShowSelectionToolbar = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@ -507,26 +501,14 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
EditableTextState get _editableText => _editableTextKey.currentState;
|
||||
|
||||
void _requestKeyboard() {
|
||||
_editableText?.requestKeyboard();
|
||||
_editableTextKey.currentState?.requestKeyboard();
|
||||
}
|
||||
|
||||
RenderEditable get _renderEditable => _editableText.renderEditable;
|
||||
RenderEditable get _renderEditable => _editableTextKey.currentState.renderEditable;
|
||||
|
||||
void _handleTapDown(TapDownDetails details) {
|
||||
_renderEditable.handleTapDown(details);
|
||||
|
||||
// The selection overlay should only be shown when the user is interacting
|
||||
// through a touch screen (via either a finger or a stylus). A mouse shouldn't
|
||||
// trigger the selection overlay.
|
||||
// For backwards-compatibility, we treat a null kind the same as touch.
|
||||
final PointerDeviceKind kind = details.kind;
|
||||
_shouldShowSelectionToolbar =
|
||||
kind == null ||
|
||||
kind == PointerDeviceKind.touch ||
|
||||
kind == PointerDeviceKind.stylus;
|
||||
}
|
||||
|
||||
void _handleForcePressStarted(ForcePressDetails details) {
|
||||
@ -541,8 +523,7 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
|
||||
from: details.globalPosition,
|
||||
cause: SelectionChangedCause.forcePress,
|
||||
);
|
||||
if (_shouldShowSelectionToolbar)
|
||||
_editableText.showToolbar();
|
||||
_editableTextKey.currentState.showToolbar();
|
||||
}
|
||||
|
||||
void _handleSingleTapUp(TapUpDetails details) {
|
||||
@ -565,33 +546,12 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
|
||||
}
|
||||
|
||||
void _handleSingleLongTapEnd(LongPressEndDetails details) {
|
||||
if (_shouldShowSelectionToolbar)
|
||||
_editableText.showToolbar();
|
||||
_editableTextKey.currentState.showToolbar();
|
||||
}
|
||||
|
||||
void _handleDoubleTapDown(TapDownDetails details) {
|
||||
_renderEditable.selectWord(cause: SelectionChangedCause.tap);
|
||||
if (_shouldShowSelectionToolbar)
|
||||
_editableText.showToolbar();
|
||||
}
|
||||
|
||||
bool _shouldShowSelectionHandles(SelectionChangedCause cause) {
|
||||
// When the text field is activated by something that doesn't trigger the
|
||||
// selection overlay, we shouldn't show the handles either.
|
||||
if (!_shouldShowSelectionToolbar)
|
||||
return false;
|
||||
|
||||
// On iOS, we don't show handles when the selection is collapsed.
|
||||
if (_effectiveController.selection.isCollapsed)
|
||||
return false;
|
||||
|
||||
if (cause == SelectionChangedCause.keyboard)
|
||||
return false;
|
||||
|
||||
if (_effectiveController.text.isNotEmpty)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
_editableTextKey.currentState.showToolbar();
|
||||
}
|
||||
|
||||
void _handleMouseDragSelectionStart(DragStartDetails details) {
|
||||
@ -618,10 +578,7 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
|
||||
|
||||
void _handleSelectionChanged(TextSelection selection, SelectionChangedCause cause) {
|
||||
if (cause == SelectionChangedCause.longPress) {
|
||||
_editableText?.bringIntoView(selection.base);
|
||||
}
|
||||
if (_shouldShowSelectionHandles(cause)) {
|
||||
_editableText?.showHandles();
|
||||
_editableTextKey.currentState?.bringIntoView(selection.base);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -397,12 +397,7 @@ class MultiTapGestureRecognizer extends GestureRecognizer {
|
||||
longTapDelay: longTapDelay,
|
||||
);
|
||||
if (onTapDown != null)
|
||||
invokeCallback<void>('onTapDown', () {
|
||||
onTapDown(event.pointer, TapDownDetails(
|
||||
globalPosition: event.position,
|
||||
kind: event.kind,
|
||||
));
|
||||
});
|
||||
invokeCallback<void>('onTapDown', () => onTapDown(event.pointer, TapDownDetails(globalPosition: event.position)));
|
||||
}
|
||||
|
||||
@override
|
||||
@ -437,15 +432,7 @@ class MultiTapGestureRecognizer extends GestureRecognizer {
|
||||
void _dispatchLongTap(int pointer, Offset lastPosition) {
|
||||
assert(_gestureMap.containsKey(pointer));
|
||||
if (onLongTapDown != null)
|
||||
invokeCallback<void>('onLongTapDown', () {
|
||||
onLongTapDown(
|
||||
pointer,
|
||||
TapDownDetails(
|
||||
globalPosition: lastPosition,
|
||||
kind: getKindForPointer(pointer),
|
||||
),
|
||||
);
|
||||
});
|
||||
invokeCallback<void>('onLongTapDown', () => onLongTapDown(pointer, TapDownDetails(globalPosition: lastPosition)));
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -64,7 +64,7 @@ abstract class GestureRecognizer extends GestureArenaMember with DiagnosticableT
|
||||
/// by providing the optional [kind] argument. If [kind] is null,
|
||||
/// the recognizer will accept pointer events from all device kinds.
|
||||
/// {@endtemplate}
|
||||
GestureRecognizer({ this.debugOwner, PointerDeviceKind kind }) : _kindFilter = kind;
|
||||
GestureRecognizer({ this.debugOwner, PointerDeviceKind kind }) : _kind = kind;
|
||||
|
||||
/// The recognizer's owner.
|
||||
///
|
||||
@ -74,11 +74,7 @@ abstract class GestureRecognizer extends GestureArenaMember with DiagnosticableT
|
||||
|
||||
/// The kind of device that's allowed to be recognized. If null, events from
|
||||
/// all device kinds will be tracked and recognized.
|
||||
final PointerDeviceKind _kindFilter;
|
||||
|
||||
/// Holds a mapping between pointer IDs and the kind of devices they are
|
||||
/// coming from.
|
||||
final Map<int, PointerDeviceKind> _pointerToKind = <int, PointerDeviceKind>{};
|
||||
final PointerDeviceKind _kind;
|
||||
|
||||
/// Registers a new pointer that might be relevant to this gesture
|
||||
/// detector.
|
||||
@ -96,7 +92,6 @@ abstract class GestureRecognizer extends GestureArenaMember with DiagnosticableT
|
||||
/// This method is called for each and all pointers being added. In
|
||||
/// most cases, you want to override [addAllowedPointer] instead.
|
||||
void addPointer(PointerDownEvent event) {
|
||||
_pointerToKind[event.pointer] = event.kind;
|
||||
if (isPointerAllowed(event)) {
|
||||
addAllowedPointer(event);
|
||||
} else {
|
||||
@ -128,17 +123,7 @@ abstract class GestureRecognizer extends GestureArenaMember with DiagnosticableT
|
||||
bool isPointerAllowed(PointerDownEvent event) {
|
||||
// Currently, it only checks for device kind. But in the future we could check
|
||||
// for other things e.g. mouse button.
|
||||
return _kindFilter == null || _kindFilter == event.kind;
|
||||
}
|
||||
|
||||
/// For a given pointer ID, returns the device kind associated with it.
|
||||
///
|
||||
/// The pointer ID is expected to be a valid one i.e. an event was received
|
||||
/// with that pointer ID.
|
||||
@protected
|
||||
PointerDeviceKind getKindForPointer(int pointer) {
|
||||
assert(_pointerToKind.containsKey(pointer));
|
||||
return _pointerToKind[pointer];
|
||||
return _kind == null || _kind == event.kind;
|
||||
}
|
||||
|
||||
/// Releases any resources used by the object.
|
||||
@ -426,7 +411,7 @@ abstract class PrimaryPointerGestureRecognizer extends OneSequenceGestureRecogni
|
||||
primaryPointer = event.pointer;
|
||||
initialPosition = event.position;
|
||||
if (deadline != null)
|
||||
_timer = Timer(deadline, () => didExceedDeadlineWithEvent(event));
|
||||
_timer = Timer(deadline, didExceedDeadline);
|
||||
}
|
||||
}
|
||||
|
||||
@ -459,23 +444,12 @@ abstract class PrimaryPointerGestureRecognizer extends OneSequenceGestureRecogni
|
||||
|
||||
/// Override to be notified when [deadline] is exceeded.
|
||||
///
|
||||
/// You must override this method or [didExceedDeadlineWithEvent] if you
|
||||
/// supply a [deadline].
|
||||
/// You must override this method if you supply a [deadline].
|
||||
@protected
|
||||
void didExceedDeadline() {
|
||||
assert(deadline == null);
|
||||
}
|
||||
|
||||
/// Same as [didExceedDeadline] but receives the [event] that initiated the
|
||||
/// gesture.
|
||||
///
|
||||
/// You must override this method or [didExceedDeadline] if you supply a
|
||||
/// [deadline].
|
||||
@protected
|
||||
void didExceedDeadlineWithEvent(PointerDownEvent event) {
|
||||
didExceedDeadline();
|
||||
}
|
||||
|
||||
@override
|
||||
void acceptGesture(int pointer) {
|
||||
_gestureAccepted = true;
|
||||
|
@ -19,16 +19,11 @@ class TapDownDetails {
|
||||
/// Creates details for a [GestureTapDownCallback].
|
||||
///
|
||||
/// The [globalPosition] argument must not be null.
|
||||
TapDownDetails({
|
||||
this.globalPosition = Offset.zero,
|
||||
this.kind,
|
||||
}) : assert(globalPosition != null);
|
||||
TapDownDetails({ this.globalPosition = Offset.zero })
|
||||
: assert(globalPosition != null);
|
||||
|
||||
/// The global position at which the pointer contacted the screen.
|
||||
final Offset globalPosition;
|
||||
|
||||
/// The kind of the device that initiated the event.
|
||||
final PointerDeviceKind kind;
|
||||
}
|
||||
|
||||
/// Signature for when a pointer that might cause a tap has contacted the
|
||||
@ -203,15 +198,15 @@ class TapGestureRecognizer extends PrimaryPointerGestureRecognizer {
|
||||
}
|
||||
|
||||
@override
|
||||
void didExceedDeadlineWithEvent(PointerDownEvent event) {
|
||||
_checkDown(event.pointer);
|
||||
void didExceedDeadline() {
|
||||
_checkDown();
|
||||
}
|
||||
|
||||
@override
|
||||
void acceptGesture(int pointer) {
|
||||
super.acceptGesture(pointer);
|
||||
if (pointer == primaryPointer) {
|
||||
_checkDown(pointer);
|
||||
_checkDown();
|
||||
_wonArenaForPrimaryPointer = true;
|
||||
_checkUp();
|
||||
}
|
||||
@ -229,15 +224,10 @@ class TapGestureRecognizer extends PrimaryPointerGestureRecognizer {
|
||||
}
|
||||
}
|
||||
|
||||
void _checkDown(int pointer) {
|
||||
void _checkDown() {
|
||||
if (!_sentTapDown) {
|
||||
if (onTapDown != null)
|
||||
invokeCallback<void>('onTapDown', () {
|
||||
onTapDown(TapDownDetails(
|
||||
globalPosition: initialPosition,
|
||||
kind: getKindForPointer(pointer),
|
||||
));
|
||||
});
|
||||
invokeCallback<void>('onTapDown', () { onTapDown(TapDownDetails(globalPosition: initialPosition)); });
|
||||
_sentTapDown = true;
|
||||
}
|
||||
}
|
||||
|
@ -513,8 +513,6 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
|
||||
&& widget.decoration != null
|
||||
&& widget.decoration.counterText == null;
|
||||
|
||||
bool _shouldShowSelectionToolbar = true;
|
||||
|
||||
InputDecoration _getEffectiveDecoration() {
|
||||
final MaterialLocalizations localizations = MaterialLocalizations.of(context);
|
||||
final ThemeData themeData = Theme.of(context);
|
||||
@ -607,41 +605,17 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
EditableTextState get _editableText => _editableTextKey.currentState;
|
||||
|
||||
void _requestKeyboard() {
|
||||
_editableText?.requestKeyboard();
|
||||
}
|
||||
|
||||
bool _shouldShowSelectionHandles(SelectionChangedCause cause) {
|
||||
// When the text field is activated by something that doesn't trigger the
|
||||
// selection overlay, we shouldn't show the handles either.
|
||||
if (!_shouldShowSelectionToolbar)
|
||||
return false;
|
||||
|
||||
if (cause == SelectionChangedCause.keyboard)
|
||||
return false;
|
||||
|
||||
if (cause == SelectionChangedCause.longPress)
|
||||
return true;
|
||||
|
||||
if (_effectiveController.text.isNotEmpty)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
_editableTextKey.currentState?.requestKeyboard();
|
||||
}
|
||||
|
||||
void _handleSelectionChanged(TextSelection selection, SelectionChangedCause cause) {
|
||||
// iOS cursor doesn't move via a selection handle. The scroll happens
|
||||
// directly from new text selection changes.
|
||||
if (_shouldShowSelectionHandles(cause)) {
|
||||
_editableText?.showHandles();
|
||||
}
|
||||
|
||||
switch (Theme.of(context).platform) {
|
||||
case TargetPlatform.iOS:
|
||||
if (cause == SelectionChangedCause.longPress) {
|
||||
_editableText?.bringIntoView(selection.base);
|
||||
_editableTextKey.currentState?.bringIntoView(selection.base);
|
||||
}
|
||||
return;
|
||||
case TargetPlatform.android:
|
||||
@ -650,13 +624,6 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
|
||||
}
|
||||
}
|
||||
|
||||
/// Toggle the toolbar when a selection handle is tapped.
|
||||
void _handleSelectionHandleTapped() {
|
||||
if (_effectiveController.selection.isCollapsed) {
|
||||
_editableText.toggleToolbar();
|
||||
}
|
||||
}
|
||||
|
||||
InteractiveInkFeature _createInkFeature(Offset globalPosition) {
|
||||
final MaterialInkController inkController = Material.of(context);
|
||||
final ThemeData themeData = Theme.of(context);
|
||||
@ -696,16 +663,6 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
|
||||
void _handleTapDown(TapDownDetails details) {
|
||||
_renderEditable.handleTapDown(details);
|
||||
_startSplash(details.globalPosition);
|
||||
|
||||
// The selection overlay should only be shown when the user is interacting
|
||||
// through a touch screen (via either a finger or a stylus). A mouse shouldn't
|
||||
// trigger the selection overlay.
|
||||
// For backwards-compatibility, we treat a null kind the same as touch.
|
||||
final PointerDeviceKind kind = details.kind;
|
||||
_shouldShowSelectionToolbar =
|
||||
kind == null ||
|
||||
kind == PointerDeviceKind.touch ||
|
||||
kind == PointerDeviceKind.stylus;
|
||||
}
|
||||
|
||||
void _handleForcePressStarted(ForcePressDetails details) {
|
||||
@ -714,8 +671,7 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
|
||||
from: details.globalPosition,
|
||||
cause: SelectionChangedCause.forcePress,
|
||||
);
|
||||
if (_shouldShowSelectionToolbar)
|
||||
_editableTextKey.currentState.showToolbar();
|
||||
_editableTextKey.currentState.showToolbar();
|
||||
}
|
||||
}
|
||||
|
||||
@ -782,16 +738,13 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
|
||||
}
|
||||
|
||||
void _handleSingleLongTapEnd(LongPressEndDetails details) {
|
||||
print('long tap end');
|
||||
if (_shouldShowSelectionToolbar)
|
||||
_editableTextKey.currentState.showToolbar();
|
||||
_editableTextKey.currentState.showToolbar();
|
||||
}
|
||||
|
||||
void _handleDoubleTapDown(TapDownDetails details) {
|
||||
if (widget.selectionEnabled) {
|
||||
_renderEditable.selectWord(cause: SelectionChangedCause.doubleTap);
|
||||
if (_shouldShowSelectionToolbar)
|
||||
_editableTextKey.currentState.showToolbar();
|
||||
_editableTextKey.currentState.showToolbar();
|
||||
}
|
||||
}
|
||||
|
||||
@ -931,7 +884,6 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
|
||||
onSelectionChanged: _handleSelectionChanged,
|
||||
onEditingComplete: widget.onEditingComplete,
|
||||
onSubmitted: widget.onSubmitted,
|
||||
onSelectionHandleTapped: _handleSelectionHandleTapped,
|
||||
inputFormatters: formatters,
|
||||
rendererIgnoresPointer: true,
|
||||
cursorWidth: widget.cursorWidth,
|
||||
|
@ -290,7 +290,6 @@ class EditableText extends StatefulWidget {
|
||||
this.onEditingComplete,
|
||||
this.onSubmitted,
|
||||
this.onSelectionChanged,
|
||||
this.onSelectionHandleTapped,
|
||||
List<TextInputFormatter> inputFormatters,
|
||||
this.rendererIgnoresPointer = false,
|
||||
this.cursorWidth = 2.0,
|
||||
@ -646,9 +645,6 @@ class EditableText extends StatefulWidget {
|
||||
/// location).
|
||||
final SelectionChangedCallback onSelectionChanged;
|
||||
|
||||
/// {@macro flutter.widgets.textSelection.onSelectionHandleTapped}
|
||||
final VoidCallback onSelectionHandleTapped;
|
||||
|
||||
/// {@template flutter.widgets.editableText.inputFormatters}
|
||||
/// Optional input validation and formatting overrides.
|
||||
///
|
||||
@ -1139,9 +1135,10 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
||||
selectionControls: widget.selectionControls,
|
||||
selectionDelegate: this,
|
||||
dragStartBehavior: widget.dragStartBehavior,
|
||||
onSelectionHandleTapped: widget.onSelectionHandleTapped,
|
||||
);
|
||||
|
||||
final bool longPress = cause == SelectionChangedCause.longPress;
|
||||
if (cause != SelectionChangedCause.keyboard && (_value.text.isNotEmpty || longPress))
|
||||
_selectionOverlay.showHandles();
|
||||
if (widget.onSelectionChanged != null)
|
||||
widget.onSelectionChanged(selection, cause);
|
||||
}
|
||||
@ -1384,22 +1381,6 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
||||
_selectionOverlay?.hide();
|
||||
}
|
||||
|
||||
/// Toggles the visibility of the toolbar.
|
||||
void toggleToolbar() {
|
||||
assert(_selectionOverlay != null);
|
||||
if (_selectionOverlay.toolbarIsVisible) {
|
||||
hideToolbar();
|
||||
} else {
|
||||
showToolbar();
|
||||
}
|
||||
}
|
||||
|
||||
/// Shows the handles at the location of the current selection.
|
||||
void showHandles() {
|
||||
assert(_selectionOverlay != null);
|
||||
_selectionOverlay.showHandles();
|
||||
}
|
||||
|
||||
VoidCallback _semanticsOnCopy(TextSelectionControls controls) {
|
||||
return widget.selectionEnabled && _hasFocus && controls?.canCopy(this) == true
|
||||
? () => controls.handleCopy(this)
|
||||
|
@ -263,7 +263,6 @@ class TextSelectionOverlay {
|
||||
this.selectionControls,
|
||||
this.selectionDelegate,
|
||||
this.dragStartBehavior = DragStartBehavior.start,
|
||||
this.onSelectionHandleTapped,
|
||||
}) : assert(value != null),
|
||||
assert(context != null),
|
||||
_value = value {
|
||||
@ -317,14 +316,6 @@ class TextSelectionOverlay {
|
||||
/// * [DragGestureRecognizer.dragStartBehavior], which gives an example for the different behaviors.
|
||||
final DragStartBehavior dragStartBehavior;
|
||||
|
||||
/// {@template flutter.widgets.textSelection.onSelectionHandleTapped}
|
||||
/// A callback that's invoked when a selection handle is tapped.
|
||||
///
|
||||
/// Both regular taps and long presses invoke this callback, but a drag
|
||||
/// gesture won't.
|
||||
/// {@endtemplate}
|
||||
final VoidCallback onSelectionHandleTapped;
|
||||
|
||||
/// Controls the fade-in and fade-out animations for the toolbar and handles.
|
||||
static const Duration fadeDuration = Duration(milliseconds: 150);
|
||||
|
||||
@ -402,26 +393,17 @@ class TextSelectionOverlay {
|
||||
/// Whether the toolbar is currently visible.
|
||||
bool get toolbarIsVisible => _toolbar != null;
|
||||
|
||||
/// Hides the entire overlay including the toolbar and the handles.
|
||||
/// Hides the overlay.
|
||||
void hide() {
|
||||
if (_handles != null) {
|
||||
_handles[0].remove();
|
||||
_handles[1].remove();
|
||||
_handles = null;
|
||||
}
|
||||
if (_toolbar != null) {
|
||||
hideToolbar();
|
||||
}
|
||||
}
|
||||
|
||||
/// Hides the toolbar part of the overlay.
|
||||
///
|
||||
/// To hide the whole overlay, see [hide].
|
||||
void hideToolbar() {
|
||||
assert(_toolbar != null);
|
||||
_toolbarController.stop();
|
||||
_toolbar.remove();
|
||||
_toolbar?.remove();
|
||||
_toolbar = null;
|
||||
|
||||
_toolbarController.stop();
|
||||
}
|
||||
|
||||
/// Final cleanup.
|
||||
@ -436,7 +418,7 @@ class TextSelectionOverlay {
|
||||
return Container(); // hide the second handle when collapsed
|
||||
return _TextSelectionHandleOverlay(
|
||||
onSelectionHandleChanged: (TextSelection newSelection) { _handleSelectionHandleChanged(newSelection, position); },
|
||||
onSelectionHandleTapped: onSelectionHandleTapped,
|
||||
onSelectionHandleTapped: _handleSelectionHandleTapped,
|
||||
layerLink: layerLink,
|
||||
renderObject: renderObject,
|
||||
selection: _selection,
|
||||
@ -494,6 +476,17 @@ class TextSelectionOverlay {
|
||||
selectionDelegate.textEditingValue = _value.copyWith(selection: newSelection, composing: TextRange.empty);
|
||||
selectionDelegate.bringIntoView(textPosition);
|
||||
}
|
||||
|
||||
void _handleSelectionHandleTapped() {
|
||||
if (_value.selection.isCollapsed) {
|
||||
if (_toolbar != null) {
|
||||
_toolbar?.remove();
|
||||
_toolbar = null;
|
||||
} else {
|
||||
showToolbar();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This widget represents a single draggable text selection handle.
|
||||
@ -609,8 +602,7 @@ class _TextSelectionHandleOverlayState
|
||||
}
|
||||
|
||||
void _handleTap() {
|
||||
if (widget.onSelectionHandleTapped != null)
|
||||
widget.onSelectionHandleTapped();
|
||||
widget.onSelectionHandleTapped();
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -2008,195 +2008,6 @@ void main() {
|
||||
await gesture.removePointer();
|
||||
});
|
||||
|
||||
testWidgets('Tap does not show handles nor toolbar', (WidgetTester tester) async {
|
||||
final TextEditingController controller = TextEditingController(
|
||||
text: 'abc def ghi',
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
CupertinoApp(
|
||||
home: Center(
|
||||
child: CupertinoTextField(controller: controller),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Tap to trigger the text field.
|
||||
await tester.tap(find.byType(CupertinoTextField));
|
||||
await tester.pump();
|
||||
|
||||
final EditableTextState editableText = tester.state(find.byType(EditableText));
|
||||
expect(editableText.selectionOverlay.handlesAreVisible, isFalse);
|
||||
expect(editableText.selectionOverlay.toolbarIsVisible, isFalse);
|
||||
});
|
||||
|
||||
testWidgets('Long press shows toolbar but not handles', (WidgetTester tester) async {
|
||||
final TextEditingController controller = TextEditingController(
|
||||
text: 'abc def ghi',
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
CupertinoApp(
|
||||
home: Center(
|
||||
child: CupertinoTextField(controller: controller),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Long press to trigger the text field.
|
||||
await tester.longPress(find.byType(CupertinoTextField));
|
||||
await tester.pump();
|
||||
// A long press in Cupertino should position the cursor without any selection.
|
||||
expect(controller.selection.isCollapsed, isTrue);
|
||||
|
||||
final EditableTextState editableText = tester.state(find.byType(EditableText));
|
||||
expect(editableText.selectionOverlay.handlesAreVisible, isFalse);
|
||||
expect(editableText.selectionOverlay.toolbarIsVisible, isTrue);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'Double tap shows handles and toolbar if selection is not collapsed',
|
||||
(WidgetTester tester) async {
|
||||
final TextEditingController controller = TextEditingController(
|
||||
text: 'abc def ghi',
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
CupertinoApp(
|
||||
home: Center(
|
||||
child: CupertinoTextField(controller: controller),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final Offset hPos = textOffsetToPosition(tester, 9); // Position of 'h'.
|
||||
|
||||
// Double tap on 'h' to select 'ghi'.
|
||||
await tester.tapAt(hPos);
|
||||
await tester.pump(const Duration(milliseconds: 50));
|
||||
await tester.tapAt(hPos);
|
||||
await tester.pump();
|
||||
|
||||
final EditableTextState editableText = tester.state(find.byType(EditableText));
|
||||
expect(editableText.selectionOverlay.handlesAreVisible, isTrue);
|
||||
expect(editableText.selectionOverlay.toolbarIsVisible, isTrue);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'Double tap shows toolbar but not handles if selection is collapsed',
|
||||
(WidgetTester tester) async {
|
||||
final TextEditingController controller = TextEditingController(
|
||||
text: 'abc def ghi',
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
CupertinoApp(
|
||||
home: Center(
|
||||
child: CupertinoTextField(controller: controller),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final Offset textEndPos = textOffsetToPosition(tester, 11); // Position at the end of text.
|
||||
|
||||
// Double tap to place the cursor at the end.
|
||||
await tester.tapAt(textEndPos);
|
||||
await tester.pump(const Duration(milliseconds: 50));
|
||||
await tester.tapAt(textEndPos);
|
||||
await tester.pump();
|
||||
|
||||
final EditableTextState editableText = tester.state(find.byType(EditableText));
|
||||
expect(editableText.selectionOverlay.handlesAreVisible, isFalse);
|
||||
expect(editableText.selectionOverlay.toolbarIsVisible, isTrue);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'Mouse long press does not show handles nor toolbar',
|
||||
(WidgetTester tester) async {
|
||||
final TextEditingController controller = TextEditingController(
|
||||
text: 'abc def ghi',
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
CupertinoApp(
|
||||
home: Center(
|
||||
child: CupertinoTextField(controller: controller),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Long press to trigger the text field.
|
||||
final Offset textFieldPos = tester.getCenter(find.byType(CupertinoTextField));
|
||||
final TestGesture gesture = await tester.startGesture(
|
||||
textFieldPos,
|
||||
kind: PointerDeviceKind.mouse,
|
||||
);
|
||||
await tester.pump(const Duration(seconds: 2));
|
||||
await gesture.up();
|
||||
await tester.pump();
|
||||
|
||||
final EditableTextState editableText = tester.state(find.byType(EditableText));
|
||||
expect(editableText.selectionOverlay.toolbarIsVisible, isFalse);
|
||||
expect(editableText.selectionOverlay.handlesAreVisible, isFalse);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'Mouse double tap does not show handles nor toolbar',
|
||||
(WidgetTester tester) async {
|
||||
final TextEditingController controller = TextEditingController(
|
||||
text: 'abc def ghi',
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
CupertinoApp(
|
||||
home: Center(
|
||||
child: CupertinoTextField(controller: controller),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final EditableTextState editableText = tester.state(find.byType(EditableText));
|
||||
|
||||
// Double tap at the end of text.
|
||||
final Offset textEndPos = textOffsetToPosition(tester, 11); // Position at the end of text.
|
||||
TestGesture gesture = await tester.startGesture(
|
||||
textEndPos,
|
||||
kind: PointerDeviceKind.mouse,
|
||||
);
|
||||
await tester.pump(const Duration(milliseconds: 50));
|
||||
await gesture.up();
|
||||
await tester.pump();
|
||||
await gesture.down(textEndPos);
|
||||
await tester.pump();
|
||||
await gesture.up();
|
||||
await tester.pump();
|
||||
|
||||
expect(editableText.selectionOverlay.toolbarIsVisible, isFalse);
|
||||
expect(editableText.selectionOverlay.handlesAreVisible, isFalse);
|
||||
|
||||
final Offset hPos = textOffsetToPosition(tester, 9); // Position of 'h'.
|
||||
|
||||
// Double tap on 'h' to select 'ghi'.
|
||||
gesture = await tester.startGesture(
|
||||
hPos,
|
||||
kind: PointerDeviceKind.mouse,
|
||||
);
|
||||
await tester.pump(const Duration(milliseconds: 50));
|
||||
await gesture.up();
|
||||
await tester.pump();
|
||||
await gesture.down(hPos);
|
||||
await tester.pump();
|
||||
await gesture.up();
|
||||
await tester.pump();
|
||||
|
||||
expect(editableText.selectionOverlay.handlesAreVisible, isFalse);
|
||||
expect(editableText.selectionOverlay.toolbarIsVisible, isFalse);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'text field respects theme',
|
||||
(WidgetTester tester) async {
|
||||
|
@ -151,7 +151,7 @@ void main() {
|
||||
expect(tap.toString(), equalsIgnoringHashCodes('TapGestureRecognizer#00000(state: ready)'));
|
||||
const PointerEvent event = PointerDownEvent(pointer: 1, position: Offset(10.0, 10.0));
|
||||
tap.addPointer(event);
|
||||
tap.didExceedDeadlineWithEvent(event);
|
||||
tap.didExceedDeadline();
|
||||
expect(tap.toString(), equalsIgnoringHashCodes('TapGestureRecognizer#00000(state: possible, sent tap down)'));
|
||||
});
|
||||
}
|
||||
|
@ -5909,9 +5909,8 @@ void main() {
|
||||
),
|
||||
);
|
||||
|
||||
final EditableTextState state =
|
||||
tester.state<EditableTextState>(find.byType(EditableText));
|
||||
final RenderEditable renderEditable = state.renderEditable;
|
||||
final RenderEditable renderEditable =
|
||||
tester.state<EditableTextState>(find.byType(EditableText)).renderEditable;
|
||||
|
||||
await tester.tapAt(const Offset(20, 10));
|
||||
renderEditable.selectWord(cause: SelectionChangedCause.longPress);
|
||||
@ -5962,281 +5961,4 @@ void main() {
|
||||
|
||||
debugDefaultTargetPlatformOverride = null;
|
||||
});
|
||||
|
||||
testWidgets('Tap shows handles but not toolbar', (WidgetTester tester) async {
|
||||
final TextEditingController controller = TextEditingController(
|
||||
text: 'abc def ghi',
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Material(
|
||||
child: TextField(controller: controller),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Tap to trigger the text field.
|
||||
await tester.tap(find.byType(TextField));
|
||||
await tester.pump();
|
||||
|
||||
final EditableTextState editableText = tester.state(find.byType(EditableText));
|
||||
expect(editableText.selectionOverlay.handlesAreVisible, isTrue);
|
||||
expect(editableText.selectionOverlay.toolbarIsVisible, isFalse);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'Tap in empty text field does not show handles nor toolbar',
|
||||
(WidgetTester tester) async {
|
||||
final TextEditingController controller = TextEditingController();
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Material(
|
||||
child: TextField(controller: controller),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Tap to trigger the text field.
|
||||
await tester.tap(find.byType(TextField));
|
||||
await tester.pump();
|
||||
|
||||
final EditableTextState editableText = tester.state(find.byType(EditableText));
|
||||
expect(editableText.selectionOverlay.handlesAreVisible, isFalse);
|
||||
expect(editableText.selectionOverlay.toolbarIsVisible, isFalse);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets('Long press shows handles and toolbar', (WidgetTester tester) async {
|
||||
final TextEditingController controller = TextEditingController(
|
||||
text: 'abc def ghi',
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Material(
|
||||
child: TextField(controller: controller),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Long press to trigger the text field.
|
||||
await tester.longPress(find.byType(TextField));
|
||||
await tester.pump();
|
||||
|
||||
final EditableTextState editableText = tester.state(find.byType(EditableText));
|
||||
expect(editableText.selectionOverlay.handlesAreVisible, isTrue);
|
||||
expect(editableText.selectionOverlay.toolbarIsVisible, isTrue);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'Long press in empty text field shows handles and toolbar',
|
||||
(WidgetTester tester) async {
|
||||
final TextEditingController controller = TextEditingController();
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Material(
|
||||
child: TextField(controller: controller),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Tap to trigger the text field.
|
||||
await tester.longPress(find.byType(TextField));
|
||||
await tester.pump();
|
||||
|
||||
final EditableTextState editableText = tester.state(find.byType(EditableText));
|
||||
expect(editableText.selectionOverlay.handlesAreVisible, isTrue);
|
||||
expect(editableText.selectionOverlay.toolbarIsVisible, isTrue);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets('Double tap shows handles and toolbar', (WidgetTester tester) async {
|
||||
final TextEditingController controller = TextEditingController(
|
||||
text: 'abc def ghi',
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Material(
|
||||
child: TextField(controller: controller),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Double tap to trigger the text field.
|
||||
await tester.tap(find.byType(TextField));
|
||||
await tester.pump(const Duration(milliseconds: 50));
|
||||
await tester.tap(find.byType(TextField));
|
||||
await tester.pump();
|
||||
|
||||
final EditableTextState editableText = tester.state(find.byType(EditableText));
|
||||
expect(editableText.selectionOverlay.handlesAreVisible, isTrue);
|
||||
expect(editableText.selectionOverlay.toolbarIsVisible, isTrue);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'Double tap in empty text field shows toolbar but not handles',
|
||||
(WidgetTester tester) async {
|
||||
final TextEditingController controller = TextEditingController();
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Material(
|
||||
child: TextField(controller: controller),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Double tap to trigger the text field.
|
||||
await tester.tap(find.byType(TextField));
|
||||
await tester.pump(const Duration(milliseconds: 50));
|
||||
await tester.tap(find.byType(TextField));
|
||||
await tester.pump();
|
||||
|
||||
final EditableTextState editableText = tester.state(find.byType(EditableText));
|
||||
expect(editableText.selectionOverlay.handlesAreVisible, isFalse);
|
||||
expect(editableText.selectionOverlay.toolbarIsVisible, isTrue);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'Mouse tap does not show handles nor toolbar',
|
||||
(WidgetTester tester) async {
|
||||
final TextEditingController controller = TextEditingController(
|
||||
text: 'abc def ghi',
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Material(
|
||||
child: TextField(controller: controller),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Long press to trigger the text field.
|
||||
final Offset textFieldPos = tester.getCenter(find.byType(TextField));
|
||||
final TestGesture gesture = await tester.startGesture(
|
||||
textFieldPos,
|
||||
pointer: 7,
|
||||
kind: PointerDeviceKind.mouse,
|
||||
);
|
||||
await tester.pump();
|
||||
await gesture.up();
|
||||
await tester.pump();
|
||||
|
||||
final EditableTextState editableText = tester.state(find.byType(EditableText));
|
||||
expect(editableText.selectionOverlay.toolbarIsVisible, isFalse);
|
||||
expect(editableText.selectionOverlay.handlesAreVisible, isFalse);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'Mouse long press does not show handles nor toolbar',
|
||||
(WidgetTester tester) async {
|
||||
final TextEditingController controller = TextEditingController(
|
||||
text: 'abc def ghi',
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Material(
|
||||
child: TextField(controller: controller),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Long press to trigger the text field.
|
||||
final Offset textFieldPos = tester.getCenter(find.byType(TextField));
|
||||
final TestGesture gesture = await tester.startGesture(
|
||||
textFieldPos,
|
||||
pointer: 7,
|
||||
kind: PointerDeviceKind.mouse,
|
||||
);
|
||||
await tester.pump(const Duration(seconds: 2));
|
||||
await gesture.up();
|
||||
await tester.pump();
|
||||
|
||||
final EditableTextState editableText = tester.state(find.byType(EditableText));
|
||||
expect(editableText.selectionOverlay.toolbarIsVisible, isFalse);
|
||||
expect(editableText.selectionOverlay.handlesAreVisible, isFalse);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'Mouse double tap does not show handles nor toolbar',
|
||||
(WidgetTester tester) async {
|
||||
final TextEditingController controller = TextEditingController(
|
||||
text: 'abc def ghi',
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Material(
|
||||
child: TextField(controller: controller),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Double tap to trigger the text field.
|
||||
final Offset textFieldPos = tester.getCenter(find.byType(TextField));
|
||||
final TestGesture gesture = await tester.startGesture(
|
||||
textFieldPos,
|
||||
pointer: 7,
|
||||
kind: PointerDeviceKind.mouse,
|
||||
);
|
||||
await tester.pump(const Duration(milliseconds: 50));
|
||||
await gesture.up();
|
||||
await tester.pump();
|
||||
await gesture.down(textFieldPos);
|
||||
await tester.pump();
|
||||
await gesture.up();
|
||||
await tester.pump();
|
||||
|
||||
final EditableTextState editableText = tester.state(find.byType(EditableText));
|
||||
expect(editableText.selectionOverlay.toolbarIsVisible, isFalse);
|
||||
expect(editableText.selectionOverlay.handlesAreVisible, isFalse);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets('Tapping selection handles toggles the toolbar', (WidgetTester tester) async {
|
||||
final TextEditingController controller = TextEditingController(
|
||||
text: 'abc def ghi',
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Material(
|
||||
child: TextField(controller: controller),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Tap to position the cursor and show the selection handles.
|
||||
final Offset ePos = textOffsetToPosition(tester, 5); // Index of 'e'.
|
||||
await tester.tapAt(ePos, pointer: 7);
|
||||
|
||||
final EditableTextState editableText = tester.state(find.byType(EditableText));
|
||||
expect(editableText.selectionOverlay.toolbarIsVisible, isFalse);
|
||||
expect(editableText.selectionOverlay.handlesAreVisible, isTrue);
|
||||
|
||||
final RenderEditable renderEditable = findRenderEditable(tester);
|
||||
final List<TextSelectionPoint> endpoints = globalize(
|
||||
renderEditable.getEndpointsForSelection(controller.selection),
|
||||
renderEditable,
|
||||
);
|
||||
expect(endpoints.length, 1);
|
||||
|
||||
// Tap the handle to show the toolbar.
|
||||
final Offset handlePos = endpoints[0].point + const Offset(0.0, 1.0);
|
||||
await tester.tapAt(handlePos, pointer: 7);
|
||||
expect(editableText.selectionOverlay.toolbarIsVisible, isTrue);
|
||||
|
||||
// Tap the handle again to hide the toolbar.
|
||||
await tester.tapAt(handlePos, pointer: 7);
|
||||
expect(editableText.selectionOverlay.toolbarIsVisible, isFalse);
|
||||
});
|
||||
}
|
||||
|
@ -898,6 +898,51 @@ void main() {
|
||||
semantics.dispose();
|
||||
});
|
||||
|
||||
testWidgets('changing selection with keyboard does not show handles', (WidgetTester tester) async {
|
||||
const String value1 = 'Hello World';
|
||||
|
||||
controller.text = value1;
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: EditableText(
|
||||
backgroundCursorColor: Colors.grey,
|
||||
controller: controller,
|
||||
selectionControls: materialTextSelectionControls,
|
||||
focusNode: focusNode,
|
||||
style: textStyle,
|
||||
cursorColor: cursorColor,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Simulate selection change via tap to show handles.
|
||||
final RenderEditable render = tester.allRenderObjects
|
||||
.firstWhere((RenderObject o) => o.runtimeType == RenderEditable);
|
||||
render.onSelectionChanged(const TextSelection.collapsed(offset: 4), render,
|
||||
SelectionChangedCause.tap);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
final EditableTextState textState = tester.state(find.byType(EditableText));
|
||||
|
||||
expect(textState.selectionOverlay.handlesAreVisible, isTrue);
|
||||
expect(
|
||||
textState.selectionOverlay.selectionDelegate.textEditingValue.selection,
|
||||
const TextSelection.collapsed(offset: 4),
|
||||
);
|
||||
|
||||
// Simulate selection change via keyboard and expect handles to disappear.
|
||||
render.onSelectionChanged(const TextSelection.collapsed(offset: 10), render,
|
||||
SelectionChangedCause.keyboard);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(textState.selectionOverlay.handlesAreVisible, isFalse);
|
||||
expect(
|
||||
textState.selectionOverlay.selectionDelegate.textEditingValue.selection,
|
||||
const TextSelection.collapsed(offset: 10),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('exposes correct cursor movement semantics', (WidgetTester tester) async {
|
||||
final SemanticsTester semantics = SemanticsTester(tester);
|
||||
|
||||
@ -1989,7 +2034,6 @@ void main() {
|
||||
// Select the first word. Both handles should be visible.
|
||||
await tester.tapAt(const Offset(20, 10));
|
||||
renderEditable.selectWord(cause: SelectionChangedCause.longPress);
|
||||
state.showHandles();
|
||||
await tester.pump();
|
||||
await verifyVisibility(HandlePositionInViewport.leftEdge, true, HandlePositionInViewport.within, true);
|
||||
|
||||
@ -2011,7 +2055,6 @@ void main() {
|
||||
// Now that the second word has been dragged fully into view, select it.
|
||||
await tester.tapAt(const Offset(80, 10));
|
||||
renderEditable.selectWord(cause: SelectionChangedCause.longPress);
|
||||
state.showHandles();
|
||||
await tester.pump();
|
||||
await verifyVisibility(HandlePositionInViewport.within, true, HandlePositionInViewport.within, true);
|
||||
|
||||
@ -2056,7 +2099,6 @@ void main() {
|
||||
// Select the first word. Both handles should be visible.
|
||||
await tester.tapAt(const Offset(20, 10));
|
||||
state.renderEditable.selectWord(cause: SelectionChangedCause.longPress);
|
||||
state.showHandles();
|
||||
await tester.pump();
|
||||
final List<Positioned> positioned =
|
||||
find.byType(Positioned).evaluate().map((Element e) => e.widget).cast<Positioned>().toList();
|
||||
@ -2176,7 +2218,6 @@ void main() {
|
||||
// Select the first word. Both handles should be visible.
|
||||
await tester.tapAt(const Offset(20, 10));
|
||||
renderEditable.selectWord(cause: SelectionChangedCause.longPress);
|
||||
state.showHandles();
|
||||
await tester.pump();
|
||||
await verifyVisibility(HandlePositionInViewport.leftEdge, true, HandlePositionInViewport.within, true);
|
||||
|
||||
@ -2198,7 +2239,6 @@ void main() {
|
||||
// Now that the second word has been dragged fully into view, select it.
|
||||
await tester.tapAt(const Offset(80, 10));
|
||||
renderEditable.selectWord(cause: SelectionChangedCause.longPress);
|
||||
state.showHandles();
|
||||
await tester.pump();
|
||||
await verifyVisibility(HandlePositionInViewport.within, true, HandlePositionInViewport.within, true);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user