@@ -15,13 +15,15 @@ typedef ChipsBuilder<T> = Widget Function(
15
15
const kObjectReplacementChar = 0xFFFD ;
16
16
17
17
extension on TextEditingValue {
18
- String get normalCharactersText => String .fromCharCodes (
18
+ String get normalCharactersText =>
19
+ String .fromCharCodes (
19
20
text.codeUnits.where ((ch) => ch != kObjectReplacementChar),
20
21
);
21
22
22
- List <int > get replacementCharacters => text.codeUnits
23
- .where ((ch) => ch == kObjectReplacementChar)
24
- .toList (growable: false );
23
+ List <int > get replacementCharacters =>
24
+ text.codeUnits
25
+ .where ((ch) => ch == kObjectReplacementChar)
26
+ .toList (growable: false );
25
27
26
28
int get replacementCharactersCount => replacementCharacters.length;
27
29
}
@@ -52,7 +54,8 @@ class ChipsInput<T> extends StatefulWidget {
52
54
this .allowChipEditing = false ,
53
55
this .focusNode,
54
56
this .initialSuggestions,
55
- }) : assert (maxChips == null || initialValue.length <= maxChips),
57
+ })
58
+ : assert (maxChips == null || initialValue.length <= maxChips),
56
59
super (key: key);
57
60
58
61
final InputDecoration decoration;
@@ -91,15 +94,16 @@ class ChipsInputState<T> extends State<ChipsInput<T>>
91
94
Set <T > _chips = < T > {};
92
95
List <T ?>? _suggestions;
93
96
final StreamController <List <T ?>?> _suggestionsStreamController =
94
- StreamController <List <T >?>.broadcast ();
97
+ StreamController <List <T >?>.broadcast ();
95
98
int _searchId = 0 ;
96
99
TextEditingValue _value = const TextEditingValue ();
97
100
TextInputConnection ? _textInputConnection;
98
101
late SuggestionsBoxController _suggestionsBoxController;
99
102
final _layerLink = LayerLink ();
100
103
final Map <T ?, String > _enteredTexts = < T , String > {};
101
104
102
- TextInputConfiguration get textInputConfiguration => TextInputConfiguration (
105
+ TextInputConfiguration get textInputConfiguration =>
106
+ TextInputConfiguration (
103
107
inputType: widget.inputType,
104
108
obscureText: widget.obscureText,
105
109
autocorrect: widget.autocorrect,
@@ -116,6 +120,7 @@ class ChipsInputState<T> extends State<ChipsInput<T>>
116
120
widget.maxChips != null && _chips.length >= widget.maxChips! ;
117
121
118
122
FocusNode ? _focusNode;
123
+
119
124
FocusNode get _effectiveFocusNode =>
120
125
widget.focusNode ?? (_focusNode ?? = FocusNode ());
121
126
late FocusAttachment _nodeAttachment;
@@ -197,7 +202,7 @@ class ChipsInputState<T> extends State<ChipsInput<T>>
197
202
final showTop = topAvailableSpace > bottomAvailableSpace;
198
203
// print("showTop: $showTop" );
199
204
final compositedTransformFollowerOffset =
200
- showTop ? Offset (0 , - size.height) : Offset .zero;
205
+ showTop ? Offset (0 , - size.height) : Offset .zero;
201
206
202
207
return StreamBuilder <List <T ?>?>(
203
208
stream: _suggestionsStreamController.stream,
@@ -217,10 +222,10 @@ class ChipsInputState<T> extends State<ChipsInput<T>>
217
222
itemBuilder: (BuildContext context, int index) {
218
223
return _suggestions != null
219
224
? widget.suggestionBuilder (
220
- context,
221
- this ,
222
- _suggestions! [index] as T ,
223
- )
225
+ context,
226
+ this ,
227
+ _suggestions! [index] as T ,
228
+ )
224
229
: Container ();
225
230
},
226
231
),
@@ -235,9 +240,9 @@ class ChipsInputState<T> extends State<ChipsInput<T>>
235
240
child: ! showTop
236
241
? suggestionsListView
237
242
: FractionalTranslation (
238
- translation: const Offset (0 , - 1 ),
239
- child: suggestionsListView,
240
- ),
243
+ translation: const Offset (0 , - 1 ),
244
+ child: suggestionsListView,
245
+ ),
241
246
),
242
247
);
243
248
}
@@ -292,7 +297,10 @@ class ChipsInputState<T> extends State<ChipsInput<T>>
292
297
Future .delayed (const Duration (milliseconds: 300 ), () {
293
298
WidgetsBinding .instance.addPostFrameCallback ((_) async {
294
299
final renderBox = context.findRenderObject () as RenderBox ;
295
- await Scrollable .of (context)? .position.ensureVisible (renderBox);
300
+ await Scrollable
301
+ .of (context)
302
+ ? .position
303
+ .ensureVisible (renderBox);
296
304
});
297
305
});
298
306
}
@@ -301,7 +309,8 @@ class ChipsInputState<T> extends State<ChipsInput<T>>
301
309
final localId = ++ _searchId;
302
310
final results = await widget.findSuggestions (value);
303
311
if (_searchId == localId && mounted) {
304
- setState (() => _suggestions =
312
+ setState (() =>
313
+ _suggestions =
305
314
results.where ((r) => ! _chips.contains (r)).toList (growable: false ));
306
315
}
307
316
_suggestionsStreamController.add (_suggestions ?? []);
@@ -328,7 +337,7 @@ class ChipsInputState<T> extends State<ChipsInput<T>>
328
337
oldTextEditingValue.replacementCharactersCount) {
329
338
final removedChip = _chips.last;
330
339
setState (() =>
331
- _chips = Set .of (_chips.take (value.replacementCharactersCount)));
340
+ _chips = Set .of (_chips.take (value.replacementCharactersCount)));
332
341
widget.onChanged (_chips.toList (growable: false ));
333
342
String ? putText = '' ;
334
343
if (widget.allowChipEditing && _enteredTexts.containsKey (removedChip)) {
@@ -349,12 +358,13 @@ class ChipsInputState<T> extends State<ChipsInput<T>>
349
358
String .fromCharCodes (_chips.map ((_) => kObjectReplacementChar)) +
350
359
(replaceText ? '' : _value.normalCharactersText) +
351
360
putText;
352
- setState (() => _value = _value.copyWith (
353
- text: updatedText,
354
- selection: TextSelection .collapsed (offset: updatedText.length),
355
- //composing: TextRange(start: 0, end: text.length),
356
- composing: TextRange .empty,
357
- ));
361
+ setState (() =>
362
+ _value = _value.copyWith (
363
+ text: updatedText,
364
+ selection: TextSelection .collapsed (offset: updatedText.length),
365
+ //composing: TextRange(start: 0, end: text.length),
366
+ composing: TextRange .empty,
367
+ ));
358
368
}
359
369
_closeInputConnectionIfNeeded (); //Hack for #34 (https://github.com/danvick/flutter_chips_input/issues/34#issuecomment-684505282). TODO: Find permanent fix
360
370
_textInputConnection ?? = TextInput .attach (this , textInputConfiguration);
@@ -451,7 +461,23 @@ class ChipsInputState<T> extends State<ChipsInput<T>>
451
461
),
452
462
);
453
463
454
- return NotificationListener <SizeChangedLayoutNotification >(
464
+ return RawKeyboardListener (
465
+ focusNode: _effectiveFocusNode,
466
+ onKey: (event) {
467
+ final str = currentTextEditingValue.text;
468
+
469
+ /// Make sure to filter event since without checking 'RawKeyDownEvent' will trigger this multiple times (2) because of RawKeyUpEvent
470
+ if (event.runtimeType.toString () == 'RawKeyDownEvent' &&
471
+ event.logicalKey == LogicalKeyboardKey .backspace &&
472
+ str.isNotEmpty) {
473
+ final sd = str.substring (0 , str.length - 1 );
474
+
475
+ /// Make sure to also update cursor position using the TextSelection.collapsed.
476
+ updateEditingValue (TextEditingValue (
477
+ text: sd,
478
+ selection: TextSelection .collapsed (offset: sd.length)));
479
+ }
480
+ }, child: NotificationListener <SizeChangedLayoutNotification >(
455
481
onNotification: (SizeChangedLayoutNotification val) {
456
482
WidgetsBinding .instance.addPostFrameCallback ((_) async {
457
483
_suggestionsBoxController.overlayEntry? .markNeedsBuild ();
@@ -485,6 +511,7 @@ class ChipsInputState<T> extends State<ChipsInput<T>>
485
511
],
486
512
),
487
513
),
514
+ )
488
515
);
489
516
}
490
517
0 commit comments