Update flutter_quill and flutter_quill_extensions (#1520)

* Update flutter_quill and flutter_quill_extensions
pull/1521/head
Ellet 1 year ago committed by GitHub
parent 700f003866
commit 2104514f6e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      CHANGELOG.md
  2. 1
      doc/todo.md
  3. 6
      example/android/app/src/main/AndroidManifest.xml
  4. 44
      example/lib/pages/home_page.dart
  5. 5
      example/pubspec.yaml
  6. 5
      example/web/index.html
  7. 4
      flutter_quill_extensions/CHANGELOG.md
  8. 41
      flutter_quill_extensions/lib/presentation/embeds/editor/image/image.dart
  9. 9
      flutter_quill_extensions/lib/presentation/embeds/editor/image/image_menu.dart
  10. 15
      flutter_quill_extensions/lib/presentation/embeds/toolbar/camera_button/camera_button.dart
  11. 4
      flutter_quill_extensions/lib/presentation/embeds/widgets/image_resizer.dart
  12. 18
      flutter_quill_extensions/lib/presentation/models/config/editor/image/image.dart
  13. 2
      flutter_quill_extensions/pubspec.yaml
  14. 2
      lib/flutter_quill.dart
  15. 8
      lib/src/models/config/editor/configurations.dart
  16. 290
      lib/src/models/config/raw_editor/configurations.dart
  17. 2
      lib/src/utils/extensions/build_context.dart
  18. 4
      lib/src/utils/string.dart
  19. 117
      lib/src/widgets/editor/editor.dart
  20. 250
      lib/src/widgets/raw_editor/raw_editor.dart
  21. 18
      lib/src/widgets/raw_editor/raw_editor_actions.dart
  22. 178
      lib/src/widgets/raw_editor/raw_editor_state.dart
  23. 32
      lib/src/widgets/raw_editor/raw_editor_state_selection_delegate_mixin.dart
  24. 37
      lib/src/widgets/raw_editor/raw_editor_state_text_input_client_mixin.dart
  25. 8
      lib/src/widgets/toolbar/buttons/quill_icon.dart
  26. 2
      lib/src/widgets/toolbar/buttons/select_header_style.dart
  27. 8
      lib/src/widgets/utils/provider.dart
  28. 2
      pubspec.yaml

@ -1,3 +1,11 @@
## [8.4.2]
- **Breaking change**: The `QuillRawEditor` configurations has been moved to a seperated class, also the `readOnly` has been renamed to `isReadOnly`, if you are using the `QuillEditor` you don't have to change anything
- Allow the developer to use override the `TextInputAction` in both `QuillRawEditor` and `QuillEditor`
- You can use now the `QuillRawEditor` without `QuillEditorProvider`
- Bug fixes
- Add implementation of image cropping in the `example`
- Update the `QuillToolbarIconButton` to use the material 3 buttons
## [8.4.1] ## [8.4.1]
- Add `copyWith` in `OptionalSize` class - Add `copyWith` in `OptionalSize` class

@ -27,6 +27,7 @@ This is a todo list page that added recently and will be updated soon.
1. Improve the Raw Quill Editor, for more [info](https://github.com/singerdmx/flutter-quill/issues/1509) 1. Improve the Raw Quill Editor, for more [info](https://github.com/singerdmx/flutter-quill/issues/1509)
2. Provide more support to all the platforms 2. Provide more support to all the platforms
3. Extract the shared properties between `QuillRawEditorConfigurations` and `QuillEditorConfigurations`
### Bugs ### Bugs

@ -42,6 +42,12 @@
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity
android:name="com.yalantis.ucrop.UCropActivity"
android:screenOrientation="portrait"
android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
<!-- Don't delete the meta-data below. <!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java --> This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data <meta-data

@ -15,6 +15,7 @@ import 'package:flutter_quill/flutter_quill.dart';
import 'package:flutter_quill_extensions/flutter_quill_extensions.dart'; import 'package:flutter_quill_extensions/flutter_quill_extensions.dart';
import 'package:flutter_quill_extensions/logic/services/image_picker/image_picker.dart'; import 'package:flutter_quill_extensions/logic/services/image_picker/image_picker.dart';
import 'package:flutter_quill_extensions/presentation/embeds/widgets/image.dart'; import 'package:flutter_quill_extensions/presentation/embeds/widgets/image.dart';
import 'package:image_cropper/image_cropper.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
@ -455,6 +456,42 @@ class _HomePageState extends State<HomePage> {
); );
} }
/// When inserting an image
OnImageInsertCallback get onImageInsert {
return (image, controller) async {
final croppedFile = await ImageCropper().cropImage(
sourcePath: image,
aspectRatioPresets: [
CropAspectRatioPreset.square,
CropAspectRatioPreset.ratio3x2,
CropAspectRatioPreset.original,
CropAspectRatioPreset.ratio4x3,
CropAspectRatioPreset.ratio16x9
],
uiSettings: [
AndroidUiSettings(
toolbarTitle: 'Cropper',
toolbarColor: Colors.deepOrange,
toolbarWidgetColor: Colors.white,
initAspectRatio: CropAspectRatioPreset.original,
lockAspectRatio: false,
),
IOSUiSettings(
title: 'Cropper',
),
WebUiSettings(
context: context,
),
],
);
final newImage = croppedFile?.path;
if (newImage == null) {
return;
}
controller.insertImageBlock(imageSource: newImage);
};
}
QuillToolbar get quillToolbar { QuillToolbar get quillToolbar {
final customButtons = [ final customButtons = [
QuillToolbarCustomButtonOptions( QuillToolbarCustomButtonOptions(
@ -481,13 +518,14 @@ class _HomePageState extends State<HomePage> {
configurations: QuillToolbarConfigurations( configurations: QuillToolbarConfigurations(
customButtons: customButtons, customButtons: customButtons,
embedButtons: FlutterQuillEmbeds.toolbarButtons( embedButtons: FlutterQuillEmbeds.toolbarButtons(
cameraButtonOptions: const QuillToolbarCameraButtonOptions(),
imageButtonOptions: QuillToolbarImageButtonOptions( imageButtonOptions: QuillToolbarImageButtonOptions(
imageButtonConfigurations: QuillToolbarImageConfigurations( imageButtonConfigurations: QuillToolbarImageConfigurations(
onImageInsertedCallback: (image) async { onImageInsertedCallback: (image) async {
_onImagePickCallback(File(image)); _onImagePickCallback(File(image));
}, },
onImageInsertCallback: onImageInsert,
), ),
// webImagePickImpl: _webImagePickImpl,
), ),
), ),
buttonOptions: QuillToolbarButtonOptions( buttonOptions: QuillToolbarButtonOptions(
@ -496,7 +534,6 @@ class _HomePageState extends State<HomePage> {
), ),
), ),
), ),
// afterButtonPressed: _focusNode.requestFocus,
); );
} }
if (isDesktop(supportWeb: false)) { if (isDesktop(supportWeb: false)) {
@ -504,6 +541,7 @@ class _HomePageState extends State<HomePage> {
configurations: QuillToolbarConfigurations( configurations: QuillToolbarConfigurations(
customButtons: customButtons, customButtons: customButtons,
embedButtons: FlutterQuillEmbeds.toolbarButtons( embedButtons: FlutterQuillEmbeds.toolbarButtons(
cameraButtonOptions: const QuillToolbarCameraButtonOptions(),
imageButtonOptions: QuillToolbarImageButtonOptions( imageButtonOptions: QuillToolbarImageButtonOptions(
imageButtonConfigurations: QuillToolbarImageConfigurations( imageButtonConfigurations: QuillToolbarImageConfigurations(
onImageInsertedCallback: (image) async { onImageInsertedCallback: (image) async {
@ -525,6 +563,7 @@ class _HomePageState extends State<HomePage> {
configurations: QuillToolbarConfigurations( configurations: QuillToolbarConfigurations(
customButtons: customButtons, customButtons: customButtons,
embedButtons: FlutterQuillEmbeds.toolbarButtons( embedButtons: FlutterQuillEmbeds.toolbarButtons(
cameraButtonOptions: const QuillToolbarCameraButtonOptions(),
videoButtonOptions: QuillToolbarVideoButtonOptions( videoButtonOptions: QuillToolbarVideoButtonOptions(
videoConfigurations: QuillToolbarVideoConfigurations( videoConfigurations: QuillToolbarVideoConfigurations(
onVideoInsertedCallback: (video) => onVideoInsertedCallback: (video) =>
@ -533,6 +572,7 @@ class _HomePageState extends State<HomePage> {
), ),
imageButtonOptions: QuillToolbarImageButtonOptions( imageButtonOptions: QuillToolbarImageButtonOptions(
imageButtonConfigurations: QuillToolbarImageConfigurations( imageButtonConfigurations: QuillToolbarImageConfigurations(
onImageInsertCallback: onImageInsert,
onImageInsertedCallback: (image) async { onImageInsertedCallback: (image) async {
_onImagePickCallback(File(image)); _onImagePickCallback(File(image));
}, },

@ -17,10 +17,11 @@ dependencies:
path_provider: ^2.1.1 path_provider: ^2.1.1
# filesystem_picker: ^4.0.0 # filesystem_picker: ^4.0.0
file_picker: ^6.1.1 file_picker: ^6.1.1
flutter_quill: ^8.2.5 flutter_quill: ^8.4.1
flutter_quill_extensions: ^0.6.3 flutter_quill_extensions: ^0.6.5
path: ^1.8.3 path: ^1.8.3
desktop_drop: ^0.4.4 desktop_drop: ^0.4.4
image_cropper: ^5.0.0
dependency_overrides: dependency_overrides:
flutter_quill: flutter_quill:

@ -32,6 +32,11 @@
<title>example</title> <title>example</title>
<link rel="manifest" href="manifest.json"> <link rel="manifest" href="manifest.json">
<!-- Croppie -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/croppie/2.6.5/croppie.css" />
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/exif-js/2.3.0/exif.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/croppie/2.6.5/croppie.min.js"></script>
<script> <script>
// The value below is injected by flutter build, do not touch. // The value below is injected by flutter build, do not touch.
const serviceWorkerVersion = null; const serviceWorkerVersion = null;

@ -1,3 +1,7 @@
## 0.6.6
- Add `onImageClicked` in the `QuillEditorImageEmbedConfigurations`
- Fix image resizing on mobile
## 0.6.5 ## 0.6.5
- Support the new improved platform checking of `flutter_quill` - Support the new improved platform checking of `flutter_quill`
- Update the Image embed builder logic - Update the Image embed builder logic

@ -112,6 +112,20 @@ class QuillEditorImageEmbedBuilder extends EmbedBuilder {
QuillSharedExtensionsConfigurations.get(context: context) QuillSharedExtensionsConfigurations.get(context: context)
.imageSaverService; .imageSaverService;
return GestureDetector( return GestureDetector(
onTap: configurations.onImageClicked ??
() => showDialog(
context: context,
builder: (context) {
return ImageOptionsMenu(
controller: controller,
configurations: configurations,
imageSource: imageSource,
imageSize: imageSize,
isReadOnly: readOnly,
imageSaverService: imageSaverService,
);
},
),
child: Builder( child: Builder(
builder: (context) { builder: (context) {
if (margin != null) { if (margin != null) {
@ -123,19 +137,6 @@ class QuillEditorImageEmbedBuilder extends EmbedBuilder {
return image; return image;
}, },
), ),
onTap: () => showDialog(
context: context,
builder: (context) {
return ImageOptionsMenu(
controller: controller,
configurations: configurations,
imageSource: imageSource,
imageSize: imageSize,
isReadOnly: readOnly,
imageSaverService: imageSaverService,
);
},
),
); );
} }
} }
@ -191,10 +192,16 @@ class QuillEditorImageEmbedBuilder extends EmbedBuilder {
// It css value as string but we will try to support it anyway // It css value as string but we will try to support it anyway
// TODO: This could be improved much better // TODO: This could be improved much better
final cssHeightValue = double.tryParse( final cssHeightValue = double.tryParse(((base.isMobile(supportWeb: false)
(attrs[Attribute.height.key] ?? '').replaceFirst('px', '')); ? attrs[Attribute.mobileHeight]
final cssWidthValue = double.tryParse( : attrs[Attribute.height.key]) ??
(attrs[Attribute.width.key] ?? '').replaceFirst('px', '')); '')
.replaceFirst('px', ''));
final cssWidthValue = double.tryParse(((!base.isMobile(supportWeb: false)
? attrs[Attribute.width.key]
: attrs[Attribute.mobileWidth]) ??
'')
.replaceFirst('px', ''));
if (cssHeightValue != null) { if (cssHeightValue != null) {
imageSize = imageSize.copyWith(height: cssHeightValue); imageSize = imageSize.copyWith(height: cssHeightValue);

@ -1,6 +1,5 @@
import 'package:flutter/cupertino.dart' show showCupertinoModalPopup; import 'package:flutter/cupertino.dart' show showCupertinoModalPopup;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
// import 'package:flutter/services.dart' show Clipboard, ClipboardData;
import 'package:flutter_quill/extensions.dart' import 'package:flutter_quill/extensions.dart'
show isDesktop, isMobile, replaceStyleStringWithSize; show isDesktop, isMobile, replaceStyleStringWithSize;
import 'package:flutter_quill/flutter_quill.dart' import 'package:flutter_quill/flutter_quill.dart'
@ -51,7 +50,7 @@ class ImageOptionsMenu extends StatelessWidget {
builder: (context) { builder: (context) {
final screenSize = MediaQuery.sizeOf(context); final screenSize = MediaQuery.sizeOf(context);
return ImageResizer( return ImageResizer(
onImageResize: (w, h) { onImageResize: (width, height) {
final res = getEmbedNode( final res = getEmbedNode(
controller, controller,
controller.selection.start, controller.selection.start,
@ -59,8 +58,8 @@ class ImageOptionsMenu extends StatelessWidget {
final attr = replaceStyleStringWithSize( final attr = replaceStyleStringWithSize(
getImageStyleString(controller), getImageStyleString(controller),
width: w, width: width,
height: h, height: height,
isMobile: isMobile(supportWeb: false), isMobile: isMobile(supportWeb: false),
); );
controller controller
@ -94,7 +93,7 @@ class ImageOptionsMenu extends StatelessWidget {
); );
// TODO: Implement the copy image // TODO: Implement the copy image
// await Clipboard.setData( // await Clipboard.setData(
// ClipboardData(text: '$imageUrl'), // ClipboardData(),
// ); // );
navigator.pop(); navigator.pop();
}, },

@ -1,5 +1,3 @@
// ignore_for_file: use_build_context_synchronously
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_quill/flutter_quill.dart'; import 'package:flutter_quill/flutter_quill.dart';
import 'package:flutter_quill/translations.dart'; import 'package:flutter_quill/translations.dart';
@ -104,6 +102,7 @@ class QuillToolbarCameraButton extends StatelessWidget {
size: iconSize * 1.77, size: iconSize * 1.77,
fillColor: iconFillColor, fillColor: iconFillColor,
borderRadius: iconTheme?.borderRadius ?? 2, borderRadius: iconTheme?.borderRadius ?? 2,
// isDesktop(supportWeb: false) ? null :
onPressed: () => _sharedOnPressed(context), onPressed: () => _sharedOnPressed(context),
); );
} }
@ -126,11 +125,9 @@ class QuillToolbarCameraButton extends StatelessWidget {
BuildContext context, BuildContext context,
QuillController controller, QuillController controller,
) async { ) async {
// if (onVideoPickCallback == null && onImagePickCallback == null) { final imagePickerService =
// throw ArgumentError( QuillSharedExtensionsConfigurations.get(context: context)
// 'onImagePickCallback and onVideoPickCallback are both null', .imagePickerService;
// );
// }
final cameraAction = await _getCameraAction(context); final cameraAction = await _getCameraAction(context);
@ -138,10 +135,6 @@ class QuillToolbarCameraButton extends StatelessWidget {
return; return;
} }
final imagePickerService =
QuillSharedExtensionsConfigurations.get(context: context)
.imagePickerService;
switch (cameraAction) { switch (cameraAction) {
case CameraAction.video: case CameraAction.video:
final videoFile = await imagePickerService.pickVideo( final videoFile = await imagePickerService.pickVideo(

@ -48,7 +48,9 @@ class ImageResizerState extends State<ImageResizer> {
case TargetPlatform.fuchsia: case TargetPlatform.fuchsia:
return _showMaterialMenu(); return _showMaterialMenu();
default: default:
throw 'Not supposed to be invoked for $defaultTargetPlatform'; throw UnsupportedError(
'Not supposed to be invoked for $defaultTargetPlatform',
);
} }
} }

@ -1,5 +1,6 @@
import 'dart:io' show File; import 'dart:io' show File;
import 'package:flutter/foundation.dart' show VoidCallback;
import 'package:flutter_quill/extensions.dart'; import 'package:flutter_quill/extensions.dart';
import 'package:meta/meta.dart' show immutable; import 'package:meta/meta.dart' show immutable;
@ -12,12 +13,11 @@ import '../../../../embeds/embed_types/image.dart';
@immutable @immutable
class QuillEditorImageEmbedConfigurations { class QuillEditorImageEmbedConfigurations {
const QuillEditorImageEmbedConfigurations({ const QuillEditorImageEmbedConfigurations({
@Deprecated('This will be deleted in 0.7.0 as we will have one menu')
this.forceUseMobileOptionMenuForImageClick = false,
ImageEmbedBuilderOnRemovedCallback? onImageRemovedCallback, ImageEmbedBuilderOnRemovedCallback? onImageRemovedCallback,
this.shouldRemoveImageCallback, this.shouldRemoveImageCallback,
this.imageProviderBuilder, this.imageProviderBuilder,
this.imageErrorWidgetBuilder, this.imageErrorWidgetBuilder,
this.onImageClicked,
}) : _onImageRemovedCallback = onImageRemovedCallback; }) : _onImageRemovedCallback = onImageRemovedCallback;
/// [onImageRemovedCallback] is called when an image is /// [onImageRemovedCallback] is called when an image is
@ -101,15 +101,7 @@ class QuillEditorImageEmbedConfigurations {
/// ///
final ImageEmbedBuilderErrorWidgetBuilder? imageErrorWidgetBuilder; final ImageEmbedBuilderErrorWidgetBuilder? imageErrorWidgetBuilder;
/// [forceUseMobileOptionMenuForImageClick] is a boolean final VoidCallback? onImageClicked;
/// flag that, when set to `true`,
/// enforces the use of the mobile-specific option menu for image clicks in
/// other platforms like desktop, this option doesn't affect mobile. it will
/// not affect web
/// This option
/// can be used to override the default behavior based on the platform.
///
final bool forceUseMobileOptionMenuForImageClick;
static ImageEmbedBuilderOnRemovedCallback get defaultOnImageRemovedCallback { static ImageEmbedBuilderOnRemovedCallback get defaultOnImageRemovedCallback {
return (imageUrl) async { return (imageUrl) async {
@ -162,10 +154,6 @@ class QuillEditorImageEmbedConfigurations {
imageProviderBuilder: imageProviderBuilder ?? this.imageProviderBuilder, imageProviderBuilder: imageProviderBuilder ?? this.imageProviderBuilder,
imageErrorWidgetBuilder: imageErrorWidgetBuilder:
imageErrorWidgetBuilder ?? this.imageErrorWidgetBuilder, imageErrorWidgetBuilder ?? this.imageErrorWidgetBuilder,
// ignore: deprecated_member_use_from_same_package
forceUseMobileOptionMenuForImageClick:
forceUseMobileOptionMenuForImageClick ??
this.forceUseMobileOptionMenuForImageClick,
); );
} }
} }

@ -1,6 +1,6 @@
name: flutter_quill_extensions name: flutter_quill_extensions
description: Embed extensions for flutter_quill including image, video, formula and etc. description: Embed extensions for flutter_quill including image, video, formula and etc.
version: 0.6.5 version: 0.6.6
homepage: https://github.com/singerdmx/flutter-quill/tree/master/flutter_quill_extensions homepage: https://github.com/singerdmx/flutter-quill/tree/master/flutter_quill_extensions
repository: https://github.com/singerdmx/flutter-quill/tree/master/flutter_quill_extensions repository: https://github.com/singerdmx/flutter-quill/tree/master/flutter_quill_extensions

@ -1,6 +1,7 @@
library flutter_quill; library flutter_quill;
export 'src/models/config/quill_configurations.dart'; export 'src/models/config/quill_configurations.dart';
export 'src/models/config/raw_editor/configurations.dart';
export 'src/models/config/toolbar/base_configurations.dart'; export 'src/models/config/toolbar/base_configurations.dart';
export 'src/models/documents/attribute.dart'; export 'src/models/documents/attribute.dart';
export 'src/models/documents/document.dart'; export 'src/models/documents/document.dart';
@ -22,6 +23,7 @@ export 'src/models/themes/quill_icon_theme.dart';
export 'src/utils/embeds.dart'; export 'src/utils/embeds.dart';
export 'src/utils/extensions/build_context.dart'; export 'src/utils/extensions/build_context.dart';
export 'src/widgets/controller.dart'; export 'src/widgets/controller.dart';
export 'src/widgets/cursor.dart';
export 'src/widgets/default_styles.dart'; export 'src/widgets/default_styles.dart';
export 'src/widgets/editor/editor.dart'; export 'src/widgets/editor/editor.dart';
export 'src/widgets/embeds.dart'; export 'src/widgets/embeds.dart';

@ -2,7 +2,7 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart' show Brightness, Uint8List, immutable; import 'package:flutter/foundation.dart' show Brightness, Uint8List, immutable;
import 'package:flutter/material.dart' import 'package:flutter/material.dart'
show TextCapitalization, TextSelectionThemeData; show TextCapitalization, TextInputAction, TextSelectionThemeData;
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:meta/meta.dart' show experimental; import 'package:meta/meta.dart' show experimental;
@ -71,6 +71,7 @@ class QuillEditorConfigurations extends Equatable {
this.elementOptions = const QuillEditorElementOptions(), this.elementOptions = const QuillEditorElementOptions(),
this.builder, this.builder,
this.magnifierConfiguration, this.magnifierConfiguration,
this.textInputAction = TextInputAction.newline,
}); });
/// The text placeholder in the quill editor /// The text placeholder in the quill editor
@ -316,6 +317,9 @@ class QuillEditorConfigurations extends Equatable {
@experimental @experimental
final TextMagnifierConfiguration? magnifierConfiguration; final TextMagnifierConfiguration? magnifierConfiguration;
/// Default to [TextInputAction.newline]
final TextInputAction textInputAction;
@override @override
List<Object?> get props => [ List<Object?> get props => [
placeholder, placeholder,
@ -369,6 +373,7 @@ class QuillEditorConfigurations extends Equatable {
QuillEditorElementOptions? elementOptions, QuillEditorElementOptions? elementOptions,
QuillEditorBuilder? builder, QuillEditorBuilder? builder,
TextMagnifierConfiguration? magnifierConfiguration, TextMagnifierConfiguration? magnifierConfiguration,
TextInputAction? textInputAction,
}) { }) {
return QuillEditorConfigurations( return QuillEditorConfigurations(
placeholder: placeholder ?? this.placeholder, placeholder: placeholder ?? this.placeholder,
@ -425,6 +430,7 @@ class QuillEditorConfigurations extends Equatable {
builder: builder ?? this.builder, builder: builder ?? this.builder,
magnifierConfiguration: magnifierConfiguration:
magnifierConfiguration ?? this.magnifierConfiguration, magnifierConfiguration ?? this.magnifierConfiguration,
textInputAction: textInputAction ?? this.textInputAction,
); );
} }
} }

@ -0,0 +1,290 @@
import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart' show Brightness, Uint8List;
import 'package:flutter/material.dart'
show
AdaptiveTextSelectionToolbar,
PointerDownEvent,
TextCapitalization,
TextInputAction;
import 'package:flutter/widgets.dart'
show
Action,
BuildContext,
Color,
ContentInsertionConfiguration,
EdgeInsets,
EdgeInsetsGeometry,
FocusNode,
Intent,
ScrollController,
ScrollPhysics,
ShortcutActivator,
TextFieldTapRegion,
TextSelectionControls,
ValueChanged,
Widget;
import 'package:meta/meta.dart' show immutable;
import '../../../widgets/controller.dart';
import '../../../widgets/cursor.dart';
import '../../../widgets/default_styles.dart';
import '../../../widgets/delegate.dart';
import '../../../widgets/link.dart';
import '../../../widgets/raw_editor/raw_editor.dart';
import '../../../widgets/raw_editor/raw_editor_state.dart';
import '../../themes/quill_dialog_theme.dart';
@immutable
class QuillRawEditorConfigurations extends Equatable {
const QuillRawEditorConfigurations({
required this.controller,
required this.focusNode,
required this.scrollController,
required this.scrollBottomInset,
required this.cursorStyle,
required this.selectionColor,
required this.selectionCtrls,
required this.embedBuilder,
required this.autoFocus,
this.showCursor = true,
this.scrollable = true,
this.padding = EdgeInsets.zero,
this.isReadOnly = false,
this.placeholder,
this.onLaunchUrl,
this.contextMenuBuilder = defaultContextMenuBuilder,
this.showSelectionHandles = false,
this.textCapitalization = TextCapitalization.none,
this.maxHeight,
this.minHeight,
this.maxContentWidth,
this.customStyles,
this.customShortcuts,
this.customActions,
this.expands = false,
this.isOnTapOutsideEnabled = true,
this.onTapOutside,
this.keyboardAppearance = Brightness.light,
this.enableInteractiveSelection = true,
this.scrollPhysics,
this.linkActionPickerDelegate = defaultLinkActionPickerDelegate,
this.customStyleBuilder,
this.customRecognizerBuilder,
this.floatingCursorDisabled = false,
this.onImagePaste,
this.customLinkPrefixes = const <String>[],
this.dialogTheme,
this.contentInsertionConfiguration,
this.textInputAction = TextInputAction.newline,
this.requestKeyboardFocusOnCheckListChanged = false,
});
/// Controls the document being edited.
final QuillController controller;
/// Controls whether this editor has keyboard focus.
final FocusNode focusNode;
final ScrollController scrollController;
final bool scrollable;
final double scrollBottomInset;
/// Additional space around the editor contents.
final EdgeInsetsGeometry padding;
/// Whether the text can be changed.
///
/// When this is set to true, the text cannot be modified
/// by any shortcut or keyboard operation. The text is still selectable.
///
/// Defaults to false. Must not be null.
final bool isReadOnly;
final String? placeholder;
/// Callback which is triggered when the user wants to open a URL from
/// a link in the document.
final ValueChanged<String>? onLaunchUrl;
/// Builds the text selection toolbar when requested by the user.
///
/// See also:
/// * [EditableText.contextMenuBuilder], which builds the default
/// text selection toolbar for [EditableText].
///
/// If not provided, no context menu will be shown.
final QuillEditorContextMenuBuilder? contextMenuBuilder;
static Widget defaultContextMenuBuilder(
BuildContext context,
QuillRawEditorState state,
) {
return TextFieldTapRegion(
child: AdaptiveTextSelectionToolbar.buttonItems(
buttonItems: state.contextMenuButtonItems,
anchors: state.contextMenuAnchors,
),
);
}
/// Whether to show selection handles.
///
/// When a selection is active, there will be two handles at each side of
/// boundary, or one handle if the selection is collapsed. The handles can be
/// dragged to adjust the selection.
///
/// See also:
///
/// * [showCursor], which controls the visibility of the cursor.
final bool showSelectionHandles;
/// Whether to show cursor.
///
/// The cursor refers to the blinking caret when the editor is focused.
///
/// See also:
///
/// * [cursorStyle], which controls the cursor visual representation.
/// * [showSelectionHandles], which controls the visibility of the selection
/// handles.
final bool showCursor;
/// The style to be used for the editing cursor.
final CursorStyle cursorStyle;
/// Configures how the platform keyboard will select an uppercase or
/// lowercase keyboard.
///
/// Only supports text keyboards, other keyboard types will ignore this
/// configuration. Capitalization is locale-aware.
///
/// Defaults to [TextCapitalization.none]. Must not be null.
///
/// See also:
///
/// * [TextCapitalization], for a description of each capitalization behavior
final TextCapitalization textCapitalization;
/// The maximum height this editor can have.
///
/// If this is null then there is no limit to the editor's height and it will
/// expand to fill its parent.
final double? maxHeight;
/// The minimum height this editor can have.
final double? minHeight;
/// The maximum width to be occupied by the content of this editor.
///
/// If this is not null and and this editor's width is larger than this value
/// then the contents will be constrained to the provided maximum width and
/// horizontally centered. This is mostly useful on devices with wide screens.
final double? maxContentWidth;
/// Allows to override [DefaultStyles].
final DefaultStyles? customStyles;
/// Whether this widget's height will be sized to fill its parent.
///
/// If set to true and wrapped in a parent widget like [Expanded] or
///
/// Defaults to false.
final bool expands;
/// Whether this editor should focus itself if nothing else is already
/// focused.
///
/// If true, the keyboard will open as soon as this text field obtains focus.
/// Otherwise, the keyboard is only shown after the user taps the text field.
///
/// Defaults to false. Cannot be null.
final bool autoFocus;
/// The color to use when painting the selection.
final Color selectionColor;
/// Delegate for building the text selection handles and toolbar.
///
/// The [QuillRawEditor] widget used on its own will not trigger the display
/// of the selection toolbar by itself. The toolbar is shown by calling
/// [QuillRawEditorState.showToolbar] in response to
/// an appropriate user event.
final TextSelectionControls selectionCtrls;
/// The appearance of the keyboard.
///
/// This setting is only honored on iOS devices.
///
/// Defaults to [Brightness.light].
final Brightness keyboardAppearance;
/// If true, then long-pressing this TextField will select text and show the
/// cut/copy/paste menu, and tapping will move the text caret.
///
/// True by default.
///
/// If false, most of the accessibility support for selecting text, copy
/// and paste, and moving the caret will be disabled.
final bool enableInteractiveSelection;
bool get selectionEnabled => enableInteractiveSelection;
/// The [ScrollPhysics] to use when vertically scrolling the input.
///
/// If not specified, it will behave according to the current platform.
///
/// See [Scrollable.physics].
final ScrollPhysics? scrollPhysics;
final Future<String?> Function(Uint8List imageBytes)? onImagePaste;
/// Contains user-defined shortcuts map.
///
/// [https://docs.flutter.dev/development/ui/advanced/actions-and-shortcuts#shortcuts]
final Map<ShortcutActivator, Intent>? customShortcuts;
/// Contains user-defined actions.
///
/// [https://docs.flutter.dev/development/ui/advanced/actions-and-shortcuts#actions]
final Map<Type, Action<Intent>>? customActions;
/// Builder function for embeddable objects.
final EmbedsBuilder embedBuilder;
final LinkActionPickerDelegate linkActionPickerDelegate;
final CustomStyleBuilder? customStyleBuilder;
final CustomRecognizerBuilder? customRecognizerBuilder;
final bool floatingCursorDisabled;
final List<String> customLinkPrefixes;
/// Configures the dialog theme.
final QuillDialogTheme? dialogTheme;
/// Configuration of handler for media content inserted via the system input
/// method.
///
/// See [https://api.flutter.dev/flutter/widgets/EditableText/contentInsertionConfiguration.html]
final ContentInsertionConfiguration? contentInsertionConfiguration;
/// Whether the [onTapOutside] should be triggered or not
/// Defaults to `true`
/// it have default implementation, check [onTapOuside] for more
final bool isOnTapOutsideEnabled;
/// This will run only when [isOnTapOutsideEnabled] is true
/// by default on desktop and web it will unfocus
/// on mobile it will only unFocus if the kind property of
/// event [PointerDownEvent] is [PointerDeviceKind.unknown]
/// you can override this to fit your needs
final Function(PointerDownEvent event, FocusNode focusNode)? onTapOutside;
/// When there is a change the check list values
/// should we request keyboard focus??
final bool requestKeyboardFocusOnCheckListChanged;
final TextInputAction textInputAction;
@override
List<Object?> get props => [
isReadOnly,
placeholder,
];
}

@ -2,7 +2,7 @@ import 'package:flutter/widgets.dart' show BuildContext;
import '../../../flutter_quill.dart'; import '../../../flutter_quill.dart';
// TODO: The comments of this file might needs to be updated. // TODO: The comments of this file is outdated and needs to be updated
/// Public shared extension /// Public shared extension
extension BuildContextExt on BuildContext { extension BuildContextExt on BuildContext {

@ -20,13 +20,13 @@ Map<String, String> parseKeyValuePairs(String s, Set<String> targetKeys) {
} }
String replaceStyleStringWithSize( String replaceStyleStringWithSize(
String s, { String cssStyle, {
required double width, required double width,
required double height, required double height,
required bool isMobile, required bool isMobile,
}) { }) {
final result = <String, String>{}; final result = <String, String>{};
final pairs = s.split(';'); final pairs = cssStyle.split(';');
for (final pair in pairs) { for (final pair in pairs) {
final index = pair.indexOf(':'); final index = pair.indexOf(':');
if (index < 0) { if (index < 0) {

@ -13,7 +13,6 @@ import '../../../flutter_quill.dart';
import '../../models/documents/nodes/container.dart' as container_node; import '../../models/documents/nodes/container.dart' as container_node;
import '../../utils/platform.dart'; import '../../utils/platform.dart';
import '../box.dart'; import '../box.dart';
import '../cursor.dart';
import '../delegate.dart'; import '../delegate.dart';
import '../float_cursor.dart'; import '../float_cursor.dart';
import '../text_selection.dart'; import '../text_selection.dart';
@ -247,60 +246,64 @@ class QuillEditorState extends State<QuillEditor>
builder: configurations.builder, builder: configurations.builder,
child: QuillRawEditor( child: QuillRawEditor(
key: _editorKey, key: _editorKey,
controller: context.requireQuillController, configurations: QuillRawEditorConfigurations(
focusNode: widget.focusNode, controller: context.requireQuillController,
scrollController: widget.scrollController, focusNode: widget.focusNode,
scrollable: configurations.scrollable, scrollController: widget.scrollController,
scrollBottomInset: configurations.scrollBottomInset, scrollable: configurations.scrollable,
padding: configurations.padding, scrollBottomInset: configurations.scrollBottomInset,
readOnly: configurations.readOnly, padding: configurations.padding,
placeholder: configurations.placeholder, isReadOnly: configurations.readOnly,
onLaunchUrl: configurations.onLaunchUrl, placeholder: configurations.placeholder,
contextMenuBuilder: showSelectionToolbar onLaunchUrl: configurations.onLaunchUrl,
? (configurations.contextMenuBuilder ?? contextMenuBuilder: showSelectionToolbar
QuillRawEditor.defaultContextMenuBuilder) ? (configurations.contextMenuBuilder ??
: null, QuillRawEditorConfigurations.defaultContextMenuBuilder)
showSelectionHandles: isMobile( : null,
platform: theme.platform, showSelectionHandles: isMobile(
supportWeb: true, platform: theme.platform,
supportWeb: true,
),
showCursor: configurations.showCursor ?? true,
cursorStyle: CursorStyle(
color: cursorColor,
backgroundColor: Colors.grey,
width: 2,
radius: cursorRadius,
offset: cursorOffset,
paintAboveText:
configurations.paintCursorAboveText ?? paintCursorAboveText,
opacityAnimates: cursorOpacityAnimates,
),
textCapitalization: configurations.textCapitalization,
minHeight: configurations.minHeight,
maxHeight: configurations.maxHeight,
maxContentWidth: configurations.maxContentWidth,
customStyles: configurations.customStyles,
expands: configurations.expands,
autoFocus: configurations.autoFocus,
selectionColor: selectionColor,
selectionCtrls:
configurations.textSelectionControls ?? textSelectionControls,
keyboardAppearance: configurations.keyboardAppearance,
enableInteractiveSelection:
configurations.enableInteractiveSelection,
scrollPhysics: configurations.scrollPhysics,
embedBuilder: _getEmbedBuilder,
linkActionPickerDelegate: configurations.linkActionPickerDelegate,
customStyleBuilder: configurations.customStyleBuilder,
customRecognizerBuilder: configurations.customRecognizerBuilder,
floatingCursorDisabled: configurations.floatingCursorDisabled,
onImagePaste: configurations.onImagePaste,
customShortcuts: configurations.customShortcuts,
customActions: configurations.customActions,
customLinkPrefixes: configurations.customLinkPrefixes,
isOnTapOutsideEnabled: configurations.isOnTapOutsideEnabled,
onTapOutside: configurations.onTapOutside,
dialogTheme: configurations.dialogTheme,
contentInsertionConfiguration:
configurations.contentInsertionConfiguration,
), ),
showCursor: configurations.showCursor,
cursorStyle: CursorStyle(
color: cursorColor,
backgroundColor: Colors.grey,
width: 2,
radius: cursorRadius,
offset: cursorOffset,
paintAboveText:
configurations.paintCursorAboveText ?? paintCursorAboveText,
opacityAnimates: cursorOpacityAnimates,
),
textCapitalization: configurations.textCapitalization,
minHeight: configurations.minHeight,
maxHeight: configurations.maxHeight,
maxContentWidth: configurations.maxContentWidth,
customStyles: configurations.customStyles,
expands: configurations.expands,
autoFocus: configurations.autoFocus,
selectionColor: selectionColor,
selectionCtrls:
configurations.textSelectionControls ?? textSelectionControls,
keyboardAppearance: configurations.keyboardAppearance,
enableInteractiveSelection: configurations.enableInteractiveSelection,
scrollPhysics: configurations.scrollPhysics,
embedBuilder: _getEmbedBuilder,
linkActionPickerDelegate: configurations.linkActionPickerDelegate,
customStyleBuilder: configurations.customStyleBuilder,
customRecognizerBuilder: configurations.customRecognizerBuilder,
floatingCursorDisabled: configurations.floatingCursorDisabled,
onImagePaste: configurations.onImagePaste,
customShortcuts: configurations.customShortcuts,
customActions: configurations.customActions,
customLinkPrefixes: configurations.customLinkPrefixes,
enableUnfocusOnTapOutside: configurations.isOnTapOutsideEnabled,
dialogTheme: configurations.dialogTheme,
contentInsertionConfiguration:
configurations.contentInsertionConfiguration,
), ),
), ),
); );
@ -435,15 +438,15 @@ class _QuillEditorSelectionGestureDetectorBuilder
return false; return false;
} }
final pos = renderEditor!.getPositionForOffset(details.globalPosition); final pos = renderEditor!.getPositionForOffset(details.globalPosition);
final result = final result = editor!.widget.configurations.controller.document
editor!.widget.controller.document.querySegmentLeafNode(pos.offset); .querySegmentLeafNode(pos.offset);
final line = result.line; final line = result.line;
if (line == null) { if (line == null) {
return false; return false;
} }
final segmentLeaf = result.leaf; final segmentLeaf = result.leaf;
if (segmentLeaf == null && line.length == 1) { if (segmentLeaf == null && line.length == 1) {
editor!.widget.controller.updateSelection( editor!.widget.configurations.controller.updateSelection(
TextSelection.collapsed(offset: pos.offset), TextSelection.collapsed(offset: pos.offset),
ChangeSource.local, ChangeSource.local,
); );

@ -1,245 +1,25 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show Uint8List;
import '../../models/themes/quill_dialog_theme.dart'; import '../../models/config/raw_editor/configurations.dart';
import '../controller.dart';
import '../cursor.dart';
import '../default_styles.dart';
import '../delegate.dart';
import '../link.dart';
import 'raw_editor_state.dart'; import 'raw_editor_state.dart';
class QuillRawEditor extends StatefulWidget { class QuillRawEditor extends StatefulWidget {
const QuillRawEditor({ QuillRawEditor({
required this.controller, required this.configurations,
required this.focusNode,
required this.scrollController,
required this.scrollBottomInset,
required this.cursorStyle,
required this.selectionColor,
required this.selectionCtrls,
required this.embedBuilder,
required this.autoFocus,
super.key, super.key,
this.scrollable = true, }) : assert(
this.padding = EdgeInsets.zero, configurations.maxHeight == null || configurations.maxHeight! > 0,
this.readOnly = false,
this.placeholder,
this.onLaunchUrl,
this.contextMenuBuilder = defaultContextMenuBuilder,
this.showSelectionHandles = false,
bool? showCursor,
this.textCapitalization = TextCapitalization.none,
this.maxHeight,
this.minHeight,
this.maxContentWidth,
this.customStyles,
this.customShortcuts,
this.customActions,
this.expands = false,
this.enableUnfocusOnTapOutside = true,
this.keyboardAppearance = Brightness.light,
this.enableInteractiveSelection = true,
this.scrollPhysics,
this.linkActionPickerDelegate = defaultLinkActionPickerDelegate,
this.customStyleBuilder,
this.customRecognizerBuilder,
this.floatingCursorDisabled = false,
this.onImagePaste,
this.customLinkPrefixes = const <String>[],
this.dialogTheme,
this.contentInsertionConfiguration,
}) : assert(maxHeight == null || maxHeight > 0, 'maxHeight cannot be null'),
assert(minHeight == null || minHeight >= 0, 'minHeight cannot be null'),
assert(maxHeight == null || minHeight == null || maxHeight >= minHeight,
'maxHeight cannot be null'), 'maxHeight cannot be null'),
showCursor = showCursor ?? true; assert(
configurations.minHeight == null || configurations.minHeight! >= 0,
/// Controls the document being edited. 'minHeight cannot be null'),
final QuillController controller; assert(
configurations.maxHeight == null ||
/// Controls whether this editor has keyboard focus. configurations.minHeight == null ||
final FocusNode focusNode; configurations.maxHeight! >= configurations.minHeight!,
final ScrollController scrollController; 'maxHeight cannot be null');
final bool scrollable;
final double scrollBottomInset; final QuillRawEditorConfigurations configurations;
final bool enableUnfocusOnTapOutside;
/// Additional space around the editor contents.
final EdgeInsetsGeometry padding;
/// Whether the text can be changed.
///
/// When this is set to true, the text cannot be modified
/// by any shortcut or keyboard operation. The text is still selectable.
///
/// Defaults to false. Must not be null.
final bool readOnly;
final String? placeholder;
/// Callback which is triggered when the user wants to open a URL from
/// a link in the document.
final ValueChanged<String>? onLaunchUrl;
/// Builds the text selection toolbar when requested by the user.
///
/// See also:
/// * [EditableText.contextMenuBuilder], which builds the default
/// text selection toolbar for [EditableText].
///
/// If not provided, no context menu will be shown.
final QuillEditorContextMenuBuilder? contextMenuBuilder;
static Widget defaultContextMenuBuilder(
BuildContext context,
QuillRawEditorState state,
) {
return TextFieldTapRegion(
child: AdaptiveTextSelectionToolbar.buttonItems(
buttonItems: state.contextMenuButtonItems,
anchors: state.contextMenuAnchors,
),
);
}
/// Whether to show selection handles.
///
/// When a selection is active, there will be two handles at each side of
/// boundary, or one handle if the selection is collapsed. The handles can be
/// dragged to adjust the selection.
///
/// See also:
///
/// * [showCursor], which controls the visibility of the cursor.
final bool showSelectionHandles;
/// Whether to show cursor.
///
/// The cursor refers to the blinking caret when the editor is focused.
///
/// See also:
///
/// * [cursorStyle], which controls the cursor visual representation.
/// * [showSelectionHandles], which controls the visibility of the selection
/// handles.
final bool showCursor;
/// The style to be used for the editing cursor.
final CursorStyle cursorStyle;
/// Configures how the platform keyboard will select an uppercase or
/// lowercase keyboard.
///
/// Only supports text keyboards, other keyboard types will ignore this
/// configuration. Capitalization is locale-aware.
///
/// Defaults to [TextCapitalization.none]. Must not be null.
///
/// See also:
///
/// * [TextCapitalization], for a description of each capitalization behavior
final TextCapitalization textCapitalization;
/// The maximum height this editor can have.
///
/// If this is null then there is no limit to the editor's height and it will
/// expand to fill its parent.
final double? maxHeight;
/// The minimum height this editor can have.
final double? minHeight;
/// The maximum width to be occupied by the content of this editor.
///
/// If this is not null and and this editor's width is larger than this value
/// then the contents will be constrained to the provided maximum width and
/// horizontally centered. This is mostly useful on devices with wide screens.
final double? maxContentWidth;
/// Allows to override [DefaultStyles].
final DefaultStyles? customStyles;
/// Whether this widget's height will be sized to fill its parent.
///
/// If set to true and wrapped in a parent widget like [Expanded] or
///
/// Defaults to false.
final bool expands;
/// Whether this editor should focus itself if nothing else is already
/// focused.
///
/// If true, the keyboard will open as soon as this text field obtains focus.
/// Otherwise, the keyboard is only shown after the user taps the text field.
///
/// Defaults to false. Cannot be null.
final bool autoFocus;
/// The color to use when painting the selection.
final Color selectionColor;
/// Delegate for building the text selection handles and toolbar.
///
/// The [QuillRawEditor] widget used on its own will not trigger the display
/// of the selection toolbar by itself. The toolbar is shown by calling
/// [QuillRawEditorState.showToolbar] in response to
/// an appropriate user event.
final TextSelectionControls selectionCtrls;
/// The appearance of the keyboard.
///
/// This setting is only honored on iOS devices.
///
/// Defaults to [Brightness.light].
final Brightness keyboardAppearance;
/// If true, then long-pressing this TextField will select text and show the
/// cut/copy/paste menu, and tapping will move the text caret.
///
/// True by default.
///
/// If false, most of the accessibility support for selecting text, copy
/// and paste, and moving the caret will be disabled.
final bool enableInteractiveSelection;
bool get selectionEnabled => enableInteractiveSelection;
/// The [ScrollPhysics] to use when vertically scrolling the input.
///
/// If not specified, it will behave according to the current platform.
///
/// See [Scrollable.physics].
final ScrollPhysics? scrollPhysics;
final Future<String?> Function(Uint8List imageBytes)? onImagePaste;
/// Contains user-defined shortcuts map.
///
/// [https://docs.flutter.dev/development/ui/advanced/actions-and-shortcuts#shortcuts]
final Map<ShortcutActivator, Intent>? customShortcuts;
/// Contains user-defined actions.
///
/// [https://docs.flutter.dev/development/ui/advanced/actions-and-shortcuts#actions]
final Map<Type, Action<Intent>>? customActions;
/// Builder function for embeddable objects.
final EmbedsBuilder embedBuilder;
final LinkActionPickerDelegate linkActionPickerDelegate;
final CustomStyleBuilder? customStyleBuilder;
final CustomRecognizerBuilder? customRecognizerBuilder;
final bool floatingCursorDisabled;
final List<String> customLinkPrefixes;
/// Configures the dialog theme.
final QuillDialogTheme? dialogTheme;
/// Configuration of handler for media content inserted via the system input
/// method.
///
/// See [https://api.flutter.dev/flutter/widgets/EditableText/contentInsertionConfiguration.html]
final ContentInsertionConfiguration? contentInsertionConfiguration;
@override @override
State<StatefulWidget> createState() => QuillRawEditorState(); State<StatefulWidget> createState() => QuillRawEditorState();

@ -76,7 +76,8 @@ class QuillEditorDeleteTextAction<T extends DirectionalTextEditingIntent>
@override @override
bool get isActionEnabled => bool get isActionEnabled =>
!state.widget.readOnly && state.textEditingValue.selection.isValid; !state.widget.configurations.isReadOnly &&
state.textEditingValue.selection.isValid;
} }
class QuillEditorUpdateTextSelectionAction< class QuillEditorUpdateTextSelectionAction<
@ -93,8 +94,8 @@ class QuillEditorUpdateTextSelectionAction<
final selection = state.textEditingValue.selection; final selection = state.textEditingValue.selection;
assert(selection.isValid); assert(selection.isValid);
final collapseSelection = final collapseSelection = intent.collapseSelection ||
intent.collapseSelection || !state.widget.selectionEnabled; !state.widget.configurations.selectionEnabled;
// Collapse to the logical start/end. // Collapse to the logical start/end.
TextSelection collapse(TextSelection selection) { TextSelection collapse(TextSelection selection) {
assert(selection.isValid); assert(selection.isValid);
@ -217,7 +218,8 @@ class QuillEditorExtendSelectionOrCaretPositionAction extends ContextAction<
@override @override
bool get isActionEnabled => bool get isActionEnabled =>
state.widget.selectionEnabled && state.textEditingValue.selection.isValid; state.widget.configurations.selectionEnabled &&
state.textEditingValue.selection.isValid;
} }
class QuillEditorUpdateTextSelectionToAdjacentLineAction< class QuillEditorUpdateTextSelectionToAdjacentLineAction<
@ -251,8 +253,8 @@ class QuillEditorUpdateTextSelectionToAdjacentLineAction<
void invoke(T intent, [BuildContext? context]) { void invoke(T intent, [BuildContext? context]) {
assert(state.textEditingValue.selection.isValid); assert(state.textEditingValue.selection.isValid);
final collapseSelection = final collapseSelection = intent.collapseSelection ||
intent.collapseSelection || !state.widget.selectionEnabled; !state.widget.configurations.selectionEnabled;
final value = state.textEditingValue; final value = state.textEditingValue;
if (!value.selection.isValid) { if (!value.selection.isValid) {
return; return;
@ -307,7 +309,7 @@ class QuillEditorSelectAllAction extends ContextAction<SelectAllTextIntent> {
} }
@override @override
bool get isActionEnabled => state.widget.selectionEnabled; bool get isActionEnabled => state.widget.configurations.selectionEnabled;
} }
class QuillEditorCopySelectionAction class QuillEditorCopySelectionAction
@ -559,7 +561,7 @@ class QuillEditorApplyLinkAction extends Action<QuillEditorApplyLinkIntent> {
return LinkStyleDialog( return LinkStyleDialog(
text: initialTextLink.text, text: initialTextLink.text,
link: initialTextLink.link, link: initialTextLink.link,
dialogTheme: state.widget.dialogTheme, dialogTheme: state.widget.configurations.dialogTheme,
); );
}, },
); );

@ -32,7 +32,6 @@ import '../../models/structs/vertical_spacing.dart';
import '../../utils/cast.dart'; import '../../utils/cast.dart';
import '../../utils/delta.dart'; import '../../utils/delta.dart';
import '../../utils/embeds.dart'; import '../../utils/embeds.dart';
import '../../utils/extensions/build_context.dart';
import '../../utils/platform.dart'; import '../../utils/platform.dart';
import '../controller.dart'; import '../controller.dart';
import '../cursor.dart'; import '../cursor.dart';
@ -77,12 +76,12 @@ class QuillRawEditorState extends EditorState
// Cursors // Cursors
late CursorCont _cursorCont; late CursorCont _cursorCont;
QuillController get controller => widget.controller; QuillController get controller => widget.configurations.controller;
// Focus // Focus
bool _didAutoFocus = false; bool _didAutoFocus = false;
bool get _hasFocus => widget.focusNode.hasFocus; bool get _hasFocus => widget.configurations.focusNode.hasFocus;
// Theme // Theme
DefaultStyles? _styles; DefaultStyles? _styles;
@ -109,10 +108,11 @@ class QuillRawEditorState extends EditorState
@override @override
void insertContent(KeyboardInsertedContent content) { void insertContent(KeyboardInsertedContent content) {
assert(widget.contentInsertionConfiguration?.allowedMimeTypes assert(widget.configurations.contentInsertionConfiguration?.allowedMimeTypes
.contains(content.mimeType) ?? .contains(content.mimeType) ??
false); false);
widget.contentInsertionConfiguration?.onContentInserted.call(content); widget.configurations.contentInsertionConfiguration?.onContentInserted
.call(content);
} }
/// Returns the [ContextMenuButtonItem]s representing the buttons in this /// Returns the [ContextMenuButtonItem]s representing the buttons in this
@ -200,7 +200,7 @@ class QuillRawEditorState extends EditorState
case ui.PointerDeviceKind.stylus: case ui.PointerDeviceKind.stylus:
case ui.PointerDeviceKind.invertedStylus: case ui.PointerDeviceKind.invertedStylus:
case ui.PointerDeviceKind.unknown: case ui.PointerDeviceKind.unknown:
widget.focusNode.unfocus(); widget.configurations.focusNode.unfocus();
break; break;
case ui.PointerDeviceKind.trackpad: case ui.PointerDeviceKind.trackpad:
throw UnimplementedError( throw UnimplementedError(
@ -211,7 +211,7 @@ class QuillRawEditorState extends EditorState
case TargetPlatform.linux: case TargetPlatform.linux:
case TargetPlatform.macOS: case TargetPlatform.macOS:
case TargetPlatform.windows: case TargetPlatform.windows:
widget.focusNode.unfocus(); widget.configurations.focusNode.unfocus();
break; break;
default: default:
throw UnsupportedError( throw UnsupportedError(
@ -227,8 +227,8 @@ class QuillRawEditorState extends EditorState
super.build(context); super.build(context);
var doc = controller.document; var doc = controller.document;
if (doc.isEmpty() && widget.placeholder != null) { if (doc.isEmpty() && widget.configurations.placeholder != null) {
final raw = widget.placeholder?.replaceAll(r'"', '\\"'); final raw = widget.configurations.placeholder?.replaceAll(r'"', '\\"');
doc = Document.fromJson( doc = Document.fromJson(
jsonDecode( jsonDecode(
'[{"attributes":{"placeholder":true},"insert":"$raw\\n"}]', '[{"attributes":{"placeholder":true},"insert":"$raw\\n"}]',
@ -246,24 +246,25 @@ class QuillRawEditorState extends EditorState
document: doc, document: doc,
selection: controller.selection, selection: controller.selection,
hasFocus: _hasFocus, hasFocus: _hasFocus,
scrollable: widget.scrollable, scrollable: widget.configurations.scrollable,
cursorController: _cursorCont, cursorController: _cursorCont,
textDirection: _textDirection, textDirection: _textDirection,
startHandleLayerLink: _startHandleLayerLink, startHandleLayerLink: _startHandleLayerLink,
endHandleLayerLink: _endHandleLayerLink, endHandleLayerLink: _endHandleLayerLink,
onSelectionChanged: _handleSelectionChanged, onSelectionChanged: _handleSelectionChanged,
onSelectionCompleted: _handleSelectionCompleted, onSelectionCompleted: _handleSelectionCompleted,
scrollBottomInset: widget.scrollBottomInset, scrollBottomInset: widget.configurations.scrollBottomInset,
padding: widget.padding, padding: widget.configurations.padding,
maxContentWidth: widget.maxContentWidth, maxContentWidth: widget.configurations.maxContentWidth,
floatingCursorDisabled: widget.floatingCursorDisabled, floatingCursorDisabled:
widget.configurations.floatingCursorDisabled,
children: _buildChildren(doc, context), children: _buildChildren(doc, context),
), ),
), ),
), ),
); );
if (widget.scrollable) { if (widget.configurations.scrollable) {
/// Since [SingleChildScrollView] does not implement /// Since [SingleChildScrollView] does not implement
/// `computeDistanceToActualBaseline` it prevents the editor from /// `computeDistanceToActualBaseline` it prevents the editor from
/// providing its baseline metrics. To address this issue we wrap /// providing its baseline metrics. To address this issue we wrap
@ -277,7 +278,7 @@ class QuillRawEditorState extends EditorState
padding: baselinePadding, padding: baselinePadding,
child: QuillSingleChildScrollView( child: QuillSingleChildScrollView(
controller: _scrollController, controller: _scrollController,
physics: widget.scrollPhysics, physics: widget.configurations.scrollPhysics,
viewportBuilder: (_, offset) => CompositedTransformTarget( viewportBuilder: (_, offset) => CompositedTransformTarget(
link: _toolbarLayerLink, link: _toolbarLayerLink,
child: MouseRegion( child: MouseRegion(
@ -288,17 +289,18 @@ class QuillRawEditorState extends EditorState
document: doc, document: doc,
selection: controller.selection, selection: controller.selection,
hasFocus: _hasFocus, hasFocus: _hasFocus,
scrollable: widget.scrollable, scrollable: widget.configurations.scrollable,
textDirection: _textDirection, textDirection: _textDirection,
startHandleLayerLink: _startHandleLayerLink, startHandleLayerLink: _startHandleLayerLink,
endHandleLayerLink: _endHandleLayerLink, endHandleLayerLink: _endHandleLayerLink,
onSelectionChanged: _handleSelectionChanged, onSelectionChanged: _handleSelectionChanged,
onSelectionCompleted: _handleSelectionCompleted, onSelectionCompleted: _handleSelectionCompleted,
scrollBottomInset: widget.scrollBottomInset, scrollBottomInset: widget.configurations.scrollBottomInset,
padding: widget.padding, padding: widget.configurations.padding,
maxContentWidth: widget.maxContentWidth, maxContentWidth: widget.configurations.maxContentWidth,
cursorController: _cursorCont, cursorController: _cursorCont,
floatingCursorDisabled: widget.floatingCursorDisabled, floatingCursorDisabled:
widget.configurations.floatingCursorDisabled,
children: _buildChildren(doc, context), children: _buildChildren(doc, context),
), ),
), ),
@ -307,11 +309,11 @@ class QuillRawEditorState extends EditorState
); );
} }
final constraints = widget.expands final constraints = widget.configurations.expands
? const BoxConstraints.expand() ? const BoxConstraints.expand()
: BoxConstraints( : BoxConstraints(
minHeight: widget.minHeight ?? 0.0, minHeight: widget.configurations.minHeight ?? 0.0,
maxHeight: widget.maxHeight ?? double.infinity, maxHeight: widget.configurations.maxHeight ?? double.infinity,
); );
// Please notice that this change will make the check fixed // Please notice that this change will make the check fixed
@ -321,13 +323,11 @@ class QuillRawEditorState extends EditorState
final isDesktopMacOS = isMacOS(supportWeb: true); final isDesktopMacOS = isMacOS(supportWeb: true);
return TextFieldTapRegion( return TextFieldTapRegion(
enabled: widget.enableUnfocusOnTapOutside, enabled: widget.configurations.isOnTapOutsideEnabled,
onTapOutside: (event) { onTapOutside: (event) {
final onTapOutside = final onTapOutside = widget.configurations.onTapOutside;
context.requireQuillEditorConfigurations.onTapOutside;
if (onTapOutside != null) { if (onTapOutside != null) {
context.requireQuillEditorConfigurations.onTapOutside onTapOutside.call(event, widget.configurations.focusNode);
?.call(event, widget.focusNode);
return; return;
} }
_defaultOnTapOutside(event); _defaultOnTapOutside(event);
@ -463,14 +463,14 @@ class QuillRawEditorState extends EditorState
meta: isDesktopMacOS, meta: isDesktopMacOS,
): const OpenSearchIntent(), ): const OpenSearchIntent(),
}, { }, {
...?widget.customShortcuts ...?widget.configurations.customShortcuts
}), }),
child: Actions( child: Actions(
actions: mergeMaps<Type, Action<Intent>>(_actions, { actions: mergeMaps<Type, Action<Intent>>(_actions, {
...?widget.customActions, ...?widget.configurations.customActions,
}), }),
child: Focus( child: Focus(
focusNode: widget.focusNode, focusNode: widget.configurations.focusNode,
onKey: _onKey, onKey: _onKey,
child: QuillKeyboardListener( child: QuillKeyboardListener(
child: Container( child: Container(
@ -549,7 +549,7 @@ class QuillRawEditorState extends EditorState
controller.document.queryChild(controller.selection.baseOffset); controller.document.queryChild(controller.selection.baseOffset);
KeyEventResult insertTabCharacter() { KeyEventResult insertTabCharacter() {
if (widget.readOnly) { if (widget.configurations.isReadOnly) {
return KeyEventResult.ignored; return KeyEventResult.ignored;
} }
controller.replaceText(controller.selection.baseOffset, 0, '\t', null); controller.replaceText(controller.selection.baseOffset, 0, '\t', null);
@ -657,10 +657,9 @@ class QuillRawEditorState extends EditorState
/// Updates the checkbox positioned at [offset] in document /// Updates the checkbox positioned at [offset] in document
/// by changing its attribute according to [value]. /// by changing its attribute according to [value].
void _handleCheckboxTap(int offset, bool value) { void _handleCheckboxTap(int offset, bool value) {
final requestKeyboardFocusOnCheckListChanged = context final requestKeyboardFocusOnCheckListChanged =
.requireQuillEditorConfigurations widget.configurations.requestKeyboardFocusOnCheckListChanged;
.requestKeyboardFocusOnCheckListChanged; if (!widget.configurations.isReadOnly) {
if (!widget.readOnly) {
_disableScrollControllerAnimateOnce = true; _disableScrollControllerAnimateOnce = true;
final currentSelection = controller.selection.copyWith(); final currentSelection = controller.selection.copyWith();
final attribute = value ? Attribute.checked : Attribute.unchecked; final attribute = value ? Attribute.checked : Attribute.unchecked;
@ -717,26 +716,27 @@ class QuillRawEditorState extends EditorState
block: node, block: node,
controller: controller, controller: controller,
textDirection: getDirectionOfNode(node), textDirection: getDirectionOfNode(node),
scrollBottomInset: widget.scrollBottomInset, scrollBottomInset: widget.configurations.scrollBottomInset,
verticalSpacing: _getVerticalSpacingForBlock(node, _styles), verticalSpacing: _getVerticalSpacingForBlock(node, _styles),
textSelection: controller.selection, textSelection: controller.selection,
color: widget.selectionColor, color: widget.configurations.selectionColor,
styles: _styles, styles: _styles,
enableInteractiveSelection: widget.enableInteractiveSelection, enableInteractiveSelection:
widget.configurations.enableInteractiveSelection,
hasFocus: _hasFocus, hasFocus: _hasFocus,
contentPadding: attrs.containsKey(Attribute.codeBlock.key) contentPadding: attrs.containsKey(Attribute.codeBlock.key)
? const EdgeInsets.all(16) ? const EdgeInsets.all(16)
: null, : null,
embedBuilder: widget.embedBuilder, embedBuilder: widget.configurations.embedBuilder,
linkActionPicker: _linkActionPicker, linkActionPicker: _linkActionPicker,
onLaunchUrl: widget.onLaunchUrl, onLaunchUrl: widget.configurations.onLaunchUrl,
cursorCont: _cursorCont, cursorCont: _cursorCont,
indentLevelCounts: indentLevelCounts, indentLevelCounts: indentLevelCounts,
clearIndents: clearIndents, clearIndents: clearIndents,
onCheckboxTap: _handleCheckboxTap, onCheckboxTap: _handleCheckboxTap,
readOnly: widget.readOnly, readOnly: widget.configurations.isReadOnly,
customStyleBuilder: widget.customStyleBuilder, customStyleBuilder: widget.configurations.customStyleBuilder,
customLinkPrefixes: widget.customLinkPrefixes, customLinkPrefixes: widget.configurations.customLinkPrefixes,
); );
result.add( result.add(
Directionality( Directionality(
@ -760,15 +760,15 @@ class QuillRawEditorState extends EditorState
final textLine = TextLine( final textLine = TextLine(
line: node, line: node,
textDirection: _textDirection, textDirection: _textDirection,
embedBuilder: widget.embedBuilder, embedBuilder: widget.configurations.embedBuilder,
customStyleBuilder: widget.customStyleBuilder, customStyleBuilder: widget.configurations.customStyleBuilder,
customRecognizerBuilder: widget.customRecognizerBuilder, customRecognizerBuilder: widget.configurations.customRecognizerBuilder,
styles: _styles!, styles: _styles!,
readOnly: widget.readOnly, readOnly: widget.configurations.isReadOnly,
controller: controller, controller: controller,
linkActionPicker: _linkActionPicker, linkActionPicker: _linkActionPicker,
onLaunchUrl: widget.onLaunchUrl, onLaunchUrl: widget.configurations.onLaunchUrl,
customLinkPrefixes: widget.customLinkPrefixes, customLinkPrefixes: widget.configurations.customLinkPrefixes,
); );
final editableTextLine = EditableTextLine( final editableTextLine = EditableTextLine(
node, node,
@ -778,8 +778,8 @@ class QuillRawEditorState extends EditorState
_getVerticalSpacingForLine(node, _styles), _getVerticalSpacingForLine(node, _styles),
_textDirection, _textDirection,
controller.selection, controller.selection,
widget.selectionColor, widget.configurations.selectionColor,
widget.enableInteractiveSelection, widget.configurations.enableInteractiveSelection,
_hasFocus, _hasFocus,
MediaQuery.devicePixelRatioOf(context), MediaQuery.devicePixelRatioOf(context),
_cursorCont); _cursorCont);
@ -842,12 +842,12 @@ class QuillRawEditorState extends EditorState
controller.addListener(_didChangeTextEditingValueListener); controller.addListener(_didChangeTextEditingValueListener);
_scrollController = widget.scrollController; _scrollController = widget.configurations.scrollController;
_scrollController.addListener(_updateSelectionOverlayForScroll); _scrollController.addListener(_updateSelectionOverlayForScroll);
_cursorCont = CursorCont( _cursorCont = CursorCont(
show: ValueNotifier<bool>(widget.showCursor), show: ValueNotifier<bool>(widget.configurations.showCursor),
style: widget.cursorStyle, style: widget.configurations.cursorStyle,
tickerProvider: this, tickerProvider: this,
); );
@ -882,7 +882,7 @@ class QuillRawEditorState extends EditorState
} }
// Focus // Focus
widget.focusNode.addListener(_handleFocusChanged); widget.configurations.focusNode.addListener(_handleFocusChanged);
} }
// KeyboardVisibilityController only checks for keyboards that // KeyboardVisibilityController only checks for keyboards that
@ -915,8 +915,8 @@ class QuillRawEditorState extends EditorState
? defaultStyles.merge(parentStyles) ? defaultStyles.merge(parentStyles)
: defaultStyles; : defaultStyles;
if (widget.customStyles != null) { if (widget.configurations.customStyles != null) {
_styles = _styles!.merge(widget.customStyles!); _styles = _styles!.merge(widget.configurations.customStyles!);
} }
_requestAutoFocusIfShould(); _requestAutoFocusIfShould();
@ -924,9 +924,9 @@ class QuillRawEditorState extends EditorState
Future<void> _requestAutoFocusIfShould() async { Future<void> _requestAutoFocusIfShould() async {
final focusManager = FocusScope.of(context); final focusManager = FocusScope.of(context);
if (!_didAutoFocus && widget.autoFocus) { if (!_didAutoFocus && widget.configurations.autoFocus) {
await Future.delayed(Duration.zero); // To avoid exceptions await Future.delayed(Duration.zero); // To avoid exceptions
focusManager.autofocus(widget.focusNode); focusManager.autofocus(widget.configurations.focusNode);
_didAutoFocus = true; _didAutoFocus = true;
} }
} }
@ -935,28 +935,29 @@ class QuillRawEditorState extends EditorState
void didUpdateWidget(QuillRawEditor oldWidget) { void didUpdateWidget(QuillRawEditor oldWidget) {
super.didUpdateWidget(oldWidget); super.didUpdateWidget(oldWidget);
_cursorCont.show.value = widget.showCursor; _cursorCont.show.value = widget.configurations.showCursor;
_cursorCont.style = widget.cursorStyle; _cursorCont.style = widget.configurations.cursorStyle;
if (controller != oldWidget.controller) { if (controller != oldWidget.configurations.controller) {
oldWidget.controller.removeListener(_didChangeTextEditingValue); oldWidget.configurations.controller
.removeListener(_didChangeTextEditingValue);
controller.addListener(_didChangeTextEditingValue); controller.addListener(_didChangeTextEditingValue);
updateRemoteValueIfNeeded(); updateRemoteValueIfNeeded();
} }
if (widget.scrollController != _scrollController) { if (widget.configurations.scrollController != _scrollController) {
_scrollController.removeListener(_updateSelectionOverlayForScroll); _scrollController.removeListener(_updateSelectionOverlayForScroll);
_scrollController = widget.scrollController; _scrollController = widget.configurations.scrollController;
_scrollController.addListener(_updateSelectionOverlayForScroll); _scrollController.addListener(_updateSelectionOverlayForScroll);
} }
if (widget.focusNode != oldWidget.focusNode) { if (widget.configurations.focusNode != oldWidget.configurations.focusNode) {
oldWidget.focusNode.removeListener(_handleFocusChanged); oldWidget.configurations.focusNode.removeListener(_handleFocusChanged);
widget.focusNode.addListener(_handleFocusChanged); widget.configurations.focusNode.addListener(_handleFocusChanged);
updateKeepAlive(); updateKeepAlive();
} }
if (controller.selection != oldWidget.controller.selection) { if (controller.selection != oldWidget.configurations.controller.selection) {
_selectionOverlay?.update(textEditingValue); _selectionOverlay?.update(textEditingValue);
} }
@ -964,19 +965,20 @@ class QuillRawEditorState extends EditorState
if (!shouldCreateInputConnection) { if (!shouldCreateInputConnection) {
closeConnectionIfNeeded(); closeConnectionIfNeeded();
} else { } else {
if (oldWidget.readOnly && _hasFocus) { if (oldWidget.configurations.isReadOnly && _hasFocus) {
openConnectionIfNeeded(); openConnectionIfNeeded();
} }
} }
// in case customStyles changed in new widget // in case customStyles changed in new widget
if (widget.customStyles != null) { if (widget.configurations.customStyles != null) {
_styles = _styles!.merge(widget.customStyles!); _styles = _styles!.merge(widget.configurations.customStyles!);
} }
} }
bool _shouldShowSelectionHandles() { bool _shouldShowSelectionHandles() {
return widget.showSelectionHandles && !controller.selection.isCollapsed; return widget.configurations.showSelectionHandles &&
!controller.selection.isCollapsed;
} }
@override @override
@ -988,7 +990,7 @@ class QuillRawEditorState extends EditorState
_selectionOverlay?.dispose(); _selectionOverlay?.dispose();
_selectionOverlay = null; _selectionOverlay = null;
controller.removeListener(_didChangeTextEditingValueListener); controller.removeListener(_didChangeTextEditingValueListener);
widget.focusNode.removeListener(_handleFocusChanged); widget.configurations.focusNode.removeListener(_handleFocusChanged);
_cursorCont.dispose(); _cursorCont.dispose();
_clipboardStatus _clipboardStatus
..removeListener(_onChangedClipboardStatus) ..removeListener(_onChangedClipboardStatus)
@ -1088,12 +1090,13 @@ class QuillRawEditorState extends EditorState
startHandleLayerLink: _startHandleLayerLink, startHandleLayerLink: _startHandleLayerLink,
endHandleLayerLink: _endHandleLayerLink, endHandleLayerLink: _endHandleLayerLink,
renderObject: renderEditor, renderObject: renderEditor,
selectionCtrls: widget.selectionCtrls, selectionCtrls: widget.configurations.selectionCtrls,
selectionDelegate: this, selectionDelegate: this,
clipboardStatus: _clipboardStatus, clipboardStatus: _clipboardStatus,
contextMenuBuilder: widget.contextMenuBuilder == null contextMenuBuilder: widget.configurations.contextMenuBuilder == null
? null ? null
: (context) => widget.contextMenuBuilder!(context, this), : (context) =>
widget.configurations.contextMenuBuilder!(context, this),
); );
_selectionOverlay!.handlesVisible = _shouldShowSelectionHandles(); _selectionOverlay!.handlesVisible = _shouldShowSelectionHandles();
_selectionOverlay!.showHandles(); _selectionOverlay!.showHandles();
@ -1127,7 +1130,8 @@ class QuillRawEditorState extends EditorState
Future<LinkMenuAction> _linkActionPicker(Node linkNode) async { Future<LinkMenuAction> _linkActionPicker(Node linkNode) async {
final link = linkNode.style.attributes[Attribute.link.key]!.value!; final link = linkNode.style.attributes[Attribute.link.key]!.value!;
return widget.linkActionPickerDelegate(context, link, linkNode); return widget.configurations
.linkActionPickerDelegate(context, link, linkNode);
} }
bool _showCaretOnScreenScheduled = false; bool _showCaretOnScreenScheduled = false;
@ -1140,13 +1144,13 @@ class QuillRawEditorState extends EditorState
bool _disableScrollControllerAnimateOnce = false; bool _disableScrollControllerAnimateOnce = false;
void _showCaretOnScreen() { void _showCaretOnScreen() {
if (!widget.showCursor || _showCaretOnScreenScheduled) { if (!widget.configurations.showCursor || _showCaretOnScreenScheduled) {
return; return;
} }
_showCaretOnScreenScheduled = true; _showCaretOnScreenScheduled = true;
SchedulerBinding.instance.addPostFrameCallback((_) { SchedulerBinding.instance.addPostFrameCallback((_) {
if (widget.scrollable || _scrollController.hasClients) { if (widget.configurations.scrollable || _scrollController.hasClients) {
_showCaretOnScreenScheduled = false; _showCaretOnScreenScheduled = false;
if (!mounted) { if (!mounted) {
@ -1213,7 +1217,7 @@ class QuillRawEditorState extends EditorState
_showCaretOnScreen(); _showCaretOnScreen();
} }
} else { } else {
widget.focusNode.requestFocus(); widget.configurations.focusNode.requestFocus();
} }
} }
@ -1291,7 +1295,7 @@ class QuillRawEditorState extends EditorState
_pastePlainText = controller.getPlainText(); _pastePlainText = controller.getPlainText();
_pasteStyleAndEmbed = controller.getAllIndividualSelectionStylesAndEmbed(); _pasteStyleAndEmbed = controller.getAllIndividualSelectionStylesAndEmbed();
if (widget.readOnly) { if (widget.configurations.isReadOnly) {
return; return;
} }
final selection = textEditingValue.selection; final selection = textEditingValue.selection;
@ -1311,7 +1315,7 @@ class QuillRawEditorState extends EditorState
/// Paste text from [Clipboard]. /// Paste text from [Clipboard].
@override @override
Future<void> pasteText(SelectionChangedCause cause) async { Future<void> pasteText(SelectionChangedCause cause) async {
if (widget.readOnly) { if (widget.configurations.isReadOnly) {
return; return;
} }
@ -1372,7 +1376,7 @@ class QuillRawEditorState extends EditorState
return; return;
} }
final onImagePaste = widget.onImagePaste; final onImagePaste = widget.configurations.onImagePaste;
if (onImagePaste != null) { if (onImagePaste != null) {
final image = await Pasteboard.image; final image = await Pasteboard.image;
@ -1411,7 +1415,7 @@ class QuillRawEditorState extends EditorState
} }
@override @override
bool get wantKeepAlive => widget.focusNode.hasFocus; bool get wantKeepAlive => widget.configurations.focusNode.hasFocus;
@override @override
AnimationController get floatingCursorResetController => AnimationController get floatingCursorResetController =>

@ -14,17 +14,18 @@ mixin RawEditorStateSelectionDelegateMixin on EditorState
implements TextSelectionDelegate { implements TextSelectionDelegate {
@override @override
TextEditingValue get textEditingValue { TextEditingValue get textEditingValue {
return widget.controller.plainTextEditingValue; return widget.configurations.controller.plainTextEditingValue;
} }
set textEditingValue(TextEditingValue value) { set textEditingValue(TextEditingValue value) {
final cursorPosition = value.selection.extentOffset; final cursorPosition = value.selection.extentOffset;
final oldText = widget.controller.document.toPlainText(); final oldText = widget.configurations.controller.document.toPlainText();
final newText = value.text; final newText = value.text;
final diff = getDiff(oldText, newText, cursorPosition); final diff = getDiff(oldText, newText, cursorPosition);
if (diff.deleted == '' && diff.inserted == '') { if (diff.deleted == '' && diff.inserted == '') {
// Only changing selection range // Only changing selection range
widget.controller.updateSelection(value.selection, ChangeSource.local); widget.configurations.controller
.updateSelection(value.selection, ChangeSource.local);
return; return;
} }
@ -34,7 +35,7 @@ mixin RawEditorStateSelectionDelegateMixin on EditorState
insertedText = insertedText =
containsEmbed ? _adjustInsertedText(diff.inserted) : diff.inserted; containsEmbed ? _adjustInsertedText(diff.inserted) : diff.inserted;
widget.controller.replaceText( widget.configurations.controller.replaceText(
diff.start, diff.deleted.length, insertedText, value.selection); diff.start, diff.deleted.length, insertedText, value.selection);
_applyPasteStyleAndEmbed(insertedText, diff.start, containsEmbed); _applyPasteStyleAndEmbed(insertedText, diff.start, containsEmbed);
@ -51,18 +52,22 @@ mixin RawEditorStateSelectionDelegateMixin on EditorState
final local = pos + offset; final local = pos + offset;
if (styleAndEmbed is Embeddable) { if (styleAndEmbed is Embeddable) {
widget.controller.replaceText(local, 0, styleAndEmbed, null); widget.configurations.controller
.replaceText(local, 0, styleAndEmbed, null);
} else { } else {
final style = styleAndEmbed as Style; final style = styleAndEmbed as Style;
if (style.isInline) { if (style.isInline) {
widget.controller widget.configurations.controller
.formatTextStyle(local, pasteStyleAndEmbed[i].length!, style); .formatTextStyle(local, pasteStyleAndEmbed[i].length!, style);
} else if (style.isBlock) { } else if (style.isBlock) {
final node = widget.controller.document.queryChild(local).node; final node = widget.configurations.controller.document
.queryChild(local)
.node;
if (node != null && if (node != null &&
pasteStyleAndEmbed[i].length == node.length - 1) { pasteStyleAndEmbed[i].length == node.length - 1) {
for (final attribute in style.values) { for (final attribute in style.values) {
widget.controller.document.format(local, 0, attribute); widget.configurations.controller.document
.format(local, 0, attribute);
} }
} }
} }
@ -164,15 +169,18 @@ mixin RawEditorStateSelectionDelegateMixin on EditorState
} }
@override @override
bool get cutEnabled => widget.contextMenuBuilder != null && !widget.readOnly; bool get cutEnabled =>
widget.configurations.contextMenuBuilder != null &&
!widget.configurations.isReadOnly;
@override @override
bool get copyEnabled => widget.contextMenuBuilder != null; bool get copyEnabled => widget.configurations.contextMenuBuilder != null;
@override @override
bool get pasteEnabled => bool get pasteEnabled =>
widget.contextMenuBuilder != null && !widget.readOnly; widget.configurations.contextMenuBuilder != null &&
!widget.configurations.isReadOnly;
@override @override
bool get selectAllEnabled => widget.contextMenuBuilder != null; bool get selectAllEnabled => widget.configurations.contextMenuBuilder != null;
} }

@ -1,8 +1,8 @@
import 'dart:ui'; import 'dart:ui';
import 'package:flutter/animation.dart'; import 'package:flutter/animation.dart' show Curves;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter/scheduler.dart'; import 'package:flutter/scheduler.dart' show SchedulerBinding;
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import '../../models/documents/document.dart'; import '../../models/documents/document.dart';
@ -27,7 +27,8 @@ mixin RawEditorStateTextInputClientMixin on EditorState
/// - cmd/ctrl+c shortcut to copy. /// - cmd/ctrl+c shortcut to copy.
/// - cmd/ctrl+a to select all. /// - cmd/ctrl+a to select all.
/// - Changing the selection using a physical keyboard. /// - Changing the selection using a physical keyboard.
bool get shouldCreateInputConnection => kIsWeb || !widget.readOnly; bool get shouldCreateInputConnection =>
kIsWeb || !widget.configurations.isReadOnly;
/// Returns `true` if there is open input connection. /// Returns `true` if there is open input connection.
bool get hasConnection => bool get hasConnection =>
@ -36,9 +37,10 @@ mixin RawEditorStateTextInputClientMixin on EditorState
/// Opens or closes input connection based on the current state of /// Opens or closes input connection based on the current state of
/// [focusNode] and [value]. /// [focusNode] and [value].
void openOrCloseConnection() { void openOrCloseConnection() {
if (widget.focusNode.hasFocus && widget.focusNode.consumeKeyboardToken()) { if (widget.configurations.focusNode.hasFocus &&
widget.configurations.focusNode.consumeKeyboardToken()) {
openConnectionIfNeeded(); openConnectionIfNeeded();
} else if (!widget.focusNode.hasFocus) { } else if (!widget.configurations.focusNode.hasFocus) {
closeConnectionIfNeeded(); closeConnectionIfNeeded();
} }
} }
@ -54,14 +56,16 @@ mixin RawEditorStateTextInputClientMixin on EditorState
this, this,
TextInputConfiguration( TextInputConfiguration(
inputType: TextInputType.multiline, inputType: TextInputType.multiline,
readOnly: widget.readOnly, readOnly: widget.configurations.isReadOnly,
inputAction: TextInputAction.newline, inputAction: widget.configurations.textInputAction,
enableSuggestions: !widget.readOnly, enableSuggestions: !widget.configurations.isReadOnly,
keyboardAppearance: widget.keyboardAppearance, keyboardAppearance: widget.configurations.keyboardAppearance,
textCapitalization: widget.textCapitalization, textCapitalization: widget.configurations.textCapitalization,
allowedMimeTypes: widget.contentInsertionConfiguration == null allowedMimeTypes:
? const <String>[] widget.configurations.contentInsertionConfiguration == null
: widget.contentInsertionConfiguration!.allowedMimeTypes, ? const <String>[]
: widget.configurations.contentInsertionConfiguration!
.allowedMimeTypes,
), ),
); );
@ -187,9 +191,10 @@ mixin RawEditorStateTextInputClientMixin on EditorState
final cursorPosition = value.selection.extentOffset; final cursorPosition = value.selection.extentOffset;
final diff = getDiff(oldText, text, cursorPosition); final diff = getDiff(oldText, text, cursorPosition);
if (diff.deleted.isEmpty && diff.inserted.isEmpty) { if (diff.deleted.isEmpty && diff.inserted.isEmpty) {
widget.controller.updateSelection(value.selection, ChangeSource.local); widget.configurations.controller
.updateSelection(value.selection, ChangeSource.local);
} else { } else {
widget.controller.replaceText( widget.configurations.controller.replaceText(
diff.start, diff.deleted.length, diff.inserted, value.selection); diff.start, diff.deleted.length, diff.inserted, value.selection);
} }
} }

@ -28,6 +28,14 @@ class QuillToolbarIconButton extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// return IconButton(
// onPressed: () {
// onPressed?.call();
// afterPressed?.call();
// },
// icon: icon ?? const SizedBox.shrink(),
// tooltip: tooltip,
// );
return ConstrainedBox( return ConstrainedBox(
constraints: BoxConstraints.tightFor(width: size, height: size), constraints: BoxConstraints.tightFor(width: size, height: size),
child: UtilityWidgets.maybeTooltip( child: UtilityWidgets.maybeTooltip(

@ -145,6 +145,8 @@ class QuillToolbarSelectHeaderStyleButtonsState
} }
final theme = Theme.of(context); final theme = Theme.of(context);
final isSelected = _selectedAttribute == attribute; final isSelected = _selectedAttribute == attribute;
// TODO: This needs to be updated to materail 3 as well just like in
// quill_icon.dart
return Padding( return Padding(
padding: const EdgeInsets.symmetric(horizontal: !kIsWeb ? 1.0 : 5.0), padding: const EdgeInsets.symmetric(horizontal: !kIsWeb ? 1.0 : 5.0),
child: ConstrainedBox( child: ConstrainedBox(

@ -98,7 +98,7 @@ class QuillToolbarProvider extends InheritedWidget {
'because ' 'because '
'The provider is $provider. Please make sure to wrap this widget' 'The provider is $provider. Please make sure to wrap this widget'
' with' ' with'
' QuillProvider widget. ' ' QuillToolbarProvider widget. '
'You might using QuillToolbar so make sure to' 'You might using QuillToolbar so make sure to'
' wrap them with the quill provider widget and setup the required ' ' wrap them with the quill provider widget and setup the required '
'configurations', 'configurations',
@ -157,8 +157,8 @@ class QuillBaseToolbarProvider extends InheritedWidget {
'because ' 'because '
'The provider is $provider. Please make sure to wrap this widget' 'The provider is $provider. Please make sure to wrap this widget'
' with' ' with'
' QuillProvider widget. ' ' QuillBaseToolbarProvider widget. '
'You might using QuillToolbar so make sure to' 'You might using QuillBaseToolbar so make sure to'
' wrap them with the quill provider widget and setup the required ' ' wrap them with the quill provider widget and setup the required '
'configurations', 'configurations',
'QuillProvider', 'QuillProvider',
@ -214,7 +214,7 @@ class QuillEditorProvider extends InheritedWidget {
'because ' 'because '
'The provider is $provider. Please make sure to wrap this widget' 'The provider is $provider. Please make sure to wrap this widget'
' with' ' with'
' QuillProvider widget. ' ' QuillEditorProvider widget. '
'You might using QuillEditor so make sure to' 'You might using QuillEditor so make sure to'
' wrap them with the quill provider widget and setup the required ' ' wrap them with the quill provider widget and setup the required '
'configurations', 'configurations',

@ -1,6 +1,6 @@
name: flutter_quill name: flutter_quill
description: A rich text editor built for the modern Android, iOS, web and desktop platforms. It is the WYSIWYG editor and a Quill component for Flutter. description: A rich text editor built for the modern Android, iOS, web and desktop platforms. It is the WYSIWYG editor and a Quill component for Flutter.
version: 8.4.1 version: 8.4.2
homepage: https://1o24bbs.com/c/bulletjournal/108 homepage: https://1o24bbs.com/c/bulletjournal/108
repository: https://github.com/singerdmx/flutter-quill repository: https://github.com/singerdmx/flutter-quill

Loading…
Cancel
Save