reka-ui
Version:
Vue port for Radix UI Primitives.
1 lines • 11.7 kB
Source Map (JSON)
{"version":3,"file":"NavigationMenuContentImpl.cjs","sources":["../../src/NavigationMenu/NavigationMenuContentImpl.vue"],"sourcesContent":["<script lang=\"ts\">\nimport type {\n DismissableLayerEmits,\n DismissableLayerProps,\n FocusOutsideEvent,\n} from '@/DismissableLayer'\nimport type { PointerDownOutsideEvent } from '@/DismissableLayer/utils'\nimport { useCollection } from '@/Collection'\n\ntype MotionAttribute = 'to-start' | 'to-end' | 'from-start' | 'from-end'\n\nexport type NavigationMenuContentImplEmits = DismissableLayerEmits\n\nexport interface NavigationMenuContentImplProps extends DismissableLayerProps {}\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, ref, watchEffect } from 'vue'\nimport { injectNavigationMenuContext } from './NavigationMenuRoot.vue'\nimport {\n EVENT_ROOT_CONTENT_DISMISS,\n focusFirst,\n getOpenState,\n getTabbableCandidates,\n makeContentId,\n makeTriggerId,\n} from './utils'\nimport { DismissableLayer } from '@/DismissableLayer'\nimport { getActiveElement, useArrowNavigation, useForwardExpose } from '@/shared'\nimport { injectNavigationMenuItemContext } from './NavigationMenuItem.vue'\n\nconst props = defineProps<NavigationMenuContentImplProps>()\nconst emits = defineEmits<NavigationMenuContentImplEmits>()\n\nconst { getItems } = useCollection({ key: 'NavigationMenu' })\nconst { forwardRef, currentElement } = useForwardExpose()\n\nconst menuContext = injectNavigationMenuContext()\nconst itemContext = injectNavigationMenuItemContext()\n\nconst triggerId = makeTriggerId(menuContext.baseId, itemContext.value)\nconst contentId = makeContentId(menuContext.baseId, itemContext.value)\n\nconst prevMotionAttributeRef = ref<MotionAttribute | null>(null)\nconst motionAttribute = computed(() => {\n const values = getItems().map(i => i.ref.id.split('trigger-')[1])\n if (menuContext.dir.value === 'rtl')\n values.reverse()\n const index = values.indexOf(menuContext.modelValue.value)\n const prevIndex = values.indexOf(menuContext.previousValue.value)\n const isSelected = itemContext.value === menuContext.modelValue.value\n const wasSelected = prevIndex === values.indexOf(itemContext.value)\n\n // We only want to update selected and the last selected content\n // this avoids animations being interrupted outside of that range\n if (!isSelected && !wasSelected)\n return prevMotionAttributeRef.value\n\n const attribute = (() => {\n // Don't provide a direction on the initial open\n if (index !== prevIndex) {\n // If we're moving to this item from another\n if (isSelected && prevIndex !== -1)\n return index > prevIndex ? 'from-end' : 'from-start'\n // If we're leaving this item for another\n if (wasSelected && index !== -1)\n return index > prevIndex ? 'to-start' : 'to-end'\n }\n // Otherwise we're entering from closed or leaving the list\n // entirely and should not animate in any direction\n return null\n })()\n\n // eslint-disable-next-line vue/no-side-effects-in-computed-properties\n prevMotionAttributeRef.value = attribute\n return attribute\n})\n\nfunction handleFocusOutside(ev: FocusOutsideEvent) {\n emits('focusOutside', ev)\n emits('interactOutside', ev)\n\n const target = ev.detail.originalEvent.target as HTMLElement\n if (target.hasAttribute('data-navigation-menu-trigger'))\n ev.preventDefault()\n\n if (!ev.defaultPrevented) {\n itemContext.onContentFocusOutside()\n\n const target = ev.target as HTMLElement\n // Only dismiss content when focus moves outside of the menu\n if (menuContext.rootNavigationMenu?.value?.contains(target))\n ev.preventDefault()\n }\n}\n\nfunction handlePointerDownOutside(ev: PointerDownOutsideEvent) {\n emits('pointerDownOutside', ev)\n\n if (!ev.defaultPrevented) {\n const target = ev.target as HTMLElement\n const isTrigger = getItems().some(i =>\n i.ref.contains(target),\n )\n const isRootViewport\n = menuContext.isRootMenu && menuContext.viewport.value?.contains(target)\n\n if (isTrigger || isRootViewport || !menuContext.isRootMenu)\n ev.preventDefault()\n }\n}\n\nwatchEffect((cleanupFn) => {\n const content = currentElement.value\n if (menuContext.isRootMenu && content) {\n // Bubble dismiss to the root content node and focus its trigger\n const handleClose = () => {\n menuContext.onItemDismiss()\n itemContext.onRootContentClose()\n if (content.contains(getActiveElement()))\n itemContext.triggerRef.value?.focus()\n }\n content.addEventListener(EVENT_ROOT_CONTENT_DISMISS, handleClose)\n\n cleanupFn(() =>\n content.removeEventListener(EVENT_ROOT_CONTENT_DISMISS, handleClose),\n )\n }\n})\n\nfunction handleEscapeKeyDown(ev: KeyboardEvent) {\n emits('escapeKeyDown', ev)\n\n if (!ev.defaultPrevented) {\n menuContext.onItemDismiss()\n itemContext.triggerRef?.value?.focus()\n itemContext.wasEscapeCloseRef.value = true\n }\n}\n\nfunction handleKeydown(ev: KeyboardEvent) {\n // prevent parent menu triggering keydown event\n if ((ev.target as HTMLElement).closest('[data-reka-navigation-menu]') !== menuContext.rootNavigationMenu.value)\n return\n\n const isMetaKey = ev.altKey || ev.ctrlKey || ev.metaKey\n const isTabKey = ev.key === 'Tab' && !isMetaKey\n const candidates = getTabbableCandidates(ev.currentTarget as HTMLElement)\n\n if (isTabKey) {\n const focusedElement = getActiveElement()\n const index = candidates.findIndex(\n candidate => candidate === focusedElement,\n )\n const isMovingBackwards = ev.shiftKey\n const nextCandidates = isMovingBackwards\n ? candidates.slice(0, index).reverse()\n : candidates.slice(index + 1, candidates.length)\n\n if (focusFirst(nextCandidates)) {\n // prevent browser tab keydown because we've handled focus\n ev.preventDefault()\n }\n else {\n // If we can't focus that means we're at the edges\n // so focus the proxy and let browser handle\n // tab/shift+tab keypress on the proxy instead\n itemContext.focusProxyRef.value?.focus()\n return\n }\n }\n\n const newSelectedElement = useArrowNavigation(\n ev,\n getActiveElement() as HTMLElement,\n undefined,\n { itemsArray: candidates, loop: false, enableIgnoredElement: true },\n )\n newSelectedElement?.focus()\n}\n\nfunction handleDismiss() {\n const rootContentDismissEvent = new Event(EVENT_ROOT_CONTENT_DISMISS, {\n bubbles: true,\n cancelable: true,\n })\n currentElement.value?.dispatchEvent(rootContentDismissEvent)\n}\n</script>\n\n<template>\n <DismissableLayer\n :id=\"contentId\"\n :ref=\"forwardRef\"\n :aria-labelledby=\"triggerId\"\n :data-motion=\"motionAttribute\"\n :data-state=\"getOpenState(menuContext.modelValue.value === itemContext.value)\"\n :data-orientation=\"menuContext.orientation\"\n v-bind=\"props\"\n @keydown=\"handleKeydown\"\n @escape-key-down=\"handleEscapeKeyDown\"\n @pointer-down-outside=\"handlePointerDownOutside\"\n @focus-outside=\"handleFocusOutside\"\n @dismiss=\"handleDismiss\"\n >\n <slot />\n </DismissableLayer>\n</template>\n"],"names":["useCollection","useForwardExpose","injectNavigationMenuContext","injectNavigationMenuItemContext","makeTriggerId","makeContentId","ref","computed","target","watchEffect","getActiveElement","EVENT_ROOT_CONTENT_DISMISS","getTabbableCandidates","focusFirst","useArrowNavigation"],"mappings":"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"}