reka-ui
Version:
Vue port for Radix UI Primitives.
1 lines • 14.7 kB
Source Map (JSON)
{"version":3,"file":"MenuContentImpl.cjs","sources":["../../src/Menu/MenuContentImpl.vue"],"sourcesContent":["<script lang=\"ts\">\nimport type { Ref } from 'vue'\nimport type {\n GraceIntent,\n Side,\n} from './utils'\nimport type { FocusScopeProps } from '@/FocusScope'\nimport type { RovingFocusGroupEmits } from '@/RovingFocus'\nimport type {\n DismissableLayerEmits,\n DismissableLayerProps,\n} from '@/DismissableLayer'\nimport type { PopperContentProps } from '@/Popper'\n\nimport {\n createContext,\n getActiveElement,\n useArrowNavigation,\n useFocusGuards,\n useForwardExpose,\n useTypeahead,\n} from '@/shared'\nimport { useBodyScrollLock } from '@/shared/useBodyScrollLock'\n\nexport interface MenuContentContext {\n onItemEnter: (event: PointerEvent) => boolean\n onItemLeave: (event: PointerEvent) => void\n onTriggerLeave: (event: PointerEvent) => boolean\n searchRef: Ref<string>\n pointerGraceTimerRef: Ref<number>\n onPointerGraceIntentChange: (intent: GraceIntent | null) => void\n}\n\nexport const [injectMenuContentContext, provideMenuContentContext]\n = createContext<MenuContentContext>('MenuContent')\n\nexport interface MenuContentImplPrivateProps {\n /**\n * When `true`, hover/focus/click interactions will be disabled on elements outside\n * the `DismissableLayer`. Users will need to click twice on outside elements to\n * interact with them: once to close the `DismissableLayer`, and again to trigger the element.\n */\n disableOutsidePointerEvents?: DismissableLayerProps['disableOutsidePointerEvents']\n /**\n * Whether scrolling outside the `MenuContent` should be prevented\n * @defaultValue false\n */\n disableOutsideScroll?: boolean\n\n /**\n * Whether focus should be trapped within the `MenuContent`\n * @defaultValue also\n */\n trapFocus?: FocusScopeProps['trapped']\n}\n\nexport type MenuContentImplEmits = DismissableLayerEmits & Omit<RovingFocusGroupEmits, 'update:currentTabStopId'> & {\n openAutoFocus: [event: Event]\n /**\n * Event handler called when auto-focusing on close.\n * Can be prevented.\n */\n closeAutoFocus: [event: Event]\n}\n\ntype MenuContentImplPrivateEmits = MenuContentImplEmits & {\n /**\n * Handler called when the `DismissableLayer` should be dismissed\n */\n dismiss: []\n}\n\nexport interface MenuContentImplProps\n extends MenuContentImplPrivateProps,\n Omit<PopperContentProps, 'dir'> {\n /**\n * When `true`, keyboard navigation will loop from last item to first, and vice versa.\n * @defaultValue false\n */\n loop?: boolean\n}\n\nexport interface MenuRootContentTypeProps\n extends Omit<MenuContentImplProps, 'disableOutsidePointerEvents' | 'disableOutsideScroll' | 'trapFocus'> {}\n</script>\n\n<script setup lang=\"ts\">\nimport {\n onUnmounted,\n ref,\n toRefs,\n watch,\n} from 'vue'\nimport { injectMenuContext, injectMenuRootContext } from './MenuRoot.vue'\nimport {\n FIRST_LAST_KEYS,\n LAST_KEYS,\n focusFirst,\n getOpenState,\n isMouseEvent,\n isPointerInGraceArea,\n} from './utils'\nimport { FocusScope } from '@/FocusScope'\nimport { DismissableLayer } from '@/DismissableLayer'\nimport {\n PopperContent,\n PopperContentPropsDefaultValue,\n} from '@/Popper'\nimport { RovingFocusGroup } from '@/RovingFocus'\n\nconst props = withDefaults(defineProps<MenuContentImplProps>(), {\n ...PopperContentPropsDefaultValue,\n})\nconst emits = defineEmits<MenuContentImplPrivateEmits>()\nconst menuContext = injectMenuContext()\nconst rootContext = injectMenuRootContext()\n\nconst { trapFocus, disableOutsidePointerEvents, loop } = toRefs(props)\n\nuseFocusGuards()\nuseBodyScrollLock(disableOutsidePointerEvents.value)\n\nconst searchRef = ref('')\nconst timerRef = ref(0)\nconst pointerGraceTimerRef = ref(0)\nconst pointerGraceIntentRef = ref<GraceIntent | null>(null)\nconst pointerDirRef = ref<Side>('right')\nconst lastPointerXRef = ref(0)\nconst currentItemId = ref<string | null>(null)\n\nconst rovingFocusGroupRef = ref<InstanceType<typeof RovingFocusGroup>>()\nconst { forwardRef, currentElement: contentElement } = useForwardExpose()\nconst { handleTypeaheadSearch } = useTypeahead()\n\nwatch(contentElement, (el) => {\n menuContext!.onContentChange(el)\n})\n\nonUnmounted(() => {\n window.clearTimeout(timerRef.value)\n})\n\nfunction isPointerMovingToSubmenu(event: PointerEvent) {\n const isMovingTowards\n = pointerDirRef.value === pointerGraceIntentRef.value?.side\n\n return (\n isMovingTowards\n && isPointerInGraceArea(event, pointerGraceIntentRef.value?.area)\n )\n}\n\nasync function handleMountAutoFocus(event: Event) {\n emits('openAutoFocus', event)\n if (event.defaultPrevented)\n return\n // when opening, explicitly focus the content area only and leave\n // `onEntryFocus` in control of focusing first item\n event.preventDefault()\n contentElement.value?.focus({\n preventScroll: true,\n })\n}\n\nfunction handleKeyDown(event: KeyboardEvent) {\n if (event.defaultPrevented)\n return\n // submenu key events bubble through portals. We only care about keys in this menu.\n const target = event.target as HTMLElement\n const isKeyDownInside\n = target.closest('[data-reka-menu-content]') === event.currentTarget\n const isModifierKey = event.ctrlKey || event.altKey || event.metaKey\n const isCharacterKey = event.key.length === 1\n\n const el = useArrowNavigation(\n event,\n getActiveElement() as HTMLElement,\n contentElement.value,\n {\n loop: loop.value,\n arrowKeyOptions: 'vertical',\n dir: rootContext?.dir.value,\n focus: true,\n attributeName: '[data-reka-collection-item]:not([data-disabled])',\n },\n )\n if (el)\n return el?.focus()\n\n // prevent \"Space\" taken account into handleTypeahead\n if (event.code === 'Space')\n return\n\n const collectionItems = rovingFocusGroupRef.value?.getItems() ?? []\n\n if (isKeyDownInside) {\n // menus should not be navigated using tab key so we prevent it\n if (event.key === 'Tab')\n event.preventDefault()\n if (!isModifierKey && isCharacterKey)\n handleTypeaheadSearch(event.key, collectionItems)\n }\n\n // focus first/last item based on key pressed\n if (event.target !== contentElement.value)\n return\n if (!FIRST_LAST_KEYS.includes(event.key))\n return\n event.preventDefault()\n const candidateNodes = [...collectionItems.map(item => item.ref)]\n if (LAST_KEYS.includes(event.key))\n candidateNodes.reverse()\n focusFirst(candidateNodes)\n}\n\nfunction handleBlur(event: FocusEvent) {\n // clear search buffer when leaving the menu\n // @ts-expect-error the provided currentTarget and target should be HTMLElement\n if (!event?.currentTarget?.contains?.(event.target)) {\n window.clearTimeout(timerRef.value)\n searchRef.value = ''\n }\n}\n\nfunction handlePointerMove(event: PointerEvent) {\n if (!isMouseEvent(event))\n return\n const target = event.target as HTMLElement\n const pointerXHasChanged = lastPointerXRef.value !== event.clientX\n\n // We don't use `event.movementX` for this check because Safari will\n // always return `0` on a pointer event.\n if (\n (event?.currentTarget as HTMLElement)?.contains(target)\n && pointerXHasChanged\n ) {\n const newDir = event.clientX > lastPointerXRef.value ? 'right' : 'left'\n pointerDirRef.value = newDir\n lastPointerXRef.value = event.clientX\n }\n}\n\nprovideMenuContentContext({\n onItemEnter: (event) => {\n // event.preventDefault() we can't prevent pointerMove event\n if (isPointerMovingToSubmenu(event))\n return true\n else\n return false\n },\n onItemLeave: (event) => {\n if (isPointerMovingToSubmenu(event))\n return\n contentElement.value?.focus()\n currentItemId.value = null\n },\n onTriggerLeave: (event) => {\n // event.preventDefault() we can't prevent pointerLeave event\n if (isPointerMovingToSubmenu(event))\n return true\n else\n return false\n },\n searchRef,\n pointerGraceTimerRef,\n onPointerGraceIntentChange: (intent) => {\n pointerGraceIntentRef.value = intent\n },\n})\n</script>\n\n<template>\n <FocusScope\n as-child\n :trapped=\"trapFocus\"\n @mount-auto-focus=\"handleMountAutoFocus\"\n @unmount-auto-focus=\"emits('closeAutoFocus', $event)\"\n >\n <DismissableLayer\n as-child\n :disable-outside-pointer-events=\"disableOutsidePointerEvents\"\n @escape-key-down=\"emits('escapeKeyDown', $event)\"\n @pointer-down-outside=\"emits('pointerDownOutside', $event)\"\n @focus-outside=\"emits('focusOutside', $event)\"\n @interact-outside=\"emits('interactOutside', $event)\"\n @dismiss=\"emits('dismiss')\"\n >\n <RovingFocusGroup\n ref=\"rovingFocusGroupRef\"\n v-model:current-tab-stop-id=\"currentItemId\"\n as-child\n orientation=\"vertical\"\n :dir=\"rootContext.dir.value\"\n :loop=\"loop\"\n @entry-focus=\"(event) => {\n emits('entryFocus', event)\n // only focus first item when using keyboard\n if (!rootContext.isUsingKeyboardRef.value) event.preventDefault();\n }\"\n >\n <PopperContent\n :ref=\"forwardRef\"\n role=\"menu\"\n :as=\"as\"\n :as-child=\"asChild\"\n aria-orientation=\"vertical\"\n data-reka-menu-content\n :data-state=\"getOpenState(menuContext.open.value)\"\n :dir=\"rootContext.dir.value\"\n :side=\"side\"\n :side-offset=\"sideOffset\"\n :align=\"align\"\n :align-offset=\"alignOffset\"\n :avoid-collisions=\"avoidCollisions\"\n :collision-boundary=\"collisionBoundary\"\n :collision-padding=\"collisionPadding\"\n :arrow-padding=\"arrowPadding\"\n :prioritize-position=\"prioritizePosition\"\n :position-strategy=\"positionStrategy\"\n :update-position-strategy=\"updatePositionStrategy\"\n :sticky=\"sticky\"\n :hide-when-detached=\"hideWhenDetached\"\n :reference=\"reference\"\n @keydown=\"handleKeyDown\"\n @blur=\"handleBlur\"\n @pointermove=\"handlePointerMove\"\n >\n <slot />\n </PopperContent>\n </RovingFocusGroup>\n </DismissableLayer>\n </FocusScope>\n</template>\n"],"names":["createContext","injectMenuContext","injectMenuRootContext","toRefs","useFocusGuards","useBodyScrollLock","ref","useForwardExpose","useTypeahead","watch","onUnmounted","isPointerInGraceArea","useArrowNavigation","getActiveElement","FIRST_LAST_KEYS","LAST_KEYS","focusFirst","isMouseEvent"],"mappings":"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"}