Skip to content

Commit ac66b29

Browse files
authored
Docs on the interaction between Shortcuts and text input (#144328)
I was talking with @tvolkert about the complex behavior of Shortcuts when a text field is focused. I created [this dartpad](https://dartpad.dev/?id=0b5c08fa85637422baa84927b7f1ee5f) to illustrate the problem, which shows a key being stolen from a text field by Shortcuts, and how to prevent that using DoNothingAndStopPropagationIntent. This PR adds a section in the docs explaining how all of this works and how to override this "stealing" problem.
1 parent 835112d commit ac66b29

File tree

2 files changed

+40
-3
lines changed

2 files changed

+40
-3
lines changed

packages/flutter/lib/src/widgets/default_text_editing_shortcuts.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import 'text_editing_intents.dart';
2525
/// cause CJK input methods to discard more text than they should when the
2626
/// backspace key is pressed during text composition on iOS.
2727
///
28+
/// {@macro flutter.widgets.editableText.shortcutsAndTextInput}
29+
///
2830
/// {@tool snippet}
2931
///
3032
/// This example shows how to use an additional [Shortcuts] widget to override

packages/flutter/lib/src/widgets/editable_text.dart

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -631,9 +631,10 @@ class _DiscreteKeyFrameSimulation extends Simulation {
631631
/// This widget provides default [Action]s for handling common text editing
632632
/// [Intent]s such as deleting, copying and pasting in the text field. These
633633
/// [Action]s can be directly invoked using [Actions.invoke] or the
634-
/// [Actions.maybeInvoke] method. The default text editing keyboard [Shortcuts]
635-
/// also use these [Intent]s and [Action]s to perform the text editing
636-
/// operations they are bound to.
634+
/// [Actions.maybeInvoke] method. The default text editing keyboard [Shortcuts],
635+
/// typically declared in [DefaultTextEditingShortcuts], also use these
636+
/// [Intent]s and [Action]s to perform the text editing operations they are
637+
/// bound to.
637638
///
638639
/// The default handling of a specific [Intent] can be overridden by placing an
639640
/// [Actions] widget above this widget. See the [Action] class and the
@@ -683,6 +684,40 @@ class _DiscreteKeyFrameSimulation extends Simulation {
683684
/// | [CopySelectionTextIntent] | Copies or cuts the selected text into the clipboard |
684685
/// | [PasteTextIntent] | Inserts the current text in the clipboard after the caret location, or replaces the selected text if the selection is not collapsed. |
685686
///
687+
/// ## Text Editing [Shortcuts]
688+
///
689+
/// It's also possible to directly remap keyboard shortcuts to new [Intent]s by
690+
/// inserting a [Shortcuts] widget above this in the widget tree. When using
691+
/// [WidgetsApp], the large set of default text editing keyboard shortcuts are
692+
/// declared near the top of the widget tree in [DefaultTextEditingShortcuts],
693+
/// and any [Shortcuts] widget between it and this [EditableText] will override
694+
/// those defaults.
695+
///
696+
/// {@template flutter.widgets.editableText.shortcutsAndTextInput}
697+
/// ### Interactions Between [Shortcuts] and Text Input
698+
///
699+
/// Shortcuts prevent text input fields from receiving their keystrokes as text
700+
/// input. For example, placing a [Shortcuts] widget in the widget tree above
701+
/// a text input field and creating a shortcut for [LogicalKeyboardKey.keyA]
702+
/// will prevent the field from receiving that key as text input. In other
703+
/// words, typing key "A" into the field will trigger the shortcut and will not
704+
/// insert a letter "a" into the field.
705+
///
706+
/// This happens because of the way that key strokes are handled in Flutter.
707+
/// When a keystroke is received in Flutter's engine, it first gives the
708+
/// framework the opportunity to handle it as a raw key event through
709+
/// [SystemChannels.keyEvent]. This is what [Shortcuts] listens to indirectly
710+
/// through its [FocusNode]. If it is not handled, then it will proceed to try
711+
/// handling it as text input through [SystemChannels.textInput], which is what
712+
/// [EditableTextState] listens to through [TextInputClient].
713+
///
714+
/// This behavior, where a shortcut prevents text input into some field, can be
715+
/// overridden by using another [Shortcuts] widget lower in the widget tree and
716+
/// mapping the desired key stroke(s) to [DoNothingAndStopPropagationIntent].
717+
/// The key event will be reported as unhandled by the framework and will then
718+
/// be sent as text input as usual.
719+
/// {@endtemplate}
720+
///
686721
/// ## Gesture Events Handling
687722
///
688723
/// When [rendererIgnoresPointer] is false (the default), this widget provides

0 commit comments

Comments
 (0)