1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5/// @docImport 'default_text_editing_shortcuts.dart';
6/// @docImport 'editable_text.dart';
7library;
8
9import 'package:flutter/services.dart';
10
11import 'actions.dart';
12import 'basic.dart';
13import 'focus_manager.dart';
14
15/// An [Intent] to send the event straight to the engine.
16///
17/// See also:
18///
19/// * [DefaultTextEditingShortcuts], which triggers this [Intent].
20class DoNothingAndStopPropagationTextIntent extends Intent {
21 /// Creates an instance of [DoNothingAndStopPropagationTextIntent].
22 const DoNothingAndStopPropagationTextIntent();
23}
24
25/// A text editing related [Intent] that performs an operation towards a given
26/// direction of the current caret location.
27abstract class DirectionalTextEditingIntent extends Intent {
28 /// Creates a [DirectionalTextEditingIntent].
29 const DirectionalTextEditingIntent(this.forward);
30
31 /// Whether the input field, if applicable, should perform the text editing
32 /// operation from the current caret location towards the end of the document.
33 ///
34 /// Unless otherwise specified by the recipient of this intent, this parameter
35 /// uses the logical order of characters in the string to determine the
36 /// direction, and is not affected by the writing direction of the text.
37 final bool forward;
38}
39
40/// Deletes the character before or after the caret location, based on whether
41/// `forward` is true.
42///
43/// {@template flutter.widgets.TextEditingIntents.logicalOrder}
44/// {@endtemplate}
45///
46/// Typically a text field will not respond to this intent if it has no active
47/// caret ([TextSelection.isValid] is false for the current selection).
48class DeleteCharacterIntent extends DirectionalTextEditingIntent {
49 /// Creates a [DeleteCharacterIntent].
50 const DeleteCharacterIntent({required bool forward}) : super(forward);
51}
52
53/// Deletes from the current caret location to the previous or next word
54/// boundary, based on whether `forward` is true.
55class DeleteToNextWordBoundaryIntent extends DirectionalTextEditingIntent {
56 /// Creates a [DeleteToNextWordBoundaryIntent].
57 const DeleteToNextWordBoundaryIntent({required bool forward}) : super(forward);
58}
59
60/// Deletes from the current caret location to the previous or next soft or hard
61/// line break, based on whether `forward` is true.
62class DeleteToLineBreakIntent extends DirectionalTextEditingIntent {
63 /// Creates a [DeleteToLineBreakIntent].
64 const DeleteToLineBreakIntent({required bool forward}) : super(forward);
65}
66
67/// A [DirectionalTextEditingIntent] that moves the caret or the selection to a
68/// new location.
69abstract class DirectionalCaretMovementIntent extends DirectionalTextEditingIntent {
70 /// Creates a [DirectionalCaretMovementIntent].
71 const DirectionalCaretMovementIntent(
72 super.forward,
73 this.collapseSelection, [
74 this.collapseAtReversal = false,
75 this.continuesAtWrap = false,
76 ]) : assert(!collapseSelection || !collapseAtReversal);
77
78 /// Whether this [Intent] should make the selection collapsed (so it becomes a
79 /// caret), after the movement.
80 ///
81 /// When [collapseSelection] is false, the input field typically only moves
82 /// the current [TextSelection.extent] to the new location, while maintains
83 /// the current [TextSelection.base] location.
84 ///
85 /// When [collapseSelection] is true, the input field typically should move
86 /// both the [TextSelection.base] and the [TextSelection.extent] to the new
87 /// location.
88 final bool collapseSelection;
89
90 /// Whether to collapse the selection when it would otherwise reverse order.
91 ///
92 /// For example, consider when forward is true and the extent is before the
93 /// base. If collapseAtReversal is true, then this will cause the selection to
94 /// collapse at the base. If it's false, then the extent will be placed at the
95 /// linebreak, reversing the order of base and offset.
96 ///
97 /// Cannot be true when collapseSelection is true.
98 final bool collapseAtReversal;
99
100 /// Whether or not to continue to the next line at a wordwrap.
101 ///
102 /// If true, when an [Intent] to go to the beginning/end of a wordwrapped line
103 /// is received and the selection is already at the beginning/end of the line,
104 /// then the selection will be moved to the next/previous line. If false, the
105 /// selection will remain at the wordwrap.
106 final bool continuesAtWrap;
107}
108
109/// Extends, or moves the current selection from the current
110/// [TextSelection.extent] position to the previous or the next character
111/// boundary.
112class ExtendSelectionByCharacterIntent extends DirectionalCaretMovementIntent {
113 /// Creates an [ExtendSelectionByCharacterIntent].
114 const ExtendSelectionByCharacterIntent({required bool forward, required bool collapseSelection})
115 : super(forward, collapseSelection);
116}
117
118/// Extends, or moves the current selection from the current
119/// [TextSelection.extent] position to the previous or the next word
120/// boundary.
121class ExtendSelectionToNextWordBoundaryIntent extends DirectionalCaretMovementIntent {
122 /// Creates an [ExtendSelectionToNextWordBoundaryIntent].
123 const ExtendSelectionToNextWordBoundaryIntent({
124 required bool forward,
125 required bool collapseSelection,
126 }) : super(forward, collapseSelection);
127}
128
129/// Extends, or moves the current selection from the current
130/// [TextSelection.extent] position to the previous or the next word
131/// boundary, or the [TextSelection.base] position if it's closer in the move
132/// direction.
133///
134/// This [Intent] typically has the same effect as an
135/// [ExtendSelectionToNextWordBoundaryIntent], except it collapses the selection
136/// when the order of [TextSelection.base] and [TextSelection.extent] would
137/// reverse.
138///
139/// This is typically only used on MacOS.
140class ExtendSelectionToNextWordBoundaryOrCaretLocationIntent
141 extends DirectionalCaretMovementIntent {
142 /// Creates an [ExtendSelectionToNextWordBoundaryOrCaretLocationIntent].
143 const ExtendSelectionToNextWordBoundaryOrCaretLocationIntent({required bool forward})
144 : super(forward, false, true);
145}
146
147/// Expands the current selection to the document boundary in the direction
148/// given by [forward].
149///
150/// Unlike [ExpandSelectionToLineBreakIntent], the extent will be moved, which
151/// matches the behavior on MacOS.
152///
153/// See also:
154///
155/// [ExtendSelectionToDocumentBoundaryIntent], which is similar but always
156/// moves the extent.
157class ExpandSelectionToDocumentBoundaryIntent extends DirectionalCaretMovementIntent {
158 /// Creates an [ExpandSelectionToDocumentBoundaryIntent].
159 const ExpandSelectionToDocumentBoundaryIntent({required bool forward}) : super(forward, false);
160}
161
162/// Expands the current selection to the closest line break in the direction
163/// given by [forward].
164///
165/// Either the base or extent can move, whichever is closer to the line break.
166/// The selection will never shrink.
167///
168/// This behavior is common on MacOS.
169///
170/// See also:
171///
172/// [ExtendSelectionToLineBreakIntent], which is similar but always moves the
173/// extent.
174class ExpandSelectionToLineBreakIntent extends DirectionalCaretMovementIntent {
175 /// Creates an [ExpandSelectionToLineBreakIntent].
176 const ExpandSelectionToLineBreakIntent({required bool forward}) : super(forward, false);
177}
178
179/// Extends, or moves the current selection from the current
180/// [TextSelection.extent] position to the closest line break in the direction
181/// given by [forward].
182///
183/// See also:
184///
185/// [ExpandSelectionToLineBreakIntent], which is similar but always increases
186/// the size of the selection.
187class ExtendSelectionToLineBreakIntent extends DirectionalCaretMovementIntent {
188 /// Creates an [ExtendSelectionToLineBreakIntent].
189 const ExtendSelectionToLineBreakIntent({
190 required bool forward,
191 required bool collapseSelection,
192 bool collapseAtReversal = false,
193 bool continuesAtWrap = false,
194 }) : assert(!collapseSelection || !collapseAtReversal),
195 super(forward, collapseSelection, collapseAtReversal, continuesAtWrap);
196}
197
198/// Extends, or moves the current selection from the current
199/// [TextSelection.extent] position to the closest position on the adjacent
200/// line.
201class ExtendSelectionVerticallyToAdjacentLineIntent extends DirectionalCaretMovementIntent {
202 /// Creates an [ExtendSelectionVerticallyToAdjacentLineIntent].
203 const ExtendSelectionVerticallyToAdjacentLineIntent({
204 required bool forward,
205 required bool collapseSelection,
206 }) : super(forward, collapseSelection);
207}
208
209/// Expands, or moves the current selection from the current
210/// [TextSelection.extent] position to the closest position on the adjacent
211/// page.
212class ExtendSelectionVerticallyToAdjacentPageIntent extends DirectionalCaretMovementIntent {
213 /// Creates an [ExtendSelectionVerticallyToAdjacentPageIntent].
214 const ExtendSelectionVerticallyToAdjacentPageIntent({
215 required bool forward,
216 required bool collapseSelection,
217 }) : super(forward, collapseSelection);
218}
219
220/// Extends, or moves the current selection from the current
221/// [TextSelection.extent] position to the previous or the next paragraph
222/// boundary.
223class ExtendSelectionToNextParagraphBoundaryIntent extends DirectionalCaretMovementIntent {
224 /// Creates an [ExtendSelectionToNextParagraphBoundaryIntent].
225 const ExtendSelectionToNextParagraphBoundaryIntent({
226 required bool forward,
227 required bool collapseSelection,
228 }) : super(forward, collapseSelection);
229}
230
231/// Extends, or moves the current selection from the current
232/// [TextSelection.extent] position to the previous or the next paragraph
233/// boundary depending on the [forward] parameter.
234///
235/// This [Intent] typically has the same effect as an
236/// [ExtendSelectionToNextParagraphBoundaryIntent], except it collapses the selection
237/// when the order of [TextSelection.base] and [TextSelection.extent] would
238/// reverse.
239///
240/// This is typically only used on MacOS.
241class ExtendSelectionToNextParagraphBoundaryOrCaretLocationIntent
242 extends DirectionalCaretMovementIntent {
243 /// Creates an [ExtendSelectionToNextParagraphBoundaryOrCaretLocationIntent].
244 const ExtendSelectionToNextParagraphBoundaryOrCaretLocationIntent({required bool forward})
245 : super(forward, false, true);
246}
247
248/// Extends, or moves the current selection from the current
249/// [TextSelection.extent] position to the start or the end of the document.
250///
251/// See also:
252///
253/// [ExtendSelectionToDocumentBoundaryIntent], which is similar but always
254/// increases the size of the selection.
255class ExtendSelectionToDocumentBoundaryIntent extends DirectionalCaretMovementIntent {
256 /// Creates an [ExtendSelectionToDocumentBoundaryIntent].
257 const ExtendSelectionToDocumentBoundaryIntent({
258 required bool forward,
259 required bool collapseSelection,
260 }) : super(forward, collapseSelection);
261}
262
263/// Scrolls to the beginning or end of the document depending on the [forward]
264/// parameter.
265class ScrollToDocumentBoundaryIntent extends DirectionalTextEditingIntent {
266 /// Creates a [ScrollToDocumentBoundaryIntent].
267 const ScrollToDocumentBoundaryIntent({required bool forward}) : super(forward);
268}
269
270/// Scrolls up or down by page depending on the [forward] parameter.
271/// Extends the selection up or down by page based on the [forward] parameter.
272class ExtendSelectionByPageIntent extends DirectionalTextEditingIntent {
273 /// Creates a [ExtendSelectionByPageIntent].
274 const ExtendSelectionByPageIntent({required bool forward}) : super(forward);
275}
276
277/// An [Intent] to select everything in the field.
278class SelectAllTextIntent extends Intent {
279 /// Creates an instance of [SelectAllTextIntent].
280 const SelectAllTextIntent(this.cause);
281
282 /// {@template flutter.widgets.TextEditingIntents.cause}
283 /// The [SelectionChangedCause] that triggered the intent.
284 /// {@endtemplate}
285 final SelectionChangedCause cause;
286}
287
288/// An [Intent] that represents a user interaction that attempts to copy or cut
289/// the current selection in the field.
290class CopySelectionTextIntent extends Intent {
291 const CopySelectionTextIntent._(this.cause, this.collapseSelection);
292
293 /// Creates an [Intent] that represents a user interaction that attempts to
294 /// cut the current selection in the field.
295 const CopySelectionTextIntent.cut(SelectionChangedCause cause) : this._(cause, true);
296
297 /// An [Intent] that represents a user interaction that attempts to copy the
298 /// current selection in the field.
299 static const CopySelectionTextIntent copy = CopySelectionTextIntent._(
300 SelectionChangedCause.keyboard,
301 false,
302 );
303
304 /// {@macro flutter.widgets.TextEditingIntents.cause}
305 final SelectionChangedCause cause;
306
307 /// Whether the original text needs to be removed from the input field if the
308 /// copy action was successful.
309 final bool collapseSelection;
310}
311
312/// An [Intent] to paste text from [Clipboard] to the field.
313class PasteTextIntent extends Intent {
314 /// Creates an instance of [PasteTextIntent].
315 const PasteTextIntent(this.cause);
316
317 /// {@macro flutter.widgets.TextEditingIntents.cause}
318 final SelectionChangedCause cause;
319}
320
321/// An [Intent] that represents a user interaction that attempts to go back to
322/// the previous editing state.
323class RedoTextIntent extends Intent {
324 /// Creates a [RedoTextIntent].
325 const RedoTextIntent(this.cause);
326
327 /// {@macro flutter.widgets.TextEditingIntents.cause}
328 final SelectionChangedCause cause;
329}
330
331/// An [Intent] that represents a user interaction that attempts to modify the
332/// current [TextEditingValue] in an input field.
333class ReplaceTextIntent extends Intent {
334 /// Creates a [ReplaceTextIntent].
335 const ReplaceTextIntent(
336 this.currentTextEditingValue,
337 this.replacementText,
338 this.replacementRange,
339 this.cause,
340 );
341
342 /// The [TextEditingValue] that this [Intent]'s action should perform on.
343 final TextEditingValue currentTextEditingValue;
344
345 /// The text to replace the original text within the [replacementRange] with.
346 final String replacementText;
347
348 /// The range of text in [currentTextEditingValue] that needs to be replaced.
349 final TextRange replacementRange;
350
351 /// {@macro flutter.widgets.TextEditingIntents.cause}
352 final SelectionChangedCause cause;
353}
354
355/// An [Intent] that represents a user interaction that attempts to go back to
356/// the previous editing state.
357class UndoTextIntent extends Intent {
358 /// Creates an [UndoTextIntent].
359 const UndoTextIntent(this.cause);
360
361 /// {@macro flutter.widgets.TextEditingIntents.cause}
362 final SelectionChangedCause cause;
363}
364
365/// An [Intent] that represents a user interaction that attempts to change the
366/// selection in an input field.
367class UpdateSelectionIntent extends Intent {
368 /// Creates an [UpdateSelectionIntent].
369 const UpdateSelectionIntent(this.currentTextEditingValue, this.newSelection, this.cause);
370
371 /// The [TextEditingValue] that this [Intent]'s action should perform on.
372 final TextEditingValue currentTextEditingValue;
373
374 /// The new [TextSelection] the input field should adopt.
375 final TextSelection newSelection;
376
377 /// {@macro flutter.widgets.TextEditingIntents.cause}
378 final SelectionChangedCause cause;
379}
380
381/// An [Intent] that represents a user interaction that attempts to swap the
382/// characters immediately around the cursor.
383class TransposeCharactersIntent extends Intent {
384 /// Creates a [TransposeCharactersIntent].
385 const TransposeCharactersIntent();
386}
387
388/// An [Intent] that represents a tap outside the field.
389///
390/// Invoked when the user taps outside the focused [EditableText] if
391/// [EditableText.onTapOutside] is null.
392///
393/// Override this [Intent] to modify the default behavior, which is to unfocus
394/// on a touch event on web and do nothing on other platforms.
395///
396/// See also:
397///
398/// * [Action.overridable] for an example on how to make an [Action]
399/// overridable.
400class EditableTextTapOutsideIntent extends Intent {
401 /// Creates an [EditableTextTapOutsideIntent].
402 const EditableTextTapOutsideIntent({required this.focusNode, required this.pointerDownEvent});
403
404 /// The [FocusNode] that this [Intent]'s action should be performed on.
405 final FocusNode focusNode;
406
407 /// The [PointerDownEvent] that initiated this [Intent].
408 final PointerDownEvent pointerDownEvent;
409}
410
411/// An [Intent] that represents a tap outside the field.
412///
413/// Invoked when the user taps up outside the focused [EditableText] if
414/// [EditableText.onTapUpOutside] is null.
415///
416/// Override this [Intent] to modify the default behavior, which is to unfocus
417/// on a touch event on web and do nothing on other platforms.
418///
419/// {@tool dartpad}
420/// A common requirement is to unfocus text fields when the user taps outside of
421/// it. For UX reasons, it's often desirable to only unfocus when the user taps
422/// outside of the text field, but not when they scroll.
423///
424/// To achieve this, you can override the default behavior of
425/// [EditableTextTapOutsideIntent] and [EditableTextTapUpOutsideIntent] to check
426/// the difference in distance between the pointer down and pointer up events
427/// before potentially unfocusing.
428///
429/// ** See code in examples/api/lib/widgets/text_editing_intents/editable_text_tap_up_outside_intent.0.dart **
430/// {@end-tool}
431///
432/// See also:
433///
434/// * [Action.overridable] for an example on how to make an [Action]
435/// overridable.
436class EditableTextTapUpOutsideIntent extends Intent {
437 /// Creates an [EditableTextTapUpOutsideIntent].
438 const EditableTextTapUpOutsideIntent({required this.focusNode, required this.pointerUpEvent});
439
440 /// The [FocusNode] that this [Intent]'s action should be performed on.
441 final FocusNode focusNode;
442
443 /// The [PointerUpEvent] that initiated this [Intent].
444 final PointerUpEvent pointerUpEvent;
445}
446