add semantics role and tab (#161260)
fixes https://github.com/flutter/flutter/issues/157134 ## 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
50f7120de5
commit
6b8b57913d
@ -43284,6 +43284,7 @@ ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/route.dart + ../../
|
|||||||
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/scrollable.dart + ../../../flutter/LICENSE
|
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/scrollable.dart + ../../../flutter/LICENSE
|
||||||
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/semantics.dart + ../../../flutter/LICENSE
|
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/semantics.dart + ../../../flutter/LICENSE
|
||||||
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/semantics_helper.dart + ../../../flutter/LICENSE
|
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/semantics_helper.dart + ../../../flutter/LICENSE
|
||||||
|
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/tabs.dart + ../../../flutter/LICENSE
|
||||||
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/tappable.dart + ../../../flutter/LICENSE
|
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/tappable.dart + ../../../flutter/LICENSE
|
||||||
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/text_field.dart + ../../../flutter/LICENSE
|
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/text_field.dart + ../../../flutter/LICENSE
|
||||||
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/services.dart + ../../../flutter/LICENSE
|
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/services.dart + ../../../flutter/LICENSE
|
||||||
@ -46226,6 +46227,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/route.dart
|
|||||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/scrollable.dart
|
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/scrollable.dart
|
||||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/semantics.dart
|
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/semantics.dart
|
||||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/semantics_helper.dart
|
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/semantics_helper.dart
|
||||||
|
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/tabs.dart
|
||||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/tappable.dart
|
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/tappable.dart
|
||||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/text_field.dart
|
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/text_field.dart
|
||||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/services.dart
|
FILE: ../../../flutter/lib/web_ui/lib/src/engine/services.dart
|
||||||
|
@ -238,6 +238,59 @@ void sendSemanticsUpdate() {
|
|||||||
_semanticsUpdate(builder.build());
|
_semanticsUpdate(builder.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@pragma('vm:entry-point')
|
||||||
|
void sendSemanticsUpdateWithRole() {
|
||||||
|
final SemanticsUpdateBuilder builder = SemanticsUpdateBuilder();
|
||||||
|
|
||||||
|
final Float64List transform = Float64List(16);
|
||||||
|
final Int32List childrenInTraversalOrder = Int32List(0);
|
||||||
|
final Int32List childrenInHitTestOrder = Int32List(0);
|
||||||
|
final Int32List additionalActions = Int32List(0);
|
||||||
|
// Identity matrix 4x4.
|
||||||
|
transform[0] = 1;
|
||||||
|
transform[5] = 1;
|
||||||
|
transform[10] = 1;
|
||||||
|
builder.updateNode(
|
||||||
|
id: 0,
|
||||||
|
flags: 0,
|
||||||
|
actions: 0,
|
||||||
|
maxValueLength: 0,
|
||||||
|
currentValueLength: 0,
|
||||||
|
textSelectionBase: -1,
|
||||||
|
textSelectionExtent: -1,
|
||||||
|
platformViewId: -1,
|
||||||
|
scrollChildren: 0,
|
||||||
|
scrollIndex: 0,
|
||||||
|
scrollPosition: 0,
|
||||||
|
scrollExtentMax: 0,
|
||||||
|
scrollExtentMin: 0,
|
||||||
|
rect: Rect.fromLTRB(0, 0, 10, 10),
|
||||||
|
elevation: 0,
|
||||||
|
thickness: 0,
|
||||||
|
identifier: "identifier",
|
||||||
|
label: "label",
|
||||||
|
labelAttributes: const <StringAttribute>[],
|
||||||
|
value: "value",
|
||||||
|
valueAttributes: const <StringAttribute>[],
|
||||||
|
increasedValue: "increasedValue",
|
||||||
|
increasedValueAttributes: const <StringAttribute>[],
|
||||||
|
decreasedValue: "decreasedValue",
|
||||||
|
decreasedValueAttributes: const <StringAttribute>[],
|
||||||
|
hint: "hint",
|
||||||
|
hintAttributes: const <StringAttribute>[],
|
||||||
|
tooltip: "tooltip",
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
transform: transform,
|
||||||
|
childrenInTraversalOrder: childrenInTraversalOrder,
|
||||||
|
childrenInHitTestOrder: childrenInHitTestOrder,
|
||||||
|
additionalActions: additionalActions,
|
||||||
|
headingLevel: 0,
|
||||||
|
linkUrl: '',
|
||||||
|
role: SemanticsRole.tab,
|
||||||
|
);
|
||||||
|
_semanticsUpdate(builder.build());
|
||||||
|
}
|
||||||
|
|
||||||
@pragma('vm:external-name', 'SemanticsUpdate')
|
@pragma('vm:external-name', 'SemanticsUpdate')
|
||||||
external void _semanticsUpdate(SemanticsUpdate update);
|
external void _semanticsUpdate(SemanticsUpdate update);
|
||||||
|
|
||||||
|
@ -339,6 +339,31 @@ class SemanticsAction {
|
|||||||
String toString() => 'SemanticsAction.$name';
|
String toString() => 'SemanticsAction.$name';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An enum to describe the role for a semantics node.
|
||||||
|
///
|
||||||
|
/// The roles are translated into native accessibility roles in each platform.
|
||||||
|
enum SemanticsRole {
|
||||||
|
/// Does not represent any role.
|
||||||
|
none,
|
||||||
|
|
||||||
|
/// A tab button.
|
||||||
|
///
|
||||||
|
/// see also:
|
||||||
|
///
|
||||||
|
/// * [tabBar], which is the role for containers of tab buttons.
|
||||||
|
tab,
|
||||||
|
|
||||||
|
/// Contains tab buttons.
|
||||||
|
///
|
||||||
|
/// see also:
|
||||||
|
///
|
||||||
|
/// * [tab], which is the role for tab buttons.
|
||||||
|
tabBar,
|
||||||
|
|
||||||
|
/// The main display for a tab.
|
||||||
|
tabPanel,
|
||||||
|
}
|
||||||
|
|
||||||
/// A Boolean value that can be associated with a semantics node.
|
/// A Boolean value that can be associated with a semantics node.
|
||||||
//
|
//
|
||||||
// When changes are made to this class, the equivalent APIs in
|
// When changes are made to this class, the equivalent APIs in
|
||||||
@ -960,6 +985,9 @@ abstract class SemanticsUpdateBuilder {
|
|||||||
/// The `linkUrl` describes the URI that this node links to. If the node is
|
/// The `linkUrl` describes the URI that this node links to. If the node is
|
||||||
/// not a link, this should be an empty string.
|
/// not a link, this should be an empty string.
|
||||||
///
|
///
|
||||||
|
/// The `role` describes the role of this node. Defaults to
|
||||||
|
/// [SemanticsRole.none] if not set.
|
||||||
|
///
|
||||||
/// See also:
|
/// See also:
|
||||||
///
|
///
|
||||||
/// * https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/heading_role
|
/// * https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/heading_role
|
||||||
@ -1000,6 +1028,7 @@ abstract class SemanticsUpdateBuilder {
|
|||||||
required Int32List additionalActions,
|
required Int32List additionalActions,
|
||||||
int headingLevel = 0,
|
int headingLevel = 0,
|
||||||
String linkUrl = '',
|
String linkUrl = '',
|
||||||
|
SemanticsRole role = SemanticsRole.none,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Update the custom semantics action associated with the given `id`.
|
/// Update the custom semantics action associated with the given `id`.
|
||||||
@ -1075,6 +1104,7 @@ base class _NativeSemanticsUpdateBuilder extends NativeFieldWrapperClass1
|
|||||||
required Int32List additionalActions,
|
required Int32List additionalActions,
|
||||||
int headingLevel = 0,
|
int headingLevel = 0,
|
||||||
String linkUrl = '',
|
String linkUrl = '',
|
||||||
|
SemanticsRole role = SemanticsRole.none,
|
||||||
}) {
|
}) {
|
||||||
assert(_matrix4IsValid(transform));
|
assert(_matrix4IsValid(transform));
|
||||||
assert(
|
assert(
|
||||||
@ -1120,6 +1150,7 @@ base class _NativeSemanticsUpdateBuilder extends NativeFieldWrapperClass1
|
|||||||
additionalActions,
|
additionalActions,
|
||||||
headingLevel,
|
headingLevel,
|
||||||
linkUrl,
|
linkUrl,
|
||||||
|
role.index,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1164,6 +1195,7 @@ base class _NativeSemanticsUpdateBuilder extends NativeFieldWrapperClass1
|
|||||||
Handle,
|
Handle,
|
||||||
Int32,
|
Int32,
|
||||||
Handle,
|
Handle,
|
||||||
|
Int32,
|
||||||
)
|
)
|
||||||
>(symbol: 'SemanticsUpdateBuilder::updateNode')
|
>(symbol: 'SemanticsUpdateBuilder::updateNode')
|
||||||
external void _updateNode(
|
external void _updateNode(
|
||||||
@ -1205,6 +1237,7 @@ base class _NativeSemanticsUpdateBuilder extends NativeFieldWrapperClass1
|
|||||||
Int32List additionalActions,
|
Int32List additionalActions,
|
||||||
int headingLevel,
|
int headingLevel,
|
||||||
String linkUrl,
|
String linkUrl,
|
||||||
|
int role,
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -57,6 +57,19 @@ const int kHorizontalScrollSemanticsActions =
|
|||||||
const int kScrollableSemanticsActions =
|
const int kScrollableSemanticsActions =
|
||||||
kVerticalScrollSemanticsActions | kHorizontalScrollSemanticsActions;
|
kVerticalScrollSemanticsActions | kHorizontalScrollSemanticsActions;
|
||||||
|
|
||||||
|
/// C/C++ representation of `SemanticsRole` defined in
|
||||||
|
/// `lib/ui/semantics.dart`.
|
||||||
|
///\warning This must match the `SemanticsRole` enum in
|
||||||
|
/// `lib/ui/semantics.dart`.
|
||||||
|
/// See also:
|
||||||
|
/// - file://./../../../lib/ui/semantics.dart
|
||||||
|
enum class SemanticsRole : int32_t {
|
||||||
|
kNone = 0,
|
||||||
|
kTab = 1,
|
||||||
|
kTabBar = 2,
|
||||||
|
kTabPanel = 3,
|
||||||
|
};
|
||||||
|
|
||||||
/// C/C++ representation of `SemanticsFlags` defined in
|
/// C/C++ representation of `SemanticsFlags` defined in
|
||||||
/// `lib/ui/semantics.dart`.
|
/// `lib/ui/semantics.dart`.
|
||||||
///\warning This must match the `SemanticsFlags` enum in
|
///\warning This must match the `SemanticsFlags` enum in
|
||||||
@ -148,6 +161,7 @@ struct SemanticsNode {
|
|||||||
int32_t headingLevel = 0;
|
int32_t headingLevel = 0;
|
||||||
|
|
||||||
std::string linkUrl;
|
std::string linkUrl;
|
||||||
|
SemanticsRole role;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Contains semantic nodes that need to be updated.
|
// Contains semantic nodes that need to be updated.
|
||||||
|
@ -68,7 +68,8 @@ void SemanticsUpdateBuilder::updateNode(
|
|||||||
const tonic::Int32List& childrenInHitTestOrder,
|
const tonic::Int32List& childrenInHitTestOrder,
|
||||||
const tonic::Int32List& localContextActions,
|
const tonic::Int32List& localContextActions,
|
||||||
int headingLevel,
|
int headingLevel,
|
||||||
std::string linkUrl) {
|
std::string linkUrl,
|
||||||
|
int role) {
|
||||||
FML_CHECK(scrollChildren == 0 ||
|
FML_CHECK(scrollChildren == 0 ||
|
||||||
(scrollChildren > 0 && childrenInHitTestOrder.data()))
|
(scrollChildren > 0 && childrenInHitTestOrder.data()))
|
||||||
<< "Semantics update contained scrollChildren but did not have "
|
<< "Semantics update contained scrollChildren but did not have "
|
||||||
@ -119,10 +120,11 @@ void SemanticsUpdateBuilder::updateNode(
|
|||||||
node.customAccessibilityActions = std::vector<int32_t>(
|
node.customAccessibilityActions = std::vector<int32_t>(
|
||||||
localContextActions.data(),
|
localContextActions.data(),
|
||||||
localContextActions.data() + localContextActions.num_elements());
|
localContextActions.data() + localContextActions.num_elements());
|
||||||
nodes_[id] = node;
|
|
||||||
|
|
||||||
node.headingLevel = headingLevel;
|
node.headingLevel = headingLevel;
|
||||||
node.linkUrl = std::move(linkUrl);
|
node.linkUrl = std::move(linkUrl);
|
||||||
|
node.role = static_cast<SemanticsRole>(role);
|
||||||
|
|
||||||
|
nodes_[id] = node;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SemanticsUpdateBuilder::updateCustomAction(int id,
|
void SemanticsUpdateBuilder::updateCustomAction(int id,
|
||||||
|
@ -67,7 +67,8 @@ class SemanticsUpdateBuilder
|
|||||||
const tonic::Int32List& childrenInHitTestOrder,
|
const tonic::Int32List& childrenInHitTestOrder,
|
||||||
const tonic::Int32List& customAccessibilityActions,
|
const tonic::Int32List& customAccessibilityActions,
|
||||||
int headingLevel,
|
int headingLevel,
|
||||||
std::string linkUrl);
|
std::string linkUrl,
|
||||||
|
int role);
|
||||||
|
|
||||||
void updateCustomAction(int id,
|
void updateCustomAction(int id,
|
||||||
std::string label,
|
std::string label,
|
||||||
|
@ -88,5 +88,48 @@ TEST_F(SemanticsUpdateBuilderTest, CanHandleAttributedStrings) {
|
|||||||
DestroyShell(std::move(shell), task_runners);
|
DestroyShell(std::move(shell), task_runners);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(SemanticsUpdateBuilderTest, CanHandleSemanticsRole) {
|
||||||
|
auto message_latch = std::make_shared<fml::AutoResetWaitableEvent>();
|
||||||
|
|
||||||
|
auto nativeSemanticsUpdate = [message_latch](Dart_NativeArguments args) {
|
||||||
|
auto handle = Dart_GetNativeArgument(args, 0);
|
||||||
|
intptr_t peer = 0;
|
||||||
|
Dart_Handle result = Dart_GetNativeInstanceField(
|
||||||
|
handle, tonic::DartWrappable::kPeerIndex, &peer);
|
||||||
|
ASSERT_FALSE(Dart_IsError(result));
|
||||||
|
SemanticsUpdate* update = reinterpret_cast<SemanticsUpdate*>(peer);
|
||||||
|
SemanticsNodeUpdates nodes = update->takeNodes();
|
||||||
|
ASSERT_EQ(nodes.size(), (size_t)1);
|
||||||
|
auto node = nodes.find(0)->second;
|
||||||
|
// Should match the updateNode in ui_test.dart.
|
||||||
|
ASSERT_EQ(node.role, SemanticsRole::kTab);
|
||||||
|
message_latch->Signal();
|
||||||
|
};
|
||||||
|
|
||||||
|
Settings settings = CreateSettingsForFixture();
|
||||||
|
TaskRunners task_runners("test", // label
|
||||||
|
GetCurrentTaskRunner(), // platform
|
||||||
|
CreateNewThread(), // raster
|
||||||
|
CreateNewThread(), // ui
|
||||||
|
CreateNewThread() // io
|
||||||
|
);
|
||||||
|
|
||||||
|
AddNativeCallback("SemanticsUpdate",
|
||||||
|
CREATE_NATIVE_ENTRY(nativeSemanticsUpdate));
|
||||||
|
|
||||||
|
std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
|
||||||
|
|
||||||
|
ASSERT_TRUE(shell->IsSetup());
|
||||||
|
auto configuration = RunConfiguration::InferFromSettings(settings);
|
||||||
|
configuration.SetEntrypoint("sendSemanticsUpdateWithRole");
|
||||||
|
|
||||||
|
shell->RunEngine(std::move(configuration), [](auto result) {
|
||||||
|
ASSERT_EQ(result, Engine::RunStatus::Success);
|
||||||
|
});
|
||||||
|
|
||||||
|
message_latch->Wait();
|
||||||
|
DestroyShell(std::move(shell), task_runners);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace testing
|
} // namespace testing
|
||||||
} // namespace flutter
|
} // namespace flutter
|
||||||
|
@ -255,6 +255,9 @@ class SemanticsFlag {
|
|||||||
String toString() => 'SemanticsFlag.$name';
|
String toString() => 'SemanticsFlag.$name';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mirrors engine/src/flutter/lib/ui/semantics.dart
|
||||||
|
enum SemanticsRole { none, tab, tabBar, tabPanel }
|
||||||
|
|
||||||
// When adding a new StringAttributeType, the classes in these file must be
|
// When adding a new StringAttributeType, the classes in these file must be
|
||||||
// updated as well.
|
// updated as well.
|
||||||
// * engine/src/flutter/lib/ui/semantics.dart
|
// * engine/src/flutter/lib/ui/semantics.dart
|
||||||
@ -341,6 +344,7 @@ class SemanticsUpdateBuilder {
|
|||||||
required Int32List additionalActions,
|
required Int32List additionalActions,
|
||||||
int headingLevel = 0,
|
int headingLevel = 0,
|
||||||
String? linkUrl,
|
String? linkUrl,
|
||||||
|
SemanticsRole role = SemanticsRole.none,
|
||||||
}) {
|
}) {
|
||||||
if (transform.length != 16) {
|
if (transform.length != 16) {
|
||||||
throw ArgumentError('transform argument must have 16 entries.');
|
throw ArgumentError('transform argument must have 16 entries.');
|
||||||
@ -382,6 +386,7 @@ class SemanticsUpdateBuilder {
|
|||||||
platformViewId: platformViewId,
|
platformViewId: platformViewId,
|
||||||
headingLevel: headingLevel,
|
headingLevel: headingLevel,
|
||||||
linkUrl: linkUrl,
|
linkUrl: linkUrl,
|
||||||
|
role: role,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -159,6 +159,7 @@ export 'engine/semantics/route.dart';
|
|||||||
export 'engine/semantics/scrollable.dart';
|
export 'engine/semantics/scrollable.dart';
|
||||||
export 'engine/semantics/semantics.dart';
|
export 'engine/semantics/semantics.dart';
|
||||||
export 'engine/semantics/semantics_helper.dart';
|
export 'engine/semantics/semantics_helper.dart';
|
||||||
|
export 'engine/semantics/tabs.dart';
|
||||||
export 'engine/semantics/tappable.dart';
|
export 'engine/semantics/tappable.dart';
|
||||||
export 'engine/semantics/text_field.dart';
|
export 'engine/semantics/text_field.dart';
|
||||||
export 'engine/services/buffers.dart';
|
export 'engine/services/buffers.dart';
|
||||||
|
@ -16,5 +16,6 @@ export 'semantics/platform_view.dart';
|
|||||||
export 'semantics/scrollable.dart';
|
export 'semantics/scrollable.dart';
|
||||||
export 'semantics/semantics.dart';
|
export 'semantics/semantics.dart';
|
||||||
export 'semantics/semantics_helper.dart';
|
export 'semantics/semantics_helper.dart';
|
||||||
|
export 'semantics/tabs.dart';
|
||||||
export 'semantics/tappable.dart';
|
export 'semantics/tappable.dart';
|
||||||
export 'semantics/text_field.dart';
|
export 'semantics/text_field.dart';
|
||||||
|
@ -55,7 +55,7 @@ class SemanticCheckable extends SemanticRole {
|
|||||||
SemanticCheckable(SemanticsObject semanticsObject)
|
SemanticCheckable(SemanticsObject semanticsObject)
|
||||||
: _kind = _checkableKindFromSemanticsFlag(semanticsObject),
|
: _kind = _checkableKindFromSemanticsFlag(semanticsObject),
|
||||||
super.withBasics(
|
super.withBasics(
|
||||||
SemanticRoleKind.checkable,
|
EngineSemanticsRole.checkable,
|
||||||
semanticsObject,
|
semanticsObject,
|
||||||
preferredLabelRepresentation: LabelRepresentation.ariaLabel,
|
preferredLabelRepresentation: LabelRepresentation.ariaLabel,
|
||||||
) {
|
) {
|
||||||
|
@ -19,7 +19,7 @@ import 'semantics.dart';
|
|||||||
class SemanticHeader extends SemanticRole {
|
class SemanticHeader extends SemanticRole {
|
||||||
SemanticHeader(SemanticsObject semanticsObject)
|
SemanticHeader(SemanticsObject semanticsObject)
|
||||||
: super.withBasics(
|
: super.withBasics(
|
||||||
SemanticRoleKind.header,
|
EngineSemanticsRole.header,
|
||||||
semanticsObject,
|
semanticsObject,
|
||||||
|
|
||||||
// Why use sizedSpan?
|
// Why use sizedSpan?
|
||||||
|
@ -10,7 +10,7 @@ import 'semantics.dart';
|
|||||||
/// level (h1 ... h6).
|
/// level (h1 ... h6).
|
||||||
class SemanticHeading extends SemanticRole {
|
class SemanticHeading extends SemanticRole {
|
||||||
SemanticHeading(SemanticsObject semanticsObject)
|
SemanticHeading(SemanticsObject semanticsObject)
|
||||||
: super.blank(SemanticRoleKind.heading, semanticsObject) {
|
: super.blank(EngineSemanticsRole.heading, semanticsObject) {
|
||||||
addFocusManagement();
|
addFocusManagement();
|
||||||
addLiveRegion();
|
addLiveRegion();
|
||||||
addRouteName();
|
addRouteName();
|
||||||
|
@ -12,7 +12,7 @@ import 'semantics.dart';
|
|||||||
/// Screen-readers takes advantage of "aria-label" to describe the visual.
|
/// Screen-readers takes advantage of "aria-label" to describe the visual.
|
||||||
class SemanticImage extends SemanticRole {
|
class SemanticImage extends SemanticRole {
|
||||||
SemanticImage(SemanticsObject semanticsObject)
|
SemanticImage(SemanticsObject semanticsObject)
|
||||||
: super.blank(SemanticRoleKind.image, semanticsObject) {
|
: super.blank(EngineSemanticsRole.image, semanticsObject) {
|
||||||
// The following behaviors can coexist with images. `LabelAndValue` is
|
// The following behaviors can coexist with images. `LabelAndValue` is
|
||||||
// not used because this behavior uses special auxiliary elements to
|
// not used because this behavior uses special auxiliary elements to
|
||||||
// supply ARIA labels.
|
// supply ARIA labels.
|
||||||
|
@ -22,7 +22,7 @@ import 'semantics.dart';
|
|||||||
class SemanticIncrementable extends SemanticRole {
|
class SemanticIncrementable extends SemanticRole {
|
||||||
SemanticIncrementable(SemanticsObject semanticsObject)
|
SemanticIncrementable(SemanticsObject semanticsObject)
|
||||||
: _focusManager = AccessibilityFocusManager(semanticsObject.owner),
|
: _focusManager = AccessibilityFocusManager(semanticsObject.owner),
|
||||||
super.blank(SemanticRoleKind.incrementable, semanticsObject) {
|
super.blank(EngineSemanticsRole.incrementable, semanticsObject) {
|
||||||
// The following generic roles can coexist with incrementables. Generic focus
|
// The following generic roles can coexist with incrementables. Generic focus
|
||||||
// management is not used by this role because the root DOM element is not
|
// management is not used by this role because the root DOM element is not
|
||||||
// the one being focused on, but the internal `<input>` element.
|
// the one being focused on, but the internal `<input>` element.
|
||||||
|
@ -9,7 +9,7 @@ import '../semantics.dart';
|
|||||||
class SemanticLink extends SemanticRole {
|
class SemanticLink extends SemanticRole {
|
||||||
SemanticLink(SemanticsObject semanticsObject)
|
SemanticLink(SemanticsObject semanticsObject)
|
||||||
: super.withBasics(
|
: super.withBasics(
|
||||||
SemanticRoleKind.link,
|
EngineSemanticsRole.link,
|
||||||
semanticsObject,
|
semanticsObject,
|
||||||
preferredLabelRepresentation: LabelRepresentation.domText,
|
preferredLabelRepresentation: LabelRepresentation.domText,
|
||||||
) {
|
) {
|
||||||
|
@ -23,7 +23,7 @@ import 'semantics.dart';
|
|||||||
class SemanticPlatformView extends SemanticRole {
|
class SemanticPlatformView extends SemanticRole {
|
||||||
SemanticPlatformView(SemanticsObject semanticsObject)
|
SemanticPlatformView(SemanticsObject semanticsObject)
|
||||||
: super.withBasics(
|
: super.withBasics(
|
||||||
SemanticRoleKind.platformView,
|
EngineSemanticsRole.platformView,
|
||||||
semanticsObject,
|
semanticsObject,
|
||||||
preferredLabelRepresentation: LabelRepresentation.ariaLabel,
|
preferredLabelRepresentation: LabelRepresentation.ariaLabel,
|
||||||
);
|
);
|
||||||
|
@ -16,7 +16,7 @@ import '../util.dart';
|
|||||||
/// of an explicit route label set on the route itself.
|
/// of an explicit route label set on the route itself.
|
||||||
class SemanticRoute extends SemanticRole {
|
class SemanticRoute extends SemanticRole {
|
||||||
SemanticRoute(SemanticsObject semanticsObject)
|
SemanticRoute(SemanticsObject semanticsObject)
|
||||||
: super.blank(SemanticRoleKind.route, semanticsObject) {
|
: super.blank(EngineSemanticsRole.route, semanticsObject) {
|
||||||
// The following behaviors can coexist with the route. Generic `RouteName`
|
// The following behaviors can coexist with the route. Generic `RouteName`
|
||||||
// and `LabelAndValue` are not used by this role because when the route
|
// and `LabelAndValue` are not used by this role because when the route
|
||||||
// names its own route an `aria-label` is used instead of
|
// names its own route an `aria-label` is used instead of
|
||||||
@ -158,10 +158,10 @@ class RouteName extends SemanticBehavior {
|
|||||||
|
|
||||||
void _lookUpNearestAncestorRoute() {
|
void _lookUpNearestAncestorRoute() {
|
||||||
SemanticsObject? parent = semanticsObject.parent;
|
SemanticsObject? parent = semanticsObject.parent;
|
||||||
while (parent != null && parent.semanticRole?.kind != SemanticRoleKind.route) {
|
while (parent != null && parent.semanticRole?.kind != EngineSemanticsRole.route) {
|
||||||
parent = parent.parent;
|
parent = parent.parent;
|
||||||
}
|
}
|
||||||
if (parent != null && parent.semanticRole?.kind == SemanticRoleKind.route) {
|
if (parent != null && parent.semanticRole?.kind == EngineSemanticsRole.route) {
|
||||||
_route = parent.semanticRole! as SemanticRoute;
|
_route = parent.semanticRole! as SemanticRoute;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ import 'package:ui/ui.dart' as ui;
|
|||||||
class SemanticScrollable extends SemanticRole {
|
class SemanticScrollable extends SemanticRole {
|
||||||
SemanticScrollable(SemanticsObject semanticsObject)
|
SemanticScrollable(SemanticsObject semanticsObject)
|
||||||
: super.withBasics(
|
: super.withBasics(
|
||||||
SemanticRoleKind.scrollable,
|
EngineSemanticsRole.scrollable,
|
||||||
semanticsObject,
|
semanticsObject,
|
||||||
preferredLabelRepresentation: LabelRepresentation.ariaLabel,
|
preferredLabelRepresentation: LabelRepresentation.ariaLabel,
|
||||||
) {
|
) {
|
||||||
|
@ -32,6 +32,7 @@ import 'platform_view.dart';
|
|||||||
import 'route.dart';
|
import 'route.dart';
|
||||||
import 'scrollable.dart';
|
import 'scrollable.dart';
|
||||||
import 'semantics_helper.dart';
|
import 'semantics_helper.dart';
|
||||||
|
import 'tabs.dart';
|
||||||
import 'tappable.dart';
|
import 'tappable.dart';
|
||||||
import 'text_field.dart';
|
import 'text_field.dart';
|
||||||
|
|
||||||
@ -235,6 +236,7 @@ class SemanticsNodeUpdate {
|
|||||||
required this.additionalActions,
|
required this.additionalActions,
|
||||||
required this.headingLevel,
|
required this.headingLevel,
|
||||||
this.linkUrl,
|
this.linkUrl,
|
||||||
|
required this.role,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// See [ui.SemanticsUpdateBuilder.updateNode].
|
/// See [ui.SemanticsUpdateBuilder.updateNode].
|
||||||
@ -341,13 +343,16 @@ class SemanticsNodeUpdate {
|
|||||||
|
|
||||||
/// See [ui.SemanticsUpdateBuilder.updateNode].
|
/// See [ui.SemanticsUpdateBuilder.updateNode].
|
||||||
final String? linkUrl;
|
final String? linkUrl;
|
||||||
|
|
||||||
|
/// See [ui.SemanticsUpdateBuilder.updateNode].
|
||||||
|
final ui.SemanticsRole role;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Identifies [SemanticRole] implementations.
|
/// Identifies [SemanticRole] implementations.
|
||||||
///
|
///
|
||||||
/// Each value corresponds to the most specific role a semantics node plays in
|
/// Each value corresponds to the most specific role a semantics node plays in
|
||||||
/// the semantics tree.
|
/// the semantics tree.
|
||||||
enum SemanticRoleKind {
|
enum EngineSemanticsRole {
|
||||||
/// Supports incrementing and/or decrementing its value.
|
/// Supports incrementing and/or decrementing its value.
|
||||||
incrementable,
|
incrementable,
|
||||||
|
|
||||||
@ -402,6 +407,15 @@ enum SemanticRoleKind {
|
|||||||
/// Denotes a header.
|
/// Denotes a header.
|
||||||
header,
|
header,
|
||||||
|
|
||||||
|
/// An individual tab button.
|
||||||
|
tab,
|
||||||
|
|
||||||
|
/// Contains tab buttons.
|
||||||
|
tabList,
|
||||||
|
|
||||||
|
/// A main content for a tab.
|
||||||
|
tabPanel,
|
||||||
|
|
||||||
/// A role used when a more specific role cannot be assigend to
|
/// A role used when a more specific role cannot be assigend to
|
||||||
/// a [SemanticsObject].
|
/// a [SemanticsObject].
|
||||||
///
|
///
|
||||||
@ -442,7 +456,7 @@ abstract class SemanticRole {
|
|||||||
late final DomElement element;
|
late final DomElement element;
|
||||||
|
|
||||||
/// The kind of the role that this .
|
/// The kind of the role that this .
|
||||||
final SemanticRoleKind kind;
|
final EngineSemanticsRole kind;
|
||||||
|
|
||||||
/// The semantics object managed by this role.
|
/// The semantics object managed by this role.
|
||||||
final SemanticsObject semanticsObject;
|
final SemanticsObject semanticsObject;
|
||||||
@ -678,7 +692,7 @@ abstract class SemanticRole {
|
|||||||
final class GenericRole extends SemanticRole {
|
final class GenericRole extends SemanticRole {
|
||||||
GenericRole(SemanticsObject semanticsObject)
|
GenericRole(SemanticsObject semanticsObject)
|
||||||
: super.withBasics(
|
: super.withBasics(
|
||||||
SemanticRoleKind.generic,
|
EngineSemanticsRole.generic,
|
||||||
semanticsObject,
|
semanticsObject,
|
||||||
// Prefer sized span because if this is a leaf it is frequently a Text widget.
|
// Prefer sized span because if this is a leaf it is frequently a Text widget.
|
||||||
// But if it turns out to be a container, then LabelAndValue will automatically
|
// But if it turns out to be a container, then LabelAndValue will automatically
|
||||||
@ -1232,6 +1246,9 @@ class SemanticsObject {
|
|||||||
/// Controls the semantics tree that this node participates in.
|
/// Controls the semantics tree that this node participates in.
|
||||||
final EngineSemanticsOwner owner;
|
final EngineSemanticsOwner owner;
|
||||||
|
|
||||||
|
/// The role of this node.
|
||||||
|
late ui.SemanticsRole role;
|
||||||
|
|
||||||
/// Bitfield showing which fields have been updated but have not yet been
|
/// Bitfield showing which fields have been updated but have not yet been
|
||||||
/// applied to the DOM.
|
/// applied to the DOM.
|
||||||
///
|
///
|
||||||
@ -1518,6 +1535,8 @@ class SemanticsObject {
|
|||||||
_markLinkUrlDirty();
|
_markLinkUrlDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
role = update.role;
|
||||||
|
|
||||||
// Apply updates to the DOM.
|
// Apply updates to the DOM.
|
||||||
_updateRole();
|
_updateRole();
|
||||||
|
|
||||||
@ -1714,51 +1733,66 @@ class SemanticsObject {
|
|||||||
/// semantics flags and actions.
|
/// semantics flags and actions.
|
||||||
SemanticRole? semanticRole;
|
SemanticRole? semanticRole;
|
||||||
|
|
||||||
SemanticRoleKind _getSemanticRoleKind() {
|
EngineSemanticsRole _getEngineSemanticsRole() {
|
||||||
// The most specific role should take precedence.
|
// The most specific role should take precedence.
|
||||||
if (isPlatformView) {
|
if (isPlatformView) {
|
||||||
return SemanticRoleKind.platformView;
|
return EngineSemanticsRole.platformView;
|
||||||
} else if (isHeading) {
|
}
|
||||||
|
switch (role) {
|
||||||
|
case ui.SemanticsRole.tab:
|
||||||
|
return EngineSemanticsRole.tab;
|
||||||
|
case ui.SemanticsRole.tabPanel:
|
||||||
|
return EngineSemanticsRole.tabPanel;
|
||||||
|
case ui.SemanticsRole.tabBar:
|
||||||
|
return EngineSemanticsRole.tabList;
|
||||||
|
case ui.SemanticsRole.none:
|
||||||
|
// fallback to checking semantics properties.
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isHeading) {
|
||||||
// IMPORTANT: because headings also cover certain kinds of headers, the
|
// IMPORTANT: because headings also cover certain kinds of headers, the
|
||||||
// `heading` role has precedence over the `header` role.
|
// `heading` role has precedence over the `header` role.
|
||||||
return SemanticRoleKind.heading;
|
return EngineSemanticsRole.heading;
|
||||||
} else if (isTextField) {
|
} else if (isTextField) {
|
||||||
return SemanticRoleKind.textField;
|
return EngineSemanticsRole.textField;
|
||||||
} else if (isIncrementable) {
|
} else if (isIncrementable) {
|
||||||
return SemanticRoleKind.incrementable;
|
return EngineSemanticsRole.incrementable;
|
||||||
} else if (isVisualOnly) {
|
} else if (isVisualOnly) {
|
||||||
return SemanticRoleKind.image;
|
return EngineSemanticsRole.image;
|
||||||
} else if (isCheckable) {
|
} else if (isCheckable) {
|
||||||
return SemanticRoleKind.checkable;
|
return EngineSemanticsRole.checkable;
|
||||||
} else if (isButton) {
|
} else if (isButton) {
|
||||||
return SemanticRoleKind.button;
|
return EngineSemanticsRole.button;
|
||||||
} else if (isScrollContainer) {
|
} else if (isScrollContainer) {
|
||||||
return SemanticRoleKind.scrollable;
|
return EngineSemanticsRole.scrollable;
|
||||||
} else if (scopesRoute) {
|
} else if (scopesRoute) {
|
||||||
return SemanticRoleKind.route;
|
return EngineSemanticsRole.route;
|
||||||
} else if (isLink) {
|
} else if (isLink) {
|
||||||
return SemanticRoleKind.link;
|
return EngineSemanticsRole.link;
|
||||||
} else if (isHeader) {
|
} else if (isHeader) {
|
||||||
return SemanticRoleKind.header;
|
return EngineSemanticsRole.header;
|
||||||
} else {
|
} else {
|
||||||
return SemanticRoleKind.generic;
|
return EngineSemanticsRole.generic;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SemanticRole _createSemanticRole(SemanticRoleKind role) {
|
SemanticRole _createSemanticRole(EngineSemanticsRole role) {
|
||||||
return switch (role) {
|
return switch (role) {
|
||||||
SemanticRoleKind.textField => SemanticTextField(this),
|
EngineSemanticsRole.textField => SemanticTextField(this),
|
||||||
SemanticRoleKind.scrollable => SemanticScrollable(this),
|
EngineSemanticsRole.scrollable => SemanticScrollable(this),
|
||||||
SemanticRoleKind.incrementable => SemanticIncrementable(this),
|
EngineSemanticsRole.incrementable => SemanticIncrementable(this),
|
||||||
SemanticRoleKind.button => SemanticButton(this),
|
EngineSemanticsRole.button => SemanticButton(this),
|
||||||
SemanticRoleKind.checkable => SemanticCheckable(this),
|
EngineSemanticsRole.checkable => SemanticCheckable(this),
|
||||||
SemanticRoleKind.route => SemanticRoute(this),
|
EngineSemanticsRole.route => SemanticRoute(this),
|
||||||
SemanticRoleKind.image => SemanticImage(this),
|
EngineSemanticsRole.image => SemanticImage(this),
|
||||||
SemanticRoleKind.platformView => SemanticPlatformView(this),
|
EngineSemanticsRole.platformView => SemanticPlatformView(this),
|
||||||
SemanticRoleKind.link => SemanticLink(this),
|
EngineSemanticsRole.link => SemanticLink(this),
|
||||||
SemanticRoleKind.heading => SemanticHeading(this),
|
EngineSemanticsRole.heading => SemanticHeading(this),
|
||||||
SemanticRoleKind.header => SemanticHeader(this),
|
EngineSemanticsRole.header => SemanticHeader(this),
|
||||||
SemanticRoleKind.generic => GenericRole(this),
|
EngineSemanticsRole.tab => SemanticTab(this),
|
||||||
|
EngineSemanticsRole.tabList => SemanticTabList(this),
|
||||||
|
EngineSemanticsRole.tabPanel => SemanticTabPanel(this),
|
||||||
|
EngineSemanticsRole.generic => GenericRole(this),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1766,7 +1800,7 @@ class SemanticsObject {
|
|||||||
/// update the DOM.
|
/// update the DOM.
|
||||||
void _updateRole() {
|
void _updateRole() {
|
||||||
SemanticRole? currentSemanticRole = semanticRole;
|
SemanticRole? currentSemanticRole = semanticRole;
|
||||||
final SemanticRoleKind kind = _getSemanticRoleKind();
|
final EngineSemanticsRole kind = _getEngineSemanticsRole();
|
||||||
final DomElement? previousElement = semanticRole?.element;
|
final DomElement? previousElement = semanticRole?.element;
|
||||||
|
|
||||||
if (currentSemanticRole != null) {
|
if (currentSemanticRole != null) {
|
||||||
|
@ -0,0 +1,64 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
import 'label_and_value.dart';
|
||||||
|
import 'semantics.dart';
|
||||||
|
|
||||||
|
/// Indicates an interactive element inside a tablist that, when activated,
|
||||||
|
/// displays its associated tabpanel.
|
||||||
|
///
|
||||||
|
/// Uses aria tab role to convey this semantic information to the element.
|
||||||
|
///
|
||||||
|
/// Screen-readers takes advantage of "aria-label" to describe the visual.
|
||||||
|
class SemanticTab extends SemanticRole {
|
||||||
|
SemanticTab(SemanticsObject semanticsObject)
|
||||||
|
: super.withBasics(
|
||||||
|
EngineSemanticsRole.tab,
|
||||||
|
semanticsObject,
|
||||||
|
preferredLabelRepresentation: LabelRepresentation.ariaLabel,
|
||||||
|
) {
|
||||||
|
setAriaRole('tab');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool focusAsRouteDefault() => focusable?.focusAsRouteDefault() ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Indicates the main display for a tab when activated.
|
||||||
|
///
|
||||||
|
/// Uses aria tabpanel role to convey this semantic information to the element.
|
||||||
|
///
|
||||||
|
/// Screen-readers takes advantage of "aria-label" to describe the visual.
|
||||||
|
class SemanticTabPanel extends SemanticRole {
|
||||||
|
SemanticTabPanel(SemanticsObject semanticsObject)
|
||||||
|
: super.withBasics(
|
||||||
|
EngineSemanticsRole.tabPanel,
|
||||||
|
semanticsObject,
|
||||||
|
preferredLabelRepresentation: LabelRepresentation.ariaLabel,
|
||||||
|
) {
|
||||||
|
setAriaRole('tabpanel');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool focusAsRouteDefault() => focusable?.focusAsRouteDefault() ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Indicates a container that contains multiple tabs.
|
||||||
|
///
|
||||||
|
/// Uses aria tablist role to convey this semantic information to the element.
|
||||||
|
///
|
||||||
|
/// Screen-readers takes advantage of "aria-label" to describe the visual.
|
||||||
|
class SemanticTabList extends SemanticRole {
|
||||||
|
SemanticTabList(SemanticsObject semanticsObject)
|
||||||
|
: super.withBasics(
|
||||||
|
EngineSemanticsRole.tabList,
|
||||||
|
semanticsObject,
|
||||||
|
preferredLabelRepresentation: LabelRepresentation.ariaLabel,
|
||||||
|
) {
|
||||||
|
setAriaRole('tablist');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool focusAsRouteDefault() => focusable?.focusAsRouteDefault() ?? false;
|
||||||
|
}
|
@ -9,7 +9,7 @@ import 'package:ui/ui.dart' as ui;
|
|||||||
class SemanticButton extends SemanticRole {
|
class SemanticButton extends SemanticRole {
|
||||||
SemanticButton(SemanticsObject semanticsObject)
|
SemanticButton(SemanticsObject semanticsObject)
|
||||||
: super.withBasics(
|
: super.withBasics(
|
||||||
SemanticRoleKind.button,
|
EngineSemanticsRole.button,
|
||||||
semanticsObject,
|
semanticsObject,
|
||||||
preferredLabelRepresentation: LabelRepresentation.domText,
|
preferredLabelRepresentation: LabelRepresentation.domText,
|
||||||
) {
|
) {
|
||||||
|
@ -196,7 +196,7 @@ class SemanticsTextEditingStrategy extends DefaultTextEditingStrategy {
|
|||||||
/// events even when VoiceOver is enabled.
|
/// events even when VoiceOver is enabled.
|
||||||
class SemanticTextField extends SemanticRole {
|
class SemanticTextField extends SemanticRole {
|
||||||
SemanticTextField(SemanticsObject semanticsObject)
|
SemanticTextField(SemanticsObject semanticsObject)
|
||||||
: super.blank(SemanticRoleKind.textField, semanticsObject) {
|
: super.blank(EngineSemanticsRole.textField, semanticsObject) {
|
||||||
_initializeEditableElement();
|
_initializeEditableElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,6 +122,9 @@ void runSemanticsTests() {
|
|||||||
group('link', () {
|
group('link', () {
|
||||||
_testLink();
|
_testLink();
|
||||||
});
|
});
|
||||||
|
group('tabs', () {
|
||||||
|
_testTabs();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _testSemanticRole() {
|
void _testSemanticRole() {
|
||||||
@ -198,7 +201,7 @@ void _testRoleLifecycle() {
|
|||||||
tester.expectSemantics('<sem role="button"></sem>');
|
tester.expectSemantics('<sem role="button"></sem>');
|
||||||
|
|
||||||
final SemanticsObject node = owner().debugSemanticsTree![0]!;
|
final SemanticsObject node = owner().debugSemanticsTree![0]!;
|
||||||
expect(node.semanticRole?.kind, SemanticRoleKind.button);
|
expect(node.semanticRole?.kind, EngineSemanticsRole.button);
|
||||||
expect(
|
expect(
|
||||||
node.semanticRole?.debugSemanticBehaviorTypes,
|
node.semanticRole?.debugSemanticBehaviorTypes,
|
||||||
containsAll(<Type>[Focusable, Tappable, LabelAndValue]),
|
containsAll(<Type>[Focusable, Tappable, LabelAndValue]),
|
||||||
@ -221,7 +224,7 @@ void _testRoleLifecycle() {
|
|||||||
tester.expectSemantics('<sem role="button">a label</sem>');
|
tester.expectSemantics('<sem role="button">a label</sem>');
|
||||||
|
|
||||||
final SemanticsObject node = owner().debugSemanticsTree![0]!;
|
final SemanticsObject node = owner().debugSemanticsTree![0]!;
|
||||||
expect(node.semanticRole?.kind, SemanticRoleKind.button);
|
expect(node.semanticRole?.kind, EngineSemanticsRole.button);
|
||||||
expect(
|
expect(
|
||||||
node.semanticRole?.debugSemanticBehaviorTypes,
|
node.semanticRole?.debugSemanticBehaviorTypes,
|
||||||
containsAll(<Type>[Focusable, Tappable, LabelAndValue]),
|
containsAll(<Type>[Focusable, Tappable, LabelAndValue]),
|
||||||
@ -653,7 +656,7 @@ void _testEngineSemanticsOwner() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Rudely replace the role with a mock, and trigger an update.
|
// Rudely replace the role with a mock, and trigger an update.
|
||||||
final MockRole mockRole = MockRole(SemanticRoleKind.generic, semanticsObject);
|
final MockRole mockRole = MockRole(EngineSemanticsRole.generic, semanticsObject);
|
||||||
semanticsObject.semanticRole = mockRole;
|
semanticsObject.semanticRole = mockRole;
|
||||||
|
|
||||||
pumpSemantics(label: 'World');
|
pumpSemantics(label: 'World');
|
||||||
@ -869,7 +872,7 @@ void _testText() {
|
|||||||
expectSemanticsTree(owner(), '''<sem><span>plain text</span></sem>''');
|
expectSemanticsTree(owner(), '''<sem><span>plain text</span></sem>''');
|
||||||
|
|
||||||
final SemanticsObject node = owner().debugSemanticsTree![0]!;
|
final SemanticsObject node = owner().debugSemanticsTree![0]!;
|
||||||
expect(node.semanticRole?.kind, SemanticRoleKind.generic);
|
expect(node.semanticRole?.kind, EngineSemanticsRole.generic);
|
||||||
expect(node.semanticRole!.behaviors!.map((m) => m.runtimeType).toList(), <Type>[
|
expect(node.semanticRole!.behaviors!.map((m) => m.runtimeType).toList(), <Type>[
|
||||||
Focusable,
|
Focusable,
|
||||||
LiveRegion,
|
LiveRegion,
|
||||||
@ -896,7 +899,7 @@ void _testText() {
|
|||||||
expectSemanticsTree(owner(), '''<sem flt-tappable=""><span>tappable text</span></sem>''');
|
expectSemanticsTree(owner(), '''<sem flt-tappable=""><span>tappable text</span></sem>''');
|
||||||
|
|
||||||
final SemanticsObject node = owner().debugSemanticsTree![0]!;
|
final SemanticsObject node = owner().debugSemanticsTree![0]!;
|
||||||
expect(node.semanticRole?.kind, SemanticRoleKind.generic);
|
expect(node.semanticRole?.kind, EngineSemanticsRole.generic);
|
||||||
expect(node.semanticRole!.behaviors!.map((m) => m.runtimeType).toList(), <Type>[
|
expect(node.semanticRole!.behaviors!.map((m) => m.runtimeType).toList(), <Type>[
|
||||||
Focusable,
|
Focusable,
|
||||||
LiveRegion,
|
LiveRegion,
|
||||||
@ -1686,7 +1689,7 @@ void _testIncrementables() {
|
|||||||
</sem>''');
|
</sem>''');
|
||||||
|
|
||||||
final SemanticsObject node = owner().debugSemanticsTree![0]!;
|
final SemanticsObject node = owner().debugSemanticsTree![0]!;
|
||||||
expect(node.semanticRole?.kind, SemanticRoleKind.incrementable);
|
expect(node.semanticRole?.kind, EngineSemanticsRole.incrementable);
|
||||||
expect(
|
expect(
|
||||||
reason: 'Incrementables use custom focus management',
|
reason: 'Incrementables use custom focus management',
|
||||||
node.semanticRole!.debugSemanticBehaviorTypes,
|
node.semanticRole!.debugSemanticBehaviorTypes,
|
||||||
@ -1881,7 +1884,7 @@ void _testTextField() {
|
|||||||
// https://github.com/flutter/flutter/issues/147200
|
// https://github.com/flutter/flutter/issues/147200
|
||||||
expect(inputElement.value, '');
|
expect(inputElement.value, '');
|
||||||
|
|
||||||
expect(node.semanticRole?.kind, SemanticRoleKind.textField);
|
expect(node.semanticRole?.kind, EngineSemanticsRole.textField);
|
||||||
expect(
|
expect(
|
||||||
reason: 'Text fields use custom focus management',
|
reason: 'Text fields use custom focus management',
|
||||||
node.semanticRole!.debugSemanticBehaviorTypes,
|
node.semanticRole!.debugSemanticBehaviorTypes,
|
||||||
@ -1920,7 +1923,7 @@ void _testCheckables() {
|
|||||||
''');
|
''');
|
||||||
|
|
||||||
final SemanticsObject node = owner().debugSemanticsTree![0]!;
|
final SemanticsObject node = owner().debugSemanticsTree![0]!;
|
||||||
expect(node.semanticRole?.kind, SemanticRoleKind.checkable);
|
expect(node.semanticRole?.kind, EngineSemanticsRole.checkable);
|
||||||
expect(
|
expect(
|
||||||
reason: 'Checkables use generic semantic behaviors',
|
reason: 'Checkables use generic semantic behaviors',
|
||||||
node.semanticRole!.debugSemanticBehaviorTypes,
|
node.semanticRole!.debugSemanticBehaviorTypes,
|
||||||
@ -2294,7 +2297,7 @@ void _testSelectables() {
|
|||||||
expectSemanticsTree(owner(), '<sem flt-tappable role="checkbox" aria-checked="true"></sem>');
|
expectSemanticsTree(owner(), '<sem flt-tappable role="checkbox" aria-checked="true"></sem>');
|
||||||
|
|
||||||
final node = owner().debugSemanticsTree![0]!;
|
final node = owner().debugSemanticsTree![0]!;
|
||||||
expect(node.semanticRole!.kind, SemanticRoleKind.checkable);
|
expect(node.semanticRole!.kind, EngineSemanticsRole.checkable);
|
||||||
expect(node.semanticRole!.debugSemanticBehaviorTypes, isNot(contains(Selectable)));
|
expect(node.semanticRole!.debugSemanticBehaviorTypes, isNot(contains(Selectable)));
|
||||||
expect(node.element.getAttribute('aria-selected'), isNull);
|
expect(node.element.getAttribute('aria-selected'), isNull);
|
||||||
|
|
||||||
@ -2325,7 +2328,7 @@ void _testTappable() {
|
|||||||
''');
|
''');
|
||||||
|
|
||||||
final SemanticsObject node = owner().debugSemanticsTree![0]!;
|
final SemanticsObject node = owner().debugSemanticsTree![0]!;
|
||||||
expect(node.semanticRole?.kind, SemanticRoleKind.button);
|
expect(node.semanticRole?.kind, EngineSemanticsRole.button);
|
||||||
expect(node.semanticRole?.debugSemanticBehaviorTypes, containsAll(<Type>[Focusable, Tappable]));
|
expect(node.semanticRole?.debugSemanticBehaviorTypes, containsAll(<Type>[Focusable, Tappable]));
|
||||||
expect(tester.getSemanticsObject(0).element.tabIndex, 0);
|
expect(tester.getSemanticsObject(0).element.tabIndex, 0);
|
||||||
|
|
||||||
@ -3010,7 +3013,7 @@ void _testRoute() {
|
|||||||
<sem role="dialog" aria-label="this is a route label"><sem-c><sem></sem></sem-c></sem>
|
<sem role="dialog" aria-label="this is a route label"><sem-c><sem></sem></sem-c></sem>
|
||||||
''');
|
''');
|
||||||
|
|
||||||
expect(owner().debugSemanticsTree![0]!.semanticRole?.kind, SemanticRoleKind.route);
|
expect(owner().debugSemanticsTree![0]!.semanticRole?.kind, EngineSemanticsRole.route);
|
||||||
|
|
||||||
semantics().semanticsEnabled = false;
|
semantics().semanticsEnabled = false;
|
||||||
});
|
});
|
||||||
@ -3049,7 +3052,7 @@ void _testRoute() {
|
|||||||
<sem role="dialog" aria-label=""><sem-c><sem></sem></sem-c></sem>
|
<sem role="dialog" aria-label=""><sem-c><sem></sem></sem-c></sem>
|
||||||
''');
|
''');
|
||||||
|
|
||||||
expect(owner().debugSemanticsTree![0]!.semanticRole?.kind, SemanticRoleKind.route);
|
expect(owner().debugSemanticsTree![0]!.semanticRole?.kind, EngineSemanticsRole.route);
|
||||||
|
|
||||||
semantics().semanticsEnabled = false;
|
semantics().semanticsEnabled = false;
|
||||||
});
|
});
|
||||||
@ -3091,8 +3094,8 @@ void _testRoute() {
|
|||||||
|
|
||||||
pumpSemantics(label: 'Route label');
|
pumpSemantics(label: 'Route label');
|
||||||
|
|
||||||
expect(owner().debugSemanticsTree![0]!.semanticRole?.kind, SemanticRoleKind.route);
|
expect(owner().debugSemanticsTree![0]!.semanticRole?.kind, EngineSemanticsRole.route);
|
||||||
expect(owner().debugSemanticsTree![2]!.semanticRole?.kind, SemanticRoleKind.generic);
|
expect(owner().debugSemanticsTree![2]!.semanticRole?.kind, EngineSemanticsRole.generic);
|
||||||
expect(
|
expect(
|
||||||
owner().debugSemanticsTree![2]!.semanticRole?.debugSemanticBehaviorTypes,
|
owner().debugSemanticsTree![2]!.semanticRole?.debugSemanticBehaviorTypes,
|
||||||
contains(RouteName),
|
contains(RouteName),
|
||||||
@ -3116,7 +3119,7 @@ void _testRoute() {
|
|||||||
<sem role="dialog"></sem>
|
<sem role="dialog"></sem>
|
||||||
''');
|
''');
|
||||||
|
|
||||||
expect(owner().debugSemanticsTree![0]!.semanticRole?.kind, SemanticRoleKind.route);
|
expect(owner().debugSemanticsTree![0]!.semanticRole?.kind, EngineSemanticsRole.route);
|
||||||
expect(owner().debugSemanticsTree![0]!.semanticRole?.behaviors, isNot(contains(RouteName)));
|
expect(owner().debugSemanticsTree![0]!.semanticRole?.behaviors, isNot(contains(RouteName)));
|
||||||
|
|
||||||
semantics().semanticsEnabled = false;
|
semantics().semanticsEnabled = false;
|
||||||
@ -3154,7 +3157,7 @@ void _testRoute() {
|
|||||||
</sem>
|
</sem>
|
||||||
''');
|
''');
|
||||||
|
|
||||||
expect(owner().debugSemanticsTree![0]!.semanticRole?.kind, SemanticRoleKind.generic);
|
expect(owner().debugSemanticsTree![0]!.semanticRole?.kind, EngineSemanticsRole.generic);
|
||||||
expect(
|
expect(
|
||||||
owner().debugSemanticsTree![2]!.semanticRole?.debugSemanticBehaviorTypes,
|
owner().debugSemanticsTree![2]!.semanticRole?.debugSemanticBehaviorTypes,
|
||||||
contains(RouteName),
|
contains(RouteName),
|
||||||
@ -3516,7 +3519,7 @@ void _testFocusable() {
|
|||||||
|
|
||||||
final SemanticsObject node = owner().debugSemanticsTree![1]!;
|
final SemanticsObject node = owner().debugSemanticsTree![1]!;
|
||||||
expect(node.isFocusable, isTrue);
|
expect(node.isFocusable, isTrue);
|
||||||
expect(node.semanticRole?.kind, SemanticRoleKind.generic);
|
expect(node.semanticRole?.kind, EngineSemanticsRole.generic);
|
||||||
expect(node.semanticRole?.debugSemanticBehaviorTypes, contains(Focusable));
|
expect(node.semanticRole?.debugSemanticBehaviorTypes, contains(Focusable));
|
||||||
|
|
||||||
final DomElement element = node.element;
|
final DomElement element = node.element;
|
||||||
@ -3581,6 +3584,71 @@ void _testLink() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _testTabs() {
|
||||||
|
test('nodes with tab role', () {
|
||||||
|
semantics()
|
||||||
|
..debugOverrideTimestampFunction(() => _testTime)
|
||||||
|
..semanticsEnabled = true;
|
||||||
|
|
||||||
|
SemanticsObject pumpSemantics() {
|
||||||
|
final SemanticsTester tester = SemanticsTester(owner());
|
||||||
|
tester.updateNode(
|
||||||
|
id: 0,
|
||||||
|
role: ui.SemanticsRole.tab,
|
||||||
|
rect: const ui.Rect.fromLTRB(0, 0, 100, 50),
|
||||||
|
);
|
||||||
|
tester.apply();
|
||||||
|
return tester.getSemanticsObject(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
final SemanticsObject object = pumpSemantics();
|
||||||
|
expect(object.semanticRole?.kind, EngineSemanticsRole.tab);
|
||||||
|
expect(object.element.getAttribute('role'), 'tab');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('nodes with tab panel role', () {
|
||||||
|
semantics()
|
||||||
|
..debugOverrideTimestampFunction(() => _testTime)
|
||||||
|
..semanticsEnabled = true;
|
||||||
|
|
||||||
|
SemanticsObject pumpSemantics() {
|
||||||
|
final SemanticsTester tester = SemanticsTester(owner());
|
||||||
|
tester.updateNode(
|
||||||
|
id: 0,
|
||||||
|
role: ui.SemanticsRole.tabPanel,
|
||||||
|
rect: const ui.Rect.fromLTRB(0, 0, 100, 50),
|
||||||
|
);
|
||||||
|
tester.apply();
|
||||||
|
return tester.getSemanticsObject(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
final SemanticsObject object = pumpSemantics();
|
||||||
|
expect(object.semanticRole?.kind, EngineSemanticsRole.tabPanel);
|
||||||
|
expect(object.element.getAttribute('role'), 'tabpanel');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('nodes with tab bar role', () {
|
||||||
|
semantics()
|
||||||
|
..debugOverrideTimestampFunction(() => _testTime)
|
||||||
|
..semanticsEnabled = true;
|
||||||
|
|
||||||
|
SemanticsObject pumpSemantics() {
|
||||||
|
final SemanticsTester tester = SemanticsTester(owner());
|
||||||
|
tester.updateNode(
|
||||||
|
id: 0,
|
||||||
|
role: ui.SemanticsRole.tabBar,
|
||||||
|
rect: const ui.Rect.fromLTRB(0, 0, 100, 50),
|
||||||
|
);
|
||||||
|
tester.apply();
|
||||||
|
return tester.getSemanticsObject(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
final SemanticsObject object = pumpSemantics();
|
||||||
|
expect(object.semanticRole?.kind, EngineSemanticsRole.tabList);
|
||||||
|
expect(object.element.getAttribute('role'), 'tablist');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// A facade in front of [ui.SemanticsUpdateBuilder.updateNode] that
|
/// A facade in front of [ui.SemanticsUpdateBuilder.updateNode] that
|
||||||
/// supplies default values for semantics attributes.
|
/// supplies default values for semantics attributes.
|
||||||
void updateNode(
|
void updateNode(
|
||||||
|
@ -115,6 +115,7 @@ class SemanticsTester {
|
|||||||
List<SemanticsNodeUpdate>? children,
|
List<SemanticsNodeUpdate>? children,
|
||||||
int? headingLevel,
|
int? headingLevel,
|
||||||
String? linkUrl,
|
String? linkUrl,
|
||||||
|
ui.SemanticsRole? role,
|
||||||
}) {
|
}) {
|
||||||
// Flags
|
// Flags
|
||||||
if (hasCheckedState ?? false) {
|
if (hasCheckedState ?? false) {
|
||||||
@ -323,6 +324,7 @@ class SemanticsTester {
|
|||||||
additionalActions: additionalActions ?? Int32List(0),
|
additionalActions: additionalActions ?? Int32List(0),
|
||||||
headingLevel: headingLevel ?? 0,
|
headingLevel: headingLevel ?? 0,
|
||||||
linkUrl: linkUrl,
|
linkUrl: linkUrl,
|
||||||
|
role: role ?? ui.SemanticsRole.none,
|
||||||
);
|
);
|
||||||
_nodeUpdates.add(update);
|
_nodeUpdates.add(update);
|
||||||
return update;
|
return update;
|
||||||
|
@ -46,7 +46,7 @@ Future<void> testMain() async {
|
|||||||
<sem><span>Hello</span></sem>''');
|
<sem><span>Hello</span></sem>''');
|
||||||
|
|
||||||
final SemanticsObject node = owner().debugSemanticsTree![0]!;
|
final SemanticsObject node = owner().debugSemanticsTree![0]!;
|
||||||
expect(node.semanticRole?.kind, SemanticRoleKind.generic);
|
expect(node.semanticRole?.kind, EngineSemanticsRole.generic);
|
||||||
expect(
|
expect(
|
||||||
reason: 'A node with a label should get a LabelAndValue role',
|
reason: 'A node with a label should get a LabelAndValue role',
|
||||||
node.semanticRole!.debugSemanticBehaviorTypes,
|
node.semanticRole!.debugSemanticBehaviorTypes,
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
library;
|
library;
|
||||||
|
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
import 'dart:ui' show lerpDouble;
|
import 'dart:ui' show SemanticsRole, lerpDouble;
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/gestures.dart' show DragStartBehavior;
|
import 'package:flutter/gestures.dart' show DragStartBehavior;
|
||||||
@ -209,9 +209,12 @@ class Tab extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return SizedBox(
|
return Semantics(
|
||||||
|
role: SemanticsRole.tab,
|
||||||
|
child: SizedBox(
|
||||||
height: height ?? calculatedHeight,
|
height: height ?? calculatedHeight,
|
||||||
child: Center(widthFactor: 1.0, child: label),
|
child: Center(widthFactor: 1.0, child: label),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1865,7 +1868,8 @@ class _TabBarState extends State<TabBar> {
|
|||||||
wrappedTabs[index],
|
wrappedTabs[index],
|
||||||
Semantics(
|
Semantics(
|
||||||
selected: index == _currentIndex,
|
selected: index == _currentIndex,
|
||||||
label: localizations.tabLabel(tabIndex: index + 1, tabCount: tabCount),
|
label:
|
||||||
|
kIsWeb ? null : localizations.tabLabel(tabIndex: index + 1, tabCount: tabCount),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -1876,7 +1880,9 @@ class _TabBarState extends State<TabBar> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget tabBar = CustomPaint(
|
Widget tabBar = Semantics(
|
||||||
|
role: SemanticsRole.tabBar,
|
||||||
|
child: CustomPaint(
|
||||||
painter: _indicatorPainter,
|
painter: _indicatorPainter,
|
||||||
child: _TabStyle(
|
child: _TabStyle(
|
||||||
animation: kAlwaysDismissedAnimation,
|
animation: kAlwaysDismissedAnimation,
|
||||||
@ -1894,6 +1900,7 @@ class _TabBarState extends State<TabBar> {
|
|||||||
children: wrappedTabs,
|
children: wrappedTabs,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (widget.isScrollable) {
|
if (widget.isScrollable) {
|
||||||
@ -2131,7 +2138,11 @@ class _TabBarViewState extends State<TabBarView> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _updateChildren() {
|
void _updateChildren() {
|
||||||
_childrenWithKey = KeyedSubtree.ensureUniqueKeysForList(widget.children);
|
_childrenWithKey = KeyedSubtree.ensureUniqueKeysForList(
|
||||||
|
widget.children.map<Widget>((Widget child) {
|
||||||
|
return Semantics(role: SemanticsRole.tabPanel, child: child);
|
||||||
|
}).toList(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleTabControllerAnimationTick() {
|
void _handleTabControllerAnimationTick() {
|
||||||
|
@ -4430,6 +4430,9 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
|
|||||||
if (_properties.tagForChildren != null) {
|
if (_properties.tagForChildren != null) {
|
||||||
config.addTagForChildren(_properties.tagForChildren!);
|
config.addTagForChildren(_properties.tagForChildren!);
|
||||||
}
|
}
|
||||||
|
if (properties.role != null) {
|
||||||
|
config.role = _properties.role!;
|
||||||
|
}
|
||||||
// Registering _perform* as action handlers instead of the user provided
|
// Registering _perform* as action handlers instead of the user provided
|
||||||
// ones to ensure that changing a user provided handler from a non-null to
|
// ones to ensure that changing a user provided handler from a non-null to
|
||||||
// another non-null value doesn't require a semantics update.
|
// another non-null value doesn't require a semantics update.
|
||||||
|
@ -16,6 +16,7 @@ import 'dart:ui'
|
|||||||
Rect,
|
Rect,
|
||||||
SemanticsAction,
|
SemanticsAction,
|
||||||
SemanticsFlag,
|
SemanticsFlag,
|
||||||
|
SemanticsRole,
|
||||||
SemanticsUpdate,
|
SemanticsUpdate,
|
||||||
SemanticsUpdateBuilder,
|
SemanticsUpdateBuilder,
|
||||||
StringAttribute,
|
StringAttribute,
|
||||||
@ -481,6 +482,7 @@ class SemanticsData with Diagnosticable {
|
|||||||
required this.currentValueLength,
|
required this.currentValueLength,
|
||||||
required this.headingLevel,
|
required this.headingLevel,
|
||||||
required this.linkUrl,
|
required this.linkUrl,
|
||||||
|
required this.role,
|
||||||
this.tags,
|
this.tags,
|
||||||
this.transform,
|
this.transform,
|
||||||
this.customSemanticsActionIds,
|
this.customSemanticsActionIds,
|
||||||
@ -738,6 +740,9 @@ class SemanticsData with Diagnosticable {
|
|||||||
/// * [CustomSemanticsAction], for an explanation of custom actions.
|
/// * [CustomSemanticsAction], for an explanation of custom actions.
|
||||||
final List<int>? customSemanticsActionIds;
|
final List<int>? customSemanticsActionIds;
|
||||||
|
|
||||||
|
/// {@macro flutter.semantics.SemanticsNode.role}
|
||||||
|
final SemanticsRole role;
|
||||||
|
|
||||||
/// Whether [flags] contains the given flag.
|
/// Whether [flags] contains the given flag.
|
||||||
bool hasFlag(SemanticsFlag flag) => (flags & flag.index) != 0;
|
bool hasFlag(SemanticsFlag flag) => (flags & flag.index) != 0;
|
||||||
|
|
||||||
@ -826,6 +831,7 @@ class SemanticsData with Diagnosticable {
|
|||||||
other.thickness == thickness &&
|
other.thickness == thickness &&
|
||||||
other.headingLevel == headingLevel &&
|
other.headingLevel == headingLevel &&
|
||||||
other.linkUrl == linkUrl &&
|
other.linkUrl == linkUrl &&
|
||||||
|
other.role == role &&
|
||||||
_sortedListsEqual(other.customSemanticsActionIds, customSemanticsActionIds);
|
_sortedListsEqual(other.customSemanticsActionIds, customSemanticsActionIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -859,6 +865,7 @@ class SemanticsData with Diagnosticable {
|
|||||||
headingLevel,
|
headingLevel,
|
||||||
linkUrl,
|
linkUrl,
|
||||||
customSemanticsActionIds == null ? null : Object.hashAll(customSemanticsActionIds!),
|
customSemanticsActionIds == null ? null : Object.hashAll(customSemanticsActionIds!),
|
||||||
|
role,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -1026,6 +1033,7 @@ class SemanticsProperties extends DiagnosticableTree {
|
|||||||
this.onFocus,
|
this.onFocus,
|
||||||
this.onDismiss,
|
this.onDismiss,
|
||||||
this.customSemanticsActions,
|
this.customSemanticsActions,
|
||||||
|
this.role,
|
||||||
}) : assert(
|
}) : assert(
|
||||||
label == null || attributedLabel == null,
|
label == null || attributedLabel == null,
|
||||||
'Only one of label or attributedLabel should be provided',
|
'Only one of label or attributedLabel should be provided',
|
||||||
@ -1799,6 +1807,19 @@ class SemanticsProperties extends DiagnosticableTree {
|
|||||||
/// * [CustomSemanticsAction], for an explanation of custom actions.
|
/// * [CustomSemanticsAction], for an explanation of custom actions.
|
||||||
final Map<CustomSemanticsAction, VoidCallback>? customSemanticsActions;
|
final Map<CustomSemanticsAction, VoidCallback>? customSemanticsActions;
|
||||||
|
|
||||||
|
/// {@template flutter.semantics.SemanticsProperties.role}
|
||||||
|
/// A enum to describe what role the subtree represents.
|
||||||
|
///
|
||||||
|
/// Setting the role for a widget subtree helps assistive technologies, such
|
||||||
|
/// as screen readers, understand and interact with the UI correctly.
|
||||||
|
///
|
||||||
|
/// Defaults to [SemanticsRole.none] if not set, which means the subtree does
|
||||||
|
/// not represent any complex ui or controls.
|
||||||
|
///
|
||||||
|
/// For a list of available roles, see [SemanticsRole].
|
||||||
|
/// {@endtemplate}
|
||||||
|
final SemanticsRole? role;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||||
super.debugFillProperties(properties);
|
super.debugFillProperties(properties);
|
||||||
@ -1835,6 +1856,7 @@ class SemanticsProperties extends DiagnosticableTree {
|
|||||||
properties.add(AttributedStringProperty('attributedHint', attributedHint, defaultValue: null));
|
properties.add(AttributedStringProperty('attributedHint', attributedHint, defaultValue: null));
|
||||||
properties.add(StringProperty('tooltip', tooltip, defaultValue: null));
|
properties.add(StringProperty('tooltip', tooltip, defaultValue: null));
|
||||||
properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
|
properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
|
||||||
|
properties.add(EnumProperty<SemanticsRole>('role', role, defaultValue: null));
|
||||||
properties.add(DiagnosticsProperty<SemanticsSortKey>('sortKey', sortKey, defaultValue: null));
|
properties.add(DiagnosticsProperty<SemanticsSortKey>('sortKey', sortKey, defaultValue: null));
|
||||||
properties.add(
|
properties.add(
|
||||||
DiagnosticsProperty<SemanticsHintOverrides>(
|
DiagnosticsProperty<SemanticsHintOverrides>(
|
||||||
@ -2392,6 +2414,7 @@ class SemanticsNode with DiagnosticableTreeMixin {
|
|||||||
_mergeAllDescendantsIntoThisNode != config.isMergingSemanticsOfDescendants ||
|
_mergeAllDescendantsIntoThisNode != config.isMergingSemanticsOfDescendants ||
|
||||||
_areUserActionsBlocked != config.isBlockingUserActions ||
|
_areUserActionsBlocked != config.isBlockingUserActions ||
|
||||||
_headingLevel != config._headingLevel ||
|
_headingLevel != config._headingLevel ||
|
||||||
|
_linkUrl != config._linkUrl ||
|
||||||
_linkUrl != config._linkUrl;
|
_linkUrl != config._linkUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2708,6 +2731,17 @@ class SemanticsNode with DiagnosticableTreeMixin {
|
|||||||
Uri? get linkUrl => _linkUrl;
|
Uri? get linkUrl => _linkUrl;
|
||||||
Uri? _linkUrl = _kEmptyConfig._linkUrl;
|
Uri? _linkUrl = _kEmptyConfig._linkUrl;
|
||||||
|
|
||||||
|
/// {@template flutter.semantics.SemanticsNode.role}
|
||||||
|
/// The role this node represents
|
||||||
|
///
|
||||||
|
/// A semantics node's role helps assistive technologies, such as screen
|
||||||
|
/// readers, understand and interact with the UI correctly.
|
||||||
|
///
|
||||||
|
/// For a list of possible roles, see [SemanticsRole].
|
||||||
|
/// {@endtemplate}
|
||||||
|
SemanticsRole get role => _role;
|
||||||
|
SemanticsRole _role = _kEmptyConfig.role;
|
||||||
|
|
||||||
bool _canPerformAction(SemanticsAction action) => _actions.containsKey(action);
|
bool _canPerformAction(SemanticsAction action) => _actions.containsKey(action);
|
||||||
|
|
||||||
static final SemanticsConfiguration _kEmptyConfig = SemanticsConfiguration();
|
static final SemanticsConfiguration _kEmptyConfig = SemanticsConfiguration();
|
||||||
@ -2773,6 +2807,7 @@ class SemanticsNode with DiagnosticableTreeMixin {
|
|||||||
_areUserActionsBlocked = config.isBlockingUserActions;
|
_areUserActionsBlocked = config.isBlockingUserActions;
|
||||||
_headingLevel = config._headingLevel;
|
_headingLevel = config._headingLevel;
|
||||||
_linkUrl = config._linkUrl;
|
_linkUrl = config._linkUrl;
|
||||||
|
_role = config._role;
|
||||||
_replaceChildren(childrenInInversePaintOrder ?? const <SemanticsNode>[]);
|
_replaceChildren(childrenInInversePaintOrder ?? const <SemanticsNode>[]);
|
||||||
|
|
||||||
if (mergeAllDescendantsIntoThisNodeValueChanged) {
|
if (mergeAllDescendantsIntoThisNodeValueChanged) {
|
||||||
@ -2821,6 +2856,7 @@ class SemanticsNode with DiagnosticableTreeMixin {
|
|||||||
final double elevation = _elevation;
|
final double elevation = _elevation;
|
||||||
double thickness = _thickness;
|
double thickness = _thickness;
|
||||||
Uri? linkUrl = _linkUrl;
|
Uri? linkUrl = _linkUrl;
|
||||||
|
SemanticsRole role = _role;
|
||||||
final Set<int> customSemanticsActionIds = <int>{};
|
final Set<int> customSemanticsActionIds = <int>{};
|
||||||
for (final CustomSemanticsAction action in _customSemanticsActions.keys) {
|
for (final CustomSemanticsAction action in _customSemanticsActions.keys) {
|
||||||
customSemanticsActionIds.add(CustomSemanticsAction.getIdentifier(action));
|
customSemanticsActionIds.add(CustomSemanticsAction.getIdentifier(action));
|
||||||
@ -2876,6 +2912,9 @@ class SemanticsNode with DiagnosticableTreeMixin {
|
|||||||
if (attributedDecreasedValue.string == '') {
|
if (attributedDecreasedValue.string == '') {
|
||||||
attributedDecreasedValue = node._attributedDecreasedValue;
|
attributedDecreasedValue = node._attributedDecreasedValue;
|
||||||
}
|
}
|
||||||
|
if (role == SemanticsRole.none) {
|
||||||
|
role = node._role;
|
||||||
|
}
|
||||||
if (tooltip == '') {
|
if (tooltip == '') {
|
||||||
tooltip = node._tooltip;
|
tooltip = node._tooltip;
|
||||||
}
|
}
|
||||||
@ -2949,6 +2988,7 @@ class SemanticsNode with DiagnosticableTreeMixin {
|
|||||||
customSemanticsActionIds: customSemanticsActionIds.toList()..sort(),
|
customSemanticsActionIds: customSemanticsActionIds.toList()..sort(),
|
||||||
headingLevel: headingLevel,
|
headingLevel: headingLevel,
|
||||||
linkUrl: linkUrl,
|
linkUrl: linkUrl,
|
||||||
|
role: role,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3026,6 +3066,7 @@ class SemanticsNode with DiagnosticableTreeMixin {
|
|||||||
additionalActions: customSemanticsActionIds ?? _kEmptyCustomSemanticsActionsList,
|
additionalActions: customSemanticsActionIds ?? _kEmptyCustomSemanticsActionsList,
|
||||||
headingLevel: data.headingLevel,
|
headingLevel: data.headingLevel,
|
||||||
linkUrl: data.linkUrl?.toString() ?? '',
|
linkUrl: data.linkUrl?.toString() ?? '',
|
||||||
|
role: data.role,
|
||||||
);
|
);
|
||||||
_dirty = false;
|
_dirty = false;
|
||||||
}
|
}
|
||||||
@ -3202,6 +3243,9 @@ class SemanticsNode with DiagnosticableTreeMixin {
|
|||||||
properties.add(
|
properties.add(
|
||||||
EnumProperty<TextDirection>('textDirection', _textDirection, defaultValue: null),
|
EnumProperty<TextDirection>('textDirection', _textDirection, defaultValue: null),
|
||||||
);
|
);
|
||||||
|
if (_role != SemanticsRole.none) {
|
||||||
|
properties.add(EnumProperty<SemanticsRole>('role', _role));
|
||||||
|
}
|
||||||
properties.add(DiagnosticsProperty<SemanticsSortKey>('sortKey', sortKey, defaultValue: null));
|
properties.add(DiagnosticsProperty<SemanticsSortKey>('sortKey', sortKey, defaultValue: null));
|
||||||
if (_textSelection?.isValid ?? false) {
|
if (_textSelection?.isValid ?? false) {
|
||||||
properties.add(
|
properties.add(
|
||||||
@ -4560,6 +4604,14 @@ class SemanticsConfiguration {
|
|||||||
_hasBeenAnnotated = true;
|
_hasBeenAnnotated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// {@macro flutter.semantics.SemanticsProperties.role}
|
||||||
|
SemanticsRole get role => _role;
|
||||||
|
SemanticsRole _role = SemanticsRole.none;
|
||||||
|
set role(SemanticsRole value) {
|
||||||
|
_role = value;
|
||||||
|
_hasBeenAnnotated = true;
|
||||||
|
}
|
||||||
|
|
||||||
/// A textual description of the owning [RenderObject].
|
/// A textual description of the owning [RenderObject].
|
||||||
///
|
///
|
||||||
/// Setting this attribute will override the [attributedLabel].
|
/// Setting this attribute will override the [attributedLabel].
|
||||||
@ -5313,6 +5365,9 @@ class SemanticsConfiguration {
|
|||||||
if (_attributedDecreasedValue.string == '') {
|
if (_attributedDecreasedValue.string == '') {
|
||||||
_attributedDecreasedValue = child._attributedDecreasedValue;
|
_attributedDecreasedValue = child._attributedDecreasedValue;
|
||||||
}
|
}
|
||||||
|
if (_role == SemanticsRole.none) {
|
||||||
|
_role = child._role;
|
||||||
|
}
|
||||||
_attributedHint = _concatAttributedString(
|
_attributedHint = _concatAttributedString(
|
||||||
thisAttributedString: _attributedHint,
|
thisAttributedString: _attributedHint,
|
||||||
thisTextDirection: textDirection,
|
thisTextDirection: textDirection,
|
||||||
@ -5365,7 +5420,8 @@ class SemanticsConfiguration {
|
|||||||
.._customSemanticsActions.addAll(_customSemanticsActions)
|
.._customSemanticsActions.addAll(_customSemanticsActions)
|
||||||
..isBlockingUserActions = isBlockingUserActions
|
..isBlockingUserActions = isBlockingUserActions
|
||||||
.._headingLevel = _headingLevel
|
.._headingLevel = _headingLevel
|
||||||
.._linkUrl = _linkUrl;
|
.._linkUrl = _linkUrl
|
||||||
|
.._role = _role;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
library;
|
library;
|
||||||
|
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
import 'dart:ui' as ui show Image, ImageFilter, TextHeightBehavior;
|
import 'dart:ui' as ui show Image, ImageFilter, SemanticsRole, TextHeightBehavior;
|
||||||
|
|
||||||
import 'package:flutter/animation.dart';
|
import 'package:flutter/animation.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
@ -7288,6 +7288,7 @@ class Semantics extends SingleChildRenderObjectWidget {
|
|||||||
VoidCallback? onDidLoseAccessibilityFocus,
|
VoidCallback? onDidLoseAccessibilityFocus,
|
||||||
VoidCallback? onFocus,
|
VoidCallback? onFocus,
|
||||||
Map<CustomSemanticsAction, VoidCallback>? customSemanticsActions,
|
Map<CustomSemanticsAction, VoidCallback>? customSemanticsActions,
|
||||||
|
ui.SemanticsRole? role,
|
||||||
}) : this.fromProperties(
|
}) : this.fromProperties(
|
||||||
key: key,
|
key: key,
|
||||||
child: child,
|
child: child,
|
||||||
@ -7362,6 +7363,7 @@ class Semantics extends SingleChildRenderObjectWidget {
|
|||||||
onTapHint != null || onLongPressHint != null
|
onTapHint != null || onLongPressHint != null
|
||||||
? SemanticsHintOverrides(onTapHint: onTapHint, onLongPressHint: onLongPressHint)
|
? SemanticsHintOverrides(onTapHint: onTapHint, onLongPressHint: onLongPressHint)
|
||||||
: null,
|
: null,
|
||||||
|
role: role,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -3897,25 +3897,32 @@ void main() {
|
|||||||
children: <TestSemantics>[
|
children: <TestSemantics>[
|
||||||
TestSemantics(
|
TestSemantics(
|
||||||
id: 4,
|
id: 4,
|
||||||
|
rect: const Rect.fromLTRB(0.0, 0.0, 232.0, 600.0),
|
||||||
|
role: SemanticsRole.tabBar,
|
||||||
|
children: <TestSemantics>[
|
||||||
|
TestSemantics(
|
||||||
|
id: 5,
|
||||||
actions: <SemanticsAction>[SemanticsAction.tap, SemanticsAction.focus],
|
actions: <SemanticsAction>[SemanticsAction.tap, SemanticsAction.focus],
|
||||||
flags: <SemanticsFlag>[
|
flags: <SemanticsFlag>[
|
||||||
SemanticsFlag.hasSelectedState,
|
SemanticsFlag.hasSelectedState,
|
||||||
SemanticsFlag.isSelected,
|
SemanticsFlag.isSelected,
|
||||||
SemanticsFlag.isFocusable,
|
SemanticsFlag.isFocusable,
|
||||||
],
|
],
|
||||||
label: 'TAB #0\nTab 1 of 2',
|
label: 'TAB #0${kIsWeb ? '' : '\nTab 1 of 2'}',
|
||||||
rect: const Rect.fromLTRB(0.0, 0.0, 116.0, kTextTabBarHeight),
|
rect: const Rect.fromLTRB(0.0, 0.0, 116.0, kTextTabBarHeight),
|
||||||
|
role: SemanticsRole.tab,
|
||||||
transform: Matrix4.translationValues(0.0, 276.0, 0.0),
|
transform: Matrix4.translationValues(0.0, 276.0, 0.0),
|
||||||
),
|
),
|
||||||
TestSemantics(
|
TestSemantics(
|
||||||
id: 5,
|
id: 6,
|
||||||
flags: <SemanticsFlag>[
|
flags: <SemanticsFlag>[
|
||||||
SemanticsFlag.hasSelectedState,
|
SemanticsFlag.hasSelectedState,
|
||||||
SemanticsFlag.isFocusable,
|
SemanticsFlag.isFocusable,
|
||||||
],
|
],
|
||||||
actions: <SemanticsAction>[SemanticsAction.tap, SemanticsAction.focus],
|
actions: <SemanticsAction>[SemanticsAction.tap, SemanticsAction.focus],
|
||||||
label: 'TAB #1\nTab 2 of 2',
|
label: 'TAB #1${kIsWeb ? '' : '\nTab 2 of 2'}',
|
||||||
rect: const Rect.fromLTRB(0.0, 0.0, 116.0, kTextTabBarHeight),
|
rect: const Rect.fromLTRB(0.0, 0.0, 116.0, kTextTabBarHeight),
|
||||||
|
role: SemanticsRole.tab,
|
||||||
transform: Matrix4.translationValues(116.0, 276.0, 0.0),
|
transform: Matrix4.translationValues(116.0, 276.0, 0.0),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -3925,6 +3932,8 @@ void main() {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(semantics, hasSemantics(expectedSemantics));
|
expect(semantics, hasSemantics(expectedSemantics));
|
||||||
@ -3953,8 +3962,8 @@ void main() {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
const String tab0title = 'This is a very wide tab #0\nTab 1 of 20';
|
const String tab0title = 'This is a very wide tab #0${kIsWeb ? '' : '\nTab 1 of 20'}';
|
||||||
const String tab10title = 'This is a very wide tab #10\nTab 11 of 20';
|
const String tab10title = 'This is a very wide tab #10${kIsWeb ? '' : '\nTab 11 of 20'}';
|
||||||
|
|
||||||
const List<SemanticsFlag> hiddenFlags = <SemanticsFlag>[
|
const List<SemanticsFlag> hiddenFlags = <SemanticsFlag>[
|
||||||
SemanticsFlag.isHidden,
|
SemanticsFlag.isHidden,
|
||||||
@ -4165,25 +4174,32 @@ void main() {
|
|||||||
children: <TestSemantics>[
|
children: <TestSemantics>[
|
||||||
TestSemantics(
|
TestSemantics(
|
||||||
id: 4,
|
id: 4,
|
||||||
|
rect: const Rect.fromLTRB(0.0, 0.0, 232.0, 600.0),
|
||||||
|
role: SemanticsRole.tabBar,
|
||||||
|
children: <TestSemantics>[
|
||||||
|
TestSemantics(
|
||||||
|
id: 5,
|
||||||
flags: <SemanticsFlag>[
|
flags: <SemanticsFlag>[
|
||||||
SemanticsFlag.hasSelectedState,
|
SemanticsFlag.hasSelectedState,
|
||||||
SemanticsFlag.isSelected,
|
SemanticsFlag.isSelected,
|
||||||
SemanticsFlag.isFocusable,
|
SemanticsFlag.isFocusable,
|
||||||
],
|
],
|
||||||
actions: <SemanticsAction>[SemanticsAction.tap, SemanticsAction.focus],
|
actions: <SemanticsAction>[SemanticsAction.tap, SemanticsAction.focus],
|
||||||
label: 'Semantics override 0\nTab 1 of 2',
|
label: 'Semantics override 0${kIsWeb ? '' : '\nTab 1 of 2'}',
|
||||||
rect: const Rect.fromLTRB(0.0, 0.0, 116.0, kTextTabBarHeight),
|
rect: const Rect.fromLTRB(0.0, 0.0, 116.0, kTextTabBarHeight),
|
||||||
|
role: SemanticsRole.tab,
|
||||||
transform: Matrix4.translationValues(0.0, 276.0, 0.0),
|
transform: Matrix4.translationValues(0.0, 276.0, 0.0),
|
||||||
),
|
),
|
||||||
TestSemantics(
|
TestSemantics(
|
||||||
id: 5,
|
id: 6,
|
||||||
flags: <SemanticsFlag>[
|
flags: <SemanticsFlag>[
|
||||||
SemanticsFlag.hasSelectedState,
|
SemanticsFlag.hasSelectedState,
|
||||||
SemanticsFlag.isFocusable,
|
SemanticsFlag.isFocusable,
|
||||||
],
|
],
|
||||||
actions: <SemanticsAction>[SemanticsAction.tap, SemanticsAction.focus],
|
actions: <SemanticsAction>[SemanticsAction.tap, SemanticsAction.focus],
|
||||||
label: 'Semantics override 1\nTab 2 of 2',
|
label: 'Semantics override 1${kIsWeb ? '' : '\nTab 2 of 2'}',
|
||||||
rect: const Rect.fromLTRB(0.0, 0.0, 116.0, kTextTabBarHeight),
|
rect: const Rect.fromLTRB(0.0, 0.0, 116.0, kTextTabBarHeight),
|
||||||
|
role: SemanticsRole.tab,
|
||||||
transform: Matrix4.translationValues(116.0, 276.0, 0.0),
|
transform: Matrix4.translationValues(116.0, 276.0, 0.0),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -4193,6 +4209,8 @@ void main() {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(semantics, hasSemantics(expectedSemantics));
|
expect(semantics, hasSemantics(expectedSemantics));
|
||||||
@ -5982,9 +6000,10 @@ void main() {
|
|||||||
label: 'Tab 1 of 2',
|
label: 'Tab 1 of 2',
|
||||||
id: 1,
|
id: 1,
|
||||||
rect: TestSemantics.fullScreen,
|
rect: TestSemantics.fullScreen,
|
||||||
|
role: SemanticsRole.tabBar,
|
||||||
children: <TestSemantics>[
|
children: <TestSemantics>[
|
||||||
TestSemantics(
|
TestSemantics(
|
||||||
label: 'TAB1\nTab 1 of 2',
|
label: 'TAB1${kIsWeb ? '' : '\nTab 1 of 2'}',
|
||||||
flags: <SemanticsFlag>[
|
flags: <SemanticsFlag>[
|
||||||
SemanticsFlag.isFocusable,
|
SemanticsFlag.isFocusable,
|
||||||
SemanticsFlag.isSelected,
|
SemanticsFlag.isSelected,
|
||||||
@ -5993,13 +6012,15 @@ void main() {
|
|||||||
id: 2,
|
id: 2,
|
||||||
rect: TestSemantics.fullScreen,
|
rect: TestSemantics.fullScreen,
|
||||||
actions: 1 | SemanticsAction.focus.index,
|
actions: 1 | SemanticsAction.focus.index,
|
||||||
|
role: SemanticsRole.tab,
|
||||||
),
|
),
|
||||||
TestSemantics(
|
TestSemantics(
|
||||||
label: 'TAB2\nTab 2 of 2',
|
label: 'TAB2${kIsWeb ? '' : '\nTab 2 of 2'}',
|
||||||
flags: <SemanticsFlag>[SemanticsFlag.isFocusable, SemanticsFlag.hasSelectedState],
|
flags: <SemanticsFlag>[SemanticsFlag.isFocusable, SemanticsFlag.hasSelectedState],
|
||||||
id: 3,
|
id: 3,
|
||||||
rect: TestSemantics.fullScreen,
|
rect: TestSemantics.fullScreen,
|
||||||
actions: <SemanticsAction>[SemanticsAction.tap, SemanticsAction.focus],
|
actions: <SemanticsAction>[SemanticsAction.tap, SemanticsAction.focus],
|
||||||
|
role: SemanticsRole.tab,
|
||||||
),
|
),
|
||||||
TestSemantics(
|
TestSemantics(
|
||||||
id: 4,
|
id: 4,
|
||||||
@ -6010,7 +6031,12 @@ void main() {
|
|||||||
rect: TestSemantics.fullScreen,
|
rect: TestSemantics.fullScreen,
|
||||||
actions: <SemanticsAction>[SemanticsAction.scrollLeft],
|
actions: <SemanticsAction>[SemanticsAction.scrollLeft],
|
||||||
children: <TestSemantics>[
|
children: <TestSemantics>[
|
||||||
TestSemantics(id: 5, rect: TestSemantics.fullScreen, label: 'PAGE1'),
|
TestSemantics(
|
||||||
|
id: 5,
|
||||||
|
rect: TestSemantics.fullScreen,
|
||||||
|
label: 'PAGE1',
|
||||||
|
role: SemanticsRole.tabPanel,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -228,6 +228,7 @@ class SemanticsUpdateBuilderSpy extends Fake implements ui.SemanticsUpdateBuilde
|
|||||||
required Int32List additionalActions,
|
required Int32List additionalActions,
|
||||||
int headingLevel = 0,
|
int headingLevel = 0,
|
||||||
String? linkUrl,
|
String? linkUrl,
|
||||||
|
ui.SemanticsRole role = ui.SemanticsRole.none,
|
||||||
}) {
|
}) {
|
||||||
// Makes sure we don't send the same id twice.
|
// Makes sure we don't send the same id twice.
|
||||||
assert(!observations.containsKey(id));
|
assert(!observations.containsKey(id));
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/physics.dart';
|
import 'package:flutter/physics.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
@ -53,6 +55,7 @@ class TestSemantics {
|
|||||||
this.scrollIndex,
|
this.scrollIndex,
|
||||||
this.scrollChildren,
|
this.scrollChildren,
|
||||||
Iterable<SemanticsTag>? tags,
|
Iterable<SemanticsTag>? tags,
|
||||||
|
this.role = SemanticsRole.none,
|
||||||
}) : assert(flags is int || flags is List<SemanticsFlag>),
|
}) : assert(flags is int || flags is List<SemanticsFlag>),
|
||||||
assert(actions is int || actions is List<SemanticsAction>),
|
assert(actions is int || actions is List<SemanticsAction>),
|
||||||
tags = tags?.toSet() ?? <SemanticsTag>{};
|
tags = tags?.toSet() ?? <SemanticsTag>{};
|
||||||
@ -76,6 +79,7 @@ class TestSemantics {
|
|||||||
this.scrollIndex,
|
this.scrollIndex,
|
||||||
this.scrollChildren,
|
this.scrollChildren,
|
||||||
Iterable<SemanticsTag>? tags,
|
Iterable<SemanticsTag>? tags,
|
||||||
|
this.role = SemanticsRole.none,
|
||||||
}) : id = 0,
|
}) : id = 0,
|
||||||
assert(flags is int || flags is List<SemanticsFlag>),
|
assert(flags is int || flags is List<SemanticsFlag>),
|
||||||
assert(actions is int || actions is List<SemanticsAction>),
|
assert(actions is int || actions is List<SemanticsAction>),
|
||||||
@ -115,6 +119,7 @@ class TestSemantics {
|
|||||||
this.scrollIndex,
|
this.scrollIndex,
|
||||||
this.scrollChildren,
|
this.scrollChildren,
|
||||||
Iterable<SemanticsTag>? tags,
|
Iterable<SemanticsTag>? tags,
|
||||||
|
this.role = SemanticsRole.none,
|
||||||
}) : assert(flags is int || flags is List<SemanticsFlag>),
|
}) : assert(flags is int || flags is List<SemanticsFlag>),
|
||||||
assert(actions is int || actions is List<SemanticsAction>),
|
assert(actions is int || actions is List<SemanticsAction>),
|
||||||
transform = _applyRootChildScale(transform),
|
transform = _applyRootChildScale(transform),
|
||||||
@ -243,6 +248,11 @@ class TestSemantics {
|
|||||||
|
|
||||||
final int? headingLevel;
|
final int? headingLevel;
|
||||||
|
|
||||||
|
/// The expected role for the node.
|
||||||
|
///
|
||||||
|
/// Defaults to SemanticsRole.none if not set.
|
||||||
|
final SemanticsRole role;
|
||||||
|
|
||||||
bool _matches(
|
bool _matches(
|
||||||
SemanticsNode? node,
|
SemanticsNode? node,
|
||||||
Map<dynamic, dynamic> matchState, {
|
Map<dynamic, dynamic> matchState, {
|
||||||
@ -378,6 +388,10 @@ class TestSemantics {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (role != node.role) {
|
||||||
|
return fail('expected node id $id to have role $role but found role ${node.role}');
|
||||||
|
}
|
||||||
|
|
||||||
if (children.isEmpty) {
|
if (children.isEmpty) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -781,6 +795,9 @@ class SemanticsTester {
|
|||||||
if (node.textDirection != null) {
|
if (node.textDirection != null) {
|
||||||
buf.writeln(' textDirection: ${node.textDirection},');
|
buf.writeln(' textDirection: ${node.textDirection},');
|
||||||
}
|
}
|
||||||
|
if (node.role != SemanticsRole.none) {
|
||||||
|
buf.writeln(' role: ${node.role},');
|
||||||
|
}
|
||||||
if (node.hasChildren) {
|
if (node.hasChildren) {
|
||||||
buf.writeln(' children: <TestSemantics>[');
|
buf.writeln(' children: <TestSemantics>[');
|
||||||
for (final SemanticsNode child in node.debugListChildrenInOrder(childOrder)) {
|
for (final SemanticsNode child in node.debugListChildrenInOrder(childOrder)) {
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
@ -717,6 +718,7 @@ void main() {
|
|||||||
maxValueLength: 15,
|
maxValueLength: 15,
|
||||||
headingLevel: 0,
|
headingLevel: 0,
|
||||||
linkUrl: Uri(path: 'l'),
|
linkUrl: Uri(path: 'l'),
|
||||||
|
role: ui.SemanticsRole.none,
|
||||||
);
|
);
|
||||||
final _FakeSemanticsNode node = _FakeSemanticsNode(data);
|
final _FakeSemanticsNode node = _FakeSemanticsNode(data);
|
||||||
|
|
||||||
@ -1015,6 +1017,7 @@ void main() {
|
|||||||
maxValueLength: 15,
|
maxValueLength: 15,
|
||||||
headingLevel: 0,
|
headingLevel: 0,
|
||||||
linkUrl: Uri(path: 'l'),
|
linkUrl: Uri(path: 'l'),
|
||||||
|
role: ui.SemanticsRole.none,
|
||||||
);
|
);
|
||||||
final _FakeSemanticsNode node = _FakeSemanticsNode(data);
|
final _FakeSemanticsNode node = _FakeSemanticsNode(data);
|
||||||
|
|
||||||
@ -1110,6 +1113,7 @@ void main() {
|
|||||||
maxValueLength: 15,
|
maxValueLength: 15,
|
||||||
headingLevel: 0,
|
headingLevel: 0,
|
||||||
linkUrl: null,
|
linkUrl: null,
|
||||||
|
role: ui.SemanticsRole.none,
|
||||||
);
|
);
|
||||||
final _FakeSemanticsNode node = _FakeSemanticsNode(data);
|
final _FakeSemanticsNode node = _FakeSemanticsNode(data);
|
||||||
|
|
||||||
@ -1212,6 +1216,7 @@ void main() {
|
|||||||
maxValueLength: 15,
|
maxValueLength: 15,
|
||||||
headingLevel: 0,
|
headingLevel: 0,
|
||||||
linkUrl: null,
|
linkUrl: null,
|
||||||
|
role: ui.SemanticsRole.none,
|
||||||
);
|
);
|
||||||
final _FakeSemanticsNode emptyNode = _FakeSemanticsNode(emptyData);
|
final _FakeSemanticsNode emptyNode = _FakeSemanticsNode(emptyData);
|
||||||
|
|
||||||
@ -1242,6 +1247,7 @@ void main() {
|
|||||||
customSemanticsActionIds: <int>[CustomSemanticsAction.getIdentifier(action)],
|
customSemanticsActionIds: <int>[CustomSemanticsAction.getIdentifier(action)],
|
||||||
headingLevel: 0,
|
headingLevel: 0,
|
||||||
linkUrl: Uri(path: 'l'),
|
linkUrl: Uri(path: 'l'),
|
||||||
|
role: ui.SemanticsRole.none,
|
||||||
);
|
);
|
||||||
final _FakeSemanticsNode fullNode = _FakeSemanticsNode(fullData);
|
final _FakeSemanticsNode fullNode = _FakeSemanticsNode(fullData);
|
||||||
|
|
||||||
@ -1328,6 +1334,7 @@ void main() {
|
|||||||
customSemanticsActionIds: <int>[CustomSemanticsAction.getIdentifier(action)],
|
customSemanticsActionIds: <int>[CustomSemanticsAction.getIdentifier(action)],
|
||||||
headingLevel: 0,
|
headingLevel: 0,
|
||||||
linkUrl: null,
|
linkUrl: null,
|
||||||
|
role: ui.SemanticsRole.none,
|
||||||
);
|
);
|
||||||
final _FakeSemanticsNode node = _FakeSemanticsNode(data);
|
final _FakeSemanticsNode node = _FakeSemanticsNode(data);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user