Adds semantics input type (#165925)
<!-- Thanks for filing a pull request! Reviewers are typically assigned within a week of filing a request. To learn more about code review, see our documentation on Tree Hygiene: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md --> fixes https://github.com/flutter/flutter/issues/162130 ## Pre-launch Checklist - [ ] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [ ] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [ ] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [ ] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [ ] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
This commit is contained in:
parent
790d1b1d9a
commit
7afe7a5f8b
@ -235,6 +235,7 @@ void sendSemanticsUpdate() {
|
||||
headingLevel: 0,
|
||||
linkUrl: '',
|
||||
controlsNodes: null,
|
||||
inputType: SemanticsInputType.none,
|
||||
);
|
||||
_semanticsUpdate(builder.build());
|
||||
}
|
||||
@ -289,6 +290,7 @@ void sendSemanticsUpdateWithRole() {
|
||||
linkUrl: '',
|
||||
role: SemanticsRole.tab,
|
||||
controlsNodes: null,
|
||||
inputType: SemanticsInputType.none,
|
||||
);
|
||||
_semanticsUpdate(builder.build());
|
||||
}
|
||||
|
@ -506,6 +506,29 @@ enum SemanticsRole {
|
||||
alert,
|
||||
}
|
||||
|
||||
/// Describe the type of data for an input field.
|
||||
///
|
||||
/// This is typically used to complement text fields.
|
||||
enum SemanticsInputType {
|
||||
/// The default for non text field.
|
||||
none,
|
||||
|
||||
/// Describes a generic text field.
|
||||
text,
|
||||
|
||||
/// Describes a url text field.
|
||||
url,
|
||||
|
||||
/// Describes a text field for phone input.
|
||||
phone,
|
||||
|
||||
/// Describes a text field that act as a search box.
|
||||
search,
|
||||
|
||||
/// Describes a text field for email input.
|
||||
email,
|
||||
}
|
||||
|
||||
/// A Boolean value that can be associated with a semantics node.
|
||||
//
|
||||
// When changes are made to this class, the equivalent APIs in
|
||||
@ -1232,6 +1255,7 @@ abstract class SemanticsUpdateBuilder {
|
||||
SemanticsRole role = SemanticsRole.none,
|
||||
required List<String>? controlsNodes,
|
||||
SemanticsValidationResult validationResult = SemanticsValidationResult.none,
|
||||
required SemanticsInputType inputType,
|
||||
});
|
||||
|
||||
/// Update the custom semantics action associated with the given `id`.
|
||||
@ -1310,6 +1334,7 @@ base class _NativeSemanticsUpdateBuilder extends NativeFieldWrapperClass1
|
||||
SemanticsRole role = SemanticsRole.none,
|
||||
required List<String>? controlsNodes,
|
||||
SemanticsValidationResult validationResult = SemanticsValidationResult.none,
|
||||
required SemanticsInputType inputType,
|
||||
}) {
|
||||
assert(_matrix4IsValid(transform));
|
||||
assert(
|
||||
@ -1358,6 +1383,7 @@ base class _NativeSemanticsUpdateBuilder extends NativeFieldWrapperClass1
|
||||
role.index,
|
||||
controlsNodes,
|
||||
validationResult.index,
|
||||
inputType.index,
|
||||
);
|
||||
}
|
||||
|
||||
@ -1405,6 +1431,7 @@ base class _NativeSemanticsUpdateBuilder extends NativeFieldWrapperClass1
|
||||
Int32,
|
||||
Handle,
|
||||
Int32,
|
||||
Int32,
|
||||
)
|
||||
>(symbol: 'SemanticsUpdateBuilder::updateNode')
|
||||
external void _updateNode(
|
||||
@ -1449,6 +1476,7 @@ base class _NativeSemanticsUpdateBuilder extends NativeFieldWrapperClass1
|
||||
int role,
|
||||
List<String>? controlsNodes,
|
||||
int validationResultIndex,
|
||||
int inputType,
|
||||
);
|
||||
|
||||
@override
|
||||
|
@ -298,6 +298,9 @@ enum SemanticsRole {
|
||||
alert,
|
||||
}
|
||||
|
||||
// Mirrors engine/src/flutter/lib/ui/semantics.dart
|
||||
enum SemanticsInputType { none, text, url, phone, search, email }
|
||||
|
||||
// When adding a new StringAttributeType, the classes in these file must be
|
||||
// updated as well.
|
||||
// * engine/src/flutter/lib/ui/semantics.dart
|
||||
@ -389,6 +392,7 @@ class SemanticsUpdateBuilder {
|
||||
SemanticsRole role = SemanticsRole.none,
|
||||
required List<String>? controlsNodes,
|
||||
SemanticsValidationResult validationResult = SemanticsValidationResult.none,
|
||||
required SemanticsInputType inputType,
|
||||
}) {
|
||||
if (transform.length != 16) {
|
||||
throw ArgumentError('transform argument must have 16 entries.');
|
||||
@ -433,6 +437,7 @@ class SemanticsUpdateBuilder {
|
||||
role: role,
|
||||
controlsNodes: controlsNodes,
|
||||
validationResult: validationResult,
|
||||
inputType: inputType,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -247,6 +247,7 @@ class SemanticsNodeUpdate {
|
||||
required this.role,
|
||||
required this.controlsNodes,
|
||||
required this.validationResult,
|
||||
required this.inputType,
|
||||
});
|
||||
|
||||
/// See [ui.SemanticsUpdateBuilder.updateNode].
|
||||
@ -362,6 +363,9 @@ class SemanticsNodeUpdate {
|
||||
|
||||
/// See [ui.SemanticsUpdateBuilder.updateNode].
|
||||
final ui.SemanticsValidationResult validationResult;
|
||||
|
||||
/// See [ui.SemanticsUpdateBuilder.updateNode].
|
||||
final ui.SemanticsInputType inputType;
|
||||
}
|
||||
|
||||
/// Identifies [SemanticRole] implementations.
|
||||
@ -1416,6 +1420,8 @@ class SemanticsObject {
|
||||
/// The role of this node.
|
||||
late ui.SemanticsRole role;
|
||||
|
||||
late ui.SemanticsInputType inputType;
|
||||
|
||||
/// List of nodes whose contents are controlled by this node.
|
||||
///
|
||||
/// The list contains [identifier]s of those nodes.
|
||||
@ -1712,6 +1718,8 @@ class SemanticsObject {
|
||||
|
||||
role = update.role;
|
||||
|
||||
inputType = update.inputType;
|
||||
|
||||
if (!unorderedListEqual<String>(controlsNodes, update.controlsNodes)) {
|
||||
controlsNodes = update.controlsNodes;
|
||||
_markControlsNodesDirty();
|
||||
|
@ -226,8 +226,7 @@ class SemanticTextField extends SemanticRole {
|
||||
}
|
||||
|
||||
DomHTMLInputElement _createSingleLineField() {
|
||||
return createDomHTMLInputElement()
|
||||
..type = semanticsObject.hasFlag(ui.SemanticsFlag.isObscured) ? 'password' : 'text';
|
||||
return createDomHTMLInputElement();
|
||||
}
|
||||
|
||||
DomHTMLTextAreaElement _createMultiLineField() {
|
||||
@ -339,12 +338,37 @@ class SemanticTextField extends SemanticRole {
|
||||
} else {
|
||||
editableElement.removeAttribute('aria-required');
|
||||
}
|
||||
_updateInputType();
|
||||
}
|
||||
|
||||
void _updateEnabledState() {
|
||||
(editableElement as DomElementWithDisabledProperty).disabled = !semanticsObject.isEnabled;
|
||||
}
|
||||
|
||||
void _updateInputType() {
|
||||
if (semanticsObject.hasFlag(ui.SemanticsFlag.isMultiline)) {
|
||||
// text area can't be annotated with input type
|
||||
return;
|
||||
}
|
||||
final DomHTMLInputElement input = editableElement as DomHTMLInputElement;
|
||||
if (semanticsObject.hasFlag(ui.SemanticsFlag.isObscured)) {
|
||||
input.type = 'password';
|
||||
} else {
|
||||
switch (semanticsObject.inputType) {
|
||||
case ui.SemanticsInputType.search:
|
||||
input.type = 'search';
|
||||
case ui.SemanticsInputType.email:
|
||||
input.type = 'email';
|
||||
case ui.SemanticsInputType.url:
|
||||
input.type = 'url';
|
||||
case ui.SemanticsInputType.phone:
|
||||
input.type = 'tel';
|
||||
default:
|
||||
input.type = 'text';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
|
@ -4859,6 +4859,7 @@ void updateNode(
|
||||
String? linkUrl,
|
||||
List<String>? controlsNodes,
|
||||
ui.SemanticsRole role = ui.SemanticsRole.none,
|
||||
ui.SemanticsInputType inputType = ui.SemanticsInputType.none,
|
||||
}) {
|
||||
transform ??= Float64List.fromList(Matrix4.identity().storage);
|
||||
childrenInTraversalOrder ??= Int32List(0);
|
||||
@ -4902,6 +4903,7 @@ void updateNode(
|
||||
headingLevel: headingLevel,
|
||||
linkUrl: linkUrl,
|
||||
controlsNodes: controlsNodes,
|
||||
inputType: inputType,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -122,6 +122,7 @@ class SemanticsTester {
|
||||
ui.SemanticsRole? role,
|
||||
List<String>? controlsNodes,
|
||||
ui.SemanticsValidationResult validationResult = ui.SemanticsValidationResult.none,
|
||||
ui.SemanticsInputType inputType = ui.SemanticsInputType.none,
|
||||
}) {
|
||||
// Flags
|
||||
if (hasCheckedState ?? false) {
|
||||
@ -345,6 +346,7 @@ class SemanticsTester {
|
||||
role: role ?? ui.SemanticsRole.none,
|
||||
controlsNodes: controlsNodes,
|
||||
validationResult: validationResult,
|
||||
inputType: inputType,
|
||||
);
|
||||
_nodeUpdates.add(update);
|
||||
return update;
|
||||
|
@ -117,6 +117,22 @@ void testMain() {
|
||||
expect(inputElement.disabled, isFalse);
|
||||
});
|
||||
|
||||
test('renders text fields with input types', () {
|
||||
const inputTypeEnumToString = <ui.SemanticsInputType, String>{
|
||||
ui.SemanticsInputType.none: 'text',
|
||||
ui.SemanticsInputType.text: 'text',
|
||||
ui.SemanticsInputType.url: 'url',
|
||||
ui.SemanticsInputType.phone: 'tel',
|
||||
ui.SemanticsInputType.search: 'search',
|
||||
ui.SemanticsInputType.email: 'email',
|
||||
};
|
||||
for (final ui.SemanticsInputType type in ui.SemanticsInputType.values) {
|
||||
createTextFieldSemantics(value: 'text', inputType: type);
|
||||
|
||||
expectSemanticsTree(owner(), '<sem><input type="${inputTypeEnumToString[type]}" /></sem>');
|
||||
}
|
||||
});
|
||||
|
||||
test('renders a disabled text field', () {
|
||||
createTextFieldSemantics(isEnabled: false, value: 'hello');
|
||||
expectSemanticsTree(owner(), '''<sem><input /></sem>''');
|
||||
@ -498,6 +514,7 @@ SemanticsObject createTextFieldSemantics({
|
||||
ui.Rect rect = const ui.Rect.fromLTRB(0, 0, 100, 50),
|
||||
int textSelectionBase = 0,
|
||||
int textSelectionExtent = 0,
|
||||
ui.SemanticsInputType inputType = ui.SemanticsInputType.text,
|
||||
}) {
|
||||
final tester = SemanticsTester(owner());
|
||||
tester.updateNode(
|
||||
@ -516,6 +533,7 @@ SemanticsObject createTextFieldSemantics({
|
||||
textDirection: ui.TextDirection.ltr,
|
||||
textSelectionBase: textSelectionBase,
|
||||
textSelectionExtent: textSelectionExtent,
|
||||
inputType: inputType,
|
||||
);
|
||||
tester.apply();
|
||||
return tester.getSemanticsObject(0);
|
||||
|
@ -199,6 +199,7 @@ Future<void> a11y_main() async {
|
||||
textDirection: TextDirection.ltr,
|
||||
additionalActions: Int32List(0),
|
||||
controlsNodes: null,
|
||||
inputType: SemanticsInputType.none,
|
||||
)
|
||||
..updateNode(
|
||||
id: 84,
|
||||
@ -235,6 +236,7 @@ Future<void> a11y_main() async {
|
||||
childrenInHitTestOrder: Int32List(0),
|
||||
childrenInTraversalOrder: Int32List(0),
|
||||
controlsNodes: null,
|
||||
inputType: SemanticsInputType.none,
|
||||
)
|
||||
..updateNode(
|
||||
id: 96,
|
||||
@ -271,6 +273,7 @@ Future<void> a11y_main() async {
|
||||
textDirection: TextDirection.ltr,
|
||||
additionalActions: Int32List(0),
|
||||
controlsNodes: null,
|
||||
inputType: SemanticsInputType.none,
|
||||
)
|
||||
..updateNode(
|
||||
id: 128,
|
||||
@ -307,6 +310,7 @@ Future<void> a11y_main() async {
|
||||
childrenInHitTestOrder: Int32List(0),
|
||||
childrenInTraversalOrder: Int32List(0),
|
||||
controlsNodes: null,
|
||||
inputType: SemanticsInputType.none,
|
||||
)
|
||||
..updateCustomAction(id: 21, label: 'Archive', hint: 'archive message');
|
||||
|
||||
@ -395,6 +399,7 @@ Future<void> a11y_string_attributes() async {
|
||||
textDirection: TextDirection.ltr,
|
||||
additionalActions: Int32List(0),
|
||||
controlsNodes: null,
|
||||
inputType: SemanticsInputType.none,
|
||||
);
|
||||
|
||||
PlatformDispatcher.instance.views.first.updateSemantics(builder.build());
|
||||
@ -1692,6 +1697,7 @@ Future<void> a11y_main_multi_view() async {
|
||||
textDirection: TextDirection.ltr,
|
||||
additionalActions: Int32List(0),
|
||||
controlsNodes: null,
|
||||
inputType: SemanticsInputType.none,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -467,6 +467,7 @@ Future<void> sendSemanticsTreeInfo() async {
|
||||
additionalActions: additionalActions,
|
||||
role: ui.SemanticsRole.tab,
|
||||
controlsNodes: null,
|
||||
inputType: ui.SemanticsInputType.none,
|
||||
);
|
||||
return builder.build();
|
||||
}
|
||||
|
@ -76,6 +76,7 @@ class LocaleInitialization extends Scenario {
|
||||
childrenInHitTestOrder: Int32List(0),
|
||||
additionalActions: Int32List(0),
|
||||
controlsNodes: null,
|
||||
inputType: SemanticsInputType.none,
|
||||
);
|
||||
|
||||
final SemanticsUpdate semanticsUpdate = semanticsUpdateBuilder.build();
|
||||
@ -137,6 +138,7 @@ class LocaleInitialization extends Scenario {
|
||||
childrenInHitTestOrder: Int32List(0),
|
||||
additionalActions: Int32List(0),
|
||||
controlsNodes: null,
|
||||
inputType: SemanticsInputType.none,
|
||||
);
|
||||
|
||||
final SemanticsUpdate semanticsUpdate = semanticsUpdateBuilder.build();
|
||||
|
@ -1699,36 +1699,39 @@ class _SearchBarState extends State<SearchBar> {
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: effectivePadding,
|
||||
child: TextField(
|
||||
autofocus: widget.autoFocus,
|
||||
onTap: widget.onTap,
|
||||
onTapAlwaysCalled: true,
|
||||
onTapOutside: widget.onTapOutside,
|
||||
focusNode: _focusNode,
|
||||
onChanged: widget.onChanged,
|
||||
onSubmitted: widget.onSubmitted,
|
||||
controller: widget.controller,
|
||||
style: effectiveTextStyle,
|
||||
enabled: widget.enabled,
|
||||
decoration: InputDecoration(hintText: widget.hintText).applyDefaults(
|
||||
InputDecorationTheme(
|
||||
hintStyle: effectiveHintStyle,
|
||||
// The configuration below is to make sure that the text field
|
||||
// in `SearchBar` will not be overridden by the overall `InputDecorationTheme`
|
||||
enabledBorder: InputBorder.none,
|
||||
border: InputBorder.none,
|
||||
focusedBorder: InputBorder.none,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
// Setting `isDense` to true to allow the text field height to be
|
||||
// smaller than 48.0
|
||||
isDense: true,
|
||||
child: Semantics(
|
||||
inputType: SemanticsInputType.search,
|
||||
child: TextField(
|
||||
autofocus: widget.autoFocus,
|
||||
onTap: widget.onTap,
|
||||
onTapAlwaysCalled: true,
|
||||
onTapOutside: widget.onTapOutside,
|
||||
focusNode: _focusNode,
|
||||
onChanged: widget.onChanged,
|
||||
onSubmitted: widget.onSubmitted,
|
||||
controller: widget.controller,
|
||||
style: effectiveTextStyle,
|
||||
enabled: widget.enabled,
|
||||
decoration: InputDecoration(hintText: widget.hintText).applyDefaults(
|
||||
InputDecorationTheme(
|
||||
hintStyle: effectiveHintStyle,
|
||||
// The configuration below is to make sure that the text field
|
||||
// in `SearchBar` will not be overridden by the overall `InputDecorationTheme`
|
||||
enabledBorder: InputBorder.none,
|
||||
border: InputBorder.none,
|
||||
focusedBorder: InputBorder.none,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
// Setting `isDense` to true to allow the text field height to be
|
||||
// smaller than 48.0
|
||||
isDense: true,
|
||||
),
|
||||
),
|
||||
textCapitalization: effectiveTextCapitalization,
|
||||
textInputAction: widget.textInputAction,
|
||||
keyboardType: widget.keyboardType,
|
||||
scrollPadding: widget.scrollPadding,
|
||||
contextMenuBuilder: widget.contextMenuBuilder,
|
||||
),
|
||||
textCapitalization: effectiveTextCapitalization,
|
||||
textInputAction: widget.textInputAction,
|
||||
keyboardType: widget.keyboardType,
|
||||
scrollPadding: widget.scrollPadding,
|
||||
contextMenuBuilder: widget.contextMenuBuilder,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -7,7 +7,7 @@ library;
|
||||
|
||||
import 'dart:collection';
|
||||
import 'dart:math' as math;
|
||||
import 'dart:ui' as ui show BoxHeightStyle, BoxWidthStyle, LineMetrics, TextBox;
|
||||
import 'dart:ui' as ui show BoxHeightStyle, BoxWidthStyle, LineMetrics, SemanticsInputType, TextBox;
|
||||
|
||||
import 'package:characters/characters.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
@ -1382,7 +1382,10 @@ class RenderEditable extends RenderBox
|
||||
..textDirection = textDirection
|
||||
..isFocused = hasFocus
|
||||
..isTextField = true
|
||||
..isReadOnly = readOnly;
|
||||
..isReadOnly = readOnly
|
||||
// This is the default for customer that uses RenderEditable directly.
|
||||
// The real value is typically set by EditableText.
|
||||
..inputType = ui.SemanticsInputType.text;
|
||||
|
||||
if (hasFocus && selectionEnabled) {
|
||||
config.onSetSelection = _handleSetSelection;
|
||||
|
@ -4550,6 +4550,10 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
|
||||
config.validationResult = _properties.validationResult;
|
||||
}
|
||||
|
||||
if (_properties.inputType != null) {
|
||||
config.inputType = _properties.inputType!;
|
||||
}
|
||||
|
||||
// Registering _perform* as action handlers instead of the user provided
|
||||
// ones to ensure that changing a user provided handler from a non-null to
|
||||
// another non-null value doesn't require a semantics update.
|
||||
|
@ -16,6 +16,7 @@ import 'dart:ui'
|
||||
Rect,
|
||||
SemanticsAction,
|
||||
SemanticsFlag,
|
||||
SemanticsInputType,
|
||||
SemanticsRole,
|
||||
SemanticsUpdate,
|
||||
SemanticsUpdateBuilder,
|
||||
@ -737,6 +738,7 @@ class SemanticsData with Diagnosticable {
|
||||
required this.role,
|
||||
required this.controlsNodes,
|
||||
required this.validationResult,
|
||||
required this.inputType,
|
||||
this.tags,
|
||||
this.transform,
|
||||
this.customSemanticsActionIds,
|
||||
@ -1005,6 +1007,9 @@ class SemanticsData with Diagnosticable {
|
||||
/// {@macro flutter.semantics.SemanticsProperties.validationResult}
|
||||
final SemanticsValidationResult validationResult;
|
||||
|
||||
/// {@macro flutter.semantics.SemanticsNode.inputType}
|
||||
final SemanticsInputType inputType;
|
||||
|
||||
/// Whether [flags] contains the given flag.
|
||||
bool hasFlag(SemanticsFlag flag) => (flags & flag.index) != 0;
|
||||
|
||||
@ -1110,6 +1115,7 @@ class SemanticsData with Diagnosticable {
|
||||
other.linkUrl == linkUrl &&
|
||||
other.role == role &&
|
||||
other.validationResult == validationResult &&
|
||||
other.inputType == inputType &&
|
||||
_sortedListsEqual(other.customSemanticsActionIds, customSemanticsActionIds) &&
|
||||
setEquals<String>(controlsNodes, other.controlsNodes);
|
||||
}
|
||||
@ -1147,6 +1153,7 @@ class SemanticsData with Diagnosticable {
|
||||
role,
|
||||
validationResult,
|
||||
controlsNodes == null ? null : Object.hashAll(controlsNodes!),
|
||||
inputType,
|
||||
),
|
||||
);
|
||||
|
||||
@ -1293,6 +1300,9 @@ class SemanticsProperties extends DiagnosticableTree {
|
||||
this.textDirection,
|
||||
this.sortKey,
|
||||
this.tagForChildren,
|
||||
this.role,
|
||||
this.controlsNodes,
|
||||
this.inputType,
|
||||
this.onTap,
|
||||
this.onLongPress,
|
||||
this.onScrollLeft,
|
||||
@ -1315,8 +1325,6 @@ class SemanticsProperties extends DiagnosticableTree {
|
||||
this.onFocus,
|
||||
this.onDismiss,
|
||||
this.customSemanticsActions,
|
||||
this.role,
|
||||
this.controlsNodes,
|
||||
this.validationResult = SemanticsValidationResult.none,
|
||||
}) : assert(
|
||||
label == null || attributedLabel == null,
|
||||
@ -2161,6 +2169,17 @@ class SemanticsProperties extends DiagnosticableTree {
|
||||
/// {@endtemplate}
|
||||
final SemanticsValidationResult validationResult;
|
||||
|
||||
/// {@template flutter.semantics.SemanticsProperties.inputType}
|
||||
/// The input type for of a editable widget.
|
||||
///
|
||||
/// This property is only used when the subtree represents a text field.
|
||||
///
|
||||
/// Assistive technologies use this property to provide better information to
|
||||
/// users. For example, screen reader reads out the input type of text field
|
||||
/// when focused.
|
||||
/// {@endtemplate}
|
||||
final SemanticsInputType? inputType;
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
@ -3139,6 +3158,18 @@ class SemanticsNode with DiagnosticableTreeMixin {
|
||||
SemanticsValidationResult get validationResult => _validationResult;
|
||||
SemanticsValidationResult _validationResult = _kEmptyConfig.validationResult;
|
||||
|
||||
/// {@template flutter.semantics.SemanticsNode.inputType}
|
||||
/// The input type for of a editable node.
|
||||
///
|
||||
/// This property is only used when this node represents a text field.
|
||||
///
|
||||
/// Assistive technologies use this property to provide better information to
|
||||
/// users. For example, screen reader reads out the input type of text field
|
||||
/// when focused.
|
||||
/// {@endtemplate}
|
||||
SemanticsInputType get inputType => _inputType;
|
||||
SemanticsInputType _inputType = _kEmptyConfig.inputType;
|
||||
|
||||
bool _canPerformAction(SemanticsAction action) => _actions.containsKey(action);
|
||||
|
||||
static final SemanticsConfiguration _kEmptyConfig = SemanticsConfiguration();
|
||||
@ -3207,6 +3238,8 @@ class SemanticsNode with DiagnosticableTreeMixin {
|
||||
_role = config._role;
|
||||
_controlsNodes = config._controlsNodes;
|
||||
_validationResult = config._validationResult;
|
||||
_inputType = config._inputType;
|
||||
|
||||
_replaceChildren(childrenInInversePaintOrder ?? const <SemanticsNode>[]);
|
||||
|
||||
if (mergeAllDescendantsIntoThisNodeValueChanged) {
|
||||
@ -3258,6 +3291,7 @@ class SemanticsNode with DiagnosticableTreeMixin {
|
||||
SemanticsRole role = _role;
|
||||
Set<String>? controlsNodes = _controlsNodes;
|
||||
SemanticsValidationResult validationResult = _validationResult;
|
||||
SemanticsInputType inputType = _inputType;
|
||||
final Set<int> customSemanticsActionIds = <int>{};
|
||||
for (final CustomSemanticsAction action in _customSemanticsActions.keys) {
|
||||
customSemanticsActionIds.add(CustomSemanticsAction.getIdentifier(action));
|
||||
@ -3316,6 +3350,9 @@ class SemanticsNode with DiagnosticableTreeMixin {
|
||||
if (role == SemanticsRole.none) {
|
||||
role = node._role;
|
||||
}
|
||||
if (inputType == SemanticsInputType.none) {
|
||||
inputType = node._inputType;
|
||||
}
|
||||
if (tooltip == '') {
|
||||
tooltip = node._tooltip;
|
||||
}
|
||||
@ -3409,6 +3446,7 @@ class SemanticsNode with DiagnosticableTreeMixin {
|
||||
role: role,
|
||||
controlsNodes: controlsNodes,
|
||||
validationResult: validationResult,
|
||||
inputType: inputType,
|
||||
);
|
||||
}
|
||||
|
||||
@ -3496,6 +3534,7 @@ class SemanticsNode with DiagnosticableTreeMixin {
|
||||
role: data.role,
|
||||
controlsNodes: data.controlsNodes?.toList(),
|
||||
validationResult: data.validationResult,
|
||||
inputType: data.inputType,
|
||||
);
|
||||
_dirty = false;
|
||||
}
|
||||
@ -3693,6 +3732,9 @@ class SemanticsNode with DiagnosticableTreeMixin {
|
||||
properties.add(DoubleProperty('elevation', elevation, defaultValue: 0.0));
|
||||
properties.add(DoubleProperty('thickness', thickness, defaultValue: 0.0));
|
||||
properties.add(IntProperty('headingLevel', _headingLevel, defaultValue: 0));
|
||||
if (_inputType != SemanticsInputType.none) {
|
||||
properties.add(EnumProperty<SemanticsInputType>('inputType', _inputType));
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a string representation of this node and its descendants.
|
||||
@ -5674,6 +5716,14 @@ class SemanticsConfiguration {
|
||||
_hasBeenAnnotated = true;
|
||||
}
|
||||
|
||||
/// {@macro flutter.semantics.SemanticsProperties.inputType}
|
||||
SemanticsInputType get inputType => _inputType;
|
||||
SemanticsInputType _inputType = SemanticsInputType.none;
|
||||
set inputType(SemanticsInputType value) {
|
||||
_inputType = value;
|
||||
_hasBeenAnnotated = true;
|
||||
}
|
||||
|
||||
// TAGS
|
||||
|
||||
/// The set of tags that this configuration wants to add to all child
|
||||
@ -5846,6 +5896,9 @@ class SemanticsConfiguration {
|
||||
if (_role == SemanticsRole.none) {
|
||||
_role = child._role;
|
||||
}
|
||||
if (_inputType == SemanticsInputType.none) {
|
||||
_inputType = child._inputType;
|
||||
}
|
||||
_attributedHint = _concatAttributedString(
|
||||
thisAttributedString: _attributedHint,
|
||||
thisTextDirection: textDirection,
|
||||
@ -5916,7 +5969,8 @@ class SemanticsConfiguration {
|
||||
.._linkUrl = _linkUrl
|
||||
.._role = _role
|
||||
.._controlsNodes = _controlsNodes
|
||||
.._validationResult = _validationResult;
|
||||
.._validationResult = _validationResult
|
||||
.._inputType = _inputType;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@
|
||||
library;
|
||||
|
||||
import 'dart:math' as math;
|
||||
import 'dart:ui' as ui show Image, ImageFilter, TextHeightBehavior;
|
||||
import 'dart:ui' as ui show Image, ImageFilter, SemanticsInputType, TextHeightBehavior;
|
||||
|
||||
import 'package:flutter/animation.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
@ -7385,6 +7385,7 @@ class Semantics extends SingleChildRenderObjectWidget {
|
||||
SemanticsRole? role,
|
||||
Set<String>? controlsNodes,
|
||||
SemanticsValidationResult validationResult = SemanticsValidationResult.none,
|
||||
ui.SemanticsInputType? inputType,
|
||||
}) : this.fromProperties(
|
||||
key: key,
|
||||
child: child,
|
||||
@ -7463,6 +7464,7 @@ class Semantics extends SingleChildRenderObjectWidget {
|
||||
role: role,
|
||||
controlsNodes: controlsNodes,
|
||||
validationResult: validationResult,
|
||||
inputType: inputType,
|
||||
),
|
||||
);
|
||||
|
||||
|
@ -5563,6 +5563,17 @@ class EditableTextState extends State<EditableText>
|
||||
(null, final double textScaleFactor) => TextScaler.linear(textScaleFactor),
|
||||
(null, null) => MediaQuery.textScalerOf(context),
|
||||
};
|
||||
final ui.SemanticsInputType inputType;
|
||||
switch (widget.keyboardType) {
|
||||
case TextInputType.phone:
|
||||
inputType = ui.SemanticsInputType.phone;
|
||||
case TextInputType.url:
|
||||
inputType = ui.SemanticsInputType.url;
|
||||
case TextInputType.emailAddress:
|
||||
inputType = ui.SemanticsInputType.email;
|
||||
default:
|
||||
inputType = ui.SemanticsInputType.text;
|
||||
}
|
||||
|
||||
return _CompositionCallback(
|
||||
compositeCallback: _compositeCallback,
|
||||
@ -5654,6 +5665,7 @@ class EditableTextState extends State<EditableText>
|
||||
return CompositedTransformTarget(
|
||||
link: _toolbarLayerLink,
|
||||
child: Semantics(
|
||||
inputType: inputType,
|
||||
onCopy: _semanticsOnCopy(controls),
|
||||
onCut: _semanticsOnCut(controls),
|
||||
onPaste: _semanticsOnPaste(controls),
|
||||
|
@ -3223,7 +3223,7 @@ void main() {
|
||||
expect(controller.value.text, initValue);
|
||||
});
|
||||
|
||||
testWidgets('Disabled SearchBar semantics node still contains value', (
|
||||
testWidgets('Disabled SearchBar semantics node still contains value and inputType', (
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
final SemanticsTester semantics = SemanticsTester(tester);
|
||||
@ -3236,7 +3236,23 @@ void main() {
|
||||
),
|
||||
);
|
||||
|
||||
expect(semantics, includesNodeWith(actions: <SemanticsAction>[], value: 'text'));
|
||||
expect(
|
||||
semantics,
|
||||
includesNodeWith(
|
||||
actions: <SemanticsAction>[],
|
||||
value: 'text',
|
||||
inputType: SemanticsInputType.search,
|
||||
),
|
||||
);
|
||||
semantics.dispose();
|
||||
});
|
||||
|
||||
testWidgets('SearchBar semantics node has search input type', (WidgetTester tester) async {
|
||||
final SemanticsTester semantics = SemanticsTester(tester);
|
||||
|
||||
await tester.pumpWidget(const MaterialApp(home: Material(child: Center(child: SearchBar()))));
|
||||
|
||||
expect(semantics, includesNodeWith(inputType: SemanticsInputType.search));
|
||||
semantics.dispose();
|
||||
});
|
||||
|
||||
|
@ -3,6 +3,8 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:math' as math;
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@ -135,6 +137,21 @@ void main() {
|
||||
expect(editable.size.height, 100);
|
||||
});
|
||||
|
||||
test('has default semantics input type', () {
|
||||
const InlineSpan text = TextSpan(text: 'text');
|
||||
final RenderEditable editable = RenderEditable(
|
||||
textDirection: TextDirection.ltr,
|
||||
startHandleLayerLink: LayerLink(),
|
||||
endHandleLayerLink: LayerLink(),
|
||||
offset: ViewportOffset.zero(),
|
||||
textSelectionDelegate: _FakeEditableTextState(),
|
||||
text: text,
|
||||
);
|
||||
final SemanticsConfiguration config = SemanticsConfiguration();
|
||||
editable.describeSemanticsConfiguration(config);
|
||||
expect(config.inputType, SemanticsInputType.text);
|
||||
});
|
||||
|
||||
test('Reports the height of the first line when maxLines is 1', () {
|
||||
final InlineSpan multilineSpan = TextSpan(
|
||||
text: 'liiiiines\n' * 10,
|
||||
|
@ -231,6 +231,7 @@ class SemanticsUpdateBuilderSpy extends Fake implements ui.SemanticsUpdateBuilde
|
||||
SemanticsRole role = SemanticsRole.none,
|
||||
required List<String>? controlsNodes,
|
||||
SemanticsValidationResult validationResult = SemanticsValidationResult.none,
|
||||
required ui.SemanticsInputType inputType,
|
||||
}) {
|
||||
// Makes sure we don't send the same id twice.
|
||||
assert(!observations.containsKey(id));
|
||||
|
@ -624,6 +624,23 @@ void main() {
|
||||
expect(data.controlsNodes, <String>{'abc', 'ghi', 'def'});
|
||||
});
|
||||
|
||||
testWidgets('Semantics can set semantics input type', (WidgetTester tester) async {
|
||||
final UniqueKey key1 = UniqueKey();
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Semantics(
|
||||
key: key1,
|
||||
inputType: SemanticsInputType.phone,
|
||||
child: const SizedBox(width: 10, height: 10),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
final SemanticsNode node1 = tester.getSemantics(find.byKey(key1));
|
||||
expect(node1.inputType, SemanticsInputType.phone);
|
||||
});
|
||||
|
||||
testWidgets('Semantics can set alert rule', (WidgetTester tester) async {
|
||||
final UniqueKey key = UniqueKey();
|
||||
await tester.pumpWidget(
|
||||
|
@ -4135,8 +4135,8 @@ void main() {
|
||||
),
|
||||
);
|
||||
|
||||
final RenderEditable render = tester.allRenderObjects.whereType<RenderEditable>().first;
|
||||
final int semanticsId = render.debugSemantics!.id;
|
||||
final SemanticsNode node = find.semantics.byValue('test').evaluate().first;
|
||||
final int semanticsId = node.id;
|
||||
|
||||
expect(controller.selection.baseOffset, 4);
|
||||
expect(controller.selection.extentOffset, 4);
|
||||
@ -4241,8 +4241,8 @@ void main() {
|
||||
),
|
||||
);
|
||||
|
||||
final RenderEditable render = tester.allRenderObjects.whereType<RenderEditable>().first;
|
||||
final int semanticsId = render.debugSemantics!.id;
|
||||
final SemanticsNode node = find.semantics.byValue('test for words').evaluate().first;
|
||||
final int semanticsId = node.id;
|
||||
|
||||
expect(controller.selection.baseOffset, 14);
|
||||
expect(controller.selection.extentOffset, 14);
|
||||
@ -4356,8 +4356,8 @@ void main() {
|
||||
),
|
||||
);
|
||||
|
||||
final RenderEditable render = tester.allRenderObjects.whereType<RenderEditable>().first;
|
||||
final int semanticsId = render.debugSemantics!.id;
|
||||
final SemanticsNode node = find.semantics.byValue('test').evaluate().first;
|
||||
final int semanticsId = node.id;
|
||||
|
||||
expect(controller.selection.baseOffset, 4);
|
||||
expect(controller.selection.extentOffset, 4);
|
||||
@ -4473,8 +4473,8 @@ void main() {
|
||||
),
|
||||
);
|
||||
|
||||
final RenderEditable render = tester.allRenderObjects.whereType<RenderEditable>().first;
|
||||
final int semanticsId = render.debugSemantics!.id;
|
||||
final SemanticsNode node = find.semantics.byValue('test for words').evaluate().first;
|
||||
final int semanticsId = node.id;
|
||||
|
||||
expect(controller.selection.baseOffset, 14);
|
||||
expect(controller.selection.extentOffset, 14);
|
||||
@ -4932,7 +4932,7 @@ void main() {
|
||||
await tester.pump();
|
||||
|
||||
final SemanticsOwner owner = tester.binding.pipelineOwner.semanticsOwner!;
|
||||
const int expectedNodeId = 5;
|
||||
const int expectedNodeId = 4;
|
||||
|
||||
expect(
|
||||
semantics,
|
||||
@ -5016,7 +5016,8 @@ void main() {
|
||||
await tester.pump();
|
||||
|
||||
final SemanticsOwner owner = tester.binding.pipelineOwner.semanticsOwner!;
|
||||
const int expectedNodeId = 5;
|
||||
final SemanticsNode node = find.semantics.byValue('ABCDEFG').evaluate().first;
|
||||
final int expectedNodeId = node.id;
|
||||
|
||||
expect(controller.value.selection.isCollapsed, isTrue);
|
||||
|
||||
|
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/physics.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
@ -587,6 +589,7 @@ class SemanticsTester {
|
||||
int? currentValueLength,
|
||||
int? maxValueLength,
|
||||
SemanticsNode? ancestor,
|
||||
SemanticsInputType? inputType,
|
||||
}) {
|
||||
bool checkNode(SemanticsNode node) {
|
||||
if (label != null && node.label != label) {
|
||||
@ -670,6 +673,9 @@ class SemanticsTester {
|
||||
if (maxValueLength != null && node.maxValueLength != maxValueLength) {
|
||||
return false;
|
||||
}
|
||||
if (inputType != null && node.inputType != inputType) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -950,6 +956,7 @@ class _IncludesNodeWith extends Matcher {
|
||||
this.scrollExtentMin,
|
||||
this.maxValueLength,
|
||||
this.currentValueLength,
|
||||
this.inputType,
|
||||
}) : assert(
|
||||
label != null ||
|
||||
value != null ||
|
||||
@ -962,7 +969,8 @@ class _IncludesNodeWith extends Matcher {
|
||||
scrollExtentMax != null ||
|
||||
scrollExtentMin != null ||
|
||||
maxValueLength != null ||
|
||||
currentValueLength != null,
|
||||
currentValueLength != null ||
|
||||
inputType != null,
|
||||
);
|
||||
final AttributedString? attributedLabel;
|
||||
final AttributedString? attributedValue;
|
||||
@ -981,6 +989,7 @@ class _IncludesNodeWith extends Matcher {
|
||||
final double? scrollExtentMin;
|
||||
final int? currentValueLength;
|
||||
final int? maxValueLength;
|
||||
final SemanticsInputType? inputType;
|
||||
|
||||
@override
|
||||
bool matches(covariant SemanticsTester item, Map<dynamic, dynamic> matchState) {
|
||||
@ -1003,6 +1012,7 @@ class _IncludesNodeWith extends Matcher {
|
||||
scrollExtentMin: scrollExtentMin,
|
||||
currentValueLength: currentValueLength,
|
||||
maxValueLength: maxValueLength,
|
||||
inputType: inputType,
|
||||
)
|
||||
.isNotEmpty;
|
||||
}
|
||||
@ -1038,6 +1048,7 @@ class _IncludesNodeWith extends Matcher {
|
||||
if (scrollExtentMin != null) 'scrollExtentMin "$scrollExtentMin"',
|
||||
if (currentValueLength != null) 'currentValueLength "$currentValueLength"',
|
||||
if (maxValueLength != null) 'maxValueLength "$maxValueLength"',
|
||||
if (inputType != null) 'inputType $inputType',
|
||||
];
|
||||
return strings.join(', ');
|
||||
}
|
||||
@ -1065,6 +1076,7 @@ Matcher includesNodeWith({
|
||||
double? scrollExtentMin,
|
||||
int? maxValueLength,
|
||||
int? currentValueLength,
|
||||
SemanticsInputType? inputType,
|
||||
}) {
|
||||
return _IncludesNodeWith(
|
||||
label: label,
|
||||
@ -1084,5 +1096,6 @@ Matcher includesNodeWith({
|
||||
scrollExtentMin: scrollExtentMin,
|
||||
maxValueLength: maxValueLength,
|
||||
currentValueLength: currentValueLength,
|
||||
inputType: inputType,
|
||||
);
|
||||
}
|
||||
|
@ -686,6 +686,7 @@ Matcher matchesSemantics({
|
||||
int? maxValueLength,
|
||||
int? currentValueLength,
|
||||
SemanticsValidationResult validationResult = SemanticsValidationResult.none,
|
||||
ui.SemanticsInputType? inputType,
|
||||
// Flags //
|
||||
bool hasCheckedState = false,
|
||||
bool isChecked = false,
|
||||
@ -770,6 +771,7 @@ Matcher matchesSemantics({
|
||||
maxValueLength: maxValueLength,
|
||||
currentValueLength: currentValueLength,
|
||||
validationResult: validationResult,
|
||||
inputType: inputType,
|
||||
// Flags
|
||||
hasCheckedState: hasCheckedState,
|
||||
isChecked: isChecked,
|
||||
@ -882,6 +884,7 @@ Matcher containsSemantics({
|
||||
int? maxValueLength,
|
||||
int? currentValueLength,
|
||||
SemanticsValidationResult validationResult = SemanticsValidationResult.none,
|
||||
ui.SemanticsInputType? inputType,
|
||||
// Flags
|
||||
bool? hasCheckedState,
|
||||
bool? isChecked,
|
||||
@ -966,6 +969,7 @@ Matcher containsSemantics({
|
||||
maxValueLength: maxValueLength,
|
||||
currentValueLength: currentValueLength,
|
||||
validationResult: validationResult,
|
||||
inputType: inputType,
|
||||
// Flags
|
||||
hasCheckedState: hasCheckedState,
|
||||
isChecked: isChecked,
|
||||
@ -2403,6 +2407,7 @@ class _MatchesSemanticsData extends Matcher {
|
||||
required this.maxValueLength,
|
||||
required this.currentValueLength,
|
||||
required this.validationResult,
|
||||
required this.inputType,
|
||||
// Flags
|
||||
required bool? hasCheckedState,
|
||||
required bool? isChecked,
|
||||
@ -2556,6 +2561,7 @@ class _MatchesSemanticsData extends Matcher {
|
||||
final int? platformViewId;
|
||||
final int? maxValueLength;
|
||||
final int? currentValueLength;
|
||||
final ui.SemanticsInputType? inputType;
|
||||
final List<Matcher>? children;
|
||||
final SemanticsValidationResult validationResult;
|
||||
|
||||
@ -2603,6 +2609,9 @@ class _MatchesSemanticsData extends Matcher {
|
||||
if (tooltip != null) {
|
||||
description.add(' with tooltip: $tooltip');
|
||||
}
|
||||
if (inputType != null) {
|
||||
description.add(' with inputType: $inputType');
|
||||
}
|
||||
if (actions.isNotEmpty) {
|
||||
final List<SemanticsAction> expectedActions =
|
||||
actions.entries
|
||||
@ -2813,6 +2822,9 @@ class _MatchesSemanticsData extends Matcher {
|
||||
if (validationResult != data.validationResult) {
|
||||
return failWithDescription(matchState, 'validationResult was: ${data.validationResult}');
|
||||
}
|
||||
if (inputType != null && inputType != data.inputType) {
|
||||
return failWithDescription(matchState, 'inputType was: ${data.inputType}');
|
||||
}
|
||||
if (actions.isNotEmpty) {
|
||||
final List<SemanticsAction> unexpectedActions = <SemanticsAction>[];
|
||||
final List<SemanticsAction> missingActions = <SemanticsAction>[];
|
||||
|
@ -732,6 +732,7 @@ void main() {
|
||||
role: ui.SemanticsRole.none,
|
||||
controlsNodes: null,
|
||||
validationResult: SemanticsValidationResult.none,
|
||||
inputType: ui.SemanticsInputType.none,
|
||||
);
|
||||
final _FakeSemanticsNode node = _FakeSemanticsNode(data);
|
||||
|
||||
@ -1036,6 +1037,7 @@ void main() {
|
||||
role: ui.SemanticsRole.none,
|
||||
controlsNodes: null,
|
||||
validationResult: SemanticsValidationResult.none,
|
||||
inputType: ui.SemanticsInputType.none,
|
||||
);
|
||||
final _FakeSemanticsNode node = _FakeSemanticsNode(data);
|
||||
|
||||
@ -1136,6 +1138,7 @@ void main() {
|
||||
role: ui.SemanticsRole.none,
|
||||
controlsNodes: null,
|
||||
validationResult: SemanticsValidationResult.none,
|
||||
inputType: ui.SemanticsInputType.none,
|
||||
);
|
||||
final _FakeSemanticsNode node = _FakeSemanticsNode(data);
|
||||
|
||||
@ -1243,6 +1246,7 @@ void main() {
|
||||
role: ui.SemanticsRole.none,
|
||||
controlsNodes: null,
|
||||
validationResult: SemanticsValidationResult.none,
|
||||
inputType: ui.SemanticsInputType.none,
|
||||
);
|
||||
final _FakeSemanticsNode emptyNode = _FakeSemanticsNode(emptyData);
|
||||
|
||||
@ -1276,6 +1280,7 @@ void main() {
|
||||
role: ui.SemanticsRole.none,
|
||||
controlsNodes: null,
|
||||
validationResult: SemanticsValidationResult.none,
|
||||
inputType: ui.SemanticsInputType.none,
|
||||
);
|
||||
final _FakeSemanticsNode fullNode = _FakeSemanticsNode(fullData);
|
||||
|
||||
@ -1365,6 +1370,7 @@ void main() {
|
||||
role: ui.SemanticsRole.none,
|
||||
controlsNodes: null,
|
||||
validationResult: SemanticsValidationResult.none,
|
||||
inputType: ui.SemanticsInputType.none,
|
||||
);
|
||||
final _FakeSemanticsNode node = _FakeSemanticsNode(data);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user