mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
[web] Support input action (#13268)
This commit is contained in:
parent
6a3baef78b
commit
4307a9b487
@ -7,6 +7,9 @@ part of engine;
|
||||
/// Make the content editable span visible to facilitate debugging.
|
||||
const bool _debugVisibleTextEditing = false;
|
||||
|
||||
/// The `keyCode` of the "Enter" key.
|
||||
const int _kReturnKeyCode = 13;
|
||||
|
||||
void _emptyCallback(dynamic _) {}
|
||||
|
||||
/// These style attributes are constant throughout the life time of an input
|
||||
@ -171,23 +174,29 @@ class EditingState {
|
||||
/// This corresponds to Flutter's [TextInputConfiguration].
|
||||
class InputConfiguration {
|
||||
InputConfiguration({
|
||||
this.inputType,
|
||||
this.obscureText = false,
|
||||
@required this.inputType,
|
||||
@required this.inputAction,
|
||||
@required this.obscureText,
|
||||
});
|
||||
|
||||
InputConfiguration.fromFlutter(Map<String, dynamic> flutterInputConfiguration)
|
||||
: inputType = EngineInputType.fromName(
|
||||
flutterInputConfiguration['inputType']['name']),
|
||||
inputAction = flutterInputConfiguration['inputAction'],
|
||||
obscureText = flutterInputConfiguration['obscureText'];
|
||||
|
||||
/// The type of information being edited in the input control.
|
||||
final EngineInputType inputType;
|
||||
|
||||
/// The default action for the input field.
|
||||
final String inputAction;
|
||||
|
||||
/// Whether to hide the text being edited.
|
||||
final bool obscureText;
|
||||
}
|
||||
|
||||
typedef _OnChangeCallback = void Function(EditingState editingState);
|
||||
typedef _OnActionCallback = void Function(String inputAction);
|
||||
|
||||
/// Wraps the DOM element used to provide text editing capabilities.
|
||||
///
|
||||
@ -219,11 +228,16 @@ class TextEditingElement {
|
||||
const Duration(milliseconds: 100);
|
||||
|
||||
final HybridTextEditing owner;
|
||||
bool _enabled = false;
|
||||
|
||||
@visibleForTesting
|
||||
bool isEnabled = false;
|
||||
|
||||
html.HtmlElement domElement;
|
||||
InputConfiguration _inputConfiguration;
|
||||
EditingState _lastEditingState;
|
||||
|
||||
_OnChangeCallback _onChange;
|
||||
_OnActionCallback _onAction;
|
||||
|
||||
final List<StreamSubscription<html.Event>> _subscriptions =
|
||||
<StreamSubscription<html.Event>>[];
|
||||
@ -261,12 +275,15 @@ class TextEditingElement {
|
||||
void enable(
|
||||
InputConfiguration inputConfig, {
|
||||
@required _OnChangeCallback onChange,
|
||||
@required _OnActionCallback onAction,
|
||||
}) {
|
||||
assert(!_enabled);
|
||||
assert(!isEnabled);
|
||||
|
||||
_initDomElement(inputConfig);
|
||||
_enabled = true;
|
||||
isEnabled = true;
|
||||
_inputConfiguration = inputConfig;
|
||||
_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
|
||||
@ -279,7 +296,7 @@ class TextEditingElement {
|
||||
if (browserEngine == BrowserEngine.blink ||
|
||||
browserEngine == BrowserEngine.unknown) {
|
||||
_subscriptions.add(domElement.onBlur.listen((_) {
|
||||
if (_enabled) {
|
||||
if (isEnabled) {
|
||||
_refocus();
|
||||
}
|
||||
}));
|
||||
@ -297,6 +314,8 @@ class TextEditingElement {
|
||||
// Subscribe to text and selection changes.
|
||||
_subscriptions.add(domElement.onInput.listen(_handleChange));
|
||||
|
||||
_subscriptions.add(domElement.onKeyDown.listen(_maybeSendAction));
|
||||
|
||||
/// Detects changes in text selection.
|
||||
///
|
||||
/// Currently only used in Firefox.
|
||||
@ -330,9 +349,9 @@ class TextEditingElement {
|
||||
///
|
||||
/// Calling [disable] also removes any registered event listeners.
|
||||
void disable() {
|
||||
assert(_enabled);
|
||||
assert(isEnabled);
|
||||
|
||||
_enabled = false;
|
||||
isEnabled = false;
|
||||
_lastEditingState = null;
|
||||
|
||||
for (int i = 0; i < _subscriptions.length; i++) {
|
||||
@ -390,7 +409,7 @@ class TextEditingElement {
|
||||
|
||||
void setEditingState(EditingState editingState) {
|
||||
_lastEditingState = editingState;
|
||||
if (!_enabled || !editingState.isValid) {
|
||||
if (!isEnabled || !editingState.isValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -416,6 +435,13 @@ class TextEditingElement {
|
||||
_onChange(_lastEditingState);
|
||||
}
|
||||
}
|
||||
|
||||
void _maybeSendAction(html.KeyboardEvent event) {
|
||||
if (event.keyCode == _kReturnKeyCode) {
|
||||
event.preventDefault();
|
||||
_onAction(_inputConfiguration.inputAction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The implementation of a persistent mode for [TextEditingElement].
|
||||
@ -512,7 +538,7 @@ class HybridTextEditing {
|
||||
///
|
||||
/// Use [stopUsingCustomEditableElement] to switch back to default element.
|
||||
void useCustomEditableElement(TextEditingElement customEditingElement) {
|
||||
if (_isEditing && customEditingElement != _customEditingElement) {
|
||||
if (isEditing && customEditingElement != _customEditingElement) {
|
||||
stopEditing();
|
||||
}
|
||||
_customEditingElement = customEditingElement;
|
||||
@ -529,20 +555,21 @@ class HybridTextEditing {
|
||||
/// Flag which shows if there is an ongoing editing.
|
||||
///
|
||||
/// Also used to define if a keyboard is needed.
|
||||
bool _isEditing = false;
|
||||
@visibleForTesting
|
||||
bool isEditing = false;
|
||||
|
||||
/// Indicates whether the input element needs to be positioned.
|
||||
///
|
||||
/// See [TextEditingElement._delayBeforePositioning].
|
||||
bool get inputElementNeedsToBePositioned =>
|
||||
!inputPositioned && _isEditing && doesKeyboardShiftInput;
|
||||
!inputPositioned && isEditing && doesKeyboardShiftInput;
|
||||
|
||||
/// Flag indicating whether the input element's position is set.
|
||||
///
|
||||
/// See [inputElementNeedsToBePositioned].
|
||||
bool inputPositioned = false;
|
||||
|
||||
Map<String, dynamic> _configuration;
|
||||
InputConfiguration _configuration;
|
||||
|
||||
/// All "flutter/textinput" platform messages should be sent to this method.
|
||||
void handleTextInput(ByteData data) {
|
||||
@ -551,11 +578,11 @@ class HybridTextEditing {
|
||||
case 'TextInput.setClient':
|
||||
final bool clientIdChanged =
|
||||
_clientId != null && _clientId != call.arguments[0];
|
||||
if (clientIdChanged && _isEditing) {
|
||||
if (clientIdChanged && isEditing) {
|
||||
stopEditing();
|
||||
}
|
||||
_clientId = call.arguments[0];
|
||||
_configuration = call.arguments[1];
|
||||
_configuration = InputConfiguration.fromFlutter(call.arguments[1]);
|
||||
break;
|
||||
|
||||
case 'TextInput.setEditingState':
|
||||
@ -564,7 +591,7 @@ class HybridTextEditing {
|
||||
break;
|
||||
|
||||
case 'TextInput.show':
|
||||
if (!_isEditing) {
|
||||
if (!isEditing) {
|
||||
_startEditing();
|
||||
}
|
||||
break;
|
||||
@ -579,7 +606,7 @@ class HybridTextEditing {
|
||||
|
||||
case 'TextInput.clearClient':
|
||||
case 'TextInput.hide':
|
||||
if (_isEditing) {
|
||||
if (isEditing) {
|
||||
stopEditing();
|
||||
}
|
||||
break;
|
||||
@ -587,17 +614,18 @@ class HybridTextEditing {
|
||||
}
|
||||
|
||||
void _startEditing() {
|
||||
assert(!_isEditing);
|
||||
_isEditing = true;
|
||||
assert(!isEditing);
|
||||
isEditing = true;
|
||||
editingElement.enable(
|
||||
InputConfiguration.fromFlutter(_configuration),
|
||||
_configuration,
|
||||
onChange: _syncEditingStateToFlutter,
|
||||
onAction: _sendInputActionToFlutter,
|
||||
);
|
||||
}
|
||||
|
||||
void stopEditing() {
|
||||
assert(_isEditing);
|
||||
_isEditing = false;
|
||||
assert(isEditing);
|
||||
isEditing = false;
|
||||
editingElement.disable();
|
||||
}
|
||||
|
||||
@ -666,6 +694,19 @@ class HybridTextEditing {
|
||||
);
|
||||
}
|
||||
|
||||
void _sendInputActionToFlutter(String inputAction) {
|
||||
ui.window.onPlatformMessage(
|
||||
'flutter/textinput',
|
||||
const JSONMethodCodec().encodeMethodCall(
|
||||
MethodCall(
|
||||
'TextInputClient.performAction',
|
||||
<dynamic>[_clientId, inputAction],
|
||||
),
|
||||
),
|
||||
_emptyCallback,
|
||||
);
|
||||
}
|
||||
|
||||
/// Positioning of input element is only done if we are not expecting input
|
||||
/// to be shifted by a virtual keyboard or if the input is already positioned.
|
||||
///
|
||||
|
||||
@ -3,10 +3,11 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:html';
|
||||
import 'dart:js_util' as js_util;
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:ui/ui.dart' as ui;
|
||||
import 'package:ui/src/engine.dart';
|
||||
import 'package:ui/src/engine.dart' hide window;
|
||||
|
||||
import 'package:test/test.dart';
|
||||
|
||||
@ -16,14 +17,21 @@ const MethodCodec codec = JSONMethodCodec();
|
||||
|
||||
TextEditingElement editingElement;
|
||||
EditingState lastEditingState;
|
||||
String lastInputAction;
|
||||
|
||||
final InputConfiguration singlelineConfig =
|
||||
InputConfiguration(inputType: EngineInputType.text);
|
||||
final InputConfiguration singlelineConfig = InputConfiguration(
|
||||
inputType: EngineInputType.text,
|
||||
obscureText: false,
|
||||
inputAction: 'TextInputAction.done',
|
||||
);
|
||||
final Map<String, dynamic> flutterSinglelineConfig =
|
||||
createFlutterConfig('text');
|
||||
|
||||
final InputConfiguration multilineConfig =
|
||||
InputConfiguration(inputType: EngineInputType.multiline);
|
||||
final InputConfiguration multilineConfig = InputConfiguration(
|
||||
inputType: EngineInputType.multiline,
|
||||
obscureText: false,
|
||||
inputAction: 'TextInputAction.newline',
|
||||
);
|
||||
final Map<String, dynamic> flutterMultilineConfig =
|
||||
createFlutterConfig('multiline');
|
||||
|
||||
@ -31,21 +39,25 @@ void trackEditingState(EditingState editingState) {
|
||||
lastEditingState = editingState;
|
||||
}
|
||||
|
||||
void trackInputAction(String inputAction) {
|
||||
lastInputAction = inputAction;
|
||||
}
|
||||
|
||||
void main() {
|
||||
tearDown(() {
|
||||
lastEditingState = null;
|
||||
lastInputAction = null;
|
||||
});
|
||||
|
||||
group('$TextEditingElement', () {
|
||||
setUp(() {
|
||||
editingElement = TextEditingElement(HybridTextEditing());
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
try {
|
||||
if (editingElement.isEnabled) {
|
||||
// Clean up all the DOM elements and event listeners.
|
||||
editingElement.disable();
|
||||
} catch (e) {
|
||||
if (e is AssertionError) {
|
||||
// This is fine. It just means the test itself disabled the editing element.
|
||||
} else {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -57,7 +69,11 @@ void main() {
|
||||
// The focus initially is on the body.
|
||||
expect(document.activeElement, document.body);
|
||||
|
||||
editingElement.enable(singlelineConfig, onChange: trackEditingState);
|
||||
editingElement.enable(
|
||||
singlelineConfig,
|
||||
onChange: trackEditingState,
|
||||
onAction: trackInputAction,
|
||||
);
|
||||
expect(
|
||||
document.getElementsByTagName('input'),
|
||||
hasLength(1),
|
||||
@ -81,7 +97,11 @@ void main() {
|
||||
});
|
||||
|
||||
test('Can read editing state correctly', () {
|
||||
editingElement.enable(singlelineConfig, onChange: trackEditingState);
|
||||
editingElement.enable(
|
||||
singlelineConfig,
|
||||
onChange: trackEditingState,
|
||||
onAction: trackInputAction,
|
||||
);
|
||||
|
||||
final InputElement input = editingElement.domElement;
|
||||
input.value = 'foo bar';
|
||||
@ -97,29 +117,50 @@ void main() {
|
||||
lastEditingState,
|
||||
EditingState(text: 'foo bar', baseOffset: 4, extentOffset: 6),
|
||||
);
|
||||
|
||||
// There should be no input action.
|
||||
expect(lastInputAction, isNull);
|
||||
});
|
||||
|
||||
test('Can set editing state correctly', () {
|
||||
editingElement.enable(singlelineConfig, onChange: trackEditingState);
|
||||
editingElement.enable(
|
||||
singlelineConfig,
|
||||
onChange: trackEditingState,
|
||||
onAction: trackInputAction,
|
||||
);
|
||||
editingElement.setEditingState(
|
||||
EditingState(text: 'foo bar baz', baseOffset: 2, extentOffset: 7));
|
||||
|
||||
checkInputEditingState(editingElement.domElement, 'foo bar baz', 2, 7);
|
||||
|
||||
// There should be no input action.
|
||||
expect(lastInputAction, isNull);
|
||||
});
|
||||
|
||||
test('Re-acquires focus', () async {
|
||||
editingElement.enable(singlelineConfig, onChange: trackEditingState);
|
||||
editingElement.enable(
|
||||
singlelineConfig,
|
||||
onChange: trackEditingState,
|
||||
onAction: trackInputAction,
|
||||
);
|
||||
expect(document.activeElement, editingElement.domElement);
|
||||
|
||||
editingElement.domElement.blur();
|
||||
// The focus remains on [editingElement.domElement].
|
||||
expect(document.activeElement, editingElement.domElement);
|
||||
|
||||
// There should be no input action.
|
||||
expect(lastInputAction, isNull);
|
||||
});
|
||||
|
||||
test('Multi-line mode also works', () {
|
||||
// The textarea element is created lazily.
|
||||
expect(document.getElementsByTagName('textarea'), hasLength(0));
|
||||
editingElement.enable(multilineConfig, onChange: trackEditingState);
|
||||
editingElement.enable(
|
||||
multilineConfig,
|
||||
onChange: trackEditingState,
|
||||
onAction: trackInputAction,
|
||||
);
|
||||
expect(document.getElementsByTagName('textarea'), hasLength(1));
|
||||
|
||||
final TextAreaElement textarea =
|
||||
@ -152,6 +193,9 @@ void main() {
|
||||
expect(document.getElementsByTagName('textarea'), hasLength(0));
|
||||
// The focus is back to the body.
|
||||
expect(document.activeElement, document.body);
|
||||
|
||||
// There should be no input action.
|
||||
expect(lastInputAction, isNull);
|
||||
});
|
||||
|
||||
test('Same instance can be re-enabled with different config', () {
|
||||
@ -160,7 +204,11 @@ void main() {
|
||||
expect(document.getElementsByTagName('textarea'), hasLength(0));
|
||||
|
||||
// Use single-line config and expect an `<input>` to be created.
|
||||
editingElement.enable(singlelineConfig, onChange: trackEditingState);
|
||||
editingElement.enable(
|
||||
singlelineConfig,
|
||||
onChange: trackEditingState,
|
||||
onAction: trackInputAction,
|
||||
);
|
||||
expect(document.getElementsByTagName('input'), hasLength(1));
|
||||
expect(document.getElementsByTagName('textarea'), hasLength(0));
|
||||
|
||||
@ -170,7 +218,11 @@ void main() {
|
||||
expect(document.getElementsByTagName('textarea'), hasLength(0));
|
||||
|
||||
// Use multi-line config and expect an `<textarea>` to be created.
|
||||
editingElement.enable(multilineConfig, onChange: trackEditingState);
|
||||
editingElement.enable(
|
||||
multilineConfig,
|
||||
onChange: trackEditingState,
|
||||
onAction: trackInputAction,
|
||||
);
|
||||
expect(document.getElementsByTagName('input'), hasLength(0));
|
||||
expect(document.getElementsByTagName('textarea'), hasLength(1));
|
||||
|
||||
@ -178,6 +230,28 @@ void main() {
|
||||
editingElement.disable();
|
||||
expect(document.getElementsByTagName('input'), hasLength(0));
|
||||
expect(document.getElementsByTagName('textarea'), hasLength(0));
|
||||
|
||||
// There should be no input action.
|
||||
expect(lastInputAction, isNull);
|
||||
});
|
||||
|
||||
test('Triggers input action', () {
|
||||
final InputConfiguration config = InputConfiguration(
|
||||
inputType: EngineInputType.text,
|
||||
obscureText: false,
|
||||
inputAction: 'TextInputAction.done',
|
||||
);
|
||||
editingElement.enable(
|
||||
config,
|
||||
onChange: trackEditingState,
|
||||
onAction: trackInputAction,
|
||||
);
|
||||
|
||||
// No input action so far.
|
||||
expect(lastInputAction, isNull);
|
||||
|
||||
dispatchKeyboardEvent(editingElement.domElement, 'keydown', keyCode: 13);
|
||||
expect(lastInputAction, 'TextInputAction.done');
|
||||
});
|
||||
|
||||
group('[persistent mode]', () {
|
||||
@ -199,8 +273,11 @@ void main() {
|
||||
expect(document.activeElement, document.body);
|
||||
|
||||
document.body.append(input);
|
||||
persistentEditingElement.enable(singlelineConfig,
|
||||
onChange: trackEditingState);
|
||||
persistentEditingElement.enable(
|
||||
singlelineConfig,
|
||||
onChange: trackEditingState,
|
||||
onAction: trackInputAction,
|
||||
);
|
||||
expect(document.activeElement, input);
|
||||
|
||||
// The input should lose focus now.
|
||||
@ -222,14 +299,20 @@ void main() {
|
||||
|
||||
// Can't enable before the input element is inserted into the DOM.
|
||||
expect(
|
||||
() => persistentEditingElement.enable(singlelineConfig,
|
||||
onChange: trackEditingState),
|
||||
() => persistentEditingElement.enable(
|
||||
singlelineConfig,
|
||||
onChange: trackEditingState,
|
||||
onAction: trackInputAction,
|
||||
),
|
||||
throwsAssertionError,
|
||||
);
|
||||
|
||||
document.body.append(input);
|
||||
persistentEditingElement.enable(singlelineConfig,
|
||||
onChange: trackEditingState);
|
||||
persistentEditingElement.enable(
|
||||
singlelineConfig,
|
||||
onChange: trackEditingState,
|
||||
onAction: trackInputAction,
|
||||
);
|
||||
expect(document.activeElement, persistentEditingElement.domElement);
|
||||
// It doesn't create a new DOM element.
|
||||
expect(persistentEditingElement.domElement, input);
|
||||
@ -249,8 +332,11 @@ void main() {
|
||||
PersistentTextEditingElement(HybridTextEditing(), input);
|
||||
|
||||
document.body.append(input);
|
||||
persistentEditingElement.enable(singlelineConfig,
|
||||
onChange: trackEditingState);
|
||||
persistentEditingElement.enable(
|
||||
singlelineConfig,
|
||||
onChange: trackEditingState,
|
||||
onAction: trackInputAction,
|
||||
);
|
||||
expect(document.activeElement, input);
|
||||
|
||||
persistentEditingElement.domElement.blur();
|
||||
@ -273,14 +359,20 @@ void main() {
|
||||
|
||||
// Can't enable before the textarea is inserted into the DOM.
|
||||
expect(
|
||||
() => persistentEditingElement.enable(singlelineConfig,
|
||||
onChange: trackEditingState),
|
||||
() => persistentEditingElement.enable(
|
||||
singlelineConfig,
|
||||
onChange: trackEditingState,
|
||||
onAction: trackInputAction,
|
||||
),
|
||||
throwsAssertionError,
|
||||
);
|
||||
|
||||
document.body.append(textarea);
|
||||
persistentEditingElement.enable(multilineConfig,
|
||||
onChange: trackEditingState);
|
||||
persistentEditingElement.enable(
|
||||
multilineConfig,
|
||||
onChange: trackEditingState,
|
||||
onAction: trackInputAction,
|
||||
);
|
||||
// Focuses the textarea.
|
||||
expect(document.activeElement, textarea);
|
||||
|
||||
@ -308,15 +400,25 @@ void main() {
|
||||
final PlatformMessagesSpy spy = PlatformMessagesSpy();
|
||||
|
||||
int clientId = 0;
|
||||
void showKeyboard({String inputType}) {
|
||||
|
||||
/// Sends the necessary platform messages to activate a text field and show
|
||||
/// the keyboard.
|
||||
///
|
||||
/// Returns the `clientId` used in the platform message.
|
||||
int showKeyboard({String inputType, String inputAction}) {
|
||||
final MethodCall setClient = MethodCall(
|
||||
'TextInput.setClient',
|
||||
<dynamic>[++clientId, createFlutterConfig(inputType)],
|
||||
<dynamic>[
|
||||
++clientId,
|
||||
createFlutterConfig(inputType, inputAction: inputAction),
|
||||
],
|
||||
);
|
||||
textEditing.handleTextInput(codec.encodeMethodCall(setClient));
|
||||
|
||||
const MethodCall show = MethodCall('TextInput.show');
|
||||
textEditing.handleTextInput(codec.encodeMethodCall(show));
|
||||
|
||||
return clientId;
|
||||
}
|
||||
|
||||
void hideKeyboard() {
|
||||
@ -337,8 +439,10 @@ void main() {
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
// TODO(mdebbar): clean-up stuff that HybridTextEditing registered on the page
|
||||
spy.deactivate();
|
||||
if (textEditing.isEditing) {
|
||||
textEditing.stopEditing();
|
||||
}
|
||||
});
|
||||
|
||||
test('setClient, show, setEditingState, hide', () {
|
||||
@ -766,6 +870,30 @@ void main() {
|
||||
|
||||
debugOperatingSystemOverride = null;
|
||||
});
|
||||
|
||||
test('sends the correct input action as a platform message', () {
|
||||
final int clientId = showKeyboard(
|
||||
inputType: 'text',
|
||||
inputAction: 'TextInputAction.next',
|
||||
);
|
||||
|
||||
// There should be no input action yet.
|
||||
expect(lastInputAction, isNull);
|
||||
|
||||
dispatchKeyboardEvent(
|
||||
textEditing.editingElement.domElement,
|
||||
'keydown',
|
||||
keyCode: 13,
|
||||
);
|
||||
|
||||
expect(spy.messages, hasLength(1));
|
||||
final MethodCall call = spy.messages.first;
|
||||
expect(call.method, 'TextInputClient.performAction');
|
||||
expect(
|
||||
call.arguments,
|
||||
<dynamic>[clientId, 'TextInputAction.next'],
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('EditingState', () {
|
||||
@ -842,6 +970,26 @@ void main() {
|
||||
});
|
||||
}
|
||||
|
||||
KeyboardEvent dispatchKeyboardEvent(
|
||||
EventTarget target,
|
||||
String type, {
|
||||
int keyCode,
|
||||
}) {
|
||||
final Function jsKeyboardEvent = js_util.getProperty(window, 'KeyboardEvent');
|
||||
final List<dynamic> eventArgs = <dynamic>[
|
||||
type,
|
||||
<String, dynamic>{
|
||||
'keyCode': keyCode,
|
||||
'cancelable': true,
|
||||
}
|
||||
];
|
||||
final KeyboardEvent event =
|
||||
js_util.callConstructor(jsKeyboardEvent, js_util.jsify(eventArgs));
|
||||
target.dispatchEvent(event);
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
MethodCall configureSetStyleMethodCall(int fontSize, String fontFamily,
|
||||
int textAlignIndex, int fontWeightIndex, int textDirectionIndex) {
|
||||
return MethodCall('TextInput.setStyle', <String, dynamic>{
|
||||
@ -909,11 +1057,13 @@ class PlatformMessagesSpy {
|
||||
Map<String, dynamic> createFlutterConfig(
|
||||
String inputType, {
|
||||
bool obscureText = false,
|
||||
String inputAction,
|
||||
}) {
|
||||
return <String, dynamic>{
|
||||
'inputType': <String, String>{
|
||||
'name': 'TextInputType.$inputType',
|
||||
},
|
||||
'obscureText': obscureText,
|
||||
'inputAction': inputAction ?? 'TextInputAction.done',
|
||||
};
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user