@@ -77,6 +77,16 @@ private void ItemsSource_PropertyChanged(DependencyObject sender, DependencyProp
77
77
if ( ItemsSource != null && ItemsSource . GetType ( ) != typeof ( InterspersedObservableCollection ) )
78
78
{
79
79
_innerItemsSource = new InterspersedObservableCollection ( ItemsSource ) ;
80
+
81
+ if ( MaxTokens . HasValue && _innerItemsSource . ItemsSource . Count > MaxTokens )
82
+ {
83
+ // Reduce down to the max as necessary.
84
+ for ( var i = _innerItemsSource . ItemsSource . Count ; i > MaxTokens ; -- i )
85
+ {
86
+ _innerItemsSource . Remove ( _innerItemsSource [ i ] ) ;
87
+ }
88
+ }
89
+
80
90
_currentTextEdit = _lastTextEdit = new PretokenStringContainer ( true ) ;
81
91
_innerItemsSource . Insert ( _innerItemsSource . Count , _currentTextEdit ) ;
82
92
ItemsSource = _innerItemsSource ;
@@ -278,18 +288,16 @@ void WaitForLoad(object s, RoutedEventArgs eargs)
278
288
}
279
289
else
280
290
{
281
- // TODO: It looks like we're setting selection and focus together on items? Not sure if that's what we want...
282
- // If that's the case, don't think this code will ever be called?
283
-
284
- //// TODO: Behavior question: if no items selected (just focus) does it just go to our last active textbox?
285
- //// Community voted that typing in the end box made sense
286
-
291
+ // If no items are selected, send input to the last active string container.
292
+ // This code is only fires during an edgecase where an item is in the process of being deleted and the user inputs a character before the focus has been redirected to a string container.
287
293
if ( _innerItemsSource [ _innerItemsSource . Count - 1 ] is ITokenStringContainer textToken )
288
294
{
289
295
var last = ContainerFromIndex ( Items . Count - 1 ) as TokenizingTextBoxItem ; // Should be our last text box
290
- var position = last . _autoSuggestTextBox . SelectionStart ;
291
- textToken . Text = last . _autoSuggestTextBox . Text . Substring ( 0 , position ) + args . Character +
292
- last . _autoSuggestTextBox . Text . Substring ( position ) ;
296
+ var text = last . _autoSuggestTextBox . Text ;
297
+ var selectionStart = last . _autoSuggestTextBox . SelectionStart ;
298
+ var position = selectionStart > text . Length ? text . Length : selectionStart ;
299
+ textToken . Text = text . Substring ( 0 , position ) + args . Character +
300
+ text . Substring ( position ) ;
293
301
294
302
last . _autoSuggestTextBox . SelectionStart = position + 1 ; // Set position to after our new character inserted
295
303
@@ -432,6 +440,12 @@ public async Task ClearAsync()
432
440
433
441
internal async Task AddTokenAsync ( object data , bool ? atEnd = null )
434
442
{
443
+ if ( MaxTokens == 0 )
444
+ {
445
+ // No tokens for you
446
+ return ;
447
+ }
448
+
435
449
if ( data is string str && TokenItemAdding != null )
436
450
{
437
451
var tiaea = new TokenItemAddingEventArgs ( str ) ;
@@ -448,24 +462,29 @@ internal async Task AddTokenAsync(object data, bool? atEnd = null)
448
462
}
449
463
}
450
464
451
- if ( TokenSelectionMode == TokenSelectionMode . Single )
465
+ // If we've been typing in the last box, just add this to the end of our collection
466
+ if ( atEnd == true || _currentTextEdit == _lastTextEdit )
452
467
{
453
- // Start at the end, remove any existing tokens.
454
- for ( var i = _innerItemsSource . Count - 1 ; i >= 0 ; -- i )
468
+ if ( MaxTokens != null && _innerItemsSource . ItemsSource . Count >= MaxTokens )
455
469
{
456
- var item = _innerItemsSource [ i ] ;
457
- if ( item is not ITokenStringContainer )
470
+ // Remove tokens from the end until below the max number.
471
+ for ( var i = _innerItemsSource . Count - 2 ; i >= 0 ; -- i )
458
472
{
459
- // Force remove the items. No warning and no option to cancel.
460
- _innerItemsSource . Remove ( item ) ;
461
- TokenItemRemoved ? . Invoke ( this , item ) ;
473
+ var item = _innerItemsSource [ i ] ;
474
+ if ( item is not ITokenStringContainer )
475
+ {
476
+ _innerItemsSource . Remove ( item ) ;
477
+ TokenItemRemoved ? . Invoke ( this , item ) ;
478
+
479
+ // Keep going until we are below the max.
480
+ if ( _innerItemsSource . ItemsSource . Count < MaxTokens )
481
+ {
482
+ break ;
483
+ }
484
+ }
462
485
}
463
486
}
464
- }
465
487
466
- // If we've been typing in the last box, just add this to the end of our collection
467
- if ( atEnd == true || _currentTextEdit == _lastTextEdit )
468
- {
469
488
_innerItemsSource . InsertAt ( _innerItemsSource . Count - 1 , data ) ;
470
489
}
471
490
else
@@ -474,6 +493,26 @@ internal async Task AddTokenAsync(object data, bool? atEnd = null)
474
493
var edit = _currentTextEdit ;
475
494
var index = _innerItemsSource . IndexOf ( edit ) ;
476
495
496
+ if ( MaxTokens != null && _innerItemsSource . ItemsSource . Count >= MaxTokens )
497
+ {
498
+ // Find the next token and remove it, until below the max number of tokens.
499
+ for ( var i = index ; i < _innerItemsSource . Count ; i ++ )
500
+ {
501
+ var item = _innerItemsSource [ i ] ;
502
+ if ( item is not ITokenStringContainer )
503
+ {
504
+ _innerItemsSource . Remove ( item ) ;
505
+ TokenItemRemoved ? . Invoke ( this , item ) ;
506
+
507
+ // Keep going until we are below the max.
508
+ if ( _innerItemsSource . ItemsSource . Count < MaxTokens )
509
+ {
510
+ break ;
511
+ }
512
+ }
513
+ }
514
+ }
515
+
477
516
// Insert our new data item at the location of our textbox
478
517
_innerItemsSource . InsertAt ( index , data ) ;
479
518
0 commit comments