mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
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:
parent
677617dcf2
commit
cd245b5f0a
@ -1,11 +1,12 @@
|
|||||||
import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
|
|
||||||
import 'package:appflowy/plugins/database/application/cell/bloc/checklist_cell_bloc.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_cell_editor.dart';
|
||||||
import 'package:appflowy/plugins/database/widgets/cell_editor/checklist_progress_bar.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:appflowy_popover/appflowy_popover.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
import '../editable_cell_skeleton/checklist.dart';
|
import '../editable_cell_skeleton/checklist.dart';
|
||||||
@ -25,6 +26,7 @@ class DesktopGridChecklistCellSkin extends IEditableChecklistCellSkin {
|
|||||||
constraints: BoxConstraints.loose(const Size(360, 400)),
|
constraints: BoxConstraints.loose(const Size(360, 400)),
|
||||||
direction: PopoverDirection.bottomWithLeftAligned,
|
direction: PopoverDirection.bottomWithLeftAligned,
|
||||||
triggerActions: PopoverTriggerFlags.none,
|
triggerActions: PopoverTriggerFlags.none,
|
||||||
|
skipTraversal: true,
|
||||||
popupBuilder: (BuildContext popoverContext) {
|
popupBuilder: (BuildContext popoverContext) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
cellContainerNotifier.isFocus = true;
|
cellContainerNotifier.isFocus = true;
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
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/flowy_svgs.g.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.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/size.dart';
|
||||||
import 'package:flowy_infra/theme_extension.dart';
|
import 'package:flowy_infra/theme_extension.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.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 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
import '../../application/cell/bloc/checklist_cell_bloc.dart';
|
import '../../application/cell/bloc/checklist_cell_bloc.dart';
|
||||||
|
|
||||||
import 'checklist_progress_bar.dart';
|
import 'checklist_progress_bar.dart';
|
||||||
|
|
||||||
class ChecklistCellEditor extends StatefulWidget {
|
class ChecklistCellEditor extends StatefulWidget {
|
||||||
@ -345,12 +347,11 @@ class NewTaskItem extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _NewTaskItemState extends State<NewTaskItem> {
|
class _NewTaskItemState extends State<NewTaskItem> {
|
||||||
late final TextEditingController _textEditingController;
|
final _textEditingController = TextEditingController();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_textEditingController = TextEditingController();
|
|
||||||
if (widget.focusNode.canRequestFocus) {
|
if (widget.focusNode.canRequestFocus) {
|
||||||
widget.focusNode.requestFocus();
|
widget.focusNode.requestFocus();
|
||||||
}
|
}
|
||||||
@ -385,15 +386,13 @@ class _NewTaskItemState extends State<NewTaskItem> {
|
|||||||
hintText: LocaleKeys.grid_checklist_addNew.tr(),
|
hintText: LocaleKeys.grid_checklist_addNew.tr(),
|
||||||
),
|
),
|
||||||
onSubmitted: (taskDescription) {
|
onSubmitted: (taskDescription) {
|
||||||
if (taskDescription.trim().isNotEmpty) {
|
if (taskDescription.isNotEmpty) {
|
||||||
context.read<ChecklistCellBloc>().add(
|
context
|
||||||
ChecklistCellEvent.createNewTask(
|
.read<ChecklistCellBloc>()
|
||||||
taskDescription.trim(),
|
.add(ChecklistCellEvent.createNewTask(taskDescription));
|
||||||
),
|
_textEditingController.clear();
|
||||||
);
|
|
||||||
}
|
}
|
||||||
widget.focusNode.requestFocus();
|
widget.focusNode.requestFocus();
|
||||||
_textEditingController.clear();
|
|
||||||
},
|
},
|
||||||
onChanged: (value) => setState(() {}),
|
onChanged: (value) => setState(() {}),
|
||||||
),
|
),
|
||||||
@ -409,16 +408,17 @@ class _NewTaskItemState extends State<NewTaskItem> {
|
|||||||
: Theme.of(context).colorScheme.primaryContainer,
|
: Theme.of(context).colorScheme.primaryContainer,
|
||||||
fontColor: Theme.of(context).colorScheme.onPrimary,
|
fontColor: Theme.of(context).colorScheme.onPrimary,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
|
||||||
onPressed: () {
|
onPressed: _textEditingController.text.isEmpty
|
||||||
final text = _textEditingController.text.trim();
|
? null
|
||||||
if (text.isNotEmpty) {
|
: () {
|
||||||
context.read<ChecklistCellBloc>().add(
|
context.read<ChecklistCellBloc>().add(
|
||||||
ChecklistCellEvent.createNewTask(text),
|
ChecklistCellEvent.createNewTask(
|
||||||
);
|
_textEditingController.text,
|
||||||
}
|
),
|
||||||
widget.focusNode.requestFocus();
|
);
|
||||||
_textEditingController.clear();
|
widget.focusNode.requestFocus();
|
||||||
},
|
_textEditingController.clear();
|
||||||
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import 'package:appflowy_popover/src/layout.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
import 'package:appflowy_popover/src/layout.dart';
|
||||||
|
|
||||||
import 'mask.dart';
|
import 'mask.dart';
|
||||||
import 'mutex.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.
|
/// the conflict won't be resolve by using Listener, we want these two gestures exclusive.
|
||||||
final PopoverClickHandler clickHandler;
|
final PopoverClickHandler clickHandler;
|
||||||
|
|
||||||
|
final bool skipTraversal;
|
||||||
|
|
||||||
/// The content area of the popover.
|
/// The content area of the popover.
|
||||||
final Widget child;
|
final Widget child;
|
||||||
|
|
||||||
@ -110,6 +113,7 @@ class Popover extends StatefulWidget {
|
|||||||
this.canClose,
|
this.canClose,
|
||||||
this.asBarrier = false,
|
this.asBarrier = false,
|
||||||
this.clickHandler = PopoverClickHandler.listener,
|
this.clickHandler = PopoverClickHandler.listener,
|
||||||
|
this.skipTraversal = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -158,6 +162,7 @@ class PopoverState extends State<Popover> {
|
|||||||
popupBuilder: widget.popupBuilder,
|
popupBuilder: widget.popupBuilder,
|
||||||
onClose: () => close(),
|
onClose: () => close(),
|
||||||
onCloseAll: () => _removeRootOverlay(),
|
onCloseAll: () => _removeRootOverlay(),
|
||||||
|
skipTraversal: widget.skipTraversal,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -263,6 +268,7 @@ class PopoverContainer extends StatefulWidget {
|
|||||||
final EdgeInsets windowPadding;
|
final EdgeInsets windowPadding;
|
||||||
final void Function() onClose;
|
final void Function() onClose;
|
||||||
final void Function() onCloseAll;
|
final void Function() onCloseAll;
|
||||||
|
final bool skipTraversal;
|
||||||
|
|
||||||
const PopoverContainer({
|
const PopoverContainer({
|
||||||
super.key,
|
super.key,
|
||||||
@ -273,6 +279,7 @@ class PopoverContainer extends StatefulWidget {
|
|||||||
required this.windowPadding,
|
required this.windowPadding,
|
||||||
required this.onClose,
|
required this.onClose,
|
||||||
required this.onCloseAll,
|
required this.onCloseAll,
|
||||||
|
required this.skipTraversal,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -293,6 +300,7 @@ class PopoverContainerState extends State<PopoverContainer> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Focus(
|
return Focus(
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
|
skipTraversal: widget.skipTraversal,
|
||||||
child: CustomSingleChildLayout(
|
child: CustomSingleChildLayout(
|
||||||
delegate: PopoverLayoutDelegate(
|
delegate: PopoverLayoutDelegate(
|
||||||
direction: widget.direction,
|
direction: widget.direction,
|
||||||
|
@ -26,6 +26,10 @@ class AppFlowyPopover extends StatelessWidget {
|
|||||||
/// the conflict won't be resolve by using Listener, we want these two gestures exclusive.
|
/// the conflict won't be resolve by using Listener, we want these two gestures exclusive.
|
||||||
final PopoverClickHandler clickHandler;
|
final PopoverClickHandler clickHandler;
|
||||||
|
|
||||||
|
/// If true the popover will not participate in focus traversal.
|
||||||
|
///
|
||||||
|
final bool skipTraversal;
|
||||||
|
|
||||||
const AppFlowyPopover({
|
const AppFlowyPopover({
|
||||||
super.key,
|
super.key,
|
||||||
required this.child,
|
required this.child,
|
||||||
@ -43,6 +47,7 @@ class AppFlowyPopover extends StatelessWidget {
|
|||||||
this.windowPadding = const EdgeInsets.all(8.0),
|
this.windowPadding = const EdgeInsets.all(8.0),
|
||||||
this.decoration,
|
this.decoration,
|
||||||
this.clickHandler = PopoverClickHandler.listener,
|
this.clickHandler = PopoverClickHandler.listener,
|
||||||
|
this.skipTraversal = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -58,6 +63,7 @@ class AppFlowyPopover extends StatelessWidget {
|
|||||||
windowPadding: windowPadding,
|
windowPadding: windowPadding,
|
||||||
offset: offset,
|
offset: offset,
|
||||||
clickHandler: clickHandler,
|
clickHandler: clickHandler,
|
||||||
|
skipTraversal: skipTraversal,
|
||||||
popupBuilder: (context) {
|
popupBuilder: (context) {
|
||||||
return _PopoverContainer(
|
return _PopoverContainer(
|
||||||
constraints: constraints,
|
constraints: constraints,
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:flowy_infra/size.dart';
|
import 'package:flowy_infra/size.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/text.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/flowy_tooltip.dart';
|
||||||
import 'package:flowy_infra_ui/widget/ignore_parent_gesture.dart';
|
import 'package:flowy_infra_ui/widget/ignore_parent_gesture.dart';
|
||||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class FlowyButton extends StatelessWidget {
|
class FlowyButton extends StatelessWidget {
|
||||||
final Widget text;
|
final Widget text;
|
||||||
@ -213,6 +214,7 @@ class FlowyTextButton extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
|
|
||||||
child = RawMaterialButton(
|
child = RawMaterialButton(
|
||||||
|
focusNode: FocusNode(skipTraversal: onPressed == null),
|
||||||
hoverElevation: 0,
|
hoverElevation: 0,
|
||||||
highlightElevation: 0,
|
highlightElevation: 0,
|
||||||
shape: RoundedRectangleBorder(borderRadius: radius ?? Corners.s6Border),
|
shape: RoundedRectangleBorder(borderRadius: radius ?? Corners.s6Border),
|
||||||
@ -237,6 +239,10 @@ class FlowyTextButton extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (onPressed == null) {
|
||||||
|
child = ExcludeFocus(child: child);
|
||||||
|
}
|
||||||
|
|
||||||
return child;
|
return child;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user