fix: focus traversal in checklist popover (#4843)

* fix: focus traversal in checklist popover

* fix: dont trim input

* chore: remove redundant state var

* chore: remove late from controller
This commit is contained in:
Mathias Mogensen 2024-03-07 14:04:10 +01:00 committed by GitHub
parent 677617dcf2
commit cd245b5f0a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 48 additions and 26 deletions

View File

@ -1,11 +1,12 @@
import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart';
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
import 'package:flutter/material.dart';
import 'package:appflowy/plugins/database/application/cell/bloc/checklist_cell_bloc.dart';
import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart';
import 'package:appflowy/plugins/database/widgets/cell_editor/checklist_cell_editor.dart';
import 'package:appflowy/plugins/database/widgets/cell_editor/checklist_progress_bar.dart';
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../editable_cell_skeleton/checklist.dart';
@ -25,6 +26,7 @@ class DesktopGridChecklistCellSkin extends IEditableChecklistCellSkin {
constraints: BoxConstraints.loose(const Size(360, 400)),
direction: PopoverDirection.bottomWithLeftAligned,
triggerActions: PopoverTriggerFlags.none,
skipTraversal: true,
popupBuilder: (BuildContext popoverContext) {
WidgetsBinding.instance.addPostFrameCallback((_) {
cellContainerNotifier.isFocus = true;

View File

@ -1,6 +1,9 @@
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart';
@ -11,11 +14,10 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/size.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../application/cell/bloc/checklist_cell_bloc.dart';
import 'checklist_progress_bar.dart';
class ChecklistCellEditor extends StatefulWidget {
@ -345,12 +347,11 @@ class NewTaskItem extends StatefulWidget {
}
class _NewTaskItemState extends State<NewTaskItem> {
late final TextEditingController _textEditingController;
final _textEditingController = TextEditingController();
@override
void initState() {
super.initState();
_textEditingController = TextEditingController();
if (widget.focusNode.canRequestFocus) {
widget.focusNode.requestFocus();
}
@ -385,15 +386,13 @@ class _NewTaskItemState extends State<NewTaskItem> {
hintText: LocaleKeys.grid_checklist_addNew.tr(),
),
onSubmitted: (taskDescription) {
if (taskDescription.trim().isNotEmpty) {
context.read<ChecklistCellBloc>().add(
ChecklistCellEvent.createNewTask(
taskDescription.trim(),
),
);
if (taskDescription.isNotEmpty) {
context
.read<ChecklistCellBloc>()
.add(ChecklistCellEvent.createNewTask(taskDescription));
_textEditingController.clear();
}
widget.focusNode.requestFocus();
_textEditingController.clear();
},
onChanged: (value) => setState(() {}),
),
@ -409,13 +408,14 @@ class _NewTaskItemState extends State<NewTaskItem> {
: Theme.of(context).colorScheme.primaryContainer,
fontColor: Theme.of(context).colorScheme.onPrimary,
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
onPressed: () {
final text = _textEditingController.text.trim();
if (text.isNotEmpty) {
onPressed: _textEditingController.text.isEmpty
? null
: () {
context.read<ChecklistCellBloc>().add(
ChecklistCellEvent.createNewTask(text),
ChecklistCellEvent.createNewTask(
_textEditingController.text,
),
);
}
widget.focusNode.requestFocus();
_textEditingController.clear();
},

View File

@ -1,7 +1,8 @@
import 'package:appflowy_popover/src/layout.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:appflowy_popover/src/layout.dart';
import 'mask.dart';
import 'mutex.dart';
@ -90,6 +91,8 @@ class Popover extends StatefulWidget {
/// the conflict won't be resolve by using Listener, we want these two gestures exclusive.
final PopoverClickHandler clickHandler;
final bool skipTraversal;
/// The content area of the popover.
final Widget child;
@ -110,6 +113,7 @@ class Popover extends StatefulWidget {
this.canClose,
this.asBarrier = false,
this.clickHandler = PopoverClickHandler.listener,
this.skipTraversal = false,
});
@override
@ -158,6 +162,7 @@ class PopoverState extends State<Popover> {
popupBuilder: widget.popupBuilder,
onClose: () => close(),
onCloseAll: () => _removeRootOverlay(),
skipTraversal: widget.skipTraversal,
),
);
@ -263,6 +268,7 @@ class PopoverContainer extends StatefulWidget {
final EdgeInsets windowPadding;
final void Function() onClose;
final void Function() onCloseAll;
final bool skipTraversal;
const PopoverContainer({
super.key,
@ -273,6 +279,7 @@ class PopoverContainer extends StatefulWidget {
required this.windowPadding,
required this.onClose,
required this.onCloseAll,
required this.skipTraversal,
});
@override
@ -293,6 +300,7 @@ class PopoverContainerState extends State<PopoverContainer> {
Widget build(BuildContext context) {
return Focus(
autofocus: true,
skipTraversal: widget.skipTraversal,
child: CustomSingleChildLayout(
delegate: PopoverLayoutDelegate(
direction: widget.direction,

View File

@ -26,6 +26,10 @@ class AppFlowyPopover extends StatelessWidget {
/// the conflict won't be resolve by using Listener, we want these two gestures exclusive.
final PopoverClickHandler clickHandler;
/// If true the popover will not participate in focus traversal.
///
final bool skipTraversal;
const AppFlowyPopover({
super.key,
required this.child,
@ -43,6 +47,7 @@ class AppFlowyPopover extends StatelessWidget {
this.windowPadding = const EdgeInsets.all(8.0),
this.decoration,
this.clickHandler = PopoverClickHandler.listener,
this.skipTraversal = false,
});
@override
@ -58,6 +63,7 @@ class AppFlowyPopover extends StatelessWidget {
windowPadding: windowPadding,
offset: offset,
clickHandler: clickHandler,
skipTraversal: skipTraversal,
popupBuilder: (context) {
return _PopoverContainer(
constraints: constraints,

View File

@ -1,12 +1,13 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flowy_infra/size.dart';
import 'package:flowy_infra_ui/style_widget/hover.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flowy_infra_ui/widget/ignore_parent_gesture.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flutter/material.dart';
class FlowyButton extends StatelessWidget {
final Widget text;
@ -213,6 +214,7 @@ class FlowyTextButton extends StatelessWidget {
);
child = RawMaterialButton(
focusNode: FocusNode(skipTraversal: onPressed == null),
hoverElevation: 0,
highlightElevation: 0,
shape: RoundedRectangleBorder(borderRadius: radius ?? Corners.s6Border),
@ -237,6 +239,10 @@ class FlowyTextButton extends StatelessWidget {
);
}
if (onPressed == null) {
child = ExcludeFocus(child: child);
}
return child;
}
}