Add web support for tooltip (flutter/engine#28966)

This commit is contained in:
chunhtai 2021-10-01 11:18:02 -07:00 committed by GitHub
parent c5791ff96e
commit 406668ff08
3 changed files with 84 additions and 4 deletions

View File

@ -52,17 +52,24 @@ class LabelAndValue extends RoleManager {
void update() {
final bool hasValue = semanticsObject.hasValue;
final bool hasLabel = semanticsObject.hasLabel;
final bool hasTooltip = semanticsObject.hasTooltip;
// If the node is incrementable the value is reported to the browser via
// the respective role manager. We do not need to also render it again here.
final bool shouldDisplayValue = hasValue && !semanticsObject.isIncrementable;
if (!hasLabel && !shouldDisplayValue) {
if (!hasLabel && !shouldDisplayValue && !hasTooltip) {
_cleanUpDom();
return;
}
final StringBuffer combinedValue = StringBuffer();
if (hasTooltip) {
combinedValue.write(semanticsObject.tooltip);
if (hasLabel || shouldDisplayValue) {
combinedValue.write('\n');
}
}
if (hasLabel) {
combinedValue.write(semanticsObject.label);
if (shouldDisplayValue) {

View File

@ -600,6 +600,22 @@ class SemanticsObject {
_dirtyFields |= _additionalActionsIndex;
}
/// See [ui.SemanticsUpdateBuilder.updateNode].
String? get tooltip => _tooltip;
String? _tooltip;
/// Whether this object contains a non-empty tooltip.
bool get hasTooltip => _tooltip != null && _tooltip!.isNotEmpty;
static const int _tooltipIndex = 1 << 22;
/// Whether the [tooltip] field has been updated but has not been
/// applied to the DOM yet.
bool get isTooltipDirty => _isDirty(_tooltipIndex);
void _markTooltipDirty() {
_dirtyFields |= _tooltipIndex;
}
/// A unique permanent identifier of the semantics node in the tree.
final int id;
@ -812,6 +828,11 @@ class SemanticsObject {
_markDecreasedValueDirty();
}
if (_tooltip != update.tooltip) {
_tooltip = update.tooltip;
_markTooltipDirty();
}
if (_textDirection != update.textDirection) {
_textDirection = update.textDirection;
_markTextDirectionDirty();
@ -882,7 +903,7 @@ class SemanticsObject {
/// Detects the roles that this semantics object corresponds to and manages
/// the lifecycles of [SemanticsObjectRole] objects.
void _updateRoles() {
_updateRole(Role.labelAndValue, (hasLabel || hasValue) && !isTextField && !isVisualOnly);
_updateRole(Role.labelAndValue, (hasLabel || hasValue || hasTooltip) && !isTextField && !isVisualOnly);
_updateRole(Role.textField, isTextField);
final bool shouldUseTappableRole =

View File

@ -131,7 +131,7 @@ void _testEngineSemanticsOwner() {
expect(placeholder.isConnected, isFalse);
});
void renderLabel(String label) {
void renderSemantics({String? label, String? tooltip}) {
final ui.SemanticsUpdateBuilder builder = ui.SemanticsUpdateBuilder();
updateNode(
builder,
@ -148,13 +148,18 @@ void _testEngineSemanticsOwner() {
id: 1,
actions: 0,
flags: 0,
label: label,
label: label ?? '',
tooltip: tooltip ?? '',
transform: Matrix4.identity().toFloat64(),
rect: const ui.Rect.fromLTRB(0, 0, 20, 20),
);
semantics().updateSemantics(builder.build());
}
void renderLabel(String label) {
renderSemantics(label: label);
}
test('produces an aria-label', () async {
semantics().semanticsEnabled = true;
@ -202,6 +207,53 @@ void _testEngineSemanticsOwner() {
semantics().semanticsEnabled = false;
});
test('tooltip is part of label', () async {
semantics().semanticsEnabled = true;
// Create
renderSemantics(tooltip: 'tooltip');
final Map<int, SemanticsObject> tree = semantics().debugSemanticsTree!;
expect(tree.length, 2);
expect(tree[0]!.id, 0);
expect(tree[0]!.element.tagName.toLowerCase(), 'flt-semantics');
expect(tree[1]!.id, 1);
expect(tree[1]!.tooltip, 'tooltip');
expectSemanticsTree('''
<sem style="$rootSemanticStyle">
<sem-c>
<sem aria-label="tooltip">
<sem-v>tooltip</sem-v>
</sem>
</sem-c>
</sem>''');
// Update
renderSemantics(label: 'Hello', tooltip: 'tooltip');
expectSemanticsTree('''
<sem style="$rootSemanticStyle">
<sem-c>
<sem aria-label="tooltip\nHello">
<sem-v>tooltip\nHello</sem-v>
</sem>
</sem-c>
</sem>''');
// Remove
renderSemantics();
expectSemanticsTree('''
<sem style="$rootSemanticStyle">
<sem-c>
<sem></sem>
</sem-c>
</sem>''');
semantics().semanticsEnabled = false;
});
test('clears semantics tree when disabled', () {
expect(semantics().debugSemanticsTree, isEmpty);
semantics().semanticsEnabled = true;