mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Fix: On the Web, cannot support multiline inputting when registering customized TextInputControl (flutter/engine#45522)
For [flutter/flutter/125875](https://github.com/flutter/flutter/issues/125875) When registering customized TextInputControl, the _PlatformTextInputControl sends inputType = TextInputType.none to the engine. After receiving TextInputType.none, the engine on the Web will create a `<input>` element instead of `<textarea>`. So there is no way to input \n(multiline). This is my solution. I tested in Android Chrome, iOS Safari, and macOS Chrome. It works for me. But I'm not sure about other use cases. I pleasure, If someone gives me suggestions.
This commit is contained in:
parent
bb1dab9840
commit
af574f2aae
@ -14,7 +14,7 @@ import '../dom.dart';
|
||||
abstract class EngineInputType {
|
||||
const EngineInputType();
|
||||
|
||||
static EngineInputType fromName(String name, {bool isDecimal = false}) {
|
||||
static EngineInputType fromName(String name, {bool isDecimal = false, bool isMultiline = false}) {
|
||||
switch (name) {
|
||||
case 'TextInputType.number':
|
||||
return isDecimal ? decimal : number;
|
||||
@ -27,7 +27,7 @@ abstract class EngineInputType {
|
||||
case 'TextInputType.multiline':
|
||||
return multiline;
|
||||
case 'TextInputType.none':
|
||||
return none;
|
||||
return isMultiline ? multilineNone : none;
|
||||
case 'TextInputType.text':
|
||||
default:
|
||||
return text;
|
||||
@ -37,6 +37,9 @@ abstract class EngineInputType {
|
||||
/// No text input.
|
||||
static const NoTextInputType none = NoTextInputType();
|
||||
|
||||
/// Multi-line no text input.
|
||||
static const MultilineNoTextInputType multilineNone = MultilineNoTextInputType();
|
||||
|
||||
/// Single-line text input type.
|
||||
static const TextInputType text = TextInputType();
|
||||
|
||||
@ -94,6 +97,31 @@ class NoTextInputType extends EngineInputType {
|
||||
String get inputmodeAttribute => 'none';
|
||||
}
|
||||
|
||||
/// See: https://github.com/flutter/flutter/issues/125875
|
||||
/// Multi-line no text input from system virtual keyboard.
|
||||
///
|
||||
/// Use this for inputting multiple lines with a customized keyboard.
|
||||
///
|
||||
/// When Flutter uses a custom virtual keyboard, it sends [TextInputType.none]
|
||||
/// with a [isMultiline] flag to block the system virtual keyboard.
|
||||
///
|
||||
/// For [MultilineNoTextInputType] (mapped to [TextInputType.none] with
|
||||
/// [isMultiline] = true), it creates a <textarea> element with the
|
||||
/// inputmode="none" attribute.
|
||||
///
|
||||
/// For [NoTextInputType] (mapped to [TextInputType.none] with
|
||||
/// [isMultiline] = false), it creates an <input> element with the
|
||||
/// inputmode="none" attribute.
|
||||
class MultilineNoTextInputType extends MultilineInputType {
|
||||
const MultilineNoTextInputType();
|
||||
|
||||
@override
|
||||
String? get inputmodeAttribute => 'none';
|
||||
|
||||
@override
|
||||
DomHTMLElement createDomElement() => createDomHTMLTextAreaElement();
|
||||
}
|
||||
|
||||
/// Single-line text input type.
|
||||
class TextInputType extends EngineInputType {
|
||||
const TextInputType();
|
||||
|
||||
@ -961,6 +961,7 @@ class InputConfiguration {
|
||||
: inputType = EngineInputType.fromName(
|
||||
flutterInputConfiguration.readJson('inputType').readString('name'),
|
||||
isDecimal: flutterInputConfiguration.readJson('inputType').tryBool('decimal') ?? false,
|
||||
isMultiline: flutterInputConfiguration.readJson('inputType').tryBool('isMultiline') ?? false,
|
||||
),
|
||||
inputAction =
|
||||
flutterInputConfiguration.tryString('inputAction') ?? 'TextInputAction.done',
|
||||
@ -1280,7 +1281,7 @@ abstract class DefaultTextEditingStrategy with CompositionAwareMixin implements
|
||||
activeDomElement.setAttribute('type', 'password');
|
||||
}
|
||||
|
||||
if (config.inputType == EngineInputType.none) {
|
||||
if (config.inputType.inputmodeAttribute == 'none') {
|
||||
activeDomElement.setAttribute('inputmode', 'none');
|
||||
}
|
||||
|
||||
|
||||
@ -441,6 +441,31 @@ Future<void> testMain() async {
|
||||
expect(event.defaultPrevented, isFalse);
|
||||
});
|
||||
|
||||
test('Triggers input action in multiline-none mode', () {
|
||||
final InputConfiguration config = InputConfiguration(
|
||||
inputType: EngineInputType.multilineNone,
|
||||
);
|
||||
editingStrategy!.enable(
|
||||
config,
|
||||
onChange: trackEditingState,
|
||||
onAction: trackInputAction,
|
||||
);
|
||||
|
||||
// No input action so far.
|
||||
expect(lastInputAction, isNull);
|
||||
|
||||
final DomKeyboardEvent event = dispatchKeyboardEvent(
|
||||
editingStrategy!.domElement!,
|
||||
'keydown',
|
||||
keyCode: _kReturnKeyCode,
|
||||
);
|
||||
|
||||
// Input action is triggered!
|
||||
expect(lastInputAction, 'TextInputAction.done');
|
||||
// And default behavior of keyboard event shouldn't have been prevented.
|
||||
expect(event.defaultPrevented, isFalse);
|
||||
});
|
||||
|
||||
test('Triggers input action and prevent new line key event for single line field', () {
|
||||
// Regression test for https://github.com/flutter/flutter/issues/113559
|
||||
final InputConfiguration config = InputConfiguration();
|
||||
@ -509,13 +534,14 @@ Future<void> testMain() async {
|
||||
required String inputType,
|
||||
String? inputAction,
|
||||
bool decimal = false,
|
||||
bool isMultiline = false,
|
||||
}) {
|
||||
final MethodCall setClient = MethodCall(
|
||||
'TextInput.setClient',
|
||||
<dynamic>[
|
||||
++clientId,
|
||||
createFlutterConfig(inputType,
|
||||
inputAction: inputAction, decimal: decimal),
|
||||
inputAction: inputAction, decimal: decimal, isMultiline: isMultiline),
|
||||
],
|
||||
);
|
||||
sendFrameworkMessage(codec.encodeMethodCall(setClient));
|
||||
@ -2133,6 +2159,52 @@ Future<void> testMain() async {
|
||||
expect(spy.messages, isEmpty);
|
||||
});
|
||||
|
||||
test('none mode works', () async {
|
||||
final MethodCall setClient = MethodCall(
|
||||
'TextInput.setClient', <dynamic>[123, createFlutterConfig('none')]);
|
||||
sendFrameworkMessage(codec.encodeMethodCall(setClient));
|
||||
|
||||
const MethodCall show = MethodCall('TextInput.show');
|
||||
sendFrameworkMessage(codec.encodeMethodCall(show));
|
||||
|
||||
// The "setSizeAndTransform" message has to be here before we call
|
||||
// checkInputEditingState, since on some platforms (e.g. Desktop Safari)
|
||||
// we don't put the input element into the DOM until we get its correct
|
||||
// dimensions from the framework.
|
||||
final MethodCall setSizeAndTransform =
|
||||
configureSetSizeAndTransformMethodCall(150, 50,
|
||||
Matrix4.translationValues(10.0, 20.0, 30.0).storage.toList());
|
||||
sendFrameworkMessage(codec.encodeMethodCall(setSizeAndTransform));
|
||||
|
||||
await waitForDesktopSafariFocus();
|
||||
|
||||
expect(textEditing!.strategy.domElement!.tagName, 'INPUT');
|
||||
expect(getEditingInputMode(), 'none');
|
||||
});
|
||||
|
||||
test('none multiline mode works', () async {
|
||||
final MethodCall setClient = MethodCall(
|
||||
'TextInput.setClient', <dynamic>[123, createFlutterConfig('none', isMultiline: true)]);
|
||||
sendFrameworkMessage(codec.encodeMethodCall(setClient));
|
||||
|
||||
const MethodCall show = MethodCall('TextInput.show');
|
||||
sendFrameworkMessage(codec.encodeMethodCall(show));
|
||||
|
||||
// The "setSizeAndTransform" message has to be here before we call
|
||||
// checkInputEditingState, since on some platforms (e.g. Desktop Safari)
|
||||
// we don't put the input element into the DOM until we get its correct
|
||||
// dimensions from the framework.
|
||||
final MethodCall setSizeAndTransform =
|
||||
configureSetSizeAndTransformMethodCall(150, 50,
|
||||
Matrix4.translationValues(10.0, 20.0, 30.0).storage.toList());
|
||||
sendFrameworkMessage(codec.encodeMethodCall(setSizeAndTransform));
|
||||
|
||||
await waitForDesktopSafariFocus();
|
||||
|
||||
expect(textEditing!.strategy.domElement!.tagName, 'TEXTAREA');
|
||||
expect(getEditingInputMode(), 'none');
|
||||
});
|
||||
|
||||
test('sets correct input type in Android', () {
|
||||
debugOperatingSystemOverride = OperatingSystem.android;
|
||||
debugBrowserEngineOverride = BrowserEngine.blink;
|
||||
@ -2164,6 +2236,11 @@ Future<void> testMain() async {
|
||||
|
||||
showKeyboard(inputType: 'none');
|
||||
expect(getEditingInputMode(), 'none');
|
||||
expect(textEditing!.strategy.domElement!.tagName, 'INPUT');
|
||||
|
||||
showKeyboard(inputType: 'none', isMultiline: true);
|
||||
expect(getEditingInputMode(), 'none');
|
||||
expect(textEditing!.strategy.domElement!.tagName, 'TEXTAREA');
|
||||
|
||||
hideKeyboard();
|
||||
});
|
||||
@ -2253,6 +2330,11 @@ Future<void> testMain() async {
|
||||
|
||||
showKeyboard(inputType: 'none');
|
||||
expect(getEditingInputMode(), 'none');
|
||||
expect(textEditing!.strategy.domElement!.tagName, 'INPUT');
|
||||
|
||||
showKeyboard(inputType: 'none', isMultiline: true);
|
||||
expect(getEditingInputMode(), 'none');
|
||||
expect(textEditing!.strategy.domElement!.tagName, 'TEXTAREA');
|
||||
|
||||
hideKeyboard();
|
||||
}
|
||||
@ -3214,11 +3296,13 @@ Map<String, dynamic> createFlutterConfig(
|
||||
List<String>? autofillHintsForFields,
|
||||
bool decimal = false,
|
||||
bool enableDeltaModel = false,
|
||||
bool isMultiline = false,
|
||||
}) {
|
||||
return <String, dynamic>{
|
||||
'inputType': <String, dynamic>{
|
||||
'name': 'TextInputType.$inputType',
|
||||
if (decimal) 'decimal': true,
|
||||
if (isMultiline) 'isMultiline': true,
|
||||
},
|
||||
'readOnly': readOnly,
|
||||
'obscureText': obscureText,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user