Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Added localization support for `mn` (Mongolian, Mongolia)

### Changed

- Search dialog buttons now adopt the toolbar's `QuillIconTheme`; bar height and spacing tightened.

### Fixed

- Whole-word search now matches non-ASCII text (Cyrillic, CJK, accented Latin).

## [11.5.0] - 2025-10-18

### Fixed
Expand Down
7 changes: 5 additions & 2 deletions lib/src/document/document.dart
Original file line number Diff line number Diff line change
Expand Up @@ -362,10 +362,13 @@ class Document {
var index = -1;
final lineText = line.toPlainText();
var pattern = RegExp.escape(substring);
// Dart's `\b`/`\w` are ASCII-only. Use Unicode-aware lookarounds instead.
if (wholeWord) {
pattern = r'\b' + pattern + r'\b';
pattern =
r'(?<![\p{L}\p{N}\p{M}_])' + pattern + r'(?![\p{L}\p{N}\p{M}_])';
}
final searchExpression = RegExp(pattern, caseSensitive: caseSensitive);
final searchExpression =
RegExp(pattern, caseSensitive: caseSensitive, unicode: true);
Comment on lines +365 to +371
while (true) {
index = lineText.indexOf(searchExpression, index + 1);
if (index < 0) {
Expand Down
2 changes: 2 additions & 0 deletions lib/src/toolbar/buttons/search/search_button.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ class QuillToolbarSearchButton extends QuillToolbarBaseButtonStateless {
controller: controller,
dialogTheme: options?.dialogTheme,
searchBarAlignment: options?.searchBarAlignment,
size: iconSize(context) * iconButtonFactor(context),
iconTheme: iconTheme(context),
),
);
}
Expand Down
81 changes: 67 additions & 14 deletions lib/src/toolbar/buttons/search/search_dialog.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import '../../../controller/quill_controller.dart';
import '../../../document/document.dart';
import '../../../document/nodes/leaf.dart';
import '../../../l10n/extensions/localizations_ext.dart';
import '../../simple_toolbar.dart';
import '../../theme/quill_dialog_theme.dart';
import '../../theme/quill_icon_theme.dart';

@immutable
class QuillToolbarSearchDialogChildBuilderExtraOptions {
Expand Down Expand Up @@ -48,6 +50,8 @@ class QuillToolbarSearchDialog extends StatefulWidget {
this.text,
this.childBuilder,
this.searchBarAlignment,
this.size,
this.iconTheme,
super.key,
});

Expand All @@ -56,6 +60,8 @@ class QuillToolbarSearchDialog extends StatefulWidget {
final String? text;
final QuillToolbarSearchDialogChildBuilder? childBuilder;
final AlignmentGeometry? searchBarAlignment;
final double? size;
final QuillIconTheme? iconTheme;

@override
QuillToolbarSearchDialogState createState() =>
Expand Down Expand Up @@ -112,7 +118,7 @@ class QuillToolbarSearchDialogState extends State<QuillToolbarSearchDialog> {
(searchBarAlignment == Alignment.bottomLeft) ||
(searchBarAlignment == Alignment.bottomRight);
final addBottomPadding = searchBarAtBottom && isMobile;
var matchShown = '';
String? matchShown;
if (_text.isNotEmpty) {
if (_offsets.isEmpty) {
matchShown = '0/0';
Expand All @@ -121,29 +127,65 @@ class QuillToolbarSearchDialogState extends State<QuillToolbarSearchDialog> {
}
}

const buttonBox = BoxConstraints.tightFor(width: 40, height: 40);
const buttonStyle = ButtonStyle(
shape: WidgetStatePropertyAll<OutlinedBorder?>(CircleBorder()),
);
final iconTheme = widget.iconTheme?.copyWith(
iconButtonUnselectedData: const IconButtonData(
visualDensity: VisualDensity.compact,
constraints: buttonBox,
style: buttonStyle,
),
iconButtonSelectedData: const IconButtonData(
visualDensity: VisualDensity.compact,
constraints: buttonBox,
style: buttonStyle,
),
) ??
const QuillIconTheme(
Comment on lines +130 to +146
iconButtonUnselectedData: IconButtonData(
visualDensity: VisualDensity.compact,
constraints: buttonBox,
style: buttonStyle,
),
iconButtonSelectedData: IconButtonData(
visualDensity: VisualDensity.compact,
constraints: buttonBox,
style: buttonStyle,
),
);
Comment on lines +134 to +157

final searchBar = Container(
height: addBottomPadding ? 50 : 45,
height: addBottomPadding ? 52 : 40,
padding: addBottomPadding ? const EdgeInsets.only(bottom: 12) : null,
child: Row(
children: [
IconButton(
icon: const Icon(Icons.close),
QuillToolbarIconButton(
tooltip: context.loc.close,
visualDensity: VisualDensity.compact,
icon: Icon(
Icons.close,
size: widget.size,
),
isSelected: false,
onPressed: () {
Navigator.of(context).pop();
},
iconTheme: iconTheme,
),
IconButton(
icon: const Icon(Icons.more_vert),
isSelected: _caseSensitive || _wholeWord,
QuillToolbarIconButton(
tooltip: context.loc.searchSettings,
visualDensity: VisualDensity.compact,
icon: Icon(
Icons.more_vert,
size: widget.size,
),
isSelected: _caseSensitive || _wholeWord,
onPressed: () {
setState(() {
_searchSettingsUnfolded = !_searchSettingsUnfolded;
});
},
iconTheme: iconTheme,
),
Expanded(
child: TextField(
Expand All @@ -152,6 +194,7 @@ class QuillToolbarSearchDialogState extends State<QuillToolbarSearchDialog> {
isDense: true,
suffixText: matchShown,
suffixStyle: widget.dialogTheme?.labelTextStyle,
contentPadding: const EdgeInsets.symmetric(horizontal: 4),
),
autofocus: true,
onChanged: _textChanged,
Expand All @@ -160,22 +203,32 @@ class QuillToolbarSearchDialogState extends State<QuillToolbarSearchDialog> {
controller: _textController,
),
),
IconButton(
icon: const Icon(Icons.keyboard_arrow_up),
QuillToolbarIconButton(
tooltip: context.loc.moveToPreviousOccurrence,
icon: Icon(
Icons.keyboard_arrow_up,
size: widget.size,
),
isSelected: false,
onPressed: (_offsets.isNotEmpty) ? _moveToPrevious : null,
iconTheme: iconTheme,
),
IconButton(
icon: const Icon(Icons.keyboard_arrow_down),
QuillToolbarIconButton(
tooltip: context.loc.moveToNextOccurrence,
icon: Icon(
Icons.keyboard_arrow_down,
size: widget.size,
),
isSelected: false,
onPressed: (_offsets.isNotEmpty) ? _moveToNext : null,
iconTheme: iconTheme,
),
],
),
);

final searchSettings = SizedBox(
height: 45,
height: 40,
child: Row(
children: [
Expanded(
Expand Down
Loading