Browse Source

fix: checklist cell did get notified after the cell content change

nathan 2 years ago
parent
commit
29e07089ca

+ 5 - 1
frontend/app_flowy/lib/plugins/grid/application/cell/checklist_cell_bloc.dart

@@ -51,7 +51,11 @@ class ChecklistCellBloc extends Bloc<ChecklistCellEvent, ChecklistCellState> {
       onCellFieldChanged: () {
       onCellFieldChanged: () {
         _loadOptions();
         _loadOptions();
       },
       },
-      onCellChanged: (_) {},
+      onCellChanged: (data) {
+        if (!isClosed && data != null) {
+          add(ChecklistCellEvent.didReceiveOptions(data));
+        }
+      },
     );
     );
   }
   }
 
 

+ 8 - 12
frontend/app_flowy/lib/plugins/grid/application/cell/checklist_cell_editor_bloc.dart

@@ -15,7 +15,6 @@ class ChecklistCellEditorBloc
     extends Bloc<ChecklistCellEditorEvent, ChecklistCellEditorState> {
     extends Bloc<ChecklistCellEditorEvent, ChecklistCellEditorState> {
   final SelectOptionFFIService _selectOptionService;
   final SelectOptionFFIService _selectOptionService;
   final GridChecklistCellController cellController;
   final GridChecklistCellController cellController;
-  Timer? _delayOperation;
 
 
   ChecklistCellEditorBloc({
   ChecklistCellEditorBloc({
     required this.cellController,
     required this.cellController,
@@ -27,7 +26,6 @@ class ChecklistCellEditorBloc
         await event.when(
         await event.when(
           initial: () async {
           initial: () async {
             _startListening();
             _startListening();
-            _loadOptions();
           },
           },
           didReceiveOptions: (data) {
           didReceiveOptions: (data) {
             emit(state.copyWith(
             emit(state.copyWith(
@@ -47,11 +45,12 @@ class ChecklistCellEditorBloc
           updateOption: (option) {
           updateOption: (option) {
             _updateOption(option);
             _updateOption(option);
           },
           },
-          selectOption: (optionId) {
-            _selectOptionService.select(optionIds: [optionId]);
-          },
-          unSelectOption: (optionId) {
-            _selectOptionService.unSelect(optionIds: [optionId]);
+          selectOption: (option) async {
+            if (option.isSelected) {
+              await _selectOptionService.unSelect(optionIds: [option.data.id]);
+            } else {
+              await _selectOptionService.select(optionIds: [option.data.id]);
+            }
           },
           },
           filterOption: (String predicate) {},
           filterOption: (String predicate) {},
         );
         );
@@ -61,7 +60,6 @@ class ChecklistCellEditorBloc
 
 
   @override
   @override
   Future<void> close() async {
   Future<void> close() async {
-    _delayOperation?.cancel();
     await cellController.dispose();
     await cellController.dispose();
     return super.close();
     return super.close();
   }
   }
@@ -119,10 +117,8 @@ class ChecklistCellEditorEvent with _$ChecklistCellEditorEvent {
       SelectOptionCellDataPB data) = _DidReceiveOptions;
       SelectOptionCellDataPB data) = _DidReceiveOptions;
   const factory ChecklistCellEditorEvent.newOption(String optionName) =
   const factory ChecklistCellEditorEvent.newOption(String optionName) =
       _NewOption;
       _NewOption;
-  const factory ChecklistCellEditorEvent.selectOption(String optionId) =
-      _SelectOption;
-  const factory ChecklistCellEditorEvent.unSelectOption(String optionId) =
-      _UnSelectOption;
+  const factory ChecklistCellEditorEvent.selectOption(
+      ChecklistSelectOption option) = _SelectOption;
   const factory ChecklistCellEditorEvent.updateOption(SelectOptionPB option) =
   const factory ChecklistCellEditorEvent.updateOption(SelectOptionPB option) =
       _UpdateOption;
       _UpdateOption;
   const factory ChecklistCellEditorEvent.deleteOption(SelectOptionPB option) =
   const factory ChecklistCellEditorEvent.deleteOption(SelectOptionPB option) =

+ 1 - 0
frontend/app_flowy/lib/plugins/grid/application/field/field_controller.dart

@@ -522,6 +522,7 @@ class FieldInfo {
       case FieldType.MultiSelect:
       case FieldType.MultiSelect:
       case FieldType.RichText:
       case FieldType.RichText:
       case FieldType.SingleSelect:
       case FieldType.SingleSelect:
+        // case FieldType.Checklist:
         return true;
         return true;
       default:
       default:
         return false;
         return false;

+ 10 - 14
frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/checklist_cell/checklist_cell.dart

@@ -36,20 +36,16 @@ class GridChecklistCellState extends State<GridChecklistCell> {
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     return BlocProvider.value(
     return BlocProvider.value(
       value: _cellBloc,
       value: _cellBloc,
-      child: BlocBuilder<ChecklistCellBloc, ChecklistCellState>(
-        builder: (context, state) {
-          return Stack(
-            alignment: AlignmentDirectional.center,
-            fit: StackFit.expand,
-            children: [
-              Padding(
-                padding: GridSize.cellContentInsets,
-                child: _wrapPopover(const ChecklistProgressBar()),
-              ),
-              InkWell(onTap: () => _popover.show()),
-            ],
-          );
-        },
+      child: Stack(
+        alignment: AlignmentDirectional.center,
+        fit: StackFit.expand,
+        children: [
+          Padding(
+            padding: GridSize.cellContentInsets,
+            child: _wrapPopover(const ChecklistProgressBar()),
+          ),
+          InkWell(onTap: () => _popover.show()),
+        ],
       ),
       ),
     );
     );
   }
   }

+ 52 - 23
frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/checklist_cell/checklist_cell_editor.dart

@@ -6,6 +6,7 @@ import 'package:app_flowy/plugins/grid/presentation/widgets/header/type_option/s
 import 'package:appflowy_popover/appflowy_popover.dart';
 import 'package:appflowy_popover/appflowy_popover.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
+import 'package:flowy_infra_ui/style_widget/button.dart';
 import 'package:flowy_infra_ui/style_widget/icon_button.dart';
 import 'package:flowy_infra_ui/style_widget/icon_button.dart';
 import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
 import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
 import 'package:flowy_infra_ui/widget/spacing.dart';
 import 'package:flowy_infra_ui/widget/spacing.dart';
@@ -24,9 +25,11 @@ class GridChecklistCellEditor extends StatefulWidget {
 
 
 class _GridChecklistCellEditorState extends State<GridChecklistCellEditor> {
 class _GridChecklistCellEditorState extends State<GridChecklistCellEditor> {
   late ChecklistCellEditorBloc bloc;
   late ChecklistCellEditorBloc bloc;
+  late PopoverMutex popoverMutex;
 
 
   @override
   @override
   void initState() {
   void initState() {
+    popoverMutex = PopoverMutex();
     bloc = ChecklistCellEditorBloc(cellController: widget.cellController);
     bloc = ChecklistCellEditorBloc(cellController: widget.cellController);
     bloc.add(const ChecklistCellEditorEvent.initial());
     bloc.add(const ChecklistCellEditorEvent.initial());
     super.initState();
     super.initState();
@@ -47,23 +50,28 @@ class _GridChecklistCellEditorState extends State<GridChecklistCellEditor> {
           final List<Widget> slivers = [
           final List<Widget> slivers = [
             const SliverChecklistPrograssBar(),
             const SliverChecklistPrograssBar(),
             SliverToBoxAdapter(
             SliverToBoxAdapter(
-                child: Container(color: Colors.red, height: 2, width: 2100)),
-            SliverToBoxAdapter(
-              child: ListView.separated(
-                controller: ScrollController(),
-                shrinkWrap: true,
-                itemCount: state.allOptions.length,
-                itemBuilder: (BuildContext context, int index) {
-                  return _ChecklistOptionCell(option: state.allOptions[index]);
-                },
-                separatorBuilder: (BuildContext context, int index) {
-                  return VSpace(GridSize.typeOptionSeparatorHeight);
-                },
+              child: Padding(
+                padding: GridSize.typeOptionContentInsets,
+                child: ListView.separated(
+                  controller: ScrollController(),
+                  shrinkWrap: true,
+                  itemCount: state.allOptions.length,
+                  itemBuilder: (BuildContext context, int index) {
+                    return _ChecklistOptionCell(
+                      option: state.allOptions[index],
+                      popoverMutex: popoverMutex,
+                    );
+                  },
+                  separatorBuilder: (BuildContext context, int index) {
+                    return VSpace(GridSize.typeOptionSeparatorHeight);
+                  },
+                ),
               ),
               ),
             ),
             ),
           ];
           ];
-          return Padding(
-            padding: const EdgeInsets.symmetric(horizontal: 8),
+
+          return ScrollConfiguration(
+            behavior: const ScrollBehavior().copyWith(scrollbars: false),
             child: CustomScrollView(
             child: CustomScrollView(
               shrinkWrap: true,
               shrinkWrap: true,
               slivers: slivers,
               slivers: slivers,
@@ -79,8 +87,10 @@ class _GridChecklistCellEditorState extends State<GridChecklistCellEditor> {
 
 
 class _ChecklistOptionCell extends StatefulWidget {
 class _ChecklistOptionCell extends StatefulWidget {
   final ChecklistSelectOption option;
   final ChecklistSelectOption option;
+  final PopoverMutex popoverMutex;
   const _ChecklistOptionCell({
   const _ChecklistOptionCell({
     required this.option,
     required this.option,
+    required this.popoverMutex,
     Key? key,
     Key? key,
   }) : super(key: key);
   }) : super(key: key);
 
 
@@ -107,10 +117,15 @@ class _ChecklistOptionCellState extends State<_ChecklistOptionCell> {
         height: GridSize.typeOptionItemHeight,
         height: GridSize.typeOptionItemHeight,
         child: Row(
         child: Row(
           children: [
           children: [
-            icon,
-            const HSpace(6),
-            FlowyText(widget.option.data.name),
-            const Spacer(),
+            Expanded(
+              child: FlowyButton(
+                text: FlowyText(widget.option.data.name),
+                leftIcon: icon,
+                onTap: () => context
+                    .read<ChecklistCellEditorBloc>()
+                    .add(ChecklistCellEditorEvent.selectOption(widget.option)),
+              ),
+            ),
             _disclosureButton(),
             _disclosureButton(),
           ],
           ],
         ),
         ),
@@ -122,8 +137,7 @@ class _ChecklistOptionCellState extends State<_ChecklistOptionCell> {
     return FlowyIconButton(
     return FlowyIconButton(
       width: 20,
       width: 20,
       onPressed: () => _popoverController.show(),
       onPressed: () => _popoverController.show(),
-      hoverColor: Colors.transparent,
-      iconPadding: const EdgeInsets.fromLTRB(4, 4, 4, 4),
+      iconPadding: const EdgeInsets.fromLTRB(2, 2, 2, 2),
       icon: svgWidget(
       icon: svgWidget(
         "editor/details",
         "editor/details",
         color: Theme.of(context).colorScheme.onSurface,
         color: Theme.of(context).colorScheme.onSurface,
@@ -137,15 +151,30 @@ class _ChecklistOptionCellState extends State<_ChecklistOptionCell> {
       offset: const Offset(20, 0),
       offset: const Offset(20, 0),
       asBarrier: true,
       asBarrier: true,
       constraints: BoxConstraints.loose(const Size(200, 300)),
       constraints: BoxConstraints.loose(const Size(200, 300)),
+      mutex: widget.popoverMutex,
+      triggerActions: PopoverTriggerFlags.none,
       child: child,
       child: child,
       popupBuilder: (BuildContext popoverContext) {
       popupBuilder: (BuildContext popoverContext) {
         return SelectOptionTypeOptionEditor(
         return SelectOptionTypeOptionEditor(
           option: widget.option.data,
           option: widget.option.data,
-          onDeleted: () {},
-          onUpdated: (updatedOption) {},
+          onDeleted: () {
+            context.read<ChecklistCellEditorBloc>().add(
+                  ChecklistCellEditorEvent.deleteOption(widget.option.data),
+                );
+
+            _popoverController.close();
+          },
+          onUpdated: (updatedOption) {
+            context.read<ChecklistCellEditorBloc>().add(
+                  ChecklistCellEditorEvent.updateOption(widget.option.data),
+                );
+          },
+          showOptions: false,
+          autoFocus: false,
+          // Use ValueKey to refresh the UI, otherwise, it will remain the old value.
           key: ValueKey(
           key: ValueKey(
             widget.option.data.id,
             widget.option.data.id,
-          ), // Use ValueKey to refresh the UI, otherwise, it will remain the old value.
+          ),
         );
         );
       },
       },
     );
     );

+ 27 - 21
frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/checklist_cell/checklist_prograss_bar.dart

@@ -1,5 +1,6 @@
 import 'package:app_flowy/generated/locale_keys.g.dart';
 import 'package:app_flowy/generated/locale_keys.g.dart';
 import 'package:app_flowy/plugins/grid/application/cell/checklist_cell_editor_bloc.dart';
 import 'package:app_flowy/plugins/grid/application/cell/checklist_cell_editor_bloc.dart';
+import 'package:app_flowy/plugins/grid/presentation/layout/sizes.dart';
 import 'package:easy_localization/easy_localization.dart';
 import 'package:easy_localization/easy_localization.dart';
 import 'package:flowy_infra/color_extension.dart';
 import 'package:flowy_infra/color_extension.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@@ -41,34 +42,39 @@ class _SliverChecklistPrograssBarDelegate
     extends SliverPersistentHeaderDelegate {
     extends SliverPersistentHeaderDelegate {
   _SliverChecklistPrograssBarDelegate();
   _SliverChecklistPrograssBarDelegate();
 
 
-  double fixHeight = 80;
+  double fixHeight = 60;
 
 
   @override
   @override
   Widget build(
   Widget build(
       BuildContext context, double shrinkOffset, bool overlapsContent) {
       BuildContext context, double shrinkOffset, bool overlapsContent) {
     return BlocBuilder<ChecklistCellEditorBloc, ChecklistCellEditorState>(
     return BlocBuilder<ChecklistCellEditorBloc, ChecklistCellEditorState>(
       builder: (context, state) {
       builder: (context, state) {
-        return Column(
-          children: [
-            if (state.percent != 0)
-              Padding(
-                padding: const EdgeInsets.symmetric(vertical: 8.0),
-                child: ChecklistPrograssBar(percent: state.percent),
+        return Container(
+          color: Theme.of(context).colorScheme.background,
+          padding: GridSize.typeOptionContentInsets,
+          child: Column(
+            children: [
+              FlowyTextField(
+                autoClearWhenDone: true,
+                hintText: LocaleKeys.grid_checklist_panelTitle.tr(),
+                onChanged: (text) {
+                  context
+                      .read<ChecklistCellEditorBloc>()
+                      .add(ChecklistCellEditorEvent.filterOption(text));
+                },
+                onSubmitted: (text) {
+                  context
+                      .read<ChecklistCellEditorBloc>()
+                      .add(ChecklistCellEditorEvent.newOption(text));
+                },
               ),
               ),
-            FlowyTextField(
-              hintText: LocaleKeys.grid_checklist_panelTitle.tr(),
-              onChanged: (text) {
-                context
-                    .read<ChecklistCellEditorBloc>()
-                    .add(ChecklistCellEditorEvent.filterOption(text));
-              },
-              onSubmitted: (text) {
-                context
-                    .read<ChecklistCellEditorBloc>()
-                    .add(ChecklistCellEditorEvent.newOption(text));
-              },
-            )
-          ],
+              if (state.percent != 0)
+                Padding(
+                  padding: const EdgeInsets.symmetric(vertical: 8.0),
+                  child: ChecklistPrograssBar(percent: state.percent),
+                ),
+            ],
+          ),
         );
         );
       },
       },
     );
     );

+ 27 - 12
frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/select_option_editor.dart

@@ -4,6 +4,7 @@ import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra_ui/style_widget/button.dart';
 import 'package:flowy_infra_ui/style_widget/button.dart';
 import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
 import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
 import 'package:flowy_infra_ui/style_widget/text.dart';
 import 'package:flowy_infra_ui/style_widget/text.dart';
+import 'package:flowy_infra_ui/style_widget/text_field.dart';
 import 'package:flowy_infra_ui/widget/spacing.dart';
 import 'package:flowy_infra_ui/widget/spacing.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/select_type_option.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/select_type_option.pb.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
@@ -18,10 +19,14 @@ class SelectOptionTypeOptionEditor extends StatelessWidget {
   final SelectOptionPB option;
   final SelectOptionPB option;
   final VoidCallback onDeleted;
   final VoidCallback onDeleted;
   final Function(SelectOptionPB) onUpdated;
   final Function(SelectOptionPB) onUpdated;
+  final bool showOptions;
+  final bool autoFocus;
   const SelectOptionTypeOptionEditor({
   const SelectOptionTypeOptionEditor({
     required this.option,
     required this.option,
     required this.onDeleted,
     required this.onDeleted,
     required this.onUpdated,
     required this.onUpdated,
+    this.showOptions = true,
+    this.autoFocus = true,
     Key? key,
     Key? key,
   }) : super(key: key);
   }) : super(key: key);
 
 
@@ -50,21 +55,29 @@ class SelectOptionTypeOptionEditor extends StatelessWidget {
           builder: (context, state) {
           builder: (context, state) {
             List<Widget> slivers = [
             List<Widget> slivers = [
               SliverToBoxAdapter(
               SliverToBoxAdapter(
-                  child: _OptionNameTextField(state.option.name)),
+                  child: _OptionNameTextField(
+                name: state.option.name,
+                autoFocus: autoFocus,
+              )),
               const SliverToBoxAdapter(child: VSpace(10)),
               const SliverToBoxAdapter(child: VSpace(10)),
               const SliverToBoxAdapter(child: _DeleteTag()),
               const SliverToBoxAdapter(child: _DeleteTag()),
-              const SliverToBoxAdapter(child: TypeOptionSeparator()),
-              SliverToBoxAdapter(
-                  child:
-                      SelectOptionColorList(selectedColor: state.option.color)),
             ];
             ];
 
 
+            if (showOptions) {
+              slivers
+                  .add(const SliverToBoxAdapter(child: TypeOptionSeparator()));
+              slivers.add(SliverToBoxAdapter(
+                  child: SelectOptionColorList(
+                      selectedColor: state.option.color)));
+            }
+
             return SizedBox(
             return SizedBox(
               width: 160,
               width: 160,
               child: Padding(
               child: Padding(
                 padding: const EdgeInsets.all(6.0),
                 padding: const EdgeInsets.all(6.0),
                 child: CustomScrollView(
                 child: CustomScrollView(
                   slivers: slivers,
                   slivers: slivers,
+                  shrinkWrap: true,
                   controller: ScrollController(),
                   controller: ScrollController(),
                   physics: StyledScrollPhysics(),
                   physics: StyledScrollPhysics(),
                 ),
                 ),
@@ -102,19 +115,21 @@ class _DeleteTag extends StatelessWidget {
 
 
 class _OptionNameTextField extends StatelessWidget {
 class _OptionNameTextField extends StatelessWidget {
   final String name;
   final String name;
-  const _OptionNameTextField(this.name, {Key? key}) : super(key: key);
+  final bool autoFocus;
+  const _OptionNameTextField(
+      {required this.name, required this.autoFocus, Key? key})
+      : super(key: key);
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
-    return InputTextField(
+    return FlowyTextField(
+      autoFucous: autoFocus,
       text: name,
       text: name,
-      maxLength: 30,
-      onCanceled: () {},
-      onDone: (optionName) {
-        if (name != optionName) {
+      onSubmitted: (newName) {
+        if (name != newName) {
           context
           context
               .read<EditSelectOptionBloc>()
               .read<EditSelectOptionBloc>()
-              .add(EditSelectOptionEvent.updateName(optionName));
+              .add(EditSelectOptionEvent.updateName(newName));
         }
         }
       },
       },
     );
     );

+ 2 - 1
frontend/app_flowy/packages/appflowy_popover/lib/src/mask.dart

@@ -88,7 +88,8 @@ class _PopoverMaskState extends State<PopoverMask> {
   }
   }
 
 
   bool _handleGlobalKeyEvent(KeyEvent event) {
   bool _handleGlobalKeyEvent(KeyEvent event) {
-    if (event.logicalKey == LogicalKeyboardKey.escape) {
+    if (event.logicalKey == LogicalKeyboardKey.escape &&
+        event is KeyDownEvent) {
       if (widget.onExit != null) {
       if (widget.onExit != null) {
         widget.onExit!();
         widget.onExit!();
       }
       }

+ 53 - 3
frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/text_field.dart

@@ -1,18 +1,28 @@
 import 'package:flowy_infra/size.dart';
 import 'package:flowy_infra/size.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:textstyle_extensions/textstyle_extensions.dart';
 
 
 class FlowyTextField extends StatefulWidget {
 class FlowyTextField extends StatefulWidget {
   final String hintText;
   final String hintText;
   final String text;
   final String text;
   final void Function(String)? onChanged;
   final void Function(String)? onChanged;
   final void Function(String)? onSubmitted;
   final void Function(String)? onSubmitted;
+  final void Function()? onCanceled;
   final bool autoFucous;
   final bool autoFucous;
+  final int? maxLength;
+  final TextEditingController? controller;
+  final bool autoClearWhenDone;
   const FlowyTextField({
   const FlowyTextField({
     this.hintText = "",
     this.hintText = "",
     this.text = "",
     this.text = "",
     this.onChanged,
     this.onChanged,
     this.onSubmitted,
     this.onSubmitted,
+    this.onCanceled,
     this.autoFucous = true,
     this.autoFucous = true,
+    this.maxLength,
+    this.controller,
+    this.autoClearWhenDone = false,
     Key? key,
     Key? key,
   }) : super(key: key);
   }) : super(key: key);
 
 
@@ -23,12 +33,19 @@ class FlowyTextField extends StatefulWidget {
 class FlowyTextFieldState extends State<FlowyTextField> {
 class FlowyTextFieldState extends State<FlowyTextField> {
   late FocusNode focusNode;
   late FocusNode focusNode;
   late TextEditingController controller;
   late TextEditingController controller;
+  var textLength = 0;
 
 
   @override
   @override
   void initState() {
   void initState() {
     focusNode = FocusNode();
     focusNode = FocusNode();
-    controller = TextEditingController();
-    controller.text = widget.text;
+    focusNode.addListener(notifyDidEndEditing);
+
+    if (widget.controller != null) {
+      controller = widget.controller!;
+    } else {
+      controller = TextEditingController();
+      controller.text = widget.text;
+    }
     if (widget.autoFucous) {
     if (widget.autoFucous) {
       WidgetsBinding.instance.addPostFrameCallback((_) {
       WidgetsBinding.instance.addPostFrameCallback((_) {
         focusNode.requestFocus();
         focusNode.requestFocus();
@@ -47,9 +64,15 @@ class FlowyTextFieldState extends State<FlowyTextField> {
       },
       },
       onSubmitted: (text) {
       onSubmitted: (text) {
         widget.onSubmitted?.call(text);
         widget.onSubmitted?.call(text);
+
+        if (widget.autoClearWhenDone) {
+          controller.text = "";
+        }
       },
       },
       maxLines: 1,
       maxLines: 1,
-      style: Theme.of(context).textTheme.bodyMedium,
+      maxLength: widget.maxLength,
+      maxLengthEnforcement: MaxLengthEnforcement.truncateAfterCompositionEnds,
+      style: TextStyles.body1.size(FontSizes.s12),
       decoration: InputDecoration(
       decoration: InputDecoration(
         contentPadding: const EdgeInsets.all(10),
         contentPadding: const EdgeInsets.all(10),
         enabledBorder: OutlineInputBorder(
         enabledBorder: OutlineInputBorder(
@@ -61,6 +84,8 @@ class FlowyTextFieldState extends State<FlowyTextField> {
         ),
         ),
         isDense: true,
         isDense: true,
         hintText: widget.hintText,
         hintText: widget.hintText,
+        suffixText: _suffixText(),
+        counterText: "",
         focusedBorder: OutlineInputBorder(
         focusedBorder: OutlineInputBorder(
           borderSide: BorderSide(
           borderSide: BorderSide(
             color: Theme.of(context).colorScheme.primary,
             color: Theme.of(context).colorScheme.primary,
@@ -71,4 +96,29 @@ class FlowyTextFieldState extends State<FlowyTextField> {
       ),
       ),
     );
     );
   }
   }
+
+  @override
+  void dispose() {
+    focusNode.removeListener(notifyDidEndEditing);
+    focusNode.dispose();
+    super.dispose();
+  }
+
+  void notifyDidEndEditing() {
+    if (!focusNode.hasFocus) {
+      if (controller.text.isEmpty) {
+        widget.onCanceled?.call();
+      } else {
+        widget.onSubmitted?.call(controller.text);
+      }
+    }
+  }
+
+  String? _suffixText() {
+    if (widget.maxLength != null) {
+      return '${textLength.toString()}/${widget.maxLength.toString()}';
+    } else {
+      return null;
+    }
+  }
 }
 }

+ 1 - 1
frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/checklist_type_option.rs

@@ -80,10 +80,10 @@ impl CellDataOperation<SelectOptionIds, SelectOptionCellChangeset> for Checklist
                 }
                 }
 
 
                 new_cell_data = select_ids.to_string();
                 new_cell_data = select_ids.to_string();
-                tracing::trace!("checklist's cell data: {}", &new_cell_data);
             }
             }
         }
         }
 
 
+        tracing::trace!("checklist's cell data: {}", &new_cell_data);
         Ok(new_cell_data)
         Ok(new_cell_data)
     }
     }
 }
 }

+ 1 - 1
frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/type_option_transform.rs

@@ -48,7 +48,7 @@ impl SelectOptionTypeOptionTransformer {
         T: SelectTypeOptionSharedAction,
         T: SelectTypeOptionSharedAction,
     {
     {
         match decoded_field_type {
         match decoded_field_type {
-            FieldType::SingleSelect | FieldType::MultiSelect => {
+            FieldType::SingleSelect | FieldType::MultiSelect | FieldType::Checklist => {
                 //
                 //
                 CellBytes::from(shared.get_selected_options(cell_data))
                 CellBytes::from(shared.get_selected_options(cell_data))
             }
             }

+ 8 - 1
frontend/rust-lib/flowy-grid/tests/grid/grid_editor.rs

@@ -390,7 +390,14 @@ fn make_test_board() -> BuildGridContext {
                 let url_field = FieldBuilder::new(url).name("link").visibility(true).build();
                 let url_field = FieldBuilder::new(url).name("link").visibility(true).build();
                 grid_builder.add_field(url_field);
                 grid_builder.add_field(url_field);
             }
             }
-            FieldType::Checklist => {}
+            FieldType::Checklist => {
+                let checklist = ChecklistTypeOptionBuilder::default()
+                    .add_option(SelectOptionPB::new(FIRST_THING))
+                    .add_option(SelectOptionPB::new(SECOND_THING))
+                    .add_option(SelectOptionPB::new(THIRD_THING));
+                let checklist_field = FieldBuilder::new(checklist).name("TODO").visibility(true).build();
+                grid_builder.add_field(checklist_field);
+            }
         }
         }
     }
     }