How to process a paste command but still let TextField handle it?
It took me hours to figure out. I was not able to find sample code, and ChatGPT and Gemini kept hallucinating answers...
Basic implementation. TextField stops responding to paste events
Here's the usual way of handling a shortkey:
Shortcuts(
shortcuts: const <ShortcutActivator, Intent>{
SingleActivator(
LogicalKeyboardKey.keyV,
// Command key on Mac.
meta: true,
): PasteIntent(),
},
child: Actions(
actions: {
PasteIntent:
CallbackAction<PasteIntent>(onInvoke: (intent) {
// Do something
})
},
child: TextField(...)
))
// Later define our custom Intent
class PasteIntent {}
And it will indeed execute your code when you press Command+V. However, this will prevent the TextField from ever processing the command.
Attempting to pass through the event to TextField
DoNothingAndStopPropagationIntent
I found this DoNothingAndStopPropagationIntent that supposedly "disable[s] a keyboard shortcut defined by a widget higher in the widget hierarchy", so I thought I could create a Shortcuts, handle the event, then add another Shortcuts using that StopPropagationIntent that wraps the TextField. It did not work.
Reusing PasteTextIntent, Actions.maybeFind
Then I tried to reuse what I assumed was the original PasteIntent used by Flutter widgets themselves. Instead of mapping Command+V to my custom PasteIntent, I used PasteTextIntent. Then in my callback, I tried to find other actions in the Context based on https://docs.flutter.dev/ui/interactivity/actions-and-shortcuts#invoking-actions. Something like this:
Action<PasteTextIntent>? paste = Actions.maybeFind<PasteTextIntent>(
context,
);
paste?.invoke(intent);
But it did not find TextField's Action for PasteTextIntent.
consumesKey: false?
Another thing I tried was to create a real Action instead of a CallbackAction, and implement Action's consumesKey.
By returning false, I hoped that Shortcut would think my Action didn't consume the event, and propagate it down to the TextField. This did not work either.
The solution: Action.overridable
Eventually, I stumbled upon Action.overridable and that's how I fixed my issue. Action.overridable makes someone allow someone else to override their Action. The beauty of it is that if you override that Action, you also get a reference to the default action.
The issue is, it only works if the creator of the original Action allows their Action to be overridable. Does TextField allow it?
And the answer is YES! So here's my final solution. This will execute my code to handle Command+V but also let TextField handle it:
Shortcuts(
shortcuts: const <ShortcutActivator, Intent>{
SingleActivator(
LogicalKeyboardKey.keyV,
// command on Mac.
meta: true,
): PasteTextIntent(SelectionChangedCause.keyboard),
},
child: Actions(
actions: <Type, Action<Intent>>{
// Override TextField's handler to also support images.
PasteTextIntent:
PasteScreenshotAction(onFilePasted: (xfile) {
filesPicked.add([xfile]);
})
},
child: TextField(...)
))
...
class PasteScreenshotAction extends Action<PasteTextIntent> {
PasteScreenshotAction({required this.onFilePasted});
final void Function(XFile xfile) onFilePasted;
@override
Object? invoke(covariant PasteTextIntent intent) async {
// Let TextField process the paste.
callingAction?.invoke(intent);
// And also handle images from the clipboard.
..
}
}