@@ -95,10 +95,18 @@ class CDSComboBox extends CDSDropdown {
9595 ) ;
9696 }
9797
98+ connectedCallback ( ) {
99+ super . connectedCallback ( ) ;
100+ if ( this . typeahead ) {
101+ this . shouldFilterItem = true ;
102+ this . setAttribute ( 'should-filter-item' , '' ) ;
103+ }
104+ }
105+
98106 /**
99107 * Handles `input` event on the `<input>` for filtering.
100108 */
101- protected _handleInput ( ) {
109+ protected _handleInput ( event : InputEvent ) {
102110 const rawQueryText = this . _filterInputNode . value ;
103111 const queryText = rawQueryText . trim ( ) . toLowerCase ( ) ;
104112
@@ -118,13 +126,49 @@ class CDSComboBox extends CDSDropdown {
118126 if ( highlightedItem ) {
119127 this . _scrollItemIntoView ( highlightedItem as HTMLElement ) ;
120128 }
121- }
122129
130+ if ( this . typeahead && event ?. inputType ?. startsWith ( 'insert' ) ) {
131+ const suggestedItem = highlightedItem . textContent ?. trim ( ) ?? '' ;
132+ if (
133+ suggestedItem . toLowerCase ( ) . startsWith ( rawQueryText . toLowerCase ( ) ) &&
134+ suggestedItem . length > rawQueryText . length
135+ ) {
136+ const suggestionText =
137+ rawQueryText + suggestedItem . slice ( rawQueryText . length ) ;
138+
139+ this . _filterInputNode . value = suggestionText ;
140+ this . _filterInputNode . setSelectionRange (
141+ rawQueryText . length ,
142+ suggestionText . length
143+ ) ;
144+
145+ this . _filterInputValue = suggestionText ;
146+ this . open = true ;
147+ this . requestUpdate ( ) ;
148+ return ;
149+ }
150+ }
151+ }
123152 this . _filterInputValue = rawQueryText ;
124153 this . open = true ;
125154 this . requestUpdate ( ) ;
126155 }
127156
157+ // removes the autocomplete suggestion
158+ protected _removeAutoCompleteSuggestion ( ) {
159+ if ( ! this . _filterInputNode ) return ;
160+ const { selectionStart, selectionEnd, value } = this . _filterInputNode ;
161+ if ( selectionStart && selectionEnd && selectionEnd > selectionStart ) {
162+ const cleanInput = value . slice ( 0 , selectionStart ) ;
163+ this . _filterInputNode . value = cleanInput ;
164+ this . _filterInputNode . setSelectionRange (
165+ cleanInput . length ,
166+ cleanInput . length
167+ ) ;
168+ return ;
169+ }
170+ }
171+
128172 // Applies filtering/highlighting to all slotted items.
129173 protected _filterItems (
130174 items : NodeListOf < Element > ,
@@ -141,9 +185,9 @@ class CDSComboBox extends CDSDropdown {
141185 comboItem . highlighted = false ;
142186 return ;
143187 }
144- const matches = ( comboItem . textContent || '' )
145- . toLowerCase ( )
146- . includes ( queryText ) ;
188+ const matches = this . typeahead
189+ ? ( comboItem . textContent || '' ) . toLowerCase ( ) . startsWith ( queryText )
190+ : ( comboItem . textContent || '' ) . toLowerCase ( ) . includes ( queryText ) ;
147191 const filterFunction =
148192 typeof this . shouldFilterItem === 'function'
149193 ? this . shouldFilterItem
@@ -198,6 +242,13 @@ class CDSComboBox extends CDSDropdown {
198242
199243 // Clear the query and selection when Escape is pressed.
200244 protected _handleInputKeydown ( event : KeyboardEvent ) {
245+ // remove the autocomplete suggestion when navigating away from the suggested item
246+ if (
247+ this . typeahead &&
248+ ( event . key === 'ArrowDown' || event . key === 'ArrowUp' )
249+ ) {
250+ this . _removeAutoCompleteSuggestion ( ) ;
251+ }
201252 if ( event . key !== 'Escape' ) {
202253 return ;
203254 }
@@ -282,6 +333,13 @@ class CDSComboBox extends CDSDropdown {
282333 itemToSelect . setAttribute ( 'aria-selected' , 'true' ) ;
283334 }
284335 this . _handleUserInitiatedToggle ( false ) ;
336+
337+ if ( this . typeahead && this . _filterInputNode ) {
338+ this . _filterInputValue = itemToSelect ?. textContent ?. trim ( ) ?? '' ;
339+
340+ const length = this . _filterInputValue . length ;
341+ this . _filterInputNode . setSelectionRange ( length , length ) ;
342+ }
285343 }
286344
287345 protected _renderLabel ( ) : TemplateResult {
@@ -375,7 +433,8 @@ class CDSComboBox extends CDSDropdown {
375433 itemMatches ! : ( item : CDSComboBoxItem , queryText : string ) => boolean ;
376434
377435 /**
378- * Provide custom filtering behavior.
436+ * Provide custom filtering behavior. This attribute will be ignored if
437+ * `typeahead` is enabled and will default to `true`
379438 */
380439 @property ( {
381440 attribute : 'should-filter-item' ,
@@ -385,6 +444,12 @@ class CDSComboBox extends CDSDropdown {
385444 } )
386445 shouldFilterItem : boolean | ShouldFilterItem = false ;
387446
447+ /**
448+ * **Experimental**: will enable autocomplete and typeahead for the input field
449+ */
450+ @property ( { type : Boolean } )
451+ typeahead = false ;
452+
388453 shouldUpdate ( changedProperties ) {
389454 super . shouldUpdate ( changedProperties ) ;
390455 const { _selectedItemContent : selectedItemContent } = this ;
@@ -398,9 +463,14 @@ class CDSComboBox extends CDSDropdown {
398463 super . updated ( changedProperties ) ;
399464 if ( changedProperties . has ( 'open' ) ) {
400465 if ( this . open && this . _filterInputNode ) {
401- this . _handleInput ( ) ;
466+ this . _handleInput ( changedProperties ) ;
402467 } else if ( ! this . open ) {
468+ // remove the autocomplete suggestion when closing the combobox
469+ this . _removeAutoCompleteSuggestion ( ) ;
403470 this . _resetFilteredItems ( ) ;
471+ if ( this . _filterInputNode . value == '' ) {
472+ this . value = '' ;
473+ }
404474 }
405475 }
406476 const { _listBoxNode : listBoxNode } = this ;
0 commit comments