@@ -30,8 +30,7 @@ import {
30
30
TreeKeyManagerOptions ,
31
31
TreeKeyManagerStrategy ,
32
32
} from './tree-key-manager-strategy' ;
33
-
34
- const DEFAULT_TYPEAHEAD_DEBOUNCE_INTERVAL_MS = 200 ;
33
+ import { Typeahead } from './typeahead' ;
35
34
36
35
function coerceObservable < T > ( data : T | Observable < T > ) : Observable < T > {
37
36
if ( ! isObservable ( data ) ) {
@@ -50,8 +49,6 @@ export class TreeKeyManager<T extends TreeKeyManagerItem> implements TreeKeyMana
50
49
private _activeItem : T | null = null ;
51
50
private _activationFollowsFocus = false ;
52
51
private _horizontal : 'ltr' | 'rtl' = 'ltr' ;
53
- private readonly _letterKeyStream = new Subject < string > ( ) ;
54
- private _typeaheadSubscription = Subscription . EMPTY ;
55
52
56
53
/**
57
54
* Predicate function that can be used to check whether an item should be skipped
@@ -62,11 +59,11 @@ export class TreeKeyManager<T extends TreeKeyManagerItem> implements TreeKeyMana
62
59
/** Function to determine equivalent items. */
63
60
private _trackByFn : ( item : T ) => unknown = ( item : T ) => item ;
64
61
65
- /** Buffer for the letters that the user has pressed when the typeahead option is turned on. */
66
- private _pressedLetters : string [ ] = [ ] ;
67
-
68
62
private _items : T [ ] = [ ] ;
69
63
64
+ private _typeahead ?: Typeahead < T > ;
65
+ private _typeaheadSubscription = Subscription . EMPTY ;
66
+
70
67
constructor ( items : Observable < T [ ] > | QueryList < T > | T [ ] , config : TreeKeyManagerOptions < T > ) {
71
68
// We allow for the items to be an array or Observable because, in some cases, the consumer may
72
69
// not have access to a QueryList of the items they want to manage (e.g. when the
@@ -75,11 +72,13 @@ export class TreeKeyManager<T extends TreeKeyManagerItem> implements TreeKeyMana
75
72
this . _items = items . toArray ( ) ;
76
73
items . changes . subscribe ( ( newItems : QueryList < T > ) => {
77
74
this . _items = newItems . toArray ( ) ;
75
+ this . _typeahead ?. setItems ( this . _items ) ;
78
76
this . _updateActiveItemIndex ( this . _items ) ;
79
77
} ) ;
80
78
} else if ( isObservable ( items ) ) {
81
79
items . subscribe ( newItems => {
82
80
this . _items = newItems ;
81
+ this . _typeahead ?. setItems ( newItems ) ;
83
82
this . _updateActiveItemIndex ( newItems ) ;
84
83
} ) ;
85
84
} else {
@@ -99,12 +98,7 @@ export class TreeKeyManager<T extends TreeKeyManagerItem> implements TreeKeyMana
99
98
this . _trackByFn = config . trackBy ;
100
99
}
101
100
if ( typeof config . typeAheadDebounceInterval !== 'undefined' ) {
102
- const typeAheadInterval =
103
- typeof config . typeAheadDebounceInterval === 'number'
104
- ? config . typeAheadDebounceInterval
105
- : DEFAULT_TYPEAHEAD_DEBOUNCE_INTERVAL_MS ;
106
-
107
- this . _setTypeAhead ( typeAheadInterval ) ;
101
+ this . _setTypeAhead ( config . typeAheadDebounceInterval ) ;
108
102
}
109
103
}
110
104
@@ -160,21 +154,14 @@ export class TreeKeyManager<T extends TreeKeyManagerItem> implements TreeKeyMana
160
154
break ;
161
155
}
162
156
163
- // Attempt to use the `event.key` which also maps it to the user's keyboard language,
164
- // otherwise fall back to resolving alphanumeric characters via the keyCode.
165
- if ( event . key && event . key . length === 1 ) {
166
- this . _letterKeyStream . next ( event . key . toLocaleUpperCase ( ) ) ;
167
- } else if ( ( keyCode >= A && keyCode <= Z ) || ( keyCode >= ZERO && keyCode <= NINE ) ) {
168
- this . _letterKeyStream . next ( String . fromCharCode ( keyCode ) ) ;
169
- }
170
-
171
- // NB: return here, in order to avoid preventing the default action of non-navigational
157
+ this . _typeahead ?. handleKey ( event ) ;
158
+ // Return here, in order to avoid preventing the default action of non-navigational
172
159
// keys or resetting the buffer of pressed letters.
173
160
return ;
174
161
}
175
162
176
163
// Reset the typeahead since the user has used a navigational key.
177
- this . _pressedLetters = [ ] ;
164
+ this . _typeahead ?. reset ( ) ;
178
165
event . preventDefault ( ) ;
179
166
}
180
167
@@ -247,6 +234,7 @@ export class TreeKeyManager<T extends TreeKeyManagerItem> implements TreeKeyMana
247
234
248
235
this . _activeItem = activeItem ?? null ;
249
236
this . _activeItemIndex = index ;
237
+ this . _typeahead ?. setCurrentSelectedItemIndex ( index ) ;
250
238
251
239
if ( options . emitChangeEvent ) {
252
240
// Emit to `change` stream as required by TreeKeyManagerStrategy interface.
@@ -270,50 +258,19 @@ export class TreeKeyManager<T extends TreeKeyManagerItem> implements TreeKeyMana
270
258
271
259
if ( newIndex > - 1 && newIndex !== this . _activeItemIndex ) {
272
260
this . _activeItemIndex = newIndex ;
261
+ this . _typeahead ?. setCurrentSelectedItemIndex ( newIndex ) ;
273
262
}
274
263
}
275
264
276
- private _setTypeAhead ( debounceInterval : number ) {
277
- this . _typeaheadSubscription . unsubscribe ( ) ;
278
-
279
- if (
280
- ( typeof ngDevMode === 'undefined' || ngDevMode ) &&
281
- this . _items . length &&
282
- this . _items . some ( item => typeof item . getLabel !== 'function' )
283
- ) {
284
- throw new Error (
285
- 'TreeKeyManager items in typeahead mode must implement the `getLabel` method.' ,
286
- ) ;
287
- }
288
-
289
- // Debounce the presses of non-navigational keys, collect the ones that correspond to letters
290
- // and convert those letters back into a string. Afterwards find the first item that starts
291
- // with that string and select it.
292
- this . _typeaheadSubscription = this . _letterKeyStream
293
- . pipe (
294
- tap ( letter => this . _pressedLetters . push ( letter ) ) ,
295
- debounceTime ( debounceInterval ) ,
296
- filter ( ( ) => this . _pressedLetters . length > 0 ) ,
297
- map ( ( ) => this . _pressedLetters . join ( '' ) . toLocaleUpperCase ( ) ) ,
298
- )
299
- . subscribe ( inputString => {
300
- // Start at 1 because we want to start searching at the item immediately
301
- // following the current active item.
302
- for ( let i = 1 ; i < this . _items . length + 1 ; i ++ ) {
303
- const index = ( this . _activeItemIndex + i ) % this . _items . length ;
304
- const item = this . _items [ index ] ;
305
-
306
- if (
307
- ! this . _skipPredicateFn ( item ) &&
308
- item . getLabel ?.( ) . toLocaleUpperCase ( ) . trim ( ) . indexOf ( inputString ) === 0
309
- ) {
310
- this . focusItem ( index ) ;
311
- break ;
312
- }
313
- }
265
+ private _setTypeAhead ( debounceInterval : number | boolean ) {
266
+ this . _typeahead = new Typeahead ( this . _items , {
267
+ debounceInterval : typeof debounceInterval === 'number' ? debounceInterval : undefined ,
268
+ skipPredicate : item => this . _skipPredicateFn ( item ) ,
269
+ } ) ;
314
270
315
- this . _pressedLetters = [ ] ;
316
- } ) ;
271
+ this . _typeaheadSubscription = this . _typeahead . selectedItem . subscribe ( item => {
272
+ this . focusItem ( item ) ;
273
+ } ) ;
317
274
}
318
275
319
276
private _findNextAvailableItemIndex ( startingIndex : number ) {
@@ -364,10 +321,6 @@ export class TreeKeyManager<T extends TreeKeyManagerItem> implements TreeKeyMana
364
321
if ( ! this . _isCurrentItemExpanded ( ) ) {
365
322
this . _activeItem . expand ( ) ;
366
323
} else {
367
- const children = this . _activeItem . getChildren ( ) ;
368
-
369
- const children2 = isObservable ( children ) ? children : observableOf ( children ) ;
370
-
371
324
coerceObservable ( this . _activeItem . getChildren ( ) )
372
325
. pipe ( take ( 1 ) )
373
326
. subscribe ( children => {
0 commit comments