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
919d2051
Unverified
Commit
919d2051
authored
Feb 16, 2022
by
Markus Aksli
Committed by
GitHub
Feb 16, 2022
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Dismiss Autocomplete with ESC (#97790)
parent
7c3f79f7
Changes
2
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
128 additions
and
5 deletions
+128
-5
autocomplete.dart
packages/flutter/lib/src/widgets/autocomplete.dart
+38
-5
autocomplete_test.dart
packages/flutter/test/widgets/autocomplete_test.dart
+90
-0
No files found.
packages/flutter/lib/src/widgets/autocomplete.dart
View file @
919d2051
...
@@ -277,8 +277,11 @@ class _RawAutocompleteState<T extends Object> extends State<RawAutocomplete<T>>
...
@@ -277,8 +277,11 @@ class _RawAutocompleteState<T extends Object> extends State<RawAutocomplete<T>>
late
final
Map
<
Type
,
Action
<
Intent
>>
_actionMap
;
late
final
Map
<
Type
,
Action
<
Intent
>>
_actionMap
;
late
final
_AutocompleteCallbackAction
<
AutocompletePreviousOptionIntent
>
_previousOptionAction
;
late
final
_AutocompleteCallbackAction
<
AutocompletePreviousOptionIntent
>
_previousOptionAction
;
late
final
_AutocompleteCallbackAction
<
AutocompleteNextOptionIntent
>
_nextOptionAction
;
late
final
_AutocompleteCallbackAction
<
AutocompleteNextOptionIntent
>
_nextOptionAction
;
late
final
_AutocompleteCallbackAction
<
DismissIntent
>
_hideOptionsAction
;
Iterable
<
T
>
_options
=
Iterable
<
T
>.
empty
();
Iterable
<
T
>
_options
=
Iterable
<
T
>.
empty
();
T
?
_selection
;
T
?
_selection
;
bool
_userHidOptions
=
false
;
String
_lastFieldText
=
''
;
final
ValueNotifier
<
int
>
_highlightedOptionIndex
=
ValueNotifier
<
int
>(
0
);
final
ValueNotifier
<
int
>
_highlightedOptionIndex
=
ValueNotifier
<
int
>(
0
);
static
const
Map
<
ShortcutActivator
,
Intent
>
_shortcuts
=
<
ShortcutActivator
,
Intent
>{
static
const
Map
<
ShortcutActivator
,
Intent
>
_shortcuts
=
<
ShortcutActivator
,
Intent
>{
...
@@ -291,31 +294,41 @@ class _RawAutocompleteState<T extends Object> extends State<RawAutocomplete<T>>
...
@@ -291,31 +294,41 @@ class _RawAutocompleteState<T extends Object> extends State<RawAutocomplete<T>>
// True iff the state indicates that the options should be visible.
// True iff the state indicates that the options should be visible.
bool
get
_shouldShowOptions
{
bool
get
_shouldShowOptions
{
return
_focusNode
.
hasFocus
&&
_selection
==
null
&&
_options
.
isNotEmpty
;
return
!
_userHidOptions
&&
_focusNode
.
hasFocus
&&
_selection
==
null
&&
_options
.
isNotEmpty
;
}
}
// Called when _textEditingController changes.
// Called when _textEditingController changes.
Future
<
void
>
_onChangedField
()
async
{
Future
<
void
>
_onChangedField
()
async
{
final
TextEditingValue
value
=
_textEditingController
.
value
;
final
Iterable
<
T
>
options
=
await
widget
.
optionsBuilder
(
final
Iterable
<
T
>
options
=
await
widget
.
optionsBuilder
(
_textEditingController
.
value
,
value
,
);
);
_options
=
options
;
_options
=
options
;
_updateHighlight
(
_highlightedOptionIndex
.
value
);
_updateHighlight
(
_highlightedOptionIndex
.
value
);
if
(
_selection
!=
null
if
(
_selection
!=
null
&&
_textEditingController
.
text
!=
widget
.
displayStringForOption
(
_selection
!))
{
&&
value
.
text
!=
widget
.
displayStringForOption
(
_selection
!))
{
_selection
=
null
;
_selection
=
null
;
}
}
// Make sure the options are no longer hidden if the content of the field
// changes (ignore selection changes).
if
(
value
.
text
!=
_lastFieldText
)
{
_userHidOptions
=
false
;
_lastFieldText
=
value
.
text
;
}
_updateOverlay
();
_updateOverlay
();
}
}
// Called when the field's FocusNode changes.
// Called when the field's FocusNode changes.
void
_onChangedFocus
()
{
void
_onChangedFocus
()
{
// Options should no longer be hidden when the field is re-focused.
_userHidOptions
=
!
_focusNode
.
hasFocus
;
_updateOverlay
();
_updateOverlay
();
}
}
// Called from fieldViewBuilder when the user submits the field.
// Called from fieldViewBuilder when the user submits the field.
void
_onFieldSubmitted
()
{
void
_onFieldSubmitted
()
{
if
(
_options
.
isEmpty
)
{
if
(
_options
.
isEmpty
||
_userHidOptions
)
{
return
;
return
;
}
}
_select
(
_options
.
elementAt
(
_highlightedOptionIndex
.
value
));
_select
(
_options
.
elementAt
(
_highlightedOptionIndex
.
value
));
...
@@ -340,13 +353,30 @@ class _RawAutocompleteState<T extends Object> extends State<RawAutocomplete<T>>
...
@@ -340,13 +353,30 @@ class _RawAutocompleteState<T extends Object> extends State<RawAutocomplete<T>>
}
}
void
_highlightPreviousOption
(
AutocompletePreviousOptionIntent
intent
)
{
void
_highlightPreviousOption
(
AutocompletePreviousOptionIntent
intent
)
{
if
(
_userHidOptions
)
{
_userHidOptions
=
false
;
_updateOverlay
();
return
;
}
_updateHighlight
(
_highlightedOptionIndex
.
value
-
1
);
_updateHighlight
(
_highlightedOptionIndex
.
value
-
1
);
}
}
void
_highlightNextOption
(
AutocompleteNextOptionIntent
intent
)
{
void
_highlightNextOption
(
AutocompleteNextOptionIntent
intent
)
{
if
(
_userHidOptions
)
{
_userHidOptions
=
false
;
_updateOverlay
();
return
;
}
_updateHighlight
(
_highlightedOptionIndex
.
value
+
1
);
_updateHighlight
(
_highlightedOptionIndex
.
value
+
1
);
}
}
void
_hideOptions
(
DismissIntent
intent
)
{
if
(!
_userHidOptions
)
{
_userHidOptions
=
true
;
_updateOverlay
();
}
}
void
_setActionsEnabled
(
bool
enabled
)
{
void
_setActionsEnabled
(
bool
enabled
)
{
// The enabled state determines whether the action will consume the
// The enabled state determines whether the action will consume the
// key shortcut or let it continue on to the underlying text field.
// key shortcut or let it continue on to the underlying text field.
...
@@ -354,11 +384,12 @@ class _RawAutocompleteState<T extends Object> extends State<RawAutocomplete<T>>
...
@@ -354,11 +384,12 @@ class _RawAutocompleteState<T extends Object> extends State<RawAutocomplete<T>>
// can be used to navigate them.
// can be used to navigate them.
_previousOptionAction
.
enabled
=
enabled
;
_previousOptionAction
.
enabled
=
enabled
;
_nextOptionAction
.
enabled
=
enabled
;
_nextOptionAction
.
enabled
=
enabled
;
_hideOptionsAction
.
enabled
=
enabled
;
}
}
// Hide or show the options overlay, if needed.
// Hide or show the options overlay, if needed.
void
_updateOverlay
()
{
void
_updateOverlay
()
{
_setActionsEnabled
(
_
shouldShowOptions
);
_setActionsEnabled
(
_
focusNode
.
hasFocus
&&
_selection
==
null
&&
_options
.
isNotEmpty
);
if
(
_shouldShowOptions
)
{
if
(
_shouldShowOptions
)
{
_floatingOptions
?.
remove
();
_floatingOptions
?.
remove
();
_floatingOptions
=
OverlayEntry
(
_floatingOptions
=
OverlayEntry
(
...
@@ -434,9 +465,11 @@ class _RawAutocompleteState<T extends Object> extends State<RawAutocomplete<T>>
...
@@ -434,9 +465,11 @@ class _RawAutocompleteState<T extends Object> extends State<RawAutocomplete<T>>
_focusNode
.
addListener
(
_onChangedFocus
);
_focusNode
.
addListener
(
_onChangedFocus
);
_previousOptionAction
=
_AutocompleteCallbackAction
<
AutocompletePreviousOptionIntent
>(
onInvoke:
_highlightPreviousOption
);
_previousOptionAction
=
_AutocompleteCallbackAction
<
AutocompletePreviousOptionIntent
>(
onInvoke:
_highlightPreviousOption
);
_nextOptionAction
=
_AutocompleteCallbackAction
<
AutocompleteNextOptionIntent
>(
onInvoke:
_highlightNextOption
);
_nextOptionAction
=
_AutocompleteCallbackAction
<
AutocompleteNextOptionIntent
>(
onInvoke:
_highlightNextOption
);
_hideOptionsAction
=
_AutocompleteCallbackAction
<
DismissIntent
>(
onInvoke:
_hideOptions
);
_actionMap
=
<
Type
,
Action
<
Intent
>>
{
_actionMap
=
<
Type
,
Action
<
Intent
>>
{
AutocompletePreviousOptionIntent:
_previousOptionAction
,
AutocompletePreviousOptionIntent:
_previousOptionAction
,
AutocompleteNextOptionIntent:
_nextOptionAction
,
AutocompleteNextOptionIntent:
_nextOptionAction
,
DismissIntent:
_hideOptionsAction
,
};
};
SchedulerBinding
.
instance
.
addPostFrameCallback
((
Duration
_
)
{
SchedulerBinding
.
instance
.
addPostFrameCallback
((
Duration
_
)
{
_updateOverlay
();
_updateOverlay
();
...
...
packages/flutter/test/widgets/autocomplete_test.dart
View file @
919d2051
...
@@ -793,6 +793,96 @@ void main() {
...
@@ -793,6 +793,96 @@ void main() {
expect
(
textEditingController
.
text
,
'goose'
);
expect
(
textEditingController
.
text
,
'goose'
);
});
});
testWidgets
(
'can hide and show options with the keyboard'
,
(
WidgetTester
tester
)
async
{
final
GlobalKey
fieldKey
=
GlobalKey
();
final
GlobalKey
optionsKey
=
GlobalKey
();
late
Iterable
<
String
>
lastOptions
;
late
FocusNode
focusNode
;
late
TextEditingController
textEditingController
;
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Scaffold
(
body:
RawAutocomplete
<
String
>(
optionsBuilder:
(
TextEditingValue
textEditingValue
)
{
return
kOptions
.
where
((
String
option
)
{
return
option
.
contains
(
textEditingValue
.
text
.
toLowerCase
());
});
},
fieldViewBuilder:
(
BuildContext
context
,
TextEditingController
fieldTextEditingController
,
FocusNode
fieldFocusNode
,
VoidCallback
onFieldSubmitted
)
{
focusNode
=
fieldFocusNode
;
textEditingController
=
fieldTextEditingController
;
return
TextFormField
(
key:
fieldKey
,
focusNode:
focusNode
,
controller:
textEditingController
,
onFieldSubmitted:
(
String
value
)
{
onFieldSubmitted
();
},
);
},
optionsViewBuilder:
(
BuildContext
context
,
AutocompleteOnSelected
<
String
>
onSelected
,
Iterable
<
String
>
options
)
{
lastOptions
=
options
;
return
Container
(
key:
optionsKey
);
},
),
),
),
);
// Enter text. The options are filtered by the text.
focusNode
.
requestFocus
();
await
tester
.
enterText
(
find
.
byKey
(
fieldKey
),
'ele'
);
await
tester
.
pumpAndSettle
();
expect
(
find
.
byKey
(
fieldKey
),
findsOneWidget
);
expect
(
find
.
byKey
(
optionsKey
),
findsOneWidget
);
expect
(
lastOptions
.
length
,
2
);
expect
(
lastOptions
.
elementAt
(
0
),
'chameleon'
);
expect
(
lastOptions
.
elementAt
(
1
),
'elephant'
);
// Hide the options.
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
escape
);
await
tester
.
pump
();
expect
(
find
.
byKey
(
fieldKey
),
findsOneWidget
);
expect
(
find
.
byKey
(
optionsKey
),
findsNothing
);
// Show the options again by pressing arrow keys
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowDown
);
await
tester
.
pump
();
expect
(
find
.
byKey
(
optionsKey
),
findsOneWidget
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
escape
);
await
tester
.
pump
();
expect
(
find
.
byKey
(
optionsKey
),
findsNothing
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
arrowUp
);
await
tester
.
pump
();
expect
(
find
.
byKey
(
optionsKey
),
findsOneWidget
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
escape
);
await
tester
.
pump
();
expect
(
find
.
byKey
(
optionsKey
),
findsNothing
);
// Show the options again by re-focusing the field.
focusNode
.
unfocus
();
await
tester
.
pump
();
expect
(
find
.
byKey
(
optionsKey
),
findsNothing
);
focusNode
.
requestFocus
();
await
tester
.
pump
();
expect
(
find
.
byKey
(
optionsKey
),
findsOneWidget
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
escape
);
await
tester
.
pump
();
expect
(
find
.
byKey
(
optionsKey
),
findsNothing
);
// Show the options again by editing the text (but not when selecting text
// or moving the caret).
await
tester
.
enterText
(
find
.
byKey
(
fieldKey
),
'elep'
);
await
tester
.
pump
();
expect
(
find
.
byKey
(
optionsKey
),
findsOneWidget
);
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
escape
);
await
tester
.
pump
();
expect
(
find
.
byKey
(
optionsKey
),
findsNothing
);
textEditingController
.
selection
=
TextSelection
.
fromPosition
(
const
TextPosition
(
offset:
3
));
await
tester
.
pump
();
expect
(
find
.
byKey
(
optionsKey
),
findsNothing
);
});
testWidgets
(
'optionsViewBuilders can use AutocompleteHighlightedOption to highlight selected option'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'optionsViewBuilders can use AutocompleteHighlightedOption to highlight selected option'
,
(
WidgetTester
tester
)
async
{
final
GlobalKey
fieldKey
=
GlobalKey
();
final
GlobalKey
fieldKey
=
GlobalKey
();
final
GlobalKey
optionsKey
=
GlobalKey
();
final
GlobalKey
optionsKey
=
GlobalKey
();
...
...
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