123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195 |
- 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 { useRtl } from "../../composables/locale.mjs";
- import { useProxiedModel } from "../../composables/proxiedModel.mjs";
- import { useScopeId } from "../../composables/scopeId.mjs"; // Utilities
- import { computed, inject, mergeProps, nextTick, onBeforeUnmount, onDeactivated, provide, ref, shallowRef, watch } from 'vue';
- import { VMenuSymbol } from "./shared.mjs";
- import { focusableChildren, focusChild, genericComponent, getNextElement, getUid, IN_BROWSER, isClickInsideElement, omit, propsFactory, useRender } from "../../util/index.mjs"; // Types
- export const makeVMenuProps = propsFactory({
- // TODO
- // disableKeys: Boolean,
- id: String,
- submenu: Boolean,
- ...omit(makeVOverlayProps({
- closeDelay: 250,
- closeOnContentClick: true,
- locationStrategy: 'connected',
- location: undefined,
- 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 {
- isRtl
- } = useRtl();
- const uid = getUid();
- const id = computed(() => props.id || `v-menu-${uid}`);
- const overlay = ref();
- const parent = inject(VMenuSymbol, null);
- const openChildren = shallowRef(new Set());
- provide(VMenuSymbol, {
- register() {
- openChildren.value.add(uid);
- },
- unregister() {
- openChildren.value.delete(uid);
- },
- closeParents(e) {
- setTimeout(() => {
- if (!openChildren.value.size && !props.persistent && (e == null || overlay.value?.contentEl && !isClickInsideElement(e, overlay.value.contentEl))) {
- isActive.value = false;
- parent?.closeParents();
- }
- }, 40);
- }
- });
- onBeforeUnmount(() => {
- parent?.unregister();
- document.removeEventListener('focusin', onFocusIn);
- });
- onDeactivated(() => isActive.value = false);
- 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();
- if (IN_BROWSER) {
- document.addEventListener('focusin', onFocusIn, {
- once: true
- });
- }
- } else {
- parent?.unregister();
- if (IN_BROWSER) {
- document.removeEventListener('focusin', onFocusIn);
- }
- }
- }, {
- immediate: true
- });
- function onClickOutside(e) {
- parent?.closeParents(e);
- }
- function onKeydown(e) {
- if (props.disabled) return;
- if (e.key === 'Tab' || e.key === 'Enter' && !props.closeOnContentClick) {
- if (e.key === 'Enter' && (e.target instanceof HTMLTextAreaElement || e.target instanceof HTMLInputElement && !!e.target.closest('form'))) return;
- if (e.key === 'Enter') e.preventDefault();
- 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();
- }
- } else if (props.submenu && e.key === (isRtl.value ? 'ArrowRight' : 'ArrowLeft')) {
- 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();
- e.stopImmediatePropagation();
- focusChild(el, 'next');
- } else if (e.key === 'ArrowUp') {
- e.preventDefault();
- e.stopImmediatePropagation();
- focusChild(el, 'prev');
- } else if (props.submenu) {
- if (e.key === (isRtl.value ? 'ArrowRight' : 'ArrowLeft')) {
- isActive.value = false;
- } else if (e.key === (isRtl.value ? 'ArrowLeft' : 'ArrowRight')) {
- e.preventDefault();
- focusChild(el, 'first');
- }
- }
- } else if (props.submenu ? e.key === (isRtl.value ? 'ArrowLeft' : 'ArrowRight') : ['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,
- "location": props.location ?? (props.submenu ? 'end' : 'bottom'),
- "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
|