Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ This project follows [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.

## [Unreleased]

### Fixed

- **`WAnchor` now hit-tests its whole bounds via `HitTestBehavior.translucent`.** The inner `GestureDetector` used the default `HitTestBehavior.deferToChild`, so it only fired when a painted child sat under the exact tap point. An anchor wrapping transparent content (a settings row, a checkbox row, a link with padding) ignored taps that landed on its empty regions, including the element centre that automated drivers and centred pointer events target. It now behaves like `WInput`/`WPopover`, which already use whole-box hit-testing, so the full anchor rectangle is tappable. (`lib/src/widgets/w_anchor.dart`; covered by `test/widgets/w_anchor_test.dart`)

## [1.2.0] - 2026-07-08

### Changed
Expand Down
10 changes: 10 additions & 0 deletions lib/src/widgets/w_anchor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,16 @@ class _WAnchorState extends State<WAnchor> {
// Only wrap with GestureDetector if there are actual gesture callbacks
if (hasGestures) {
innerChild = GestureDetector(
// Translucent so the whole anchor bounds are tappable, not only the
// opaque descendants. The GestureDetector defaults to
// `HitTestBehavior.deferToChild`, which only receives a pointer when a
// painted child sits under it; an anchor wrapping transparent content
// (a settings row with text on one side, a link, an icon row) then
// ignored taps that landed on its empty regions. Real users tapping the
// blank part of a row, and every centre-of-element tap from a driver
// (dusk / Playwright), silently did nothing. Translucent matches the
// whole-box tap target WInput and WPopover already use.
behavior: HitTestBehavior.translucent,
onTap: widget.isDisabled ? null : widget.onTap,
onLongPress: widget.isDisabled ? null : widget.onLongPress,
onDoubleTap: widget.isDisabled ? null : widget.onDoubleTap,
Expand Down
34 changes: 34 additions & 0 deletions test/widgets/w_anchor_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,40 @@ void main() {
expect(tapped, isTrue);
});

testWidgets(
'onTap fires when tapping a transparent region of the anchor',
(tester) async {
// A settings-row-style anchor: the interactive content (a label) sits
// on the left, leaving the centre/right transparent. With the
// GestureDetector defaulting to deferToChild, a tap on the transparent
// centre missed the gesture entirely, so a real user tapping the blank
// part of a row, and every centre-of-element tap from a driver
// (dusk / Playwright), silently did nothing. The whole anchor bounds
// must be tappable.
bool tapped = false;
await tester.pumpWidget(
wrapWithTheme(
WAnchor(
onTap: () => tapped = true,
child: const SizedBox(
width: 300,
height: 60,
child: Align(
alignment: Alignment.centerLeft,
child: Text('Dark'),
),
),
),
),
);

// Tap the centre of the anchor: a transparent region with no opaque
// child under it (the label is pinned to the far left).
await tester.tapAt(tester.getCenter(find.byType(WAnchor)));
await tester.pump();
expect(tapped, isTrue);
});

testWidgets('onLongPress callback fires', (tester) async {
bool pressed = false;
await tester.pumpWidget(
Expand Down
Loading