| | |
| | | |
| | | import type { MenuItemRegistered, MenuProvider, SubMenuProps } from '../types'; |
| | | |
| | | import { computed, onBeforeUnmount, onMounted, reactive, ref } from 'vue'; |
| | | import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref } from 'vue'; |
| | | |
| | | import { useNamespace } from '../../../../composables/src'; |
| | | import { VbenHoverCard } from '../../../shadcn-ui/src'; |
| | |
| | | const items = ref<MenuProvider['items']>({}); |
| | | const subMenus = ref<MenuProvider['subMenus']>({}); |
| | | const timer = ref<null | ReturnType<typeof setTimeout>>(null); |
| | | const popupRef = ref<HTMLElement>(); |
| | | const popupTriggerRef = ref<InstanceType<typeof SubMenuContent>>(); |
| | | |
| | | function togglePopup() { |
| | | if (props.disabled) return; |
| | | if (timer.value) window.clearTimeout(timer.value); |
| | | if (opened.value) rootMenu.closeMenu(props.path, parentPaths.value); |
| | | else rootMenu.openMenu(props.path, parentPaths.value); |
| | | } |
| | | |
| | | async function handlePopupKeydown(event: KeyboardEvent) { |
| | | if (props.disabled) return; |
| | | if (['Enter', ' ', 'ArrowDown'].includes(event.key)) { |
| | | event.preventDefault(); |
| | | event.stopPropagation(); |
| | | rootMenu.openMenu(props.path, parentPaths.value); |
| | | await nextTick(); |
| | | popupRef.value?.querySelector<HTMLElement>('[role="menuitem"]')?.focus(); |
| | | } else if (event.key === 'Escape') { |
| | | event.preventDefault(); |
| | | event.stopPropagation(); |
| | | rootMenu.closeMenu(props.path, parentPaths.value); |
| | | const trigger = popupTriggerRef.value?.$el; |
| | | if (trigger instanceof HTMLElement) trigger.focus(); |
| | | } |
| | | } |
| | | |
| | | createSubMenuContext({ |
| | | addSubMenu, |
| | |
| | | > |
| | | <template #trigger> |
| | | <SubMenuContent |
| | | ref="popupTriggerRef" |
| | | :class="is('active', active)" |
| | | :icon="menuIcon" |
| | | :is-menu-more="isSubMenuMore" |
| | | :is-top-level-menu-submenu="isTopLevelMenuSubmenu" |
| | | :level="currentLevel" |
| | | :path="path" |
| | | @click.stop="handleClick" |
| | | role="menuitem" |
| | | :tabindex="disabled ? -1 : 0" |
| | | aria-haspopup="menu" |
| | | :aria-expanded="opened" |
| | | @click.stop="togglePopup" |
| | | @keydown="handlePopupKeydown" |
| | | > |
| | | <template #title> |
| | | <slot name="title"></slot> |
| | |
| | | </SubMenuContent> |
| | | </template> |
| | | <div |
| | | ref="popupRef" |
| | | :class="[nsMenu.is(mode, true), nsMenu.e('popup')]" |
| | | @keydown.esc="handlePopupKeydown" |
| | | @focus="(e) => handleMouseenter(e, 100)" |
| | | @mouseenter="(e) => handleMouseenter(e, 100)" |
| | | @mouseleave="() => handleMouseleave(true)" |
| | |
| | | <ul |
| | | :class="[nsMenu.b(), is('rounded', rounded)]" |
| | | :style="subMenuStyle" |
| | | role="menu" |
| | | > |
| | | <slot></slot> |
| | | </ul> |