import { mergeProps as _mergeProps, createVNode as _createVNode } from "vue"; // Styles import "./VMenu.css"; // Components import { VDialogTransition } from "../transitions/index.mjs"; import { VDefaultsProvider } from "../VDefaultsProvider/index.mjs"; import { VOverlay } from "../VOverlay/index.mjs"; import { makeVOverlayProps } from "../VOverlay/VOverlay.mjs"; // Composables import { forwardRefs } from "../../composables/forwardRefs.mjs"; import { useProxiedModel } from "../../composables/proxiedModel.mjs"; import { useScopeId } from "../../composables/scopeId.mjs"; // Utilities import { computed, inject, mergeProps, nextTick, provide, ref, shallowRef, watch } from 'vue'; import { VMenuSymbol } from "./shared.mjs"; import { focusableChildren, focusChild, genericComponent, getNextElement, getUid, omit, propsFactory, useRender } from "../../util/index.mjs"; // Types export const makeVMenuProps = propsFactory({ // TODO // disableKeys: Boolean, id: String, ...omit(makeVOverlayProps({ closeDelay: 250, closeOnContentClick: true, locationStrategy: 'connected', openDelay: 300, scrim: false, scrollStrategy: 'reposition', transition: { component: VDialogTransition } }), ['absolute']) }, 'VMenu'); export const VMenu = genericComponent()({ name: 'VMenu', props: makeVMenuProps(), emits: { 'update:modelValue': value => true }, setup(props, _ref) { let { slots } = _ref; const isActive = useProxiedModel(props, 'modelValue'); const { scopeId } = useScopeId(); const uid = getUid(); const id = computed(() => props.id || `v-menu-${uid}`); const overlay = ref(); const parent = inject(VMenuSymbol, null); const openChildren = shallowRef(0); provide(VMenuSymbol, { register() { ++openChildren.value; }, unregister() { --openChildren.value; }, closeParents() { setTimeout(() => { if (!openChildren.value) { isActive.value = false; parent?.closeParents(); } }, 40); } }); async function onFocusIn(e) { const before = e.relatedTarget; const after = e.target; await nextTick(); if (isActive.value && before !== after && overlay.value?.contentEl && // We're the topmost menu overlay.value?.globalTop && // It isn't the document or the menu body ![document, overlay.value.contentEl].includes(after) && // It isn't inside the menu body !overlay.value.contentEl.contains(after)) { const focusable = focusableChildren(overlay.value.contentEl); focusable[0]?.focus(); } } watch(isActive, val => { if (val) { parent?.register(); document.addEventListener('focusin', onFocusIn, { once: true }); } else { parent?.unregister(); document.removeEventListener('focusin', onFocusIn); } }); function onClickOutside() { parent?.closeParents(); } function onKeydown(e) { if (props.disabled) return; if (e.key === 'Tab') { const nextElement = getNextElement(focusableChildren(overlay.value?.contentEl, false), e.shiftKey ? 'prev' : 'next', el => el.tabIndex >= 0); if (!nextElement) { isActive.value = false; overlay.value?.activatorEl?.focus(); } } } function onActivatorKeydown(e) { if (props.disabled) return; const el = overlay.value?.contentEl; if (el && isActive.value) { if (e.key === 'ArrowDown') { e.preventDefault(); focusChild(el, 'next'); } else if (e.key === 'ArrowUp') { e.preventDefault(); focusChild(el, 'prev'); } } else if (['ArrowDown', 'ArrowUp'].includes(e.key)) { isActive.value = true; e.preventDefault(); setTimeout(() => setTimeout(() => onActivatorKeydown(e))); } } const activatorProps = computed(() => mergeProps({ 'aria-haspopup': 'menu', 'aria-expanded': String(isActive.value), 'aria-owns': id.value, onKeydown: onActivatorKeydown }, props.activatorProps)); useRender(() => { const overlayProps = VOverlay.filterProps(props); return _createVNode(VOverlay, _mergeProps({ "ref": overlay, "id": id.value, "class": ['v-menu', props.class], "style": props.style }, overlayProps, { "modelValue": isActive.value, "onUpdate:modelValue": $event => isActive.value = $event, "absolute": true, "activatorProps": activatorProps.value, "onClick:outside": onClickOutside, "onKeydown": onKeydown }, scopeId), { activator: slots.activator, default: function () { for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } return _createVNode(VDefaultsProvider, { "root": "VMenu" }, { default: () => [slots.default?.(...args)] }); } }); }); return forwardRefs({ id, ΨopenChildren: openChildren }, overlay); } }); //# sourceMappingURL=VMenu.mjs.map