Skip to content

Conversation

@chunhtai
Copy link
Contributor

@chunhtai chunhtai commented May 10, 2022

Previously the text editing shortcuts were disabled on web because shortcuts are handled on the platform side for text fields.

This pr changes it that the is update disabled if the focus node is under the Editable Text so that other widgets who want to handle the shortcuts if it is fired above the EditableText

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

@flutter-dashboard
Copy link

It looks like this pull request may not have tests. Please make sure to add tests before merging. If you need an exemption to this rule, contact Hixie on the #hackers channel in Chat (don't just cc him here, he won't see it! He's on Discord!).

If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix?

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

@flutter-dashboard flutter-dashboard bot added a: text input Entering text in a text field or keyboard related problems framework flutter/packages/flutter repository. See also f: labels. labels May 10, 2022
@chunhtai chunhtai force-pushed the web-text-shortcut branch from 77e9481 to 5ef4084 Compare May 10, 2022 17:41
Copy link
Contributor Author

@chunhtai chunhtai May 10, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These shortcuts were ignored because the shortcut is built on top of the DefaultTextEditingShortcuts which prevented these shortcut from bubbling up.

I am not sure if this is the correct behavior, but there are tests to verify the arrow key does not cause scroll in CupertinoTextField, so I remove these shortcuts to maintain the same behavior

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe @Piinks knows? These shortcuts allow scrolling on a normal Scrollable, and I think they were not used by text inputs because the arrow keys move the cursor instead.

@chunhtai chunhtai requested a review from justinmc May 10, 2022 19:02
Copy link
Contributor

@justinmc justinmc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously, if you wanted to remap ctrl-A to ctrl-B, you could do this on all platforms by using a Shortcuts widget at the root of the app and mapping ctrl-A to DoNothingIntent and ctrl-B to SelectAllTextIntent (see code below). This worked on web as well as other platforms.

With this PR, on web, ctrl-A is correctly blocked but ctrl-B doesn't work because SelectAllTextIntent isn't used anymore in EditableText. Is it possible to still include those Intent to Action mappings?

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

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  static const String _title = 'Flutter Code Sample';

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: _title,
      home: Shortcuts(
        shortcuts: {
          const SingleActivator(LogicalKeyboardKey.keyB, control: true): const SelectAllTextIntent(SelectionChangedCause.keyboard),
          const SingleActivator(LogicalKeyboardKey.keyA, control: true): DoNothingIntent(),
        },
        child: Scaffold(
          appBar: AppBar(title: const Text(_title)),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: const <Widget>[
                TextField(),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe @Piinks knows? These shortcuts allow scrolling on a normal Scrollable, and I think they were not used by text inputs because the arrow keys move the cursor instead.

@chunhtai
Copy link
Contributor Author

requirement

  1. shortcut is not handled for web.
  2. because the Autocomplete widget, EdtiableText cannot override shortcuts
  3. EditableText need to still be able to handle selectall and etc on web

@chunhtai
Copy link
Contributor Author

Either I need to annotate the textediting intent that fired from the DefaultTextEditingShortcut so the actions in EditableText know when to ignore these intent if platform is web, or I create a new intent subclass that allows for parent shortcut to override.

Either way iseems hacky. I guess it will be easier to keep this as is and add web specific shortcut back to SelectionArea.

@chunhtai chunhtai closed this May 10, 2022
@chunhtai chunhtai changed the title Refactor web text editing shortcuts Add more test for web text editing shortcuts May 10, 2022
@flutter-dashboard flutter-dashboard bot added the f: cupertino flutter/packages/flutter/cupertino repository label May 10, 2022
@chunhtai
Copy link
Contributor Author

I guess the tests are still worth adding

@chunhtai chunhtai reopened this May 10, 2022
@chunhtai chunhtai force-pushed the web-text-shortcut branch from 1d3e0b1 to f7a7b6e Compare May 10, 2022 20:57
@chunhtai chunhtai requested a review from justinmc May 10, 2022 20:57
@chunhtai chunhtai force-pushed the web-text-shortcut branch from f7a7b6e to 75b64b3 Compare May 10, 2022 22:27
@chunhtai chunhtai changed the title Add more test for web text editing shortcuts Refactor web text editing shortcuts May 10, 2022
@chunhtai
Copy link
Contributor Author

I figured out a solution that fix all the requirements

expect(copySpy.invoked, isTrue);
expect(pasteSpy.invoked, isTrue);
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }), skip: kIsWeb); // [intended] Web uses a different set of shortcuts.
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

web can now handle test editing shortcut as long as the context in not inside TextField

@flutter-dashboard flutter-dashboard bot added the f: material design flutter/packages/flutter/material repository. label May 11, 2022
Copy link
Contributor

@justinmc justinmc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my comment with the code sample, I want to make sure I understand this and can reproduce the desired behavior. Then I can make sure the docs are clear about it. Otherwise looks good.

Also, are you familiar with @LongCatIsLooong's Action.overridable? I don't think that it would improve this situation really, but just making sure it's on your radar.

if (kIsWeb) {
// On web platform, this shortcuts makes sure the following:
//
// 1. Shortcuts fired in TextField are swallowed.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"fired in TextField" => "fired when a TextField is focused"

Let me know if that's not what you mean. I think that's more explicit. Same for point 2 below.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By "swallowed" do you mean that Flutter ignores them and passes them through to the browser?

//
// 1. Shortcuts fired in TextField are swallowed.
// 2. Shortcuts fired above TextField will still trigger corresponding
// intents.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which intents?

/// Creates a [DefaultTextEditingShortcuts] widget that provides the default text editing
/// shortcuts on the current platform.
DefaultTextEditingShortcuts({
const DefaultTextEditingShortcuts({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's cool that this can be const now.

// [DoNothingAndStopPropagationTextIntent] will not be handled because the
// context is above the TextField. The key event will then trigger the
// default Text Editing Shortcuts.
result = Shortcuts(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I see, it took me a couple of times reading this code before I even realized there are 2 nested Shortcuts widgets now.

Can you explain these two situations in code? So in the app below running the code from this PR, I would expect that after tapping the button, ctrl/cmd - A would invoke SelectAllTextIntent, but it would not reach _SelectAllAction in EditableText due to the focus being outside of EditableText?

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

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  MyApp({Key? key}) : super(key: key);

  static const String _title = 'Flutter Code Sample';

  final FocusNode _focusNode = FocusNode();

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: _title,
      home: Shortcuts(
        shortcuts: {
          const SingleActivator(LogicalKeyboardKey.keyB, control: true): const SelectAllTextIntent(SelectionChangedCause.keyboard),
          const SingleActivator(LogicalKeyboardKey.keyB, meta: true): const SelectAllTextIntent(SelectionChangedCause.keyboard),
          //const SingleActivator(LogicalKeyboardKey.keyA, control: true): DoNothingIntent(),
          //const SingleActivator(LogicalKeyboardKey.keyA, meta: true): DoNothingIntent(),
        },
        child: Scaffold(
          appBar: AppBar(title: const Text(_title)),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Focus(
                  focusNode: _focusNode,
                  child: TextButton(
                    onPressed: () {
                      _focusNode.requestFocus();
                    },
                    child: const Text('Press to focus me'),
                  ),
                ),
                const TextField(),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this situation, in Web ctrl+A will first fire DoNothingAndStopPropagationTextIntent, but no one will handle it since the focus is above the TextField. It will then fire SelectAllTextIntent but still no one will handle it since the focus is above the TextField.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood and I verified this behavior on web and Mac with my code example 👍

@chunhtai
Copy link
Contributor Author

I can't think of a way to solve this using Action.override. Do you have other idea in your mind?

These are all the situations I need to cover on web

If shortcuts are fired when focusNode is below TextField

  1. AutoComplete widget overrides arrow up and down shortcuts to be able to keyboard select options
  2. the Scrollable shortcuts cannot be fired (See WidgetsApp _defaultWebShortcuts)

If shortcuts are fired when focusNode is above TextField

  1. the Scrollable shortcuts can be fired (See WidgetsApp _defaultWebShortcuts)

Copy link
Contributor

@justinmc justinmc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just some docs improvements really, otherwise everything looks good to me. Thanks for being patient with my careful code review. I was able to run my demo app that I pasted in my last review and verify all of the behavior you mentioned. I think this is a clean way to do this 👍 .

Widget build(BuildContext context) {
Widget result = child;
if (kIsWeb) {
// On web platform, these shortcuts makes sure the following:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"On web platform" => "On the web"

"makes sure" => "make sure of"

Comment on lines 437 to 438
// 1. Shortcuts fired when a TextField is focused are ignored and
// forwarded to the browser.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shortcuts fired when an EditableText is focused are ignored and forwarded to the browser by the EditableText's Actions, because it maps DoNothingAndStopPropagationTextIntent to DoNothingAction.

Comment on lines 439 to 440
// 2. Shortcuts fired when no TextField is focused will still trigger
// default text editing Shortcuts
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shortcuts fired when no EditableText is focused will still trigger _shortcuts assuming DoNothingAndStopPropagationTextIntent is unhandled elsewhere.

Comment on lines 442 to 445
// In (2), the disabling shortcuts will still fire; however, the
// [DoNothingAndStopPropagationTextIntent] will not be handled because the
// context is above the TextField. The key event will then trigger the
// default Text Editing Shortcuts.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once the above changes have been made to 1 and 2, remove this paragraph?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the docs here will be good with the changes above, but it has the effect of coupling DefaultTextEditingShortcuts and EditableText. Maybe that's not a big deal because the coupling is kind of inherent. Or we could be generic in the comments above to remove the coupling, but maybe it's not worth it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be fine unless we would have other text editing widget that do no use EditableText.

// [DoNothingAndStopPropagationTextIntent] will not be handled because the
// context is above the TextField. The key event will then trigger the
// default Text Editing Shortcuts.
result = Shortcuts(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood and I verified this behavior on web and Mac with my code example 👍

// context is above the TextField. The key event will then trigger the
// default Text Editing Shortcuts.
result = Shortcuts(
debugLabel: '<Web disabling text shortcuts>',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: "Web Disabling Text Editing Shortcuts"?

Looking at other debugLabels in the codebase, most capitalize all of the first letters like this, but not all...

Copy link
Contributor

@justinmc justinmc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍

@fluttergithubbot fluttergithubbot merged commit 533816d into flutter:master May 12, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request May 12, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/plugins that referenced this pull request May 13, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Aug 30, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/plugins that referenced this pull request Aug 30, 2022
auto-submit bot pushed a commit that referenced this pull request May 25, 2024
## Description

This PRs fixes a Web issue on Linux related to numpad keys.
In #145464, I introduced numpad shortcuts for Linux. These shortcuts work well on a desktop Linux application but they broke the Linux+Web numpad logic.

When I added these shortcuts, I expected them to not be active on Web (because I knew that on Web, those shortcuts are handled by the browser). But there is a trick: text editing shortcuts are still defined on Web but they are disabled at the editable text level so one can use them in components that are not `EditableText` (see #103377).
In this PR, I used the same approach than for other text editing shortcuts: when on web associate those shortcuts to the `DoNothingAndStopPropagationTextIntent` intent.

## Related Issue

Fixes #148447.

## Tests

Updates 2 tests.
Adds 2 tests.
victorsanni pushed a commit to victorsanni/flutter that referenced this pull request May 31, 2024
## Description

This PRs fixes a Web issue on Linux related to numpad keys.
In flutter#145464, I introduced numpad shortcuts for Linux. These shortcuts work well on a desktop Linux application but they broke the Linux+Web numpad logic.

When I added these shortcuts, I expected them to not be active on Web (because I knew that on Web, those shortcuts are handled by the browser). But there is a trick: text editing shortcuts are still defined on Web but they are disabled at the editable text level so one can use them in components that are not `EditableText` (see flutter#103377).
In this PR, I used the same approach than for other text editing shortcuts: when on web associate those shortcuts to the `DoNothingAndStopPropagationTextIntent` intent.

## Related Issue

Fixes flutter#148447.

## Tests

Updates 2 tests.
Adds 2 tests.
m3c-fnicola pushed a commit to m3c-fnicola/flutter that referenced this pull request Jun 24, 2024
## Description

This PRs fixes a Web issue on Linux related to numpad keys.
In flutter#145464, I introduced numpad shortcuts for Linux. These shortcuts work well on a desktop Linux application but they broke the Linux+Web numpad logic.

When I added these shortcuts, I expected them to not be active on Web (because I knew that on Web, those shortcuts are handled by the browser). But there is a trick: text editing shortcuts are still defined on Web but they are disabled at the editable text level so one can use them in components that are not `EditableText` (see flutter#103377).
In this PR, I used the same approach than for other text editing shortcuts: when on web associate those shortcuts to the `DoNothingAndStopPropagationTextIntent` intent.

## Related Issue

Fixes flutter#148447.

## Tests

Updates 2 tests.
Adds 2 tests.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

a: text input Entering text in a text field or keyboard related problems f: cupertino flutter/packages/flutter/cupertino repository f: material design flutter/packages/flutter/material repository. framework flutter/packages/flutter repository. See also f: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants