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

using text capitalization value in web #19564

Merged
merged 9 commits into from
Jul 10, 2020
Merged
Show file tree
Hide file tree
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
1 change: 1 addition & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/text/word_break_properties.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/text/word_breaker.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/text_editing/autofill_hint.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/text_editing/input_type.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/text_editing/text_capitalization.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/text_editing/text_editing.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/util.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/validators.dart
Expand Down
1 change: 1 addition & 0 deletions lib/web_ui/lib/src/engine.dart
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ part 'engine/text/word_break_properties.dart';
part 'engine/text/word_breaker.dart';
part 'engine/text_editing/autofill_hint.dart';
part 'engine/text_editing/input_type.dart';
part 'engine/text_editing/text_capitalization.dart';
part 'engine/text_editing/text_editing.dart';
part 'engine/util.dart';
part 'engine/validators.dart';
Expand Down
91 changes: 91 additions & 0 deletions lib/web_ui/lib/src/engine/text_editing/text_capitalization.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

part of engine;

/// Controls the capitalization of the text.
///
/// This corresponds to Flutter's [TextCapitalization].
///
/// Uses `text-transform` css property.
/// See: https://developer.mozilla.org/en-US/docs/Web/CSS/text-transform
enum TextCapitalization {
/// Uppercase for the first letter of each word.
words,

/// Currently not implemented on Flutter Web. Uppercase for the first letter
/// of each sentence.
sentences,

/// Uppercase for each letter.
characters,

/// Lowercase for each letter.
none,
}

/// Helper class for text capitalization.
///
/// Uses `autocapitalize` attribute on input element.
/// See: https://developers.google.com/web/updates/2015/04/autocapitalize
/// https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autocapitalize
class TextCapitalizationConfig {
final TextCapitalization textCapitalization;

const TextCapitalizationConfig.defaultCapitalization()
: textCapitalization = TextCapitalization.none;

TextCapitalizationConfig.fromInputConfiguration(String inputConfiguration)
: this.textCapitalization =
inputConfiguration == 'TextCapitalization.words'
? TextCapitalization.words
: inputConfiguration == 'TextCapitalization.characters'
? TextCapitalization.characters
: inputConfiguration == 'TextCapitalization.sentences'
? TextCapitalization.sentences
: TextCapitalization.none;

/// Sets `autocapitalize` attribute on input elements.
///
/// This attribute is only available for mobile browsers.
///
/// Note that in mobile browsers the onscreen keyboards provide sentence
/// level capitalization as default as apposed to no capitalization on desktop
/// browser.
///
/// See: https://developers.google.com/web/updates/2015/04/autocapitalize
/// https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autocapitalize
void setAutocapitalizeAttribute(html.HtmlElement domElement) {
String autocapitalize = '';
switch (textCapitalization) {
case TextCapitalization.words:
// TODO: There is a bug for `words` level capitalization in IOS now.
// For now go back to default. Remove the check after bug is resolved.
// https://bugs.webkit.org/show_bug.cgi?id=148504
if (browserEngine == BrowserEngine.webkit) {
autocapitalize = 'sentences';
} else {
autocapitalize = 'words';
}
break;
case TextCapitalization.characters:
autocapitalize = 'characters';
break;
case TextCapitalization.sentences:
autocapitalize = 'sentences';
break;
case TextCapitalization.none:
default:
autocapitalize = 'off';
break;
}
if (domElement is html.InputElement) {
html.InputElement element = domElement;
element.setAttribute('autocapitalize', autocapitalize);
} else if (domElement is html.TextAreaElement) {
html.TextAreaElement element = domElement;
element.setAttribute('autocapitalize', autocapitalize);
}
}
}
101 changes: 71 additions & 30 deletions lib/web_ui/lib/src/engine/text_editing/text_editing.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.


part of engine;

/// Make the content editable span visible to facilitate debugging.
Expand Down Expand Up @@ -115,8 +114,10 @@ class EngineAutofillForm {
if (fields != null) {
for (Map<String, dynamic> field in fields.cast<Map<String, dynamic>>()) {
final Map<String, dynamic> autofillInfo = field['autofill'];
final AutofillInfo autofill =
AutofillInfo.fromFrameworkMessage(autofillInfo);
final AutofillInfo autofill = AutofillInfo.fromFrameworkMessage(
autofillInfo,
textCapitalization: TextCapitalizationConfig.fromInputConfiguration(
field['textCapitalization']));

// The focused text editing element will not be created here.
final AutofillInfo focusedElement =
Expand Down Expand Up @@ -170,16 +171,24 @@ class EngineAutofillForm {
keys.forEach((String key) {
final html.Element element = elements![key]!;
subscriptions.add(element.onInput.listen((html.Event e) {
_handleChange(element, key);
if (items![key] == null) {
throw StateError(
'Autofill would not work withuot Autofill value set');
} else {
final AutofillInfo autofillInfo = items![key] as AutofillInfo;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a better way of handling non-nullability here? @yjbanov

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if you assign it to a local variable:

final AutofillInfo? info = items![key];
if (info == null) {
  ...
} else {
  // Dart will automatically infer that `info` is not null here.
  _handleChange(element, info);
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed this part.

Thanks for the suggestion, it was also giving error Autofill? cannot be passed to Autofill when I tried to use a local variable.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are two things that can be null, items and items![key].

I imagine if we're null-asserting items, then we should make the field itself non-null.

Otherwise, @mdebbar's suggestion should work.

_handleChange(element, autofillInfo);
}
}));
});
return subscriptions;
}

void _handleChange(html.Element domElement, String? tag) {
EditingState newEditingState = EditingState.fromDomElement(domElement as html.HtmlElement?);
void _handleChange(html.Element domElement, AutofillInfo autofillInfo) {
EditingState newEditingState = EditingState.fromDomElement(
domElement as html.HtmlElement?,
textCapitalization: autofillInfo.textCapitalization);

_sendAutofillEditingState(tag, newEditingState);
_sendAutofillEditingState(autofillInfo.uniqueIdentifier, newEditingState);
}

/// Sends the 'TextInputClient.updateEditingStateWithTag' message to the framework.
Expand Down Expand Up @@ -207,7 +216,11 @@ class EngineAutofillForm {
/// These values are to be used when a text field have autofill enabled.
@visibleForTesting
class AutofillInfo {
AutofillInfo({required this.editingState, required this.uniqueIdentifier, required this.hint});
AutofillInfo(
{required this.editingState,
required this.uniqueIdentifier,
required this.hint,
required this.textCapitalization});

/// The current text and selection state of a text field.
final EditingState editingState;
Expand All @@ -217,14 +230,29 @@ class AutofillInfo {
/// Used as id of the text field.
final String uniqueIdentifier;

/// Information on how should autofilled text capitalized.
///
/// For example for [TextCapitalization.characters] each letter is converted
/// to upper case.
///
/// This value is not necessary for autofilling the focused element since
/// [DefaultTextEditingStrategy._inputConfiguration] already has this
/// information.
///
/// On the other hand for the multi element forms, for the input elements
/// other the focused field, we need to use this information.
final TextCapitalizationConfig textCapitalization;

/// Attribute used for autofill.
///
/// Used as a guidance to the browser as to the type of information expected
/// in the field.
/// See: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete
final String hint;

factory AutofillInfo.fromFrameworkMessage(Map<String, dynamic> autofill) {
factory AutofillInfo.fromFrameworkMessage(Map<String, dynamic> autofill,
{TextCapitalizationConfig textCapitalization =
const TextCapitalizationConfig.defaultCapitalization()}) {
assert(autofill != null); // ignore: unnecessary_null_comparison
final String uniqueIdentifier = autofill['uniqueIdentifier']!;
final List<dynamic> hintsList = autofill['hints'];
Expand All @@ -233,7 +261,8 @@ class AutofillInfo {
return AutofillInfo(
uniqueIdentifier: uniqueIdentifier,
hint: BrowserAutofillHints.instance.flutterToEngine(hintsList[0]),
editingState: editingState);
editingState: editingState,
textCapitalization: textCapitalization);
}

void applyToDomElement(html.HtmlElement domElement,
Expand Down Expand Up @@ -302,7 +331,9 @@ class EditingState {
///
/// [domElement] can be a [InputElement] or a [TextAreaElement] depending on
/// the [InputType] of the text field.
factory EditingState.fromDomElement(html.HtmlElement? domElement) {
factory EditingState.fromDomElement(html.HtmlElement? domElement,
{TextCapitalizationConfig textCapitalization =
const TextCapitalizationConfig.defaultCapitalization()}) {
if (domElement is html.InputElement) {
html.InputElement element = domElement;
return EditingState(
Expand Down Expand Up @@ -352,10 +383,10 @@ class EditingState {
if (runtimeType != other.runtimeType) {
return false;
}
return other is EditingState
&& other.text == text
&& other.baseOffset == baseOffset
&& other.extentOffset == extentOffset;
return other is EditingState &&
other.text == text &&
other.baseOffset == baseOffset &&
other.extentOffset == extentOffset;
}

@override
Expand Down Expand Up @@ -396,6 +427,7 @@ class InputConfiguration {
required this.inputAction,
required this.obscureText,
required this.autocorrect,
required this.textCapitalization,
this.autofill,
this.autofillGroup,
});
Expand All @@ -407,9 +439,12 @@ class InputConfiguration {
inputAction = flutterInputConfiguration['inputAction'],
obscureText = flutterInputConfiguration['obscureText'],
autocorrect = flutterInputConfiguration['autocorrect'],
textCapitalization = TextCapitalizationConfig.fromInputConfiguration(
flutterInputConfiguration['textCapitalization']),
autofill = flutterInputConfiguration.containsKey('autofill')
? AutofillInfo.fromFrameworkMessage(flutterInputConfiguration['autofill'])
: null,
? AutofillInfo.fromFrameworkMessage(
flutterInputConfiguration['autofill'])
: null,
autofillGroup = EngineAutofillForm.fromFrameworkMessage(
flutterInputConfiguration['autofill'],
flutterInputConfiguration['fields']);
Expand All @@ -435,6 +470,8 @@ class InputConfiguration {
final AutofillInfo? autofill;

final EngineAutofillForm? autofillGroup;

final TextCapitalizationConfig textCapitalization;
}

typedef _OnChangeCallback = void Function(EditingState? editingState);
Expand Down Expand Up @@ -500,18 +537,18 @@ class GloballyPositionedTextEditingStrategy extends DefaultTextEditingStrategy {
void placeElement() {
super.placeElement();
if (hasAutofillGroup) {
_geometry?.applyToDomElement(focusedFormElement!);
placeForm();
// On Chrome, when a form is focused, it opens an autofill menu
// immeddiately.
// Flutter framework sends `setEditableSizeAndTransform` for informing
// the engine about the location of the text field. This call will
// arrive after `show` call.
// Therefore on Chrome we place the element when
// `setEditableSizeAndTransform` method is called and focus on the form
// only after placing it to the correct position. Hence autofill menu
// does not appear on top-left of the page.
focusedFormElement!.focus();
_geometry?.applyToDomElement(focusedFormElement!);
placeForm();
// On Chrome, when a form is focused, it opens an autofill menu
// immeddiately.
// Flutter framework sends `setEditableSizeAndTransform` for informing
// the engine about the location of the text field. This call will
// arrive after `show` call.
// Therefore on Chrome we place the element when
// `setEditableSizeAndTransform` method is called and focus on the form
// only after placing it to the correct position. Hence autofill menu
// does not appear on top-left of the page.
focusedFormElement!.focus();
} else {
_geometry?.applyToDomElement(domElement);
}
Expand Down Expand Up @@ -551,6 +588,7 @@ abstract class DefaultTextEditingStrategy implements TextEditingStrategy {
set domElement(html.HtmlElement element) {
_domElement = element;
}

html.HtmlElement? _domElement;

late InputConfiguration _inputConfiguration;
Expand Down Expand Up @@ -694,7 +732,8 @@ abstract class DefaultTextEditingStrategy implements TextEditingStrategy {
void _handleChange(html.Event event) {
assert(isEnabled);

EditingState newEditingState = EditingState.fromDomElement(domElement);
EditingState newEditingState = EditingState.fromDomElement(domElement,
textCapitalization: _inputConfiguration.textCapitalization);

if (newEditingState != _lastEditingState) {
_lastEditingState = newEditingState;
Expand Down Expand Up @@ -818,6 +857,7 @@ class IOSTextEditingStrategy extends GloballyPositionedTextEditingStrategy {
} else {
domRenderer.glassPaneElement!.append(domElement);
}
inputConfig.textCapitalization.setAutocapitalizeAttribute(domElement);
}

@override
Expand Down Expand Up @@ -948,6 +988,7 @@ class AndroidTextEditingStrategy extends GloballyPositionedTextEditingStrategy {
} else {
domRenderer.glassPaneElement!.append(domElement);
}
inputConfig.textCapitalization.setAutocapitalizeAttribute(domElement);
}

@override
Expand Down
Loading