Skip to content

Commit

Permalink
Add web support for tooltip (flutter#28966)
Browse files Browse the repository at this point in the history
  • Loading branch information
chunhtai authored Oct 1, 2021
1 parent 6df286b commit e094ce6
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 4 deletions.
9 changes: 8 additions & 1 deletion lib/web_ui/lib/src/engine/semantics/label_and_value.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
23 changes: 22 additions & 1 deletion lib/web_ui/lib/src/engine/semantics/semantics.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -812,6 +828,11 @@ class SemanticsObject {
_markDecreasedValueDirty();
}

if (_tooltip != update.tooltip) {
_tooltip = update.tooltip;
_markTooltipDirty();
}

if (_textDirection != update.textDirection) {
_textDirection = update.textDirection;
_markTextDirectionDirty();
Expand Down Expand Up @@ -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 =
Expand Down
56 changes: 54 additions & 2 deletions lib/web_ui/test/engine/semantics/semantics_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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;

Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit e094ce6

Please sign in to comment.