Add Shift+Enter shortcut example for TextField.#167952
Add Shift+Enter shortcut example for TextField.#167952auto-submit[bot] merged 3 commits intoflutter:masterfrom
Conversation
|
I wonder if this option should be made opt-in. I'll leave it to the framework folks... |
|
We should be careful about creating behavior that differs from other (desktop) platforms |
|
I think it makes sense to make this opt-in. I've seen both behaviors in native web apps (cmd+enter submits or not in a multiline field). So I'm thinking Flutter should be configurable as well if I'm understanding correctly. CC @mdebbar |
|
Let's figure out the framework side of the story first. That will dictate how the web side will be implemented. @justinmc or @Renzo-Olivares do you wanna chime in with some ideas on how to provide this opt-in in the framework? I suggest that we think about this holistically to create a cohesive story for handling cmd+enter, shift+enter, etc. |
|
I agree that we should have a solution that covers all platforms here. It sounds like this might not be possible to solve in a straightforward way exclusively in the framework though (say with Shortcuts), because the enter key action is caught by the embedder first. |
|
Could we solve this by, in each embedder, when a non-newline input action and a enter+shift keystroke is received, sending that key even to the framework instead of handling it as a submit? Then in the framework, the app developer could handle it via Shortcuts. But what about the default case, where the app developer just wants the field to submit? We would probably need to map shift+enter to a DoNothingAndStopPropagation intent by default. Just some vague notes on ideas thrown out in triage. We'd have to try this and/or think about this more. |
|
Currently you can do something like this and the field won't be submitted on Shortcuts(
shortcuts: <ShortcutActivator, Intent>{
SingleActivator(LogicalKeyboardKey.enter, shift: true): const SelectAllTextIntent(
SelectionChangedCause.keyboard,
),
},
child: TextField(
maxLines: null,
textInputAction: TextInputAction.done,
onSubmitted: (String? value) {
debugPrint('Submitted: $value');
},
),
),Maybe in the web default text editing shortcuts mapping we could add: SingleActivator(LogicalKeyboardKey.enter, shift: true): DoNothingAndStopPropagationEnterKeyTextIntent(),and make the action it maps to overridable. Then someone could override this with: Actions(
actions: <Type, Action<Intent>> {
DoNothingAndStopPropagationEnterKeyTextIntent : _insertNewLineAction,
},
child: TextField(
controller: _controller,
maxLines: null,
textInputAction: TextInputAction.done,
onSubmitted: (String? value) {
debugPrint('Submitted: $value');
},
),
),Though currently this is already possible at the moment since we don't define default shortcuts for enter + shift. Shortcuts(
shortcuts: <ShortcutActivator, Intent>{
if (kIsWeb)
SingleActivator(LogicalKeyboardKey.enter, shift: true):
InsertNewLineTextIntent(),
},
child: Actions(
actions: <Type, Action<Intent>>{
if (kIsWeb)
InsertNewLineTextIntent:
CallbackAction<InsertNewLineTextIntent>(
onInvoke: (InsertNewLineTextIntent intent) {
final TextEditingValue value =
_controller.value;
final String newText = value.text.replaceRange(
value.selection.start,
value.selection.end,
'\n',
);
_controller.value = value.copyWith(
text: newText,
selection: TextSelection.collapsed(
offset: value.selection.start + 1,
),
);
},
),
},
child: TextField(
controller: _controller,
maxLines: null,
textInputAction: TextInputAction.done,
onSubmitted: (String? value) {
debugPrint('Submitted: $value');
},
),
),
),
class InsertNewLineTextIntent extends Intent {} |
|
@ksokolovskyi What do you think about changing this PR based on the suggestions given by @Renzo-Olivares? |
|
@justinmc @Renzo-Olivares @mdebbar, sorry for the silence from my side. Thanks a lot for your thoughts around this issue! |
|
Hi @justinmc @Renzo-Olivares, I reverted my changes to the web engine and added Without the override, the behavior remains the same as in the stable, but now users can override the default action for Demo Source Codeimport 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const App());
}
class App extends StatelessWidget {
const App({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Padding(
padding: const EdgeInsets.all(20),
child: Center(
child: Input(),
),
),
),
);
}
}
class Input extends StatefulWidget {
const Input({super.key});
@override
State<Input> createState() => _InputState();
}
class _InputState extends State<Input> {
final _controller = TextEditingController();
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Actions(
actions: <Type, Action<Intent>>{
if (kIsWeb)
DoNothingAndStopEnterKeyPropagationIntent:
CallbackAction<DoNothingAndStopEnterKeyPropagationIntent>(
onInvoke: (DoNothingAndStopEnterKeyPropagationIntent intent) {
final TextEditingValue value = _controller.value;
final String newText = value.text.replaceRange(
value.selection.end,
null,
'\n',
);
_controller.value = value.copyWith(
text: newText,
selection: TextSelection.collapsed(
offset: value.selection.start + 1,
),
);
return null;
},
),
},
child: TextField(
controller: _controller,
decoration: InputDecoration(border: OutlineInputBorder()),
maxLines: null,
textInputAction: TextInputAction.done,
onSubmitted: (value) {
print('ON SUBMITTED: "$value"');
},
),
);
}
}
DemoIn the demo, I first type text, then press demo.movI am not sure whether this is what you expected, so I would greatly appreciate feedback from your side. |
justinmc
left a comment
There was a problem hiding this comment.
I think ideally we should get the default behavior correct on web out-of-the-box. @ksokolovskyi can you take a look at exactly what that default behavior should be if you haven't already? So what should happen on the native web in each of these cases in a multiline field:
- enter
- shift + enter
- cmd/ctrl + enter
- option + enter
- alt + enter
With the current state of the PR, I think app developers still have some work to do to get this behavior correct, and reimplementing the insertion of a newline character is a bit rough for something that could be fairly common.
| /// | ||
| /// See also: | ||
| /// | ||
| /// * [DefaultTextEditingShortcuts], which triggers this [Intent]. |
There was a problem hiding this comment.
Nit: Maybe link to DoNothingAndStopPropagationIntent too?
|
@justinmc on the web, the multiline field ( Since there is no default submission of the As the issue's OP works on an AI chat toolkit, I decided to take a look at how popular AI chat clients' fields behave.
|
|
Ah you're right, this is not a built-in behavior on web, sorry. I guess Flutter shouldn't try to make this work by default either. So then we need to decide if it's worth it to make this change, or if we expect everyone to use the workaround. Option 1: No changeWe make no code change and expect users to do the workaround in order to handle this, where they catch shift+enter with a Shortcuts widget and then define what happens in an Actions. If we choose this option, maybe we should add this as an example in the examples directory in order to help users discover this. Taking @Renzo-Olivares's code from #167952 (comment): Must use Shortcuts and ActionsShortcuts(
shortcuts: <ShortcutActivator, Intent>{
if (kIsWeb)
SingleActivator(LogicalKeyboardKey.enter, shift: true):
InsertNewLineTextIntent(),
},
child: Actions(
actions: <Type, Action<Intent>>{
if (kIsWeb)
InsertNewLineTextIntent:
CallbackAction<InsertNewLineTextIntent>(
onInvoke: (InsertNewLineTextIntent intent) {
final TextEditingValue value =
_controller.value;
final String newText = value.text.replaceRange(
value.selection.start,
value.selection.end,
'\n',
);
_controller.value = value.copyWith(
text: newText,
selection: TextSelection.collapsed(
offset: value.selection.start + 1,
),
);
},
),
},
child: TextField(
controller: _controller,
maxLines: null,
textInputAction: TextInputAction.done,
onSubmitted: (String? value) {
debugPrint('Submitted: $value');
},
),
),
),
class InsertNewLineTextIntent extends Intent {}Option 2: Add DoNothingAndStopPropagationEnterKeyTextIntentIn this case we add DoNothingAndStopPropagationEnterKeyTextIntent to DefaultTextEditingShortcuts in order to make this a little bit easier on users: Only need to use ActionsActions(
actions: <Type, Action<Intent>> {
DoNothingAndStopPropagationEnterKeyTextIntent : CallbackAction<InsertNewLineTextIntent>(
onInvoke: (InsertNewLineTextIntent intent) {
final TextEditingValue value =
_controller.value;
final String newText = value.text.replaceRange(
value.selection.start,
value.selection.end,
'\n',
);
_controller.value = value.copyWith(
text: newText,
selection: TextSelection.collapsed(
offset: value.selection.start + 1,
),
);
},
),
},
child: TextField(
controller: _controller,
maxLines: null,
textInputAction: TextInputAction.done,
onSubmitted: (String? value) {
debugPrint('Submitted: $value');
},
),
),My thoughtsI like option 1. Adding the intent DoNothingAndStopPropagationEnterKeyTextIntent seems out of the ordinary for this one specific case when users can already do this themselves. It looks like on the web users also need to set up this behavior themselves. I think adding an example will help Flutter developers figure this out. |
|
@justinmc thanks a lot for your thoughts on this issue and detailed options description! @Renzo-Olivares @kevmoo @mdebbar, what do you think? |
|
I like option 1 as well, and definitely agree that we should add an example in the examples directory and link it in the |
|
@ksokolovskyi Sounds good then, if you want to edit this PR to just be an example of how to do this, I'd be happy to rereview! |
|
@justinmc @Renzo-Olivares Thanks! I'll be away next week, so I'll proceed with the update upon my return. |
Renzo-Olivares
left a comment
There was a problem hiding this comment.
LGTM, thank you for the contribution!
loic-sharma
left a comment
There was a problem hiding this comment.
Thanks for the wonderful contribution, and thanks everyone for the excellent discussion! :)
Co-authored-by: Loïc Sharma <[email protected]>
|
Thanks a lot, everyone, for the review and suggestions! |
Closes #167902
This PR adds a new
TextFieldexample which shows how to useShortcutsandActionswidgets to create a customShift+Enterkeyboard shortcut for inserting a new line.example.mov