caduandrade / tabbed_view

Widget inspired by the classic Desktop-style tab component.
MIT License
49 stars 16 forks source link

TAB traversal, edit fields in invisible tab do get focus #48

Open Haugpro opened 1 month ago

Haugpro commented 1 month ago

Hi and thanks first for this helpful component. In my opinion the flutter focus system is not very easy to grasp, so I ask for the best way to solve this problem: When I create some tabs in tabbed_view and some or all of them contain widgets that might get focus, they really do get focus via TAB traversal, even if the tab content is not visible (not the active tab). If I have tab #3 content actually visible and use the TAB key so that the edit field in the invisible tab #1 gets focus, all text input now goes to that invisible edit field. Is there a recommended way to block invisible pages from receiving focus? Thanks

caduandrade commented 1 month ago

Hi @Haugpro!

I'll see how I did to keep the tab invisible. Maybe I can wrap the component with some Widget to prevent this behavior.

caduandrade commented 1 month ago

This example demonstrates your case right?

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

void main() {
  runApp(TabbedViewExample());
}

class TabbedViewExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        debugShowCheckedModeBanner: false, home: TabbedViewExamplePage());
  }
}

class TabbedViewExamplePage extends StatefulWidget {
  @override
  _TabbedViewExamplePageState createState() => _TabbedViewExamplePageState();
}

class _TabbedViewExamplePageState extends State<TabbedViewExamplePage> {
  late TabbedViewController _controller;

  @override
  void initState() {
    super.initState();
    List<TabData> tabs = [];

    for(int i = 1 ; i<10;i++) {
      tabs.add(TabData(
          text: 'Tab $i',
          leading: (context, status) => Icon(Icons.star, size: 16),
          content: Padding(child: Text('Hello $i'), padding: EdgeInsets.all(8))));
    }
    tabs.add(TabData(
        closable: false,
        text: 'TextField 1',
        content: Padding(
            child: TextField(
                decoration: InputDecoration(
                    isDense: true, border: OutlineInputBorder())),
            padding: EdgeInsets.all(8)),
        keepAlive: true));

    tabs.add(TabData(
        closable: false,
        text: 'TextField 2',
        content: Padding(
            child: TextField(
                decoration: InputDecoration(
                    isDense: true, border: OutlineInputBorder())),
            padding: EdgeInsets.all(8)),
        keepAlive: true));

    _controller = TabbedViewController(tabs);
  }

  @override
  Widget build(BuildContext context) {
    TabbedView tabbedView = TabbedView(controller: _controller);
    Widget w =
        TabbedViewTheme(child: tabbedView, data: TabbedViewThemeData.mobile());
    return Scaffold(body: Container(child: w, padding: EdgeInsets.all(32)));
  }
}
caduandrade commented 1 month ago

I believe that solved the problem.

In the previous example, it was necessary to click the TAB key 3 times to return to the same text field. The first click went to the second (tab not selected), the second click removed focus (default behavior), and the third click returned to the first text field.

With the commit, it is now 2 clicks. The second text field of the unselected tab does not gain focus.

If you want to test the main before releasing the version, just clone the repo and point to the local directory.

Haugpro commented 1 month ago

Hi Carlos, thanks for this solution. I tried a lot over the weekend and finally found the roughly same answer. I now wrap the indendet content widget in an additional stateful widget which conditionally adds an ExcludeFocus at the top of the tree. I toggle that in the onTabSelection callback. A bit strange however was that I then lost all state in my tab-content since that ExcludeFocus toggle forces a rebuild; I say it's strange, since my tab-content has a GlobaKey. Finally I wrapped the content with a KeyedSubtree and that works.

caduandrade commented 1 month ago

With the API fix, I believe you won't need to create these controls. I'll release a new version.

Haugpro commented 1 month ago

supi