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
2900347a
Unverified
Commit
2900347a
authored
Jul 13, 2021
by
Jonah Williams
Committed by
GitHub
Jul 13, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[flutter] prevent errant text field clicks from losing focus (#86041)
parent
f6e5227b
Changes
6
Show whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
135 additions
and
35 deletions
+135
-35
text_field.dart
packages/flutter/lib/src/material/text_field.dart
+26
-23
editable_text.dart
packages/flutter/lib/src/widgets/editable_text.dart
+3
-4
routes.dart
packages/flutter/lib/src/widgets/routes.dart
+65
-6
debug_test.dart
packages/flutter/test/material/debug_test.dart
+1
-1
text_field_focus_test.dart
packages/flutter/test/material/text_field_focus_test.dart
+39
-0
widget_tester_live_device_test.dart
...ges/flutter_test/test/widget_tester_live_device_test.dart
+1
-1
No files found.
packages/flutter/lib/src/material/text_field.dart
View file @
2900347a
...
...
@@ -1306,7 +1306,9 @@ class _TextFieldState extends State<TextField> with RestorationMixin implements
semanticsMaxValueLength
=
null
;
}
return
MouseRegion
(
return
FocusTrapArea
(
focusNode:
focusNode
,
child:
MouseRegion
(
cursor:
effectiveMouseCursor
,
onEnter:
(
PointerEnterEvent
event
)
=>
_handleHover
(
true
),
onExit:
(
PointerExitEvent
event
)
=>
_handleHover
(
false
),
...
...
@@ -1333,6 +1335,7 @@ class _TextFieldState extends State<TextField> with RestorationMixin implements
),
),
),
),
);
}
}
packages/flutter/lib/src/widgets/editable_text.dart
View file @
2900347a
...
...
@@ -1654,13 +1654,12 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
widget
.
focusNode
.
addListener
(
_handleFocusChanged
);
updateKeepAlive
();
}
if
(!
_shouldCreateInputConnection
)
{
_closeInputConnectionIfNeeded
();
}
else
{
if
(
oldWidget
.
readOnly
&&
_hasFocus
)
{
}
else
if
(
oldWidget
.
readOnly
&&
_hasFocus
)
{
_openInputConnection
();
}
}
if
(
kIsWeb
&&
_hasInputConnection
)
{
if
(
oldWidget
.
readOnly
!=
widget
.
readOnly
)
{
...
...
packages/flutter/lib/src/widgets/routes.dart
View file @
2900347a
...
...
@@ -814,7 +814,7 @@ class _ModalScopeState<T> extends State<_ModalScope<T>> {
controller:
primaryScrollController
,
child:
FocusScope
(
node:
focusScopeNode
,
// immutable
child:
_
FocusTrap
(
child:
FocusTrap
(
focusScopeNode:
focusScopeNode
,
child:
RepaintBoundary
(
child:
AnimatedBuilder
(
...
...
@@ -1987,16 +1987,32 @@ typedef RoutePageBuilder = Widget Function(BuildContext context, Animation<doubl
/// See [ModalRoute.buildTransitions] for complete definition of the parameters.
typedef
RouteTransitionsBuilder
=
Widget
Function
(
BuildContext
context
,
Animation
<
double
>
animation
,
Animation
<
double
>
secondaryAnimation
,
Widget
child
);
/// The [FocusTrap] widget removes focus when a mouse primary pointer makes contact with another
/// region of the screen.
///
/// When a primary pointer makes contact with the screen, this widget determines if that pointer
/// contacted an existing focused widget. If not, this asks the [FocusScopeNode] to reset the
/// focus state. This allows [TextField]s and other focusable widgets to give up their focus
/// state, without creating a gesture detector that competes with others on screen.
class
_FocusTrap
extends
SingleChildRenderObjectWidget
{
const
_FocusTrap
({
///
/// In cases where focus is conceptually larger than the focused render object, a [FocusTrapArea]
/// can be used to expand the focus area to include all render objects below that. This is used by
/// the [TextField] widgets to prevent a loss of focus when interacting with decorations on the
/// text area.
///
/// See also:
///
/// * [FocusTrapArea], the widget that allows expanding the conceptual focus area.
class
FocusTrap
extends
SingleChildRenderObjectWidget
{
/// Create a new [FocusTrap] widget scoped to the provided [focusScopeNode].
const
FocusTrap
({
required
this
.
focusScopeNode
,
required
Widget
child
,
})
:
super
(
child:
child
);
Key
?
key
,
})
:
super
(
child:
child
,
key:
key
);
/// The [focusScopeNode] that this focus trap widget operates on.
final
FocusScopeNode
focusScopeNode
;
@override
...
...
@@ -2005,11 +2021,50 @@ class _FocusTrap extends SingleChildRenderObjectWidget {
}
@override
void
updateRenderObject
(
BuildContext
context
,
covariant
_RenderFocusTrap
renderObject
)
{
void
updateRenderObject
(
BuildContext
context
,
RenderObject
renderObject
)
{
if
(
renderObject
is
_RenderFocusTrap
)
renderObject
.
focusScopeNode
=
focusScopeNode
;
}
}
/// Declares a widget subtree which is part of the provided [focusNode]'s focus area
/// without attaching focus to that region.
///
/// This is used by text field widgets which decorate a smaller editable text area.
/// This area is conceptually part of the editable text, but not attached to the
/// focus context. The [FocusTrapArea] is used to inform the framework of this
/// relationship, so that primary pointer contact inside of this region but above
/// the editable text focus will not trigger loss of focus.
///
/// See also:
///
/// * [FocusTrap], the widget which removes focus based on primary pointer interactions.
class
FocusTrapArea
extends
SingleChildRenderObjectWidget
{
/// Create a new [FocusTrapArea] that expands the area of the provided [focusNode].
const
FocusTrapArea
({
required
this
.
focusNode
,
Key
?
key
,
Widget
?
child
})
:
super
(
key:
key
,
child:
child
);
/// The [FocusNode] that the focus trap area will expand to.
final
FocusNode
focusNode
;
@override
RenderObject
createRenderObject
(
BuildContext
context
)
{
return
_RenderFocusTrapArea
(
focusNode
);
}
@override
void
updateRenderObject
(
BuildContext
context
,
RenderObject
renderObject
)
{
if
(
renderObject
is
_RenderFocusTrapArea
)
renderObject
.
focusNode
=
focusNode
;
}
}
class
_RenderFocusTrapArea
extends
RenderProxyBox
{
_RenderFocusTrapArea
(
this
.
focusNode
);
FocusNode
focusNode
;
}
class
_RenderFocusTrap
extends
RenderProxyBoxWithHitTestBehavior
{
_RenderFocusTrap
(
this
.
_focusScopeNode
);
...
...
@@ -2079,6 +2134,10 @@ class _RenderFocusTrap extends RenderProxyBoxWithHitTestBehavior {
hitCurrentFocus
=
true
;
break
;
}
if
(
target
is
_RenderFocusTrapArea
&&
target
.
focusNode
==
focusNode
)
{
hitCurrentFocus
=
true
;
break
;
}
}
if
(!
hitCurrentFocus
)
focusNode
.
unfocus
(
disposition:
UnfocusDisposition
.
scope
);
...
...
packages/flutter/test/material/debug_test.dart
View file @
2900347a
...
...
@@ -134,7 +134,7 @@ void main() {
' _FadeUpwardsPageTransition
\n
'
' AnimatedBuilder
\n
'
' RepaintBoundary
\n
'
'
_
FocusTrap
\n
'
' FocusTrap
\n
'
' _FocusMarker
\n
'
' Semantics
\n
'
' FocusScope
\n
'
...
...
packages/flutter/test/material/text_field_focus_test.dart
View file @
2900347a
...
...
@@ -431,6 +431,45 @@ void main() {
expect
(
focusNodeB
.
hasFocus
,
true
);
},
variant:
TargetPlatformVariant
.
desktop
());
testWidgets
(
'A Focused text-field will not lose focus when clicking on its decoration'
,
(
WidgetTester
tester
)
async
{
final
FocusNode
focusNodeA
=
FocusNode
();
final
Key
iconKey
=
UniqueKey
();
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Material
(
child:
ListView
(
children:
<
Widget
>[
TextField
(
focusNode:
focusNodeA
,
decoration:
InputDecoration
(
icon:
Icon
(
Icons
.
copy_all
,
key:
iconKey
),
),
),
],
),
),
),
);
final
TestGesture
down1
=
await
tester
.
startGesture
(
tester
.
getCenter
(
find
.
byType
(
TextField
).
first
),
kind:
PointerDeviceKind
.
mouse
);
await
tester
.
pump
();
await
tester
.
pumpAndSettle
();
await
down1
.
up
();
await
down1
.
removePointer
();
expect
(
focusNodeA
.
hasFocus
,
true
);
// Click on the icon which has a different RO than the text field's focus node context
final
TestGesture
down2
=
await
tester
.
startGesture
(
tester
.
getCenter
(
find
.
byKey
(
iconKey
)),
kind:
PointerDeviceKind
.
mouse
);
await
tester
.
pump
();
await
tester
.
pumpAndSettle
();
await
down2
.
up
();
await
down2
.
removePointer
();
expect
(
focusNodeA
.
hasFocus
,
true
);
},
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
();
...
...
packages/flutter_test/test/widget_tester_live_device_test.dart
View file @
2900347a
...
...
@@ -52,9 +52,9 @@ Some possible finders for the widgets at Offset(400.0, 300.0):
find.byType(FadeTransition)
find.byType(FractionalTranslation)
find.byType(SlideTransition)
find.widgetWithText(FocusTrap, '
Test
')
find.widgetWithText(PrimaryScrollController, '
Test
')
find.widgetWithText(PageStorage, '
Test
')
find.widgetWithText(Offstage, '
Test
')
'''
.
trim
().
split
(
'
\n
'
)));
printedMessages
.
clear
();
...
...
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