Ensure the Autocomplete options scroll as needed. (#83863)
This commit is contained in:
parent
4705e0cc19
commit
a714c97c0b
@ -2,6 +2,7 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'package:flutter/scheduler.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
import 'ink_well.dart';
|
import 'ink_well.dart';
|
||||||
@ -292,15 +293,24 @@ class _AutocompleteOptions<T extends Object> extends StatelessWidget {
|
|||||||
itemCount: options.length,
|
itemCount: options.length,
|
||||||
itemBuilder: (BuildContext context, int index) {
|
itemBuilder: (BuildContext context, int index) {
|
||||||
final T option = options.elementAt(index);
|
final T option = options.elementAt(index);
|
||||||
final bool highlight = AutocompleteHighlightedOption.of(context) == index;
|
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
onSelected(option);
|
onSelected(option);
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Builder(
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
final bool highlight = AutocompleteHighlightedOption.of(context) == index;
|
||||||
|
if (highlight) {
|
||||||
|
SchedulerBinding.instance!.addPostFrameCallback((Duration timeStamp) {
|
||||||
|
Scrollable.ensureVisible(context, alignment: 0.5);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Container(
|
||||||
color: highlight ? Theme.of(context).focusColor : null,
|
color: highlight ? Theme.of(context).focusColor : null,
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: Text(displayStringForOption(option)),
|
child: Text(displayStringForOption(option)),
|
||||||
|
);
|
||||||
|
}
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -402,9 +402,9 @@ void main() {
|
|||||||
expect(lastSelection, 'lemur');
|
expect(lastSelection, 'lemur');
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('keyboard navigation of the options properly highlights the option', (WidgetTester tester) async {
|
// Ensures that the option with the given label has a given background color
|
||||||
|
// if given, or no background if color is null.
|
||||||
void checkOptionHighlight(String label, Color? color) {
|
void checkOptionHighlight(WidgetTester tester, String label, Color? color) {
|
||||||
final RenderBox renderBox = tester.renderObject<RenderBox>(find.ancestor(matching: find.byType(Container), of: find.text(label)));
|
final RenderBox renderBox = tester.renderObject<RenderBox>(find.ancestor(matching: find.byType(Container), of: find.text(label)));
|
||||||
if (color != null) {
|
if (color != null) {
|
||||||
// Check to see that the container is painted with the highlighted background color.
|
// Check to see that the container is painted with the highlighted background color.
|
||||||
@ -416,6 +416,7 @@ void main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
testWidgets('keyboard navigation of the options properly highlights the option', (WidgetTester tester) async {
|
||||||
const Color highlightColor = Color(0xFF112233);
|
const Color highlightColor = Color(0xFF112233);
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
MaterialApp(
|
MaterialApp(
|
||||||
@ -442,15 +443,68 @@ void main() {
|
|||||||
expect(list.semanticChildCount, 2);
|
expect(list.semanticChildCount, 2);
|
||||||
|
|
||||||
// Initially the first option should be highlighted
|
// Initially the first option should be highlighted
|
||||||
checkOptionHighlight('chameleon', highlightColor);
|
checkOptionHighlight(tester, 'chameleon', highlightColor);
|
||||||
checkOptionHighlight('elephant', null);
|
checkOptionHighlight(tester, 'elephant', null);
|
||||||
|
|
||||||
// Move the selection down
|
// Move the selection down
|
||||||
await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
|
||||||
// Highlight should be moved to the second item
|
// Highlight should be moved to the second item
|
||||||
checkOptionHighlight('chameleon', null);
|
checkOptionHighlight(tester, 'chameleon', null);
|
||||||
checkOptionHighlight('elephant', highlightColor);
|
checkOptionHighlight(tester, 'elephant', highlightColor);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('keyboard navigation keeps the highlighted option scrolled into view', (WidgetTester tester) async {
|
||||||
|
const Color highlightColor = Color(0xFF112233);
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
theme: ThemeData.light().copyWith(
|
||||||
|
focusColor: highlightColor,
|
||||||
|
),
|
||||||
|
home: Scaffold(
|
||||||
|
body: Autocomplete<String>(
|
||||||
|
optionsBuilder: (TextEditingValue textEditingValue) {
|
||||||
|
return kOptions.where((String option) {
|
||||||
|
return option.contains(textEditingValue.text.toLowerCase());
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.tap(find.byType(TextFormField));
|
||||||
|
await tester.enterText(find.byType(TextFormField), 'e');
|
||||||
|
await tester.pump();
|
||||||
|
expect(find.byType(ListView), findsOneWidget);
|
||||||
|
final ListView list = find.byType(ListView).evaluate().first.widget as ListView;
|
||||||
|
expect(list.semanticChildCount, 6);
|
||||||
|
|
||||||
|
// Highlighted item should be at the top
|
||||||
|
expect(tester.getTopLeft(find.text('chameleon')).dy, equals(64.0));
|
||||||
|
checkOptionHighlight(tester, 'chameleon', highlightColor);
|
||||||
|
|
||||||
|
// Move down the list of options
|
||||||
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
|
||||||
|
await tester.pump();
|
||||||
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
|
||||||
|
await tester.pump();
|
||||||
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
|
||||||
|
await tester.pump();
|
||||||
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
// First item should have scrolled off the top, and not be selected.
|
||||||
|
expect(find.text('chameleon'), findsNothing);
|
||||||
|
|
||||||
|
// Highlighted item 'lemur' should be centered in the options popup
|
||||||
|
expect(tester.getTopLeft(find.text('mouse')).dy, equals(187.0));
|
||||||
|
checkOptionHighlight(tester, 'mouse', highlightColor);
|
||||||
|
|
||||||
|
// The other items on screen should not be selected.
|
||||||
|
checkOptionHighlight(tester, 'goose', null);
|
||||||
|
checkOptionHighlight(tester, 'lemur', null);
|
||||||
|
checkOptionHighlight(tester, 'northern white rhinoceros', null);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user