maheshj01 / searchfield

A highly customizable simple and easy to use flutter Widget to add a searchfield to your Flutter Application.This Widget allows you to search and select from list of suggestions.
MIT License
84 stars 63 forks source link

Shift+Tab should respect sequence of SearchField in a form with SuggestionState.hidden #125

Closed DessisInformatica closed 8 months ago

DessisInformatica commented 8 months ago

Describe the bug If the searchfield is in a form, move to the next field and try to return with shift+tab, it stops working, that is, it only returns to the inner field with 3x shift+tab, and if before 3x, it stops selecting the desired option. It seems to me that it is lost. The problem occurs when the searchfield is between other TextFormField's and suggestionState is marked as Suggestion.hidden, so that when you return with shift+tab, you cannot navigate between the options with the Tab

To Reproduce Try to return with shift+tab after passing through the searchfield field (when the searchfield is between other TextFormField's and suggestionState is marked as Suggestion.hidden)

Expected behavior Just respect the sequence of the fields

Code sample

Show code sample ```dart import 'package:flutter/material.dart'; import 'package:searchfield/searchfield.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter App', theme: ThemeData( colorSchemeSeed: Colors.indigo, useMaterial3: true, brightness: Brightness.light, ), darkTheme: ThemeData( colorSchemeSeed: Colors.blue, useMaterial3: true, brightness: Brightness.dark, ), home: SearchFieldSample(), debugShowCheckedModeBanner: false, ); } } class SearchFieldSample extends StatefulWidget { const SearchFieldSample({Key? key}) : super(key: key); @override State createState() => _SearchFieldSampleState(); } class _SearchFieldSampleState extends State { int suggestionsCount = 12; final focus1 = FocusNode(); final focus2 = FocusNode(); final focus3 = FocusNode(); @override void initState() { suggestions = [ 'United States', 'Germany', 'Washington', 'Paris', 'Jakarta', 'Australia', 'India', 'Czech Republic', 'Lorem Ipsum', ]; super.initState(); } final TextEditingController searchController = TextEditingController(); var suggestions = []; int counter = 0; @override Widget build(BuildContext context) { Widget searchChild(x) => Padding( padding: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 12), child: Text(x, style: TextStyle(fontSize: 18, color: Colors.black)), ); return Scaffold( appBar: AppBar(title: Text('Searchfield Keyboard Support')), floatingActionButton: FloatingActionButton( onPressed: () { setState(() { suggestionsCount++; counter++; suggestions.add('suggestion $suggestionsCount'); }); }, child: Icon(Icons.add), ), body: Padding( padding: const EdgeInsets.all(8.0), child: Column( mainAxisAlignment: MainAxisAlignment.start, children: [ TextFormField( focusNode: focus1, textInputAction: TextInputAction.next, autovalidateMode: AutovalidateMode.onUserInteraction, decoration: InputDecoration( labelText: 'Flutter TextFormField', ), validator: (value) { if (value == null || value.length < 4) { return 'error'; } return null; }), SizedBox( height: 50, ), SearchField( focusNode: focus2, suggestionDirection: SuggestionDirection.flex, onSearchTextChanged: (query) { final filter = suggestions .where((element) => element.toLowerCase().contains(query.toLowerCase())) .toList(); return filter .map((e) => SearchFieldListItem(e, child: searchChild(e))) .toList(); }, onTap: () {}, controller: searchController, autovalidateMode: AutovalidateMode.onUserInteraction, validator: (value) { if (value == null || !suggestions.contains(value.trim())) { return 'Enter a valid country name'; } return null; }, onSubmit: (x) {}, autofocus: false, key: const Key('searchfield'), hint: 'Search by country name', itemHeight: 50, onTapOutside: (x) { // focus.unfocus(); }, suggestionStyle: const TextStyle(fontSize: 18, color: Colors.black), searchStyle: TextStyle(fontSize: 18, color: Colors.black), suggestionItemDecoration: BoxDecoration( // color: Colors.grey[100], // borderRadius: BorderRadius.circular(10), border: Border( bottom: BorderSide( color: Colors.grey.shade200, width: 1, ), ), ), searchInputDecoration: InputDecoration( hintStyle: TextStyle(fontSize: 18, color: Colors.grey), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(24), borderSide: const BorderSide( width: 1, color: Colors.orange, style: BorderStyle.solid, ), ), border: OutlineInputBorder( borderRadius: BorderRadius.circular(24), borderSide: const BorderSide( width: 1, color: Colors.black, style: BorderStyle.solid, ), ), fillColor: Colors.white, filled: true, contentPadding: const EdgeInsets.symmetric( horizontal: 20, ), ), suggestionsDecoration: SuggestionDecoration( // border: Border.all(color: Colors.orange), // elevation: 8.0, selectionColor: Colors.grey.shade100, hoverColor: Colors.purple.shade100, gradient: LinearGradient( colors: [ Color(0xfffc466b), Color.fromARGB(255, 103, 128, 255) ], stops: [0.25, 0.75], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.only( bottomLeft: Radius.circular(10), bottomRight: Radius.circular(10), )), suggestions: suggestions .map((e) => SearchFieldListItem(e, child: searchChild(e))) .toList(), suggestionState: Suggestion.hidden, onSuggestionTap: (SearchFieldListItem x) { print('suggestion'); FocusScope.of(context).requestFocus(focus3); }, ), SizedBox( height: 50, ), TextFormField( focusNode: focus3, autovalidateMode: AutovalidateMode.onUserInteraction, decoration: InputDecoration( labelText: 'Flutter TextFormField', ), validator: (value) { if (value == null || value.length < 4) { return 'error'; } return null; }), ], ), )); } } ```
DessisInformatica commented 8 months ago

In fact, it "returns" to the options, even if the boxsuggestions are hidden, giving the impression of having to press shift+tab several times. Somehow, the component should only leave the main field if a previous option is already selected when executing shift+tab, as it behaves in the normal tab (advancing).

DessisInformatica commented 8 months ago

This exception is occurring:

Another exception was thrown: Assertion failed: file:///C:/flutter/packages/flutter/lib/src/widgets/overlay.dart:207:12 Another exception was thrown: This widget has been unmounted, so the State no longer has a context (and should be considered defunct).

maheshj01 commented 8 months ago

Hello @DessisInformatica, Thanks for filing the issue, and Good catch. Will get that fixed soon.

maheshj01 commented 8 months ago

Hi @DessisInformatica, Navigating between suggestions with the tab key should only work if suggestions are visible on the screen, Is this the intended behavior? Searchfield will only lose focus (switch to other widget) with Tab key when Overlay (Suggestions)is not shown.

maheshj01 commented 8 months ago

I have made a PR as per the above fix can you please test this behavior by adding the following to pubspec.yaml

  searchfield:
    git:
      url: https://github.com/maheshmnj/searchfield.git
      ref: fix-125
DessisInformatica commented 8 months ago

Thanks for answering.

I took the test, but this issue is still not good.

Follow the code. If you test, you will see that the sequence between widgets appears to be a problem.

Thank you for your attention.

import 'package:flutter/material.dart';
import 'package:searchfield/searchfield.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter App',
      theme: ThemeData(
        colorSchemeSeed: Colors.indigo,
        useMaterial3: true,
        brightness: Brightness.light,
      ),
      darkTheme: ThemeData(
        colorSchemeSeed: Colors.blue,
        useMaterial3: true,
        brightness: Brightness.dark,
      ),
      home: SearchFieldSample(),
      debugShowCheckedModeBanner: false,
    );
  }
}

class SearchFieldSample extends StatefulWidget {
  const SearchFieldSample({Key? key}) : super(key: key);

  @override
  State<SearchFieldSample> createState() => _SearchFieldSampleState();
}

class _SearchFieldSampleState extends State<SearchFieldSample> {
  int suggestionsCount = 12;
  final foco1 = FocusNode();
  final foco2 = FocusNode();
  final foco3 = FocusNode();
  TextEditingController cont1 = TextEditingController();
  TextEditingController cont2 = TextEditingController();
  TextEditingController cont3 = TextEditingController();

  @override
  void initState() {
    suggestions = [
      'United States',
      'Germany',
      'Washington',
      'Paris',
      'Jakarta',
      'Australia',
      'India',
      'Czech Republic',
      'Lorem Ipsum',
    ];
    super.initState();
  }

  final TextEditingController searchController = TextEditingController();
  var suggestions = <String>[];
  int counter = 0;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(title: const Text('Searchfield Keyboard Support')),
        body: Padding(
          padding: const EdgeInsets.all(8.0),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.start,
            children: [
              SizedBox(
                width: 200,
                height: 50,
                child: SearchField(
                  focusNode: foco1,
                  controller: cont1,
                  hint: 'Basic SearchField',
                  suggestions: ['ABC', 'DEF', 'GHI', 'JKL']
                      .map(SearchFieldListItem<String>.new)
                      .toList(),
                  suggestionState: Suggestion.expand,
                ),
              ),
              SizedBox(
                width: 200,
                height: 50,
                child: SearchField(
                  focusNode: foco2,
                  controller: cont2,
                  hint: 'Basic SearchField',
                  suggestions: ['ABC', 'DEF', 'GHI', 'JKL']
                      .map(SearchFieldListItem<String>.new)
                      .toList(),
                  suggestionState: Suggestion.expand,
                ),
              ),
              SizedBox(
                width: 200,
                height: 50,
                child: SearchField(
                  focusNode: foco3,
                  controller: cont3,
                  hint: 'Basic SearchField',
                  suggestions: ['ABC', 'DEF', 'GHI', 'JKL']
                      .map(SearchFieldListItem<String>.new)
                      .toList(),
                  suggestionState: Suggestion.expand,
                ),
              ),
            ],
          ),
        ));
  }
}
maheshj01 commented 8 months ago

Thanks for the response @DessisInformatica, Can you please clarify how the behavior should be? This is currently as described above.

Searchfield will only lose focus (switch to other widget) with Tab key when Overlay (Suggestions)is not shown.

maheshj01 commented 8 months ago

Alternatively, can you test this behavior where I have commented this line

https://github.com/maheshmnj/searchfield/blob/ef0b40accaf8041c6a852c727fb5324c5e8cbb43/lib/src/searchfield.dart#L755

But you won't be able to navigate between suggestions using tab key.

maheshj01 commented 8 months ago

Upon investigating little about different web components, I have found that tab/shift+tab should only switch focus to other components and not navigate the suggestions. So the above fix should work. Looking forward to hearing from you @DessisInformatica

This behavior is found in https://api.flutter.dev/flutter/material/Autocomplete-class.html and also in https://mui.com/material-ui/react-autocomplete/#playground

maheshj01 commented 8 months ago

This will be published in the upcoming release v1.0.0