Adds aria-controls support (#163894)
<!-- 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 --> adding a new property in semantics properties called controlsVisibilityOfNodes, where developer can assign SemanticsProperties.identifier of other nodes to indicates which nodes' visibilities this node controls fixes https://github.com/flutter/flutter/issues/162125 ## 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:
@@ -234,6 +234,7 @@ void sendSemanticsUpdate() {
|
||||
additionalActions: additionalActions,
|
||||
headingLevel: 0,
|
||||
linkUrl: '',
|
||||
controlsNodes: null,
|
||||
);
|
||||
_semanticsUpdate(builder.build());
|
||||
}
|
||||
@@ -287,6 +288,7 @@ void sendSemanticsUpdateWithRole() {
|
||||
headingLevel: 0,
|
||||
linkUrl: '',
|
||||
role: SemanticsRole.tab,
|
||||
controlsNodes: null,
|
||||
);
|
||||
_semanticsUpdate(builder.build());
|
||||
}
|
||||
|
||||
@@ -1129,6 +1129,7 @@ abstract class SemanticsUpdateBuilder {
|
||||
int headingLevel = 0,
|
||||
String linkUrl = '',
|
||||
SemanticsRole role = SemanticsRole.none,
|
||||
required List<String>? controlsNodes,
|
||||
});
|
||||
|
||||
/// Update the custom semantics action associated with the given `id`.
|
||||
@@ -1205,6 +1206,7 @@ base class _NativeSemanticsUpdateBuilder extends NativeFieldWrapperClass1
|
||||
int headingLevel = 0,
|
||||
String linkUrl = '',
|
||||
SemanticsRole role = SemanticsRole.none,
|
||||
required List<String>? controlsNodes,
|
||||
}) {
|
||||
assert(_matrix4IsValid(transform));
|
||||
assert(
|
||||
@@ -1251,6 +1253,7 @@ base class _NativeSemanticsUpdateBuilder extends NativeFieldWrapperClass1
|
||||
headingLevel,
|
||||
linkUrl,
|
||||
role.index,
|
||||
controlsNodes,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1296,6 +1299,7 @@ base class _NativeSemanticsUpdateBuilder extends NativeFieldWrapperClass1
|
||||
Int32,
|
||||
Handle,
|
||||
Int32,
|
||||
Handle,
|
||||
)
|
||||
>(symbol: 'SemanticsUpdateBuilder::updateNode')
|
||||
external void _updateNode(
|
||||
@@ -1338,6 +1342,7 @@ base class _NativeSemanticsUpdateBuilder extends NativeFieldWrapperClass1
|
||||
int headingLevel,
|
||||
String linkUrl,
|
||||
int role,
|
||||
List<String>? controlsNodes,
|
||||
);
|
||||
|
||||
@override
|
||||
|
||||
@@ -69,7 +69,8 @@ void SemanticsUpdateBuilder::updateNode(
|
||||
const tonic::Int32List& localContextActions,
|
||||
int headingLevel,
|
||||
std::string linkUrl,
|
||||
int role) {
|
||||
int role,
|
||||
const std::vector<std::string>& controlsNodes) {
|
||||
FML_CHECK(scrollChildren == 0 ||
|
||||
(scrollChildren > 0 && childrenInHitTestOrder.data()))
|
||||
<< "Semantics update contained scrollChildren but did not have "
|
||||
|
||||
@@ -68,7 +68,8 @@ class SemanticsUpdateBuilder
|
||||
const tonic::Int32List& customAccessibilityActions,
|
||||
int headingLevel,
|
||||
std::string linkUrl,
|
||||
int role);
|
||||
int role,
|
||||
const std::vector<std::string>& controlsNodes);
|
||||
|
||||
void updateCustomAction(int id,
|
||||
std::string label,
|
||||
|
||||
@@ -371,6 +371,7 @@ class SemanticsUpdateBuilder {
|
||||
int headingLevel = 0,
|
||||
String? linkUrl,
|
||||
SemanticsRole role = SemanticsRole.none,
|
||||
required List<String>? controlsNodes,
|
||||
}) {
|
||||
if (transform.length != 16) {
|
||||
throw ArgumentError('transform argument must have 16 entries.');
|
||||
@@ -413,6 +414,7 @@ class SemanticsUpdateBuilder {
|
||||
headingLevel: headingLevel,
|
||||
linkUrl: linkUrl,
|
||||
role: role,
|
||||
controlsNodes: controlsNodes,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ class EngineAccessibilityFeatures implements ui.AccessibilityFeatures {
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
final List<String> features = <String>[];
|
||||
final features = <String>[];
|
||||
if (accessibleNavigation) {
|
||||
features.add('accessibleNavigation');
|
||||
}
|
||||
@@ -239,6 +239,7 @@ class SemanticsNodeUpdate {
|
||||
required this.headingLevel,
|
||||
this.linkUrl,
|
||||
required this.role,
|
||||
required this.controlsNodes,
|
||||
});
|
||||
|
||||
/// See [ui.SemanticsUpdateBuilder.updateNode].
|
||||
@@ -348,6 +349,9 @@ class SemanticsNodeUpdate {
|
||||
|
||||
/// See [ui.SemanticsUpdateBuilder.updateNode].
|
||||
final ui.SemanticsRole role;
|
||||
|
||||
/// See [ui.SemanticsUpdateBuilder.updateNode].
|
||||
final List<String>? controlsNodes;
|
||||
}
|
||||
|
||||
/// Identifies [SemanticRole] implementations.
|
||||
@@ -672,6 +676,10 @@ abstract class SemanticRole {
|
||||
if (semanticsObject.isIdentifierDirty) {
|
||||
_updateIdentifier();
|
||||
}
|
||||
|
||||
if (semanticsObject.isControlsNodesDirty) {
|
||||
_updateControls();
|
||||
}
|
||||
}
|
||||
|
||||
void _updateIdentifier() {
|
||||
@@ -682,6 +690,26 @@ abstract class SemanticRole {
|
||||
}
|
||||
}
|
||||
|
||||
void _updateControls() {
|
||||
if (semanticsObject.hasControlsNodes) {
|
||||
semanticsObject.owner.addOneTimePostUpdateCallback(() {
|
||||
final elementIds = <String>[];
|
||||
for (final String identifier in semanticsObject.controlsNodes!) {
|
||||
final int? semanticNodeId = semanticsObject.owner.identifiersToIds[identifier];
|
||||
if (semanticNodeId == null) {
|
||||
continue;
|
||||
}
|
||||
elementIds.add('flt-semantic-node-$semanticNodeId');
|
||||
}
|
||||
if (elementIds.isNotEmpty) {
|
||||
setAttribute('aria-controls', elementIds.join(' '));
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
removeAttribute('aria-controls');
|
||||
}
|
||||
|
||||
/// Whether this role was disposed of.
|
||||
bool get isDisposed => _isDisposed;
|
||||
bool _isDisposed = false;
|
||||
@@ -1277,6 +1305,23 @@ class SemanticsObject {
|
||||
/// The role of this node.
|
||||
late ui.SemanticsRole role;
|
||||
|
||||
/// List of nodes whose contents are controlled by this node.
|
||||
///
|
||||
/// The list contains [identifier]s of those nodes.
|
||||
List<String>? controlsNodes;
|
||||
|
||||
/// Whether this object controls at least one node.
|
||||
bool get hasControlsNodes => controlsNodes != null && controlsNodes!.isNotEmpty;
|
||||
|
||||
static const int _controlsNodesIndex = 1 << 27;
|
||||
|
||||
/// Whether the [controlsNodes] field has been updated but has not been
|
||||
/// applied to the DOM yet.
|
||||
bool get isControlsNodesDirty => _isDirty(_controlsNodesIndex);
|
||||
void _markControlsNodesDirty() {
|
||||
_dirtyFields |= _controlsNodesIndex;
|
||||
}
|
||||
|
||||
/// Bitfield showing which fields have been updated but have not yet been
|
||||
/// applied to the DOM.
|
||||
///
|
||||
@@ -1423,7 +1468,13 @@ class SemanticsObject {
|
||||
}
|
||||
|
||||
if (_identifier != update.identifier) {
|
||||
if (_identifier?.isNotEmpty ?? false) {
|
||||
owner.identifiersToIds.remove(_identifier);
|
||||
}
|
||||
_identifier = update.identifier;
|
||||
if (_identifier?.isNotEmpty ?? false) {
|
||||
owner.identifiersToIds[_identifier!] = id;
|
||||
}
|
||||
_markIdentifierDirty();
|
||||
}
|
||||
|
||||
@@ -1569,6 +1620,11 @@ class SemanticsObject {
|
||||
|
||||
role = update.role;
|
||||
|
||||
if (!unorderedListEqual<String>(controlsNodes, update.controlsNodes)) {
|
||||
controlsNodes = update.controlsNodes;
|
||||
_markControlsNodesDirty();
|
||||
}
|
||||
|
||||
// Apply updates to the DOM.
|
||||
_updateRole();
|
||||
|
||||
@@ -1635,7 +1691,7 @@ class SemanticsObject {
|
||||
|
||||
// Always render in traversal order, because the accessibility traversal
|
||||
// is determined by the DOM order of elements.
|
||||
final List<SemanticsObject> childrenInRenderOrder = <SemanticsObject>[];
|
||||
final childrenInRenderOrder = <SemanticsObject>[];
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
childrenInRenderOrder.add(owner._semanticsTree[childrenInTraversalOrder[i]]!);
|
||||
}
|
||||
@@ -1669,7 +1725,7 @@ class SemanticsObject {
|
||||
}
|
||||
|
||||
// At this point it is guaranteed to have had a non-empty previous child list.
|
||||
final List<SemanticsObject> previousChildrenInRenderOrder = _currentChildrenInRenderOrder!;
|
||||
final previousChildrenInRenderOrder = _currentChildrenInRenderOrder!;
|
||||
final int previousCount = previousChildrenInRenderOrder.length;
|
||||
|
||||
// Both non-empty case.
|
||||
@@ -1690,7 +1746,7 @@ class SemanticsObject {
|
||||
|
||||
// Indices into the old child list pointing at children that also exist in
|
||||
// the new child list.
|
||||
final List<int> intersectionIndicesOld = <int>[];
|
||||
final intersectionIndicesOld = <int>[];
|
||||
|
||||
int newIndex = 0;
|
||||
|
||||
@@ -1724,7 +1780,7 @@ class SemanticsObject {
|
||||
// The longest sub-sequence in the old list maximizes the number of children
|
||||
// that do not need to be moved.
|
||||
final List<int?> longestSequence = longestIncreasingSubsequence(intersectionIndicesOld);
|
||||
final List<int> stationaryIds = <int>[];
|
||||
final stationaryIds = <int>[];
|
||||
for (int i = 0; i < longestSequence.length; i += 1) {
|
||||
stationaryIds.add(
|
||||
previousChildrenInRenderOrder[intersectionIndicesOld[longestSequence[i]!]].id,
|
||||
@@ -2551,6 +2607,7 @@ class EngineSemanticsOwner {
|
||||
SemanticsUpdatePhase _phase = SemanticsUpdatePhase.idle;
|
||||
|
||||
final Map<int, SemanticsObject> _semanticsTree = <int, SemanticsObject>{};
|
||||
final Map<String, int> identifiersToIds = <String, int>{};
|
||||
|
||||
/// Map [SemanticsObject.id] to parent [SemanticsObject] it was attached to
|
||||
/// this frame.
|
||||
@@ -2851,8 +2908,8 @@ AFTER: $description
|
||||
/// Complexity: n*log(n)
|
||||
List<int> longestIncreasingSubsequence(List<int> list) {
|
||||
final int len = list.length;
|
||||
final List<int> predecessors = <int>[];
|
||||
final List<int> mins = <int>[0];
|
||||
final predecessors = <int>[];
|
||||
final mins = <int>[0];
|
||||
int longest = 0;
|
||||
for (int i = 0; i < len; i++) {
|
||||
// Binary search for the largest positive `j ≤ longest`
|
||||
@@ -2885,7 +2942,7 @@ List<int> longestIncreasingSubsequence(List<int> list) {
|
||||
}
|
||||
}
|
||||
// Reconstruct the longest subsequence
|
||||
final List<int> seq = List<int>.filled(longest, 0);
|
||||
final seq = List<int>.filled(longest, 0);
|
||||
int k = mins[longest];
|
||||
for (int i = longest - 1; i >= 0; i--) {
|
||||
seq[i] = k;
|
||||
|
||||
@@ -551,6 +551,57 @@ bool listEquals<T>(List<T>? a, List<T>? b) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Determines if lists [a] and [b] are deep equivalent, regardless of their
|
||||
/// order.
|
||||
///
|
||||
/// Returns true if the lists are both null, or if they are both non-null, have
|
||||
/// the same length, and contain the same elements regardless of their order.
|
||||
/// Returns false otherwise.
|
||||
bool unorderedListEqual<T>(List<T>? a, List<T>? b) {
|
||||
if (a == b) {
|
||||
return true;
|
||||
}
|
||||
if ((a?.isEmpty ?? true) && (b?.isEmpty ?? true)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((a == null) != (b == null)) {
|
||||
return false;
|
||||
}
|
||||
// They most both be non-null now, and at least one of them is not empty.
|
||||
if (a!.length != b!.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (a.length == 1) {
|
||||
return a.first == b.first;
|
||||
}
|
||||
|
||||
if (a.length == 2) {
|
||||
return (a.first == b.first && a.last == b.last) || (a.last == b.first && a.first == b.last);
|
||||
}
|
||||
|
||||
// Complex cases.
|
||||
final Map<T, int> wordCounts = <T, int>{};
|
||||
for (final T word in a) {
|
||||
final int count = wordCounts[word] ?? 0;
|
||||
wordCounts[word] = count + 1;
|
||||
}
|
||||
|
||||
for (final T otherWord in b) {
|
||||
final int? count = wordCounts[otherWord];
|
||||
if (count == null || count == 0) {
|
||||
return false;
|
||||
}
|
||||
if (count == 1) {
|
||||
wordCounts.remove(otherWord);
|
||||
} else {
|
||||
wordCounts[otherWord] = count - 1;
|
||||
}
|
||||
}
|
||||
return wordCounts.isEmpty;
|
||||
}
|
||||
|
||||
// HTML only supports a single radius, but Flutter ImageFilter supports separate
|
||||
// horizontal and vertical radii. The best approximation we can provide is to
|
||||
// average the two radii together for a single compromise value.
|
||||
|
||||
@@ -129,6 +129,9 @@ void runSemanticsTests() {
|
||||
group('table', () {
|
||||
_testTables();
|
||||
});
|
||||
group('controlsNodes', () {
|
||||
_testControlsNodes();
|
||||
});
|
||||
}
|
||||
|
||||
void _testSemanticRole() {
|
||||
@@ -4038,6 +4041,97 @@ void _testTables() {
|
||||
semantics().semanticsEnabled = false;
|
||||
}
|
||||
|
||||
void _testControlsNodes() {
|
||||
test('can have multiple controlled nodes', () {
|
||||
semantics()
|
||||
..debugOverrideTimestampFunction(() => _testTime)
|
||||
..semanticsEnabled = true;
|
||||
|
||||
final SemanticsTester tester = SemanticsTester(owner());
|
||||
tester.updateNode(
|
||||
id: 0,
|
||||
controlsNodes: <String>['a'],
|
||||
rect: const ui.Rect.fromLTRB(0, 0, 100, 50),
|
||||
children: <SemanticsNodeUpdate>[
|
||||
tester.updateNode(id: 1, identifier: 'a'),
|
||||
tester.updateNode(id: 2, identifier: 'b'),
|
||||
],
|
||||
);
|
||||
tester.apply();
|
||||
|
||||
SemanticsObject object = tester.getSemanticsObject(0);
|
||||
expect(object.element.getAttribute('aria-controls'), 'flt-semantic-node-1');
|
||||
|
||||
tester.updateNode(
|
||||
id: 0,
|
||||
controlsNodes: <String>['b'],
|
||||
rect: const ui.Rect.fromLTRB(0, 0, 100, 50),
|
||||
children: <SemanticsNodeUpdate>[
|
||||
tester.updateNode(id: 1, identifier: 'a'),
|
||||
tester.updateNode(id: 2, identifier: 'b'),
|
||||
],
|
||||
);
|
||||
tester.apply();
|
||||
|
||||
object = tester.getSemanticsObject(0);
|
||||
expect(object.element.getAttribute('aria-controls'), 'flt-semantic-node-2');
|
||||
|
||||
tester.updateNode(
|
||||
id: 0,
|
||||
controlsNodes: <String>['a', 'b'],
|
||||
rect: const ui.Rect.fromLTRB(0, 0, 100, 50),
|
||||
children: <SemanticsNodeUpdate>[
|
||||
tester.updateNode(id: 1, identifier: 'a'),
|
||||
tester.updateNode(id: 2, identifier: 'b'),
|
||||
],
|
||||
);
|
||||
tester.apply();
|
||||
|
||||
object = tester.getSemanticsObject(0);
|
||||
expect(object.element.getAttribute('aria-controls'), 'flt-semantic-node-1 flt-semantic-node-2');
|
||||
|
||||
tester.updateNode(
|
||||
id: 0,
|
||||
controlsNodes: <String>['a', 'b', 'c'],
|
||||
rect: const ui.Rect.fromLTRB(0, 0, 100, 50),
|
||||
children: <SemanticsNodeUpdate>[
|
||||
tester.updateNode(id: 1, identifier: 'a'),
|
||||
tester.updateNode(id: 2, identifier: 'b'),
|
||||
tester.updateNode(id: 3, identifier: 'c'),
|
||||
tester.updateNode(id: 4, identifier: 'd'),
|
||||
],
|
||||
);
|
||||
tester.apply();
|
||||
|
||||
object = tester.getSemanticsObject(0);
|
||||
expect(
|
||||
object.element.getAttribute('aria-controls'),
|
||||
'flt-semantic-node-1 flt-semantic-node-2 flt-semantic-node-3',
|
||||
);
|
||||
|
||||
tester.updateNode(
|
||||
id: 0,
|
||||
controlsNodes: <String>['a', 'b', 'd'],
|
||||
rect: const ui.Rect.fromLTRB(0, 0, 100, 50),
|
||||
children: <SemanticsNodeUpdate>[
|
||||
tester.updateNode(id: 1, identifier: 'a'),
|
||||
tester.updateNode(id: 2, identifier: 'b'),
|
||||
tester.updateNode(id: 3, identifier: 'c'),
|
||||
tester.updateNode(id: 4, identifier: 'd'),
|
||||
],
|
||||
);
|
||||
tester.apply();
|
||||
|
||||
object = tester.getSemanticsObject(0);
|
||||
expect(
|
||||
object.element.getAttribute('aria-controls'),
|
||||
'flt-semantic-node-1 flt-semantic-node-2 flt-semantic-node-4',
|
||||
);
|
||||
});
|
||||
|
||||
semantics().semanticsEnabled = false;
|
||||
}
|
||||
|
||||
/// A facade in front of [ui.SemanticsUpdateBuilder.updateNode] that
|
||||
/// supplies default values for semantics attributes.
|
||||
void updateNode(
|
||||
@@ -4077,6 +4171,7 @@ void updateNode(
|
||||
Int32List? additionalActions,
|
||||
int headingLevel = 0,
|
||||
String? linkUrl,
|
||||
List<String>? controlsNodes,
|
||||
}) {
|
||||
transform ??= Float64List.fromList(Matrix4.identity().storage);
|
||||
childrenInTraversalOrder ??= Int32List(0);
|
||||
@@ -4118,6 +4213,7 @@ void updateNode(
|
||||
additionalActions: additionalActions,
|
||||
headingLevel: headingLevel,
|
||||
linkUrl: linkUrl,
|
||||
controlsNodes: controlsNodes,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -118,6 +118,7 @@ class SemanticsTester {
|
||||
int? headingLevel,
|
||||
String? linkUrl,
|
||||
ui.SemanticsRole? role,
|
||||
List<String>? controlsNodes,
|
||||
}) {
|
||||
// Flags
|
||||
if (hasCheckedState ?? false) {
|
||||
@@ -333,6 +334,7 @@ class SemanticsTester {
|
||||
headingLevel: headingLevel ?? 0,
|
||||
linkUrl: linkUrl,
|
||||
role: role ?? ui.SemanticsRole.none,
|
||||
controlsNodes: controlsNodes,
|
||||
);
|
||||
_nodeUpdates.add(update);
|
||||
return update;
|
||||
|
||||
@@ -154,4 +154,22 @@ void testMain() {
|
||||
expect('$exception', contains('operation failed'));
|
||||
}
|
||||
});
|
||||
|
||||
test('unordered list equality', () {
|
||||
expect(unorderedListEqual<int>(null, null), isTrue);
|
||||
expect(unorderedListEqual<int>(<int>[], null), isTrue);
|
||||
expect(unorderedListEqual<int>(<int>[], <int>[]), isTrue);
|
||||
expect(unorderedListEqual<int>(<int>[1], <int>[1]), isTrue);
|
||||
expect(unorderedListEqual<int>(<int>[1, 2], <int>[1, 2]), isTrue);
|
||||
expect(unorderedListEqual<int>(<int>[2, 1], <int>[1, 2]), isTrue);
|
||||
expect(unorderedListEqual<int>(<int>[2, 1, 3], <int>[3, 1, 2]), isTrue);
|
||||
|
||||
expect(unorderedListEqual<int>(<int>[1], null), isFalse);
|
||||
expect(unorderedListEqual<int>(<int>[1], <int>[2]), isFalse);
|
||||
expect(unorderedListEqual<int>(<int>[1, 2], <int>[2, 2]), isFalse);
|
||||
expect(unorderedListEqual<int>(<int>[1, 2], <int>[2, 3]), isFalse);
|
||||
expect(unorderedListEqual<int>(<int>[1, 2], <int>[3, 4]), isFalse);
|
||||
expect(unorderedListEqual<int>(<int>[2, 1, 3], <int>[3, 1, 1]), isFalse);
|
||||
expect(unorderedListEqual<int>(<int>[1, 1, 2], <int>[3, 1, 1]), isFalse);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -198,6 +198,7 @@ Future<void> a11y_main() async {
|
||||
tooltip: 'tooltip',
|
||||
textDirection: TextDirection.ltr,
|
||||
additionalActions: Int32List(0),
|
||||
controlsNodes: null,
|
||||
)
|
||||
..updateNode(
|
||||
id: 84,
|
||||
@@ -233,6 +234,7 @@ Future<void> a11y_main() async {
|
||||
additionalActions: Int32List(0),
|
||||
childrenInHitTestOrder: Int32List(0),
|
||||
childrenInTraversalOrder: Int32List(0),
|
||||
controlsNodes: null,
|
||||
)
|
||||
..updateNode(
|
||||
id: 96,
|
||||
@@ -268,6 +270,7 @@ Future<void> a11y_main() async {
|
||||
tooltip: 'tooltip',
|
||||
textDirection: TextDirection.ltr,
|
||||
additionalActions: Int32List(0),
|
||||
controlsNodes: null,
|
||||
)
|
||||
..updateNode(
|
||||
id: 128,
|
||||
@@ -303,6 +306,7 @@ Future<void> a11y_main() async {
|
||||
textDirection: TextDirection.ltr,
|
||||
childrenInHitTestOrder: Int32List(0),
|
||||
childrenInTraversalOrder: Int32List(0),
|
||||
controlsNodes: null,
|
||||
)
|
||||
..updateCustomAction(id: 21, label: 'Archive', hint: 'archive message');
|
||||
|
||||
@@ -390,6 +394,7 @@ Future<void> a11y_string_attributes() async {
|
||||
tooltip: 'tooltip',
|
||||
textDirection: TextDirection.ltr,
|
||||
additionalActions: Int32List(0),
|
||||
controlsNodes: null,
|
||||
);
|
||||
|
||||
PlatformDispatcher.instance.views.first.updateSemantics(builder.build());
|
||||
|
||||
@@ -75,6 +75,7 @@ class LocaleInitialization extends Scenario {
|
||||
childrenInTraversalOrder: Int32List(0),
|
||||
childrenInHitTestOrder: Int32List(0),
|
||||
additionalActions: Int32List(0),
|
||||
controlsNodes: null,
|
||||
);
|
||||
|
||||
final SemanticsUpdate semanticsUpdate = semanticsUpdateBuilder.build();
|
||||
@@ -135,6 +136,7 @@ class LocaleInitialization extends Scenario {
|
||||
childrenInTraversalOrder: Int32List(0),
|
||||
childrenInHitTestOrder: Int32List(0),
|
||||
additionalActions: Int32List(0),
|
||||
controlsNodes: null,
|
||||
);
|
||||
|
||||
final SemanticsUpdate semanticsUpdate = semanticsUpdateBuilder.build();
|
||||
|
||||
Reference in New Issue
Block a user