import { MdcComponent } from '@aurelia-mdc-web/base' ; import { MDCMenuFoundation , DefaultFocusState , MDCMenuAdapter , Corner , MDCMenuItemComponentEventDetail , strings , cssClasses } from '@material/menu' ; import { MdcMenuSurface } from '@aurelia-mdc-web/menu-surface' ; import { MdcList , IMdcListActionEvent , IMdcListItemElement , IMdcListElement } from '@aurelia-mdc-web/list' ; import { MDCListIndex } from '@material/list' ; import { MDCMenuDistance } from '@material/menu-surface' ; import { numbers as listConstants } from '@material/list/constants' ; import { closest } from '@material/dom/ponyfill' ; import { inject , customElement , bindingMode , useView , PLATFORM } from 'aurelia-framework' ; import { bindable } from 'aurelia-typed-observable-plugin' ; strings . SELECTED_EVENT = strings . SELECTED_EVENT . toLowerCase ( ) ; selector emits @ inject ( Element ) @ customElement ( 'mdc-menu' ) @ useView ( PLATFORM . moduleName ( './mdc-menu.html' ) ) export class MdcMenu extends MdcComponent < MDCMenuFoundation > { menuSurface : MdcMenuSurface ; get list_ ( ) : MdcList | undefined { const el = this . root . querySelector ( 'mdc-list,mdc-deprecated-list' ) ; return ( el as IMdcListElement ) ?. au . controller . viewModel ; } /** Used to indicate that the menu is using fixed positioning */ @ bindable . booleanAttr fixed : boolean ; @ bindable . booleanAttr typeahead : boolean ; async typeaheadChanged ( ) { await this . initialised ; if ( this . list_ ) { this . list_ . typeahead = this . typeahead ; } } /** Makes the menu element direct child of the body */ @ bindable . booleanAttr ( { defaultBindingMode : bindingMode . oneTime } ) hoistToBody : boolean ; /** Set to indicate an element the menu should be anchored to */ @ bindable anchor ? : Element | null ; /** Sets default focus state where the menu should focus every time when menu is opened. Focuses the list root ('list') element by default. */ @ bindable defaultFocusState : keyof typeof DefaultFocusState = 'LIST_ROOT' ; async defaultFocusStateChanged ( ) { await this . initialised ; this . foundation ?. setDefaultFocusState ( DefaultFocusState [ this . defaultFocusState ] ) ; } /** Override the opening point of the menu. (Default: TOP_START) */ @ bindable anchorCorner : keyof typeof Corner ; /** Sets the distance from the anchor point that the menu surface should be shown */ @ bindable anchorMargin : Partial < MDCMenuDistance > ; /** Sets whether the menu should open and close without animation when the open/close methods are called */ @ bindable . booleanAttr quickOpen : boolean ; /** Sets whether the menu surface should stay open after item selection */ @ bindable . booleanAttr stayOpenOnSelection : boolean ; @ bindable maxHeight : number ; /** Sets whether focus should be restored after the menu is closed */ @ bindable . booleanAttr skipRestoreFocus : boolean ; skipRestoreFocusChanged ( ) { if ( this . skipRestoreFocus ) { this . root . setAttribute ( 'data-menu-item-skip-restore-focus' , 'true' ) ; } else { this . root . removeAttribute ( 'data-menu-item-skip-restore-focus' ) ; } } handleKeydown_ ( evt ) { this . foundation ?. handleKeydown ( evt ) ; return true ; } handleItemAction_ ( evt ) { this . foundation ?. handleItemAction ( this . items [ evt . detail . index ] ) ; return true ; } handleMenuSurfaceOpened_ ( ) { this . foundation ?. handleMenuSurfaceOpened ( ) ; return true ; } get open ( ) : boolean { return this . menuSurface . open ; } set open ( value ) { this . menuSurface . open = value ; } /** Toggles the menu to open or close */ toggle ( ) { this . open = ! this . open ; } openAnchored ( anchor ) { this . anchor = anchor ; this . open = true ; } get wrapFocus ( ) : boolean { return this . list_ ? this . list_ . wrapFocus : false ; } set wrapFocus ( value ) { if ( this . list_ ) { this . list_ . wrapFocus = value ; } } return get typeaheadInProgress ( ) { return this . list_ ? this . list_ . typeaheadInProgress : false ; } /**
     * 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 : [ ] ; } 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 ; } } async initialise ( ) { await this . menuSurface . initialised ; if ( this . defaultFocusState !== undefined ) { this . defaultFocusStateChanged ( ) ; } if ( this . hoistToBody ) { this . root . style . position = 'fixed' ; } } 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 ( ) ; } } param corner setAnchorCorner ( corner ) { this . menuSurface . setAnchorCorner ( corner ) ; } 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 '' ; } setAbsolutePosition ( x , y ) { this . menuSurface . setAbsolutePosition ( x , y ) ; } 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 ) => { if ( ! this . stayOpenOnSelection ) { this . menuSurface ?. close ( skipRestoreFocus ) ; } } , getElementIndex : ( element ) => this . items . indexOf ( element ) , notifySelected : ( evtData ) => { const item = this . items [ evtData . index ] ; this . emit < IMdcMenuItemComponentEventDetail > ( strings . SELECTED_EVENT , { index : evtData . index , item , data : ( item as IMdcListItemElement ) . au ?. controller . viewModel . value } ) ; } , 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 ) ; } } export interface IMdcMenuItemComponentEventDetail extends MDCMenuItemComponentEventDetail { data : unknown ; } hidden export interface IMdcMenuItemComponentEvent extends Event { readonly detail : IMdcMenuItemComponentEventDetail ; }