From 77fd24faca96dcbb09bfe66fb4dff096d59f5c11 Mon Sep 17 00:00:00 2001 From: lucas-goldner Date: Thu, 4 Jun 2026 13:23:00 +0900 Subject: [PATCH 1/3] fix: Enhance button behavior for disabled state --- .../lib/src/material/button_style_button.dart | 10 +++ .../test/material/elevated_button_test.dart | 69 +++++++++++++------ 2 files changed, 59 insertions(+), 20 deletions(-) diff --git a/packages/flutter/lib/src/material/button_style_button.dart b/packages/flutter/lib/src/material/button_style_button.dart index 71be80bcc85c5..41f110ebace4e 100644 --- a/packages/flutter/lib/src/material/button_style_button.dart +++ b/packages/flutter/lib/src/material/button_style_button.dart @@ -571,6 +571,16 @@ class _ButtonStyleState extends State with TickerProviderStat ), ); + // When the button is disabled, absorb taps to prevent them from + // propagating to parent gesture detectors. + // See https://github.com/flutter/flutter/issues/138981 + result = GestureDetector( + onTap: widget.enabled ? null : () {}, + behavior: HitTestBehavior.opaque, + excludeFromSemantics: true, + child: result, + ); + if (widget.tooltip != null) { result = Tooltip(message: widget.tooltip, child: result); } diff --git a/packages/flutter/test/material/elevated_button_test.dart b/packages/flutter/test/material/elevated_button_test.dart index 64bfb34576793..eca9e243c7c40 100644 --- a/packages/flutter/test/material/elevated_button_test.dart +++ b/packages/flutter/test/material/elevated_button_test.dart @@ -1629,30 +1629,32 @@ void main() { } }); - testWidgets('ElevatedButton uses InkSparkle only for Android non-web when useMaterial3 is true', ( - WidgetTester tester, - ) async { - final theme = ThemeData(); + testWidgets( + 'ElevatedButton uses InkSparkle only for Android non-web when useMaterial3 is true', + (WidgetTester tester) async { + final theme = ThemeData(); - await tester.pumpWidget( - MaterialApp( - theme: theme, - home: Center( - child: ElevatedButton(onPressed: () {}, child: const Text('button')), + await tester.pumpWidget( + MaterialApp( + theme: theme, + home: Center( + child: ElevatedButton(onPressed: () {}, child: const Text('button')), + ), ), - ), - ); + ); - final InkWell buttonInkWell = tester.widget( - find.descendant(of: find.byType(ElevatedButton), matching: find.byType(InkWell)), - ); + final InkWell buttonInkWell = tester.widget( + find.descendant(of: find.byType(ElevatedButton), matching: find.byType(InkWell)), + ); - if (debugDefaultTargetPlatformOverride! == TargetPlatform.android && !kIsWeb) { - expect(buttonInkWell.splashFactory, equals(InkSparkle.splashFactory)); - } else { - expect(buttonInkWell.splashFactory, equals(InkRipple.splashFactory)); - } - }, variant: TargetPlatformVariant.all()); + if (debugDefaultTargetPlatformOverride! == TargetPlatform.android && !kIsWeb) { + expect(buttonInkWell.splashFactory, equals(InkSparkle.splashFactory)); + } else { + expect(buttonInkWell.splashFactory, equals(InkRipple.splashFactory)); + } + }, + variant: TargetPlatformVariant.all(), + ); testWidgets('ElevatedButton uses InkRipple when useMaterial3 is false', ( WidgetTester tester, @@ -2692,4 +2694,31 @@ void main() { // The button should still be focused. expect(getButtonFocusNode().hasFocus, true); }); + + // Regression test for https://github.com/flutter/flutter/issues/138981 + testWidgets('Disabled ElevatedButton does not let taps pass through to parent GestureDetector', ( + WidgetTester tester, + ) async { + var parentTapCount = 0; + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: GestureDetector( + onTap: () { + parentTapCount++; + }, + child: const Center(child: ElevatedButton(onPressed: null, child: Text('Disabled'))), + ), + ), + ), + ); + + // Tap the disabled button. + await tester.tap(find.byType(ElevatedButton)); + await tester.pumpAndSettle(); + + // The parent GestureDetector should NOT have received the tap. + expect(parentTapCount, 0); + }); } From f80d5b96d580ccccd90924fa40e5394c7a19d390 Mon Sep 17 00:00:00 2001 From: lucas-goldner Date: Thu, 4 Jun 2026 13:27:40 +0900 Subject: [PATCH 2/3] fix: Update button behavior for enabled and disabled states --- packages/flutter/lib/src/material/button_style_button.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/flutter/lib/src/material/button_style_button.dart b/packages/flutter/lib/src/material/button_style_button.dart index 41f110ebace4e..b10b62a0fbd36 100644 --- a/packages/flutter/lib/src/material/button_style_button.dart +++ b/packages/flutter/lib/src/material/button_style_button.dart @@ -576,7 +576,7 @@ class _ButtonStyleState extends State with TickerProviderStat // See https://github.com/flutter/flutter/issues/138981 result = GestureDetector( onTap: widget.enabled ? null : () {}, - behavior: HitTestBehavior.opaque, + behavior: widget.enabled ? HitTestBehavior.deferToChild : HitTestBehavior.opaque, excludeFromSemantics: true, child: result, ); From c742fe7fa8a016e89c3f952284ee07c01b1849c8 Mon Sep 17 00:00:00 2001 From: lucas-goldner Date: Tue, 9 Jun 2026 12:50:09 +0900 Subject: [PATCH 3/3] chore: Format elevated button test --- .../test/material/elevated_button_test.dart | 42 +++++++++---------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/packages/flutter/test/material/elevated_button_test.dart b/packages/flutter/test/material/elevated_button_test.dart index eca9e243c7c40..7c37105e3dbb2 100644 --- a/packages/flutter/test/material/elevated_button_test.dart +++ b/packages/flutter/test/material/elevated_button_test.dart @@ -1629,32 +1629,30 @@ void main() { } }); - testWidgets( - 'ElevatedButton uses InkSparkle only for Android non-web when useMaterial3 is true', - (WidgetTester tester) async { - final theme = ThemeData(); + testWidgets('ElevatedButton uses InkSparkle only for Android non-web when useMaterial3 is true', ( + WidgetTester tester, + ) async { + final theme = ThemeData(); - await tester.pumpWidget( - MaterialApp( - theme: theme, - home: Center( - child: ElevatedButton(onPressed: () {}, child: const Text('button')), - ), + await tester.pumpWidget( + MaterialApp( + theme: theme, + home: Center( + child: ElevatedButton(onPressed: () {}, child: const Text('button')), ), - ); + ), + ); - final InkWell buttonInkWell = tester.widget( - find.descendant(of: find.byType(ElevatedButton), matching: find.byType(InkWell)), - ); + final InkWell buttonInkWell = tester.widget( + find.descendant(of: find.byType(ElevatedButton), matching: find.byType(InkWell)), + ); - if (debugDefaultTargetPlatformOverride! == TargetPlatform.android && !kIsWeb) { - expect(buttonInkWell.splashFactory, equals(InkSparkle.splashFactory)); - } else { - expect(buttonInkWell.splashFactory, equals(InkRipple.splashFactory)); - } - }, - variant: TargetPlatformVariant.all(), - ); + if (debugDefaultTargetPlatformOverride! == TargetPlatform.android && !kIsWeb) { + expect(buttonInkWell.splashFactory, equals(InkSparkle.splashFactory)); + } else { + expect(buttonInkWell.splashFactory, equals(InkRipple.splashFactory)); + } + }, variant: TargetPlatformVariant.all()); testWidgets('ElevatedButton uses InkRipple when useMaterial3 is false', ( WidgetTester tester,