Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 648ced6

Browse files
committed
Label sources
1 parent ee6a908 commit 648ced6

File tree

4 files changed

+91
-16
lines changed

4 files changed

+91
-16
lines changed

lib/web_ui/lib/src/engine/semantics/incrementable.dart

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@ class Incrementable extends PrimaryRoleManager {
2828
// the one being focused on, but the internal `<input>` element.
2929
addLiveRegion();
3030
addRouteName();
31-
addLabelAndValue(preferredRepresentation: LabelRepresentation.ariaLabel);
31+
addLabelAndValue(
32+
preferredRepresentation: LabelRepresentation.ariaLabel,
33+
labelSources: _incrementableLabelSources,
34+
);
3235

3336
append(_element);
3437
_element.type = 'range';
@@ -60,6 +63,12 @@ class Incrementable extends PrimaryRoleManager {
6063
_focusManager.manage(semanticsObject.id, _element);
6164
}
6265

66+
// The incrementable role manager has custom reporting of the semantics value.
67+
// We do not need to also render it as a label.
68+
static final Set<LabelSource> _incrementableLabelSources = LabelAndValue
69+
.allLabelSources
70+
.difference(<LabelSource>{LabelSource.value});
71+
6372
@override
6473
bool focusAsRouteDefault() {
6574
_element.focus();

lib/web_ui/lib/src/engine/semantics/label_and_value.dart

Lines changed: 55 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -425,14 +425,33 @@ final class SizedSpanRepresentation extends LabelRepresentationBehavior {
425425
DomElement get focusTarget => _domText;
426426
}
427427

428+
/// The source of the label attribute for a semantic node.
429+
enum LabelSource {
430+
/// The label is provided by the [SemanticsObject.label] property.
431+
label,
432+
433+
/// The label is provided by the [SemanticsObject.value] property.
434+
value,
435+
436+
/// The label is provided by the [SemanticsObject.hint] property.
437+
hint,
438+
439+
/// The label is provided by the [SemanticsObject.tooltip] property.
440+
tooltip,
441+
}
442+
428443
/// Renders [SemanticsObject.label] and/or [SemanticsObject.value] to the semantics DOM.
429444
///
430445
/// The value is not always rendered. Some semantics nodes correspond to
431446
/// interactive controls. In such case the value is reported via that element's
432447
/// `value` attribute rather than rendering it separately.
433448
class LabelAndValue extends RoleManager {
434-
LabelAndValue(SemanticsObject semanticsObject, PrimaryRoleManager owner, { required this.preferredRepresentation })
435-
: super(Role.labelAndValue, semanticsObject, owner);
449+
LabelAndValue(
450+
SemanticsObject semanticsObject,
451+
PrimaryRoleManager owner, {
452+
required this.preferredRepresentation,
453+
this.labelSources = allLabelSources,
454+
}) : super(Role.labelAndValue, semanticsObject, owner);
436455

437456
/// The preferred representation of the label in the DOM.
438457
///
@@ -443,6 +462,17 @@ class LabelAndValue extends RoleManager {
443462
/// instead.
444463
LabelRepresentation preferredRepresentation;
445464

465+
/// The sources of the label that are allowed to be used.
466+
final Set<LabelSource> labelSources;
467+
468+
/// All possible sources of the label.
469+
static const Set<LabelSource> allLabelSources = <LabelSource>{
470+
LabelSource.label,
471+
LabelSource.value,
472+
LabelSource.hint,
473+
LabelSource.tooltip,
474+
};
475+
446476
@override
447477
void update() {
448478
final String? computedLabel = _computeLabel();
@@ -477,20 +507,34 @@ class LabelAndValue extends RoleManager {
477507
return representation;
478508
}
479509

510+
String? get _label =>
511+
labelSources.contains(LabelSource.label) && semanticsObject.hasLabel
512+
? semanticsObject.label
513+
: null;
514+
515+
String? get _value =>
516+
labelSources.contains(LabelSource.value) && semanticsObject.hasValue
517+
? semanticsObject.value
518+
: null;
519+
520+
String? get _tooltip =>
521+
labelSources.contains(LabelSource.tooltip) && semanticsObject.hasTooltip
522+
? semanticsObject.tooltip
523+
: null;
524+
525+
String? get _hint =>
526+
labelSources.contains(LabelSource.hint) ? semanticsObject.hint : null;
527+
480528
/// Computes the final label to be assigned to the node.
481529
///
482530
/// The label is a concatenation of tooltip, label, hint, and value, whichever
483-
/// combination is present.
531+
/// combination is present and allowed by [labelSources].
484532
String? _computeLabel() {
485-
// If the node is incrementable the value is reported to the browser via
486-
// the respective role manager. We do not need to also render it again here.
487-
final bool shouldDisplayValue = !semanticsObject.isIncrementable && semanticsObject.hasValue;
488-
489533
return computeDomSemanticsLabel(
490-
tooltip: semanticsObject.hasTooltip ? semanticsObject.tooltip : null,
491-
label: semanticsObject.hasLabel ? semanticsObject.label : null,
492-
hint: semanticsObject.hint,
493-
value: shouldDisplayValue ? semanticsObject.value : null,
534+
tooltip: _tooltip,
535+
label: _label,
536+
hint: _hint,
537+
value: _value,
494538
);
495539
}
496540

lib/web_ui/lib/src/engine/semantics/link.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,16 @@ class Link extends PrimaryRoleManager {
1111
PrimaryRole.link,
1212
semanticsObject,
1313
preferredLabelRepresentation: LabelRepresentation.domText,
14+
labelSources: _linkLabelSources,
1415
) {
1516
addTappable();
1617
}
1718

19+
// The semantics value is consumed by the [Link] role manager, so there's no
20+
// need to also render it as a label.
21+
static final Set<LabelSource> _linkLabelSources = LabelAndValue.allLabelSources
22+
.difference(<LabelSource>{LabelSource.value});
23+
1824
@override
1925
DomElement createElement() {
2026
final DomElement element = domDocument.createElement('a');

lib/web_ui/lib/src/engine/semantics/semantics.dart

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -434,12 +434,20 @@ abstract class PrimaryRoleManager {
434434
///
435435
/// If `labelRepresentation` is true, configures the [LabelAndValue] role with
436436
/// [LabelAndValue.labelRepresentation] set to true.
437-
PrimaryRoleManager.withBasics(this.role, this.semanticsObject, { required LabelRepresentation preferredLabelRepresentation }) {
437+
PrimaryRoleManager.withBasics(
438+
this.role,
439+
this.semanticsObject, {
440+
required LabelRepresentation preferredLabelRepresentation,
441+
Set<LabelSource> labelSources = LabelAndValue.allLabelSources,
442+
}) {
438443
element = _initElement(createElement(), semanticsObject);
439444
addFocusManagement();
440445
addLiveRegion();
441446
addRouteName();
442-
addLabelAndValue(preferredRepresentation: preferredLabelRepresentation);
447+
addLabelAndValue(
448+
preferredRepresentation: preferredLabelRepresentation,
449+
labelSources: labelSources,
450+
);
443451
}
444452

445453
/// Initializes a blank role for a [semanticsObject].
@@ -566,8 +574,16 @@ abstract class PrimaryRoleManager {
566574
LabelAndValue? _labelAndValue;
567575

568576
/// Adds generic label features.
569-
void addLabelAndValue({ required LabelRepresentation preferredRepresentation }) {
570-
addSecondaryRole(_labelAndValue = LabelAndValue(semanticsObject, this, preferredRepresentation: preferredRepresentation));
577+
void addLabelAndValue({
578+
required LabelRepresentation preferredRepresentation,
579+
Set<LabelSource> labelSources = LabelAndValue.allLabelSources,
580+
}) {
581+
addSecondaryRole(_labelAndValue = LabelAndValue(
582+
semanticsObject,
583+
this,
584+
preferredRepresentation: preferredRepresentation,
585+
labelSources: labelSources,
586+
));
571587
}
572588

573589
/// Adds generic functionality for handling taps and clicks.

0 commit comments

Comments
 (0)