1+ import 'package:collection/collection.dart' ;
12import 'package:flet/flet.dart' ;
23import 'package:flutter/material.dart' ;
34import 'package:flutter/scheduler.dart' ;
@@ -16,21 +17,27 @@ class DropdownControl extends StatefulWidget {
1617class _DropdownControlState extends State <DropdownControl > {
1718 late final FocusNode _focusNode;
1819 late final TextEditingController _controller;
20+ String ? _value;
21+ bool _suppressTextChange = false ;
1922
2023 @override
2124 void initState () {
2225 super .initState ();
23- _focusNode = FocusNode ();
2426
25- _controller = TextEditingController (text: widget.control.getString ("text" ));
27+ // initialize controller
28+ _value = widget.control.getString ("value" );
29+ final text = widget.control.getString ("text" ) ?? _value ?? "" ;
30+ _controller = TextEditingController (text: text);
31+ _controller.addListener (_onTextChange);
32+
33+ _focusNode = FocusNode ();
2634 _focusNode.addListener (_onFocusChange);
2735 widget.control.addInvokeMethodListener (_invokeMethod);
28-
29- _controller.addListener (_onTextChange);
3036 }
3137
3238 void _onTextChange () {
33- debugPrint ("Typed text: ${_controller .text }" );
39+ if (_suppressTextChange) return ;
40+
3441 if (_controller.text != widget.control.getString ("text" )) {
3542 widget.control.updateProperties ({"text" : _controller.text});
3643 widget.control.triggerEvent ("text_change" , _controller.text);
@@ -41,6 +48,17 @@ class _DropdownControlState extends State<DropdownControl> {
4148 widget.control.triggerEvent (_focusNode.hasFocus ? "focus" : "blur" );
4249 }
4350
51+ /// Updates text without triggering a text change event.
52+ void _updateControllerText (String text) {
53+ _suppressTextChange = true ;
54+ _controller.value = TextEditingValue (
55+ text: text,
56+ selection: TextSelection .collapsed (offset: text.length),
57+ );
58+ _suppressTextChange = false ;
59+ widget.control.updateProperties ({"text" : text}, python: false );
60+ }
61+
4462 @override
4563 void dispose () {
4664 _focusNode.removeListener (_onFocusChange);
@@ -65,13 +83,12 @@ class _DropdownControlState extends State<DropdownControl> {
6583 debugPrint ("DropdownMenu build: ${widget .control .id }" );
6684
6785 var theme = Theme .of (context);
68- bool editable = widget.control.getBool ("editable" , false )! ;
69- bool autofocus = widget.control.getBool ("autofocus" , false )! ;
86+ var editable = widget.control.getBool ("editable" , false )! ;
87+ var autofocus = widget.control.getBool ("autofocus" , false )! ;
7088 var textSize = widget.control.getDouble ("text_size" );
7189 var color = widget.control.getColor ("color" , context);
7290
73- TextAlign textAlign =
74- widget.control.getTextAlign ("text_align" , TextAlign .start)! ;
91+ var textAlign = widget.control.getTextAlign ("text_align" , TextAlign .start)! ;
7592
7693 var fillColor = widget.control.getColor ("fill_color" , context);
7794 var borderColor = widget.control.getColor ("border_color" , context);
@@ -85,7 +102,7 @@ class _DropdownControlState extends State<DropdownControl> {
85102 var bgColor = widget.control.getWidgetStateColor ("bgcolor" , theme);
86103 var elevation = widget.control.getWidgetStateDouble ("elevation" );
87104
88- FormFieldInputBorder inputBorder = widget.control
105+ var inputBorder = widget.control
89106 .getFormFieldInputBorder ("border" , FormFieldInputBorder .outline)! ;
90107
91108 InputBorder ? border;
@@ -115,7 +132,7 @@ class _DropdownControlState extends State<DropdownControl> {
115132 ? BorderSide .none
116133 : BorderSide (
117134 color: borderColor ??
118- theme.colorScheme.onSurface.withOpacity ( 0.38 ),
135+ theme.colorScheme.onSurface.withValues (alpha : 0.38 ),
119136 width: borderWidth ?? 1.0 ));
120137 }
121138 }
@@ -176,30 +193,54 @@ class _DropdownControlState extends State<DropdownControl> {
176193 color: color ?? theme.colorScheme.onSurface);
177194 }
178195
179- var items = widget.control
196+ // build dropdown items
197+ var options = widget.control
180198 .children ("options" )
181- .map <DropdownMenuEntry <String >>((Control itemCtrl) {
182- bool itemDisabled = widget.control.disabled || itemCtrl.disabled;
183- ButtonStyle ? style = itemCtrl.getButtonStyle ("style" , theme);
184-
185- return DropdownMenuEntry <String >(
186- enabled: ! itemDisabled,
187- value: itemCtrl.getString ("key" ) ??
188- itemCtrl.getString ("text" ) ??
189- itemCtrl.id.toString (),
190- label: itemCtrl.getString ("text" ) ??
191- itemCtrl.getString ("key" ) ??
192- itemCtrl.id.toString (),
193- labelWidget: itemCtrl.buildWidget ("content" ),
194- leadingIcon: itemCtrl.buildIconOrWidget ("leading_icon" ),
195- trailingIcon: itemCtrl.buildIconOrWidget ("trailing_icon" ),
196- style: style,
197- );
198- }).toList ();
199-
200- String ? value = widget.control.getString ("value" );
201- if (items.where ((item) => item.value == value).isEmpty) {
202- value = null ;
199+ .map <DropdownMenuEntry <String >?>((Control itemCtrl) {
200+ bool itemDisabled = widget.control.disabled || itemCtrl.disabled;
201+ ButtonStyle ? style = itemCtrl.getButtonStyle ("style" , theme);
202+
203+ var optionKey = itemCtrl.getString ("key" );
204+ var optionText = itemCtrl.getString ("text" );
205+
206+ var optionValue = optionKey ?? optionText;
207+ var optionLabel = optionText ?? optionKey;
208+ if (optionValue == null || optionLabel == null ) {
209+ return null ;
210+ }
211+
212+ return DropdownMenuEntry <String >(
213+ enabled: ! itemDisabled,
214+ value: optionValue,
215+ label: optionLabel,
216+ labelWidget: itemCtrl.buildWidget ("content" ),
217+ leadingIcon: itemCtrl.buildIconOrWidget ("leading_icon" ),
218+ trailingIcon: itemCtrl.buildIconOrWidget ("trailing_icon" ),
219+ style: style,
220+ );
221+ })
222+ .nonNulls
223+ .toList ();
224+
225+ var value = widget.control.getString ("value" );
226+ var selectedOption = options.firstWhereOrNull ((o) => o.value == value);
227+ value = selectedOption? .value;
228+
229+ // keep controller text in sync with backend-driven value changes
230+ if (_value != value) {
231+ if (value == null ) {
232+ if (_value != null && _controller.text.isNotEmpty) {
233+ // clears dropdown field
234+ _updateControllerText ("" );
235+ }
236+ } else {
237+ final String entryLabel =
238+ selectedOption? .label ?? widget.control.getString ("text" ) ?? value;
239+ if (_controller.text != entryLabel) {
240+ _updateControllerText (entryLabel);
241+ }
242+ }
243+ _value = value;
203244 }
204245
205246 TextCapitalization textCapitalization = widget.control
@@ -237,20 +278,16 @@ class _DropdownControlState extends State<DropdownControl> {
237278 errorText: widget.control.getString ("error_text" ),
238279 hintText: widget.control.getString ("hint_text" ),
239280 helperText: widget.control.getString ("helper_text" ),
240- // menuStyle: MenuStyle(
241- // backgroundColor: widget.control.getWidgetStateColor("bgcolor", theme),
242- // elevation: widget.control.getWidgetStateDouble("elevation"),
243- // fixedSize: WidgetStateProperty.all(Size.fromWidth(menuWidth)),
244- // ),
245281 menuStyle: menuStyle,
246282 inputDecorationTheme: inputDecorationTheme,
247283 onSelected: widget.control.disabled
248284 ? null
249- : (String ? value) {
250- widget.control.updateProperties ({"value" : value});
251- widget.control.triggerEvent ("select" , value);
285+ : (String ? selection) {
286+ _value = selection;
287+ widget.control.updateProperties ({"value" : selection});
288+ widget.control.triggerEvent ("select" , selection);
252289 },
253- dropdownMenuEntries: items ,
290+ dropdownMenuEntries: options ,
254291 );
255292
256293 var didAutoFocus = false ;
0 commit comments