diff --git a/lib/widgets/clearing_dropdown_menu.dart b/lib/widgets/clearing_dropdown_menu.dart new file mode 100644 index 0000000..62b2a88 --- /dev/null +++ b/lib/widgets/clearing_dropdown_menu.dart @@ -0,0 +1,81 @@ +import 'package:flutter/material.dart'; + +class ClearingDropdownMenu extends StatefulWidget { + final List> entries; + final T? initialValue; + final void Function(T? item)? onSelected; + final String? label; + + const ClearingDropdownMenu({ + super.key, + required this.entries, + this.initialValue, + this.onSelected, + this.label, + }); + + @override + State> createState() => + _ClearingDropdownMenuState(); +} + +class _ClearingDropdownMenuState extends State> { + late final TextEditingController _controller; + final FocusNode _focusNode = FocusNode(); + T? _selectedValue; + + @override + void initState() { + super.initState(); + _selectedValue = widget.initialValue; + _controller = TextEditingController( + text: _selectedValue != null + ? widget.entries + .firstWhere((entry) => entry.value == _selectedValue) + .label + : ''); + + _focusNode.addListener(_onFocusChange); + } + + void _onFocusChange() { + if (!_focusNode.hasFocus) { + // Check if the current text matches any entry + final validEntry = widget.entries.any((entry) => + entry.label.toLowerCase() == _controller.text.toLowerCase()); + + if (!validEntry) { + // Clear the text and selection + _controller.clear(); + setState(() { + _selectedValue = null; + }); + widget.onSelected?.call(null); + } + } + } + + @override + Widget build(BuildContext context) { + return DropdownMenu( + controller: _controller, + focusNode: _focusNode, + initialSelection: widget.initialValue, + onSelected: (T? value) { + setState(() { + _selectedValue = value; + }); + widget.onSelected?.call(value); + }, + dropdownMenuEntries: widget.entries, + label: widget.label != null ? Text(widget.label!) : null, + ); + } + + @override + void dispose() { + _controller.dispose(); + _focusNode.dispose(); + super.dispose(); + } +}