Skip to content

Conversation

@kubatatami
Copy link
Contributor

This PR adds TapOutsideConfiguration where you can define a custom default logic for onTapOutside.

TapOutsideConfiguration(
  behavior: AlwaysUnfocusTapOutsideBehavior(),
  // behavior: const NeverUnfocusTapOutsideBehavior(),
  // behavior: const CustomTapOutsideBehavior(),
  child: Column(
    children: [
      // This TextField will use onTapOutside from TapOutsideConfiguration
      TextField(),
      // Of course you can still define onTapOutside
      TextField(onTapOutside: (event) => print('Tapped outside')),
    ],
  ),
)

Custom logic can be define like this:

class CustomTapOutsideBehavior extends TapOutsideBehavior {
  const CustomTapOutsideBehavior();

  @override
  void defaultOnTapOutside(PointerDownEvent event, FocusNode focusNode) {
    // any custom logic here
  }
}

This PR implements also two simple AlwaysUnfocusTapOutsideBehavior and NeverUnfocusTapOutsideBehavior.

Resolves #150123

Pre-launch Checklist

@github-actions github-actions 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 Jun 12, 2024
@kubatatami kubatatami force-pushed the tap-outside-configuration branch 4 times, most recently from 7eec31f to d56cd0a Compare June 12, 2024 19:27
@gspencergoog gspencergoog self-requested a review June 12, 2024 22:26
@flutter-dashboard
Copy link

Golden file changes have been found for this pull request. Click here to view and triage (e.g. because this is an intentional change).

If you are still iterating on this change and are not ready to resolve the images on the Flutter Gold dashboard, consider marking this PR as a draft pull request above. You will still be able to view image results on the dashboard, commenting will be silenced, and the check will not try to resolve itself until marked ready for review.

For more guidance, visit Writing a golden file test for package:flutter.

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

Changes reported for pull request #150125 at sha 95268c3

@flutter-dashboard flutter-dashboard bot added the will affect goldens Changes to golden files label Jun 13, 2024
@kubatatami kubatatami force-pushed the tap-outside-configuration branch from 95268c3 to 94471f9 Compare June 13, 2024 08:42
@flutter-dashboard
Copy link

Golden file changes are available for triage from new commit, Click here to view.

For more guidance, visit Writing a golden file test for package:flutter.

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

Changes reported for pull request #150125 at sha 94471f9

/// * [TapOutsideConfiguration], the inherited widget that controls how
/// [EditableText] widgets behave in a subtree.
@immutable
class TapOutsideBehavior {
Copy link
Contributor

Choose a reason for hiding this comment

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

TapRegion isn't only used for text, it's a generic mechanism, so at the least this should be called something else if it is only to apply to EditableText's use of TapRegion.

However, I'd encourage you to think a little broader and see if there's a way we can configure default behavior for any kind of TapRegion, not just the one that EditableText happens to use. For example, maybe there should be a special subclass of TapRegion that looks for behaviors instead of taking an onTapOutside /onTapInside functions? But maybe there's a better solution than that? Then we can change EditableText to use your solution and it will also be more generally useful.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hey @gspencergoog. Thanks for the answer.
I was thinking about more generic solution. I tried to implement it on the TapRegion level and pass groupId as parameter to defaultOnTapOutside so you could implement different behaviour for different groups. The problem with the more generic solution is that you may need more then just a PointerDownEvent event that onTapOutside from TapRegion gives you. For example, to implement default behavior of EditableText.onTapOutside you need also FocusNode and probably this will be the problem for other cases. I haven't found a solution to this. If you know any let me know and I will modify PR. Otherwise I will try to adjust naming. Is EditTextTapOutsideConfiguration ok?

@gspencergoog
Copy link
Contributor

I wonder if this could be handled by turning the tap outside behavior that happens inside of EditableText into an Intent. That would allow overriding of the action, allow passing of the FocusNode and event to the action, and not introduce yet another InheritedWidget that people need to discover.

It would be implemented similar to DeleteCharacterIntent/_DeleteTextAction, which is an overridable action on the text field in editable_text.dart.

Something like this:

class EditableTextTapOutsideIntent extends Intent {
	const EditableTextTapOutsideIntent({required this.focusNode, this.pointerDownEvent});

	final FocusNode focusNode;
	final PointerDownEvent pointerDownEvent;
}

And then, in editable_text.dart, you would create an action and make it overridable:

  late final Map<Type, Action<Intent>> _actions = <Type, Action<Intent>>{ // already exists
	// ...
  	EditableTextTapOutsideIntent: _makeOverridable(_EditableTextTapOutsideAction<EditableTextTapOutsideIntent>()),
	// ...
  };

class _EditableTextTapOutsideAction<T extends EditableTextTapOutsideIntent> extends ContextAction<T> {
	const _EditableTextTapOutsideAction();

  @override
  Object? invoke(T intent, [BuildContext? context]) {
    /// The focus dropping behavior is only present on desktop platforms
    /// and mobile browsers.
    switch (defaultTargetPlatform) {
      case TargetPlatform.android:
      case TargetPlatform.iOS:
      case TargetPlatform.fuchsia:
        // On mobile platforms, we don't unfocus on touch events unless they're
        // in the web browser, but we do unfocus for all other kinds of events.
        switch (intent.pointerDownEvent.kind) {
          case ui.PointerDeviceKind.touch:
            if (kIsWeb) {
              intentl.focusNode.unfocus();
            }
          case ui.PointerDeviceKind.mouse:
          case ui.PointerDeviceKind.stylus:
          case ui.PointerDeviceKind.invertedStylus:
          case ui.PointerDeviceKind.unknown:
            intent.focusNode.unfocus();
          case ui.PointerDeviceKind.trackpad:
            throw UnimplementedError(
                'Unexpected pointer down event for trackpad');
        }
      case TargetPlatform.linux:
      case TargetPlatform.macOS:
      case TargetPlatform.windows:
        intent.focusNode.unfocus();
    }
  }
}

And finally invoke it in EditableTextState:

  void _defaultOnTapOutside(PointerDownEvent event) {
	Actions.maybeInvoke(context, EditableTextTapOutsideIntent(event, widget.focusNode));
  }

To override it, you just define an Actions widget above the EditableText somewhere in the widget tree that maps EditableTextTapOutsideIntent to your custom action.

This way it would use an existing mechanism that is already part of EditableText, allows for overriding, and doesn't create a separate new mechanism that people need to discover and understand.

(fyi, I haven't tried compiling or running the above, it's just hot off the top of my keyboard)

@goderbauer
Copy link
Member

(triage) @kubatatami Do you still have plans to follow up on the feedback given above?

@kubatatami
Copy link
Contributor Author

@goderbauer Yes. I will try to follow up on the feedback early next week.

@kubatatami kubatatami force-pushed the tap-outside-configuration branch from 94471f9 to b5d2db7 Compare September 16, 2024 20:14
@kubatatami
Copy link
Contributor Author

@gspencergoog @goderbauer I changed PR to use Intent and Actions. I used code prepared by @gspencergoog with minor changes. The only major changes I had to do were:

  • change order of TextFieldTapRegion and Actions
  • add a Builder widget to have a context that I can use to invoke Actions.invoke
  • use Actions.invoke instead of Actions.maybeInvoke because we want to be sure that some action is always invoked (default or custom)

/// Creates a [EditableTextTapOutsideIntent].
const EditableTextTapOutsideIntent({required this.focusNode, required this.pointerDownEvent});

/// The [FocusNode] that this [Intent]'s action should perform on.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
/// The [FocusNode] that this [Intent]'s action should perform on.
/// The [FocusNode] that this [Intent]'s action should be performed on.

const TransposeCharactersIntent();
}

/// An [Intent] that represents a tap outside the field.
Copy link
Contributor

Choose a reason for hiding this comment

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

Please add more description here: when is it invoked, why someone might want to invoke it or override it themselves, and a "See Also:" section with a link to Action.overridable to explain how those work. This is the public API doc that will be on the API docs site, so it is what will explain this feature.

@chunhtai
Copy link
Contributor

chunhtai commented Oct 8, 2024

Hi @kubatatami , do you still plan to come back to this pr?

@kubatatami
Copy link
Contributor Author

Hi @kubatatami , do you still plan to come back to this pr?

Yes. I will improve docs this week.

@kubatatami
Copy link
Contributor Author

@gspencergoog @chunhtai More description added. Please let me know if there is anything else to improve.

Copy link
Contributor

@gspencergoog gspencergoog left a comment

Choose a reason for hiding this comment

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

32384589-a60f0e74-c078-11e7-9bc1-e5b5287aea9d

Thanks for all of your hard work on this!

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.

A few nits here, but there's one thing that I think we should do before merging this. In the docs for onTapOutside, can you mention the new EditableTextTapOutsideIntent? I want to make sure both APIs are discoverable and avoid confusing users that might not realize setting onTapOutside will prevent triggering EditableTextTapOutsideIntent.

@kubatatami
Copy link
Contributor Author

A few nits here, but there's one thing that I think we should do before merging this. In the docs for onTapOutside, can you mention the new EditableTextTapOutsideIntent? I want to make sure both APIs are discoverable and avoid confusing users that might not realize setting onTapOutside will prevent triggering EditableTextTapOutsideIntent.

@justinmc Done. Please let me know if there is anything else to improve.

@Piinks Piinks requested a review from justinmc October 15, 2024 22:26
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 👍

Thank you for the quick fixes!

@bleroux bleroux added the autosubmit Merge PR when tree becomes green via auto submit App label Oct 16, 2024
@auto-submit auto-submit bot merged commit 279d30a into flutter:master Oct 16, 2024
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Oct 16, 2024
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Oct 17, 2024
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Feb 12, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Feb 13, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Feb 13, 2025
github-merge-queue bot pushed a commit that referenced this pull request Feb 28, 2025
…ide (#162575)

This PR adds an `Action` for configuring a default action of
`EditableText.onTapUpOutside`. This is the equivalent to what
#150125 did for
`EditableText.onTapOutside`.

Fixes #162212

## Pre-launch Checklist

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

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md

---------

Co-authored-by: Victor Sanni <[email protected]>
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 6, 2025
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 7, 2025
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 autosubmit Merge PR when tree becomes green via auto submit App framework flutter/packages/flutter repository. See also f: labels. will affect goldens Changes to golden files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Proposal to make TextField.onTapOutside default logic configurable

6 participants