- Created lib/core/semantics/ with semantic ID constants and helper widget
- Enabled SemanticsBinding on web builds for accessibility tree exposure
- Added semantic IDs to:
- ProfileDropdown (button, settings, theme options, logout)
- TopHeaderBar room tabs (frontHall, controlRoom, security, parlor)
- NavPanel items (nav_item_{id})
This enables browser automation tools like Puppeteer and WebDriver to
discover and interact with Flutter widgets via the accessibility tree.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
119 lines
2.6 KiB
Dart
119 lines
2.6 KiB
Dart
import 'package:flutter/material.dart';
|
|
|
|
/// Wraps a widget with semantic information for accessibility and automation.
|
|
///
|
|
/// Usage:
|
|
/// ```dart
|
|
/// SemanticWidget(
|
|
/// id: ProfileSemantics.button,
|
|
/// label: 'Open profile menu',
|
|
/// child: IconButton(...),
|
|
/// )
|
|
/// ```
|
|
///
|
|
/// For buttons, use `button: true`. For other interactive elements,
|
|
/// set the appropriate semantic properties.
|
|
class SemanticWidget extends StatelessWidget {
|
|
const SemanticWidget({
|
|
super.key,
|
|
required this.id,
|
|
required this.child,
|
|
this.label,
|
|
this.hint,
|
|
this.button = false,
|
|
this.link = false,
|
|
this.header = false,
|
|
this.textField = false,
|
|
this.enabled = true,
|
|
this.selected,
|
|
this.checked,
|
|
this.value,
|
|
this.excludeSemantics = false,
|
|
});
|
|
|
|
/// Unique identifier for this widget, exposed via [SemanticsProperties.identifier].
|
|
final String id;
|
|
|
|
/// The widget to wrap.
|
|
final Widget child;
|
|
|
|
/// Accessibility label describing the widget.
|
|
final String? label;
|
|
|
|
/// Hint text for screen readers.
|
|
final String? hint;
|
|
|
|
/// Whether this widget represents a button.
|
|
final bool button;
|
|
|
|
/// Whether this widget represents a link.
|
|
final bool link;
|
|
|
|
/// Whether this widget represents a header.
|
|
final bool header;
|
|
|
|
/// Whether this widget represents a text field.
|
|
final bool textField;
|
|
|
|
/// Whether the widget is enabled.
|
|
final bool enabled;
|
|
|
|
/// Whether the widget is selected (for toggle buttons, tabs).
|
|
final bool? selected;
|
|
|
|
/// Whether the widget is checked (for checkboxes).
|
|
final bool? checked;
|
|
|
|
/// Current value (for sliders, progress indicators).
|
|
final String? value;
|
|
|
|
/// Whether to exclude child semantics.
|
|
final bool excludeSemantics;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Semantics(
|
|
identifier: id,
|
|
label: label,
|
|
hint: hint,
|
|
button: button,
|
|
link: link,
|
|
header: header,
|
|
textField: textField,
|
|
enabled: enabled,
|
|
selected: selected,
|
|
checked: checked,
|
|
value: value,
|
|
excludeSemantics: excludeSemantics,
|
|
child: child,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Extension to easily wrap any widget with semantic info.
|
|
extension SemanticExtension on Widget {
|
|
/// Wraps this widget with a semantic identifier.
|
|
Widget withSemantics({
|
|
required String id,
|
|
String? label,
|
|
String? hint,
|
|
bool button = false,
|
|
bool link = false,
|
|
bool enabled = true,
|
|
bool? selected,
|
|
bool? checked,
|
|
}) {
|
|
return SemanticWidget(
|
|
id: id,
|
|
label: label,
|
|
hint: hint,
|
|
button: button,
|
|
link: link,
|
|
enabled: enabled,
|
|
selected: selected,
|
|
checked: checked,
|
|
child: this,
|
|
);
|
|
}
|
|
}
|