UNPKG

vue-material-adapter

Version:

Vue 3 wrapper arround Material Components for the Web

289 lines (235 loc) 7.47 kB
import { closest } from '@material/dom/ponyfill.js'; import { MDCMenuFoundation } from '@material/menu/foundation.js'; import { computed, onBeforeUnmount, onMounted, reactive, toRefs, watch, } from 'vue'; import { emitCustomEvent } from '../base/index.js'; const { cssClasses, strings } = MDCMenuFoundation; export default { name: 'mcw-menu', props: { modelValue: [Boolean, Object], quickOpen: Boolean, anchorCorner: [String, Number], anchorMargin: Object, fixed: Boolean, absolutePosition: Array, typeAhead: Boolean, singleSelection: { type: Boolean, default: () => true }, defaultFocusState: { type: String, default: () => 'LIST_ROOT' }, }, setup(props, { emit }) { const uiState = reactive({ classes: {}, styles: {}, menuOpen: false, myWrapFocus: true, menuSurface: undefined, list: undefined, }); let foundation; const getListItemByIndex = index => { return uiState.list?.getListItemByIndex(index); }; const getListElementByIndex = index => uiState.list?.getListElementByIndex(index); const surfaceOpen = computed({ get() { return uiState.menuOpen; }, set(value) { uiState.menuOpen = value; }, }); const wrapFocus = computed({ get() { return uiState.myWrapFocus; }, set(nv) { uiState.myWrapFocus = nv; }, }); const layout = () => uiState.list?.layout(); const handleAction = index => { foundation.handleItemAction(getListElementByIndex(index)); }; const handleKeydown = event_ => foundation.handleKeydown(event_); const handleMenuSurfaceOpened = () => { foundation.handleMenuSurfaceOpened(); emit('mdcmenusurface:opened'); }; const handleMenuSurfaceClosed = () => { emit('mdcmenusurface:closed'); }; const onChange = item => { uiState.menuOpen = item; emit('update:modelValue', item); }; const setAnchorCorner = corner => { uiState.menuSurface.setAnchorCorner(corner); }; const setAnchorElement = element => { uiState.menuSurface.setMenuSurfaceAnchorElement(element); }; const setSelectedIndex = index => uiState.list?.setSelectedIndex(index); const getSelectedIndex = () => uiState.list?.getSelectedIndex() ?? -1; const setAnchorMargin = margin => { uiState.menuSurface.setAnchorMargin(margin); }; const getPrimaryTextAtIndex = index => { const item = getListElementByIndex(index); if (item && uiState.list) { return uiState.list.getPrimaryText(item) || ''; } return ''; }; const setFixedPosition = isFixed => { uiState.menuSurface.setFixedPosition(isFixed); }; const setIsHoisted = isHoisted => { uiState.menuSurface.setIsHoisted(isHoisted); }; const setAbsolutePosition = (x, y) => { uiState.menuSurface.setAbsolutePosition(x, y); }; const typeaheadInProgress = () => uiState.list.typeAheadInProgress ?? false; const typeaheadMatchItem = (nextChar, startingIndex) => { if (uiState.list) { return uiState.list.typeaheadMatchItem(nextChar, startingIndex); } return -1; }; const setSingleSelection = singleSelection => uiState.list?.setSingleSelection(singleSelection); const focusItemAtIndex = index => getListItemByIndex(index).focus(); const getMenuItemCount = () => uiState.list.getListItemCount(); const adapter = { addClassToElementAtIndex: (index, className) => { const listItem = getListItemByIndex(index); listItem.addClass(className); }, removeClassFromElementAtIndex: (index, className) => { const listItem = getListItemByIndex(index); listItem.removeClass(className); }, addAttributeToElementAtIndex: (index, attribute, value) => { const listItem = getListItemByIndex(index); listItem.setAttribute(attribute, value); }, removeAttributeFromElementAtIndex: (index, attribute) => { const listItem = getListItemByIndex(index); listItem.removeAttribute(attribute); }, getAttributeFromElementAtIndex: (index, attribute) => { const listItem = getListItemByIndex(index); return listItem.getAttribute(attribute); }, elementContainsClass: (element, className) => element.classList.contains(className), closeSurface: skipRestoreFocus => { uiState.menuSurface.close(skipRestoreFocus); emit('update:modelValue', false); }, getElementIndex: element => { return uiState.list?.getListElementIndex(element); }, notifySelected: eventData => { const item = getListElementByIndex(eventData.index); const rootElement = uiState.menuSurface.$el; emitCustomEvent(rootElement, strings.SELECTED_EVENT, { index: eventData.index, item, }); emit('select', { index: eventData.index, item, }); }, getMenuItemCount, focusItemAtIndex, focusListRoot: () => { uiState.list.focus(); }, isSelectableItemAtIndex: index => { const item = getListElementByIndex(index); return !!closest(item, `.${cssClasses.MENU_SELECTION_GROUP}`); }, getSelectedSiblingOfItemAtIndex: index => { const item = getListElementByIndex(index); const selectionGroupElement = closest( item, `.${cssClasses.MENU_SELECTION_GROUP}`, ); const selectedItemElement = selectionGroupElement.querySelector( `.${cssClasses.MENU_SELECTED_LIST_ITEM}`, ); return selectedItemElement ? uiState.list.getListElementIndex(selectedItemElement) : -1; }, }; watch( () => props.modelValue, nv => { uiState.menuOpen = nv; }, ); onMounted(() => { uiState.menuOpen = props.modelValue; foundation = new MDCMenuFoundation(adapter); foundation.init(); if (props.fixed) { uiState.menuSurface.setFixedPosition(props.fixed); } if (props.absolutePosition) { const [x, y] = props.absolutePosition; uiState.menuSurface.setAbsolutePosition(x, y); } foundation.setDefaultFocusState(props.defaultFocusState); }); onBeforeUnmount(() => { foundation.destroy(); }); const getMenuItemValues = attribute => { const le = uiState.list?.listElements; const returnValue = le.map( element => element.getAttribute(attribute) || '', ); return returnValue; }; const getMenuItemTextAtIndex = index => uiState.list?.listElements[index]?.textContent; return { ...toRefs(uiState), handleAction, handleKeydown, onChange, handleMenuSurfaceOpened, handleMenuSurfaceClosed, setAbsolutePosition, setIsHoisted, setFixedPosition, setAnchorMargin, setAnchorElement, setAnchorCorner, getSelectedIndex, setSelectedIndex, wrapFocus, surfaceOpen, layout, getPrimaryTextAtIndex, typeaheadInProgress, typeaheadMatchItem, setSingleSelection, focusItemAtIndex, getMenuItemCount, getMenuItemValues, getMenuItemTextAtIndex, }; }, };