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.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..2726aa6467e85 --- /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..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 @@ -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'; @@ -1181,6 +1182,9 @@ abstract class DefaultTextEditingStrategy with CompositionAwareMixin implements activeDomElement.setAttribute('inputmode', 'none'); } + final EngineInputAction action = EngineInputAction.fromName(config.inputAction); + action.configureInputAction(activeDomElement); + final AutofillInfo? autofill = config.autofill; if (autofill != null) { autofill.applyToDomElement(activeDomElement, focusedElement: true); 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,