@@ -18,6 +18,7 @@ import {
1818 Section as BaseSection ,
1919 ListState ,
2020 Item as ReactAriaItem ,
21+ useListState ,
2122} from 'react-stately' ;
2223
2324import { useEvent } from '../../../_internal' ;
@@ -325,65 +326,55 @@ export const FilterPicker = forwardRef(function FilterPicker<T extends object>(
325326 return str . replace ( / = 2 / g, ':' ) . replace ( / = 0 / g, '=' ) ;
326327 } ;
327328
329+ // Create a local collection for label extraction only (not for rendering)
330+ // This gives us immediate access to textValue without waiting for FilterListBox
331+ const localCollectionState = useListState ( {
332+ children : children as any ,
333+ items : items as any ,
334+ selectionMode : 'none' as any , // We don't need selection management
335+ } ) ;
336+
328337 // ---------------------------------------------------------------------------
329- // Map public-facing keys (without React's "." prefix) to the actual React
330- // element keys that appear in the collection (which usually have the `.$`
331- // or `.` prefix added by React when children are in an array). This ensures
332- // that the key we pass to ListBox exactly matches the keys it receives from
333- // React Aria, so the initial selection is highlighted correctly.
338+ // Map user-provided keys to collection keys using the local collection.
339+ // The local collection handles key normalization, so we search for matching
340+ // items by comparing normalized keys.
334341 // ---------------------------------------------------------------------------
335342
336- const findReactKey = useCallback (
343+ const findCollectionKey = useCallback (
337344 ( lookup : Key ) : Key => {
338345 if ( lookup == null ) return lookup ;
339346
340347 const normalizedLookup = normalizeKeyValue ( lookup ) ;
341- let foundKey : Key = lookup ;
342-
343- const traverse = ( nodes : ReactNode ) : void => {
344- Children . forEach ( nodes , ( child : ReactNode ) => {
345- if ( ! child || typeof child !== 'object' ) return ;
346- const element = child as ReactElement ;
347-
348- if ( element . key != null ) {
349- if ( normalizeKeyValue ( element . key ) === normalizedLookup ) {
350- foundKey = element . key ;
351- }
352- }
353-
354- if (
355- element . props &&
356- typeof element . props === 'object' &&
357- 'children' in element . props
358- ) {
359- traverse ( ( element . props as any ) . children ) ;
360- }
361- } ) ;
362- } ;
363-
364- if ( children ) traverse ( children as ReactNode ) ;
348+ for ( const item of localCollectionState . collection ) {
349+ if ( normalizeKeyValue ( item . key ) === normalizedLookup ) {
350+ return item . key ;
351+ }
352+ }
365353
366- return foundKey ;
354+ // Fallback: return the lookup key as-is
355+ return lookup ;
367356 } ,
368- [ children ] ,
357+ [ localCollectionState . collection ] ,
369358 ) ;
370359
371360 const mappedSelectedKey = useMemo ( ( ) => {
372361 if ( selectionMode !== 'single' ) return null ;
373- return effectiveSelectedKey ? findReactKey ( effectiveSelectedKey ) : null ;
374- } , [ selectionMode , effectiveSelectedKey , findReactKey ] ) ;
362+ return effectiveSelectedKey
363+ ? findCollectionKey ( effectiveSelectedKey )
364+ : null ;
365+ } , [ selectionMode , effectiveSelectedKey , findCollectionKey ] ) ;
375366
376367 const mappedSelectedKeys = useMemo ( ( ) => {
377368 if ( selectionMode !== 'multiple' ) return undefined ;
378369
379370 if ( effectiveSelectedKeys === 'all' ) return 'all' as const ;
380371
381372 if ( Array . isArray ( effectiveSelectedKeys ) ) {
382- return ( effectiveSelectedKeys as Key [ ] ) . map ( ( k ) => findReactKey ( k ) ) ;
373+ return ( effectiveSelectedKeys as Key [ ] ) . map ( ( k ) => findCollectionKey ( k ) ) ;
383374 }
384375
385376 return effectiveSelectedKeys ;
386- } , [ selectionMode , effectiveSelectedKeys , findReactKey ] ) ;
377+ } , [ selectionMode , effectiveSelectedKeys , findCollectionKey ] ) ;
387378
388379 // Given an iterable of keys (array or Set) toggle membership for duplicates
389380 const processSelectionArray = ( iterable : Iterable < Key > ) : string [ ] => {
@@ -399,79 +390,18 @@ export const FilterPicker = forwardRef(function FilterPicker<T extends object>(
399390 return Array . from ( resultSet ) ;
400391 } ;
401392
402- // Helper to get selected item labels for display
393+ // Helper to get selected item labels for display using local collection
403394 const getSelectedLabels = ( ) => {
395+ const collection = localCollectionState . collection ;
396+
404397 // Handle "all" selection - return all available labels
405398 if ( selectionMode === 'multiple' && effectiveSelectedKeys === 'all' ) {
406399 const allLabels : string [ ] = [ ] ;
407-
408- // Extract from items prop if available
409- if ( items ) {
410- const extractFromItems = ( itemsArray : unknown [ ] ) : void => {
411- itemsArray . forEach ( ( item ) => {
412- if ( item && typeof item === 'object' ) {
413- const itemObj = item as ItemWithKey ;
414- if ( Array . isArray ( itemObj . children ) ) {
415- // Section-like object
416- extractFromItems ( itemObj . children ) ;
417- } else {
418- // Regular item - extract label
419- const label =
420- itemObj . textValue ||
421- ( itemObj as any ) . label ||
422- ( typeof ( itemObj as any ) . children === 'string'
423- ? ( itemObj as any ) . children
424- : '' ) ||
425- String (
426- ( itemObj as any ) . children ||
427- itemObj . key ||
428- itemObj . id ||
429- item ,
430- ) ;
431- allLabels . push ( label ) ;
432- }
433- }
434- } ) ;
435- } ;
436-
437- const itemsArray = Array . isArray ( items )
438- ? items
439- : Array . from ( items as Iterable < unknown > ) ;
440- extractFromItems ( itemsArray ) ;
441- return allLabels ;
442- }
443-
444- // Extract from children if available
445- if ( children ) {
446- const extractAllLabels = ( nodes : ReactNode ) : void => {
447- if ( ! nodes ) return ;
448- Children . forEach ( nodes , ( child : ReactNode ) => {
449- if ( ! child || typeof child !== 'object' ) return ;
450- const element = child as ReactElement ;
451-
452- if ( element . type === ReactAriaItem ) {
453- const props = element . props as any ;
454- const label =
455- props . textValue ||
456- ( typeof props . children === 'string' ? props . children : '' ) ||
457- String ( props . children || '' ) ;
458- allLabels . push ( label ) ;
459- }
460-
461- if (
462- element . props &&
463- typeof element . props === 'object' &&
464- 'children' in element . props
465- ) {
466- extractAllLabels ( ( element . props as any ) . children ) ;
467- }
468- } ) ;
469- } ;
470-
471- extractAllLabels ( children as ReactNode ) ;
472- return allLabels ;
400+ for ( const item of collection ) {
401+ if ( item . type === 'item' ) {
402+ allLabels . push ( item . textValue || String ( item . key ) ) ;
403+ }
473404 }
474-
475405 return allLabels ;
476406 }
477407
@@ -486,86 +416,26 @@ export const FilterPicker = forwardRef(function FilterPicker<T extends object>(
486416 const labels : string [ ] = [ ] ;
487417 const processedKeys = new Set < string > ( ) ;
488418
489- // Extract from items prop if available
490- if ( items ) {
491- const extractFromItems = ( itemsArray : unknown [ ] ) : void => {
492- itemsArray . forEach ( ( item ) => {
493- if ( item && typeof item === 'object' ) {
494- const itemObj = item as ItemWithKey ;
495- if ( Array . isArray ( itemObj . children ) ) {
496- // Section-like object
497- extractFromItems ( itemObj . children ) ;
498- } else {
499- // Regular item - check if selected
500- const itemKey = itemObj . key || itemObj . id ;
501- if (
502- itemKey != null &&
503- selectedSet . has ( normalizeKeyValue ( itemKey ) )
504- ) {
505- const label =
506- itemObj . textValue ||
507- ( itemObj as any ) . label ||
508- ( typeof ( itemObj as any ) . children === 'string'
509- ? ( itemObj as any ) . children
510- : '' ) ||
511- String ( ( itemObj as any ) . children || itemKey ) ;
512- labels . push ( label ) ;
513- processedKeys . add ( normalizeKeyValue ( itemKey ) ) ;
514- }
515- }
516- }
517- } ) ;
518- } ;
519-
520- const itemsArray = Array . isArray ( items )
521- ? items
522- : Array . from ( items as Iterable < unknown > ) ;
523- extractFromItems ( itemsArray ) ;
524- }
525-
526- // Extract from children if available (for mixed mode or fallback)
527- if ( children ) {
528- const extractLabelsWithTracking = ( nodes : ReactNode ) : void => {
529- if ( ! nodes ) return ;
530- Children . forEach ( nodes , ( child : ReactNode ) => {
531- if ( ! child || typeof child !== 'object' ) return ;
532- const element = child as ReactElement ;
533-
534- if ( element . type === ReactAriaItem ) {
535- const childKey = String ( element . key ) ;
536- if ( selectedSet . has ( normalizeKeyValue ( childKey ) ) ) {
537- const props = element . props as any ;
538- const label =
539- props . textValue ||
540- ( typeof props . children === 'string' ? props . children : '' ) ||
541- String ( props . children || '' ) ;
542- labels . push ( label ) ;
543- processedKeys . add ( normalizeKeyValue ( childKey ) ) ;
544- }
545- }
546-
547- if (
548- element . props &&
549- typeof element . props === 'object' &&
550- 'children' in element . props
551- ) {
552- extractLabelsWithTracking ( ( element . props as any ) . children ) ;
553- }
554- } ) ;
555- } ;
556-
557- extractLabelsWithTracking ( children as ReactNode ) ;
419+ // Use collection to get labels for selected items
420+ for ( const item of collection ) {
421+ if (
422+ item . type === 'item' &&
423+ selectedSet . has ( normalizeKeyValue ( item . key ) )
424+ ) {
425+ labels . push ( item . textValue || String ( item . key ) ) ;
426+ processedKeys . add ( normalizeKeyValue ( item . key ) ) ;
427+ }
558428 }
559429
560- // Handle custom values that don 't have corresponding items/children
430+ // Handle custom values that aren 't in the collection
561431 const selectedKeysArr =
562432 selectionMode === 'multiple' && effectiveSelectedKeys !== 'all'
563433 ? ( effectiveSelectedKeys || [ ] ) . map ( String )
564434 : effectiveSelectedKey != null
565435 ? [ String ( effectiveSelectedKey ) ]
566436 : [ ] ;
567437
568- // Add labels for any selected keys that weren't processed (custom values)
438+ // Add labels for any selected keys that weren't found in collection (custom values)
569439 selectedKeysArr . forEach ( ( key ) => {
570440 if ( ! processedKeys . has ( normalizeKeyValue ( key ) ) ) {
571441 // This is a custom value, use the key as the label
0 commit comments