From fc3fc2e13b8fb03af674136569dbc011bca3028e Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Sun, 11 Oct 2026 15:51:47 +0300 Subject: [PATCH] fix(ui): the dropdown menu is at least as wide as its trigger The trigger is measured when the menu opens and becomes the menu's minimum width, so the open menu lines up with the field. Refs #1444 --- lib/shared/widgets/querya_dropdown.dart | 14 +++++++-- test/shared/querya_dropdown_test.dart | 40 +++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/lib/shared/widgets/querya_dropdown.dart b/lib/shared/widgets/querya_dropdown.dart index 53c9b942..b917d6d2 100644 --- a/lib/shared/widgets/querya_dropdown.dart +++ b/lib/shared/widgets/querya_dropdown.dart @@ -88,6 +88,10 @@ class _QueryaDropdownState extends material.State> { var _closingWithExit = false; var _exitGeneration = 0; + /// Keys the trigger so the open menu can be at least as wide as it (#1444). + final material.GlobalKey _triggerKey = material.GlobalKey(); + double _menuMinWidth = 0; + @override void initState() { super.initState(); @@ -359,6 +363,8 @@ class _QueryaDropdownState extends material.State> { _exitGeneration++; _closingWithExit = false; _menuOpen.value = true; + final width = _triggerKey.currentContext?.size?.width ?? 0; + if (width != _menuMinWidth) setState(() => _menuMinWidth = width); }, onClose: () { // Outside-tap / focus loss: overlay already gone — snap state only. @@ -381,6 +387,9 @@ class _QueryaDropdownState extends material.State> { shadowColor: const material.WidgetStatePropertyAll( QueryaDropdownTokens.menuShadowColor, ), + minimumSize: material.WidgetStatePropertyAll( + material.Size(_menuMinWidth, 0), + ), maximumSize: material.WidgetStatePropertyAll( material.Size(double.infinity, effectiveMaxHeight), ), @@ -412,10 +421,11 @@ class _QueryaDropdownState extends material.State> { label: label, fieldWidth: fieldWidth, ); + final keyed = material.KeyedSubtree(key: _triggerKey, child: trigger); if (widget.expandToParent) { - return material.SizedBox(width: double.infinity, child: trigger); + return material.SizedBox(width: double.infinity, child: keyed); } - return trigger; + return keyed; }, ); diff --git a/test/shared/querya_dropdown_test.dart b/test/shared/querya_dropdown_test.dart index 63998360..91e8d5b3 100644 --- a/test/shared/querya_dropdown_test.dart +++ b/test/shared/querya_dropdown_test.dart @@ -46,6 +46,46 @@ void main() { expect(box.height, QueryaDropdownTokens.triggerHeight); }); + testWidgets('the open menu is at least as wide as the trigger', + (tester) async { + await tester.pumpWidget( + queryaThemeTestShell( + child: material.Scaffold( + body: material.Align( + alignment: material.Alignment.topLeft, + child: QueryaDropdown( + value: 1, + width: 320, + items: const [ + QueryaDropdownItem(value: 1, label: 'One'), + QueryaDropdownItem(value: 2, label: 'Two'), + ], + onSelected: (_) {}, + ), + ), + ), + ), + ); + await tester.pump(); + + final triggerWidth = tester.getSize(find.byType(QueryaDropdown)).width; + expect(triggerWidth, 320); + + await tester.tap(find.text('One')); + await tester.pumpAndSettle(); + + final surfaces = find.ancestor( + of: find.text('Two'), + matching: find.byType(material.Material), + ); + final widest = surfaces + .evaluate() + .map((e) => tester.getSize(find.byWidget(e.widget)).width) + .reduce((a, b) => a > b ? a : b); + + expect(widest, greaterThanOrEqualTo(triggerWidth)); + }); + testWidgets('opens menu and reports selection', (tester) async { int? picked; await tester.pumpWidget(