Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 60 additions & 1 deletion lib/web_ui/lib/src/engine/text_editing.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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});
Expand Down Expand Up @@ -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 `<input>`.
Expand Down Expand Up @@ -225,6 +234,7 @@ class TextEditingElement {
html.HtmlElement domElement;
EditingState _lastEditingState;
_OnChangeCallback _onChange;
_OnActionCallback _onAction;

final List<StreamSubscription<html.Event>> _subscriptions =
<StreamSubscription<html.Event>>[];
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -700,6 +734,7 @@ class HybridTextEditing {
editingElement.enable(
InputConfiguration.fromFlutter(_configuration),
onChange: _syncEditingStateToFlutter,
onAction: _sendInputActionToFlutter,
);
}

Expand Down Expand Up @@ -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', <dynamic>[
_clientId,
_InputActionToFlutter(inputAction),
]),
),
_emptyCallback,
);
}

/// These style attributes are dynamic throughout the life time of an input
/// element.
Expand Down