import React , { useCallback , useState , useRef , useEffect } from 'react' ; import { useFoundation , raf } from '@rmwc/base' ; import { MDCSelectFoundation , MDCSelectAdapter , cssClasses } from '@material/select' ; import { FloatingLabelApi } from '@rmwc/floating-label' ; import { MenuApi } from '@rmwc/menu' ; import { Corner } from '@material/menu-surface' ; import { SelectIconApi } from '../select-icon' ; import { SelectProps } from './' ; export const useSelectFoundation = ( props ) => { const [ notchWidth , setNotchWidth ] = useState < number > ( ) ; const [ lineRippleActive , setLineRippleActive ] = useState ( false ) ; const [ lineRippleCenter , setLineRippleCenter ] = useState ( 0 ) ; const [ floatLabel , setFloatLabel ] = useState ( false ) ; const [ menuOpen , setMenuOpen ] = useState ( false ) ; const [ selectedTextContent , setSelectedTextContent ] = useState ( '' ) ; const selectedIndex = useRef ( - 1 ) ; const floatingLabel = useRef < FloatingLabelApi | null > ( ) ; const setFloatingLabel = ( api ) => { floatingLabel . current = api ; } ; const menu = useRef < MenuApi > ( ) ; const setMenu = ( api ) => { menu . current = api ; } ; const anchor = useRef < HTMLElement | null > ( null ) ; const setAnchor = ( el ) => { anchor . current = el ; } ; const leadingIcon = useRef < SelectIconApi | null > ( ) ; const setLeadingIcon = ( api ) => { leadingIcon . current = api ; } ; const nativeControl = useRef < HTMLSelectElement > ( ) ; const setNativeControl = ( el ) => { nativeControl . current = el ; } ; const silenceChange = useRef ( false ) ; const { foundation , ... elements } = useFoundation ( { props , elements : { rootEl : true , selectedTextEl : true } , foundation : ( { rootEl , selectedTextEl , getProps , emit } ) => { const isNative = ( ) => ! getProps ( ) . enhanced ; const getSelectAdapterMethods = ( ) : Partial < MDCSelectAdapter > => { const items = ( ) : HTMLElement [ ] => ( isNative ( ) ? Array . apply < null , any , HTMLOptionElement [ ] > ( null , nativeControl . current ?. options ) : menu . current ?. items ( ) ) || [ ] ; const getValue = ( el ) => { return ( el . getAttribute ( 'data-value' ) || el . getAttribute ( 'value' ) || '' ) ; } ; return { getSelectedMenuItem : ( ) => { if ( isNative ( ) ) { return nativeControl . current ?. selectedOptions [ 0 ] || null ; } if ( selectedIndex . current === - 1 ) { return ( menu . current ?. getSurfaceElement ( ) ?. querySelector ( '.mdc-list-item--activated' ) || null ) ; } else { return items ( ) [ selectedIndex . current ] ; } } , getMenuItemAttr : ( menuItem , attr ) => { if ( attr === 'data-value' ) { return getValue ( menuItem ) ; } return menuItem . getAttribute ( attr ) ; } , setSelectedText : ( text ) => { setSelectedTextContent ( text ) ; } , isSelectedTextFocused : ( ) => ! ! ( selectedTextEl . ref && selectedTextEl . ref === document . activeElement ) , getSelectedTextAttr : ( attr ) => selectedTextEl . getProp ( attr ) , setSelectedTextAttr : ( attr , value ) => { if ( attr === 'tabindex' ) { if ( isNative ( ) ) return ; attr = 'tabIndex' ; } selectedTextEl . setProp ( attr , value ) ; } , openMenu : ( ) => { setMenuOpen ( true ) ; } , closeMenu : ( ) => { setMenuOpen ( false ) ; } , getAnchorElement : ( ) => anchor . current , setMenuAnchorElement : ( anchorEl ) => setAnchor ( anchorEl ) , setMenuAnchorCorner : ( anchorCorner ) => menu . current ?. setAnchorCorner ( anchorCorner ) , setMenuWrapFocus : ( wrapFocus ) => { } , setAttributeAtIndex : ( ... args ) => menu . current ?. setAttributeForElementIndex ( ... args ) , removeAttributeAtIndex : ( index , attributeName ) => menu . current ?. items ( ) [ index ] ?. removeAttribute ( attributeName ) , focusMenuItemAtIndex : ( ... args ) => menu . current ?. focusItemAtIndex ( ... args ) , getMenuItemCount : ( ) => { return items ( ) . length ; } , getMenuItemValues : ( ) => items ( ) . map ( getValue ) || [ ] , getMenuItemTextAtIndex : ( index ) => { return items ( ) [ index ] . textContent as string ; } , addClassAtIndex : ( ... args ) => menu . current ?. addClassToElementIndex ( ... args ) , removeClassAtIndex : ( ... args ) => menu . current ?. removeClassFromElementAtIndex ( ... args ) } ; } ; const getCommonAdapterMethods = ( ) => { return { addClass : ( className ) => { rootEl . addClass ( className ) ; } , removeClass : ( className ) => { rootEl . removeClass ( className ) ; } , hasClass : ( className ) => rootEl . hasClass ( className ) , isRtl : ( ) => rootEl . ref && window . getComputedStyle ( rootEl . ref ) . getPropertyValue ( 'direction' ) === 'rtl' , setRippleCenter : ( normalizedX ) => { setLineRippleCenter ( normalizedX ) ; } , activateBottomLine : ( ) => setLineRippleActive ( true ) , deactivateBottomLine : ( ) => setLineRippleActive ( false ) , notifyChange : ( value ) => { if ( ! silenceChange . current ) { emit ( 'onChange' , { index : selectedIndex . current , value } , true ) ; } } } ; } ; const getOutlineAdapterMethods = ( ) => { return { hasOutline : ( ) => ! ! getProps ( ) . outlined , notchOutline : ( labelWidth ) => { setNotchWidth ( labelWidth ) ; } , closeOutline : ( ) => { setNotchWidth ( undefined ) ; } } ; } ; const getLabelAdapterMethods = ( ) => { return { hasLabel : ( ) => ! ! getProps ( ) . label , floatLabel : ( shouldFloat ) => { setFloatLabel ( shouldFloat ) ; } , getLabelWidth : ( ) => { return floatingLabel . current ?. getWidth ( ) || 0 ; } } ; } ; const getFoundationMap = ( ) => { return { leadingIcon : ( leadingIcon . current && leadingIcon . current . getFoundation ( ) ) || undefined } ; } ; const f = new MDCSelectFoundation ( { ... getSelectAdapterMethods ( ) , ... getCommonAdapterMethods ( ) , ... getOutlineAdapterMethods ( ) , ... getLabelAdapterMethods ( ) } , getFoundationMap ( ) ) ; const adapter = ( f as any ) . adapter_ as MDCSelectAdapter ; f . updateLabel_ = ( ) => { const doWork = ( ) => { const value = f . getValue ( ) ; const optionHasValue = ! ! getProps ( ) . placeholder || value . length > 0 ; if ( adapter . hasLabel ( ) ) { f . notchOutline ( optionHasValue ) ; if ( ! adapter . hasClass ( cssClasses . FOCUSED ) ) { adapter . floatLabel ( optionHasValue ) ; } } } ; doWork ( ) ; } ; Object . defineProperty ( f , 'menuItemValues_' , { get : ( ) => { return adapter . getMenuItemValues ( ) ; } } ) ; const init = f . init . bind ( f ) ; f . init = ( ) => { silenceChange . current = true ; init ( ) ; const placeholder = String ( getProps ( ) . placeholder || '' ) ; if ( ! f . getValue ( ) && placeholder ) { adapter . setSelectedText ( placeholder ) ; } silenceChange . current = false ; } ; return f ; } } ) ; const { selectedTextEl , rootEl } = elements ; const handleFocus = useCallback ( ( evt ) => { props . onFocus ?. ( evt ) ; foundation . handleFocus ( ) ; } , [ props . onFocus , foundation ] ) ; const handleBlur = useCallback ( ( evt ) => { props . onBlur ?. ( evt ) ; foundation . handleBlur ( ) ; } , [ props . onBlur , foundation ] ) ; const handleClick = useCallback ( ( evt ) => { if ( rootEl . ref ?. querySelector ( '.mdc-menu-surface--open' ) ) { return ; } const getNormalizedXCoordinate = ( evt ) => { const targetClientRect = evt . target . getBoundingClientRect ( ) ; const xCoordinate = evt . clientX ; return xCoordinate - targetClientRect . left ; } ; const coord = getNormalizedXCoordinate ( evt ) ; selectedTextEl . ref && selectedTextEl . ref . focus ( ) ; foundation . handleClick ( coord ) ; } , [ foundation , selectedTextEl . ref , rootEl . ref ] ) ; const handleKeydown = useCallback ( ( evt ) => { props . onKeyDown ?. ( evt ) ; foundation . handleKeydown ( evt ) ; } , [ foundation , props . onKeyDown ] ) ; const handleMenuSelected = useCallback ( ( index ) => { selectedIndex . current = index ; foundation . handleMenuItemAction ( index ) ; } , [ foundation ] ) ; const handleMenuOpened = useCallback ( ( ) => { foundation . handleMenuOpened ( ) ; } , [ foundation ] ) ; const handleMenuClosed = useCallback ( ( ) => { setMenuOpen ( false ) ; foundation . handleMenuClosed ( ) ; } , [ foundation ] ) ; const foundationValue = foundation . getValue ( ) ; const value = props . value ?? ( ( selectedIndex . current === - 1 ? props . defaultValue : undefined ) as string ) ; const optionsLength = Array . isArray ( props . options ) ? props . options . length : Object . values ( props . options || { } ) . length ; useEffect ( ( ) => { silenceChange . current = true ; if ( value !== undefined && value !== foundationValue ) { const index = foundation . menuItemValues_ . indexOf ( value ) ; selectedIndex . current = index ; foundation . setValue ( value || '' ) ; } raf ( ( ) => { silenceChange . current = false ; } ) ; } , [ value , foundationValue , optionsLength , foundation ] ) ; useEffect ( ( ) => { foundation . setDisabled ( ! ! props . disabled ) ; } , [ foundation , props . disabled ] ) ; useEffect ( ( ) => { rootEl . ref && menu . current ?. setAnchorElement ( rootEl . ref ) ; } , [ rootEl . ref ] ) ; return { notchWidth , menuOpen , lineRippleActive , lineRippleCenter , floatLabel , selectedIndex : selectedIndex . current , selectedTextContent , setFloatingLabel , setMenu , setLeadingIcon , setNativeControl , handleFocus , handleBlur , handleClick , handleKeydown , handleMenuClosed , handleMenuOpened , handleMenuSelected , ... elements } ; } ;