Skip to content

[SuperTextField] Flutter keyboard bugs workarounds and easier mapping shortcuts to keyboard actions #169

@venkatd

Description

@venkatd

Overview

Implementing TextFieldKeyboardHandler is not user friendly for two reasons:

  1. RawKeyEvent and RawKeyboard.instance.keysPressed have many open bugs in Flutter (listed below)
  2. Mapping keyboard shortcut combinations to handlers is tedious.

The proposed changes would be:

  • TextFieldKeyboardHandler would take LogicalKeyEvent (or something like it) as a parameter rather than RawKeyEvent. The event would protect SuperTextField devs from the messy details of Flutter bugs. (All of these bugs have been open for months, many with no activity, so don't think they will get closed any time soon.)

  • Expose a ShortcutsHandler to make mapping keyboard combinations to TextFieldKeyboardHandler easier, similar to how in Flutter we can map shortcuts to intents.

Here is a gist of the relevant code we have in our own app:
https://gist.github.com/venkatd/dcdefb929206b73ccb3d73b955d80f0c#file-logical_key_event-dart

I currently convert a RawKeyEvent to a LogicalKeyEvent in each handler. In this scenario below logicalKeyEvent.character just works out of the box since the workarounds are internal to LogicalKeyEvent. I can access the character and decide if I want to insert it or not.

TextFieldKeyboardHandlerResult insertTypedCharacter({
  required AttributedTextEditingController controller,
  required SuperSelectableTextState selectableTextState,
  required RawKeyEvent keyEvent,
}) {
  final logicalKeyEvent = LogicalKeyEvent.fromRawKeyEvent(
    keyEvent,
    RawKeyboard.instance.keysPressed,
  );

  final char = logicalKeyEvent.character;
  if (char == null) return TextFieldKeyboardHandlerResult.notHandled;

  controller.execute(ReplaceSelectedText(char));

  return TextFieldKeyboardHandlerResult.handled;
}

A dev can add their own key bindings earlier in the list of actions to override default shortcuts:

final myHandlers = <TextFieldKeyboardHandler>[
  ShortcutsHandler({
    LogicalKeySet(LogicalKeyboardKey.enter, LogicalKeyboardKey.shift,
        LogicalKeyboardKey.control): ReplaceSelectedText('\n---\n'),
    LogicalKeySet(LogicalKeyboardKey.enter, LogicalKeyboardKey.shift):
        ReplaceSelectedText('\n'),
  }),
  ...kPlatformKeyboardHandlers,
];

LogicalKeyEvent is also used in the shortcuts implementation so I can look up the matching key combination from a map (https://gist.github.com/venkatd/dcdefb929206b73ccb3d73b955d80f0c#file-keyboard_handlers-dart-L170-L186)

Perhaps renaming LogicalKeyEvent to TextFieldKeyboardEvent` could make the intent clearer?

Related Flutter PRs/issues

ShortcutActivator PR: flutter/flutter#78522

This is specialized to the Shortcuts/Intents/Actions, while LogicalKeyEvent takes a similar approach to remove OS keyboard combinations.

The "WHY" section in the PR explains the gotcha's on how we would want to map keyboard shortcuts to actions.

If I want to maintain a map of a KeySet to a handler and properly trigger the handlers, I need to account for the edge cases explained in "WHY".

"Sticky" modifier keys on Flutter web:
flutter/flutter#75627 (comment)
(Apparently other platforms too, but I could only repro on web)

Flutter web reports UI keys
flutter/flutter#81940
We're handling this on the keyboard handler side, but I've encapsulated this filter so any dev writing their own action doens't have to think about this edge case.

MacOS, if a keyboard shortcut combo includes the shift key, the letter becomes upper-cased so doesn't match:
flutter/flutter#75595

Finally, there's an issue related to a quote key on international keyboard layouts, but I think this would be SuperTextField specific since using a TextInputChannel would resolve the issue. Filing it inside this repo for now:
#244

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions