Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit c643366

Browse files
authored
Support keyboard types on mobile browsers (#13044)
1 parent 139051f commit c643366

File tree

6 files changed

+252
-85
lines changed

6 files changed

+252
-85
lines changed

ci/licenses_golden/licenses_flutter

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -426,7 +426,8 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/text/ruler.dart
426426
FILE: ../../../flutter/lib/web_ui/lib/src/engine/text/unicode_range.dart
427427
FILE: ../../../flutter/lib/web_ui/lib/src/engine/text/word_break_properties.dart
428428
FILE: ../../../flutter/lib/web_ui/lib/src/engine/text/word_breaker.dart
429-
FILE: ../../../flutter/lib/web_ui/lib/src/engine/text_editing.dart
429+
FILE: ../../../flutter/lib/web_ui/lib/src/engine/text_editing/input_type.dart
430+
FILE: ../../../flutter/lib/web_ui/lib/src/engine/text_editing/text_editing.dart
430431
FILE: ../../../flutter/lib/web_ui/lib/src/engine/util.dart
431432
FILE: ../../../flutter/lib/web_ui/lib/src/engine/validators.dart
432433
FILE: ../../../flutter/lib/web_ui/lib/src/engine/vector_math.dart

lib/web_ui/lib/src/engine.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,8 @@ part 'engine/text/ruler.dart';
9393
part 'engine/text/unicode_range.dart';
9494
part 'engine/text/word_break_properties.dart';
9595
part 'engine/text/word_breaker.dart';
96-
part 'engine/text_editing.dart';
96+
part 'engine/text_editing/input_type.dart';
97+
part 'engine/text_editing/text_editing.dart';
9798
part 'engine/util.dart';
9899
part 'engine/validators.dart';
99100
part 'engine/vector_math.dart';

lib/web_ui/lib/src/engine/browser_detection.dart

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,19 +77,35 @@ OperatingSystem _operatingSystem;
7777
///
7878
/// This is used to implement operating system specific behavior such as
7979
/// soft keyboards.
80-
OperatingSystem get operatingSystem =>
81-
_operatingSystem ??= _detectOperatingSystem();
80+
OperatingSystem get operatingSystem {
81+
if (debugOperatingSystemOverride != null) {
82+
return debugOperatingSystemOverride;
83+
}
84+
return _operatingSystem ??= _detectOperatingSystem();
85+
}
86+
87+
/// Override the value of [operatingSystem].
88+
///
89+
/// Setting this to `null` lets [operatingSystem] detect the real OS that the
90+
/// app is running on.
91+
///
92+
/// This is intended to be used for testing and debugging only.
93+
OperatingSystem debugOperatingSystemOverride;
8294

8395
OperatingSystem _detectOperatingSystem() {
8496
final String platform = html.window.navigator.platform;
97+
final String userAgent = html.window.navigator.userAgent;
8598

8699
if (platform.startsWith('Mac')) {
87100
return OperatingSystem.macOs;
88101
} else if (platform.toLowerCase().contains('iphone') ||
89102
platform.toLowerCase().contains('ipad') ||
90103
platform.toLowerCase().contains('ipod')) {
91104
return OperatingSystem.iOs;
92-
} else if (platform.toLowerCase().contains('android')) {
105+
} else if (userAgent.contains('Android')) {
106+
// The Android OS reports itself as "Linux armv8l" in
107+
// [html.window.navigator.platform]. So we have to check the user-agent to
108+
// determine if the OS is Android or not.
93109
return OperatingSystem.android;
94110
} else if (platform.startsWith('Linux')) {
95111
return OperatingSystem.linux;
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
part of engine;
6+
7+
/// Various types of inputs used in text fields.
8+
///
9+
/// These types are coming from Flutter's [TextInputType]. Currently, we don't
10+
/// support all the types. We fallback to [EngineInputType.text] when Flutter
11+
/// sends a type that isn't supported.
12+
// TODO(flutter_web): Support more types.
13+
abstract class EngineInputType {
14+
const EngineInputType();
15+
16+
static EngineInputType fromName(String name) {
17+
switch (name) {
18+
case 'TextInputType.number':
19+
return number;
20+
case 'TextInputType.phone':
21+
return phone;
22+
case 'TextInputType.emailAddress':
23+
return emailAddress;
24+
case 'TextInputType.url':
25+
return url;
26+
case 'TextInputType.multiline':
27+
return multiline;
28+
29+
case 'TextInputType.text':
30+
default:
31+
return text;
32+
}
33+
}
34+
35+
/// Single-line text input type.
36+
static const TextInputType text = TextInputType();
37+
38+
/// Numeric input type.
39+
static const NumberInputType number = NumberInputType();
40+
41+
/// Phone number input type.
42+
static const PhoneInputType phone = PhoneInputType();
43+
44+
/// Email address input type.
45+
static const EmailInputType emailAddress = EmailInputType();
46+
47+
/// URL input type.
48+
static const UrlInputType url = UrlInputType();
49+
50+
/// Multi-line text input type.
51+
static const MultilineInputType multiline = MultilineInputType();
52+
53+
/// The HTML `inputmode` attribute to be set on the DOM element.
54+
///
55+
/// This HTML attribute helps the browser decide what kind of keyboard works
56+
/// best for this text field.
57+
///
58+
/// For various `inputmode` values supported by browsers, see:
59+
/// <https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/inputmode>.
60+
String get inputmodeAttribute;
61+
62+
/// Create the appropriate DOM element for this input type.
63+
html.HtmlElement createDomElement() => html.InputElement();
64+
65+
/// Given a [domElement], set attributes that are specific to this input type.
66+
void configureDomElement(html.HtmlElement domElement) {
67+
if (inputmodeAttribute == null) {
68+
return;
69+
}
70+
71+
// Only apply `inputmode` in mobile browsers so that the right virtual
72+
// keyboard shows up.
73+
if (operatingSystem == OperatingSystem.iOs ||
74+
operatingSystem == OperatingSystem.android) {
75+
domElement.setAttribute('inputmode', inputmodeAttribute);
76+
}
77+
}
78+
}
79+
80+
/// Single-line text input type.
81+
class TextInputType extends EngineInputType {
82+
const TextInputType();
83+
84+
@override
85+
final String inputmodeAttribute = 'text';
86+
}
87+
88+
/// Numeric input type.
89+
class NumberInputType extends EngineInputType {
90+
const NumberInputType();
91+
92+
@override
93+
final String inputmodeAttribute = 'numeric';
94+
}
95+
96+
/// Phone number input type.
97+
class PhoneInputType extends EngineInputType {
98+
const PhoneInputType();
99+
100+
@override
101+
final String inputmodeAttribute = 'tel';
102+
}
103+
104+
/// Email address input type.
105+
class EmailInputType extends EngineInputType {
106+
const EmailInputType();
107+
108+
@override
109+
final String inputmodeAttribute = 'email';
110+
}
111+
112+
/// URL input type.
113+
class UrlInputType extends EngineInputType {
114+
const UrlInputType();
115+
116+
@override
117+
final String inputmodeAttribute = 'url';
118+
}
119+
120+
/// Multi-line text input type.
121+
class MultilineInputType extends EngineInputType {
122+
const MultilineInputType();
123+
124+
@override
125+
final String inputmodeAttribute = null;
126+
127+
@override
128+
html.HtmlElement createDomElement() => html.TextAreaElement();
129+
}

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

Lines changed: 7 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -163,31 +163,6 @@ class EditingState {
163163
}
164164
}
165165

166-
/// Various types of inputs used in text fields.
167-
///
168-
/// These types are coming from Flutter's [TextInputType]. Currently, we don't
169-
/// support all the types. We fallback to [InputType.text] when Flutter sends
170-
/// a type that isn't supported.
171-
// TODO(flutter_web): Support more types.
172-
enum InputType {
173-
/// Single-line plain text.
174-
text,
175-
176-
/// Multi-line text.
177-
multiline,
178-
}
179-
180-
InputType _getInputTypeFromString(String inputType) {
181-
switch (inputType) {
182-
case 'TextInputType.multiline':
183-
return InputType.multiline;
184-
185-
case 'TextInputType.text':
186-
default:
187-
return InputType.text;
188-
}
189-
}
190-
191166
/// Controls the appearance of the input control being edited.
192167
///
193168
/// For example, [inputType] determines whether we should use `<input>` or
@@ -201,12 +176,12 @@ class InputConfiguration {
201176
});
202177

203178
InputConfiguration.fromFlutter(Map<String, dynamic> flutterInputConfiguration)
204-
: inputType = _getInputTypeFromString(
179+
: inputType = EngineInputType.fromName(
205180
flutterInputConfiguration['inputType']['name']),
206181
obscureText = flutterInputConfiguration['obscureText'];
207182

208183
/// The type of information being edited in the input control.
209-
final InputType inputType;
184+
final EngineInputType inputType;
210185

211186
/// Whether to hide the text being edited.
212187
final bool obscureText;
@@ -341,6 +316,7 @@ class TextEditingElement {
341316
_subscriptions.add(domElement.onKeyUp.listen((event) {
342317
_handleChange(event);
343318
}));
319+
344320
/// In Firefox the context menu item "Select All" does not work without
345321
/// listening to onSelect. On the other browsers onSelectionChange is
346322
/// enough for covering "Select All" functionality.
@@ -370,19 +346,10 @@ class TextEditingElement {
370346
}
371347

372348
void _initDomElement(InputConfiguration inputConfig) {
373-
switch (inputConfig.inputType) {
374-
case InputType.text:
375-
domElement = owner.createInputElement();
376-
break;
377-
378-
case InputType.multiline:
379-
domElement = owner.createTextAreaElement();
380-
break;
381-
382-
default:
383-
throw UnsupportedError(
384-
'Unsupported input type: ${inputConfig.inputType}');
385-
}
349+
domElement = inputConfig.inputType.createDomElement();
350+
inputConfig.inputType.configureDomElement(domElement);
351+
_setStaticStyleAttributes(domElement);
352+
owner._setDynamicStyleAttributes(domElement);
386353
domRenderer.glassPaneElement.append(domElement);
387354
}
388355

@@ -757,20 +724,6 @@ class HybridTextEditing {
757724
void setStyleOutsideOfScreen(html.HtmlElement domElement) {
758725
domElement.style.transform = 'translate(-9999px, -9999px)';
759726
}
760-
761-
html.InputElement createInputElement() {
762-
final html.InputElement input = html.InputElement();
763-
_setStaticStyleAttributes(input);
764-
_setDynamicStyleAttributes(input);
765-
return input;
766-
}
767-
768-
html.TextAreaElement createTextAreaElement() {
769-
final html.TextAreaElement textarea = html.TextAreaElement();
770-
_setStaticStyleAttributes(textarea);
771-
_setDynamicStyleAttributes(textarea);
772-
return textarea;
773-
}
774727
}
775728

776729
/// Information on the font and alignment of a text editing element.

0 commit comments

Comments
 (0)