diff --git a/CHANGELOG.md b/CHANGELOG.md index 8eef335..dcfdadf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # 10.0.0 -* Migrate to 2.10.0 +* Migrate to 2.10.0. +* Add shouldShowSelectionHandles and textSelectionGestureDetectorBuilder call back to define the behavior of handles and toolbar. +* Shortcut support for web and desktop. # 9.0.3 diff --git a/README-ZH.md b/README-ZH.md index 55b4eba..eb8e949 100644 --- a/README-ZH.md +++ b/README-ZH.md @@ -2,9 +2,12 @@ [![pub package](https://img.shields.io/pub/v/extended_text_field.svg)](https://pub.dartlang.org/packages/extended_text_field) [![GitHub stars](https://img.shields.io/github/stars/fluttercandies/extended_text_field)](https://github.com/fluttercandies/extended_text_field/stargazers) [![GitHub forks](https://img.shields.io/github/forks/fluttercandies/extended_text_field)](https://github.com/fluttercandies/extended_text_field/network) [![GitHub license](https://img.shields.io/github/license/fluttercandies/extended_text_field)](https://github.com/fluttercandies/extended_text_field/blob/master/LICENSE) [![GitHub issues](https://img.shields.io/github/issues/fluttercandies/extended_text_field)](https://github.com/fluttercandies/extended_text_field/issues) flutter-candies +文档语言: [English](README.md) | 中文简体 + 官方输入框的扩展组件,支持图片,@某人,自定义文字背景。也支持自定义菜单和选择器。 -文档语言: [English](README.md) | [中文简体](README-ZH.md) +[ExtendedTextField 在线 Demo](https://fluttercandies.github.io/extended_text_field/) + - [extended_text_field](#extended_text_field) - [限制](#限制) diff --git a/README.md b/README.md index 70e1544..c485efa 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,11 @@ [![pub package](https://img.shields.io/pub/v/extended_text_field.svg)](https://pub.dartlang.org/packages/extended_text_field) [![GitHub stars](https://img.shields.io/github/stars/fluttercandies/extended_text_field)](https://github.com/fluttercandies/extended_text_field/stargazers) [![GitHub forks](https://img.shields.io/github/forks/fluttercandies/extended_text_field)](https://github.com/fluttercandies/extended_text_field/network) [![GitHub license](https://img.shields.io/github/license/fluttercandies/extended_text_field)](https://github.com/fluttercandies/extended_text_field/blob/master/LICENSE) [![GitHub issues](https://img.shields.io/github/issues/fluttercandies/extended_text_field)](https://github.com/fluttercandies/extended_text_field/issues) flutter-candies +Language: English | [中文简体](README-ZH.md) + Extended official text field to build special text like inline image, @somebody, custom background etc quickly.It also support to build custom seleciton toolbar and handles. -Language: [English](README.md) | [中文简体](README-ZH.md) +[Web demo for ExtendedTextField](https://fluttercandies.github.io/extended_text_field/) - [extended_text_field](#extended_text_field) - [Limitation](#limitation) diff --git a/analysis_options.yaml b/analysis_options.yaml index 4b0da0b..a995197 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -173,7 +173,7 @@ linter: - slash_for_doc_comments # - sort_child_properties_last # not yet tested - sort_constructors_first - - sort_pub_dependencies + # - sort_pub_dependencies - sort_unnamed_constructors_first - test_types_in_equals - throw_in_finally diff --git a/example/analysis_options.yaml b/example/analysis_options.yaml index 61b6c4d..4b0da0b 100644 --- a/example/analysis_options.yaml +++ b/example/analysis_options.yaml @@ -1,29 +1,207 @@ -# This file configures the analyzer, which statically analyzes Dart code to -# check for errors, warnings, and lints. +# Specify analysis options. # -# The issues identified by the analyzer are surfaced in the UI of Dart-enabled -# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be -# invoked from the command line by running `flutter analyze`. +# Until there are meta linter rules, each desired lint must be explicitly enabled. +# See: https://github.com/dart-lang/linter/issues/288 +# +# For a list of lints, see: http://dart-lang.github.io/linter/lints/ +# See the configuration guide for more +# https://github.com/dart-lang/sdk/tree/master/pkg/analyzer#configuring-the-analyzer +# +# There are other similar analysis options files in the flutter repos, +# which should be kept in sync with this file: +# +# - analysis_options.yaml (this file) +# - packages/flutter/lib/analysis_options_user.yaml +# - https://github.com/flutter/plugins/blob/master/analysis_options.yaml +# - https://github.com/flutter/engine/blob/master/analysis_options.yaml +# +# This file contains the analysis options used by Flutter tools, such as IntelliJ, +# Android Studio, and the `flutter analyze` command. -# The following line activates a set of recommended lints for Flutter apps, -# packages, and plugins designed to encourage good coding practices. -include: package:flutter_lints/flutter.yaml +analyzer: + strong-mode: + implicit-casts: false + implicit-dynamic: false + errors: + # treat missing required parameters as a warning (not a hint) + missing_required_param: warning + # treat missing returns as a warning (not a hint) + missing_return: warning + # allow having TODOs in the code + todo: ignore + # Ignore analyzer hints for updating pubspecs when using Future or + # Stream and not importing dart:async + # Please see https://github.com/flutter/flutter/pull/24528 for details. + sdk_version_async_exported_from_core: ignore + # exclude: + # - "bin/cache/**" + # # the following two are relative to the stocks example and the flutter package respectively + # # see https://github.com/dart-lang/sdk/issues/28463 + # - "lib/i18n/messages_*.dart" + # - "lib/src/http/**" linter: - # The lint rules applied to this project can be customized in the - # section below to disable rules from the `package:flutter_lints/flutter.yaml` - # included above or to enable additional rules. A list of all available lints - # and their documentation is published at - # https://dart-lang.github.io/linter/lints/index.html. - # - # Instead of disabling a lint rule for the entire project in the - # section below, it can also be suppressed for a single line of code - # or a specific dart file by using the `// ignore: name_of_lint` and - # `// ignore_for_file: name_of_lint` syntax on the line or in the file - # producing the lint. rules: - # avoid_print: false # Uncomment to disable the `avoid_print` rule - # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule - -# Additional information about this file can be found at -# https://dart.dev/guides/language/analysis-options + # these rules are documented on and in the same order as + # the Dart Lint rules page to make maintenance easier + # https://github.com/dart-lang/linter/blob/master/example/all.yaml + - always_declare_return_types + - always_put_control_body_on_new_line + # - always_put_required_named_parameters_first # we prefer having parameters in the same order as fields https://github.com/flutter/flutter/issues/10219 + - always_require_non_null_named_parameters + - always_specify_types + - annotate_overrides + # - avoid_annotating_with_dynamic # conflicts with always_specify_types + # - avoid_as # required for implicit-casts: true + - avoid_bool_literals_in_conditional_expressions + # - avoid_catches_without_on_clauses # we do this commonly + # - avoid_catching_errors # we do this commonly + - avoid_classes_with_only_static_members + # - avoid_double_and_int_checks # only useful when targeting JS runtime + - avoid_empty_else + # - avoid_equals_and_hash_code_on_mutable_classes # not yet tested + - avoid_field_initializers_in_const_classes + - avoid_function_literals_in_foreach_calls + # - avoid_implementing_value_types # not yet tested + - avoid_init_to_null + # - avoid_js_rounded_ints # only useful when targeting JS runtime + - avoid_null_checks_in_equality_operators + # - avoid_positional_boolean_parameters # not yet tested + # - avoid_print # not yet tested + # - avoid_private_typedef_functions # we prefer having typedef (discussion in https://github.com/flutter/flutter/pull/16356) + # - avoid_redundant_argument_values # not yet tested + - avoid_relative_lib_imports + - avoid_renaming_method_parameters + - avoid_return_types_on_setters + # - avoid_returning_null # there are plenty of valid reasons to return null + # - avoid_returning_null_for_future # not yet tested + - avoid_returning_null_for_void + # - avoid_returning_this # there are plenty of valid reasons to return this + # - avoid_setters_without_getters # not yet tested + # - avoid_shadowing_type_parameters # not yet tested + - avoid_single_cascade_in_expression_statements + - avoid_slow_async_io + - avoid_types_as_parameter_names + # - avoid_types_on_closure_parameters # conflicts with always_specify_types + # - avoid_unnecessary_containers # not yet tested + - avoid_unused_constructor_parameters + - avoid_void_async + # - avoid_web_libraries_in_flutter # not yet tested + - await_only_futures + - camel_case_extensions + - camel_case_types + - cancel_subscriptions + # - cascade_invocations # not yet tested + # - close_sinks # not reliable enough + # - comment_references # blocked on https://github.com/flutter/flutter/issues/20765 + # - constant_identifier_names # needs an opt-out https://github.com/dart-lang/linter/issues/204 + - control_flow_in_finally + # - curly_braces_in_flow_control_structures # not yet tested + # - diagnostic_describe_all_properties # not yet tested + - directives_ordering + - empty_catches + - empty_constructor_bodies + - empty_statements + # - file_names # not yet tested + - flutter_style_todos + - hash_and_equals + - implementation_imports + # - invariant_booleans # too many false positives: https://github.com/dart-lang/linter/issues/811 + - iterable_contains_unrelated_type + # - join_return_with_assignment # not yet tested + - library_names + - library_prefixes + # - lines_longer_than_80_chars # not yet tested + - list_remove_unrelated_type + # - literal_only_boolean_expressions # too many false positives: https://github.com/dart-lang/sdk/issues/34181 + # - missing_whitespace_between_adjacent_strings # not yet tested + - no_adjacent_strings_in_list + - no_duplicate_case_values + # - no_logic_in_create_state # not yet tested + # - no_runtimeType_toString # not yet tested + - non_constant_identifier_names + # - null_closures # not yet tested + # - omit_local_variable_types # opposite of always_specify_types + # - one_member_abstracts # too many false positives + # - only_throw_errors # https://github.com/flutter/flutter/issues/5792 + - overridden_fields + - package_api_docs + - package_names + - package_prefixed_library_names + # - parameter_assignments # we do this commonly + - prefer_adjacent_string_concatenation + - prefer_asserts_in_initializer_lists + # - prefer_asserts_with_message # not yet tested + - prefer_collection_literals + - prefer_conditional_assignment + - prefer_const_constructors + - prefer_const_constructors_in_immutables + - prefer_const_declarations + - prefer_const_literals_to_create_immutables + # - prefer_constructors_over_static_methods # not yet tested + - prefer_contains + # - prefer_double_quotes # opposite of prefer_single_quotes + - prefer_equal_for_default_values + # - prefer_expression_function_bodies # conflicts with https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#consider-using--for-short-functions-and-methods + - prefer_final_fields + - prefer_final_in_for_each + - prefer_final_locals + - prefer_for_elements_to_map_fromIterable + - prefer_foreach + # - prefer_function_declarations_over_variables # not yet tested + - prefer_generic_function_type_aliases + - prefer_if_elements_to_conditional_expressions + - prefer_if_null_operators + - prefer_initializing_formals + - prefer_inlined_adds + # - prefer_int_literals # not yet tested + # - prefer_interpolation_to_compose_strings # not yet tested + - prefer_is_empty + - prefer_is_not_empty + - prefer_is_not_operator + - prefer_iterable_whereType + # - prefer_mixin # https://github.com/dart-lang/language/issues/32 + # - prefer_null_aware_operators # disable until NNBD, see https://github.com/flutter/flutter/pull/32711#issuecomment-492930932 + # - prefer_relative_imports # not yet tested + - prefer_single_quotes + - prefer_spread_collections + - prefer_typing_uninitialized_variables + - prefer_void_to_null + # - provide_deprecation_message # not yet tested + # - public_member_api_docs # enabled on a case-by-case basis; see e.g. packages/analysis_options.yaml + - recursive_getters + - slash_for_doc_comments + # - sort_child_properties_last # not yet tested + - sort_constructors_first + - sort_pub_dependencies + - sort_unnamed_constructors_first + - test_types_in_equals + - throw_in_finally + # - type_annotate_public_apis # subset of always_specify_types + - type_init_formals + # - unawaited_futures # too many false positives + # - unnecessary_await_in_return # not yet tested + - unnecessary_brace_in_string_interps + - unnecessary_const + # - unnecessary_final # conflicts with prefer_final_locals + - unnecessary_getters_setters + # - unnecessary_lambdas # has false positives: https://github.com/dart-lang/linter/issues/498 + - unnecessary_new + - unnecessary_null_aware_assignments + - unnecessary_null_in_if_null_operators + - unnecessary_overrides + - unnecessary_parenthesis + - unnecessary_statements + - unnecessary_string_interpolations + - unnecessary_this + - unrelated_type_equality_checks + # - unsafe_html # not yet tested + - use_full_hex_values_for_flutter_colors + # - use_function_type_syntax_for_parameters # not yet tested + # - use_key_in_widget_constructors # not yet tested + - use_rethrow_when_possible + # - use_setters_to_change_properties # not yet tested + # - use_string_buffers # has false positives: https://github.com/dart-lang/sdk/issues/34182 + # - use_to_and_as_if_applicable # has false positives, so we prefer to catch this by code-review + - valid_regexps + - void_checks diff --git a/example/lib/pages/complex/text_demo.dart b/example/lib/pages/complex/text_demo.dart index aa14724..448694f 100644 --- a/example/lib/pages/complex/text_demo.dart +++ b/example/lib/pages/complex/text_demo.dart @@ -127,6 +127,14 @@ class _TextDemoState extends State { //EditableText(controller: controller, focusNode: focusNode, style: style, cursorColor: cursorColor, backgroundCursorColor: backgroundCursorColor) ExtendedTextField( key: _key, + // StrutStyle get strutStyle { + // if (_strutStyle == null) { + // return StrutStyle.fromTextStyle(style, forceStrutHeight: true); + // } + // return _strutStyle!.inheritFromTextStyle(style); + // } + // default strutStyle is not good for WidgetSpan + strutStyle: const StrutStyle(), specialTextSpanBuilder: MySpecialTextSpanBuilder( showAtBackground: true, ), diff --git a/example/lib/pages/main_page.dart b/example/lib/pages/main_page.dart index 75c097d..dfdbcf0 100644 --- a/example/lib/pages/main_page.dart +++ b/example/lib/pages/main_page.dart @@ -1,6 +1,7 @@ import 'package:collection/collection.dart'; import 'package:example/example_routes.dart'; import 'package:ff_annotation_route_library/ff_annotation_route_library.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -55,17 +56,18 @@ class MainPage extends StatelessWidget { }, ), ), - ButtonTheme( - padding: const EdgeInsets.only(right: 10.0), - minWidth: 0.0, - child: TextButton( - child: - Image.network('https://pub.idqqimg.com/wpa/images/group.png'), - onPressed: () { - launch('https://jq.qq.com/?_wv=1027&k=5bcc0gy'); - }, - ), - ) + if (!kIsWeb) + ButtonTheme( + padding: const EdgeInsets.only(right: 10.0), + minWidth: 0.0, + child: TextButton( + child: Image.network( + 'https://pub.idqqimg.com/wpa/images/group.png'), + onPressed: () { + launch('https://jq.qq.com/?_wv=1027&k=5bcc0gy'); + }, + ), + ) ], ), body: ListView.builder( diff --git a/example/lib/pages/simple/custom_toolbar.dart b/example/lib/pages/simple/custom_toolbar.dart index a0f20a3..c57daf0 100644 --- a/example/lib/pages/simple/custom_toolbar.dart +++ b/example/lib/pages/simple/custom_toolbar.dart @@ -1,3 +1,5 @@ +// ignore_for_file: always_put_control_body_on_new_line + import 'package:example/special_text/my_extended_text_selection_controls.dart'; import 'package:example/special_text/my_special_text_span_builder.dart'; import 'package:extended_text_field/extended_text_field.dart'; @@ -47,9 +49,91 @@ class _CustomToolBarState extends State { specialTextSpanBuilder: _mySpecialTextSpanBuilder, controller: controller, maxLines: null, + // StrutStyle get strutStyle { + // if (_strutStyle == null) { + // return StrutStyle.fromTextStyle(style, forceStrutHeight: true); + // } + // return _strutStyle!.inheritFromTextStyle(style); + // } + // default strutStyle is not good for WidgetSpan + strutStyle: const StrutStyle(), + shouldShowSelectionHandles: _shouldShowSelectionHandles, + textSelectionGestureDetectorBuilder: ({ + required ExtendedTextSelectionGestureDetectorBuilderDelegate + delegate, + required Function showToolbar, + required Function hideToolbar, + required Function? onTap, + required BuildContext context, + required Function? requestKeyboard, + }) { + return MyCommonTextSelectionGestureDetectorBuilder( + delegate: delegate, + showToolbar: showToolbar, + hideToolbar: hideToolbar, + onTap: onTap, + context: context, + requestKeyboard: requestKeyboard, + ); + }, ), ), ), ); } + + bool _shouldShowSelectionHandles( + SelectionChangedCause? cause, + CommonTextSelectionGestureDetectorBuilder selectionGestureDetectorBuilder, + TextEditingValue editingValue, + ) { + // When the text field is activated by something that doesn't trigger the + // selection overlay, we shouldn't show the handles either. + + // + // if (!selectionGestureDetectorBuilder.shouldShowSelectionToolbar) + // return false; + + if (cause == SelectionChangedCause.keyboard) return false; + + // if (widget.readOnly && _effectiveController.selection.isCollapsed) + // return false; + + // if (!_isEnabled) return false; + + if (cause == SelectionChangedCause.longPress) return true; + + if (editingValue.text.isNotEmpty) return true; + + return false; + } +} + +class MyCommonTextSelectionGestureDetectorBuilder + extends CommonTextSelectionGestureDetectorBuilder { + MyCommonTextSelectionGestureDetectorBuilder( + {required ExtendedTextSelectionGestureDetectorBuilderDelegate delegate, + required Function showToolbar, + required Function hideToolbar, + required Function? onTap, + required BuildContext context, + required Function? requestKeyboard}) + : super( + delegate: delegate, + showToolbar: showToolbar, + hideToolbar: hideToolbar, + onTap: onTap, + context: context, + requestKeyboard: requestKeyboard, + ); + @override + void onTapDown(TapDownDetails details) { + super.onTapDown(details); + + /// always show toolbar + shouldShowSelectionToolbar = true; + } + + @override + bool get showToolbarInWeb => true; } diff --git a/example/lib/pages/simple/widget_span.dart b/example/lib/pages/simple/widget_span.dart index 99675f0..b60b5c4 100644 --- a/example/lib/pages/simple/widget_span.dart +++ b/example/lib/pages/simple/widget_span.dart @@ -64,6 +64,14 @@ class _WidgetSpanDemoState extends State { controller: controller, specialTextSpanBuilder: _emailSpanBuilder, maxLines: null, + // StrutStyle get strutStyle { + // if (_strutStyle == null) { + // return StrutStyle.fromTextStyle(style, forceStrutHeight: true); + // } + // return _strutStyle!.inheritFromTextStyle(style); + // } + // default strutStyle is not good for WidgetSpan + strutStyle: const StrutStyle(), decoration: InputDecoration( suffixIcon: IconButton( icon: const Icon(Icons.add), diff --git a/example/lib/special_text/emoji_text.dart b/example/lib/special_text/emoji_text.dart index 8ea80e7..406ae81 100644 --- a/example/lib/special_text/emoji_text.dart +++ b/example/lib/special_text/emoji_text.dart @@ -1,5 +1,4 @@ import 'package:extended_text_library/extended_text_library.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; ///emoji/image text @@ -12,9 +11,7 @@ class EmojiText extends SpecialText { InlineSpan finishText() { final String key = toString(); - ///https://github.com/flutter/flutter/issues/42086 - /// widget span is not working on web - if (EmojiUitl.instance.emojiMap.containsKey(key) && !kIsWeb) { + if (EmojiUitl.instance.emojiMap.containsKey(key)) { //fontsize id define image height //size = 30.0/26.0 * fontSize const double size = 20.0; diff --git a/example/lib/special_text/my_special_text_span_builder.dart b/example/lib/special_text/my_special_text_span_builder.dart index e623aa7..ae1bbe7 100644 --- a/example/lib/special_text/my_special_text_span_builder.dart +++ b/example/lib/special_text/my_special_text_span_builder.dart @@ -1,6 +1,5 @@ import 'package:example/special_text/image_text.dart'; import 'package:extended_text_library/extended_text_library.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'at_text.dart'; diff --git a/example/pubspec.yaml b/example/pubspec.yaml index f89209e..eafa0d2 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -15,7 +15,7 @@ version: 1.0.0+1 environment: sdk: '>=2.12.0 <3.0.0' - flutter: ">=2.8.0" + flutter: ">=2.10.0" dependencies: # The following adds the Cupertino Icons font to your application. @@ -24,8 +24,8 @@ dependencies: cupertino_icons: ^1.0.4 - extended_text_field: - path: ../ + extended_text: ^9.0.0 + extended_text_library: ^9.0.0 ff_annotation_route_library: ^3.0.0 flutter: sdk: flutter @@ -38,7 +38,9 @@ dev_dependencies: flutter_test: sdk: flutter dependency_overrides: - + extended_text_field: + path: ../ + # For information on the generic Dart part of this file, see the # following page: https://www.dartlang.org/tools/pub/pubspec diff --git a/lib/src/extended_editable_text.dart b/lib/src/extended_editable_text.dart index 4088681..b054c0c 100644 --- a/lib/src/extended_editable_text.dart +++ b/lib/src/extended_editable_text.dart @@ -177,6 +177,7 @@ class ExtendedEditableText extends StatefulWidget { this.restorationId, this.scrollBehavior, this.enableIMEPersonalizedLearning = true, + this.showToolbarInWeb = false, }) : assert(controller != null), assert(focusNode != null), assert(obscuringCharacter != null && obscuringCharacter.length == 1), @@ -233,6 +234,8 @@ class ExtendedEditableText extends StatefulWidget { showCursor = showCursor ?? !readOnly, super(key: key); + final bool showToolbarInWeb; + ///build your ccustom text span final SpecialTextSpanBuilder? specialTextSpanBuilder; @@ -596,7 +599,7 @@ class ExtendedEditableText extends StatefulWidget { /// /// For [CupertinoTextField]s, the value is set to the ambient /// [CupertinoThemeData.primaryColor] with 20% opacity. For [TextField]s, the - /// value is set to the ambient [ThemeData.textSelectionColor]. + /// value is set to the ambient [TextSelectionThemeData.selectionColor]. final Color? selectionColor; /// {@template flutter.widgets.editableText.selectionControls} @@ -1233,8 +1236,7 @@ class ExtendedEditableTextState extends State final ValueNotifier _cursorVisibilityNotifier = ValueNotifier(true); final GlobalKey _editableKey = GlobalKey(); - final ClipboardStatusNotifier? _clipboardStatus = - kIsWeb ? null : ClipboardStatusNotifier(); + ClipboardStatusNotifier? _clipboardStatus; TextInputConnection? _textInputConnection; ExtendedTextSelectionOverlay? _selectionOverlay; @@ -1426,6 +1428,8 @@ class ExtendedEditableTextState extends State vsync: this, duration: _fadeDuration, )..addListener(_onCursorColorTick); + _clipboardStatus = + kIsWeb && !widget.showToolbarInWeb ? null : ClipboardStatusNotifier(); _clipboardStatus?.addListener(_onChangedClipboardStatus); widget.controller.addListener(_didChangeTextEditingValue); widget.focusNode.addListener(_handleFocusChanged); @@ -1531,6 +1535,11 @@ class ExtendedEditableTextState extends State widget.selectionControls?.canPaste(this) == true) { _clipboardStatus?.update(); } + + if (oldWidget.showToolbarInWeb != widget.showToolbarInWeb) { + _clipboardStatus = + kIsWeb && !widget.showToolbarInWeb ? null : ClipboardStatusNotifier(); + } } @override @@ -2585,12 +2594,12 @@ class ExtendedEditableTextState extends State /// /// Returns `false` if a toolbar couldn't be shown, such as when the toolbar /// is already shown, or when no text selection currently exists. - bool showToolbar() { + bool showToolbar({bool showToolbarInWeb = false}) { // Web is using native dom elements to enable clipboard functionality of the // toolbar: copy, paste, select, cut. It might also provide additional // functionality depending on the browser (such as translate). Due to this // we should not show a Flutter toolbar for the editable text elements. - if (kIsWeb) { + if (kIsWeb && !showToolbarInWeb) { return false; } diff --git a/lib/src/extended_text_field.dart b/lib/src/extended_text_field.dart index 51a4ef4..35d80ed 100644 --- a/lib/src/extended_text_field.dart +++ b/lib/src/extended_text_field.dart @@ -241,6 +241,8 @@ class ExtendedTextField extends StatefulWidget { this.clipBehavior = Clip.hardEdge, this.restorationId, this.enableIMEPersonalizedLearning = true, + this.shouldShowSelectionHandles, + this.textSelectionGestureDetectorBuilder, }) : assert(textAlign != null), assert(readOnly != null), assert(autofocus != null), @@ -298,7 +300,16 @@ class ExtendedTextField extends StatefulWidget { )), super(key: key); - ///build your ccustom text span + /// create custom TextSelectionGestureDetectorBuilder + final TextSelectionGestureDetectorBuilderCallback? + textSelectionGestureDetectorBuilder; + + /// Whether should show selection handles + /// handles are not shown in desktop or web as default + /// you can define your behavior + final ShouldShowSelectionHandlesCallback? shouldShowSelectionHandles; + + /// build your ccustom text span final SpecialTextSpanBuilder? specialTextSpanBuilder; /// Controls the text being edited. @@ -900,19 +911,8 @@ class _ExtendedTextFieldState extends State @override void initState() { super.initState(); - _selectionGestureDetectorBuilder = - CommonTextSelectionGestureDetectorBuilder( - delegate: this, - hideToolbar: () { - _editableText!.hideToolbar(); - }, - showToolbar: () { - _editableText!.showToolbar(); - }, - onTap: widget.onTap, - context: context, - requestKeyboard: _requestKeyboard, - ); + _initGestureDetectorBuilder(); + if (widget.controller == null) { _createLocalController(); } @@ -920,6 +920,42 @@ class _ExtendedTextFieldState extends State _effectiveFocusNode.addListener(_handleFocusChanged); } + void _initGestureDetectorBuilder() { + if (widget.textSelectionGestureDetectorBuilder != null) { + _selectionGestureDetectorBuilder = + widget.textSelectionGestureDetectorBuilder!( + delegate: this, + hideToolbar: () { + _editableText!.hideToolbar(); + }, + showToolbar: () { + _editableText!.showToolbar( + showToolbarInWeb: _selectionGestureDetectorBuilder.showToolbarInWeb, + ); + }, + onTap: widget.onTap, + context: context, + requestKeyboard: _requestKeyboard, + ); + } else { + _selectionGestureDetectorBuilder = + CommonTextSelectionGestureDetectorBuilder( + delegate: this, + hideToolbar: () { + _editableText!.hideToolbar(); + }, + showToolbar: () { + _editableText!.showToolbar( + showToolbarInWeb: _selectionGestureDetectorBuilder.showToolbarInWeb, + ); + }, + onTap: widget.onTap, + context: context, + requestKeyboard: _requestKeyboard, + ); + } + } + bool get _canRequestFocus { final NavigationMode mode = MediaQuery.maybeOf(context)?.navigationMode ?? NavigationMode.traditional; @@ -962,6 +998,9 @@ class _ExtendedTextFieldState extends State _showSelectionHandles = !widget.readOnly; } } + if (widget.textSelectionGestureDetectorBuilder != + oldWidget.textSelectionGestureDetectorBuilder) + _initGestureDetectorBuilder(); } @override @@ -1004,6 +1043,13 @@ class _ExtendedTextFieldState extends State } bool _shouldShowSelectionHandles(SelectionChangedCause? cause) { + if (widget.shouldShowSelectionHandles != null) { + return widget.shouldShowSelectionHandles!( + cause, + _selectionGestureDetectorBuilder, + _editableText!.textEditingValue, + ); + } // When the text field is activated by something that doesn't trigger the // selection overlay, we shouldn't show the handles either. if (!_selectionGestureDetectorBuilder.shouldShowSelectionToolbar) @@ -1116,8 +1162,8 @@ class _ExtendedTextFieldState extends State final TextSelectionThemeData selectionTheme = TextSelectionTheme.of(context); final TextStyle style = theme.textTheme.subtitle1!.merge(widget.style); - final Brightness keyboardAppearance = - widget.keyboardAppearance ?? theme.primaryColorBrightness; + final Brightness keyboardAppearance = widget.keyboardAppearance ?? + ThemeData.estimateBrightnessForColor(theme.primaryColor); final TextEditingController controller = _effectiveController; final FocusNode focusNode = _effectiveFocusNode; final List formatters = [ @@ -1278,8 +1324,9 @@ class _ExtendedTextFieldState extends State autofillClient: this, autocorrectionTextRectColor: autocorrectionTextRectColor, clipBehavior: widget.clipBehavior, - restorationId: 'editable', + restorationId: 'ExtendedEditableText', enableIMEPersonalizedLearning: widget.enableIMEPersonalizedLearning, + showToolbarInWeb: _selectionGestureDetectorBuilder.showToolbarInWeb, ), ), );