Revert "dont allow focus on buttons"

This reverts commit dbe43897b6.
This commit is contained in:
Fabian Freund
2026-01-14 05:22:10 +01:00
parent 057d14b6d9
commit 53df72d906
5 changed files with 320 additions and 424 deletions
@@ -57,7 +57,6 @@ import 'package:weblibre/features/user/domain/repositories/general_settings.dart
import 'package:weblibre/presentation/hooks/cached_future.dart'; import 'package:weblibre/presentation/hooks/cached_future.dart';
import 'package:weblibre/presentation/hooks/menu_controller.dart'; import 'package:weblibre/presentation/hooks/menu_controller.dart';
import 'package:weblibre/presentation/icons/tor_icons.dart'; import 'package:weblibre/presentation/icons/tor_icons.dart';
import 'package:weblibre/presentation/widgets/non_focusable.dart';
import 'package:weblibre/presentation/widgets/selectable_chips.dart'; import 'package:weblibre/presentation/widgets/selectable_chips.dart';
import 'package:weblibre/presentation/widgets/url_icon.dart'; import 'package:weblibre/presentation/widgets/url_icon.dart';
@@ -326,90 +325,78 @@ class BrowserTabBar extends HookConsumerWidget {
return Visibility( return Visibility(
visible: tabBarReaderView || readerabilityStateActive, visible: tabBarReaderView || readerabilityStateActive,
child: NonFocusable( child: ReaderButton(
child: ReaderButton( buttonBuilder: (isLoading, readerActive, icon) =>
buttonBuilder: (isLoading, readerActive, icon) => InkWell(
InkWell( onTap: isLoading
onTap: isLoading ? null
? null : () async {
: () async { await ref
await ref .read(
.read( readerableScreenControllerProvider
readerableScreenControllerProvider .notifier,
.notifier, )
) .toggleReaderView(!readerActive);
.toggleReaderView( },
!readerActive, child: Padding(
); padding: const EdgeInsets.symmetric(
}, vertical: 15.0,
child: Padding( horizontal: 8.0,
padding: const EdgeInsets.symmetric(
vertical: 15.0,
horizontal: 8.0,
),
child: icon,
), ),
child: icon,
), ),
), ),
), ),
); );
}, },
), ),
if (showExtensionShortcut) if (showExtensionShortcut)
NonFocusable( ExtensionShortcutMenu(
child: ExtensionShortcutMenu( controller: extensionMenuController,
controller: extensionMenuController, child: IconButton(
child: IconButton( onPressed: () {
onPressed: () { if (extensionMenuController.isOpen) {
if (extensionMenuController.isOpen) { extensionMenuController.close();
extensionMenuController.close(); } else {
} else { extensionMenuController.open();
extensionMenuController.open(); }
} },
}, icon: const Icon(MdiIcons.puzzle),
icon: const Icon(MdiIcons.puzzle),
),
), ),
), ),
if (selectedTabId != null) if (selectedTabId != null)
NonFocusable( TabMenu(
child: TabMenu( controller: trippleDotMenuController,
controller: trippleDotMenuController, selectedTabId: selectedTabId,
selectedTabId: selectedTabId, builder: (context, controller, child) {
builder: (context, controller, child) { return InkWell(
return InkWell( onTap: () {
onTap: () { if (controller.isOpen) {
if (controller.isOpen) { controller.close();
controller.close(); } else {
} else { controller.open();
controller.open(); }
} },
}, child: const Padding(
child: const Padding( padding: EdgeInsets.symmetric(
padding: EdgeInsets.symmetric( horizontal: 8.0,
horizontal: 8.0, vertical: 15.0,
vertical: 15.0,
),
child: Icon(MdiIcons.dotsVertical),
), ),
); child: Icon(MdiIcons.dotsVertical),
}, ),
), );
},
), ),
if (showMainToolbarTabsCount) if (showMainToolbarTabsCount)
NonFocusable( TabsCountButton(
child: TabsCountButton( selectedTabId: selectedTabId,
selectedTabId: selectedTabId, displayedSheet: displayedSheet,
displayedSheet: displayedSheet, showLongPressMenu: true,
showLongPressMenu: true,
),
), ),
if (showMainToolbarNavigationButton) if (showMainToolbarNavigationButton)
NonFocusable( NavigationMenuButton(
child: NavigationMenuButton( selectedTabId: selectedTabId,
selectedTabId: selectedTabId, showNavigationButtons: true,
showNavigationButtons: true,
),
), ),
], ],
), ),
@@ -453,42 +440,32 @@ class ContextualToolbar extends HookConsumerWidget {
children: [ children: [
if (tabState?.historyState.canGoBack == true || if (tabState?.historyState.canGoBack == true ||
tabState?.isLoading == true) tabState?.isLoading == true)
NonFocusable( NavigateBackButton(
child: NavigateBackButton( selectedTabId: selectedTabId,
selectedTabId: selectedTabId, isLoading: tabState?.isLoading ?? false,
isLoading: tabState?.isLoading ?? false,
),
) )
else else
NonFocusable( IconButton(
child: IconButton( onPressed: () async {
onPressed: () async { await BookmarkListRoute(
await BookmarkListRoute( entryGuid: BookmarkRoot.root.id,
entryGuid: BookmarkRoot.root.id, ).push(context);
).push(context); },
}, icon: const Icon(MdiIcons.bookmarkMultiple),
icon: const Icon(MdiIcons.bookmarkMultiple),
),
), ),
if (tabState?.historyState.canGoForward == true) if (tabState?.historyState.canGoForward == true)
NonFocusable( NavigateForwardButton(selectedTabId: selectedTabId)
child: NavigateForwardButton(selectedTabId: selectedTabId),
)
else else
NonFocusable(child: ShareMenuButton(selectedTabId: selectedTabId)), ShareMenuButton(selectedTabId: selectedTabId),
const NonFocusable(child: AddTabButton()), const AddTabButton(),
NonFocusable( TabsCountButton(
child: TabsCountButton( selectedTabId: selectedTabId,
selectedTabId: selectedTabId, displayedSheet: displayedSheet,
displayedSheet: displayedSheet, showLongPressMenu: false,
showLongPressMenu: false,
),
), ),
NonFocusable( NavigationMenuButton(
child: NavigationMenuButton( selectedTabId: selectedTabId,
selectedTabId: selectedTabId, showNavigationButtons: false,
showNavigationButtons: false,
),
), ),
], ],
); );
@@ -651,17 +628,15 @@ class ShareMenuButton extends HookConsumerWidget {
ShowQrCodeMenuItemButton(selectedTabId: selectedTabId), ShowQrCodeMenuItemButton(selectedTabId: selectedTabId),
], ],
builder: (context, controller, child) { builder: (context, controller, child) {
return NonFocusable( return IconButton(
child: IconButton( onPressed: () {
onPressed: () { if (controller.isOpen) {
if (controller.isOpen) { controller.close();
controller.close(); } else {
} else { controller.open();
controller.open(); }
} },
}, icon: const Icon(Icons.share),
icon: const Icon(Icons.share),
),
); );
}, },
); );
@@ -730,19 +705,17 @@ class NavigationMenuButton extends HookConsumerWidget {
alignment: WrapAlignment.center, alignment: WrapAlignment.center,
children: [ children: [
...pageExtensions.map( ...pageExtensions.map(
(extension) => NonFocusable( (extension) => IconButton(
child: IconButton( onPressed: () async {
onPressed: () async { //Use parents .ref because after onPressed this consumer gets disposed already
//Use parents .ref because after onPressed this consumer gets disposed already await addonService.invokeAddonAction(
await addonService.invokeAddonAction( extension.extensionId,
extension.extensionId, WebExtensionActionType.page,
WebExtensionActionType.page, );
); },
}, icon: Padding(
icon: Padding( padding: const EdgeInsets.only(top: 4.0),
padding: const EdgeInsets.only(top: 4.0), child: ExtensionBadgeIcon(extension),
child: ExtensionBadgeIcon(extension),
),
), ),
), ),
), ),
@@ -974,20 +947,18 @@ class NavigateForwardButton extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
return NonFocusable( return IconButton(
child: IconButton( onPressed: canGoForward
onPressed: canGoForward ? () async {
? () async { final controller = ref.read(
final controller = ref.read( tabSessionProvider(tabId: selectedTabId).notifier,
tabSessionProvider(tabId: selectedTabId).notifier, );
);
await controller.goForward(); await controller.goForward();
menuControllerToClose?.close(); menuControllerToClose?.close();
} }
: null, : null,
icon: const Icon(Icons.arrow_forward), icon: const Icon(Icons.arrow_forward),
),
); );
} }
} }
@@ -1008,37 +979,33 @@ class NavigateBackButton extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
return NonFocusable( return IconButton(
child: IconButton( onPressed: (canGoBack || isLoading)
onPressed: (canGoBack || isLoading) ? () async {
? () async { final controller = ref.read(
final controller = ref.read( tabSessionProvider(tabId: selectedTabId).notifier,
tabSessionProvider(tabId: selectedTabId).notifier, );
);
final isReaderActive = ref.read( final isReaderActive = ref.read(
selectedTabStateProvider.select( selectedTabStateProvider.select(
(state) => state?.readerableState.active ?? false, (state) => state?.readerableState.active ?? false,
), ),
); );
if (isLoading) { if (isLoading) {
await controller.stopLoading(); await controller.stopLoading();
} else if (isReaderActive) { } else if (isReaderActive) {
await ref await ref
.read(readerableScreenControllerProvider.notifier) .read(readerableScreenControllerProvider.notifier)
.toggleReaderView(false); .toggleReaderView(false);
} else { } else {
await controller.goBack(); await controller.goBack();
}
menuControllerToClose?.close();
} }
: null,
icon: isLoading menuControllerToClose?.close();
? const Icon(Icons.close) }
: const Icon(Icons.arrow_back), : null,
), icon: isLoading ? const Icon(Icons.close) : const Icon(Icons.arrow_back),
); );
} }
} }
@@ -1052,30 +1019,28 @@ class AddTabButton extends HookConsumerWidget {
return TabCreationMenu( return TabCreationMenu(
controller: tabMenuController, controller: tabMenuController,
child: NonFocusable( child: IconButton(
child: IconButton( onPressed: () async {
onPressed: () async { final settings = ref.read(generalSettingsWithDefaultsProvider);
final settings = ref.read(generalSettingsWithDefaultsProvider);
await SearchRoute( await SearchRoute(
tabType: tabType:
ref.read(selectedTabTypeProvider) ?? ref.read(selectedTabTypeProvider) ??
settings.defaultCreateTabType, settings.defaultCreateTabType,
).push(context); ).push(context);
if (context.mounted) { if (context.mounted) {
const BrowserRoute().go(context); const BrowserRoute().go(context);
} }
}, },
icon: const Icon(MdiIcons.tabPlus), icon: const Icon(MdiIcons.tabPlus),
onLongPress: () { onLongPress: () {
if (tabMenuController.isOpen) { if (tabMenuController.isOpen) {
tabMenuController.close(); tabMenuController.close();
} else { } else {
tabMenuController.open(); tabMenuController.open();
} }
}, },
),
), ),
); );
} }
@@ -152,24 +152,22 @@ class GridTabPreview extends HookConsumerWidget {
child: Text('Close all from ${tabState.url.host}'), child: Text('Close all from ${tabState.url.host}'),
), ),
], ],
child: NonFocusable( child: IconButton(
child: IconButton( visualDensity: const VisualDensity(
visualDensity: const VisualDensity( horizontal: -4.0,
horizontal: -4.0, vertical: -4.0,
vertical: -4.0,
),
onPressed: onDelete,
onLongPress: onDeleteAll != null
? () {
if (extendedDeleteMenuController.isOpen) {
extendedDeleteMenuController.close();
} else {
extendedDeleteMenuController.open();
}
}
: null,
icon: const Icon(Icons.close),
), ),
onPressed: onDelete,
onLongPress: onDeleteAll != null
? () {
if (extendedDeleteMenuController.isOpen) {
extendedDeleteMenuController.close();
} else {
extendedDeleteMenuController.open();
}
}
: null,
icon: const Icon(Icons.close),
), ),
), ),
?trailingChild, ?trailingChild,
@@ -352,20 +350,18 @@ class ListTabPreview extends HookConsumerWidget {
child: Text('Close all from ${tabState.url.host}'), child: Text('Close all from ${tabState.url.host}'),
), ),
], ],
child: NonFocusable( child: IconButton(
child: IconButton( onPressed: onDelete,
onPressed: onDelete, onLongPress: onDeleteAll != null
onLongPress: onDeleteAll != null ? () {
? () { if (extendedDeleteMenuController.isOpen) {
if (extendedDeleteMenuController.isOpen) { extendedDeleteMenuController.close();
extendedDeleteMenuController.close(); } else {
} else { extendedDeleteMenuController.open();
extendedDeleteMenuController.open();
}
} }
: null, }
icon: const Icon(Icons.close), : null,
), icon: const Icon(Icons.close),
), ),
), ),
?trailingChild, ?trailingChild,
@@ -631,12 +627,10 @@ class SuggestedSingleGridTabPreview extends StatelessWidget {
tabId: tabId, tabId: tabId,
isActive: tabId == activeTabId, isActive: tabId == activeTabId,
onTap: onTap, onTap: onTap,
trailingChild: const NonFocusable( trailingChild: const IconButton(
child: IconButton( visualDensity: VisualDensity(horizontal: -4.0, vertical: -4.0),
visualDensity: VisualDensity(horizontal: -4.0, vertical: -4.0), icon: Icon(MdiIcons.creation),
icon: Icon(MdiIcons.creation), onPressed: null,
onPressed: null,
),
), ),
), ),
); );
@@ -665,12 +659,10 @@ class SuggestedSingleListTabPreview extends StatelessWidget {
tabId: tabId, tabId: tabId,
isActive: tabId == activeTabId, isActive: tabId == activeTabId,
onTap: onTap, onTap: onTap,
trailingChild: const NonFocusable( trailingChild: const IconButton(
child: IconButton( visualDensity: VisualDensity(horizontal: -4.0, vertical: -4.0),
visualDensity: VisualDensity(horizontal: -4.0, vertical: -4.0), icon: Icon(MdiIcons.creation),
icon: Icon(MdiIcons.creation), onPressed: null,
onPressed: null,
),
), ),
), ),
); );
@@ -26,7 +26,6 @@ import 'package:weblibre/features/geckoview/features/tabs/presentation/widgets/c
import 'package:weblibre/features/tor/presentation/controllers/start_tor_proxy.dart'; import 'package:weblibre/features/tor/presentation/controllers/start_tor_proxy.dart';
import 'package:weblibre/features/user/domain/repositories/general_settings.dart'; import 'package:weblibre/features/user/domain/repositories/general_settings.dart';
import 'package:weblibre/presentation/hooks/menu_controller.dart'; import 'package:weblibre/presentation/hooks/menu_controller.dart';
import 'package:weblibre/presentation/widgets/non_focusable.dart';
import 'package:weblibre/presentation/widgets/speech_to_text_button.dart'; import 'package:weblibre/presentation/widgets/speech_to_text_button.dart';
import 'package:weblibre/utils/ui_helper.dart' as ui_helper; import 'package:weblibre/utils/ui_helper.dart' as ui_helper;
@@ -83,31 +82,27 @@ class TabViewHeader extends HookConsumerWidget {
Row( Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
NonFocusable( IconButton(
child: IconButton( icon: const Icon(MdiIcons.tabSearch),
icon: const Icon(MdiIcons.tabSearch), iconSize: 18,
iconSize: 18, padding: EdgeInsets.zero,
padding: EdgeInsets.zero, tooltip: 'Search inside tabs',
tooltip: 'Search inside tabs', onPressed: () {
onPressed: () { switch (ref.read(tabsViewModeControllerProvider)) {
switch (ref.read( case TabsViewMode.tree:
tabsViewModeControllerProvider, case TabsViewMode.list:
)) { break;
case TabsViewMode.tree: case TabsViewMode.grid:
case TabsViewMode.list: ref
break; .read(
case TabsViewMode.grid: tabsViewModeControllerProvider.notifier,
ref )
.read( .set(TabsViewMode.list);
tabsViewModeControllerProvider.notifier, }
)
.set(TabsViewMode.list);
}
searchMode.value = true; searchMode.value = true;
searchTextFocus.requestFocus(); searchTextFocus.requestFocus();
}, },
),
), ),
const SizedBox( const SizedBox(
height: 32, height: 32,
@@ -131,23 +126,21 @@ class TabViewHeader extends HookConsumerWidget {
), ),
) )
.toList(), .toList(),
child: NonFocusable( child: IconButton(
child: IconButton( tooltip: 'Change view mode',
tooltip: 'Change view mode', onPressed: () {
onPressed: () { if (viewModeMenuController.isOpen) {
if (viewModeMenuController.isOpen) { viewModeMenuController.close();
viewModeMenuController.close(); } else {
} else { viewModeMenuController.open();
viewModeMenuController.open(); }
} },
}, icon: Row(
icon: Row( mainAxisSize: MainAxisSize.min,
mainAxisSize: MainAxisSize.min, children: [
children: [ Icon(tabsViewMode.icon, size: 18),
Icon(tabsViewMode.icon, size: 18), const Icon(Icons.arrow_drop_down, size: 18),
const Icon(Icons.arrow_drop_down, size: 18), ],
],
),
), ),
), ),
), ),
@@ -174,42 +167,40 @@ class TabViewHeader extends HookConsumerWidget {
style: const TextStyle(fontSize: 10), style: const TextStyle(fontSize: 10),
) )
: null, : null,
child: NonFocusable( child: IconButton.filledTonal(
child: IconButton.filledTonal( icon: const Icon(MdiIcons.imageAutoAdjust),
icon: const Icon(MdiIcons.imageAutoAdjust), isSelected: tabSuggestionsEnabled,
isSelected: tabSuggestionsEnabled, iconSize: 18,
iconSize: 18, padding: EdgeInsets.zero,
padding: EdgeInsets.zero, tooltip: downloadProgress != null
tooltip: downloadProgress != null ? 'Downloading AI models (${downloadProgress.progress.toInt()}%)'
? 'Downloading AI models (${downloadProgress.progress.toInt()}%)' : tabSuggestionsEnabled
: tabSuggestionsEnabled ? 'Disable AI tab suggestions'
? 'Disable AI tab suggestions' : 'Enable AI tab suggestions',
: 'Enable AI tab suggestions', onPressed: () async {
onPressed: () async { if (!tabSuggestionsEnabled) {
if (!tabSuggestionsEnabled) { final result =
final result = await showEnableAiTabSuggestionsDialog(
await showEnableAiTabSuggestionsDialog( context,
context, );
);
if (result == true) { if (result == true) {
ref
.read(
tabSuggestionsControllerProvider
.notifier,
)
.enable();
}
} else {
ref ref
.read( .read(
tabSuggestionsControllerProvider tabSuggestionsControllerProvider
.notifier, .notifier,
) )
.disable(); .enable();
} }
}, } else {
), ref
.read(
tabSuggestionsControllerProvider
.notifier,
)
.disable();
}
},
), ),
); );
}, },
@@ -513,18 +504,16 @@ class TabViewHeader extends HookConsumerWidget {
}, },
), ),
], ],
child: NonFocusable( child: IconButton(
child: IconButton( tooltip: 'Tab actions',
tooltip: 'Tab actions', onPressed: () {
onPressed: () { if (tabsActionMenuController.isOpen) {
if (tabsActionMenuController.isOpen) { tabsActionMenuController.close();
tabsActionMenuController.close(); } else {
} else { tabsActionMenuController.open();
tabsActionMenuController.open(); }
} },
}, icon: const Icon(MdiIcons.dotsVertical),
icon: const Icon(MdiIcons.dotsVertical),
),
), ),
), ),
], ],
@@ -548,15 +537,13 @@ class TabViewHeader extends HookConsumerWidget {
searchTextController.text = data; searchTextController.text = data;
}, },
), ),
NonFocusable( IconButton(
child: IconButton( onPressed: () {
onPressed: () { searchTextController.clear();
searchTextController.clear(); searchTextFocus.requestFocus();
searchTextFocus.requestFocus(); searchMode.value = false;
searchMode.value = false; },
}, icon: const Icon(Icons.clear),
icon: const Icon(Icons.clear),
),
), ),
], ],
), ),
@@ -26,7 +26,6 @@ import 'package:weblibre/features/geckoview/features/tabs/data/entities/containe
import 'package:weblibre/features/geckoview/features/tabs/domain/providers.dart'; import 'package:weblibre/features/geckoview/features/tabs/domain/providers.dart';
import 'package:weblibre/features/geckoview/features/tabs/domain/providers/selected_container.dart'; import 'package:weblibre/features/geckoview/features/tabs/domain/providers/selected_container.dart';
import 'package:weblibre/features/geckoview/features/tabs/utils/container_colors.dart'; import 'package:weblibre/features/geckoview/features/tabs/utils/container_colors.dart';
import 'package:weblibre/presentation/widgets/non_focusable.dart';
class TabsActionButton extends HookConsumerWidget { class TabsActionButton extends HookConsumerWidget {
final bool isActive; final bool isActive;
@@ -57,65 +56,63 @@ class TabsActionButton extends HookConsumerWidget {
selectedContainerDataProvider.select((value) => value.value?.color), selectedContainerDataProvider.select((value) => value.value?.color),
); );
return NonFocusable( return InkWell(
child: InkWell( onTap: onTap,
onTap: onTap, onDoubleTap: onDoubleTap,
onDoubleTap: onDoubleTap, onLongPress: onLongPress,
onLongPress: onLongPress, child: Padding(
child: Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 15.0),
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 15.0), child: Container(
child: Container( decoration: BoxDecoration(
decoration: BoxDecoration( border: Border.all(
border: Border.all( width: 2.0,
width: 2.0, color: isActive
color: isActive ? theme.colorScheme.primary
? theme.colorScheme.primary : DefaultTextStyle.of(context).style.color!,
: DefaultTextStyle.of(context).style.color!,
),
borderRadius: BorderRadius.circular(5.0),
color: containerColor.mapNotNull(ContainerColors.forAppBar),
), ),
constraints: const BoxConstraints(minWidth: 25.0), borderRadius: BorderRadius.circular(5.0),
child: Center( color: containerColor.mapNotNull(ContainerColors.forAppBar),
child: tabCount.when( ),
skipLoadingOnReload: true, constraints: const BoxConstraints(minWidth: 25.0),
data: (count) { child: Center(
return Text( child: tabCount.when(
count.toString(), skipLoadingOnReload: true,
style: TextStyle( data: (count) {
fontWeight: FontWeight.bold, return Text(
fontSize: 14.0, count.toString(),
color: isActive ? theme.colorScheme.primary : null, style: TextStyle(
), fontWeight: FontWeight.bold,
); fontSize: 14.0,
}, color: isActive ? theme.colorScheme.primary : null,
loading: () => (tabCount.hasValue) ),
? Text( );
tabCount.value.toString(), },
style: TextStyle( loading: () => (tabCount.hasValue)
fontWeight: FontWeight.bold, ? Text(
fontSize: 14.0, tabCount.value.toString(),
color: isActive ? theme.colorScheme.primary : null, style: TextStyle(
), fontWeight: FontWeight.bold,
) fontSize: 14.0,
: const Skeletonizer(child: Text('00')), color: isActive ? theme.colorScheme.primary : null,
error: (error, stackTrace) { ),
logger.e( )
'Could not determine tab count', : const Skeletonizer(child: Text('00')),
error: error, error: (error, stackTrace) {
stackTrace: stackTrace, logger.e(
); 'Could not determine tab count',
error: error,
stackTrace: stackTrace,
);
return Text( return Text(
'-1', '-1',
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontSize: 14.0, fontSize: 14.0,
color: isActive ? theme.colorScheme.primary : null, color: isActive ? theme.colorScheme.primary : null,
), ),
); );
}, },
),
), ),
), ),
), ),
@@ -1,45 +0,0 @@
/*
* Copyright (c) 2024-2025 Fabian Freund.
*
* This file is part of WebLibre
* (see https://weblibre.eu).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:flutter/material.dart';
/// A wrapper that prevents a widget from receiving keyboard focus.
///
/// This fixes the issue where arrow keys on a bluetooth keyboard navigate
/// to toolbar buttons instead of moving the text cursor in input fields.
///
/// Example:
/// ```dart
/// NonFocusable(
/// child: IconButton(
/// onPressed: () {},
/// icon: Icon(Icons.menu),
/// ),
/// )
/// ```
class NonFocusable extends StatelessWidget {
final Widget child;
const NonFocusable({required this.child, super.key});
@override
Widget build(BuildContext context) {
return Focus(canRequestFocus: false, skipTraversal: true, child: child);
}
}