269 lines
7.6 KiB
JavaScript
269 lines
7.6 KiB
JavaScript
// Components
|
|
import { VMenuSymbol } from "../VMenu/shared.mjs"; // Composables
|
|
import { makeDelayProps, useDelay } from "../../composables/delay.mjs"; // Utilities
|
|
import { computed, effectScope, inject, mergeProps, nextTick, onScopeDispose, ref, watch, watchEffect } from 'vue';
|
|
import { bindProps, getCurrentInstance, IN_BROWSER, matchesSelector, propsFactory, refElement, unbindProps } from "../../util/index.mjs"; // Types
|
|
export const makeActivatorProps = propsFactory({
|
|
target: [String, Object],
|
|
activator: [String, Object],
|
|
activatorProps: {
|
|
type: Object,
|
|
default: () => ({})
|
|
},
|
|
openOnClick: {
|
|
type: Boolean,
|
|
default: undefined
|
|
},
|
|
openOnHover: Boolean,
|
|
openOnFocus: {
|
|
type: Boolean,
|
|
default: undefined
|
|
},
|
|
closeOnContentClick: Boolean,
|
|
...makeDelayProps()
|
|
}, 'VOverlay-activator');
|
|
export function useActivator(props, _ref) {
|
|
let {
|
|
isActive,
|
|
isTop
|
|
} = _ref;
|
|
const vm = getCurrentInstance('useActivator');
|
|
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 cursorTarget = ref();
|
|
const availableEvents = {
|
|
onClick: e => {
|
|
e.stopPropagation();
|
|
activatorEl.value = e.currentTarget || e.target;
|
|
if (!isActive.value) {
|
|
cursorTarget.value = [e.clientX, e.clientY];
|
|
}
|
|
isActive.value = !isActive.value;
|
|
},
|
|
onMouseenter: e => {
|
|
if (e.sourceCapabilities?.firesTouchEvents) return;
|
|
isHovered = true;
|
|
activatorEl.value = e.currentTarget || e.target;
|
|
runOpenDelay();
|
|
},
|
|
onMouseleave: e => {
|
|
isHovered = false;
|
|
runCloseDelay();
|
|
},
|
|
onFocus: e => {
|
|
if (matchesSelector(e.target, ':focus-visible') === false) return;
|
|
isFocused = true;
|
|
e.stopPropagation();
|
|
activatorEl.value = e.currentTarget || e.target;
|
|
runOpenDelay();
|
|
},
|
|
onBlur: e => {
|
|
isFocused = false;
|
|
e.stopPropagation();
|
|
runCloseDelay();
|
|
}
|
|
};
|
|
const activatorEvents = computed(() => {
|
|
const events = {};
|
|
if (openOnClick.value) {
|
|
events.onClick = availableEvents.onClick;
|
|
}
|
|
if (props.openOnHover) {
|
|
events.onMouseenter = availableEvents.onMouseenter;
|
|
events.onMouseleave = availableEvents.onMouseleave;
|
|
}
|
|
if (openOnFocus.value) {
|
|
events.onFocus = availableEvents.onFocus;
|
|
events.onBlur = availableEvents.onBlur;
|
|
}
|
|
return events;
|
|
});
|
|
const contentEvents = computed(() => {
|
|
const events = {};
|
|
if (props.openOnHover) {
|
|
events.onMouseenter = () => {
|
|
isHovered = true;
|
|
runOpenDelay();
|
|
};
|
|
events.onMouseleave = () => {
|
|
isHovered = false;
|
|
runCloseDelay();
|
|
};
|
|
}
|
|
if (openOnFocus.value) {
|
|
events.onFocusin = () => {
|
|
isFocused = true;
|
|
runOpenDelay();
|
|
};
|
|
events.onFocusout = () => {
|
|
isFocused = false;
|
|
runCloseDelay();
|
|
};
|
|
}
|
|
if (props.closeOnContentClick) {
|
|
const menu = inject(VMenuSymbol, null);
|
|
events.onClick = () => {
|
|
isActive.value = false;
|
|
menu?.closeParents();
|
|
};
|
|
}
|
|
return events;
|
|
});
|
|
const scrimEvents = computed(() => {
|
|
const events = {};
|
|
if (props.openOnHover) {
|
|
events.onMouseenter = () => {
|
|
if (firstEnter) {
|
|
isHovered = true;
|
|
firstEnter = false;
|
|
runOpenDelay();
|
|
}
|
|
};
|
|
events.onMouseleave = () => {
|
|
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;
|
|
}
|
|
});
|
|
watch(isActive, val => {
|
|
if (!val) {
|
|
setTimeout(() => {
|
|
cursorTarget.value = undefined;
|
|
});
|
|
}
|
|
}, {
|
|
flush: 'post'
|
|
});
|
|
const activatorRef = ref();
|
|
watchEffect(() => {
|
|
if (!activatorRef.value) return;
|
|
nextTick(() => {
|
|
activatorEl.value = refElement(activatorRef.value);
|
|
});
|
|
});
|
|
const targetRef = ref();
|
|
const target = computed(() => {
|
|
if (props.target === 'cursor' && cursorTarget.value) return cursorTarget.value;
|
|
if (targetRef.value) return refElement(targetRef.value);
|
|
return getTarget(props.target, vm) || activatorEl.value;
|
|
});
|
|
const targetEl = computed(() => {
|
|
return Array.isArray(target.value) ? undefined : target.value;
|
|
});
|
|
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,
|
|
target,
|
|
targetEl,
|
|
targetRef,
|
|
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;
|
|
bindProps(el, mergeProps(activatorEvents.value, _props));
|
|
}
|
|
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;
|
|
unbindProps(el, mergeProps(activatorEvents.value, _props));
|
|
}
|
|
function getActivator() {
|
|
let selector = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : props.activator;
|
|
const activator = getTarget(selector, vm);
|
|
|
|
// The activator should only be a valid element (Ignore comments and text nodes)
|
|
activatorEl.value = activator?.nodeType === Node.ELEMENT_NODE ? activator : undefined;
|
|
return activatorEl.value;
|
|
}
|
|
}
|
|
function getTarget(selector, vm) {
|
|
if (!selector) return;
|
|
let target;
|
|
if (selector === 'parent') {
|
|
let el = vm?.proxy?.$el?.parentNode;
|
|
while (el?.hasAttribute('data-no-activator')) {
|
|
el = el.parentNode;
|
|
}
|
|
target = el;
|
|
} else if (typeof selector === 'string') {
|
|
// Selector
|
|
target = document.querySelector(selector);
|
|
} else if ('$el' in selector) {
|
|
// Component (ref)
|
|
target = selector.$el;
|
|
} else {
|
|
// HTMLElement | Element | [x, y]
|
|
target = selector;
|
|
}
|
|
return target;
|
|
}
|
|
//# sourceMappingURL=useActivator.mjs.map
|