Skip to content
This repository has been archived by the owner on Feb 22, 2023. It is now read-only.

Commit

Permalink
Add requestFocusOnTap to DropdownMenu (#117504)
Browse files Browse the repository at this point in the history
* Add canRequestFocus to TextField and requestFocusOnTap to DropdownMenu

* Address comments

* Address comments

---------

Co-authored-by: Qun Cheng <[email protected]>
  • Loading branch information
QuncCccccc and QuncCccccc authored Jan 27, 2023
1 parent fc3e824 commit ad1a44d
Show file tree
Hide file tree
Showing 5 changed files with 299 additions and 40 deletions.
4 changes: 3 additions & 1 deletion examples/api/lib/material/dropdown_menu/dropdown_menu.0.dart
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,9 @@ class _DropdownMenuExampleState extends State<DropdownMenuExample> {
leadingIcon: const Icon(Icons.search),
label: const Text('Icon'),
dropdownMenuEntries: iconEntries,
inputDecorationTheme: const InputDecorationTheme(filled: true),
inputDecorationTheme: const InputDecorationTheme(
filled: true,
contentPadding: EdgeInsets.symmetric(vertical: 5.0)),
onSelected: (IconLabel? icon) {
setState(() {
selectedIcon = icon;
Expand Down
40 changes: 35 additions & 5 deletions packages/flutter/lib/src/material/dropdown_menu.dart
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ class DropdownMenu<T> extends StatefulWidget {
this.controller,
this.initialSelection,
this.onSelected,
this.requestFocusOnTap,
required this.dropdownMenuEntries,
});

Expand Down Expand Up @@ -228,6 +229,19 @@ class DropdownMenu<T> extends StatefulWidget {
/// Defaults to null. If null, only the text field is updated.
final ValueChanged<T?>? onSelected;

/// Determine if the dropdown button requests focus and the on-screen virtual
/// keyboard is shown in response to a touch event.
///
/// By default, on mobile platforms, tapping on the text field and opening
/// the menu will not cause a focus request and the virtual keyboard will not
/// appear. The default behavior for desktop platforms is for the dropdown to
/// take the focus.
///
/// Defaults to null. Setting this field to true or false, rather than allowing
/// the implementation to choose based on the platform, can be useful for
/// applications that want to override the default behavior.
final bool? requestFocusOnTap;

/// Descriptions of the menu items in the [DropdownMenu].
///
/// This is a required parameter. It is recommended that at least one [DropdownMenuEntry]
Expand All @@ -242,7 +256,6 @@ class DropdownMenu<T> extends StatefulWidget {
class _DropdownMenuState<T> extends State<DropdownMenu<T>> {
final GlobalKey _anchorKey = GlobalKey();
final GlobalKey _leadingKey = GlobalKey();
final FocusNode _textFocusNode = FocusNode();
final MenuController _controller = MenuController();
late final TextEditingController _textEditingController;
late bool _enableFilter;
Expand Down Expand Up @@ -288,6 +301,23 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> {
}
}

bool canRequestFocus() {
if (widget.requestFocusOnTap != null) {
return widget.requestFocusOnTap!;
}

switch (Theme.of(context).platform) {
case TargetPlatform.iOS:
case TargetPlatform.android:
case TargetPlatform.fuchsia:
return false;
case TargetPlatform.macOS:
case TargetPlatform.linux:
case TargetPlatform.windows:
return true;
}
}

void refreshLeadingPadding() {
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
Expand Down Expand Up @@ -428,7 +458,6 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> {

@override
void dispose() {
_textEditingController.dispose();
super.dispose();
}

Expand Down Expand Up @@ -489,13 +518,12 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> {
builder: (BuildContext context, MenuController controller, Widget? child) {
assert(_initialMenu != null);
final Widget trailingButton = Padding(
padding: const EdgeInsets.symmetric(horizontal: 4.0),
padding: const EdgeInsets.all(4.0),
child: IconButton(
isSelected: controller.isOpen,
icon: widget.trailingIcon ?? const Icon(Icons.arrow_drop_down),
selectedIcon: widget.selectedTrailingIcon ?? const Icon(Icons.arrow_drop_up),
onPressed: () {
_textFocusNode.requestFocus();
handlePressed(controller);
},
),
Expand All @@ -511,7 +539,9 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> {
width: widget.width,
children: <Widget>[
TextField(
focusNode: _textFocusNode,
canRequestFocus: canRequestFocus(),
enableInteractiveSelection: canRequestFocus(),
textAlignVertical: TextAlignVertical.center,
style: effectiveTextStyle,
controller: _textEditingController,
onEditingComplete: () {
Expand Down
12 changes: 10 additions & 2 deletions packages/flutter/lib/src/material/text_field.dart
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,7 @@ class TextField extends StatefulWidget {
this.scribbleEnabled = true,
this.enableIMEPersonalizedLearning = true,
this.contextMenuBuilder = _defaultContextMenuBuilder,
this.canRequestFocus = true,
this.spellCheckConfiguration,
this.magnifierConfiguration,
}) : assert(obscuringCharacter.length == 1),
Expand Down Expand Up @@ -762,6 +763,13 @@ class TextField extends StatefulWidget {
/// * [AdaptiveTextSelectionToolbar], which is built by default.
final EditableTextContextMenuBuilder? contextMenuBuilder;

/// Determine whether this text field can request the primary focus.
///
/// Defaults to true. If false, the text field will not request focus
/// when tapped, or when its context menu is displayed. If false it will not
/// be possible to move the focus to the text field with tab key.
final bool canRequestFocus;

static Widget _defaultContextMenuBuilder(BuildContext context, EditableTextState editableTextState) {
return AdaptiveTextSelectionToolbar.editableText(
editableTextState: editableTextState,
Expand Down Expand Up @@ -976,15 +984,15 @@ class _TextFieldState extends State<TextField> with RestorationMixin implements
if (widget.controller == null) {
_createLocalController();
}
_effectiveFocusNode.canRequestFocus = _isEnabled;
_effectiveFocusNode.canRequestFocus = widget.canRequestFocus && _isEnabled;
_effectiveFocusNode.addListener(_handleFocusChanged);
}

bool get _canRequestFocus {
final NavigationMode mode = MediaQuery.maybeNavigationModeOf(context) ?? NavigationMode.traditional;
switch (mode) {
case NavigationMode.traditional:
return _isEnabled;
return widget.canRequestFocus && _isEnabled;
case NavigationMode.directional:
return true;
}
Expand Down
Loading

0 comments on commit ad1a44d

Please sign in to comment.