Unverified Commit ae7fcc7e authored by Eric egramond's avatar Eric egramond Committed by GitHub

Updating the Slider Widget to allow up and down arrow keys to navigate out of...

Updating the Slider Widget to allow up and down arrow keys to navigate out of the slider when in directional NavigationMode. (#103149)
parent 2f657536
...@@ -475,13 +475,22 @@ class _SliderState extends State<Slider> with TickerProviderStateMixin { ...@@ -475,13 +475,22 @@ class _SliderState extends State<Slider> with TickerProviderStateMixin {
Timer? interactionTimer; Timer? interactionTimer;
final GlobalKey _renderObjectKey = GlobalKey(); final GlobalKey _renderObjectKey = GlobalKey();
// Keyboard mapping for a focused slider. // 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.arrowUp): _AdjustSliderIntent.up(),
SingleActivator(LogicalKeyboardKey.arrowDown): _AdjustSliderIntent.down(), SingleActivator(LogicalKeyboardKey.arrowDown): _AdjustSliderIntent.down(),
SingleActivator(LogicalKeyboardKey.arrowLeft): _AdjustSliderIntent.left(), SingleActivator(LogicalKeyboardKey.arrowLeft): _AdjustSliderIntent.left(),
SingleActivator(LogicalKeyboardKey.arrowRight): _AdjustSliderIntent.right(), 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. // Action mapping for a focused slider.
late Map<Type, Action<Intent>> _actionMap; late Map<Type, Action<Intent>> _actionMap;
...@@ -735,13 +744,23 @@ class _SliderState extends State<Slider> with TickerProviderStateMixin { ...@@ -735,13 +744,23 @@ class _SliderState extends State<Slider> with TickerProviderStateMixin {
break; 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( return Semantics(
container: true, container: true,
slider: true, slider: true,
onDidGainAccessibilityFocus: handleDidGainAccessibilityFocus, onDidGainAccessibilityFocus: handleDidGainAccessibilityFocus,
child: FocusableActionDetector( child: FocusableActionDetector(
actions: _actionMap, actions: _actionMap,
shortcuts: _shortcutMap, shortcuts: shortcutMap,
focusNode: focusNode, focusNode: focusNode,
autofocus: widget.autofocus, autofocus: widget.autofocus,
enabled: _enabled, enabled: _enabled,
......
...@@ -2154,6 +2154,102 @@ void main() { ...@@ -2154,6 +2154,102 @@ void main() {
expect(value, 0.5); expect(value, 0.5);
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS })); }, 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 { testWidgets('Slider gains keyboard focus when it gains semantics focus on Windows', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester); final SemanticsTester semantics = SemanticsTester(tester);
final SemanticsOwner semanticsOwner = tester.binding.pipelineOwner.semanticsOwner!; final SemanticsOwner semanticsOwner = tester.binding.pipelineOwner.semanticsOwner!;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment