import React , { useCallback , useMemo , useRef , useState } from 'react' ; import { isComponentType } from '@leafygreen-ui/lib' ; import { InternalOption , InternalProps as InternalOptionProps , OptionElement , ReactEmpty , } from './Option' ; import { InternalOptionGroup , OptionGroupElement } from './OptionGroup' ; import { isFragment } from 'react-is' ; function isReactEmpty ( value ) { return ( value === undefined || value === null || value === false || value === '' ) ; } export function traverseSelectChildren ( children , optionFn ) { React . Children . forEach ( children , child => { if ( isComponentType < OptionElement > ( child , 'Option' ) ) { optionFn ( child ) ; } else if ( isComponentType < OptionGroupElement > ( child , 'OptionGroup' ) ) { traverseSelectChildren ( child . props . children , option => optionFn ( option , child ) ) ; } else if ( isFragment ( child ) ) { traverseSelectChildren ( child . props . children , optionFn ) ; } } ) ; } export function convertToInternalElements ( children , optionFn , fallbackFn ? ) { return React . Children . map ( children , child => { if ( isComponentType < OptionElement > ( child , 'Option' ) ) { return < InternalOption { ... optionFn ( child ) } / > ; } else if ( isComponentType < OptionGroupElement > ( child , 'OptionGroup' ) ) { const { children : options , ... rest } = child . props ; return ( < InternalOptionGroup className = { undefined } { ... rest } > { convertToInternalElements ( options , option => optionFn ( option , child ) , fallbackFn ) } < /InternalOptionGroup>); } else if ( isFragment ( child ) ) { return convertToInternalElements ( child . props . children , optionFn , fallbackFn ) ; } else if ( ! isReactEmpty ( child ) ) { fallbackFn ?. ( child ) ; } return null ; } ) ; } export function getOptionValue ( option ) { if ( option === null ) { return '' ; } if ( option . props . value !== undefined ) { return option . props . value ; } if ( Array . isArray ( option . props . children ) ) { return option . props . children . filter ( child => ! isReactEmpty ( child ) ) . join ( '' ) ; } return option . props . children . toString ( ) ; } export function isOptionDisabled ( option , group ) { return ( option . props . disabled ?? false ) || ( group ?. props ?. disabled ?? false ) ; } export function isOptionSelectable ( option , group , value ) { return getOptionValue ( option ) === value && ! isOptionDisabled ( option , group ) ; } /**
 * Given the selected option from a previous render, attempts to find a
 * match among the options of the next render to become the next selected
 * option, so that the state of the component doesn't need to be lost.
 */ export function reconcileOption ( children , previousOption ) { let sameInstance ; let sameChildrenAndValue ; let sameExplicitValue ; let sameValue ; traverseSelectChildren ( children , option => { if ( option === previousOption ) { sameInstance = previousOption ; } else if ( option . props . children === previousOption . props . children && option . props . value === previousOption . props . value ) { sameChildrenAndValue ??= option ; } else if ( option . props . value !== undefined && option . props . value === previousOption . props . value ) { sameExplicitValue ??= option ; } else if ( getOptionValue ( option ) === getOptionValue ( previousOption ) ) { sameValue ??= option ; } } ) ; return ( sameInstance ?? sameChildrenAndValue ?? sameExplicitValue ?? sameValue ?? null ) ; } export function useObservedRef < T > ( callback , initialValue , options ) ; export function useObservedRef < T > ( callback , options ? ) ; export function useObservedRef < T > ( callback , options ? ) ; export function useObservedRef < T > ( callback , { initialValue , deps = [ ] , } = { } ) { const ref = useRef ( initialValue ) ; return useMemo ( ( ) => ( { get current ( ) { return ref . current ; } , set current ( nextValue ) { ref . current = nextValue ; callback ( nextValue ) ; } , } ) , [ callback , ref , ... deps , ] ) ; } type SettableRef < T > = React . RefCallback < T > | React . MutableRefObject < T > ; type ValueOrArray < T > = T | ReadonlyArray < T > ; export function useForwardedRef < T > ( forwardedRefOrRefs , initialValue ) ; export function useForwardedRef < T > ( forwardedRefOrRefs , initialValue ) ; export function useForwardedRef < T > ( forwardedRefOrRefs , initialValue ? ) { const forwardValueToRefs = useCallback ( < T > ( forwardedRefOrRefs , nextValue ) => { if ( Array . isArray ( forwardedRefOrRefs ) ) { forwardedRefOrRefs . forEach ( forwardValueToRefs ) ; } else if ( typeof forwardedRefOrRefs === 'function' ) { forwardedRefOrRefs ( nextValue ) ; } else if ( forwardedRefOrRefs ) { forwardedRefOrRefs . current = nextValue ; } } , [ ] ) ; return useObservedRef ( useCallback ( value => forwardValueToRefs ( forwardedRefOrRefs , value ) , [ forwardedRefOrRefs , forwardValueToRefs , ] ) , { initialValue } ) ; } /**
 * TODO: Explore obsoleting `useElementNode` in favor of this
 */ export const useStateRef = < T > ( initialValue ? ) => { const [ value , setValue ] = useState ( initialValue ) ; return useObservedRef ( setValue , { initialValue , deps : [ value ] } ) ; } ;