license import { MDCComponent } from '@material/base/component' ; import { CustomEventListener , SpecificEventListener } from '@material/base/types' ; import { closest } from '@material/dom/ponyfill' ; import { MDCList , MDCListFactory } from '@material/list/component' ; import { numbers as listConstants } from '@material/list/constants' ; import { MDCListFoundation } from '@material/list/foundation' ; import { MDCListActionEvent , MDCListIndex } from '@material/list/types' ; import { MDCMenuSurface , MDCMenuSurfaceFactory } from '@material/menu-surface/component' ; import { Corner } from '@material/menu-surface/constants' ; import { MDCMenuSurfaceFoundation } from '@material/menu-surface/foundation' ; import { MDCMenuDistance } from '@material/menu-surface/types' ; import { MDCMenuAdapter } from './adapter' ; import { cssClasses , DefaultFocusState , strings } from './constants' ; import { MDCMenuFoundation } from './foundation' ; import { MDCMenuItemComponentEventDetail } from './types' ; export type MDCMenuFactory = ( el : Element , foundation ? : MDCMenuFoundation ) => MDCMenu ; export class MDCMenu extends MDCComponent < MDCMenuFoundation > { static override attachTo ( root ) { return new MDCMenu ( root ) ; } private menuSurfaceFactory ! : MDCMenuSurfaceFactory ; private listFactory ! : MDCListFactory ; private menuSurface ! : MDCMenuSurface ; private list ! : MDCList | null ; private handleKeydown ! : SpecificEventListener < 'keydown' > ; private handleItemAction ! : CustomEventListener < MDCListActionEvent > ; private handleMenuSurfaceOpened ! : EventListener ; override initialize ( menuSurfaceFactory = ( el ) => new MDCMenuSurface ( el ) , listFactory = ( el ) => new MDCList ( el ) ) { this . menuSurfaceFactory = menuSurfaceFactory ; this . listFactory = listFactory ; } override initialSyncWithDOM ( ) { this . menuSurface = this . menuSurfaceFactory ( this . root ) ; const list = this . root . querySelector ( strings . LIST_SELECTOR ) ; if ( list ) { this . list = this . listFactory ( list ) ; this . list . wrapFocus = true ; } else { this . list = null ; } this . handleKeydown = ( evt ) => { this . foundation . handleKeydown ( evt ) ; } ; this . handleItemAction = ( evt ) => { this . foundation . handleItemAction ( this . items [ evt . detail . index ] ) ; } ; this . handleMenuSurfaceOpened = ( ) => { this . foundation . handleMenuSurfaceOpened ( ) ; } ; this . menuSurface . listen ( MDCMenuSurfaceFoundation . strings . OPENED_EVENT , this . handleMenuSurfaceOpened ) ; this . listen ( 'keydown' , this . handleKeydown ) ; this . listen ( MDCListFoundation . strings . ACTION_EVENT , this . handleItemAction ) ; } override destroy ( ) { if ( this . list ) { this . list . destroy ( ) ; } this . menuSurface . destroy ( ) ; this . menuSurface . unlisten ( MDCMenuSurfaceFoundation . strings . OPENED_EVENT , this . handleMenuSurfaceOpened ) ; this . unlisten ( 'keydown' , this . handleKeydown ) ; this . unlisten ( MDCListFoundation . strings . ACTION_EVENT , this . handleItemAction ) ; super . destroy ( ) ; } get open ( ) : boolean { return this . menuSurface . isOpen ( ) ; } set open ( value ) { if ( value ) { this . menuSurface . open ( ) ; } else { this . menuSurface . close ( ) ; } } get wrapFocus ( ) : boolean { return this . list ? this . list . wrapFocus : false ; } set wrapFocus ( value ) { if ( this . list ) { this . list . wrapFocus = value ; } } param value set hasTypeahead ( value ) { if ( this . list ) { this . list . hasTypeahead = value ; } } return get typeaheadInProgress ( ) { return this . list ? this . list . typeaheadInProgress : false ; } param nextChar param startingIndex return typeaheadMatchItem ( nextChar , startingIndex ? ) { if ( this . list ) { return this . list . typeaheadMatchItem ( nextChar , startingIndex ) ; } return - 1 ; } /**
     * Layout the underlying list element in the case of any dynamic updates
     * to its structure.
     */ layout ( ) { if ( this . list ) { this . list . layout ( ) ; } } /**
     * Return the items within the menu. Note that this only contains the set of elements within
     * the items container that are proper list items, and not supplemental / presentational DOM
     * elements.
     */ get items ( ) : Element [ ] { return this . list ? this . list . listElements : [ ] ; } param singleSelection set singleSelection ( singleSelection ) { if ( this . list ) { this . list . singleSelection = singleSelection ; } } return get selectedIndex ( ) : MDCListIndex { return this . list ? this . list . selectedIndex : listConstants . UNSET_INDEX ; } param index set selectedIndex ( index ) { if ( this . list ) { this . list . selectedIndex = index ; } } set quickOpen ( quickOpen ) { this . menuSurface . quickOpen = quickOpen ; } param focusState setDefaultFocusState ( focusState ) { this . foundation . setDefaultFocusState ( focusState ) ; } param corner setAnchorCorner ( corner ) { this . menuSurface . setAnchorCorner ( corner ) ; } setAnchorMargin ( margin ) { this . menuSurface . setAnchorMargin ( margin ) ; } param index setSelectedIndex ( index ) { this . foundation . setSelectedIndex ( index ) ; } param index param isEnabled setEnabled ( index , isEnabled ) { this . foundation . setEnabled ( index , isEnabled ) ; } return getOptionByIndex ( index ) { const items = this . items ; if ( index < items . length ) { return this . items [ index ] ; } else { return null ; } } param index return getPrimaryTextAtIndex ( index ) { const item = this . getOptionByIndex ( index ) ; if ( item && this . list ) { return this . list . getPrimaryText ( item ) || '' ; } return '' ; } setFixedPosition ( isFixed ) { this . menuSurface . setFixedPosition ( isFixed ) ; } setIsHoisted ( isHoisted ) { this . menuSurface . setIsHoisted ( isHoisted ) ; } setAbsolutePosition ( x , y ) { this . menuSurface . setAbsolutePosition ( x , y ) ; } /**
     * Sets the element that the menu-surface is anchored to.
     */ setAnchorElement ( element ) { this . menuSurface . anchorElement = element ; } override getDefaultFoundation ( ) { const adapter = { addClassToElementAtIndex : ( index , className ) => { const list = this . items ; list [ index ] . classList . add ( className ) ; } , removeClassFromElementAtIndex : ( index , className ) => { const list = this . items ; list [ index ] . classList . remove ( className ) ; } , addAttributeToElementAtIndex : ( index , attr , value ) => { const list = this . items ; list [ index ] . setAttribute ( attr , value ) ; } , removeAttributeFromElementAtIndex : ( index , attr ) => { const list = this . items ; list [ index ] . removeAttribute ( attr ) ; } , getAttributeFromElementAtIndex : ( index , attr ) => { const list = this . items ; return list [ index ] . getAttribute ( attr ) ; } , elementContainsClass : ( element , className ) => element . classList . contains ( className ) , closeSurface : ( skipRestoreFocus ) => { this . menuSurface . close ( skipRestoreFocus ) ; } , getElementIndex : ( element ) => this . items . indexOf ( element ) , notifySelected : ( evtData ) => { this . emit < MDCMenuItemComponentEventDetail > ( strings . SELECTED_EVENT , { index : evtData . index , item : this . items [ evtData . index ] , } ) ; } , getMenuItemCount : ( ) => this . items . length , focusItemAtIndex : ( index ) => { ( this . items [ index ] as HTMLElement ) . focus ( ) ; } , focusListRoot : ( ) => { ( this . root . querySelector ( strings . LIST_SELECTOR ) as HTMLElement ) . focus ( ) ; } , isSelectableItemAtIndex : ( index ) => ! ! closest ( this . items [ index ] , `.${ cssClasses . MENU_SELECTION_GROUP }` ) , getSelectedSiblingOfItemAtIndex : ( index ) => { const selectionGroupEl = closest ( this . items [ index ] , `.${ cssClasses . MENU_SELECTION_GROUP }` ) as HTMLElement ; const selectedItemEl = selectionGroupEl . querySelector ( `.${ cssClasses . MENU_SELECTED_LIST_ITEM }` ) ; return selectedItemEl ? this . items . indexOf ( selectedItemEl ) : - 1 ; } , } ; return new MDCMenuFoundation ( adapter ) ; } }