Updating the Slider Widget to allow up and down arrow keys to navigate out of the slider when in directional NavigationMode. (#103149)

This commit is contained in:
Eric egramond 2022-05-07 03:39:06 -07:00 committed by GitHub
parent 2f657536c8
commit ae7fcc7e51
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 117 additions and 2 deletions

View File

@ -475,13 +475,22 @@ class _SliderState extends State<Slider> with TickerProviderStateMixin {
Timer? interactionTimer;
final GlobalKey _renderObjectKey = GlobalKey();
// Keyboard mapping for a focused slider.
final Map<ShortcutActivator, Intent> _shortcutMap = const <ShortcutActivator, Intent>{
static const Map<ShortcutActivator, Intent> _traditionalNavShortcutMap = <ShortcutActivator, Intent>{
SingleActivator(LogicalKeyboardKey.arrowUp): _AdjustSliderIntent.up(),
SingleActivator(LogicalKeyboardKey.arrowDown): _AdjustSliderIntent.down(),
SingleActivator(LogicalKeyboardKey.arrowLeft): _AdjustSliderIntent.left(),
SingleActivator(LogicalKeyboardKey.arrowRight): _AdjustSliderIntent.right(),
};
// Keyboard mapping for a focused slider when using directional navigation.
// The vertical inputs are not handled to allow navigating out of the slider.
static const Map<ShortcutActivator, Intent> _directionalNavShortcutMap = <ShortcutActivator, Intent>{
SingleActivator(LogicalKeyboardKey.arrowLeft): _AdjustSliderIntent.left(),
SingleActivator(LogicalKeyboardKey.arrowRight): _AdjustSliderIntent.right(),
};
// Action mapping for a focused slider.
late Map<Type, Action<Intent>> _actionMap;
@ -735,13 +744,23 @@ class _SliderState extends State<Slider> with TickerProviderStateMixin {
break;
}
final Map<ShortcutActivator, Intent> shortcutMap;
switch (MediaQuery.of(context).navigationMode) {
case NavigationMode.directional:
shortcutMap = _directionalNavShortcutMap;
break;
case NavigationMode.traditional:
shortcutMap = _traditionalNavShortcutMap;
break;
}
return Semantics(
container: true,
slider: true,
onDidGainAccessibilityFocus: handleDidGainAccessibilityFocus,
child: FocusableActionDetector(
actions: _actionMap,
shortcuts: _shortcutMap,
shortcuts: shortcutMap,
focusNode: focusNode,
autofocus: widget.autofocus,
enabled: _enabled,

View File

@ -2154,6 +2154,102 @@ void main() {
expect(value, 0.5);
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
testWidgets('In directional nav, Slider can be navigated out of by using up and down arrows', (WidgetTester tester) async {
const Map<ShortcutActivator, Intent> shortcuts = <ShortcutActivator, Intent>{
SingleActivator(LogicalKeyboardKey.arrowLeft): DirectionalFocusIntent(TraversalDirection.left),
SingleActivator(LogicalKeyboardKey.arrowRight): DirectionalFocusIntent(TraversalDirection.right),
SingleActivator(LogicalKeyboardKey.arrowDown): DirectionalFocusIntent(TraversalDirection.down),
SingleActivator(LogicalKeyboardKey.arrowUp): DirectionalFocusIntent(TraversalDirection.up),
};
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
double topSliderValue = 0.5;
double bottomSliderValue = 0.5;
await tester.pumpWidget(
MaterialApp(
home: Shortcuts(
shortcuts: shortcuts,
child: Material(
child: Center(
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
return MediaQuery(
data: const MediaQueryData(navigationMode: NavigationMode.directional),
child: Column(
children: <Widget>[
Slider(
value: topSliderValue,
onChanged: (double newValue) {
setState(() {
topSliderValue = newValue;
});
},
autofocus: true,
),
Slider(
value: bottomSliderValue,
onChanged: (double newValue) {
setState(() {
bottomSliderValue = newValue;
});
},
),
]
),
);
}),
),
),
),
),
);
await tester.pumpAndSettle();
// The top slider is auto-focused and can be adjusted with left and right arrow keys.
await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
await tester.pumpAndSettle();
expect(topSliderValue, 0.55, reason: 'focused top Slider increased after first arrowRight');
expect(bottomSliderValue, 0.5, reason: 'unfocused bottom Slider unaffected by first arrowRight');
await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft);
await tester.pumpAndSettle();
expect(topSliderValue, 0.5, reason: 'focused top Slider decreased after first arrowLeft');
expect(bottomSliderValue, 0.5, reason: 'unfocused bottom Slider unaffected by first arrowLeft');
// Pressing the down-arrow key moves focus down to the bottom slider
await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown);
await tester.pumpAndSettle();
expect(topSliderValue, 0.5, reason: 'arrowDown unfocuses top Slider, does not alter its value');
expect(bottomSliderValue, 0.5, reason: 'arrowDown focuses bottom Slider, does not alter its value');
// The bottom slider is now focused and can be adjusted with left and right arrow keys.
await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
await tester.pumpAndSettle();
expect(topSliderValue, 0.5, reason: 'unfocused top Slider unaffected by second arrowRight');
expect(bottomSliderValue, 0.55, reason: 'focused bottom Slider increased by second arrowRight');
await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft);
await tester.pumpAndSettle();
expect(topSliderValue, 0.5, reason: 'unfocused top Slider unaffected by second arrowLeft');
expect(bottomSliderValue, 0.5, reason: 'focused bottom Slider decreased by second arrowLeft');
// Pressing the up-arrow key moves focus back up to the top slider
await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
await tester.pumpAndSettle();
expect(topSliderValue, 0.5, reason: 'arrowUp focuses top Slider, does not alter its value');
expect(bottomSliderValue, 0.5, reason: 'arrowUp unfocuses bottom Slider, does not alter its value');
// The top slider is now focused again and can be adjusted with left and right arrow keys.
await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
await tester.pumpAndSettle();
expect(topSliderValue, 0.55, reason: 'focused top Slider increased after third arrowRight');
expect(bottomSliderValue, 0.5, reason: 'unfocused bottom Slider unaffected by third arrowRight');
await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft);
await tester.pumpAndSettle();
expect(topSliderValue, 0.5, reason: 'focused top Slider decreased after third arrowRight');
expect(bottomSliderValue, 0.5, reason: 'unfocused bottom Slider unaffected by third arrowRight');
});
testWidgets('Slider gains keyboard focus when it gains semantics focus on Windows', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
final SemanticsOwner semanticsOwner = tester.binding.pipelineOwner.semanticsOwner!;