@@ -65,7 +65,7 @@ import type { IIcon } from "./Icon.js";
6565
6666// Templates
6767import InputTemplate from "./InputTemplate.js" ;
68- import { StartsWith } from "./Filters.js" ;
68+ import * as Filters from "./Filters.js" ;
6969
7070import {
7171 VALUE_STATE_SUCCESS ,
@@ -100,6 +100,7 @@ import type { ListItemClickEventDetail, ListSelectionChangeEventDetail } from ".
100100import type ResponsivePopover from "./ResponsivePopover.js" ;
101101import type InputKeyHint from "./types/InputKeyHint.js" ;
102102import type InputComposition from "./features/InputComposition.js" ;
103+ import InputSuggestionsFilter from "./types/InputSuggestionsFilter.js" ;
103104
104105/**
105106 * Interface for components that represent a suggestion item, usable in `ui5-input`
@@ -492,6 +493,15 @@ class Input extends UI5Element implements SuggestionComponent, IFormInputElement
492493 @property ( { type : Boolean } )
493494 open = false ;
494495
496+ /**
497+ * Defines the filter type of the component.
498+ * @default "None"
499+ * @public
500+ * @since 2.19.0
501+ */
502+ @property ( )
503+ filter : `${InputSuggestionsFilter } ` = InputSuggestionsFilter . None ;
504+
495505 /**
496506 * Defines whether the clear icon is visible.
497507 * @default false
@@ -787,6 +797,10 @@ class Input extends UI5Element implements SuggestionComponent, IFormInputElement
787797 return ;
788798 }
789799
800+ if ( this . filter !== InputSuggestionsFilter . None ) {
801+ this . _filterItems ( this . typedInValue ) ;
802+ }
803+
790804 const autoCompletedChars = innerInput . selectionEnd ! - innerInput . selectionStart ! ;
791805
792806 // Typehead causes issues on Android devices, so we disable it for now
@@ -820,7 +834,13 @@ class Input extends UI5Element implements SuggestionComponent, IFormInputElement
820834 }
821835
822836 if ( this . typedInValue . length && this . value . length ) {
823- innerInput . setSelectionRange ( this . typedInValue . length , this . value . length ) ;
837+ // "Contains" filtering requires custom selection range handling.
838+ // Example: "e" → "Belgium" (item does not start with typed value, so select all).
839+ if ( this . filter === InputSuggestionsFilter . Contains ) {
840+ this . _adjustContainsSelectionRange ( ) ;
841+ } else {
842+ innerInput . setSelectionRange ( this . typedInValue . length , this . value . length ) ;
843+ }
824844 }
825845
826846 this . fireDecoratorEvent ( "type-ahead" ) ;
@@ -835,6 +855,22 @@ class Input extends UI5Element implements SuggestionComponent, IFormInputElement
835855 }
836856 }
837857
858+ _adjustContainsSelectionRange ( ) {
859+ const innerInput = this . getInputDOMRefSync ( ) ! ;
860+ const visibleItems = this . Suggestions ?. _getItems ( ) . filter ( item => ! item . hidden ) as IInputSuggestionItemSelectable [ ] ;
861+ const currentItem = visibleItems ?. find ( item => { return item . selected || item . focused ; } ) ;
862+ const groupItems = this . _flattenItems . filter ( item => this . _isGroupItem ( item ) ) ;
863+
864+ if ( currentItem && ! groupItems . includes ( currentItem ) ) {
865+ const doesItemStartWithTypedValue = currentItem ?. text ?. toLowerCase ( ) . startsWith ( this . typedInValue . toLowerCase ( ) ) ;
866+ if ( doesItemStartWithTypedValue ) {
867+ innerInput . setSelectionRange ( this . typedInValue . length , this . value . length ) ;
868+ } else {
869+ innerInput . setSelectionRange ( 0 , this . value . length ) ;
870+ }
871+ }
872+ }
873+
838874 _onkeydown ( e : KeyboardEvent ) {
839875 this . _isKeyNavigation = true ;
840876 this . _shouldAutocomplete = ! this . noTypeahead && ! ( isBackSpace ( e ) || isDelete ( e ) || isEscape ( e ) ) ;
@@ -911,8 +947,9 @@ class Input extends UI5Element implements SuggestionComponent, IFormInputElement
911947
912948 get currentItemIndex ( ) {
913949 const allItems = this . Suggestions ?. _getItems ( ) as IInputSuggestionItemSelectable [ ] ;
914- const currentItem = allItems . find ( item => { return item . selected || item . focused ; } ) ;
915- const indexOfCurrentItem = currentItem ? allItems . indexOf ( currentItem ) : - 1 ;
950+ const visibleItems = allItems . filter ( item => ! item . hidden ) ;
951+ const currentItem = visibleItems . find ( item => { return item . selected || item . focused ; } ) ;
952+ const indexOfCurrentItem = currentItem ? visibleItems . indexOf ( currentItem ) : - 1 ;
916953 return indexOfCurrentItem ;
917954 }
918955
@@ -1310,11 +1347,15 @@ class Input extends UI5Element implements SuggestionComponent, IFormInputElement
13101347 this . Suggestions . updateSelectedItemPosition ( - 1 ) ;
13111348 }
13121349
1350+ if ( this . filter && ( e . target as HTMLInputElement ) . value === "" ) {
1351+ this . open = false ;
1352+ }
1353+
13131354 this . isTyping = true ;
13141355 }
13151356
13161357 _startsWithMatchingItems ( str : string ) : Array < IInputSuggestionItemSelectable > {
1317- return StartsWith ( str , this . _selectableItems , "text" ) ;
1358+ return Filters . StartsWith ( str , this . _selectableItems , "text" ) ;
13181359 }
13191360
13201361 _getFirstMatchingItem ( current : string ) : IInputSuggestionItemSelectable | undefined {
@@ -1337,6 +1378,52 @@ class Input extends UI5Element implements SuggestionComponent, IFormInputElement
13371378 item . selected = true ;
13381379 }
13391380
1381+ _filterItems ( value : string ) {
1382+ let matchingItems : Array < IInputSuggestionItem > = [ ] ;
1383+ const groupItems = this . _flattenItems . filter ( item => this . _isGroupItem ( item ) ) ;
1384+
1385+ this . _resetItemVisibility ( ) ;
1386+
1387+ if ( groupItems . length ) {
1388+ matchingItems = this . _filterGroups ( this . filter , groupItems ) ;
1389+ } else {
1390+ matchingItems = ( Filters [ this . filter ] ) ( value , this . _selectableItems , "text" ) ;
1391+ }
1392+ this . _selectableItems . forEach ( item => {
1393+ item . hidden = ! matchingItems . includes ( item ) ;
1394+ } ) ;
1395+
1396+ if ( matchingItems . length === 0 ) {
1397+ this . open = false ;
1398+ }
1399+ }
1400+
1401+ _filterGroups ( filterType : `${InputSuggestionsFilter } `, groupItems : IInputSuggestionItem [ ] ) {
1402+ const filteredGroupItems : IInputSuggestionItem [ ] = [ ] ;
1403+ groupItems . forEach ( groupItem => {
1404+ const currentGroupItems = ( Filters [ filterType ] ) ( this . typedInValue , groupItem . items ?? [ ] , "text" ) ;
1405+ filteredGroupItems . push ( ...currentGroupItems ) ;
1406+ if ( currentGroupItems . length === 0 ) {
1407+ groupItem . hidden = true ;
1408+ } else {
1409+ groupItem . hidden = false ;
1410+ }
1411+ } ) ;
1412+ return filteredGroupItems ;
1413+ }
1414+
1415+ _resetItemVisibility ( ) {
1416+ this . _flattenItems . forEach ( item => {
1417+ if ( this . _isGroupItem ( item ) ) {
1418+ item . items ?. forEach ( i => {
1419+ i . hidden = false ;
1420+ } ) ;
1421+ return ;
1422+ }
1423+ item . hidden = false ;
1424+ } ) ;
1425+ }
1426+
13401427 _handleTypeAhead ( item : IInputSuggestionItemSelectable ) {
13411428 const value = item . text ? item . text : "" ;
13421429
0 commit comments