Skip to content

EditableText should handle shortcuts related to text formatting without overriding with Shortcuts #174758

Closed

Description

@orestesgaolin

Steps to reproduce

  1. Run this sample on macOS and type left option + a in the text field.

Expected results

Expected result is to receive proper text input event of diacritics even when the same keyboard combination is used for a different shortcut.

I know about the suggested workaround in cases like this in the section Interactions Between Shortcuts and Text Input but that is not feasible for any app that uses dynamic shortcuts configuration like this one https://github.com/orestesgaolin/flutter_desktop_sandbox.

The keyboard combination for diacritics and other super specific text input actions should be handled by the TextField/EditableText even if there's shorcut defined for the same combination above in the tree.
Alternatively there should be a way to detect that a given shortcut should also be reported to TextInputClient.

Actual results

Key combination is captured by the MaterialApp's Shortcuts.

Code sample

Code sample
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() {
  runApp(const MainApp());
}

final globalKey = GlobalKey<NavigatorState>();

class MainApp extends StatelessWidget {
  const MainApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      navigatorKey: globalKey,
      shortcuts: {
        LogicalKeySet(LogicalKeyboardKey.altLeft, LogicalKeyboardKey.keyA):
            ShowOverlayIntent('Alt + A pressed!'),
      },
      actions: {
        ShowOverlayIntent: CallbackAction<ShowOverlayIntent>(
          onInvoke: (intent) {
            final overlay = globalKey.currentState?.overlay;
            if (overlay == null) {
              return;
            }
            final overlayEntry = OverlayEntry(
              builder: (context) => Positioned(
                bottom: 100,
                left: 50,
                right: 50,
                child: Material(
                  color: Colors.transparent,
                  child: Container(
                    padding: const EdgeInsets.all(10),
                    color: Colors.black.withValues(alpha: 0.7),
                    child: Text(
                      intent.message,
                      style: const TextStyle(color: Colors.white, fontSize: 16),
                    ),
                  ),
                ),
              ),
            );

            overlay?.insert(overlayEntry);
            Future.delayed(const Duration(seconds: 2), () {
              overlayEntry.remove();
            });
          },
        ),
      },
      home: Scaffold(body: Center(child: TextField())),
    );
  }
}

class ShowOverlayIntent extends Intent {
  final String message;

  const ShowOverlayIntent(this.message);
}

Screenshots or Video

Screenshots / Video demonstration
screenshot_20250830_171839.mp4

Logs

Flutter Doctor output

Doctor output
[✓] Flutter (Channel stable, 3.35.2, on macOS 15.6.1 24G90 darwin-arm64, locale pl-PL) [273ms]
    • Flutter version 3.35.2 on channel stable at /Users/dominik/fvm/versions/stable
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision 05db968908 (5 days ago), 2025-08-25 10:21:35 -0700
    • Engine revision a8bfdfc394
    • Dart version 3.9.0
    • DevTools version 2.48.0
    • Feature flags: enable-web, no-enable-linux-desktop, enable-macos-desktop, no-enable-windows-desktop, enable-android,
      enable-ios, cli-animations, enable-lldb-debugging

[!] Android toolchain - develop for Android devices (Android SDK version 35.0.0) [657ms]
    • Android SDK at /Users/dominik/Library/Android/sdk
    • Emulator version 35.4.9.0 (build_id 13025442) (CL:N/A)
    ✗ cmdline-tools component is missing.
      Try installing or updating Android Studio.
      Alternatively, download the tools from https://developer.android.com/studio#command-line-tools-only and make sure to
      set the ANDROID_HOME environment variable.
      See https://developer.android.com/studio/command-line for more details.
    ✗ Android license status unknown.
      Run `flutter doctor --android-licenses` to accept the SDK licenses.
      See https://flutter.dev/to/macos-android-setup for more details.

[✓] Xcode - develop for iOS and macOS (Xcode 16.4) [1 035ms]
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Build 16F6
    • CocoaPods version 1.16.2

[✓] Chrome - develop for the web [7ms]
    • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Android Studio (version 2024.3) [6ms]
    • Android Studio at /Applications/Android Studio.app/Contents
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart
    • Java version OpenJDK Runtime Environment (build 21.0.5+-13047016-b750.29)

[✓] VS Code (version 1.103.2) [5ms]
    • VS Code at /Applications/Visual Studio Code.app/Contents
    • Flutter extension version 3.116.0

[✓] Connected device (3 available) [5,9s]
    • iPhone 12 mini (wireless) (mobile) • 00008101-0001510C3CF0001E • ios            • iOS 18.5 22F76
    • macOS (desktop)                    • macos                     • darwin-arm64   • macOS 15.6.1 24G90 darwin-arm64
    • Chrome (web)                       • chrome                    • web-javascript • Google Chrome 139.0.7258.155

[✓] Network resources [232ms]
    • All expected network resources are available.

! Doctor found issues in 1 category.

Activity

  1. justinmc commented on Aug 31, 2025

    @justinmc
    Contributor

    I know about the suggested workaround in cases like this in the section Interactions Between Shortcuts and Text Input but that is not feasible for any app that uses dynamic shortcuts configuration like this one https://github.com/orestesgaolin/flutter_desktop_sandbox.

    Can you explain in more detail why that approach won't work in your app?

    This is probably a situation where Flutter needs to provide the ability to support both use cases. I imagine there are valid reasons to capture diacritic keyboard shortcuts.

  2. orestesgaolin commented on Aug 31, 2025

    @orestesgaolin
    ContributorAuthor

    Can you explain in more detail why that approach won't work in your app?

    It's not possible to get all defined text field shortcuts to cancel them out, even if I wrap TextField with DefaultTextEditing shortcuts.

    An extreme case of that is setting single key shortcuts which will override typing a given letter, see video below (this is not overridden with custom Shortcuts, just overridden with DefaultTextEditingShortcuts):

    screenshot_20250831_163052.mp4

    This behavior can potentially be overridden if I wrap TextField with something like below i.e. single keys will always be ignored (this does not exclude single activators for function keys or any other special purpose keys):

      Map<ShortcutActivator, Intent> getTextFieldShortcuts() {
        final definedKeys = shortcuts.map((s) => s.keys);
        final definedSingleKeys = definedKeys.where((e) => e.triggers.length == 1).expand((e) => e.triggers);
    
        return {
          for (final key in definedSingleKeys) SingleActivator(key): DoNothingAndStopPropagationIntent(),
        };
      }

    The more I think about it, the more it seems that there are only text editing shortcuts with meta and alt key (which are provided with DefaultTextEditingShortcuts e.g. alt + arrow left or cmd + a) and extra characters with alt key (e.g. alt + a resulting in diacritics ą). So in theory if I have any shortcut that includes any of the alt keys, I should be able to cancel it with something like this:

    Map<ShortcutActivator, Intent> getTextFieldShortcuts() {
        final map = <ShortcutActivator, Intent>{};
        final definedKeys = shortcuts.map((s) => s.keys).toList();
        final definedSingleKeys = definedKeys.where((e) => e.triggers.length == 1).expand((e) => e.triggers); // todo: ignore function keys
        final definedWithAlt = definedKeys.where(
          (e) =>
              e.triggers.length == 2 &&
              (e.triggers.contains(LogicalKeyboardKey.alt) ||
                  e.triggers.contains(LogicalKeyboardKey.altLeft) ||
                  e.triggers.contains(LogicalKeyboardKey.altRight)),
        );
    
        for (final key in definedSingleKeys) {
          map[SingleActivator(key, alt: true)] = DoNothingAndStopPropagationIntent();
        }
    
        final altKeys = definedWithAlt
            .expand((e) => e.triggers)
            .where(
              (e) => e != LogicalKeyboardKey.alt && e != LogicalKeyboardKey.altLeft && e != LogicalKeyboardKey.altRight,
            )
            .toSet();
    
        for (final key in altKeys) {
          map[SingleActivator(key, alt: true)] = DoNothingAndStopPropagationIntent(); // include alt keys!
        }
        return map;
      }

    and then use like this:

        child: DefaultTextEditingShortcuts(
          child: Shortcuts(
            shortcuts: getTextFieldShortcuts(),
            child: EditableText(

    This ensures that even if user defines alt + a shortcut, the text field will behave as expected. It's a bit of whack-a-mole game because there may be some more text specific shortcuts that I didn't think of to cancel. Anyway, it seems that this is sufficient for me to provide:

    • DefaultTextEditingShortcuts
    • Shortcuts with map of shortcuts cancelling out any potential user-defined shortcuts that include alt
    screenshot_20250831_165447.mp4

    All in all, it seems that for my purposes I should be able to find workarounds in existing framework code, although it would be still useful to redefine text editing shortcuts like suggested in #97830. Hence, I'm closing my issue in favor of #97830.

  3. github-actions commented on Sep 14, 2025

    @github-actions

    This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.

  4. locked as resolved and limited conversation to collaborators on Sep 14, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions