@@ -21,6 +21,7 @@ import '../window.dart';
21
21
import 'accessibility.dart' ;
22
22
import 'checkable.dart' ;
23
23
import 'focusable.dart' ;
24
+ import 'header.dart' ;
24
25
import 'heading.dart' ;
25
26
import 'image.dart' ;
26
27
import 'incrementable.dart' ;
@@ -396,14 +397,17 @@ enum SemanticRoleKind {
396
397
/// The node's role is to host a platform view.
397
398
platformView,
398
399
400
+ /// Contains a link.
401
+ link,
402
+
403
+ /// Denotes a header.
404
+ header,
405
+
399
406
/// A role used when a more specific role cannot be assigend to
400
407
/// a [SemanticsObject] .
401
408
///
402
409
/// Provides a label or a value.
403
410
generic,
404
-
405
- /// Contains a link.
406
- link,
407
411
}
408
412
409
413
/// Responsible for setting the `role` ARIA attribute, for attaching
@@ -688,23 +692,18 @@ final class GenericRole extends SemanticRole {
688
692
return ;
689
693
}
690
694
691
- // Assign one of three roles to the element: group, heading, text.
695
+ // Assign one of two roles to the element: group or text.
692
696
//
693
697
// - "group" is used when the node has children, irrespective of whether the
694
698
// node is marked as a header or not. This is because marking a group
695
699
// as a "heading" will prevent the AT from reaching its children.
696
- // - "heading" is used when the framework explicitly marks the node as a
697
- // heading and the node does not have children.
698
700
// - If a node has a label and no children, assume is a paragraph of text.
699
701
// In HTML text has no ARIA role. It's just a DOM node with text inside
700
702
// it. Previously, role="text" was used, but it was only supported by
701
703
// Safari, and it was removed starting Safari 17.
702
704
if (semanticsObject.hasChildren) {
703
705
labelAndValue! .preferredRepresentation = LabelRepresentation .ariaLabel;
704
706
setAriaRole ('group' );
705
- } else if (semanticsObject.hasFlag (ui.SemanticsFlag .isHeader)) {
706
- labelAndValue! .preferredRepresentation = LabelRepresentation .domText;
707
- setAriaRole ('heading' );
708
707
} else {
709
708
labelAndValue! .preferredRepresentation = LabelRepresentation .sizedSpan;
710
709
removeAttribute ('role' );
@@ -1123,10 +1122,24 @@ class SemanticsObject {
1123
1122
_dirtyFields | = _platformViewIdIndex;
1124
1123
}
1125
1124
1126
- /// See [ui.SemanticsUpdateBuilder.updateNode] .
1127
- int get headingLevel => _headingLevel;
1125
+ // This field is not exposed publicly because code that applies heading levels
1126
+ // should use [effectiveHeadingLevel] instead.
1128
1127
int _headingLevel = 0 ;
1129
1128
1129
+ /// The effective heading level value to be used when rendering this node as
1130
+ /// a heading.
1131
+ ///
1132
+ /// If a heading is rendered from a header, uses heading level 2.
1133
+ int get effectiveHeadingLevel {
1134
+ if (_headingLevel != 0 ) {
1135
+ return _headingLevel;
1136
+ } else {
1137
+ // This branch may be taken when a heading is rendered from a header,
1138
+ // where the heading level is not provided.
1139
+ return 2 ;
1140
+ }
1141
+ }
1142
+
1130
1143
static const int _headingLevelIndex = 1 << 24 ;
1131
1144
1132
1145
/// Whether the [headingLevel] field has been updated but has not been
@@ -1136,6 +1149,36 @@ class SemanticsObject {
1136
1149
_dirtyFields | = _headingLevelIndex;
1137
1150
}
1138
1151
1152
+ /// Whether this object represents a heading.
1153
+ ///
1154
+ /// Typically, a heading is a prominent piece of text that provides a title
1155
+ /// for a section in the UI.
1156
+ ///
1157
+ /// Labeled empty headers are treated as headings too.
1158
+ ///
1159
+ /// See also:
1160
+ ///
1161
+ /// * [isHeader] , which also describes the rest of the screen, and is
1162
+ /// sometimes presented to the user as a heading.
1163
+ bool get isHeading => _headingLevel != 0 || isHeader && hasLabel && ! hasChildren;
1164
+
1165
+ /// Whether this object represents a header.
1166
+ ///
1167
+ /// A header is used for one of two purposes:
1168
+ ///
1169
+ /// * Introduce the content of the main screen or a page. In this case, the
1170
+ /// header is a, possibly labeled, container of widgets that together
1171
+ /// provide the description of the screen.
1172
+ /// * Provide a heading (like [isHeading] ). Native mobile apps do not have a
1173
+ /// notion of "heading". It is common to mark headings as headers instead
1174
+ /// and the screen readers will announce "heading". Labeled empty headers
1175
+ /// are treated as heading by the web engine.
1176
+ ///
1177
+ /// See also:
1178
+ ///
1179
+ /// * [isHeading] , which determines whether this node represents a heading.
1180
+ bool get isHeader => hasFlag (ui.SemanticsFlag .isHeader);
1181
+
1139
1182
/// See [ui.SemanticsUpdateBuilder.updateNode] .
1140
1183
String ? get identifier => _identifier;
1141
1184
String ? _identifier;
@@ -1271,10 +1314,7 @@ class SemanticsObject {
1271
1314
/// Whether this object represents an editable text field.
1272
1315
bool get isTextField => hasFlag (ui.SemanticsFlag .isTextField);
1273
1316
1274
- /// Whether this object represents a heading element.
1275
- bool get isHeading => headingLevel != 0 ;
1276
-
1277
- /// Whether this object represents an editable text field.
1317
+ /// Whether this object represents an interactive link.
1278
1318
bool get isLink => hasFlag (ui.SemanticsFlag .isLink);
1279
1319
1280
1320
/// Whether this object needs screen readers attention right away.
@@ -1673,6 +1713,8 @@ class SemanticsObject {
1673
1713
if (isPlatformView) {
1674
1714
return SemanticRoleKind .platformView;
1675
1715
} else if (isHeading) {
1716
+ // IMPORTANT: because headings also cover certain kinds of headers, the
1717
+ // `heading` role has precedence over the `header` role.
1676
1718
return SemanticRoleKind .heading;
1677
1719
} else if (isTextField) {
1678
1720
return SemanticRoleKind .textField;
@@ -1690,6 +1732,8 @@ class SemanticsObject {
1690
1732
return SemanticRoleKind .route;
1691
1733
} else if (isLink) {
1692
1734
return SemanticRoleKind .link;
1735
+ } else if (isHeader) {
1736
+ return SemanticRoleKind .header;
1693
1737
} else {
1694
1738
return SemanticRoleKind .generic;
1695
1739
}
@@ -1707,6 +1751,7 @@ class SemanticsObject {
1707
1751
SemanticRoleKind .platformView => SemanticPlatformView (this ),
1708
1752
SemanticRoleKind .link => SemanticLink (this ),
1709
1753
SemanticRoleKind .heading => SemanticHeading (this ),
1754
+ SemanticRoleKind .header => SemanticHeader (this ),
1710
1755
SemanticRoleKind .generic => GenericRole (this ),
1711
1756
};
1712
1757
}
0 commit comments