@lekseek/ui
Version:
Vue 3 components library
93 lines (79 loc) • 2.76 kB
text/typescript
import { Directive } from 'vue';
const HANDLERS_PROPERTY = '__v-click-outside';
const HAS_WINDOWS = typeof window !== 'undefined';
const HAS_NAVIGATOR = typeof navigator !== 'undefined';
const IS_TOUCH =
HAS_WINDOWS &&
('ontouchstart' in window ||
(HAS_NAVIGATOR && (navigator as any).msMaxTouchPoints > 0));
const EVENTS = IS_TOUCH ? ['touchstart'] : ['click'];
function processDirectiveArguments (bindingValue: any) {
const isFunction = typeof bindingValue === 'function';
if (!isFunction && typeof bindingValue !== 'object') {
throw new Error(
'v-click-outside: Binding value must be a function or an object',
);
}
return {
handler: isFunction ? bindingValue : bindingValue.handler,
middleware: bindingValue.middleware || ((item: any) => item),
events: bindingValue.events || EVENTS,
isActive: !(bindingValue.isActive === false),
};
}
function onEvent ({ el, event, handler, middleware }: any) {
// Note: composedPath is not supported on IE and Edge, more information here:
// https://developer.mozilla.org/en-US/docs/Web/API/Event/composedPath
// In the meanwhile, we are using el.contains for those browsers, not
// the ideal solution, but using IE or EDGE is not ideal either.
const path = event.path || (event.composedPath && event.composedPath());
const isClickOutside = path ? path.indexOf(el) < 0 : !el.contains(event.target);
if (!isClickOutside) {
return;
}
if (middleware(event)) {
handler(event);
}
}
function beforeMount (el: any, { value }: any): void {
const { events, handler, middleware, isActive } = processDirectiveArguments(
value,
);
if (!isActive) {
return;
}
el[HANDLERS_PROPERTY] = events.map((eventName: any) => ({
event: eventName,
handler: (event: any) => onEvent({ event, el, handler, middleware }),
}));
el[HANDLERS_PROPERTY].forEach(({ event, handler }: any) =>
setTimeout(() => {
// Note: More info about this implementation can be found here:
// https://github.com/ndelvalle/v-click-outside/issues/137
if (!el[HANDLERS_PROPERTY]) {
return;
}
document.documentElement.addEventListener(event, handler, false);
}, 0),
);
}
function unmounted (el: any) {
const handlers = el[HANDLERS_PROPERTY] || [];
handlers.forEach(({ event, handler }: any) =>
document.documentElement.removeEventListener(event, handler, false),
);
delete el[HANDLERS_PROPERTY];
}
function updated (el: any, { value, oldValue }: any) {
if (JSON.stringify(value) === JSON.stringify(oldValue)) {
return;
}
unmounted(el);
beforeMount(el, { value });
}
const directive: Directive = {
beforeMount,
updated,
unmounted,
};
export default directive;