From c1ceff3351eaf3c3eca3e0054eb019ec6ac5aad3 Mon Sep 17 00:00:00 2001 From: TimWhiting Date: Mon, 15 Aug 2022 14:02:20 -0600 Subject: [PATCH 1/3] add enterkeyhint property to web textfields --- lib/web_ui/lib/src/engine.dart | 1 + .../src/engine/text_editing/input_action.dart | 155 ++++++++++++++++++ .../src/engine/text_editing/text_editing.dart | 4 + lib/web_ui/test/text_editing_test.dart | 21 +++ 4 files changed, 181 insertions(+) create mode 100644 lib/web_ui/lib/src/engine/text_editing/input_action.dart diff --git a/lib/web_ui/lib/src/engine.dart b/lib/web_ui/lib/src/engine.dart index 166f78ffac659..7a4827c72efc8 100644 --- a/lib/web_ui/lib/src/engine.dart +++ b/lib/web_ui/lib/src/engine.dart @@ -156,6 +156,7 @@ export 'engine/text/word_break_properties.dart'; export 'engine/text/word_breaker.dart'; export 'engine/text_editing/autofill_hint.dart'; export 'engine/text_editing/composition_aware_mixin.dart'; +export 'engine/text_editing/input_action.dart'; export 'engine/text_editing/input_type.dart'; export 'engine/text_editing/text_capitalization.dart'; export 'engine/text_editing/text_editing.dart'; diff --git a/lib/web_ui/lib/src/engine/text_editing/input_action.dart b/lib/web_ui/lib/src/engine/text_editing/input_action.dart new file mode 100644 index 0000000000000..44e96633a6854 --- /dev/null +++ b/lib/web_ui/lib/src/engine/text_editing/input_action.dart @@ -0,0 +1,155 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import '../browser_detection.dart'; +import '../dom.dart'; + +/// Various input action types used in text fields. +/// +/// These types are coming from Flutter's [TextInputAction]. Currently, the web doesn't +/// support all the types. We fallback to [EngineInputAction.none] when Flutter +/// sends a type that isn't supported. +abstract class EngineInputAction { + const EngineInputAction(); + + static EngineInputAction fromName(String name) { + switch (name) { + case 'TextInputAction.continueAction': + case 'TextInputAction.next': + return next; + case 'TextInputAction.previous': + return previous; + case 'TextInputAction.done': + return done; + case 'TextInputAction.go': + return go; + case 'TextInputAction.newline': + return enter; + case 'TextInputAction.search': + return search; + case 'TextInputAction.send': + return send; + case 'TextInputAction.emergencyCall': + case 'TextInputAction.join': + case 'TextInputAction.none': + case 'TextInputAction.route': + case 'TextInputAction.unspecified': + default: + return none; + } + } + + /// No input action + static const NoInputAction none = NoInputAction(); + + /// Action to go to next + static const NextInputAction next = NextInputAction(); + + /// Action to go to previous + static const PreviousInputAction previous = PreviousInputAction(); + + /// Action to be finished + static const DoneInputAction done = DoneInputAction(); + + /// Action to Go + static const GoInputAction go = GoInputAction(); + + /// Action to insert newline + static const EnterInputAction enter = EnterInputAction(); + + /// Action to search + static const SearchInputAction search = SearchInputAction(); + + /// Action to send + static const SendInputAction send = SendInputAction(); + + + /// The HTML `enterkeyhint` attribute to be set on the DOM element. + /// + /// This HTML attribute helps the browser decide what kind of keyboard action + /// to use for this text field + /// + /// For various `enterkeyhint` values supported by browsers, see: + /// . + String? get enterkeyhintAttribute; + + /// Given a [domElement], set attributes that are specific to this input action. + void configureInputAction(DomHTMLElement domElement) { + if (enterkeyhintAttribute == null) { + return; + } + + // Only apply `enterkeyhint` in mobile browsers so that the right virtual + // keyboard shows up. + if (operatingSystem == OperatingSystem.iOs || + operatingSystem == OperatingSystem.android || + enterkeyhintAttribute == EngineInputAction.none.enterkeyhintAttribute) { + domElement.setAttribute('enterkeyhint', enterkeyhintAttribute!); + } + } +} + +/// No action specified +class NoInputAction extends EngineInputAction { + const NoInputAction(); + + @override + String? get enterkeyhintAttribute => null; +} + +/// Typically inserting a new line. +class EnterInputAction extends EngineInputAction { + const EnterInputAction(); + + @override + String? get enterkeyhintAttribute => 'enter'; +} + +/// Typically meaning there is nothing more to input and the input method editor (IME) will be closed. +class DoneInputAction extends EngineInputAction { + const DoneInputAction(); + + @override + String? get enterkeyhintAttribute => 'done'; +} + +/// Typically meaning to take the user to the target of the text they typed. +class GoInputAction extends EngineInputAction { + const GoInputAction(); + + @override + String? get enterkeyhintAttribute => 'go'; +} + +/// Typically taking the user to the next field that will accept text. +class NextInputAction extends EngineInputAction { + const NextInputAction(); + + @override + String? get enterkeyhintAttribute => 'next'; +} + +/// Typically taking the user to the previous field that will accept text. +class PreviousInputAction extends EngineInputAction { + const PreviousInputAction(); + + @override + String? get enterkeyhintAttribute => 'previous'; +} + +/// Typically taking the user to the results of searching for the text they have typed. +class SearchInputAction extends EngineInputAction { + const SearchInputAction(); + + @override + String? get enterkeyhintAttribute => 'search'; +} + +/// Typically delivering the text to its target. +class SendInputAction extends EngineInputAction { + const SendInputAction(); + + @override + String? get enterkeyhintAttribute => 'send'; +} diff --git a/lib/web_ui/lib/src/engine/text_editing/text_editing.dart b/lib/web_ui/lib/src/engine/text_editing/text_editing.dart index f9ec958f18b79..3ef1601475451 100644 --- a/lib/web_ui/lib/src/engine/text_editing/text_editing.dart +++ b/lib/web_ui/lib/src/engine/text_editing/text_editing.dart @@ -21,6 +21,7 @@ import '../text/paragraph.dart'; import '../util.dart'; import 'autofill_hint.dart'; import 'composition_aware_mixin.dart'; +import 'input_action.dart'; import 'input_type.dart'; import 'text_capitalization.dart'; @@ -1180,6 +1181,9 @@ abstract class DefaultTextEditingStrategy with CompositionAwareMixin implements if (config.inputType == EngineInputType.none) { activeDomElement.setAttribute('inputmode', 'none'); } + + final EngineInputAction action = EngineInputAction.fromName(config.inputAction); + action.configureInputAction(activeDomElement); final AutofillInfo? autofill = config.autofill; if (autofill != null) { diff --git a/lib/web_ui/test/text_editing_test.dart b/lib/web_ui/test/text_editing_test.dart index 480adc8725bfc..5b08d532ef3bc 100644 --- a/lib/web_ui/test/text_editing_test.dart +++ b/lib/web_ui/test/text_editing_test.dart @@ -159,6 +159,27 @@ Future testMain() async { editingStrategy!.disable(); }); + test('Knows how to create non-default text actions', () { + final InputConfiguration config = InputConfiguration( + inputAction: 'TextInputAction.send' + ); + editingStrategy!.enable( + config, + onChange: trackEditingState, + onAction: trackInputAction, + ); + expect(defaultTextEditingRoot.querySelectorAll('input'), hasLength(1)); + final DomElement input = defaultTextEditingRoot.querySelector('input')!; + expect(editingStrategy!.domElement, input); + if (operatingSystem == OperatingSystem.iOs || operatingSystem == OperatingSystem.android){ + expect(input.getAttribute('enterkeyhint'), 'send'); + } else { + expect(input.getAttribute('enterkeyhint'), null); + } + + editingStrategy!.disable(); + }); + test('Knows to turn autocorrect off', () { final InputConfiguration config = InputConfiguration( autocorrect: false, From c97737dcd87382a9f44419364273d561d58f6224 Mon Sep 17 00:00:00 2001 From: Tim Whiting Date: Tue, 23 Aug 2022 10:16:52 -0600 Subject: [PATCH 2/3] fix copyright --- lib/web_ui/lib/src/engine/text_editing/input_action.dart | 2 +- lib/web_ui/lib/src/engine/text_editing/text_editing.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/web_ui/lib/src/engine/text_editing/input_action.dart b/lib/web_ui/lib/src/engine/text_editing/input_action.dart index 44e96633a6854..9e7ee4156fdde 100644 --- a/lib/web_ui/lib/src/engine/text_editing/input_action.dart +++ b/lib/web_ui/lib/src/engine/text_editing/input_action.dart @@ -1,4 +1,4 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. +// Copyright 2022 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. diff --git a/lib/web_ui/lib/src/engine/text_editing/text_editing.dart b/lib/web_ui/lib/src/engine/text_editing/text_editing.dart index 3ef1601475451..bccb866a408ae 100644 --- a/lib/web_ui/lib/src/engine/text_editing/text_editing.dart +++ b/lib/web_ui/lib/src/engine/text_editing/text_editing.dart @@ -1181,7 +1181,7 @@ abstract class DefaultTextEditingStrategy with CompositionAwareMixin implements if (config.inputType == EngineInputType.none) { activeDomElement.setAttribute('inputmode', 'none'); } - + final EngineInputAction action = EngineInputAction.fromName(config.inputAction); action.configureInputAction(activeDomElement); From c3e245a2199bb4183791587ae1f583dadb1a6d57 Mon Sep 17 00:00:00 2001 From: Tim Whiting Date: Thu, 25 Aug 2022 12:49:16 -0600 Subject: [PATCH 3/3] address review comments --- ci/licenses_golden/licenses_flutter | 1 + lib/web_ui/lib/src/engine/text_editing/input_action.dart | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index c78b93d5c0076..6586dfbb7127c 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -1274,6 +1274,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/text/word_break_properties.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/text/word_breaker.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/text_editing/autofill_hint.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/text_editing/composition_aware_mixin.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/text_editing/input_action.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/text_editing/input_type.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/text_editing/text_capitalization.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/text_editing/text_editing.dart diff --git a/lib/web_ui/lib/src/engine/text_editing/input_action.dart b/lib/web_ui/lib/src/engine/text_editing/input_action.dart index 9e7ee4156fdde..2726aa6467e85 100644 --- a/lib/web_ui/lib/src/engine/text_editing/input_action.dart +++ b/lib/web_ui/lib/src/engine/text_editing/input_action.dart @@ -1,4 +1,4 @@ -// Copyright 2022 The Flutter Authors. All rights reserved. +// Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -27,9 +27,9 @@ abstract class EngineInputAction { case 'TextInputAction.newline': return enter; case 'TextInputAction.search': - return search; + return search; case 'TextInputAction.send': - return send; + return send; case 'TextInputAction.emergencyCall': case 'TextInputAction.join': case 'TextInputAction.none':