hook solution:
<template>
  <p ref="myP">does click outside of p : {{ isClickOutside ? 'Yes' : 'No' }}</p>
</template>
<script lang="ts" setup>
import { useOnClickOutside } from './useOnClickOutside'
const myP = ref()
const isClickOutside = useOnClickOutside(myP, () => {
  console.log('click outside of p')
})
</script>
useOnClickOutside.js
import { onBeforeUnmount, onMounted, ref } from 'vue'
// DOMRef is dom by ref
export function useOnClickOutside(DOMRef = null, callback) {
  const isClickOutside = ref(false)
  function handleClick(event) {
    if (DOMRef?.value && !DOM.value.contains(event.target)) {
      callback()
      isClickOutside.value = true
      return
    }
    isClickOutside.value = false
  }
  onMounted(() => {
    document.addEventListener('mousedown', handleClick)
  })
  onBeforeUnmount(() => {
    document.removeEventListener('mousedown', handleClick)
  })
  return isClickOutside
}
component solution:
use:
<template>
   <OnClickOutside :clickOutside="conosole.log('log when clickoutside of div')">
     <div>when clicked outside div</div>
   </OnClickOutside>
</template>
vue2 solution:
<script>
  export default {
    name: 'OnClickOutside',
    props: ['clickOutside'],
    mounted() {
      const listener = e => {
        if (e.target === this.$el || this.$el.contains(e.target)) {
          return
        }
        this.clickOutside()
      }
      document.addEventListener('click', listener)
      this.$once('hook:beforeDestroy', () => document.removeEventListener('click', listener))
    },
    render() {
      return this.$slots.default[0]
    },
  }
</script>
vue3:
<script>
  import { getCurrentInstance, onMounted, onBeforeUnmount, ref, defineComponent } from 'vue'
  export default defineComponent({
    name: 'OnClickOutside',
    props: ['clickOutside'],
    setup(props, { emit, attrs, slots }) {
      const vm = getCurrentInstance()
      const listener = event => {
        const isClickInside = vm.subTree.children.some(element => {
          const el = element.el
          return event.target === el || el.contains(event.target)
        })
        if (isClickInside) {
          console.log('clickInside')
          return
        }
        props.clickOutside && props.clickOutside()
      }
      onMounted(() => {
        document.addEventListener('click', listener)
      })
      onBeforeUnmount(() => {
        document.removeEventListener('click', listener)
      })
      return () => slots.default()
    },
  })
</script>