Wires up expanded state in web engine (#164048)
<!-- Thanks for filing a pull request! Reviewers are typically assigned within a week of filing a request. To learn more about code review, see our documentation on Tree Hygiene: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md --> fixes https://github.com/flutter/flutter/issues/162141 ## 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 --------- Co-authored-by: Mouad Debbar <mouad.debbar@gmail.com>
This commit is contained in:
parent
b8f274ddce
commit
37ff5a337a
@ -42685,6 +42685,7 @@ ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/scene_view.dart + ../../../fl
|
|||||||
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics.dart + ../../../flutter/LICENSE
|
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics.dart + ../../../flutter/LICENSE
|
||||||
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/accessibility.dart + ../../../flutter/LICENSE
|
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/accessibility.dart + ../../../flutter/LICENSE
|
||||||
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/checkable.dart + ../../../flutter/LICENSE
|
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/checkable.dart + ../../../flutter/LICENSE
|
||||||
|
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/expandable.dart + ../../../flutter/LICENSE
|
||||||
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/focusable.dart + ../../../flutter/LICENSE
|
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/focusable.dart + ../../../flutter/LICENSE
|
||||||
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/header.dart + ../../../flutter/LICENSE
|
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/header.dart + ../../../flutter/LICENSE
|
||||||
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/heading.dart + ../../../flutter/LICENSE
|
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/heading.dart + ../../../flutter/LICENSE
|
||||||
@ -45617,6 +45618,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/scene_view.dart
|
|||||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics.dart
|
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics.dart
|
||||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/accessibility.dart
|
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/accessibility.dart
|
||||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/checkable.dart
|
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/checkable.dart
|
||||||
|
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/expandable.dart
|
||||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/focusable.dart
|
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/focusable.dart
|
||||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/header.dart
|
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/header.dart
|
||||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/heading.dart
|
FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/heading.dart
|
||||||
|
@ -105,6 +105,7 @@ export 'engine/scene_painting.dart';
|
|||||||
export 'engine/scene_view.dart';
|
export 'engine/scene_view.dart';
|
||||||
export 'engine/semantics/accessibility.dart';
|
export 'engine/semantics/accessibility.dart';
|
||||||
export 'engine/semantics/checkable.dart';
|
export 'engine/semantics/checkable.dart';
|
||||||
|
export 'engine/semantics/expandable.dart';
|
||||||
export 'engine/semantics/focusable.dart';
|
export 'engine/semantics/focusable.dart';
|
||||||
export 'engine/semantics/header.dart';
|
export 'engine/semantics/header.dart';
|
||||||
export 'engine/semantics/heading.dart';
|
export 'engine/semantics/heading.dart';
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
export 'semantics/accessibility.dart';
|
export 'semantics/accessibility.dart';
|
||||||
export 'semantics/checkable.dart';
|
export 'semantics/checkable.dart';
|
||||||
|
export 'semantics/expandable.dart';
|
||||||
export 'semantics/focusable.dart';
|
export 'semantics/focusable.dart';
|
||||||
export 'semantics/header.dart';
|
export 'semantics/header.dart';
|
||||||
export 'semantics/heading.dart';
|
export 'semantics/heading.dart';
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
// 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 'semantics.dart';
|
||||||
|
|
||||||
|
/// Adds expandability behavior to a semantic node.
|
||||||
|
///
|
||||||
|
/// An expandable node would have the `aria-expanded` attribute set to "true" if the node
|
||||||
|
/// is currently expanded (i.e. [SemanticsObject.isExpanded] is true), and set
|
||||||
|
/// to "false" if it's not expanded (i.e. [SemanticsObject.isExpanded] is
|
||||||
|
/// false). If the node is not expandable (i.e. [SemanticsObject.isExpandable]
|
||||||
|
/// is false), then `aria-expanded` is unset.
|
||||||
|
class Expandable extends SemanticBehavior {
|
||||||
|
Expandable(super.semanticsObject, super.owner);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void update() {
|
||||||
|
if (semanticsObject.isFlagsDirty) {
|
||||||
|
if (semanticsObject.isExpandable) {
|
||||||
|
owner.setAttribute('aria-expanded', semanticsObject.isExpanded);
|
||||||
|
} else {
|
||||||
|
owner.removeAttribute('aria-expanded');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -20,6 +20,7 @@ import '../vector_math.dart';
|
|||||||
import '../window.dart';
|
import '../window.dart';
|
||||||
import 'accessibility.dart';
|
import 'accessibility.dart';
|
||||||
import 'checkable.dart';
|
import 'checkable.dart';
|
||||||
|
import 'expandable.dart';
|
||||||
import 'focusable.dart';
|
import 'focusable.dart';
|
||||||
import 'header.dart';
|
import 'header.dart';
|
||||||
import 'heading.dart';
|
import 'heading.dart';
|
||||||
@ -461,6 +462,7 @@ abstract class SemanticRole {
|
|||||||
addRouteName();
|
addRouteName();
|
||||||
addLabelAndValue(preferredRepresentation: preferredLabelRepresentation);
|
addLabelAndValue(preferredRepresentation: preferredLabelRepresentation);
|
||||||
addSelectableBehavior();
|
addSelectableBehavior();
|
||||||
|
addExpandableBehavior();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initializes a blank role for a [semanticsObject].
|
/// Initializes a blank role for a [semanticsObject].
|
||||||
@ -627,6 +629,10 @@ abstract class SemanticRole {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void addExpandableBehavior() {
|
||||||
|
addSemanticBehavior(Expandable(semanticsObject, this));
|
||||||
|
}
|
||||||
|
|
||||||
/// Adds a semantic behavior to this role.
|
/// Adds a semantic behavior to this role.
|
||||||
///
|
///
|
||||||
/// This method should be called by concrete implementations of
|
/// This method should be called by concrete implementations of
|
||||||
@ -1947,6 +1953,19 @@ class SemanticsObject {
|
|||||||
/// selected.
|
/// selected.
|
||||||
bool get isSelected => hasFlag(ui.SemanticsFlag.isSelected);
|
bool get isSelected => hasFlag(ui.SemanticsFlag.isSelected);
|
||||||
|
|
||||||
|
/// If true, this node represents something that can be annotated as
|
||||||
|
/// "expanded", such as a expansion tile or drop down menu
|
||||||
|
///
|
||||||
|
/// Expandability is managed by `aria-expanded`.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [isExpanded], which indicates whether the node is currently selected.
|
||||||
|
bool get isExpandable => hasFlag(ui.SemanticsFlag.hasExpandedState);
|
||||||
|
|
||||||
|
/// Indicates whether the node is currently expanded.
|
||||||
|
bool get isExpanded => hasFlag(ui.SemanticsFlag.isExpanded);
|
||||||
|
|
||||||
/// Role-specific adjustment of the vertical position of the child container.
|
/// Role-specific adjustment of the vertical position of the child container.
|
||||||
///
|
///
|
||||||
/// This is used, for example, by the [SemanticScrollable] to compensate for the
|
/// This is used, for example, by the [SemanticScrollable] to compensate for the
|
||||||
|
@ -86,6 +86,9 @@ void runSemanticsTests() {
|
|||||||
group('selectables', () {
|
group('selectables', () {
|
||||||
_testSelectables();
|
_testSelectables();
|
||||||
});
|
});
|
||||||
|
group('expandables', () {
|
||||||
|
_testExpandables();
|
||||||
|
});
|
||||||
group('tappable', () {
|
group('tappable', () {
|
||||||
_testTappable();
|
_testTappable();
|
||||||
});
|
});
|
||||||
@ -2328,6 +2331,77 @@ void _testSelectables() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _testExpandables() {
|
||||||
|
test('renders and updates non-expandable, expanded, and unexpanded nodes', () async {
|
||||||
|
semantics()
|
||||||
|
..debugOverrideTimestampFunction(() => _testTime)
|
||||||
|
..semanticsEnabled = true;
|
||||||
|
|
||||||
|
final tester = SemanticsTester(owner());
|
||||||
|
tester.updateNode(
|
||||||
|
id: 0,
|
||||||
|
rect: const ui.Rect.fromLTRB(0, 0, 100, 60),
|
||||||
|
children: <SemanticsNodeUpdate>[
|
||||||
|
tester.updateNode(id: 1, isSelectable: false, rect: const ui.Rect.fromLTRB(0, 0, 100, 20)),
|
||||||
|
tester.updateNode(
|
||||||
|
id: 2,
|
||||||
|
isExpandable: true,
|
||||||
|
isExpanded: false,
|
||||||
|
rect: const ui.Rect.fromLTRB(0, 20, 100, 40),
|
||||||
|
),
|
||||||
|
tester.updateNode(
|
||||||
|
id: 3,
|
||||||
|
isExpandable: true,
|
||||||
|
isExpanded: true,
|
||||||
|
rect: const ui.Rect.fromLTRB(0, 40, 100, 60),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
tester.apply();
|
||||||
|
|
||||||
|
expectSemanticsTree(owner(), '''
|
||||||
|
<sem>
|
||||||
|
<sem-c>
|
||||||
|
<sem></sem>
|
||||||
|
<sem aria-expanded="false"></sem>
|
||||||
|
<sem aria-expanded="true"></sem>
|
||||||
|
</sem-c>
|
||||||
|
</sem>
|
||||||
|
''');
|
||||||
|
|
||||||
|
// Missing attributes cannot be expressed using HTML patterns, so check directly.
|
||||||
|
final nonExpandable = owner().debugSemanticsTree![1]!.element;
|
||||||
|
expect(nonExpandable.getAttribute('aria-expanded'), isNull);
|
||||||
|
|
||||||
|
// Flip the values and check that that ARIA attribute is updated.
|
||||||
|
tester.updateNode(
|
||||||
|
id: 2,
|
||||||
|
isExpandable: true,
|
||||||
|
isExpanded: true,
|
||||||
|
rect: const ui.Rect.fromLTRB(0, 20, 100, 40),
|
||||||
|
);
|
||||||
|
tester.updateNode(
|
||||||
|
id: 3,
|
||||||
|
isExpandable: true,
|
||||||
|
isExpanded: false,
|
||||||
|
rect: const ui.Rect.fromLTRB(0, 40, 100, 60),
|
||||||
|
);
|
||||||
|
tester.apply();
|
||||||
|
|
||||||
|
expectSemanticsTree(owner(), '''
|
||||||
|
<sem>
|
||||||
|
<sem-c>
|
||||||
|
<sem></sem>
|
||||||
|
<sem aria-expanded="true"></sem>
|
||||||
|
<sem aria-expanded="false"></sem>
|
||||||
|
</sem-c>
|
||||||
|
</sem>
|
||||||
|
''');
|
||||||
|
|
||||||
|
semantics().semanticsEnabled = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void _testTappable() {
|
void _testTappable() {
|
||||||
test('renders an enabled button', () async {
|
test('renders an enabled button', () async {
|
||||||
semantics()
|
semantics()
|
||||||
|
@ -34,6 +34,8 @@ class SemanticsTester {
|
|||||||
bool? isChecked,
|
bool? isChecked,
|
||||||
bool? isSelectable,
|
bool? isSelectable,
|
||||||
bool? isSelected,
|
bool? isSelected,
|
||||||
|
bool? isExpandable,
|
||||||
|
bool? isExpanded,
|
||||||
bool? isButton,
|
bool? isButton,
|
||||||
bool? isLink,
|
bool? isLink,
|
||||||
bool? isTextField,
|
bool? isTextField,
|
||||||
@ -130,6 +132,12 @@ class SemanticsTester {
|
|||||||
if (isSelected ?? false) {
|
if (isSelected ?? false) {
|
||||||
flags |= ui.SemanticsFlag.isSelected.index;
|
flags |= ui.SemanticsFlag.isSelected.index;
|
||||||
}
|
}
|
||||||
|
if (isExpandable ?? false) {
|
||||||
|
flags |= ui.SemanticsFlag.hasExpandedState.index;
|
||||||
|
}
|
||||||
|
if (isExpanded ?? false) {
|
||||||
|
flags |= ui.SemanticsFlag.isExpanded.index;
|
||||||
|
}
|
||||||
if (isButton ?? false) {
|
if (isButton ?? false) {
|
||||||
flags |= ui.SemanticsFlag.isButton.index;
|
flags |= ui.SemanticsFlag.isButton.index;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user