diff --git a/lib/web_ui/lib/src/engine/text_editing.dart b/lib/web_ui/lib/src/engine/text_editing.dart index 116401de8c609..4049793ef457e 100644 --- a/lib/web_ui/lib/src/engine/text_editing.dart +++ b/lib/web_ui/lib/src/engine/text_editing.dart @@ -42,6 +42,14 @@ void _setStaticStyleAttributes(html.HtmlElement domElement) { } } +enum _InputAction { + /// Procced to next enput element. + next, + + /// Newline has been entered in current input element. + newline, +} + /// The current text and selection state of a text field. class EditingState { EditingState({this.text, this.baseOffset = 0, this.extentOffset = 0}); @@ -162,6 +170,7 @@ class InputConfiguration { } typedef _OnChangeCallback = void Function(EditingState editingState); +typedef _OnActionCallback = void Function(_InputAction inputAction); enum ElementType { /// The backing element is an ``. @@ -225,6 +234,7 @@ class TextEditingElement { html.HtmlElement domElement; EditingState _lastEditingState; _OnChangeCallback _onChange; + _OnActionCallback _onAction; final List> _subscriptions = >[]; @@ -268,12 +278,14 @@ class TextEditingElement { void enable( InputConfiguration inputConfig, { @required _OnChangeCallback onChange, + @required _OnActionCallback onAction, }) { assert(!_enabled); _initDomElement(inputConfig); _enabled = true; _onChange = onChange; + _onAction = onAction; // Chrome on Android will hide the onscreen keyboard when you tap outside // the text box. Instead, we want the framework to tell us to hide the @@ -304,7 +316,8 @@ class TextEditingElement { // Subscribe to text and selection changes. _subscriptions ..add(html.document.onSelectionChange.listen(_handleChange)) - ..add(domElement.onInput.listen(_handleChange)); + ..add(domElement.onInput.listen(_handleChange)) + ..add(domElement.onKeyDown.listen(_handleKeyDown)); } /// Disables the element so it's no longer used for text editing. @@ -440,6 +453,27 @@ class TextEditingElement { _onChange(_lastEditingState); } + // Map KeyboardEvent to InputAction. + void _handleKeyDown(html.KeyboardEvent event) { + // Tab and enter key can only be mapped to actions for single line inputs. + if (_elementType == ElementType.input) { + // Trigger newline action if enter key was pressed. + if (event.which == 13) { + _onAction(_InputAction.newline); + } + // Trigger next action if tab key was pressed. + if (event.which == 9) { + _onAction(_InputAction.next); + // The default action of the browser is to focus the next element. + // As the browser is not able to focus flutter widgets, it will + // focus some part of its UI (e.g. the location bar). + // We prevent this action and leave it to the corresponding widget + // to change the focus. + event.preventDefault(); + } + } + } + @visibleForTesting EditingState calculateEditingState() { assert(domElement != null); @@ -700,6 +734,7 @@ class HybridTextEditing { editingElement.enable( InputConfiguration.fromFlutter(_configuration), onChange: _syncEditingStateToFlutter, + onAction: _sendInputActionToFlutter, ); } @@ -790,6 +825,30 @@ class HybridTextEditing { bool get doesKeyboardShiftInput => browserEngine == BrowserEngine.webkit && operatingSystem == OperatingSystem.iOs; + _InputActionToFlutter(_InputAction inputAction) { + switch (inputAction) { + case _InputAction.next: + return 'TextInputAction.next'; + break; + case _InputAction.newline: + return 'TextInputAction.newline'; + break; + } + return 'TextInputAction.unspecified'; + } + + void _sendInputActionToFlutter(_InputAction inputAction) { + ui.window.onPlatformMessage( + 'flutter/textinput', + const JSONMethodCodec().encodeMethodCall( + MethodCall('TextInputClient.performAction', [ + _clientId, + _InputActionToFlutter(inputAction), + ]), + ), + _emptyCallback, + ); + } /// These style attributes are dynamic throughout the life time of an input /// element.