vuetify
Version:
Vue Material Component Framework
249 lines (246 loc) • 7.12 kB
JavaScript
// Composables
import { makeDelayProps, useDelay } from "../../composables/delay.mjs";
import { VMenuSymbol } from "../VMenu/shared.mjs"; // Utilities
import { getCurrentInstance, IN_BROWSER, isComponentInstance, propsFactory, SUPPORTS_FOCUS_VISIBLE } from "../../util/index.mjs";
import { computed, effectScope, inject, nextTick, onScopeDispose, ref, watch, watchEffect } from 'vue';
// Types
export const makeActivatorProps = propsFactory({
activator: [String, Object],
activatorProps: {
type: Object,
default: () => ({})
},
openOnClick: {
type: Boolean,
default: undefined
},
openOnHover: Boolean,
openOnFocus: {
type: Boolean,
default: undefined
},
closeOnContentClick: Boolean,
...makeDelayProps()
}, 'v-overlay-activator');
export function useActivator(props, _ref) {
let {
isActive,
isTop
} = _ref;
const activatorEl = ref();
let isHovered = false;
let isFocused = false;
let firstEnter = true;
const openOnFocus = computed(() => props.openOnFocus || props.openOnFocus == null && props.openOnHover);
const openOnClick = computed(() => props.openOnClick || props.openOnClick == null && !props.openOnHover && !openOnFocus.value);
const {
runOpenDelay,
runCloseDelay
} = useDelay(props, value => {
if (value === (props.openOnHover && isHovered || openOnFocus.value && isFocused) && !(props.openOnHover && isActive.value && !isTop.value)) {
if (isActive.value !== value) {
firstEnter = true;
}
isActive.value = value;
}
});
const availableEvents = {
click: e => {
e.stopPropagation();
activatorEl.value = e.currentTarget || e.target;
isActive.value = !isActive.value;
},
mouseenter: e => {
isHovered = true;
activatorEl.value = e.currentTarget || e.target;
runOpenDelay();
},
mouseleave: e => {
isHovered = false;
runCloseDelay();
},
focus: e => {
if (SUPPORTS_FOCUS_VISIBLE && !e.target.matches(':focus-visible')) return;
isFocused = true;
e.stopPropagation();
activatorEl.value = e.currentTarget || e.target;
runOpenDelay();
},
blur: e => {
isFocused = false;
e.stopPropagation();
runCloseDelay();
}
};
const activatorEvents = computed(() => {
const events = {};
if (openOnClick.value) {
events.click = availableEvents.click;
}
if (props.openOnHover) {
events.mouseenter = availableEvents.mouseenter;
events.mouseleave = availableEvents.mouseleave;
}
if (openOnFocus.value) {
events.focus = availableEvents.focus;
events.blur = availableEvents.blur;
}
return events;
});
const contentEvents = computed(() => {
const events = {};
if (props.openOnHover) {
events.mouseenter = () => {
isHovered = true;
runOpenDelay();
};
events.mouseleave = () => {
isHovered = false;
runCloseDelay();
};
}
if (props.closeOnContentClick) {
const menu = inject(VMenuSymbol, null);
events.click = () => {
isActive.value = false;
menu?.closeParents();
};
}
return events;
});
const scrimEvents = computed(() => {
const events = {};
if (props.openOnHover) {
events.mouseenter = () => {
if (firstEnter) {
isHovered = true;
firstEnter = false;
runOpenDelay();
}
};
events.mouseleave = () => {
isHovered = false;
runCloseDelay();
};
}
return events;
});
watch(isTop, val => {
if (val && (props.openOnHover && !isHovered && (!openOnFocus.value || !isFocused) || openOnFocus.value && !isFocused && (!props.openOnHover || !isHovered))) {
isActive.value = false;
}
});
const activatorRef = ref();
watchEffect(() => {
if (!activatorRef.value) return;
nextTick(() => {
const activator = activatorRef.value;
activatorEl.value = isComponentInstance(activator) ? activator.$el : activator;
});
});
const vm = getCurrentInstance('useActivator');
let scope;
watch(() => !!props.activator, val => {
if (val && IN_BROWSER) {
scope = effectScope();
scope.run(() => {
_useActivator(props, vm, {
activatorEl,
activatorEvents
});
});
} else if (scope) {
scope.stop();
}
}, {
flush: 'post',
immediate: true
});
onScopeDispose(() => {
scope?.stop();
});
return {
activatorEl,
activatorRef,
activatorEvents,
contentEvents,
scrimEvents
};
}
function _useActivator(props, vm, _ref2) {
let {
activatorEl,
activatorEvents
} = _ref2;
watch(() => props.activator, (val, oldVal) => {
if (oldVal && val !== oldVal) {
const activator = getActivator(oldVal);
activator && unbindActivatorProps(activator);
}
if (val) {
nextTick(() => bindActivatorProps());
}
}, {
immediate: true
});
watch(() => props.activatorProps, () => {
bindActivatorProps();
});
onScopeDispose(() => {
unbindActivatorProps();
});
function bindActivatorProps() {
let el = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getActivator();
let _props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : props.activatorProps;
if (!el) return;
Object.entries(activatorEvents.value).forEach(_ref3 => {
let [name, cb] = _ref3;
el.addEventListener(name, cb);
});
Object.keys(_props).forEach(k => {
if (_props[k] == null) {
el.removeAttribute(k);
} else {
el.setAttribute(k, _props[k]);
}
});
}
function unbindActivatorProps() {
let el = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getActivator();
let _props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : props.activatorProps;
if (!el) return;
Object.entries(activatorEvents.value).forEach(_ref4 => {
let [name, cb] = _ref4;
el.removeEventListener(name, cb);
});
Object.keys(_props).forEach(k => {
el.removeAttribute(k);
});
}
function getActivator() {
let selector = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : props.activator;
let activator;
if (selector) {
if (selector === 'parent') {
let el = vm?.proxy?.$el?.parentNode;
while (el.hasAttribute('data-no-activator')) {
el = el.parentNode;
}
activator = el;
} else if (typeof selector === 'string') {
// Selector
activator = document.querySelector(selector);
} else if ('$el' in selector) {
// Component (ref)
activator = selector.$el;
} else {
// HTMLElement | Element
activator = selector;
}
}
// The activator should only be a valid element (Ignore comments and text nodes)
activatorEl.value = activator?.nodeType === Node.ELEMENT_NODE ? activator : null;
return activatorEl.value;
}
}
//# sourceMappingURL=useActivator.mjs.map