vue-click-outside-of

Vue 3 directive and hook for detecting click outside an element

Usage no npm install needed!

<script type="module">
  import vueClickOutsideOf from 'https://cdn.skypack.dev/vue-click-outside-of';
</script>

README

vue-click-outside-of

Vue 3 directive and hook for detecting click outside an element.

English | 简体中文

🔧Install

$ npm install --save vue-click-outside-of
or
$ yarn add vue-click-outside-of

🚀Usage

Directive

Edit vue-click-outside-of-directive

<template>
  <div v-click-outside="onClickOutside">target1</div>
  <div v-click-outside="vClickOutsideConfig">target2</div>
</template>

<script>
import { ClickOutside } from "vue-click-outside-of";
export default {
  directives: { ClickOutside },
  data() {
    return {
      vClickOutsideConfig: {
        type: "downUp",
        handler: this.handler,
        before: this.before,
      },
    };
  },
  methods: {
    onClickOutside(mousedownEv, mouseupEv) {
      console.log("Clicked outside of target1");
    },
    handler(mousedownEv, mouseupEv) {
      console.log(
        "Clicked outside of target2 (Using config), before callback returned true"
      );
    },
    before(event) {
      return true;
    },
  },
};
</script>

You can register global directive by following code.
import { createApp } from "vue";
import App from "./App.vue";
import VueClickOutsidePlugin from "vue-click-outside-of";

const app = createApp(App);

app.use(VueClickOutsidePlugin).mount("#app");

Hook

Edit vue-click-outside-of-hook

<template>
  <div ref="target">Inside element</div>
  <div>Outside element</div>
</template>

<script>
import { ref } from "vue";
import { onClickOutside } from "vue-click-outside-of";
export default {
  setup() {
    const target = ref(null);
    const option = {
      type: "downUp",
      before: () => {
        return true;
      },
    };

    onClickOutside((mousedownEv, mouseupEv) => {
      console.log("Clicked outside");
    }, target, option);

    return { target };
  },
};
</script>

🎗️ escape hatch for Teleport

Sometimes, you may not know which elements should be excluded when you register click outside handler. This is why we provide the markSibling method.

Edit vue-click-outside-of-teleport

<template>
  <div ref="childElementRef">inside element</div>
  <teleport to="body">
    <div ref="teleportElementRef">teleport element</div>
  </teleport>
</template>

<script>
// Child.vue
import { onMounted, ref } from "vue";
import { markSibling } from "vue-click-outside-of";

export default {
  setup() {
    const childElementRef = ref();
    const teleportElementRef = ref();
    onMounted(() => {
      // avoid executing the `click outside handler` which registered
      // on the parent component after the element belonging to `<Teleport>` is clicked.
      markSibling(teleportElementRef.value, childElementRef.value);
    });
    return {
      childElementRef,
      teleportElementRef,
    };
  },
};
</script>
<template>
  <div ref="target">
    <Child></Child>
  </div>
  <div>outside element</div>
</template>

<script>
import { onMounted, ref } from "vue";
import { onClickOutside } from "vue-click-outside-of";
import Child from "./Child.vue";

export default {
  components: {
    Child,
  },
  setup() {
    const target = ref();
    onClickOutside(() => {
      console.log("click outside");
    }, target);
    return { target };
  },
};
</script>

🎯Options

export interface ClickOutsideOption<T extends keyof EventMap> {
  /**
   * Indicates which event should trigger click outside handler.
   *
   * - downUp - *default* value. It was composed of mousedown event and mouseup event.
   *   click outside handler will not trigger as long as one of events target is internal element.
   * - click
   * - dblclick
   *
   * @default "all"
   */
  type?: T;
  /**
   * The click outside handler not executed when click target was contained with excluded element.
   *
   * You can use *before* option also to prevent executing click outside handler.
   */
  exclude?: ClickOutsideTarget;
  /**
   * The function will be executed before executing click outside handler.
   * it should return a boolean to decide click outside handler should be fire or not.
   *
   * You can use *exclude* option also if you want to exclude some element only.
   */
  before?: (...args: Parameters<EventMap[T]>) => boolean;
  /**
   * indicates which button was pressed on the mouse to trigger the click outside handler.
   * The option not support `dblclick` type.
   *
   * - "left"
   * - "right"
   * - "all" -  "default value".
   *
   * @default "all"
   */
  button?: Button;
  /**
   * use capture mode when adding the event listener
   * @default false
   */
  capture?: boolean;
  /**
   * @default document.documentElement
   */
  background?: HTMLElement | Document | Window | SVGElement;
}

export interface EventMap {
  // mousedownEv was undefined if user already pressed mouse before register click outside handler.
  downUp: (mousedownEv: MouseEvent | undefined, mouseupEv: MouseEvent) => void;
  click: (ev: MouseEvent) => void;
  dblclick: (ev: MouseEvent) => void;
}

type ClickOutsideRawTarget =
  | Element
  | ComponentPublicInstance
  | ComponentInternalInstance;

export type ClickOutsideTarget =
  | ClickOutsideRawTarget
  | ClickOutsideRawTarget[]
  | Ref<ClickOutsideRawTarget | undefined>
  | Ref<ClickOutsideRawTarget | undefined>[];