Add: test for QuillController clipboard

Dart Formatted
pull/1843/head
Douglas Ward 12 months ago
parent e76daa27b1
commit f1231b7851
  1. 1
      lib/src/models/config/toolbar/simple_toolbar_button_options.dart
  2. 2
      lib/src/models/config/toolbar/simple_toolbar_configurations.dart
  3. 46
      lib/src/models/documents/nodes/line.dart
  4. 68
      lib/src/widgets/quill/quill_controller.dart
  5. 4
      lib/src/widgets/raw_editor/raw_editor_state.dart
  6. 3
      lib/src/widgets/raw_editor/raw_editor_state_selection_delegate_mixin.dart
  7. 11
      lib/src/widgets/toolbar/base_button/base_value_button.dart
  8. 38
      lib/src/widgets/toolbar/buttons/clipboard_button.dart
  9. 3
      lib/src/widgets/toolbar/buttons/toggle_style_button.dart
  10. 86
      lib/src/widgets/toolbar/simple_toolbar.dart
  11. 53
      test/widgets/controller_test.dart

@ -75,7 +75,6 @@ class QuillSimpleToolbarButtonOptions extends Equatable {
this.linkStyle = const QuillToolbarLinkStyleButtonOptions(),
this.linkStyle2 = const QuillToolbarLinkStyleButton2Options(),
this.customButtons = const QuillToolbarCustomButtonOptions(),
this.clipboardCut = const QuillToolbarToggleStyleButtonOptions(),
this.clipboardCopy = const QuillToolbarToggleStyleButtonOptions(),
this.clipboardPaste = const QuillToolbarToggleStyleButtonOptions(),

@ -107,11 +107,9 @@ class QuillSimpleToolbarConfigurations extends QuillSharedToolbarProperties {
this.showSearchButton = true,
this.showSubscript = true,
this.showSuperscript = true,
this.showClipboardCut = true,
this.showClipboardCopy = true,
this.showClipboardPaste = true,
this.linkStyleType = LinkStyleType.original,
this.headerStyleType = HeaderStyleType.original,

@ -43,7 +43,9 @@ base class Line extends QuillContainer<Leaf?> {
if (parent!.isLast) {
return null;
}
return parent!.next is Block ? (parent!.next as Block).first as Line? : parent!.next as Line?;
return parent!.next is Block
? (parent!.next as Block).first as Line?
: parent!.next as Line?;
}
@override
@ -51,7 +53,9 @@ base class Line extends QuillContainer<Leaf?> {
@override
Delta toDelta() {
final delta = children.map((child) => child.toDelta()).fold(Delta(), (dynamic a, b) => a.concat(b));
final delta = children
.map((child) => child.toDelta())
.fold(Delta(), (dynamic a, b) => a.concat(b));
var attributes = style;
if (parent is Block) {
final block = parent as Block;
@ -130,11 +134,17 @@ base class Line extends QuillContainer<Leaf?> {
final isLineFormat = (index + local == thisLength) && local == 1;
if (isLineFormat) {
assert(style.values.every((attr) => attr.scope == AttributeScope.block || attr.scope == AttributeScope.ignore), 'It is not allowed to apply inline attributes to line itself.');
assert(
style.values.every((attr) =>
attr.scope == AttributeScope.block ||
attr.scope == AttributeScope.ignore),
'It is not allowed to apply inline attributes to line itself.');
_format(style);
} else {
// Otherwise forward to children as it's an inline format update.
assert(style.values.every((attr) => attr.scope == AttributeScope.inline || attr.scope == AttributeScope.ignore));
assert(style.values.every((attr) =>
attr.scope == AttributeScope.inline ||
attr.scope == AttributeScope.ignore));
assert(index + local != thisLength);
super.retain(index, local, style);
}
@ -205,15 +215,21 @@ base class Line extends QuillContainer<Leaf?> {
// Ensure that we're only unwrapping the block only if we unset a single
// block format in the `parentStyle` and there are no more block formats
// left to unset.
if (blockStyle.value == null && parentStyle.containsKey(blockStyle.key) && parentStyle.length == 1) {
if (blockStyle.value == null &&
parentStyle.containsKey(blockStyle.key) &&
parentStyle.length == 1) {
_unwrap();
} else if (!const MapEquality().equals(newStyle.getBlocksExceptHeader(), parentStyle)) {
} else if (!const MapEquality()
.equals(newStyle.getBlocksExceptHeader(), parentStyle)) {
_unwrap();
// Block style now can contain multiple attributes
if (newStyle.attributes.keys.any(Attribute.exclusiveBlockKeys.contains)) {
parentStyle.removeWhere((key, attr) => Attribute.exclusiveBlockKeys.contains(key));
if (newStyle.attributes.keys
.any(Attribute.exclusiveBlockKeys.contains)) {
parentStyle.removeWhere(
(key, attr) => Attribute.exclusiveBlockKeys.contains(key));
}
parentStyle.removeWhere((key, attr) => newStyle?.attributes.keys.contains(key) ?? false);
parentStyle.removeWhere(
(key, attr) => newStyle?.attributes.keys.contains(key) ?? false);
final parentStyleToMerge = Style.attr(parentStyle);
newStyle = newStyle.mergeAll(parentStyleToMerge);
_applyBlockStyles(newStyle);
@ -345,7 +361,8 @@ base class Line extends QuillContainer<Leaf?> {
void handle(Style style) {
for (final attr in result.values) {
if (!style.containsKey(attr.key) || (style.attributes[attr.key]?.value != attr.value)) {
if (!style.containsKey(attr.key) ||
(style.attributes[attr.key]?.value != attr.value)) {
excluded.add(attr);
}
}
@ -382,7 +399,8 @@ base class Line extends QuillContainer<Leaf?> {
/// Returns each node segment's offset in selection
/// with its corresponding style or embed as a list
List<OffsetValue> collectAllIndividualStylesAndEmbed(int offset, int len, {int beg = 0}) {
List<OffsetValue> collectAllIndividualStylesAndEmbed(int offset, int len,
{int beg = 0}) {
final local = math.min(length - offset, len);
final result = <OffsetValue>[];
@ -414,7 +432,8 @@ base class Line extends QuillContainer<Leaf?> {
final remaining = len - local;
if (remaining > 0 && nextLine != null) {
final rest = nextLine!.collectAllIndividualStylesAndEmbed(0, remaining, beg: local + beg);
final rest = nextLine!
.collectAllIndividualStylesAndEmbed(0, remaining, beg: local + beg);
result.addAll(rest);
}
@ -484,7 +503,8 @@ base class Line extends QuillContainer<Leaf?> {
final remaining = len - local;
if (remaining > 0 && nextLine != null) {
final rest = nextLine!.collectAllStylesWithOffsets(0, remaining, beg: local);
final rest =
nextLine!.collectAllStylesWithOffsets(0, remaining, beg: local);
result.addAll(rest);
}

@ -108,7 +108,9 @@ class QuillController extends ChangeNotifier {
/// Only attributes applied to all characters within this range are
/// included in the result.
Style getSelectionStyle() {
return document.collectStyle(selection.start, selection.end - selection.start).mergeAll(toggledStyle);
return document
.collectStyle(selection.start, selection.end - selection.start)
.mergeAll(toggledStyle);
}
// Increases or decreases the indent of the current selection by 1.
@ -177,19 +179,23 @@ class QuillController extends ChangeNotifier {
/// Returns all styles and Embed for each node within selection
List<OffsetValue> getAllIndividualSelectionStylesAndEmbed() {
final stylesAndEmbed = document.collectAllIndividualStyleAndEmbed(selection.start, selection.end - selection.start);
final stylesAndEmbed = document.collectAllIndividualStyleAndEmbed(
selection.start, selection.end - selection.start);
return stylesAndEmbed;
}
/// Returns plain text for each node within selection
String getPlainText() {
final text = document.getPlainText(selection.start, selection.end - selection.start);
final text =
document.getPlainText(selection.start, selection.end - selection.start);
return text;
}
/// Returns all styles for any character within the specified text range.
List<Style> getAllSelectionStyles() {
final styles = document.collectAllStyles(selection.start, selection.end - selection.start)..add(toggledStyle);
final styles = document.collectAllStyles(
selection.start, selection.end - selection.start)
..add(toggledStyle);
return styles;
}
@ -235,7 +241,8 @@ class QuillController extends ChangeNotifier {
/// clear editor
void clear() {
replaceText(0, plainTextEditingValue.text.length - 1, '', const TextSelection.collapsed(offset: 0));
replaceText(0, plainTextEditingValue.text.length - 1, '',
const TextSelection.collapsed(offset: 0));
}
void replaceText(
@ -261,9 +268,13 @@ class QuillController extends ChangeNotifier {
delta.last.isInsert &&
// pasted text should not use toggledStyle
(data is! String || data.length < 2);
if (shouldRetainDelta && toggledStyle.isNotEmpty && delta.length == 2 && delta.last.data == '\n') {
if (shouldRetainDelta &&
toggledStyle.isNotEmpty &&
delta.length == 2 &&
delta.last.data == '\n') {
// if all attributes are inline, shouldRetainDelta should be false
final anyAttributeNotInline = toggledStyle.values.any((attr) => !attr.isInline);
final anyAttributeNotInline =
toggledStyle.values.any((attr) => !attr.isInline);
if (!anyAttributeNotInline) {
shouldRetainDelta = false;
}
@ -308,7 +319,8 @@ class QuillController extends ChangeNotifier {
/// forward == true && textAfter.isEmpty
/// Android only
/// see https://github.com/singerdmx/flutter-quill/discussions/514
void handleDelete(int cursorPosition, bool forward) => onDelete?.call(cursorPosition, forward);
void handleDelete(int cursorPosition, bool forward) =>
onDelete?.call(cursorPosition, forward);
void formatTextStyle(int index, int len, Style style) {
style.attributes.forEach((key, attr) {
@ -322,7 +334,9 @@ class QuillController extends ChangeNotifier {
Attribute? attribute, {
bool shouldNotifyListeners = true,
}) {
if (len == 0 && attribute!.isInline && attribute.key != Attribute.link.key) {
if (len == 0 &&
attribute!.isInline &&
attribute.key != Attribute.link.key) {
// Add the attribute to our toggledStyle.
// It will be used later upon insertion.
toggledStyle = toggledStyle.put(attribute);
@ -332,7 +346,9 @@ class QuillController extends ChangeNotifier {
// Transform selection against the composed change and give priority to
// the change. This is needed in cases when format operation actually
// inserts data into the document (e.g. embeds).
final adjustedSelection = selection.copyWith(baseOffset: change.transformPosition(selection.baseOffset), extentOffset: change.transformPosition(selection.extentOffset));
final adjustedSelection = selection.copyWith(
baseOffset: change.transformPosition(selection.baseOffset),
extentOffset: change.transformPosition(selection.extentOffset));
if (selection != adjustedSelection) {
_updateSelection(adjustedSelection);
}
@ -341,7 +357,8 @@ class QuillController extends ChangeNotifier {
}
}
void formatSelection(Attribute? attribute, {bool shouldNotifyListeners = true}) {
void formatSelection(Attribute? attribute,
{bool shouldNotifyListeners = true}) {
formatText(
selection.start,
selection.end - selection.start,
@ -423,10 +440,13 @@ class QuillController extends ChangeNotifier {
super.dispose();
}
void _updateSelection(TextSelection textSelection, {bool insertNewline = false}) {
void _updateSelection(TextSelection textSelection,
{bool insertNewline = false}) {
_selection = textSelection;
final end = document.length - 1;
_selection = selection.copyWith(baseOffset: math.min(selection.baseOffset, end), extentOffset: math.min(selection.extentOffset, end));
_selection = selection.copyWith(
baseOffset: math.min(selection.baseOffset, end),
extentOffset: math.min(selection.extentOffset, end));
if (keepStyleOnNewLine) {
if (insertNewline && selection.start > 0) {
final style = document.collectStyle(selection.start - 1, 0);
@ -475,8 +495,10 @@ class QuillController extends ChangeNotifier {
if (!selection.isCollapsed) {
Clipboard.setData(ClipboardData(text: _pastePlainText));
if (!copy) {
if (readOnly) return false;
final sel = selection;
replaceText(sel.start, sel.end - sel.start, '', TextSelection.collapsed(offset: sel.start));
replaceText(sel.start, sel.end - sel.start, '',
TextSelection.collapsed(offset: sel.start));
}
return true;
}
@ -525,7 +547,8 @@ class QuillController extends ChangeNotifier {
selection.start,
selection.end - selection.start,
plainText.text!,
TextSelection.collapsed(offset: selection.start + plainText.text!.length),
TextSelection.collapsed(
offset: selection.start + plainText.text!.length),
);
updateEditor?.call();
return true;
@ -566,16 +589,21 @@ class QuillController extends ChangeNotifier {
bool ignoreFocus = false,
bool shouldNotifyListeners = true,
}) {
final containsEmbed = insertedText.codeUnits.contains(Embed.kObjectReplacementInt);
insertedText = containsEmbed ? _adjustInsertedText(insertedText) : insertedText;
final containsEmbed =
insertedText.codeUnits.contains(Embed.kObjectReplacementInt);
insertedText =
containsEmbed ? _adjustInsertedText(insertedText) : insertedText;
replaceText(index, len, insertedText, textSelection, ignoreFocus: ignoreFocus, shouldNotifyListeners: shouldNotifyListeners);
replaceText(index, len, insertedText, textSelection,
ignoreFocus: ignoreFocus, shouldNotifyListeners: shouldNotifyListeners);
_applyPasteStyleAndEmbed(insertedText, index, containsEmbed);
}
void _applyPasteStyleAndEmbed(String insertedText, int start, bool containsEmbed) {
if (insertedText == pastePlainText && pastePlainText != '' || containsEmbed) {
void _applyPasteStyleAndEmbed(
String insertedText, int start, bool containsEmbed) {
if (insertedText == pastePlainText && pastePlainText != '' ||
containsEmbed) {
final pos = start;
for (final p in pasteStyleAndEmbed) {
final offset = p.offset;

@ -148,8 +148,8 @@ class QuillRawEditorState extends EditorState
/// Paste text from [Clipboard].
@override
Future<void> pasteText(SelectionChangedCause cause) async {
if ( await controller.clipboardPaste(updateEditor: () => bringIntoView(textEditingValue.selection.extent) ) ) {
if (await controller.clipboardPaste(
updateEditor: () => bringIntoView(textEditingValue.selection.extent))) {
return;
}

@ -26,7 +26,8 @@ mixin RawEditorStateSelectionDelegateMixin on EditorState
return;
}
widget.configurations.controller.replaceTextWithEmbeds(diff.start, diff.deleted.length, diff.inserted, value.selection);
widget.configurations.controller.replaceTextWithEmbeds(
diff.start, diff.deleted.length, diff.inserted, value.selection);
}
@override

@ -107,6 +107,11 @@ abstract class QuillToolbarBaseValueButtonState<
}
}
typedef QuillToolbarToggleStyleBaseButton = QuillToolbarBaseValueButton<QuillToolbarToggleStyleButtonOptions, QuillToolbarToggleStyleButtonExtraOptions>;
typedef QuillToolbarToggleStyleBaseButtonState<W extends QuillToolbarToggleStyleBaseButton> = QuillToolbarBaseValueButtonState<W, QuillToolbarToggleStyleButtonOptions, QuillToolbarToggleStyleButtonExtraOptions, bool>;
typedef QuillToolbarToggleStyleBaseButton = QuillToolbarBaseValueButton<
QuillToolbarToggleStyleButtonOptions,
QuillToolbarToggleStyleButtonExtraOptions>;
typedef QuillToolbarToggleStyleBaseButtonState<
W extends QuillToolbarToggleStyleBaseButton>
= QuillToolbarBaseValueButtonState<W, QuillToolbarToggleStyleButtonOptions,
QuillToolbarToggleStyleButtonExtraOptions, bool>;

@ -1,5 +1,6 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import '../../../../extensions.dart';
@ -15,9 +16,11 @@ class ClipboardMonitor {
Timer? _timer;
void monitorClipboard(bool add, void Function() listener) {
if (kIsWeb) return;
if (add) {
_clipboardStatus.addListener(listener);
_timer = Timer.periodic(const Duration(seconds: 1), (timer) => _clipboardStatus.update());
_timer = Timer.periodic(
const Duration(seconds: 1), (timer) => _clipboardStatus.update());
} else {
_timer?.cancel();
_clipboardStatus.removeListener(listener);
@ -26,9 +29,13 @@ class ClipboardMonitor {
}
class QuillToolbarClipboardButton extends QuillToolbarToggleStyleBaseButton {
QuillToolbarClipboardButton({required super.controller, required this.clipboard, required super.options, super.key});
QuillToolbarClipboardButton(
{required super.controller,
required this.clipboardAction,
super.options = const QuillToolbarToggleStyleButtonOptions(),
super.key});
final ClipboardAction clipboard;
final ClipboardAction clipboardAction;
final ClipboardMonitor _monitor = ClipboardMonitor();
@ -36,16 +43,21 @@ class QuillToolbarClipboardButton extends QuillToolbarToggleStyleBaseButton {
State<StatefulWidget> createState() => QuillToolbarClipboardButtonState();
}
class QuillToolbarClipboardButtonState extends QuillToolbarToggleStyleBaseButtonState<QuillToolbarClipboardButton> {
class QuillToolbarClipboardButtonState
extends QuillToolbarToggleStyleBaseButtonState<
QuillToolbarClipboardButton> {
@override
bool get currentStateValue {
switch (widget.clipboard) {
switch (widget.clipboardAction) {
case ClipboardAction.cut:
return !controller.readOnly && !controller.selection.isCollapsed;
case ClipboardAction.copy:
return !controller.selection.isCollapsed;
case ClipboardAction.paste:
return !controller.readOnly && widget._monitor._clipboardStatus.value == ClipboardStatus.pasteable;
return !controller.readOnly &&
(kIsWeb ||
widget._monitor._clipboardStatus.value ==
ClipboardStatus.pasteable);
}
}
@ -53,33 +65,33 @@ class QuillToolbarClipboardButtonState extends QuillToolbarToggleStyleBaseButton
@override
void addExtraListener() {
if ( widget.clipboard == ClipboardAction.paste) {
if (widget.clipboardAction == ClipboardAction.paste) {
widget._monitor.monitorClipboard(true, _listenClipboardStatus);
}
}
@override
void removeExtraListener(covariant QuillToolbarClipboardButton oldWidget) {
if ( widget.clipboard == ClipboardAction.paste) {
if (widget.clipboardAction == ClipboardAction.paste) {
oldWidget._monitor.monitorClipboard(false, _listenClipboardStatus);
}
}
@override
String get defaultTooltip => switch (widget.clipboard) {
ClipboardAction.cut => 'cut',
String get defaultTooltip => switch (widget.clipboardAction) {
ClipboardAction.cut => 'Cut',
ClipboardAction.copy => context.loc.copy,
ClipboardAction.paste => 'paste',
ClipboardAction.paste => 'Paste',
};
IconData get _icon => switch (widget.clipboard) {
IconData get _icon => switch (widget.clipboardAction) {
ClipboardAction.cut => Icons.cut_outlined,
ClipboardAction.copy => Icons.copy_outlined,
ClipboardAction.paste => Icons.paste_outlined,
};
void _onPressed() {
switch (widget.clipboard) {
switch (widget.clipboardAction) {
case ClipboardAction.cut:
controller.clipboardSelection(false);
break;

@ -37,7 +37,8 @@ class QuillToolbarToggleStyleButton extends QuillToolbarToggleStyleBaseButton {
}
class QuillToolbarToggleStyleButtonState
extends QuillToolbarToggleStyleBaseButtonState<QuillToolbarToggleStyleButton> {
extends QuillToolbarToggleStyleBaseButtonState<
QuillToolbarToggleStyleButton> {
Style get _selectionStyle => controller.getSelectionStyle();
@override

@ -8,7 +8,8 @@ import 'base_toolbar.dart';
import 'buttons/alignment/select_alignment_buttons.dart';
import 'buttons/arrow_indicated_list_button.dart';
class QuillSimpleToolbar extends StatelessWidget implements PreferredSizeWidget {
class QuillSimpleToolbar extends StatelessWidget
implements PreferredSizeWidget {
const QuillSimpleToolbar({
required this.configurations,
super.key,
@ -36,15 +37,23 @@ class QuillSimpleToolbar extends StatelessWidget implements PreferredSizeWidget
configurations.showBackgroundColorButton ||
configurations.showClearFormat ||
theEmbedButtons?.isNotEmpty == true,
configurations.showLeftAlignment || configurations.showCenterAlignment || configurations.showRightAlignment || configurations.showJustifyAlignment || configurations.showDirection,
configurations.showLeftAlignment ||
configurations.showCenterAlignment ||
configurations.showRightAlignment ||
configurations.showJustifyAlignment ||
configurations.showDirection,
configurations.showHeaderStyle,
configurations.showListNumbers || configurations.showListBullets || configurations.showListCheck || configurations.showCodeBlock,
configurations.showListNumbers ||
configurations.showListBullets ||
configurations.showListCheck ||
configurations.showCodeBlock,
configurations.showQuote || configurations.showIndent,
configurations.showLink || configurations.showSearchButton
];
List<Widget> childrenBuilder(BuildContext context) {
final toolbarConfigurations = context.requireQuillSimpleToolbarConfigurations;
final toolbarConfigurations =
context.requireQuillSimpleToolbarConfigurations;
final globalIconSize = toolbarConfigurations.buttonOptions.base.iconSize;
@ -148,13 +157,25 @@ class QuillSimpleToolbar extends StatelessWidget implements PreferredSizeWidget
options: toolbarConfigurations.buttonOptions.clearFormat,
),
if (theEmbedButtons != null)
for (final builder in theEmbedButtons) builder(globalController, globalIconSize ?? kDefaultIconSize, context.quillToolbarBaseButtonOptions?.iconTheme, configurations.dialogTheme),
if (configurations.showDividers && isButtonGroupShown[0] && (isButtonGroupShown[1] || isButtonGroupShown[2] || isButtonGroupShown[3] || isButtonGroupShown[4] || isButtonGroupShown[5]))
for (final builder in theEmbedButtons)
builder(
globalController,
globalIconSize ?? kDefaultIconSize,
context.quillToolbarBaseButtonOptions?.iconTheme,
configurations.dialogTheme),
if (configurations.showDividers &&
isButtonGroupShown[0] &&
(isButtonGroupShown[1] ||
isButtonGroupShown[2] ||
isButtonGroupShown[3] ||
isButtonGroupShown[4] ||
isButtonGroupShown[5]))
divider,
if (configurations.showAlignmentButtons)
QuillToolbarSelectAlignmentButtons(
controller: globalController,
options: toolbarConfigurations.buttonOptions.selectAlignmentButtons.copyWith(
options: toolbarConfigurations.buttonOptions.selectAlignmentButtons
.copyWith(
showLeftAlignment: configurations.showLeftAlignment,
showCenterAlignment: configurations.showCenterAlignment,
showRightAlignment: configurations.showRightAlignment,
@ -167,20 +188,34 @@ class QuillSimpleToolbar extends StatelessWidget implements PreferredSizeWidget
options: toolbarConfigurations.buttonOptions.direction,
controller: globalController,
),
if (configurations.showDividers && isButtonGroupShown[1] && (isButtonGroupShown[2] || isButtonGroupShown[3] || isButtonGroupShown[4] || isButtonGroupShown[5])) divider,
if (configurations.showDividers &&
isButtonGroupShown[1] &&
(isButtonGroupShown[2] ||
isButtonGroupShown[3] ||
isButtonGroupShown[4] ||
isButtonGroupShown[5]))
divider,
if (configurations.showHeaderStyle) ...[
if (configurations.headerStyleType.isOriginal)
QuillToolbarSelectHeaderStyleDropdownButton(
controller: globalController,
options: toolbarConfigurations.buttonOptions.selectHeaderStyleDropdownButton,
options: toolbarConfigurations
.buttonOptions.selectHeaderStyleDropdownButton,
)
else
QuillToolbarSelectHeaderStyleButtons(
controller: globalController,
options: toolbarConfigurations.buttonOptions.selectHeaderStyleButtons,
options:
toolbarConfigurations.buttonOptions.selectHeaderStyleButtons,
),
],
if (configurations.showDividers && configurations.showHeaderStyle && isButtonGroupShown[2] && (isButtonGroupShown[3] || isButtonGroupShown[4] || isButtonGroupShown[5])) divider,
if (configurations.showDividers &&
configurations.showHeaderStyle &&
isButtonGroupShown[2] &&
(isButtonGroupShown[3] ||
isButtonGroupShown[4] ||
isButtonGroupShown[5]))
divider,
if (configurations.showListNumbers)
QuillToolbarToggleStyleButton(
attribute: Attribute.ol,
@ -204,7 +239,9 @@ class QuillSimpleToolbar extends StatelessWidget implements PreferredSizeWidget
options: toolbarConfigurations.buttonOptions.codeBlock,
controller: globalController,
),
if (configurations.showDividers && isButtonGroupShown[3] && (isButtonGroupShown[4] || isButtonGroupShown[5])) ...[
if (configurations.showDividers &&
isButtonGroupShown[3] &&
(isButtonGroupShown[4] || isButtonGroupShown[5])) ...[
divider,
],
if (configurations.showQuote)
@ -225,7 +262,10 @@ class QuillSimpleToolbar extends StatelessWidget implements PreferredSizeWidget
isIncrease: false,
options: toolbarConfigurations.buttonOptions.indentDecrease,
),
if (configurations.showDividers && isButtonGroupShown[4] && isButtonGroupShown[5]) divider,
if (configurations.showDividers &&
isButtonGroupShown[4] &&
isButtonGroupShown[5])
divider,
if (configurations.showLink)
toolbarConfigurations.linkStyleType.isOriginal
? QuillToolbarLinkStyleButton(
@ -305,11 +345,15 @@ class QuillSimpleToolbar extends StatelessWidget implements PreferredSizeWidget
return Container(
decoration: configurations.decoration ??
BoxDecoration(
color: configurations.color ?? Theme.of(context).canvasColor,
color:
configurations.color ?? Theme.of(context).canvasColor,
),
constraints: BoxConstraints.tightFor(
height: configurations.axis == Axis.horizontal ? _toolbarSize : null,
width: configurations.axis == Axis.vertical ? _toolbarSize : null,
height: configurations.axis == Axis.horizontal
? _toolbarSize
: null,
width:
configurations.axis == Axis.vertical ? _toolbarSize : null,
),
child: QuillToolbarArrowIndicatedButtonList(
axis: configurations.axis,
@ -323,7 +367,9 @@ class QuillSimpleToolbar extends StatelessWidget implements PreferredSizeWidget
}
@override
Size get preferredSize => configurations.axis == Axis.horizontal ? const Size.fromHeight(kDefaultToolbarSize) : const Size.fromWidth(kDefaultToolbarSize);
Size get preferredSize => configurations.axis == Axis.horizontal
? const Size.fromHeight(kDefaultToolbarSize)
: const Size.fromWidth(kDefaultToolbarSize);
}
/// The divider which is used for separation of buttons in the toolbar.
@ -339,10 +385,12 @@ class QuillToolbarDivider extends StatelessWidget {
});
/// Provides a horizontal divider for vertical toolbar.
const QuillToolbarDivider.horizontal({Key? key, Color? color, double? space}) : this(Axis.horizontal, color: color, space: space, key: key);
const QuillToolbarDivider.horizontal({Key? key, Color? color, double? space})
: this(Axis.horizontal, color: color, space: space, key: key);
/// Provides a horizontal divider for horizontal toolbar.
const QuillToolbarDivider.vertical({Key? key, Color? color, double? space}) : this(Axis.vertical, color: color, space: space, key: key);
const QuillToolbarDivider.vertical({Key? key, Color? color, double? space})
: this(Axis.vertical, color: color, space: space, key: key);
/// The axis along which the toolbar is.
final Axis axis;

@ -6,6 +6,7 @@ import 'package:test/test.dart';
void main() {
const testDocumentContents = 'data';
late QuillController controller;
WidgetsFlutterBinding.ensureInitialized();
setUp(() {
controller = QuillController.basic()
@ -119,6 +120,26 @@ void main() {
expect((result[1].value as Embeddable).type, BlockEmbed.imageType);
});
test('getAllIndividualSelectionStylesAndEmbed mixed', () {
controller
..replaceText(0, 4, 'bold plain italic', null)
..formatText(0, 4, Attribute.bold)
..formatText(11, 17, Attribute.italic)
..updateSelection(const TextSelection(baseOffset: 2, extentOffset: 14),
ChangeSource.local);
expect(controller.getPlainText(), 'ld plain ita',
reason: 'Selection spans 3 styles');
//
final result = controller.getAllIndividualSelectionStylesAndEmbed();
expect(result.length, 2);
expect(result[0].offset, 0);
expect(result[0].length, 2, reason: 'First style is 2 characters bold');
expect(result[0].value, const Style().put(Attribute.bold));
expect(result[1].offset, 9);
expect(result[1].length, 3, reason: 'Last style is 3 characters italic');
expect(result[1].value, const Style().put(Attribute.italic));
});
test('getPlainText', () {
controller.updateSelection(
const TextSelection(baseOffset: 0, extentOffset: 4),
@ -302,5 +323,37 @@ void main() {
expect(controller.document.toDelta(),
Delta()..insert('test $originalContents'));
});
test('clipboardSelection empty', () {
expect(controller.clipboardSelection(true), false,
reason: 'No effect when no selection');
expect(controller.clipboardSelection(false), false);
});
test('clipboardSelection', () {
controller
..replaceText(0, 4, 'bold plain italic', null)
..formatText(0, 4, Attribute.bold)
..formatText(11, 17, Attribute.italic)
..updateSelection(const TextSelection(baseOffset: 2, extentOffset: 14),
ChangeSource.local);
//
expect(controller.clipboardSelection(true), true);
expect(controller.document.length, 18,
reason: 'Copy does not change the document');
expect(controller.clipboardSelection(false), true);
expect(controller.document.length, 6, reason: 'Cut changes the document');
//
controller
..readOnly = true
..updateSelection(const TextSelection(baseOffset: 2, extentOffset: 4),
ChangeSource.local);
expect(controller.selection.isCollapsed, false);
expect(controller.clipboardSelection(true), true);
expect(controller.document.length, 6);
expect(controller.clipboardSelection(false), false);
expect(controller.document.length, 6,
reason: 'Cut not permitted on readOnly document');
});
});
}

Loading…
Cancel
Save