@@ -5,6 +5,7 @@ import React, {
55 ReactElement ,
66 ReactNode ,
77 RefObject ,
8+ useCallback ,
89 useLayoutEffect ,
910 useMemo ,
1011 useRef ,
@@ -115,8 +116,11 @@ export interface CubeFilterListBoxProps<T>
115116 searchPlaceholder ?: string ;
116117 /** Whether the search input should have autofocus */
117118 autoFocus ?: boolean ;
118- /** Custom filter function for determining if an option should be included in search results */
119- filter ?: FilterFn ;
119+ /**
120+ * Custom filter function for determining if an option should be included in search results.
121+ * Pass `false` to disable internal filtering completely (useful for external filtering).
122+ */
123+ filter ?: FilterFn | false ;
120124 /** Custom label to display when no results are found after filtering */
121125 emptyLabel ?: ReactNode ;
122126 /** Custom styles for the search input */
@@ -160,6 +164,18 @@ export interface CubeFilterListBoxProps<T>
160164 * These are merged with customValueProps for new custom values.
161165 */
162166 newCustomValueProps ?: Partial < CubeItemProps < T > > ;
167+
168+ /**
169+ * Controlled search value. When provided, the search input becomes controlled.
170+ * Use with `onSearchChange` to manage the search state externally.
171+ */
172+ searchValue ?: string ;
173+
174+ /**
175+ * Callback fired when the search input value changes.
176+ * Use with `searchValue` for controlled search input.
177+ */
178+ onSearchChange ?: ( value : string ) => void ;
163179}
164180
165181const PROP_STYLES = [ ...BASE_STYLES , ...OUTER_STYLES , ...COLOR_STYLES ] ;
@@ -247,6 +263,8 @@ export const FilterListBox = forwardRef(function FilterListBox<
247263 allValueProps,
248264 customValueProps,
249265 newCustomValueProps,
266+ searchValue : controlledSearchValue ,
267+ onSearchChange,
250268 ...otherProps
251269 } = props ;
252270
@@ -386,13 +404,30 @@ export const FilterListBox = forwardRef(function FilterListBox<
386404 ( props as any ) [ 'aria-label' ] ||
387405 ( typeof label === 'string' ? label : undefined ) ;
388406
389- const [ searchValue , setSearchValue ] = useState ( '' ) ;
407+ // Controlled/uncontrolled search value pattern
408+ const [ internalSearchValue , setInternalSearchValue ] = useState ( '' ) ;
409+ const isSearchControlled = controlledSearchValue !== undefined ;
410+ const searchValue = isSearchControlled
411+ ? controlledSearchValue
412+ : internalSearchValue ;
413+
414+ const handleSearchChange = useCallback (
415+ ( value : string ) => {
416+ if ( ! isSearchControlled ) {
417+ setInternalSearchValue ( value ) ;
418+ }
419+ onSearchChange ?.( value ) ;
420+ } ,
421+ [ isSearchControlled , onSearchChange ] ,
422+ ) ;
423+
390424 const { contains } = useFilter ( { sensitivity : 'base' } ) ;
391425
392- // Choose the text filter function: user-provided `filter` prop (if any)
426+ // Choose the text filter function: user-provided `filter` prop (if any),
393427 // or the default `contains` helper from `useFilter`.
428+ // When filter={false}, disable filtering completely.
394429 const textFilterFn = useMemo < FilterFn > (
395- ( ) => filter || contains ,
430+ ( ) => ( filter === false ? ( ) => true : filter || contains ) ,
396431 [ filter , contains ] ,
397432 ) ;
398433
@@ -774,7 +809,7 @@ export const FilterListBox = forwardRef(function FilterListBox<
774809 if ( searchValue ) {
775810 // Clear the current search if any text is present.
776811 e . preventDefault ( ) ;
777- setSearchValue ( '' ) ;
812+ handleSearchChange ( '' ) ;
778813 } else {
779814 // Notify parent that Escape was pressed on an empty input.
780815 if ( onEscape ) {
@@ -893,7 +928,7 @@ export const FilterListBox = forwardRef(function FilterListBox<
893928 }
894929 onChange = { ( e ) => {
895930 const value = e . target . value ;
896- setSearchValue ( value ) ;
931+ handleSearchChange ( value ) ;
897932 } }
898933 { ...keyboardProps }
899934 { ...modAttrs ( mods ) }
0 commit comments