Skip to content

Commit 4307a9b

Browse files
authored
[web] Support input action (flutter#13268)
1 parent 6a3baef commit 4307a9b

File tree

2 files changed

+247
-56
lines changed

2 files changed

+247
-56
lines changed

lib/web_ui/lib/src/engine/text_editing/text_editing.dart

Lines changed: 63 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ part of engine;
77
/// Make the content editable span visible to facilitate debugging.
88
const bool _debugVisibleTextEditing = false;
99

10+
/// The `keyCode` of the "Enter" key.
11+
const int _kReturnKeyCode = 13;
12+
1013
void _emptyCallback(dynamic _) {}
1114

1215
/// These style attributes are constant throughout the life time of an input
@@ -171,23 +174,29 @@ class EditingState {
171174
/// This corresponds to Flutter's [TextInputConfiguration].
172175
class InputConfiguration {
173176
InputConfiguration({
174-
this.inputType,
175-
this.obscureText = false,
177+
@required this.inputType,
178+
@required this.inputAction,
179+
@required this.obscureText,
176180
});
177181

178182
InputConfiguration.fromFlutter(Map<String, dynamic> flutterInputConfiguration)
179183
: inputType = EngineInputType.fromName(
180184
flutterInputConfiguration['inputType']['name']),
185+
inputAction = flutterInputConfiguration['inputAction'],
181186
obscureText = flutterInputConfiguration['obscureText'];
182187

183188
/// The type of information being edited in the input control.
184189
final EngineInputType inputType;
185190

191+
/// The default action for the input field.
192+
final String inputAction;
193+
186194
/// Whether to hide the text being edited.
187195
final bool obscureText;
188196
}
189197

190198
typedef _OnChangeCallback = void Function(EditingState editingState);
199+
typedef _OnActionCallback = void Function(String inputAction);
191200

192201
/// Wraps the DOM element used to provide text editing capabilities.
193202
///
@@ -219,11 +228,16 @@ class TextEditingElement {
219228
const Duration(milliseconds: 100);
220229

221230
final HybridTextEditing owner;
222-
bool _enabled = false;
231+
232+
@visibleForTesting
233+
bool isEnabled = false;
223234

224235
html.HtmlElement domElement;
236+
InputConfiguration _inputConfiguration;
225237
EditingState _lastEditingState;
238+
226239
_OnChangeCallback _onChange;
240+
_OnActionCallback _onAction;
227241

228242
final List<StreamSubscription<html.Event>> _subscriptions =
229243
<StreamSubscription<html.Event>>[];
@@ -261,12 +275,15 @@ class TextEditingElement {
261275
void enable(
262276
InputConfiguration inputConfig, {
263277
@required _OnChangeCallback onChange,
278+
@required _OnActionCallback onAction,
264279
}) {
265-
assert(!_enabled);
280+
assert(!isEnabled);
266281

267282
_initDomElement(inputConfig);
268-
_enabled = true;
283+
isEnabled = true;
284+
_inputConfiguration = inputConfig;
269285
_onChange = onChange;
286+
_onAction = onAction;
270287

271288
// Chrome on Android will hide the onscreen keyboard when you tap outside
272289
// the text box. Instead, we want the framework to tell us to hide the
@@ -279,7 +296,7 @@ class TextEditingElement {
279296
if (browserEngine == BrowserEngine.blink ||
280297
browserEngine == BrowserEngine.unknown) {
281298
_subscriptions.add(domElement.onBlur.listen((_) {
282-
if (_enabled) {
299+
if (isEnabled) {
283300
_refocus();
284301
}
285302
}));
@@ -297,6 +314,8 @@ class TextEditingElement {
297314
// Subscribe to text and selection changes.
298315
_subscriptions.add(domElement.onInput.listen(_handleChange));
299316

317+
_subscriptions.add(domElement.onKeyDown.listen(_maybeSendAction));
318+
300319
/// Detects changes in text selection.
301320
///
302321
/// Currently only used in Firefox.
@@ -330,9 +349,9 @@ class TextEditingElement {
330349
///
331350
/// Calling [disable] also removes any registered event listeners.
332351
void disable() {
333-
assert(_enabled);
352+
assert(isEnabled);
334353

335-
_enabled = false;
354+
isEnabled = false;
336355
_lastEditingState = null;
337356

338357
for (int i = 0; i < _subscriptions.length; i++) {
@@ -390,7 +409,7 @@ class TextEditingElement {
390409

391410
void setEditingState(EditingState editingState) {
392411
_lastEditingState = editingState;
393-
if (!_enabled || !editingState.isValid) {
412+
if (!isEnabled || !editingState.isValid) {
394413
return;
395414
}
396415

@@ -416,6 +435,13 @@ class TextEditingElement {
416435
_onChange(_lastEditingState);
417436
}
418437
}
438+
439+
void _maybeSendAction(html.KeyboardEvent event) {
440+
if (event.keyCode == _kReturnKeyCode) {
441+
event.preventDefault();
442+
_onAction(_inputConfiguration.inputAction);
443+
}
444+
}
419445
}
420446

421447
/// The implementation of a persistent mode for [TextEditingElement].
@@ -512,7 +538,7 @@ class HybridTextEditing {
512538
///
513539
/// Use [stopUsingCustomEditableElement] to switch back to default element.
514540
void useCustomEditableElement(TextEditingElement customEditingElement) {
515-
if (_isEditing && customEditingElement != _customEditingElement) {
541+
if (isEditing && customEditingElement != _customEditingElement) {
516542
stopEditing();
517543
}
518544
_customEditingElement = customEditingElement;
@@ -529,20 +555,21 @@ class HybridTextEditing {
529555
/// Flag which shows if there is an ongoing editing.
530556
///
531557
/// Also used to define if a keyboard is needed.
532-
bool _isEditing = false;
558+
@visibleForTesting
559+
bool isEditing = false;
533560

534561
/// Indicates whether the input element needs to be positioned.
535562
///
536563
/// See [TextEditingElement._delayBeforePositioning].
537564
bool get inputElementNeedsToBePositioned =>
538-
!inputPositioned && _isEditing && doesKeyboardShiftInput;
565+
!inputPositioned && isEditing && doesKeyboardShiftInput;
539566

540567
/// Flag indicating whether the input element's position is set.
541568
///
542569
/// See [inputElementNeedsToBePositioned].
543570
bool inputPositioned = false;
544571

545-
Map<String, dynamic> _configuration;
572+
InputConfiguration _configuration;
546573

547574
/// All "flutter/textinput" platform messages should be sent to this method.
548575
void handleTextInput(ByteData data) {
@@ -551,11 +578,11 @@ class HybridTextEditing {
551578
case 'TextInput.setClient':
552579
final bool clientIdChanged =
553580
_clientId != null && _clientId != call.arguments[0];
554-
if (clientIdChanged && _isEditing) {
581+
if (clientIdChanged && isEditing) {
555582
stopEditing();
556583
}
557584
_clientId = call.arguments[0];
558-
_configuration = call.arguments[1];
585+
_configuration = InputConfiguration.fromFlutter(call.arguments[1]);
559586
break;
560587

561588
case 'TextInput.setEditingState':
@@ -564,7 +591,7 @@ class HybridTextEditing {
564591
break;
565592

566593
case 'TextInput.show':
567-
if (!_isEditing) {
594+
if (!isEditing) {
568595
_startEditing();
569596
}
570597
break;
@@ -579,25 +606,26 @@ class HybridTextEditing {
579606

580607
case 'TextInput.clearClient':
581608
case 'TextInput.hide':
582-
if (_isEditing) {
609+
if (isEditing) {
583610
stopEditing();
584611
}
585612
break;
586613
}
587614
}
588615

589616
void _startEditing() {
590-
assert(!_isEditing);
591-
_isEditing = true;
617+
assert(!isEditing);
618+
isEditing = true;
592619
editingElement.enable(
593-
InputConfiguration.fromFlutter(_configuration),
620+
_configuration,
594621
onChange: _syncEditingStateToFlutter,
622+
onAction: _sendInputActionToFlutter,
595623
);
596624
}
597625

598626
void stopEditing() {
599-
assert(_isEditing);
600-
_isEditing = false;
627+
assert(isEditing);
628+
isEditing = false;
601629
editingElement.disable();
602630
}
603631

@@ -666,6 +694,19 @@ class HybridTextEditing {
666694
);
667695
}
668696

697+
void _sendInputActionToFlutter(String inputAction) {
698+
ui.window.onPlatformMessage(
699+
'flutter/textinput',
700+
const JSONMethodCodec().encodeMethodCall(
701+
MethodCall(
702+
'TextInputClient.performAction',
703+
<dynamic>[_clientId, inputAction],
704+
),
705+
),
706+
_emptyCallback,
707+
);
708+
}
709+
669710
/// Positioning of input element is only done if we are not expecting input
670711
/// to be shifted by a virtual keyboard or if the input is already positioned.
671712
///

0 commit comments

Comments
 (0)