Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
f5e4d2b4
Unverified
Commit
f5e4d2b4
authored
Jul 29, 2022
by
Greg Spencer
Committed by
GitHub
Jul 29, 2022
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Replace FocusTrap with TapRegionSurface (#107262)
parent
347de992
Changes
21
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
21 changed files
with
1808 additions
and
591 deletions
+1808
-591
density.dart
dev/manual_tests/lib/density.dart
+5
-7
text_field_tap_region.0.dart
...s/api/lib/widgets/tap_region/text_field_tap_region.0.dart
+250
-0
text_field_tap_region.0_test.dart
...test/widgets/tap_region/text_field_tap_region.0_test.dart
+100
-0
bottom_tab_bar.dart
packages/flutter/lib/src/cupertino/bottom_tab_bar.dart
+20
-16
text_field.dart
packages/flutter/lib/src/cupertino/text_field.dart
+20
-12
text_field.dart
packages/flutter/lib/src/material/text_field.dart
+25
-6
app.dart
packages/flutter/lib/src/widgets/app.dart
+5
-2
editable_text.dart
packages/flutter/lib/src/widgets/editable_text.dart
+171
-88
routes.dart
packages/flutter/lib/src/widgets/routes.dart
+33
-218
tap_region.dart
packages/flutter/lib/src/widgets/tap_region.dart
+562
-0
text_selection.dart
packages/flutter/lib/src/widgets/text_selection.dart
+24
-17
widgets.dart
packages/flutter/lib/widgets.dart
+1
-0
text_field_test.dart
packages/flutter/test/cupertino/text_field_test.dart
+140
-0
debug_test.dart
packages/flutter/test/material/debug_test.dart
+1
-1
input_decorator_test.dart
packages/flutter/test/material/input_decorator_test.dart
+3
-3
text_field_focus_test.dart
packages/flutter/test/material/text_field_focus_test.dart
+17
-14
text_field_test.dart
packages/flutter/test/material/text_field_test.dart
+220
-0
editable_text_shortcuts_test.dart
...es/flutter/test/widgets/editable_text_shortcuts_test.dart
+1
-0
editable_text_test.dart
packages/flutter/test/widgets/editable_text_test.dart
+22
-4
routes_test.dart
packages/flutter/test/widgets/routes_test.dart
+0
-203
tap_region_test.dart
packages/flutter/test/widgets/tap_region_test.dart
+188
-0
No files found.
dev/manual_tests/lib/density.dart
View file @
f5e4d2b4
...
...
@@ -631,13 +631,11 @@ class _MyHomePageState extends State<MyHomePage> {
data:
Theme
.
of
(
context
).
copyWith
(
visualDensity:
_model
.
density
),
child:
Directionality
(
textDirection:
_model
.
rtl
?
TextDirection
.
rtl
:
TextDirection
.
ltr
,
child:
Scrollbar
(
child:
MediaQuery
(
data:
MediaQuery
.
of
(
context
).
copyWith
(
textScaleFactor:
_model
.
size
),
child:
SizedBox
.
expand
(
child:
ListView
(
children:
tiles
,
),
child:
MediaQuery
(
data:
MediaQuery
.
of
(
context
).
copyWith
(
textScaleFactor:
_model
.
size
),
child:
SizedBox
.
expand
(
child:
ListView
(
children:
tiles
,
),
),
),
...
...
examples/api/lib/widgets/tap_region/text_field_tap_region.0.dart
0 → 100644
View file @
f5e4d2b4
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/// Flutter code sample for [TextFieldTapRegion].
import
'package:flutter/material.dart'
;
import
'package:flutter/services.dart'
;
void
main
(
)
=>
runApp
(
const
TapRegionApp
());
class
TapRegionApp
extends
StatelessWidget
{
const
TapRegionApp
({
super
.
key
});
@override
Widget
build
(
BuildContext
context
)
{
return
MaterialApp
(
home:
Scaffold
(
appBar:
AppBar
(
title:
const
Text
(
'TextFieldTapRegion Example'
)),
body:
const
TextFieldTapRegionExample
(),
),
);
}
}
class
TextFieldTapRegionExample
extends
StatefulWidget
{
const
TextFieldTapRegionExample
({
super
.
key
});
@override
State
<
TextFieldTapRegionExample
>
createState
()
=>
_TextFieldTapRegionExampleState
();
}
class
_TextFieldTapRegionExampleState
extends
State
<
TextFieldTapRegionExample
>
{
int
value
=
0
;
@override
Widget
build
(
BuildContext
context
)
{
return
ListView
(
children:
<
Widget
>[
Center
(
child:
Padding
(
padding:
const
EdgeInsets
.
all
(
20.0
),
child:
SizedBox
(
width:
150
,
height:
80
,
child:
IntegerSpinnerField
(
value:
value
,
autofocus:
true
,
onChanged:
(
int
newValue
)
{
if
(
value
==
newValue
)
{
// Avoid unnecessary redraws.
return
;
}
setState
(()
{
// Update the value and redraw.
value
=
newValue
;
});
},
),
),
),
),
],
);
}
}
/// An integer example of the generic [SpinnerField] that validates input and
/// increments by a delta.
class
IntegerSpinnerField
extends
StatelessWidget
{
const
IntegerSpinnerField
({
super
.
key
,
required
this
.
value
,
this
.
autofocus
=
false
,
this
.
delta
=
1
,
this
.
onChanged
,
});
final
int
value
;
final
bool
autofocus
;
final
int
delta
;
final
ValueChanged
<
int
>?
onChanged
;
@override
Widget
build
(
BuildContext
context
)
{
return
SpinnerField
<
int
>(
value:
value
,
onChanged:
onChanged
,
autofocus:
autofocus
,
fromString:
(
String
stringValue
)
=>
int
.
tryParse
(
stringValue
)
??
value
,
increment:
(
int
i
)
=>
i
+
delta
,
decrement:
(
int
i
)
=>
i
-
delta
,
// Add a text formatter that only allows integer values and a leading
// minus sign.
inputFormatters:
<
TextInputFormatter
>[
TextInputFormatter
.
withFunction
(
(
TextEditingValue
oldValue
,
TextEditingValue
newValue
)
{
String
newString
;
if
(
newValue
.
text
.
startsWith
(
'-'
))
{
newString
=
'-
${newValue.text.replaceAll(RegExp(r'\D'), '')}
'
;
}
else
{
newString
=
newValue
.
text
.
replaceAll
(
RegExp
(
r'\D'
),
''
);
}
return
newValue
.
copyWith
(
text:
newString
,
selection:
newValue
.
selection
.
copyWith
(
baseOffset:
newValue
.
selection
.
baseOffset
.
clamp
(
0
,
newString
.
length
),
extentOffset:
newValue
.
selection
.
extentOffset
.
clamp
(
0
,
newString
.
length
),
),
);
},
)
],
);
}
}
/// A generic "spinner" field example which adds extra buttons next to a
/// [TextField] to increment and decrement the value.
///
/// This widget uses [TextFieldTapRegion] to indicate that tapping on the
/// spinner buttons should not cause the text field to lose focus.
class
SpinnerField
<
T
>
extends
StatefulWidget
{
SpinnerField
({
super
.
key
,
required
this
.
value
,
required
this
.
fromString
,
this
.
autofocus
=
false
,
String
Function
(
T
value
)?
asString
,
this
.
increment
,
this
.
decrement
,
this
.
onChanged
,
this
.
inputFormatters
=
const
<
TextInputFormatter
>[],
})
:
asString
=
asString
??
((
T
value
)
=>
value
.
toString
());
final
T
value
;
final
T
Function
(
T
value
)?
increment
;
final
T
Function
(
T
value
)?
decrement
;
final
String
Function
(
T
value
)
asString
;
final
T
Function
(
String
value
)
fromString
;
final
ValueChanged
<
T
>?
onChanged
;
final
List
<
TextInputFormatter
>
inputFormatters
;
final
bool
autofocus
;
@override
State
<
SpinnerField
<
T
>>
createState
()
=>
_SpinnerFieldState
<
T
>();
}
class
_SpinnerFieldState
<
T
>
extends
State
<
SpinnerField
<
T
>>
{
TextEditingController
controller
=
TextEditingController
();
@override
void
initState
()
{
super
.
initState
();
_updateText
(
widget
.
asString
(
widget
.
value
));
}
@override
void
dispose
()
{
controller
.
dispose
();
super
.
dispose
();
}
@override
void
didUpdateWidget
(
covariant
SpinnerField
<
T
>
oldWidget
)
{
super
.
didUpdateWidget
(
oldWidget
);
if
(
oldWidget
.
asString
!=
widget
.
asString
||
oldWidget
.
value
!=
widget
.
value
)
{
final
String
newText
=
widget
.
asString
(
widget
.
value
);
_updateText
(
newText
);
}
}
void
_updateText
(
String
text
,
{
bool
collapsed
=
true
})
{
if
(
text
!=
controller
.
text
)
{
controller
.
value
=
TextEditingValue
(
text:
text
,
selection:
collapsed
?
TextSelection
.
collapsed
(
offset:
text
.
length
)
:
TextSelection
(
baseOffset:
0
,
extentOffset:
text
.
length
),
);
}
}
void
_spin
(
T
Function
(
T
value
)?
spinFunction
)
{
if
(
spinFunction
==
null
)
{
return
;
}
final
T
newValue
=
spinFunction
(
widget
.
value
);
widget
.
onChanged
?.
call
(
newValue
);
_updateText
(
widget
.
asString
(
newValue
),
collapsed:
false
);
}
void
_increment
()
{
_spin
(
widget
.
increment
);
}
void
_decrement
()
{
_spin
(
widget
.
decrement
);
}
@override
Widget
build
(
BuildContext
context
)
{
return
CallbackShortcuts
(
bindings:
<
ShortcutActivator
,
VoidCallback
>{
const
SingleActivator
(
LogicalKeyboardKey
.
arrowUp
):
_increment
,
const
SingleActivator
(
LogicalKeyboardKey
.
arrowDown
):
_decrement
,
},
child:
Row
(
children:
<
Widget
>[
Expanded
(
child:
TextField
(
autofocus:
widget
.
autofocus
,
inputFormatters:
widget
.
inputFormatters
,
decoration:
const
InputDecoration
(
border:
OutlineInputBorder
(),
),
onChanged:
(
String
value
)
=>
widget
.
onChanged
?.
call
(
widget
.
fromString
(
value
)),
controller:
controller
,
textAlign:
TextAlign
.
center
,
),
),
const
SizedBox
(
width:
12
),
// Without this TextFieldTapRegion, tapping on the buttons below would
// increment the value, but it would cause the text field to be
// unfocused, since tapping outside of a text field should unfocus it
// on non-mobile platforms.
TextFieldTapRegion
(
child:
Column
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
children:
<
Widget
>[
Expanded
(
child:
OutlinedButton
(
onPressed:
_increment
,
child:
const
Icon
(
Icons
.
add
),
),
),
Expanded
(
child:
OutlinedButton
(
onPressed:
_decrement
,
child:
const
Icon
(
Icons
.
remove
),
),
),
],
),
)
],
),
);
}
}
examples/api/test/widgets/tap_region/text_field_tap_region.0_test.dart
0 → 100644
View file @
f5e4d2b4
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'package:flutter/material.dart'
;
import
'package:flutter_api_samples/widgets/tap_region/text_field_tap_region.0.dart'
as
example
;
import
'package:flutter_test/flutter_test.dart'
;
void
main
(
)
{
testWidgets
(
'shows a text field with a zero count, and the spinner buttons'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
example
.
TapRegionApp
(),
);
expect
(
find
.
byType
(
TextField
),
findsOneWidget
);
expect
(
getFieldValue
(
tester
).
text
,
equals
(
'0'
));
expect
(
find
.
byIcon
(
Icons
.
add
),
findsOneWidget
);
expect
(
find
.
byIcon
(
Icons
.
remove
),
findsOneWidget
);
});
testWidgets
(
'tapping increment/decrement works'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
example
.
TapRegionApp
(),
);
await
tester
.
pump
();
expect
(
getFieldValue
(
tester
).
text
,
equals
(
'0'
));
expect
(
getFieldValue
(
tester
).
selection
,
equals
(
const
TextSelection
.
collapsed
(
offset:
1
)),
);
await
tester
.
tap
(
find
.
byIcon
(
Icons
.
add
));
await
tester
.
pumpAndSettle
();
expect
(
getFieldValue
(
tester
).
text
,
equals
(
'1'
));
expect
(
getFieldValue
(
tester
).
selection
,
equals
(
const
TextSelection
(
baseOffset:
0
,
extentOffset:
1
)),
);
await
tester
.
tap
(
find
.
byIcon
(
Icons
.
remove
));
await
tester
.
pumpAndSettle
();
await
tester
.
tap
(
find
.
byIcon
(
Icons
.
remove
));
await
tester
.
pumpAndSettle
();
expect
(
getFieldValue
(
tester
).
text
,
equals
(
'-1'
));
expect
(
getFieldValue
(
tester
).
selection
,
equals
(
const
TextSelection
(
baseOffset:
0
,
extentOffset:
2
)),
);
});
testWidgets
(
'entering text and then incrementing/decrementing works'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
example
.
TapRegionApp
(),
);
await
tester
.
pump
();
await
tester
.
tap
(
find
.
byIcon
(
Icons
.
add
));
await
tester
.
pumpAndSettle
();
expect
(
getFieldValue
(
tester
).
text
,
equals
(
'1'
));
expect
(
getFieldValue
(
tester
).
selection
,
equals
(
const
TextSelection
(
baseOffset:
0
,
extentOffset:
1
)),
);
await
tester
.
enterText
(
find
.
byType
(
TextField
),
'123'
);
await
tester
.
pumpAndSettle
();
expect
(
getFieldValue
(
tester
).
text
,
equals
(
'123'
));
expect
(
getFieldValue
(
tester
).
selection
,
equals
(
const
TextSelection
.
collapsed
(
offset:
3
)),
);
await
tester
.
tap
(
find
.
byIcon
(
Icons
.
remove
));
await
tester
.
pumpAndSettle
();
await
tester
.
tap
(
find
.
byIcon
(
Icons
.
remove
));
await
tester
.
pumpAndSettle
();
expect
(
getFieldValue
(
tester
).
text
,
equals
(
'121'
));
expect
(
getFieldValue
(
tester
).
selection
,
equals
(
const
TextSelection
(
baseOffset:
0
,
extentOffset:
3
)),
);
final
FocusNode
textFieldFocusNode
=
Focus
.
of
(
tester
.
element
(
find
.
byWidgetPredicate
((
Widget
widget
)
{
return
widget
.
runtimeType
.
toString
()
==
'_Editable'
;
}),
),
);
expect
(
textFieldFocusNode
.
hasPrimaryFocus
,
isTrue
);
});
}
TextEditingValue
getFieldValue
(
WidgetTester
tester
)
{
return
(
tester
.
widget
(
find
.
byType
(
TextField
))
as
TextField
).
controller
!.
value
;
}
packages/flutter/lib/src/cupertino/bottom_tab_bar.dart
View file @
f5e4d2b4
...
...
@@ -230,22 +230,26 @@ class CupertinoTabBar extends StatelessWidget implements PreferredSizeWidget {
_wrapActiveItem
(
context
,
Expanded
(
child:
Semantics
(
selected:
active
,
hint:
localizations
.
tabSemanticsLabel
(
tabIndex:
index
+
1
,
tabCount:
items
.
length
,
),
child:
MouseRegion
(
cursor:
kIsWeb
?
SystemMouseCursors
.
click
:
MouseCursor
.
defer
,
child:
GestureDetector
(
behavior:
HitTestBehavior
.
opaque
,
onTap:
onTap
==
null
?
null
:
()
{
onTap
!(
index
);
},
child:
Padding
(
padding:
const
EdgeInsets
.
only
(
bottom:
4.0
),
child:
Column
(
mainAxisAlignment:
MainAxisAlignment
.
end
,
children:
_buildSingleTabItem
(
items
[
index
],
active
),
// Make tab items part of the EditableText tap region so that
// switching tabs doesn't unfocus text fields.
child:
TextFieldTapRegion
(
child:
Semantics
(
selected:
active
,
hint:
localizations
.
tabSemanticsLabel
(
tabIndex:
index
+
1
,
tabCount:
items
.
length
,
),
child:
MouseRegion
(
cursor:
kIsWeb
?
SystemMouseCursors
.
click
:
MouseCursor
.
defer
,
child:
GestureDetector
(
behavior:
HitTestBehavior
.
opaque
,
onTap:
onTap
==
null
?
null
:
()
{
onTap
!(
index
);
},
child:
Padding
(
padding:
const
EdgeInsets
.
only
(
bottom:
4.0
),
child:
Column
(
mainAxisAlignment:
MainAxisAlignment
.
end
,
children:
_buildSingleTabItem
(
items
[
index
],
active
),
),
),
),
),
...
...
packages/flutter/lib/src/cupertino/text_field.dart
View file @
f5e4d2b4
...
...
@@ -251,6 +251,7 @@ class CupertinoTextField extends StatefulWidget {
this
.
onChanged
,
this
.
onEditingComplete
,
this
.
onSubmitted
,
this
.
onTapOutside
,
this
.
inputFormatters
,
this
.
enabled
,
this
.
cursorWidth
=
2.0
,
...
...
@@ -411,6 +412,7 @@ class CupertinoTextField extends StatefulWidget {
this
.
onChanged
,
this
.
onEditingComplete
,
this
.
onSubmitted
,
this
.
onTapOutside
,
this
.
inputFormatters
,
this
.
enabled
,
this
.
cursorWidth
=
2.0
,
...
...
@@ -692,6 +694,9 @@ class CupertinoTextField extends StatefulWidget {
/// the user is done editing.
final
ValueChanged
<
String
>?
onSubmitted
;
/// {@macro flutter.widgets.editableText.onTapOutside}
final
TapRegionCallback
?
onTapOutside
;
/// {@macro flutter.widgets.editableText.inputFormatters}
final
List
<
TextInputFormatter
>?
inputFormatters
;
...
...
@@ -1277,6 +1282,7 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with Restoratio
onSelectionChanged:
_handleSelectionChanged
,
onEditingComplete:
widget
.
onEditingComplete
,
onSubmitted:
widget
.
onSubmitted
,
onTapOutside:
widget
.
onTapOutside
,
inputFormatters:
formatters
,
rendererIgnoresPointer:
true
,
cursorWidth:
widget
.
cursorWidth
,
...
...
@@ -1315,18 +1321,20 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with Restoratio
_requestKeyboard
();
},
onDidGainAccessibilityFocus:
handleDidGainAccessibilityFocus
,
child:
IgnorePointer
(
ignoring:
!
enabled
,
child:
Container
(
decoration:
effectiveDecoration
,
color:
!
enabled
&&
effectiveDecoration
==
null
?
disabledColor
:
null
,
child:
_selectionGestureDetectorBuilder
.
buildGestureDetector
(
behavior:
HitTestBehavior
.
translucent
,
child:
Align
(
alignment:
Alignment
(-
1.0
,
_textAlignVertical
.
y
),
widthFactor:
1.0
,
heightFactor:
1.0
,
child:
_addTextDependentAttachments
(
paddedEditable
,
textStyle
,
placeholderStyle
),
child:
TextFieldTapRegion
(
child:
IgnorePointer
(
ignoring:
!
enabled
,
child:
Container
(
decoration:
effectiveDecoration
,
color:
!
enabled
&&
effectiveDecoration
==
null
?
disabledColor
:
null
,
child:
_selectionGestureDetectorBuilder
.
buildGestureDetector
(
behavior:
HitTestBehavior
.
translucent
,
child:
Align
(
alignment:
Alignment
(-
1.0
,
_textAlignVertical
.
y
),
widthFactor:
1.0
,
heightFactor:
1.0
,
child:
_addTextDependentAttachments
(
paddedEditable
,
textStyle
,
placeholderStyle
),
),
),
),
),
...
...
packages/flutter/lib/src/material/text_field.dart
View file @
f5e4d2b4
...
...
@@ -320,6 +320,7 @@ class TextField extends StatefulWidget {
bool
?
enableInteractiveSelection
,
this
.
selectionControls
,
this
.
onTap
,
this
.
onTapOutside
,
this
.
mouseCursor
,
this
.
buildCounter
,
this
.
scrollController
,
...
...
@@ -675,6 +676,24 @@ class TextField extends StatefulWidget {
/// {@endtemplate}
final
GestureTapCallback
?
onTap
;
/// {@macro flutter.widgets.editableText.onTapOutside}
///
/// {@tool dartpad}
/// This example shows how to use a `TextFieldTapRegion` to wrap a set of
/// "spinner" buttons that increment and decrement a value in the [TextField]
/// without causing the text field to lose keyboard focus.
///
/// This example includes a generic `SpinnerField<T>` class that you can copy
/// into your own project and customize.
///
/// ** See code in examples/api/lib/widgets/tap_region/text_field_tap_region.0.dart **
/// {@end-tool}
///
/// See also:
///
/// * [TapRegion] for how the region group is determined.
final
TapRegionCallback
?
onTapOutside
;
/// The cursor for a mouse pointer when it enters or is hovering over the
/// widget.
///
...
...
@@ -1267,6 +1286,7 @@ class _TextFieldState extends State<TextField> with RestorationMixin implements
onSubmitted:
widget
.
onSubmitted
,
onAppPrivateCommand:
widget
.
onAppPrivateCommand
,
onSelectionHandleTapped:
_handleSelectionHandleTapped
,
onTapOutside:
widget
.
onTapOutside
,
inputFormatters:
formatters
,
rendererIgnoresPointer:
true
,
mouseCursor:
MouseCursor
.
defer
,
// TextField will handle the cursor
...
...
@@ -1334,12 +1354,11 @@ class _TextFieldState extends State<TextField> with RestorationMixin implements
semanticsMaxValueLength
=
null
;
}
return
FocusTrapArea
(
focusNode:
focusNode
,
child:
MouseRegion
(
cursor:
effectiveMouseCursor
,
onEnter:
(
PointerEnterEvent
event
)
=>
_handleHover
(
true
),
onExit:
(
PointerExitEvent
event
)
=>
_handleHover
(
false
),
return
MouseRegion
(
cursor:
effectiveMouseCursor
,
onEnter:
(
PointerEnterEvent
event
)
=>
_handleHover
(
true
),
onExit:
(
PointerExitEvent
event
)
=>
_handleHover
(
false
),
child:
TextFieldTapRegion
(
child:
IgnorePointer
(
ignoring:
!
_isEnabled
,
child:
AnimatedBuilder
(
...
...
packages/flutter/lib/src/widgets/app.dart
View file @
f5e4d2b4
...
...
@@ -26,6 +26,7 @@ import 'scrollable.dart';
import
'semantics_debugger.dart'
;
import
'shared_app_data.dart'
;
import
'shortcuts.dart'
;
import
'tap_region.dart'
;
import
'text.dart'
;
import
'title.dart'
;
import
'widget_inspector.dart'
;
...
...
@@ -1740,8 +1741,10 @@ class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver {
actions:
widget
.
actions
??
WidgetsApp
.
defaultActions
,
child:
FocusTraversalGroup
(
policy:
ReadingOrderTraversalPolicy
(),
child:
ShortcutRegistrar
(
child:
child
,
child:
TapRegionSurface
(
child:
ShortcutRegistrar
(
child:
child
,
),
),
),
),
...
...
packages/flutter/lib/src/widgets/editable_text.dart
View file @
f5e4d2b4
This diff is collapsed.
Click to expand it.
packages/flutter/lib/src/widgets/routes.dart
View file @
f5e4d2b4
This diff is collapsed.
Click to expand it.
packages/flutter/lib/src/widgets/tap_region.dart
0 → 100644
View file @
f5e4d2b4
This diff is collapsed.
Click to expand it.
packages/flutter/lib/src/widgets/text_selection.dart
View file @
f5e4d2b4
...
...
@@ -20,6 +20,7 @@ import 'editable_text.dart';
import
'framework.dart'
;
import
'gesture_detector.dart'
;
import
'overlay.dart'
;
import
'tap_region.dart'
;
import
'ticker_provider.dart'
;
import
'transitions.dart'
;
...
...
@@ -958,8 +959,10 @@ class SelectionOverlay {
dragStartBehavior:
dragStartBehavior
,
);
}
return
ExcludeSemantics
(
child:
handle
,
return
TextFieldTapRegion
(
child:
ExcludeSemantics
(
child:
handle
,
),
);
}
...
...
@@ -983,8 +986,10 @@ class SelectionOverlay {
dragStartBehavior:
dragStartBehavior
,
);
}
return
ExcludeSemantics
(
child:
handle
,
return
TextFieldTapRegion
(
child:
ExcludeSemantics
(
child:
handle
,
),
);
}
...
...
@@ -1015,19 +1020,21 @@ class SelectionOverlay {
selectionEndpoints
.
first
.
point
.
dy
-
lineHeightAtStart
,
);
return
Directionality
(
textDirection:
Directionality
.
of
(
this
.
context
),
child:
_SelectionToolbarOverlay
(
preferredLineHeight:
lineHeightAtStart
,
toolbarLocation:
toolbarLocation
,
layerLink:
toolbarLayerLink
,
editingRegion:
editingRegion
,
selectionControls:
selectionControls
,
midpoint:
midpoint
,
selectionEndpoints:
selectionEndpoints
,
visibility:
toolbarVisible
,
selectionDelegate:
selectionDelegate
,
clipboardStatus:
clipboardStatus
,
return
TextFieldTapRegion
(
child:
Directionality
(
textDirection:
Directionality
.
of
(
this
.
context
),
child:
_SelectionToolbarOverlay
(
preferredLineHeight:
lineHeightAtStart
,
toolbarLocation:
toolbarLocation
,
layerLink:
toolbarLayerLink
,
editingRegion:
editingRegion
,
selectionControls:
selectionControls
,
midpoint:
midpoint
,
selectionEndpoints:
selectionEndpoints
,
visibility:
toolbarVisible
,
selectionDelegate:
selectionDelegate
,
clipboardStatus:
clipboardStatus
,
),
),
);
}
...
...
packages/flutter/lib/widgets.dart
View file @
f5e4d2b4
...
...
@@ -128,6 +128,7 @@ export 'src/widgets/slotted_render_object_widget.dart';
export
'src/widgets/spacer.dart'
;
export
'src/widgets/status_transitions.dart'
;
export
'src/widgets/table.dart'
;
export
'src/widgets/tap_region.dart'
;
export
'src/widgets/text.dart'
;
export
'src/widgets/text_editing_intents.dart'
;
export
'src/widgets/text_selection.dart'
;
...
...
packages/flutter/test/cupertino/text_field_test.dart
View file @
f5e4d2b4
...
...
@@ -5960,4 +5960,144 @@ void main() {
expect
(
controller
.
selection
.
extentOffset
,
5
);
},
variant:
const
TargetPlatformVariant
(<
TargetPlatform
>{
TargetPlatform
.
iOS
,
TargetPlatform
.
macOS
}));
});
group
(
'TapRegion integration'
,
()
{
testWidgets
(
'Tapping outside loses focus on desktop'
,
(
WidgetTester
tester
)
async
{
final
FocusNode
focusNode
=
FocusNode
(
debugLabel:
'Test Node'
);
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
SizedBox
(
width:
100
,
height:
100
,
child:
CupertinoTextField
(
autofocus:
true
,
focusNode:
focusNode
,
),
),
),
),
);
await
tester
.
pump
();
expect
(
focusNode
.
hasPrimaryFocus
,
isTrue
);
// Tap outside the border.
await
tester
.
tapAt
(
const
Offset
(
10
,
10
));
await
tester
.
pump
();
expect
(
focusNode
.
hasPrimaryFocus
,
isFalse
);
},
variant:
TargetPlatformVariant
.
desktop
());
testWidgets
(
"Tapping outside doesn't lose focus on mobile"
,
(
WidgetTester
tester
)
async
{
final
FocusNode
focusNode
=
FocusNode
(
debugLabel:
'Test Node'
);
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
SizedBox
(
width:
100
,
height:
100
,
child:
CupertinoTextField
(
autofocus:
true
,
focusNode:
focusNode
,
),
),
),
),
);
await
tester
.
pump
();
expect
(
focusNode
.
hasPrimaryFocus
,
isTrue
);
// Tap just outside the border, but not inside the EditableText.
await
tester
.
tapAt
(
const
Offset
(
10
,
10
));
await
tester
.
pump
();
// Focus is lost on mobile browsers, but not mobile apps.
expect
(
focusNode
.
hasPrimaryFocus
,
kIsWeb
?
isFalse
:
isTrue
);
},
variant:
TargetPlatformVariant
.
mobile
());
testWidgets
(
"tapping on toolbar doesn't lose focus"
,
(
WidgetTester
tester
)
async
{
final
TextEditingController
controller
;
final
EditableTextState
state
;
controller
=
TextEditingController
(
text:
'A B C'
);
final
FocusNode
focusNode
=
FocusNode
(
debugLabel:
'Test Node'
);
await
tester
.
pumpWidget
(
CupertinoApp
(
debugShowCheckedModeBanner:
false
,
home:
CupertinoPageScaffold
(
child:
Align
(
child:
SizedBox
(
width:
200
,
height:
200
,
child:
CupertinoTextField
(
autofocus:
true
,
focusNode:
focusNode
,
controller:
controller
,
),
),
),
),
),
);
await
tester
.
pump
();
expect
(
focusNode
.
hasPrimaryFocus
,
isTrue
);
state
=
tester
.
state
<
EditableTextState
>(
find
.
byType
(
EditableText
));
// Select the first 2 words.
state
.
renderEditable
.
selectPositionAt
(
from:
textOffsetToPosition
(
tester
,
0
),
to:
textOffsetToPosition
(
tester
,
2
),
cause:
SelectionChangedCause
.
tap
,
);
final
Offset
midSelection
=
textOffsetToPosition
(
tester
,
2
);
// Right click the selection.
final
TestGesture
gesture
=
await
tester
.
startGesture
(
midSelection
,
kind:
PointerDeviceKind
.
mouse
,
buttons:
kSecondaryMouseButton
,
);
await
tester
.
pump
();
await
gesture
.
up
();
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
'Copy'
),
findsOneWidget
);
// Copy the first word.
await
tester
.
tap
(
find
.
text
(
'Copy'
));
await
tester
.
pump
();
expect
(
focusNode
.
hasPrimaryFocus
,
isTrue
);
},
variant:
TargetPlatformVariant
.
all
(),
skip:
kIsWeb
,
// [intended] The toolbar isn't rendered by Flutter on the web, it's rendered by the browser.
);
testWidgets
(
"Tapping on border doesn't lose focus"
,
(
WidgetTester
tester
)
async
{
final
FocusNode
focusNode
=
FocusNode
(
debugLabel:
'Test Node'
);
await
tester
.
pumpWidget
(
CupertinoApp
(
home:
Center
(
child:
SizedBox
(
width:
100
,
height:
100
,
child:
CupertinoTextField
(
autofocus:
true
,
focusNode:
focusNode
,
),
),
),
),
);
await
tester
.
pump
();
expect
(
focusNode
.
hasPrimaryFocus
,
isTrue
);
final
Rect
borderBox
=
tester
.
getRect
(
find
.
byType
(
CupertinoTextField
));
// Tap just inside the border, but not inside the EditableText.
await
tester
.
tapAt
(
borderBox
.
topLeft
+
const
Offset
(
1
,
1
));
await
tester
.
pump
();
expect
(
focusNode
.
hasPrimaryFocus
,
isTrue
);
},
variant:
TargetPlatformVariant
.
all
());
});
}
packages/flutter/test/material/debug_test.dart
View file @
f5e4d2b4
...
...
@@ -134,7 +134,6 @@ void main() {
' _FadeUpwardsPageTransition
\n
'
' AnimatedBuilder
\n
'
' RepaintBoundary
\n
'
' FocusTrap
\n
'
' _FocusMarker
\n
'
' Semantics
\n
'
' FocusScope
\n
'
...
...
@@ -192,6 +191,7 @@ void main() {
' Focus
\n
'
' Shortcuts
\n
'
' ShortcutRegistrar
\n
'
' TapRegionSurface
\n
'
' _FocusMarker
\n
'
' Focus
\n
'
' _FocusTraversalGroupMarker
\n
'
...
...
packages/flutter/test/material/input_decorator_test.dart
View file @
f5e4d2b4
...
...
@@ -5417,7 +5417,7 @@ void main() {
final
double
floatedLabelWidth
=
getLabelRect
(
tester
).
width
;
expect
(
floatedLabelWidth
>
labelWidth
,
isTrue
);
expect
(
floatedLabelWidth
,
greaterThan
(
labelWidth
)
);
final
Widget
target
=
getLabeledInputDecorator
(
FloatingLabelBehavior
.
auto
);
await
tester
.
pumpWidget
(
target
);
...
...
@@ -5430,8 +5430,8 @@ void main() {
// Default animation duration is 200 millisecond.
await
tester
.
pumpFrames
(
target
,
const
Duration
(
milliseconds:
100
));
expect
(
getLabelRect
(
tester
).
width
>
labelWidth
,
isTrue
);
expect
(
getLabelRect
(
tester
).
width
<
floatedLabelWidth
,
isTrue
);
expect
(
getLabelRect
(
tester
).
width
,
greaterThan
(
labelWidth
)
);
expect
(
getLabelRect
(
tester
).
width
,
lessThanOrEqualTo
(
floatedLabelWidth
)
);
await
tester
.
pumpAndSettle
();
...
...
packages/flutter/test/material/text_field_focus_test.dart
View file @
f5e4d2b4
...
...
@@ -186,7 +186,7 @@ void main() {
expect
(
find
.
byType
(
TextField
),
findsOneWidget
);
expect
(
tester
.
testTextInput
.
isVisible
,
isTrue
);
await
tester
.
drag
(
find
.
byType
(
ListView
),
const
Offset
(
0.0
,
-
1000.0
));
await
tester
.
drag
(
find
.
byType
(
TextField
),
const
Offset
(
0.0
,
-
1000.0
));
await
tester
.
pump
();
expect
(
find
.
byType
(
TextField
,
skipOffstage:
false
),
findsOneWidget
);
expect
(
tester
.
testTextInput
.
isVisible
,
isTrue
);
...
...
@@ -225,7 +225,7 @@ void main() {
FocusScope
.
of
(
tester
.
element
(
find
.
byType
(
TextField
))).
requestFocus
(
focusNode
);
await
tester
.
pump
();
expect
(
find
.
byType
(
TextField
),
findsOneWidget
);
await
tester
.
drag
(
find
.
byType
(
ListView
),
const
Offset
(
0.0
,
-
1000.0
));
await
tester
.
drag
(
find
.
byType
(
TextField
),
const
Offset
(
0.0
,
-
1000.0
));
await
tester
.
pump
();
expect
(
find
.
byType
(
TextField
,
skipOffstage:
false
),
findsOneWidget
);
await
tester
.
pumpWidget
(
makeTest
(
'test'
));
...
...
@@ -490,8 +490,8 @@ void main() {
},
variant:
TargetPlatformVariant
.
desktop
());
testWidgets
(
'A Focused text-field will lose focus when clicking outside of its hitbox with a mouse on desktop after tab navigation'
,
(
WidgetTester
tester
)
async
{
final
FocusNode
focusNodeA
=
FocusNode
();
final
FocusNode
focusNodeB
=
FocusNode
();
final
FocusNode
focusNodeA
=
FocusNode
(
debugLabel:
'A'
);
final
FocusNode
focusNodeB
=
FocusNode
(
debugLabel:
'B'
);
final
Key
key
=
UniqueKey
();
await
tester
.
pumpWidget
(
...
...
@@ -518,30 +518,33 @@ void main() {
);
// Tab over to the 3rd text field.
for
(
int
i
=
0
;
i
<
3
;
i
+=
1
)
{
await
tester
.
sendKeyDownEvent
(
LogicalKeyboardKey
.
tab
);
await
tester
.
sendKeyUpEvent
(
LogicalKeyboardKey
.
tab
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
tab
);
await
tester
.
pump
();
}
Future
<
void
>
click
(
Finder
finder
)
async
{
final
TestGesture
gesture
=
await
tester
.
startGesture
(
tester
.
getCenter
(
finder
),
kind:
PointerDeviceKind
.
mouse
,
);
await
gesture
.
up
();
await
gesture
.
removePointer
();
}
expect
(
focusNodeA
.
hasFocus
,
true
);
expect
(
focusNodeB
.
hasFocus
,
false
);
// Click on the container to not hit either text field.
final
TestGesture
down2
=
await
tester
.
startGesture
(
tester
.
getCenter
(
find
.
byKey
(
key
)),
kind:
PointerDeviceKind
.
mouse
);
await
click
(
find
.
byKey
(
key
)
);
await
tester
.
pump
();
await
tester
.
pumpAndSettle
();
await
down2
.
up
();
await
down2
.
removePointer
();
expect
(
focusNodeA
.
hasFocus
,
false
);
expect
(
focusNodeB
.
hasFocus
,
false
);
// Second text field can still gain focus.
final
TestGesture
down3
=
await
tester
.
startGesture
(
tester
.
getCenter
(
find
.
byType
(
TextField
).
last
),
kind:
PointerDeviceKind
.
mouse
);
await
click
(
find
.
byType
(
TextField
).
last
);
await
tester
.
pump
();
await
tester
.
pumpAndSettle
();
await
down3
.
up
();
await
down3
.
removePointer
();
expect
(
focusNodeA
.
hasFocus
,
false
);
expect
(
focusNodeB
.
hasFocus
,
true
);
...
...
packages/flutter/test/material/text_field_test.dart
View file @
f5e4d2b4
...
...
@@ -11785,4 +11785,224 @@ void main() {
expect
(
controller
.
selection
.
extentOffset
,
5
);
},
variant:
const
TargetPlatformVariant
(<
TargetPlatform
>{
TargetPlatform
.
iOS
,
TargetPlatform
.
macOS
}));
});
group
(
'TapRegion integration'
,
()
{
testWidgets
(
'Tapping outside loses focus on desktop'
,
(
WidgetTester
tester
)
async
{
final
FocusNode
focusNode
=
FocusNode
(
debugLabel:
'Test Node'
);
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Scaffold
(
body:
Center
(
child:
SizedBox
(
width:
100
,
height:
100
,
child:
Opacity
(
opacity:
0.5
,
child:
TextField
(
autofocus:
true
,
focusNode:
focusNode
,
decoration:
const
InputDecoration
(
hintText:
'Placeholder'
,
border:
OutlineInputBorder
(),
),
),
),
),
),
),
),
);
await
tester
.
pump
();
expect
(
focusNode
.
hasPrimaryFocus
,
isTrue
);
await
tester
.
tapAt
(
const
Offset
(
10
,
10
));
await
tester
.
pump
();
expect
(
focusNode
.
hasPrimaryFocus
,
isFalse
);
},
variant:
TargetPlatformVariant
.
desktop
());
testWidgets
(
"Tapping outside doesn't lose focus on mobile"
,
(
WidgetTester
tester
)
async
{
final
FocusNode
focusNode
=
FocusNode
(
debugLabel:
'Test Node'
);
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Scaffold
(
body:
Center
(
child:
SizedBox
(
width:
100
,
height:
100
,
child:
Opacity
(
opacity:
0.5
,
child:
TextField
(
autofocus:
true
,
focusNode:
focusNode
,
decoration:
const
InputDecoration
(
hintText:
'Placeholder'
,
border:
OutlineInputBorder
(),
),
),
),
),
),
),
),
);
await
tester
.
pump
();
expect
(
focusNode
.
hasPrimaryFocus
,
isTrue
);
await
tester
.
tapAt
(
const
Offset
(
10
,
10
));
await
tester
.
pump
();
// Focus is lost on mobile browsers, but not mobile apps.
expect
(
focusNode
.
hasPrimaryFocus
,
kIsWeb
?
isFalse
:
isTrue
);
},
variant:
TargetPlatformVariant
.
mobile
());
testWidgets
(
"Tapping on toolbar doesn't lose focus"
,
(
WidgetTester
tester
)
async
{
final
FocusNode
focusNode
=
FocusNode
(
debugLabel:
'Test Node'
);
final
TextEditingController
controller
=
TextEditingController
(
text:
'A B C'
);
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Scaffold
(
body:
Center
(
child:
SizedBox
(
width:
100
,
height:
100
,
child:
Opacity
(
opacity:
0.5
,
child:
TextField
(
controller:
controller
,
focusNode:
focusNode
,
decoration:
const
InputDecoration
(
hintText:
'Placeholder'
),
),
),
),
),
),
),
);
// The selectWordsInRange with SelectionChangedCause.tap seems to be needed to show the toolbar.
final
EditableTextState
state
=
tester
.
state
<
EditableTextState
>(
find
.
byType
(
EditableText
));
state
.
renderEditable
.
selectWordsInRange
(
from:
Offset
.
zero
,
cause:
SelectionChangedCause
.
tap
);
final
Offset
aPosition
=
textOffsetToPosition
(
tester
,
1
);
// Right clicking shows the menu.
final
TestGesture
gesture
=
await
tester
.
startGesture
(
aPosition
,
kind:
PointerDeviceKind
.
mouse
,
buttons:
kSecondaryMouseButton
,
);
await
tester
.
pump
();
await
gesture
.
up
();
await
tester
.
pumpAndSettle
();
// Sanity check that the toolbar widget exists.
expect
(
find
.
text
(
'Copy'
),
findsOneWidget
);
expect
(
focusNode
.
hasPrimaryFocus
,
isTrue
);
// Now tap on it to see if we lose focus.
await
tester
.
tap
(
find
.
text
(
'Copy'
));
await
tester
.
pumpAndSettle
();
expect
(
focusNode
.
hasPrimaryFocus
,
isTrue
);
},
variant:
TargetPlatformVariant
.
all
(),
skip:
isBrowser
,
// [intended] On the web, the toolbar isn't rendered by Flutter.
);
testWidgets
(
"Tapping on input decorator doesn't lose focus"
,
(
WidgetTester
tester
)
async
{
final
FocusNode
focusNode
=
FocusNode
(
debugLabel:
'Test Node'
);
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Scaffold
(
body:
Center
(
child:
SizedBox
(
width:
100
,
height:
100
,
child:
Opacity
(
opacity:
0.5
,
child:
TextField
(
autofocus:
true
,
focusNode:
focusNode
,
decoration:
const
InputDecoration
(
hintText:
'Placeholder'
,
border:
OutlineInputBorder
(),
),
),
),
),
),
),
),
);
await
tester
.
pump
();
expect
(
focusNode
.
hasPrimaryFocus
,
isTrue
);
final
Rect
decorationBox
=
tester
.
getRect
(
find
.
byType
(
TextField
));
// Tap just inside the decoration, but not inside the EditableText.
await
tester
.
tapAt
(
decorationBox
.
topLeft
+
const
Offset
(
1
,
1
));
await
tester
.
pump
();
expect
(
focusNode
.
hasPrimaryFocus
,
isTrue
);
},
variant:
TargetPlatformVariant
.
all
());
// PointerDownEvents can't be trackpad events, apparently, so we skip that one.
for
(
final
PointerDeviceKind
pointerDeviceKind
in
PointerDeviceKind
.
values
.
toSet
()..
remove
(
PointerDeviceKind
.
trackpad
))
{
testWidgets
(
'Default TextField handling of onTapOutside follows platform conventions for
${pointerDeviceKind.name}
'
,
(
WidgetTester
tester
)
async
{
final
FocusNode
focusNode
=
FocusNode
(
debugLabel:
'Test'
);
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Scaffold
(
body:
Column
(
children:
<
Widget
>[
const
Text
(
'Outside'
),
TextField
(
autofocus:
true
,
focusNode:
focusNode
,
),
],
),
),
),
);
await
tester
.
pump
();
Future
<
void
>
click
(
Finder
finder
)
async
{
final
TestGesture
gesture
=
await
tester
.
startGesture
(
tester
.
getCenter
(
finder
),
kind:
pointerDeviceKind
,
);
await
gesture
.
up
();
await
gesture
.
removePointer
();
}
expect
(
focusNode
.
hasPrimaryFocus
,
isTrue
);
await
click
(
find
.
text
(
'Outside'
));
switch
(
pointerDeviceKind
)
{
case
PointerDeviceKind
.
touch
:
switch
(
defaultTargetPlatform
)
{
case
TargetPlatform
.
iOS
:
case
TargetPlatform
.
android
:
case
TargetPlatform
.
fuchsia
:
expect
(
focusNode
.
hasPrimaryFocus
,
equals
(!
kIsWeb
));
break
;
case
TargetPlatform
.
linux
:
case
TargetPlatform
.
macOS
:
case
TargetPlatform
.
windows
:
expect
(
focusNode
.
hasPrimaryFocus
,
isFalse
);
break
;
}
break
;
case
PointerDeviceKind
.
mouse
:
case
PointerDeviceKind
.
stylus
:
case
PointerDeviceKind
.
invertedStylus
:
case
PointerDeviceKind
.
trackpad
:
case
PointerDeviceKind
.
unknown
:
expect
(
focusNode
.
hasPrimaryFocus
,
isFalse
);
break
;
}
},
variant:
TargetPlatformVariant
.
all
());
}
});
}
packages/flutter/test/widgets/editable_text_shortcuts_test.dart
View file @
f5e4d2b4
...
...
@@ -1475,6 +1475,7 @@ void main() {
offset:
2
,
);
await
tester
.
pumpWidget
(
buildEditableText
());
await
tester
.
pump
();
// Wait for autofocus to take effect.
await
sendKeyCombination
(
tester
,
const
SingleActivator
(
LogicalKeyboardKey
.
arrowDown
));
await
tester
.
pump
();
...
...
packages/flutter/test/widgets/editable_text_test.dart
View file @
f5e4d2b4
...
...
@@ -12595,13 +12595,22 @@ class MockTextFormatter extends TextInputFormatter {
class
MockTextSelectionControls
extends
Fake
implements
TextSelectionControls
{
@override
Widget
buildToolbar
(
BuildContext
context
,
Rect
globalEditableRegion
,
double
textLineHeight
,
Offset
position
,
List
<
TextSelectionPoint
>
endpoints
,
TextSelectionDelegate
delegate
,
ClipboardStatusNotifier
?
clipboardStatus
,
Offset
?
lastSecondaryTapDownPosition
)
{
return
Container
();
Widget
buildToolbar
(
BuildContext
context
,
Rect
globalEditableRegion
,
double
textLineHeight
,
Offset
position
,
List
<
TextSelectionPoint
>
endpoints
,
TextSelectionDelegate
delegate
,
ClipboardStatusNotifier
?
clipboardStatus
,
Offset
?
lastSecondaryTapDownPosition
,
)
{
return
const
SizedBox
();
}
@override
Widget
buildHandle
(
BuildContext
context
,
TextSelectionHandleType
type
,
double
textLineHeight
,
[
VoidCallback
?
onTap
])
{
return
Container
();
return
const
SizedBox
();
}
@override
...
...
@@ -12671,7 +12680,16 @@ class _CustomTextSelectionControls extends TextSelectionControls {
final
VoidCallback
?
onCut
;
@override
Widget
buildToolbar
(
BuildContext
context
,
Rect
globalEditableRegion
,
double
textLineHeight
,
Offset
position
,
List
<
TextSelectionPoint
>
endpoints
,
TextSelectionDelegate
delegate
,
ClipboardStatusNotifier
?
clipboardStatus
,
Offset
?
lastSecondaryTapDownPosition
)
{
Widget
buildToolbar
(
BuildContext
context
,
Rect
globalEditableRegion
,
double
textLineHeight
,
Offset
position
,
List
<
TextSelectionPoint
>
endpoints
,
TextSelectionDelegate
delegate
,
ClipboardStatusNotifier
?
clipboardStatus
,
Offset
?
lastSecondaryTapDownPosition
,
)
{
final
Offset
selectionMidpoint
=
position
;
final
TextSelectionPoint
startTextSelectionPoint
=
endpoints
[
0
];
final
TextSelectionPoint
endTextSelectionPoint
=
endpoints
.
length
>
1
...
...
packages/flutter/test/widgets/routes_test.dart
View file @
f5e4d2b4
...
...
@@ -5,7 +5,6 @@
import
'dart:collection'
;
import
'dart:ui'
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter/services.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
...
...
@@ -1976,143 +1975,6 @@ void main() {
await
tester
.
restoreFrom
(
restorationData
);
expect
(
find
.
byType
(
AlertDialog
),
findsOneWidget
);
},
skip:
isBrowser
);
// https://github.com/flutter/flutter/issues/33615
testWidgets
(
'FocusTrap moves focus to given focus scope when triggered'
,
(
WidgetTester
tester
)
async
{
final
FocusScopeNode
focusScope
=
FocusScopeNode
();
final
FocusNode
focusNode
=
FocusNode
(
debugLabel:
'Test'
);
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
FocusScope
(
node:
focusScope
,
child:
FocusTrap
(
focusScopeNode:
focusScope
,
child:
Column
(
children:
<
Widget
>[
const
Text
(
'Other Widget'
),
FocusTrapTestWidget
(
'Focusable'
,
focusNode:
focusNode
,
onTap:
()
{
focusNode
.
requestFocus
();
}),
],
),
),
),
),
);
await
tester
.
pump
();
Future
<
void
>
click
(
Finder
finder
)
async
{
final
TestGesture
gesture
=
await
tester
.
startGesture
(
tester
.
getCenter
(
finder
),
kind:
PointerDeviceKind
.
mouse
,
);
await
gesture
.
up
();
await
gesture
.
removePointer
();
}
expect
(
focusScope
.
hasFocus
,
isFalse
);
expect
(
focusNode
.
hasFocus
,
isFalse
);
await
click
(
find
.
text
(
'Focusable'
));
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
expect
(
focusScope
.
hasFocus
,
isTrue
);
expect
(
focusNode
.
hasPrimaryFocus
,
isTrue
);
await
click
(
find
.
text
(
'Other Widget'
));
// Have to wait out the double click timer.
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
switch
(
defaultTargetPlatform
)
{
case
TargetPlatform
.
iOS
:
case
TargetPlatform
.
android
:
if
(
kIsWeb
)
{
// Web is a desktop platform.
expect
(
focusScope
.
hasPrimaryFocus
,
isTrue
);
expect
(
focusNode
.
hasFocus
,
isFalse
);
}
else
{
expect
(
focusScope
.
hasFocus
,
isTrue
);
expect
(
focusNode
.
hasPrimaryFocus
,
isTrue
);
}
break
;
case
TargetPlatform
.
fuchsia
:
case
TargetPlatform
.
linux
:
case
TargetPlatform
.
macOS
:
case
TargetPlatform
.
windows
:
expect
(
focusScope
.
hasPrimaryFocus
,
isTrue
);
expect
(
focusNode
.
hasFocus
,
isFalse
);
break
;
}
},
variant:
TargetPlatformVariant
.
all
());
testWidgets
(
"FocusTrap doesn't unfocus if focus was set to something else before the frame ends"
,
(
WidgetTester
tester
)
async
{
final
FocusScopeNode
focusScope
=
FocusScopeNode
();
final
FocusNode
focusNode
=
FocusNode
(
debugLabel:
'Test'
);
final
FocusNode
otherFocusNode
=
FocusNode
(
debugLabel:
'Other'
);
FocusNode
?
previousFocus
;
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
FocusScope
(
node:
focusScope
,
child:
FocusTrap
(
focusScopeNode:
focusScope
,
child:
Column
(
children:
<
Widget
>[
FocusTrapTestWidget
(
'Other Widget'
,
focusNode:
otherFocusNode
,
onTap:
()
{
previousFocus
=
FocusManager
.
instance
.
primaryFocus
;
otherFocusNode
.
requestFocus
();
},
),
FocusTrapTestWidget
(
'Focusable'
,
focusNode:
focusNode
,
onTap:
()
{
focusNode
.
requestFocus
();
},
),
],
),
),
),
),
);
Future
<
void
>
click
(
Finder
finder
)
async
{
final
TestGesture
gesture
=
await
tester
.
startGesture
(
tester
.
getCenter
(
finder
),
kind:
PointerDeviceKind
.
mouse
,
);
await
gesture
.
up
();
await
gesture
.
removePointer
();
}
await
tester
.
pump
();
expect
(
focusScope
.
hasFocus
,
isFalse
);
expect
(
focusNode
.
hasPrimaryFocus
,
isFalse
);
await
click
(
find
.
text
(
'Focusable'
));
expect
(
focusScope
.
hasFocus
,
isTrue
);
expect
(
focusNode
.
hasPrimaryFocus
,
isTrue
);
await
click
(
find
.
text
(
'Other Widget'
));
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
// The previous focus as collected by the "Other Widget" should be the
// previous focus, not be unfocused to the scope, since the primary focus
// was set by something other than the FocusTrap (the "Other Widget") during
// the frame.
expect
(
previousFocus
,
equals
(
focusNode
));
expect
(
focusScope
.
hasFocus
,
isTrue
);
expect
(
focusNode
.
hasPrimaryFocus
,
isFalse
);
expect
(
otherFocusNode
.
hasPrimaryFocus
,
isTrue
);
},
variant:
TargetPlatformVariant
.
all
());
}
double
_getOpacity
(
GlobalKey
key
,
WidgetTester
tester
)
{
...
...
@@ -2327,68 +2189,3 @@ class _RestorableDialogTestWidget extends StatelessWidget {
);
}
}
class
FocusTrapTestWidget
extends
StatefulWidget
{
const
FocusTrapTestWidget
(
this
.
label
,
{
super
.
key
,
required
this
.
focusNode
,
this
.
onTap
,
this
.
autofocus
=
false
,
});
final
String
label
;
final
FocusNode
focusNode
;
final
VoidCallback
?
onTap
;
final
bool
autofocus
;
@override
State
<
FocusTrapTestWidget
>
createState
()
=>
_FocusTrapTestWidgetState
();
}
class
_FocusTrapTestWidgetState
extends
State
<
FocusTrapTestWidget
>
{
Color
color
=
Colors
.
white
;
@override
void
initState
()
{
super
.
initState
();
widget
.
focusNode
.
addListener
(
_handleFocusChange
);
}
void
_handleFocusChange
()
{
if
(
widget
.
focusNode
.
hasPrimaryFocus
)
{
setState
(()
{
color
=
Colors
.
grey
.
shade500
;
});
}
else
{
setState
(()
{
color
=
Colors
.
white
;
});
}
}
@override
void
dispose
()
{
widget
.
focusNode
.
removeListener
(
_handleFocusChange
);
widget
.
focusNode
.
dispose
();
super
.
dispose
();
}
@override
Widget
build
(
BuildContext
context
)
{
return
Focus
(
autofocus:
widget
.
autofocus
,
focusNode:
widget
.
focusNode
,
child:
GestureDetector
(
onTap:
()
{
widget
.
onTap
?.
call
();
},
child:
Container
(
color:
color
,
alignment:
Alignment
.
center
,
child:
Text
(
widget
.
label
,
style:
const
TextStyle
(
color:
Colors
.
black
)),
),
),
);
}
}
packages/flutter/test/widgets/tap_region_test.dart
0 → 100644
View file @
f5e4d2b4
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'dart:ui'
;
import
'package:flutter/material.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
void
main
(
)
{
testWidgets
(
'TapRegionSurface detects outside taps'
,
(
WidgetTester
tester
)
async
{
final
Set
<
String
>
clickedOutside
=
<
String
>{};
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
Column
(
children:
<
Widget
>[
const
Text
(
'Outside Surface'
),
TapRegionSurface
(
child:
Row
(
children:
<
Widget
>[
const
Text
(
'Outside'
),
TapRegion
(
onTapOutside:
(
PointerEvent
event
)
{
clickedOutside
.
add
(
'No Group'
);
},
child:
const
Text
(
'No Group'
),
),
TapRegion
(
groupId:
1
,
onTapOutside:
(
PointerEvent
event
)
{
clickedOutside
.
add
(
'Group 1 A'
);
},
child:
const
Text
(
'Group 1 A'
),
),
TapRegion
(
groupId:
1
,
onTapOutside:
(
PointerEvent
event
)
{
clickedOutside
.
add
(
'Group 1 B'
);
},
child:
const
Text
(
'Group 1 B'
),
),
],
),
),
],
),
),
);
await
tester
.
pump
();
Future
<
void
>
click
(
Finder
finder
)
async
{
final
TestGesture
gesture
=
await
tester
.
startGesture
(
tester
.
getCenter
(
finder
),
kind:
PointerDeviceKind
.
mouse
,
);
await
gesture
.
up
();
await
gesture
.
removePointer
();
}
expect
(
clickedOutside
,
isEmpty
);
await
click
(
find
.
text
(
'No Group'
));
expect
(
clickedOutside
,
unorderedEquals
(<
String
>{
'Group 1 A'
,
'Group 1 B'
,
}));
clickedOutside
.
clear
();
await
click
(
find
.
text
(
'Group 1 A'
));
expect
(
clickedOutside
,
equals
(<
String
>{
'No Group'
,
}));
clickedOutside
.
clear
();
await
click
(
find
.
text
(
'Group 1 B'
));
expect
(
clickedOutside
,
equals
(<
String
>{
'No Group'
,
}));
clickedOutside
.
clear
();
await
click
(
find
.
text
(
'Outside'
));
expect
(
clickedOutside
,
unorderedEquals
(<
String
>{
'No Group'
,
'Group 1 A'
,
'Group 1 B'
,
}));
clickedOutside
.
clear
();
await
click
(
find
.
text
(
'Outside Surface'
));
expect
(
clickedOutside
,
isEmpty
);
});
testWidgets
(
'TapRegionSurface detects inside taps'
,
(
WidgetTester
tester
)
async
{
final
Set
<
String
>
clickedInside
=
<
String
>{};
await
tester
.
pumpWidget
(
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
Column
(
children:
<
Widget
>[
const
Text
(
'Outside Surface'
),
TapRegionSurface
(
child:
Row
(
children:
<
Widget
>[
const
Text
(
'Outside'
),
TapRegion
(
onTapInside:
(
PointerEvent
event
)
{
clickedInside
.
add
(
'No Group'
);
},
child:
const
Text
(
'No Group'
),
),
TapRegion
(
groupId:
1
,
onTapInside:
(
PointerEvent
event
)
{
clickedInside
.
add
(
'Group 1 A'
);
},
child:
const
Text
(
'Group 1 A'
),
),
TapRegion
(
groupId:
1
,
onTapInside:
(
PointerEvent
event
)
{
clickedInside
.
add
(
'Group 1 B'
);
},
child:
const
Text
(
'Group 1 B'
),
),
],
),
),
],
),
),
);
await
tester
.
pump
();
Future
<
void
>
click
(
Finder
finder
)
async
{
final
TestGesture
gesture
=
await
tester
.
startGesture
(
tester
.
getCenter
(
finder
),
kind:
PointerDeviceKind
.
mouse
,
);
await
gesture
.
up
();
await
gesture
.
removePointer
();
}
expect
(
clickedInside
,
isEmpty
);
await
click
(
find
.
text
(
'No Group'
));
expect
(
clickedInside
,
unorderedEquals
(<
String
>{
'No Group'
,
}));
clickedInside
.
clear
();
await
click
(
find
.
text
(
'Group 1 A'
));
expect
(
clickedInside
,
equals
(<
String
>{
'Group 1 A'
,
'Group 1 B'
,
}));
clickedInside
.
clear
();
await
click
(
find
.
text
(
'Group 1 B'
));
expect
(
clickedInside
,
equals
(<
String
>{
'Group 1 A'
,
'Group 1 B'
,
}));
clickedInside
.
clear
();
await
click
(
find
.
text
(
'Outside'
));
expect
(
clickedInside
,
isEmpty
);
clickedInside
.
clear
();
await
click
(
find
.
text
(
'Outside Surface'
));
expect
(
clickedInside
,
isEmpty
);
});
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment