Skip to content

Commit

Permalink
Chore: improved doc comments and renamed params in QuillEditorConfig
Browse files Browse the repository at this point in the history
  • Loading branch information
CatHood0 committed Nov 29, 2024
1 parent a4177ca commit fb5b11a
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 80 deletions.
75 changes: 38 additions & 37 deletions lib/src/editor/config/editor_config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ class QuillEditorConfig {
const QuillEditorConfig({
this.scrollable = true,
this.padding = EdgeInsets.zero,
this.placeholderComponentsConfiguration,
this.cursorPlaceholderConfig,
@experimental this.placeholderConfig,
@experimental this.cursorPlaceholderConfig,
@experimental this.characterShortcutEvents = const [],
@experimental this.spaceShortcutEvents = const [],
this.autoFocus = false,
Expand Down Expand Up @@ -141,38 +141,39 @@ class QuillEditorConfig {
@experimental
final List<SpaceShortcutEvent> spaceShortcutEvents;

/// Whether the line is empty, this let us add a placeholder
///
/// _Note: these placeholders are limited only to lines with block styles_
///
/// ### Example
///
/// Assume that you want to show only a placeholder text for the header items,
/// so, we only need to configure `placeholderComponentsConfiguration` like:
///
///```dart
///final configuration = PlaceholderComponentsConfiguration(
/// builders: <String, PlaceholderConfigurationBuilder>{
/// Attribute.header.key: (Attribute attr, style) {
/// final values = [30, 27, 22];
/// final level = attr.value as int?;
/// if (level == null) return null;
/// final fontSize = values[level - 1];
/// return PlaceholderArguments(
/// placeholderText: 'Header $level',
/// style: TextStyle(fontSize: fontSize.toDouble()).merge(style),
/// );
/// },
/// },
/// // if you use custom attribute keys into the `builders` param, them you will need to implement that
/// // here too
/// customBlockAttributesKeys: null,
///),
///```
final PlaceholderConfig? placeholderComponentsConfiguration;

/// This argument configure how will be showed the placeholder at right or left of the cursor
final CursorPlaceholderConfig? cursorPlaceholderConfig;
/// Configuration for displaying placeholders in empty lines or near the cursor.
///
/// ### Example
///
/// To show a placeholder text specifically for header items:
///
/// ```dart
/// final configuration = PlaceholderConfig(
/// builders: <String, PlaceholderComponentBuilder>{
/// Attribute.header.key: (Attribute attr, style) {
/// final values = [30, 27, 22];
/// final level = attr.value as int?;
/// if (level == null) return null;
/// final fontSize = values[(level - 1 < 0 || level - 1 > 3 ? 0 : level - 1)];
/// return PlaceholderTextBuilder(
/// text: 'Header $level',
/// style: TextStyle(fontSize: fontSize.toDouble()).merge(style),
/// );
/// },
/// },
/// // If using custom attributes, register their keys here.
/// customBlockAttributesKeys: null,
/// );
/// ```
@experimental
final PlaceholderConfig? placeholderConfig;

/// Configures how a placeholder is displayed relative to the cursor.
///
/// This argument specifies the appearance, style, and position of a placeholder
/// shown at the cursor's location in an empty line.
@experimental
final CursorPlaceholderConfig? cursorPlaceholderConfig;

/// A handler for keys that are pressed when the editor is focused.
///
Expand Down Expand Up @@ -542,7 +543,7 @@ class QuillEditorConfig {
ContentInsertionConfiguration? contentInsertionConfiguration,
GlobalKey<EditorState>? editorKey,
TextSelectionThemeData? textSelectionThemeData,
PlaceholderConfig? placeholderComponentsConfiguration,
PlaceholderConfig? placeholderConfig,
bool? requestKeyboardFocusOnCheckListChanged,
CursorPlaceholderConfig? cursorPlaceholderConfig,
TextMagnifierConfiguration? magnifierConfiguration,
Expand All @@ -555,8 +556,8 @@ class QuillEditorConfig {
return QuillEditorConfig(
cursorPlaceholderConfig:
cursorPlaceholderConfig ?? this.cursorPlaceholderConfig,
placeholderComponentsConfiguration: placeholderComponentsConfiguration ??
this.placeholderComponentsConfiguration,
placeholderConfig: placeholderConfig ??
this.placeholderConfig,
customLeadingBlockBuilder:
customLeadingBlockBuilder ?? this.customLeadingBlockBuilder,
placeholder: placeholder ?? this.placeholder,
Expand Down
4 changes: 2 additions & 2 deletions lib/src/editor/editor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -256,11 +256,11 @@ class QuillEditorState extends State<QuillEditor>
final showSelectionToolbar = configurations.enableInteractiveSelection &&
configurations.enableSelectionToolbar;
final placeholderBuilder =
widget.config.placeholderComponentsConfiguration == null
widget.config.placeholderConfig == null
? null
: PlaceholderBuilder(
configuration:
widget.config.placeholderComponentsConfiguration!);
widget.config.placeholderConfig!);
final child = QuillRawEditor(
key: _editorKey,
controller: controller,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ final List<String> _blackList = List.unmodifiable(<String>[
...Attribute.ignoreKeys,
]);

/// A utility class for managing placeholder rendering logic in a document editor.
///
/// The `PlaceholderBuilder` is responsible for determining when a placeholder
/// should be displayed in an empty node and for constructing the corresponding
/// visual representation.
///
/// - [configuration]: An instance of [PlaceholderConfig] containing placeholder
/// rendering rules and attribute customizations.
@experimental
@immutable
class PlaceholderBuilder {
Expand All @@ -31,7 +39,14 @@ class PlaceholderBuilder {
Set<String>? get customBlockAttributesKeys =>
configuration.customBlockAttributesKeys;

/// Check if this node need to show a placeholder
/// Determines whether a given [Line] node should display a placeholder.
///
/// This method checks if the node is empty and contains a block-level attribute
/// matching a builder key or custom attribute, excluding keys in the blacklist.
///
/// Returns a tuple:
/// - [bool]: Whether a placeholder should be shown.
/// - [String]: The key of the matching attribute, if applicable.
@experimental
(bool, String) shouldShowPlaceholder(Line node) {
if (builders.isEmpty) return (false, '');
Expand All @@ -54,12 +69,11 @@ class PlaceholderBuilder {
return (node.isEmpty && shouldShow, key);
}

/// Build is similar to build method from any widget but
/// this only has the responsability of create a WidgetSpan to be showed
/// by the line when the node is empty
/// Constructs a [WidgetSpan] for rendering a placeholder in an empty line.
///
/// Before use this, we should always use [shouldShowPlaceholder] to avoid
/// show any placeholder where is not needed
/// This method creates a visual representation of the placeholder based on
/// the block attribute and style configurations provided. Use [shouldShowPlaceholder]
/// before invoking this method to ensure the placeholder is needed.
@experimental
WidgetSpan? build({
required Attribute blockAttribute,
Expand All @@ -73,11 +87,11 @@ class PlaceholderBuilder {
final configuration =
builders[blockAttribute.key]?.call(blockAttribute, lineStyle);
// we don't need to add a placeholder that is null or contains a empty text
if (configuration == null || configuration.placeholderText.trim().isEmpty) {
if (configuration == null || configuration.text.trim().isEmpty) {
return null;
}
final textWidget = Text(
configuration.placeholderText,
configuration.text,
style: configuration.style,
textDirection: textDirection,
softWrap: true,
Expand All @@ -86,19 +100,9 @@ class PlaceholderBuilder {
textScaler: textScaler,
textWidthBasis: TextWidthBasis.longestLine,
);
// we use [Row] widget with [Expanded] to take whole the available width
// when the line has not defined an alignment.
//
// this behavior is different when the align is left or justify, because
// if we align the line to the center (example), row will take the whole
// width, creating a visual unexpected behavior where the caret being putted
// at the offset 0 (you can think this like the caret appears at the first char
// of the line when it is aligned at the left side instead appears at the middle
// if the line is centered)
//
// Note:
// this code is subject to changes because we need to get a better solution
// to this implementation

// Use a [Row] with [Expanded] for placeholders in lines without explicit alignment.
// This ensures the placeholder spans the full width, avoiding unexpected alignment issues.
return WidgetSpan(
style: lineStyle,
child: align == TextAlign.end || align == TextAlign.center
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
import 'package:flutter/material.dart' show TextStyle, immutable;
import 'package:flutter/material.dart' show TextStyle;
import 'package:meta/meta.dart' show experimental, immutable;
import '../../../../document/attribute.dart';

typedef PlaceholderComponentBuilder = PlaceholderTextBuilder? Function(
Attribute, TextStyle);

/// Configuration class for defining how placeholders are handled in the editor.
///
/// The `PlaceholderConfig` allows customization of placeholder behavior by
/// providing builders for rendering specific components and defining custom
/// attribute keys that should be recognized during the placeholder build process.
///
/// - [builders]: A map associating placeholder keys with their respective
/// component builders, allowing custom rendering logic.
/// - [customBlockAttributesKeys]: A set of additional attribute keys to include
/// in placeholder processing. By default, only predefined keys are considered.
@experimental
@immutable
class PlaceholderConfig {
const PlaceholderConfig({
Expand All @@ -15,8 +27,7 @@ class PlaceholderConfig {
return const PlaceholderConfig(builders: {});
}

/// These attributes are used with the default ones
/// to let us add placeholder to custom block attributes
/// Add custom keys here to include them in placeholder builds, as external keys are ignored by default.
final Set<String>? customBlockAttributesKeys;
final Map<String, PlaceholderComponentBuilder> builders;

Expand All @@ -36,10 +47,10 @@ class PlaceholderConfig {
@immutable
class PlaceholderTextBuilder {
const PlaceholderTextBuilder({
required this.placeholderText,
required this.text,
required this.style,
});

final String placeholderText;
final String text;
final TextStyle style;
}
17 changes: 11 additions & 6 deletions lib/src/editor/raw_editor/config/raw_editor_config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ class QuillRawEditorConfig {
required this.autoFocus,
required this.characterShortcutEvents,
required this.spaceShortcutEvents,
this.placeholderBuilder,
this.cursorPlaceholderConfig,
@experimental this.placeholderBuilder,
@experimental this.cursorPlaceholderConfig,
@experimental this.onKeyPressed,
this.showCursor = true,
this.scrollable = true,
Expand Down Expand Up @@ -108,10 +108,17 @@ class QuillRawEditorConfig {
///```
final List<CharacterShortcutEvent> characterShortcutEvents;

/// Contains all necessary logic to build the placeholder
/// given for the devs
/// Configuration for displaying placeholders in empty lines or near the cursor.
@experimental
final PlaceholderBuilder? placeholderBuilder;

/// Configures how a placeholder is displayed relative to the cursor.
///
/// This argument specifies the appearance, style, and position of a placeholder
/// shown at the cursor's location in an empty line.
@experimental
final CursorPlaceholderConfig? cursorPlaceholderConfig;

/// Contains all the events that will be handled when
/// space key is pressed
///
Expand All @@ -134,8 +141,6 @@ class QuillRawEditorConfig {
///```
final List<SpaceShortcutEvent> spaceShortcutEvents;

/// This argument configure how will be showed the placeholder at right or left of the cursor
final CursorPlaceholderConfig? cursorPlaceholderConfig;

/// A handler for keys that are pressed when the editor is focused.
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@ import 'package:flutter/material.dart';
const TextStyle _defaultPlaceholderStyle =
TextStyle(color: Colors.grey, fontStyle: FontStyle.italic);

/// This class contains all necessary configurations
/// to show the wanted placeholder at the level of the cursor
/// Configuration for displaying a placeholder near the cursor in a rich text editor.
///
/// You can see this as some Rich Text Editors can contains a feature
/// where if the line is empty and not contains any block style (like
/// header, align, codeblock, etc), then will show a text
/// like (assume that "|" is the cursor): "| start writing"
/// The `CursorPlaceholderConfig` defines the appearance, position, and behavior
/// of a placeholder that is shown when a line is empty and the cursor is present.
/// This feature mimics behavior in some rich text editors where placeholder text
/// (e.g., "Start writing...") appears as a prompt when no content is entered.
@immutable
class CursorPlaceholderConfig {
const CursorPlaceholderConfig({
Expand All @@ -19,6 +18,11 @@ class CursorPlaceholderConfig {
required this.offset,
});

/// Creates a basic configuration for a cursor placeholder with default text and style.
///
/// Parameters:
/// - [textStyle]: An optional custom style for the placeholder text.
/// Defaults to [_defaultPlaceholderStyle] if not provided.
factory CursorPlaceholderConfig.basic({TextStyle? textStyle}) {
return CursorPlaceholderConfig(
text: 'Enter text...',
Expand All @@ -37,17 +41,17 @@ class CursorPlaceholderConfig {
);
}

/// The text that will be showed at the right
/// this text that will be showed at the right
/// or left of the cursor
final String text;

/// The textStyle of the placeholder
/// this is the text style of the placeholder
final TextStyle textStyle;

/// Decides if the placeholder should be showed
final bool show;

/// Decides the offset where will be painted the text
/// The offset position where the placeholder text will be rendered.
final Offset? offset;

@override
Expand Down

0 comments on commit fb5b11a

Please sign in to comment.