diff --git a/lib/src/editor/config/editor_config.dart b/lib/src/editor/config/editor_config.dart index 73f2f0654..3774f3aa3 100644 --- a/lib/src/editor/config/editor_config.dart +++ b/lib/src/editor/config/editor_config.dart @@ -293,7 +293,7 @@ class QuillEditorConfig { final double? maxContentWidth; /// Allows to override [DefaultStyles]. - final DefaultStyles? customStyles; + final DefaultStylesOverride? customStyles; /// Whether this editor's height will be sized to fill its parent. /// @@ -502,7 +502,7 @@ class QuillEditorConfig { double? minHeight, double? maxHeight, double? maxContentWidth, - DefaultStyles? customStyles, + DefaultStylesOverride? customStyles, bool? expands, TextCapitalization? textCapitalization, Brightness? keyboardAppearance, diff --git a/lib/src/editor/raw_editor/builders/leading_block_builder.dart b/lib/src/editor/raw_editor/builders/leading_block_builder.dart index f93b76f3d..27385cb83 100644 --- a/lib/src/editor/raw_editor/builders/leading_block_builder.dart +++ b/lib/src/editor/raw_editor/builders/leading_block_builder.dart @@ -23,6 +23,7 @@ class LeadingConfig { required this.value, required this.onCheckboxTap, required this.attrs, + this.listPointAlignment, this.withDot = true, this.index, this.lineSize, @@ -40,6 +41,7 @@ class LeadingConfig { final TextStyle? style; final double? width; final double? padding; + final AlignmentDirectional? listPointAlignment; // these values are used if the leading is from a check list final QuillCheckboxBuilder? uiBuilder; diff --git a/lib/src/editor/raw_editor/config/raw_editor_config.dart b/lib/src/editor/raw_editor/config/raw_editor_config.dart index f2eda163c..47bdd497b 100644 --- a/lib/src/editor/raw_editor/config/raw_editor_config.dart +++ b/lib/src/editor/raw_editor/config/raw_editor_config.dart @@ -285,8 +285,8 @@ class QuillRawEditorConfig { /// horizontally centered. This is mostly useful on devices with wide screens. final double? maxContentWidth; - /// Allows to override [DefaultStyles]. - final DefaultStyles? customStyles; + /// Allows to override [DefaultStylesOverride]. + final DefaultStylesOverride? customStyles; /// Whether this widget's height will be sized to fill its parent. /// diff --git a/lib/src/editor/raw_editor/raw_editor_state.dart b/lib/src/editor/raw_editor/raw_editor_state.dart index 2c5cdf04d..250e2295c 100644 --- a/lib/src/editor/raw_editor/raw_editor_state.dart +++ b/lib/src/editor/raw_editor/raw_editor_state.dart @@ -892,13 +892,15 @@ class QuillRawEditorState extends EditorState void didChangeDependencies() { super.didChangeDependencies(); final parentStyles = QuillStyles.getStyles(context, true); - final defaultStyles = DefaultStyles.getInstance(context); + final stylesOverride = widget.config.customStyles; + final defaultStyles = DefaultStyles.getInstance(context, + baseStyleOverride: stylesOverride?.defaultTextStyle); _styles = (parentStyles != null) ? defaultStyles.merge(parentStyles) : defaultStyles; if (widget.config.customStyles != null) { - _styles = _styles!.merge(widget.config.customStyles!); + _styles = _styles!.applyOverrides(widget.config.customStyles!); } _requestAutoFocusIfShould(); @@ -951,9 +953,10 @@ class QuillRawEditorState extends EditorState } } + final stylesOverride = widget.config.customStyles; // in case customStyles changed in new widget - if (widget.config.customStyles != null) { - _styles = _styles!.merge(widget.config.customStyles!); + if (stylesOverride != null) { + _styles = _styles!.applyOverrides(stylesOverride); } if (widget.config.actionConfiguration != diff --git a/lib/src/editor/style_widgets/bullet_point.dart b/lib/src/editor/style_widgets/bullet_point.dart index abba0d9d0..f4d2e0fba 100644 --- a/lib/src/editor/style_widgets/bullet_point.dart +++ b/lib/src/editor/style_widgets/bullet_point.dart @@ -7,19 +7,21 @@ class QuillBulletPoint extends StatelessWidget { this.padding = 0, this.backgroundColor, this.textAlign, + AlignmentDirectional? alignment, super.key, - }); + }) : alignment = alignment ?? AlignmentDirectional.topEnd; final TextStyle style; final double width; final double padding; final Color? backgroundColor; final TextAlign? textAlign; + final AlignmentDirectional alignment; @override Widget build(BuildContext context) { return Container( - alignment: AlignmentDirectional.topEnd, + alignment: alignment, width: width, padding: EdgeInsetsDirectional.only(end: padding), color: backgroundColor, diff --git a/lib/src/editor/style_widgets/number_point.dart b/lib/src/editor/style_widgets/number_point.dart index fadba673e..3d8a24cf9 100644 --- a/lib/src/editor/style_widgets/number_point.dart +++ b/lib/src/editor/style_widgets/number_point.dart @@ -12,9 +12,10 @@ class QuillNumberPoint extends StatelessWidget { this.textAlign, this.withDot = true, this.padding = 0.0, - super.key, this.backgroundColor, - }); + AlignmentDirectional? alignment, + super.key, + }) : alignment = alignment ?? AlignmentDirectional.topEnd; final String index; final Map indentLevelCounts; @@ -26,12 +27,13 @@ class QuillNumberPoint extends StatelessWidget { final double padding; final Color? backgroundColor; final TextAlign? textAlign; + final AlignmentDirectional alignment; @override Widget build(BuildContext context) { if (!attrs.containsKey(Attribute.indent.key) && indentLevelCounts.isEmpty) { return Container( - alignment: AlignmentDirectional.topEnd, + alignment: alignment, width: width, padding: EdgeInsetsDirectional.only(end: padding), color: backgroundColor, @@ -43,7 +45,7 @@ class QuillNumberPoint extends StatelessWidget { ); } return Container( - alignment: AlignmentDirectional.topEnd, + alignment: alignment, width: width, padding: EdgeInsetsDirectional.only(end: padding), color: backgroundColor, diff --git a/lib/src/editor/widgets/default_leading_components/bullet_point_leading.dart b/lib/src/editor/widgets/default_leading_components/bullet_point_leading.dart index 57f823a08..ce418face 100644 --- a/lib/src/editor/widgets/default_leading_components/bullet_point_leading.dart +++ b/lib/src/editor/widgets/default_leading_components/bullet_point_leading.dart @@ -6,4 +6,5 @@ Widget bulletPointLeading(LeadingConfig config) => QuillBulletPoint( style: config.style!, width: config.width!, padding: config.padding!, + alignment: config.listPointAlignment, ); diff --git a/lib/src/editor/widgets/default_leading_components/number_point_leading.dart b/lib/src/editor/widgets/default_leading_components/number_point_leading.dart index 02aba7932..fd2a711f7 100644 --- a/lib/src/editor/widgets/default_leading_components/number_point_leading.dart +++ b/lib/src/editor/widgets/default_leading_components/number_point_leading.dart @@ -10,4 +10,5 @@ Widget numberPointLeading(LeadingConfig config) => QuillNumberPoint( attrs: config.attrs, width: config.width!, padding: config.padding!, + alignment: config.listPointAlignment, ); diff --git a/lib/src/editor/widgets/default_styles.dart b/lib/src/editor/widgets/default_styles.dart index 87636f63c..be60bdb58 100644 --- a/lib/src/editor/widgets/default_styles.dart +++ b/lib/src/editor/widgets/default_styles.dart @@ -181,11 +181,13 @@ class DefaultListBlockStyle extends DefaultTextBlockStyle { this.indentWidthBuilder = TextBlockUtils.defaultIndentWidthBuilder, this.numberPointWidthBuilder = TextBlockUtils.defaultNumberPointWidthBuilder, + this.pointAlignment, }); final QuillCheckboxBuilder? checkboxUIBuilder; final LeadingBlockIndentWidth indentWidthBuilder; final LeadingBlockNumberPointWidth numberPointWidthBuilder; + final AlignmentDirectional? pointAlignment; @override DefaultListBlockStyle copyWith({ @@ -197,6 +199,7 @@ class DefaultListBlockStyle extends DefaultTextBlockStyle { QuillCheckboxBuilder? checkboxUIBuilder, LeadingBlockIndentWidth? indentWidthBuilder, LeadingBlockNumberPointWidth? numberPointWidthBuilder, + AlignmentDirectional? pointAlignment, }) { return DefaultListBlockStyle( style ?? this.style, @@ -207,7 +210,8 @@ class DefaultListBlockStyle extends DefaultTextBlockStyle { checkboxUIBuilder ?? this.checkboxUIBuilder, indentWidthBuilder: indentWidthBuilder ?? this.indentWidthBuilder, numberPointWidthBuilder: - numberPointWidthBuilder ?? this.numberPointWidthBuilder, + numberPointWidthBuilder ?? this.numberPointWidthBuilder, + pointAlignment: pointAlignment ?? this.pointAlignment, ); } } @@ -226,6 +230,7 @@ class DefaultStyles { this.lineHeightTight, this.lineHeightOneAndHalf, this.lineHeightDouble, + this.defaultTextStyle, this.bold, this.subscript, this.superscript, @@ -260,6 +265,7 @@ class DefaultStyles { final DefaultTextBlockStyle? lineHeightTight; final DefaultTextBlockStyle? lineHeightOneAndHalf; final DefaultTextBlockStyle? lineHeightDouble; + final TextStyle? defaultTextStyle; final TextStyle? bold; final TextStyle? subscript; final TextStyle? superscript; @@ -286,14 +292,17 @@ class DefaultStyles { /// Custom palette of colors final Map? palette; - static DefaultStyles getInstance(BuildContext context) { + static DefaultStyles getInstance(BuildContext context, + {ValueOverride? baseStyleOverride}) { final themeData = Theme.of(context); final defaultTextStyle = DefaultTextStyle.of(context); - final baseStyle = defaultTextStyle.style.copyWith( - fontSize: 16, - height: 1.15, - decoration: TextDecoration.none, - ); + final baseStyle = defaultTextStyle.style + .copyWith( + fontSize: 16, + height: 1.15, + decoration: TextDecoration.none, + ) + .override(baseStyleOverride); const baseHorizontalSpacing = HorizontalSpacing(0, 0); const baseVerticalSpacing = VerticalSpacing(6, 0); final fontFamily = themeData.isCupertino ? 'Menlo' : 'Roboto Mono'; @@ -306,9 +315,9 @@ class DefaultStyles { return DefaultStyles( h1: DefaultTextBlockStyle( - defaultTextStyle.style.copyWith( + baseStyle.copyWith( fontSize: 34, - color: defaultTextStyle.style.color, + color: baseStyle.color, letterSpacing: -0.5, height: 1.083, fontWeight: FontWeight.bold, @@ -319,9 +328,9 @@ class DefaultStyles { VerticalSpacing.zero, null), h2: DefaultTextBlockStyle( - defaultTextStyle.style.copyWith( + baseStyle.copyWith( fontSize: 30, - color: defaultTextStyle.style.color, + color: baseStyle.color, letterSpacing: -0.8, height: 1.067, fontWeight: FontWeight.bold, @@ -332,9 +341,9 @@ class DefaultStyles { VerticalSpacing.zero, null), h3: DefaultTextBlockStyle( - defaultTextStyle.style.copyWith( + baseStyle.copyWith( fontSize: 24, - color: defaultTextStyle.style.color, + color: baseStyle.color, letterSpacing: -0.5, height: 1.083, fontWeight: FontWeight.bold, @@ -346,9 +355,9 @@ class DefaultStyles { null, ), h4: DefaultTextBlockStyle( - defaultTextStyle.style.copyWith( + baseStyle.copyWith( fontSize: 20, - color: defaultTextStyle.style.color, + color: baseStyle.color, letterSpacing: -0.4, height: 1.1, fontWeight: FontWeight.bold, @@ -360,9 +369,9 @@ class DefaultStyles { null, ), h5: DefaultTextBlockStyle( - defaultTextStyle.style.copyWith( + baseStyle.copyWith( fontSize: 18, - color: defaultTextStyle.style.color, + color: baseStyle.color, letterSpacing: -0.2, height: 1.11, fontWeight: FontWeight.bold, @@ -374,9 +383,9 @@ class DefaultStyles { null, ), h6: DefaultTextBlockStyle( - defaultTextStyle.style.copyWith( + baseStyle.copyWith( fontSize: 16, - color: defaultTextStyle.style.color, + color: baseStyle.color, letterSpacing: -0.1, height: 1.125, fontWeight: FontWeight.bold, @@ -461,7 +470,7 @@ class DefaultStyles { decoration: TextDecoration.underline, ), placeHolder: DefaultTextBlockStyle( - defaultTextStyle.style.copyWith( + baseStyle.copyWith( fontSize: 20, height: 1.5, color: Colors.grey.withValues(alpha: 0.6), @@ -566,4 +575,131 @@ class DefaultStyles { palette: other.palette ?? palette, ); } + + /// Applies overrides to this DefaultStyles instance and returns a new instance. + DefaultStyles applyOverrides(DefaultStylesOverride overrides) { + return DefaultStyles( + h1: h1?.override(overrides.h1), + h2: h2?.override(overrides.h2), + h3: h3?.override(overrides.h3), + h4: h4?.override(overrides.h4), + h5: h5?.override(overrides.h5), + h6: h6?.override(overrides.h6), + paragraph: paragraph?.override(overrides.paragraph), + lineHeightNormal: lineHeightNormal?.override(overrides.lineHeightNormal), + lineHeightTight: lineHeightTight?.override(overrides.lineHeightTight), + lineHeightOneAndHalf: + lineHeightOneAndHalf?.override(overrides.lineHeightOneAndHalf), + lineHeightDouble: lineHeightDouble?.override(overrides.lineHeightDouble), + defaultTextStyle: defaultTextStyle?.override(overrides.defaultTextStyle), + bold: bold?.override(overrides.bold), + subscript: subscript?.override(overrides.subscript), + superscript: superscript?.override(overrides.superscript), + italic: italic?.override(overrides.italic), + small: small?.override(overrides.small), + underline: underline?.override(overrides.underline), + strikeThrough: strikeThrough?.override(overrides.strikeThrough), + inlineCode: inlineCode?.override(overrides.inlineCode), + link: link?.override(overrides.link), + color: color?.override(overrides.color), + placeHolder: placeHolder?.override(overrides.placeHolder), + lists: lists?.override(overrides.lists), + quote: quote?.override(overrides.quote), + code: code?.override(overrides.code), + indent: indent?.override(overrides.indent), + align: align?.override(overrides.align), + leading: leading?.override(overrides.leading), + sizeSmall: sizeSmall?.override(overrides.sizeSmall), + sizeLarge: sizeLarge?.override(overrides.sizeLarge), + sizeHuge: sizeHuge?.override(overrides.sizeHuge), + palette: palette?.override(overrides.palette), + ); + } +} + +typedef ValueOverride = T Function(T value); + +class DefaultStylesOverride { + const DefaultStylesOverride({ + this.h1, + this.h2, + this.h3, + this.h4, + this.h5, + this.h6, + this.paragraph, + this.lineHeightNormal, + this.lineHeightTight, + this.lineHeightOneAndHalf, + this.lineHeightDouble, + this.defaultTextStyle, + this.bold, + this.subscript, + this.superscript, + this.italic, + this.small, + this.underline, + this.strikeThrough, + this.inlineCode, + this.link, + this.color, + this.placeHolder, + this.lists, + this.quote, + this.code, + this.indent, + this.align, + this.leading, + this.sizeSmall, + this.sizeLarge, + this.sizeHuge, + this.palette, + }); + + final ValueOverride? h1; + final ValueOverride? h2; + final ValueOverride? h3; + final ValueOverride? h4; + final ValueOverride? h5; + final ValueOverride? h6; + final ValueOverride? paragraph; + final ValueOverride? lineHeightNormal; + final ValueOverride? lineHeightTight; + final ValueOverride? lineHeightOneAndHalf; + final ValueOverride? lineHeightDouble; + final ValueOverride? defaultTextStyle; + final ValueOverride? bold; + final ValueOverride? subscript; + final ValueOverride? superscript; + final ValueOverride? italic; + final ValueOverride? small; + final ValueOverride? underline; + final ValueOverride? strikeThrough; + + /// Theme of inline code. + final ValueOverride? inlineCode; + final ValueOverride? sizeSmall; // 'small' + final ValueOverride? sizeLarge; // 'large' + final ValueOverride? sizeHuge; // 'huge' + final ValueOverride? link; + final ValueOverride? color; + final ValueOverride? placeHolder; + final ValueOverride? lists; + final ValueOverride? quote; + final ValueOverride? code; + final ValueOverride? indent; + final ValueOverride? align; + final ValueOverride? leading; + + /// Custom palette of colors + final ValueOverride>? palette; +} + +extension _DefaultStylesOverrideExtension on T { + T override(ValueOverride? override) { + if (override != null) { + return override(this); + } + return this; + } } diff --git a/lib/src/editor/widgets/text/text_block.dart b/lib/src/editor/widgets/text/text_block.dart index 01a4208e4..314333fcb 100644 --- a/lib/src/editor/widgets/text/text_block.dart +++ b/lib/src/editor/widgets/text/text_block.dart @@ -275,6 +275,9 @@ class EditableTextBlock extends StatelessWidget { index: isOrdered || isCodeBlock ? index : null, count: count, enabled: !isCheck ? null : !(checkBoxReadOnly ?? readOnly), + listPointAlignment: isOrdered || isUnordered || isCodeBlock + ? defaultStyles.lists?.pointAlignment + : null, style: () { if (isOrdered) { return defaultStyles.leading!.style.copyWith(