Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
73e5345
provide a way to globally override the context menu in the text selec…
matthewhendrix Sep 9, 2026
7532101
Merge branch 'main' into global-contextMenuBuilder-override
matthewhendrix Sep 10, 2026
a1ed54a
update to use the material_ui package's text selection theme instead …
matthewhendrix Sep 10, 2026
7462883
address Gemini's PR feedback by simplifying the override logic in ser…
matthewhendrix Sep 10, 2026
8f51f46
simplify context menu builder lerp
matthewhendrix Sep 10, 2026
f8d4b3a
update the doc comment and add a test for _lerpContextMenuBuilder
matthewhendrix Sep 10, 2026
9567b27
Merge branch 'main' into global-contextMenuBuilder-override
matthewhendrix Sep 10, 2026
a2d1265
address comments - remove the now unused _defaultContextMenuBuilder f…
matthewhendrix Sep 10, 2026
cd43d25
fix test
matthewhendrix Sep 10, 2026
5e03c82
add change log pending entry
matthewhendrix Sep 11, 2026
d18c36c
add a . to the end
matthewhendrix Sep 11, 2026
b4c1130
grammar
matthewhendrix Sep 11, 2026
5df9d9c
fix static analysis failures - unused imports and redudant args
matthewhendrix Sep 11, 2026
9a0692a
Merge branch 'main' into global-contextMenuBuilder-override
matthewhendrix Sep 11, 2026
13948b1
update the version type to minor, attempt to format test without touc…
matthewhendrix Sep 11, 2026
ddd102f
fix test failure when running on chrome platform
matthewhendrix Sep 11, 2026
072e1e4
Default to the built-in default context menu but allow an explicit nu…
matthewhendrix Sep 17, 2026
e0fce8d
make SearchAnchor.bar's contextMenuBuilder nullable and a test for th…
matthewhendrix Sep 17, 2026
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
13 changes: 10 additions & 3 deletions packages/material_ui/lib/src/search_anchor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import 'material_state.dart';
import 'search_bar_theme.dart';
import 'search_view_theme.dart';
import 'text_field.dart';
import 'text_selection_theme.dart';
import 'text_theme.dart';
import 'theme.dart';

Expand Down Expand Up @@ -241,7 +242,7 @@ class SearchAnchor extends StatefulWidget {
TextInputAction? textInputAction,
TextInputType? keyboardType,
EdgeInsets scrollPadding,
EditableTextContextMenuBuilder contextMenuBuilder,
EditableTextContextMenuBuilder? contextMenuBuilder,
bool enabled,
SmartDashesType? smartDashesType,
SmartQuotesType? smartQuotesType,
Expand Down Expand Up @@ -1309,7 +1310,7 @@ class _SearchAnchorWithSearchBar extends SearchAnchor {
super.textInputAction,
super.keyboardType,
EdgeInsets scrollPadding = const EdgeInsets.all(20.0),
EditableTextContextMenuBuilder contextMenuBuilder = SearchBar._defaultContextMenuBuilder,
EditableTextContextMenuBuilder? contextMenuBuilder = SearchBar._defaultContextMenuBuilder,
Comment thread
matthewhendrix marked this conversation as resolved.
super.enabled,
super.smartDashesType,
super.smartQuotesType,
Expand Down Expand Up @@ -1812,6 +1813,12 @@ class _SearchBarState extends State<SearchBar> {
)
.toList();

final EditableTextContextMenuBuilder? resolvedContextMenuBuilder =
widget.contextMenuBuilder == SearchBar._defaultContextMenuBuilder
? (TextSelectionTheme.of(context).contextMenuBuilder ??
SearchBar._defaultContextMenuBuilder)
: widget.contextMenuBuilder;

return ConstrainedBox(
constraints: widget.constraints ?? searchBarTheme.constraints ?? defaults.constraints!,
child: Opacity(
Expand Down Expand Up @@ -1875,7 +1882,7 @@ class _SearchBarState extends State<SearchBar> {
textInputAction: widget.textInputAction,
keyboardType: widget.keyboardType,
scrollPadding: widget.scrollPadding,
contextMenuBuilder: widget.contextMenuBuilder,
contextMenuBuilder: resolvedContextMenuBuilder,
smartDashesType: widget.smartDashesType,
smartQuotesType: widget.smartQuotesType,
),
Expand Down
11 changes: 10 additions & 1 deletion packages/material_ui/lib/src/selectable_text.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import 'adaptive_text_selection_toolbar.dart';
import 'desktop_text_selection.dart';
import 'magnifier.dart';
import 'text_selection.dart';
import 'text_selection_theme.dart';
import 'theme.dart';

// Examples can assume:
Expand Down Expand Up @@ -757,12 +758,20 @@ class _SelectableTextState extends State<SelectableText>
widget.style ?? _controller._textSpan.style,
);
}

final TextScaler? effectiveScaler =
widget.textScaler ??
switch (widget.textScaleFactor) {
null => null,
final double textScaleFactor => TextScaler.linear(textScaleFactor),
};

final EditableTextContextMenuBuilder? resolvedContextMenuBuilder =
widget.contextMenuBuilder == SelectableText._defaultContextMenuBuilder
? (TextSelectionTheme.of(context).contextMenuBuilder ??
SelectableText._defaultContextMenuBuilder)
: widget.contextMenuBuilder;

final Widget child = RepaintBoundary(
child: EditableText(
key: editableTextKey,
Expand Down Expand Up @@ -805,7 +814,7 @@ class _SelectableTextState extends State<SelectableText>
scrollPhysics: widget.scrollPhysics,
scrollBehavior: widget.scrollBehavior,
autofillHints: null,
contextMenuBuilder: widget.contextMenuBuilder,
contextMenuBuilder: resolvedContextMenuBuilder,
),
);

Expand Down
11 changes: 10 additions & 1 deletion packages/material_ui/lib/src/text_field.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import 'material_state.dart';
import 'selectable_text.dart' show iOSHorizontalOffset;
import 'spell_check_suggestions_toolbar.dart';
import 'text_selection.dart';
import 'text_selection_theme.dart';
import 'theme.dart';

export 'package:flutter/services.dart'
Expand Down Expand Up @@ -888,6 +889,8 @@ class TextField extends StatefulWidget {
/// * [AdaptiveTextSelectionToolbar], which is built by default.
/// * [BrowserContextMenu], which allows the browser's context menu on web to
/// be disabled and Flutter-rendered context menus to appear.
/// * [ThemeData.textSelectionTheme], which provides a way to override the
/// default context menu.
final EditableTextContextMenuBuilder? contextMenuBuilder;

/// Determine whether this text field can request the primary focus.
Expand Down Expand Up @@ -1696,6 +1699,12 @@ class _TextFieldState extends State<TextField>
};
}

final EditableTextContextMenuBuilder? resolvedContextMenuBuilder =
widget.contextMenuBuilder == TextField._defaultContextMenuBuilder
? (TextSelectionTheme.of(context).contextMenuBuilder ??
TextField._defaultContextMenuBuilder)
: widget.contextMenuBuilder;

Widget child = RepaintBoundary(
child: UnmanagedRestorationScope(
bucket: bucket,
Expand Down Expand Up @@ -1767,7 +1776,7 @@ class _TextFieldState extends State<TextField>
enableIMEPersonalizedLearning: widget.enableIMEPersonalizedLearning,
enableInlinePrediction: widget.enableInlinePrediction,
contentInsertionConfiguration: widget.contentInsertionConfiguration,
contextMenuBuilder: widget.contextMenuBuilder,
contextMenuBuilder: resolvedContextMenuBuilder,
spellCheckConfiguration: spellCheckConfiguration,
magnifierConfiguration:
widget.magnifierConfiguration ?? TextMagnifier.adaptiveMagnifierConfiguration,
Expand Down
9 changes: 8 additions & 1 deletion packages/material_ui/lib/src/text_form_field.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import 'adaptive_text_selection_toolbar.dart';
import 'input_decorator.dart';
import 'material_state.dart';
import 'text_field.dart';
import 'text_selection_theme.dart';

export 'package:flutter/services.dart' show SmartDashesType, SmartQuotesType;

Expand Down Expand Up @@ -246,6 +247,12 @@ class TextFormField extends FormField<String> {
onChanged?.call(value);
}

final EditableTextContextMenuBuilder? resolvedContextMenuBuilder =
contextMenuBuilder == _defaultContextMenuBuilder
? (TextSelectionTheme.of(field.context).contextMenuBuilder ??
_defaultContextMenuBuilder)
: contextMenuBuilder;

return UnmanagedRestorationScope(
bucket: field.bucket,
child: TextField(
Expand Down Expand Up @@ -309,7 +316,7 @@ class TextFormField extends FormField<String> {
scrollController: scrollController,
enableIMEPersonalizedLearning: enableIMEPersonalizedLearning,
mouseCursor: mouseCursor,
contextMenuBuilder: contextMenuBuilder,
contextMenuBuilder: resolvedContextMenuBuilder,
spellCheckConfiguration: spellCheckConfiguration,
magnifierConfiguration: magnifierConfiguration,
undoController: undoController,
Expand Down
45 changes: 42 additions & 3 deletions packages/material_ui/lib/src/text_selection_theme.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,12 @@ import 'theme.dart';
@immutable
class TextSelectionThemeData with Diagnosticable {
/// Creates the set of properties used to configure [TextField]s.
const TextSelectionThemeData({this.cursorColor, this.selectionColor, this.selectionHandleColor});
const TextSelectionThemeData({
this.cursorColor,
this.selectionColor,
this.selectionHandleColor,
this.contextMenuBuilder,
});

/// The color of the cursor in the text field.
///
Expand All @@ -58,17 +63,31 @@ class TextSelectionThemeData with Diagnosticable {
/// containing your [TextField] or [SelectableText] with a [CupertinoTheme].
final Color? selectionHandleColor;

/// {@macro flutter.widgets.EditableText.contextMenuBuilder}
///
/// Provides a way to globally override the default Flutter context menu for
/// text fields and selectable text.
///
/// See also:
///
/// * [AdaptiveTextSelectionToolbar], which is built by default.
/// * [BrowserContextMenu], which allows the browser's context menu on web to
/// be disabled and Flutter-rendered context menus to appear.
final EditableTextContextMenuBuilder? contextMenuBuilder;

/// Creates a copy of this object with the given fields replaced with the
/// specified values.
TextSelectionThemeData copyWith({
Color? cursorColor,
Color? selectionColor,
Color? selectionHandleColor,
EditableTextContextMenuBuilder? contextMenuBuilder,
}) {
return TextSelectionThemeData(
cursorColor: cursorColor ?? this.cursorColor,
selectionColor: selectionColor ?? this.selectionColor,
selectionHandleColor: selectionHandleColor ?? this.selectionHandleColor,
contextMenuBuilder: contextMenuBuilder ?? this.contextMenuBuilder,
);
}

Expand All @@ -89,11 +108,23 @@ class TextSelectionThemeData with Diagnosticable {
cursorColor: Color.lerp(a?.cursorColor, b?.cursorColor, t),
selectionColor: Color.lerp(a?.selectionColor, b?.selectionColor, t),
selectionHandleColor: Color.lerp(a?.selectionHandleColor, b?.selectionHandleColor, t),
contextMenuBuilder: _lerpContextMenuBuilder(a, b, t),
);
}

/// Linear interpolation doesn't make much sense between two context menu builders,
/// so instead of interpolating, choose one based on the value of 't'.
static EditableTextContextMenuBuilder? _lerpContextMenuBuilder(
TextSelectionThemeData? a,
TextSelectionThemeData? b,
double t,
) {
return t < 0.5 ? a?.contextMenuBuilder : b?.contextMenuBuilder;
}
Comment thread
matthewhendrix marked this conversation as resolved.

@override
int get hashCode => Object.hash(cursorColor, selectionColor, selectionHandleColor);
int get hashCode =>
Object.hash(cursorColor, selectionColor, selectionHandleColor, contextMenuBuilder);

@override
bool operator ==(Object other) {
Expand All @@ -106,7 +137,8 @@ class TextSelectionThemeData with Diagnosticable {
return other is TextSelectionThemeData &&
other.cursorColor == cursorColor &&
other.selectionColor == selectionColor &&
other.selectionHandleColor == selectionHandleColor;
other.selectionHandleColor == selectionHandleColor &&
other.contextMenuBuilder == contextMenuBuilder;
}

@override
Expand All @@ -115,6 +147,13 @@ class TextSelectionThemeData with Diagnosticable {
properties.add(ColorProperty('cursorColor', cursorColor, defaultValue: null));
properties.add(ColorProperty('selectionColor', selectionColor, defaultValue: null));
properties.add(ColorProperty('selectionHandleColor', selectionHandleColor, defaultValue: null));
properties.add(
DiagnosticsProperty<EditableTextContextMenuBuilder?>(
'contextMenuBuilder',
contextMenuBuilder,
defaultValue: null,
),
);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
changelog: |
- Adds a global context menu builder override to TextSelectionThemeData.
version: minor
Loading