diff --git a/docs/.vuepress/components/OnOutsidePressExample.vue b/docs/.vuepress/components/OnOutsidePressExample.vue new file mode 100644 index 000000000..7e8d0fe49 --- /dev/null +++ b/docs/.vuepress/components/OnOutsidePressExample.vue @@ -0,0 +1,24 @@ + + + diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index c117cba35..9cb835f07 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -91,7 +91,8 @@ module.exports = { ["composable/event/event", "event"], ["composable/event/onMouseMove", "onMouseMove"], ["composable/event/onResize", "onResize"], - ["composable/event/onScroll", "onScroll"] + ["composable/event/onScroll", "onScroll"], + ["composable/event/onOutsidePress", "onOutsidePress"] ] }, { diff --git a/docs/README.md b/docs/README.md index efa65a638..79b7205c2 100644 --- a/docs/README.md +++ b/docs/README.md @@ -51,6 +51,7 @@ Check out the [examples folder](examples) or start hacking on [codesandbox](http - [Mouse Move](composable/event/onMouseMove) - Attach `mousemove` listener to a DOM element - [Resize](composable/event/onResize) - Attach `resize` listener to a DOM element - [Scroll](composable/event/onScroll) - Attach `scroll` listener to a DOM element +- [onOutsidePress](composable/event/onOutsidePress) - Execute callback when click is outside of element ### Date diff --git a/docs/api/vue-composable.api.md b/docs/api/vue-composable.api.md index 1debba14a..3981f1ea7 100644 --- a/docs/api/vue-composable.api.md +++ b/docs/api/vue-composable.api.md @@ -1260,6 +1260,14 @@ export function useDebounce( options?: Options ): T; +// @public (undocumented) +export function useEvent( + el: RefTyped, + name: K, + listener: (this: Document, ev: DocumentEventMap[K]) => any, + options?: boolean | AddEventListenerOptions +): RemoveEventFunction; + // @public (undocumented) export function useEvent< T extends { @@ -1608,6 +1616,36 @@ export function useOnMouseMove( wait?: number ): MouseMoveResult; +// @public (undocumented) +export function useOnOutsidePress( + el: RefTyped, + onOutsidePressCallback: (ev: MouseEvent) => void +): RemoveEventFunction; + +// @public (undocumented) +export function useOnOutsidePress( + el: RefElement, + onOutsidePressCallback: (ev: MouseEvent) => void +): RemoveEventFunction; + +// @public (undocumented) +export function useOnOutsidePress( + el: Ref | Ref, + onOutsidePressCallback: (ev: MouseEvent) => void +): RemoveEventFunction; + +// @public (undocumented) +export function useOnOutsidePress( + el: Ref, + onOutsidePressCallback: (ev: MouseEvent) => void +): RemoveEventFunction; + +// @public (undocumented) +export function useOnOutsidePress( + el: RefElement, + onOutsidePressCallback: (ev: MouseEvent) => void +): RemoveEventFunction; + // @public (undocumented) export function useOnResize(el: RefTyped, wait: number): ResizeResult; diff --git a/docs/composable/event/onOutsidePress.md b/docs/composable/event/onOutsidePress.md new file mode 100644 index 000000000..65a2ff57a --- /dev/null +++ b/docs/composable/event/onOutsidePress.md @@ -0,0 +1,63 @@ +# useOnOutsidePress + +> Execute callback when click is outside of element + +## Parameters + +```js +import { useOnOutsidePress } from "vue-composable"; + +useOnOutsidePress(element, callback); +``` + +| Parameters | Type | Required | Default | Description | +| ---------- | ------------------------- | -------- | ------- | ---------------------------------------- | +| element | `Ref` | `true` | | Element to keep track if clicked outside | +| callback | `(e: MouseEvent) => void` | `true` | | Callback when clicked outside | + +## Methods + +The `useOnOutsidePress` function exposes the following methods: + +```js +import { useOnOutsidePress } from "vue-composable"; + +const remove = useOnOutsidePress(); +``` + +| Signature | Description | +| --------- | ----------------------------------- | +| `remove` | Manually removes the event listener | + +## Example + + + +### Code + +```vue + + + +``` diff --git a/packages/vue-composable/README.md b/packages/vue-composable/README.md index efa726aab..6833f8964 100644 --- a/packages/vue-composable/README.md +++ b/packages/vue-composable/README.md @@ -53,6 +53,7 @@ Check our [documentation](https://pikax.me/vue-composable/) - [Mouse Move](https://pikax.me/vue-composable/composable/event/onMouseMove) - Attach `mousemove` listener to a DOM element - [Resize](https://pikax.me/vue-composable/composable/event/onResize) - Attach `resize` listener to a DOM element - [Scroll](https://pikax.me/vue-composable/composable/event/onScroll) - Attach `scroll` listener to a DOM element +- [onOutsidePress](https://pikax.me/vue-composable/composable/event/onOutsidePress) - Execute callback when click is outside of element ### Date diff --git a/packages/vue-composable/__tests__/event/onOutsidePress.spec.ts b/packages/vue-composable/__tests__/event/onOutsidePress.spec.ts new file mode 100644 index 000000000..38d500044 --- /dev/null +++ b/packages/vue-composable/__tests__/event/onOutsidePress.spec.ts @@ -0,0 +1,101 @@ +import { createVue } from "../utils"; +import { useOnOutsidePress } from "../../src"; + +describe("onOutsidePress", () => { + const addEventListenerMock = jest.fn(); + let addEventListener: any; + beforeAll(() => { + addEventListener = document.addEventListener; + document.addEventListener = addEventListenerMock; + }); + + beforeEach(() => { + addEventListenerMock.mockClear(); + }); + + afterAll(() => { + document.addEventListener = addEventListener; + }); + + test("should work mousedown", () => { + const element = document.createElement("div"); + let handler: (a: any) => void = {} as any; + + addEventListenerMock.mockImplementation((_, h) => { + handler = h; + }); + + let callback = jest.fn(); + const { mount } = createVue({ + template: "
", + setup() { + useOnOutsidePress(element, callback); + } + }); + mount(); + + expect(callback).not.toHaveBeenCalled(); + + expect(addEventListenerMock).toHaveBeenLastCalledWith( + "mousedown", + expect.any(Function), + { passive: true } + ); + + // inside + handler({ + target: element + }); + expect(callback).not.toHaveBeenCalled(); + + // outside + handler({ + target: document.createElement("div") + }); + expect(callback).toHaveBeenCalledTimes(1); + }); + + // test("should work touchstart", () => { + // const element = document.createElement("div"); + // let handler: (a: any) => void = {} as any; + + // addEventListenerMock.mockImplementation((_, h) => { + // handler = h; + // }); + + // // remove mousedown + // const onmousedown = document.documentElement.onmousedown; + // document.documentElement.onmousedown + + // let callback = jest.fn(); + // const { mount } = createVue({ + // template: "
", + // setup() { + // useOnOutsidePress(element, callback); + // }, + // }); + // mount(); + + // expect(callback).not.toHaveBeenCalled(); + + // expect(addEventListenerMock).toHaveBeenLastCalledWith( + // "touchstart", + // expect.any(Function), + // { passive: true } + // ); + + // // inside + // handler({ + // target: element, + // }); + // expect(callback).not.toHaveBeenCalled(); + + // // outside + // handler({ + // target: document.createElement("div"), + // }); + // expect(callback).toHaveBeenCalledTimes(1); + + // document.documentElement.onmousedown = onmousedown; + // }); +}); diff --git a/packages/vue-composable/src/event/event.ts b/packages/vue-composable/src/event/event.ts index 694706526..3d24b3795 100644 --- a/packages/vue-composable/src/event/event.ts +++ b/packages/vue-composable/src/event/event.ts @@ -3,6 +3,13 @@ import { RefTyped, NO_OP, wrap } from "../utils"; export type RemoveEventFunction = () => void; +export function useEvent( + el: RefTyped, + name: K, + listener: (this: Document, ev: DocumentEventMap[K]) => any, + options?: boolean | AddEventListenerOptions +): RemoveEventFunction; + export function useEvent< T extends { addEventListener: ( @@ -41,12 +48,14 @@ export function useEvent( listener: (this: Document, ev: WindowEventMap[K]) => any, options?: boolean | AddEventListenerOptions ): RemoveEventFunction; + export function useEvent( el: Element | Ref, name: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions ): RemoveEventFunction; + export function useEvent( el: Element | Ref | RefTyped | RefTyped, name: string, diff --git a/packages/vue-composable/src/event/index.ts b/packages/vue-composable/src/event/index.ts index 1bc859f07..dc6a44ec4 100644 --- a/packages/vue-composable/src/event/index.ts +++ b/packages/vue-composable/src/event/index.ts @@ -2,3 +2,4 @@ export * from "./event"; export * from "./onMouseMove"; export * from "./onResize"; export * from "./onScroll"; +export * from "./onOutsidePress"; diff --git a/packages/vue-composable/src/event/onOutsidePress.ts b/packages/vue-composable/src/event/onOutsidePress.ts new file mode 100644 index 000000000..c8a45e0f3 --- /dev/null +++ b/packages/vue-composable/src/event/onOutsidePress.ts @@ -0,0 +1,46 @@ +import { Ref } from "../api"; +import { RemoveEventFunction, useEvent } from "./event"; +import { RefTyped, RefElement, wrap, isClient } from "../utils"; + +const events: Array = ["mousedown", "touchstart"]; + +export function useOnOutsidePress( + el: RefTyped, + onOutsidePressCallback: (ev: MouseEvent) => void +): RemoveEventFunction; + +export function useOnOutsidePress( + el: RefElement, + onOutsidePressCallback: (ev: MouseEvent) => void +): RemoveEventFunction; + +export function useOnOutsidePress( + el: Ref | Ref, + onOutsidePressCallback: (ev: MouseEvent) => void +): RemoveEventFunction; + +export function useOnOutsidePress( + el: Ref, + onOutsidePressCallback: (ev: MouseEvent) => void +): RemoveEventFunction; + +export function useOnOutsidePress( + el: RefElement, + onOutsidePressCallback: (ev: MouseEvent) => void +): RemoveEventFunction; +export function useOnOutsidePress( + el: any, + onOutsidePressCallback: (ev: MouseEvent) => void +): RemoveEventFunction { + if (!isClient) { + return () => {}; + } + const element: Ref = wrap(el); + const handler = (e: MouseEvent) => + element.value && + !element.value.contains(e.target as Node) && + onOutsidePressCallback(e); + + const event = events.find(x => `on${x}` in document.documentElement)!; + return useEvent(document, event, handler, { passive: true }); +} diff --git a/readme.md b/readme.md index efa726aab..6833f8964 100644 --- a/readme.md +++ b/readme.md @@ -53,6 +53,7 @@ Check our [documentation](https://pikax.me/vue-composable/) - [Mouse Move](https://pikax.me/vue-composable/composable/event/onMouseMove) - Attach `mousemove` listener to a DOM element - [Resize](https://pikax.me/vue-composable/composable/event/onResize) - Attach `resize` listener to a DOM element - [Scroll](https://pikax.me/vue-composable/composable/event/onScroll) - Attach `scroll` listener to a DOM element +- [onOutsidePress](https://pikax.me/vue-composable/composable/event/onOutsidePress) - Execute callback when click is outside of element ### Date