@@ -80,12 +80,14 @@ export type Group<T extends object> = {
8080 [ uniqueGroupKey ] : true ;
8181} ;
8282
83+ type OptionOrGroup < T extends object > = T | Group < T > ;
84+
8385export const buildGroup = < T extends object > ( group : Omit < Group < T > , typeof uniqueGroupKey > ) : Group < T > => ( {
8486 ...group ,
8587 [ uniqueGroupKey ] : true ,
8688} ) ;
8789
88- const isGroup = < T extends object > ( optionOrGroup : T | Group < T > ) : optionOrGroup is Group < T > => {
90+ const isGroup = < T extends object > ( optionOrGroup : OptionOrGroup < T > ) : optionOrGroup is Group < T > => {
8991 return uniqueGroupKey in optionOrGroup && optionOrGroup [ uniqueGroupKey ] === true ;
9092} ;
9193
@@ -141,7 +143,7 @@ type PickerProps<T extends object> = {
141143 /**
142144 * The options to display in the picker. This can be a flat array of options or an array of groups.
143145 */
144- optionsOrGroups : T [ ] | Group < T > [ ] ;
146+ optionsOrGroups : OptionOrGroup < T > [ ] ;
145147 /**
146148 * A function that returns the id of an option.
147149 */
@@ -199,11 +201,11 @@ type PickerProps<T extends object> = {
199201} ;
200202
201203export type PickerContextState < T extends object > = {
202- $optionsOrGroups : WritableAtom < T [ ] | Group < T > [ ] > ;
204+ $optionsOrGroups : WritableAtom < OptionOrGroup < T > [ ] > ;
203205 $groupStatusMap : WritableAtom < GroupStatusMap > ;
204206 $compactView : WritableAtom < boolean > ;
205207 $activeOptionId : WritableAtom < string | undefined > ;
206- $filteredOptions : WritableAtom < T [ ] | Group < T > [ ] > ;
208+ $filteredOptions : WritableAtom < OptionOrGroup < T > [ ] > ;
207209 $flattenedFilteredOptions : ReadableAtom < T [ ] > ;
208210 $totalOptionCount : ReadableAtom < number > ;
209211 $areAllGroupsDisabled : ReadableAtom < boolean > ;
@@ -250,7 +252,7 @@ export const getRegex = (searchTerm: string) => {
250252 return new RegExp ( `${ pattern } .+` , 'i' ) ;
251253} ;
252254
253- const getFirstOption = < T extends object > ( options : T [ ] | Group < T > [ ] ) : T | undefined => {
255+ const getFirstOption = < T extends object > ( options : OptionOrGroup < T > [ ] ) : T | undefined => {
254256 const firstOptionOrGroup = options [ 0 ] ;
255257 if ( ! firstOptionOrGroup ) {
256258 return ;
@@ -263,7 +265,7 @@ const getFirstOption = <T extends object>(options: T[] | Group<T>[]): T | undefi
263265} ;
264266
265267const getFirstOptionId = < T extends object > (
266- options : T [ ] | Group < T > [ ] ,
268+ options : OptionOrGroup < T > [ ] ,
267269 getOptionId : ( item : T ) => string
268270) : string | undefined => {
269271 const firstOptionOrGroup = getFirstOption ( options ) ;
@@ -275,7 +277,7 @@ const getFirstOptionId = <T extends object>(
275277} ;
276278
277279const findOption = < T extends object > (
278- options : T [ ] | Group < T > [ ] ,
280+ options : OptionOrGroup < T > [ ] ,
279281 id : string ,
280282 getOptionId : ( item : T ) => string
281283) : T | undefined => {
@@ -293,7 +295,7 @@ const findOption = <T extends object>(
293295 }
294296} ;
295297
296- const flattenOptions = < T extends object > ( options : ( T | Group < T > ) [ ] ) : T [ ] => {
298+ const flattenOptions = < T extends object > ( options : OptionOrGroup < T > [ ] ) : T [ ] => {
297299 const flattened : T [ ] = [ ] ;
298300 for ( const optionOrGroup of options ) {
299301 if ( isGroup ( optionOrGroup ) ) {
@@ -307,7 +309,7 @@ const flattenOptions = <T extends object>(options: (T | Group<T>)[]): T[] => {
307309
308310type GroupStatusMap = Record < string , boolean > ;
309311
310- const useTogglableGroups = < T extends object > ( options : ( T | Group < T > ) [ ] ) => {
312+ const useTogglableGroups = < T extends object > ( options : OptionOrGroup < T > [ ] ) => {
311313 const groupsWithOptions = useMemo ( ( ) => {
312314 const ids : string [ ] = [ ] ;
313315 for ( const optionOrGroup of options ) {
@@ -478,6 +480,18 @@ const useComputed = <Value, OriginStores extends AnyStore[]>(
478480 return useState ( ( ) => computed ( stores , cb ) ) [ 0 ] ;
479481} ;
480482
483+ const countOptions = < T extends object > ( optionsOrGroups : OptionOrGroup < T > [ ] ) => {
484+ let count = 0 ;
485+ for ( const optionOrGroup of optionsOrGroups ) {
486+ if ( isGroup ( optionOrGroup ) ) {
487+ count += optionOrGroup . options . length ;
488+ } else {
489+ count ++ ;
490+ }
491+ }
492+ return count ;
493+ } ;
494+
481495export const Picker = typedMemo ( < T extends object > ( props : PickerProps < T > ) => {
482496 const {
483497 getOptionId,
@@ -501,24 +515,9 @@ export const Picker = typedMemo(<T extends object>(props: PickerProps<T>) => {
501515 const $activeOptionId = useAtom ( getFirstOptionId ( optionsOrGroups , getOptionId ) ) ;
502516 const $compactView = useAtom ( true ) ;
503517 const $optionsOrGroups = useAtom ( optionsOrGroups ) ;
504- useEffect ( ( ) => {
505- $optionsOrGroups . set ( optionsOrGroups ) ;
506- } , [ optionsOrGroups , $optionsOrGroups ] ) ;
507- const $totalOptionCount = useComputed ( [ $optionsOrGroups ] , ( optionsOrGroups ) => {
508- let count = 0 ;
509- for ( const optionOrGroup of optionsOrGroups ) {
510- if ( isGroup ( optionOrGroup ) ) {
511- count += optionOrGroup . options . length ;
512- } else {
513- count ++ ;
514- }
515- }
516- return count ;
517- } ) ;
518- const $filteredOptions = useAtom < T [ ] | Group < T > [ ] > ( [ ] ) ;
519- const $flattenedFilteredOptions = useComputed ( [ $filteredOptions ] , ( filteredOptions ) =>
520- flattenOptions ( filteredOptions )
521- ) ;
518+ const $totalOptionCount = useComputed ( [ $optionsOrGroups ] , countOptions ) ;
519+ const $filteredOptions = useAtom < OptionOrGroup < T > [ ] > ( [ ] ) ;
520+ const $flattenedFilteredOptions = useComputed ( [ $filteredOptions ] , flattenOptions ) ;
522521 const $hasOptions = useComputed ( [ $totalOptionCount ] , ( count ) => count > 0 ) ;
523522 const $filteredOptionsCount = useComputed ( [ $flattenedFilteredOptions ] , ( options ) => options . length ) ;
524523 const $hasFilteredOptions = useComputed ( [ $filteredOptionsCount ] , ( count ) => count > 0 ) ;
@@ -542,10 +541,15 @@ export const Picker = typedMemo(<T extends object>(props: PickerProps<T>) => {
542541 [ $filteredOptions , getOptionId , onSelect ]
543542 ) ;
544543
544+ // Sync the picker's nanostores when props change
545545 useEffect ( ( ) => {
546546 $selectedItem . set ( selectedOption ) ;
547547 } , [ $selectedItem , selectedOption ] ) ;
548548
549+ useEffect ( ( ) => {
550+ $optionsOrGroups . set ( optionsOrGroups ) ;
551+ } , [ optionsOrGroups , $optionsOrGroups ] ) ;
552+
549553 const ctx = useMemo (
550554 ( ) =>
551555 ( {
@@ -647,8 +651,8 @@ const PickerSyncer = typedMemo(<T extends object>() => {
647651 return true ;
648652 }
649653 } ) ;
650- $filteredOptions . set ( filtered as T [ ] | Group < T > [ ] ) ;
651- $activeOptionId . set ( getFirstOptionId ( filtered as T [ ] | Group < T > [ ] , getOptionId ) ) ;
654+ $filteredOptions . set ( filtered ) ;
655+ $activeOptionId . set ( getFirstOptionId ( filtered , getOptionId ) ) ;
652656 } else {
653657 const lowercasedSearchTerm = debouncedSearchTerm . toLowerCase ( ) ;
654658 const filtered = [ ] ;
@@ -667,8 +671,8 @@ const PickerSyncer = typedMemo(<T extends object>() => {
667671 }
668672 }
669673 }
670- $filteredOptions . set ( filtered as T [ ] | Group < T > [ ] ) ;
671- $activeOptionId . set ( getFirstOptionId ( filtered as T [ ] | Group < T > [ ] , getOptionId ) ) ;
674+ $filteredOptions . set ( filtered ) ;
675+ $activeOptionId . set ( getFirstOptionId ( filtered , getOptionId ) ) ;
672676 }
673677 } , [
674678 debouncedSearchTerm ,
0 commit comments