From 0aba38a98ab5a0108cd68767bd28dbaf905ae339 Mon Sep 17 00:00:00 2001 From: ngorin Date: Thu, 7 Sep 2023 13:02:31 +0300 Subject: [PATCH 1/9] feat: create useOnClickOutside hook --- .../utils/useOnClickOutside/index.ts | 0 .../useOnClickOutside/useOnClickOutside.tsx | 36 +++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 src/components/utils/useOnClickOutside/index.ts create mode 100644 src/components/utils/useOnClickOutside/useOnClickOutside.tsx diff --git a/src/components/utils/useOnClickOutside/index.ts b/src/components/utils/useOnClickOutside/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/components/utils/useOnClickOutside/useOnClickOutside.tsx b/src/components/utils/useOnClickOutside/useOnClickOutside.tsx new file mode 100644 index 0000000000..e3be395fcb --- /dev/null +++ b/src/components/utils/useOnClickOutside/useOnClickOutside.tsx @@ -0,0 +1,36 @@ +import React from 'react'; + +export type UseOnClickOutsideType = ({ + observedRef, + enabled, +}: { + observedRef: React.RefObject; + enabled?: boolean; + handleCallback?: () => void; +}) => void; + +export const useOnClickOutside: UseOnClickOutsideType = ({ + observedRef, + enabled = true, + handleCallback, +}) => { + React.useEffect(() => { + if (enabled && observedRef) { + const handler = (e: MouseEvent) => { + const elem = observedRef?.current; + + if (elem && !elem.contains(e.target as Node) && handleCallback) { + handleCallback(); + } + }; + + window.addEventListener('click', handler, {capture: true}); + + return () => { + window.removeEventListener('click', handler, {capture: true}); + }; + } + + return undefined; + }, [enabled, handleCallback, observedRef]); +}; From 52a1c42fc67439a2cdcb2ee7375c88a6820f6e37 Mon Sep 17 00:00:00 2001 From: ngorin Date: Wed, 13 Sep 2023 18:12:16 +0300 Subject: [PATCH 2/9] feat: create useOnClickOutside hook --- .../useOnClickOutside/__tests__/Demo.tsx | 33 +++++++++++++++++++ .../__tests__/useOnClickOutside.test.tsx | 20 +++++++++++ .../utils/useOnClickOutside/index.ts | 2 ++ 3 files changed, 55 insertions(+) create mode 100644 src/components/utils/useOnClickOutside/__tests__/Demo.tsx create mode 100644 src/components/utils/useOnClickOutside/__tests__/useOnClickOutside.test.tsx diff --git a/src/components/utils/useOnClickOutside/__tests__/Demo.tsx b/src/components/utils/useOnClickOutside/__tests__/Demo.tsx new file mode 100644 index 0000000000..3d583ba75f --- /dev/null +++ b/src/components/utils/useOnClickOutside/__tests__/Demo.tsx @@ -0,0 +1,33 @@ +/* eslint-disable jsx-a11y/click-events-have-key-events */ +import React from 'react'; + +import {useOnClickOutside} from '../useOnClickOutside'; + +export const Demo = () => { + const observerRef = React.useRef(null); + const [status, setStatus] = React.useState<1 | 0>(0); + + const handleOutsideClick = () => { + setStatus(0); + }; + + const handleClick = () => { + setStatus(1); + }; + + useOnClickOutside({ + observedRef: observerRef, + enabled: true, + handleCallback: handleOutsideClick, + }); + + return ( +
+

{status}

+
+ {'Target'} +
+
{'Outside'}
+
+ ); +}; diff --git a/src/components/utils/useOnClickOutside/__tests__/useOnClickOutside.test.tsx b/src/components/utils/useOnClickOutside/__tests__/useOnClickOutside.test.tsx new file mode 100644 index 0000000000..ff396575b1 --- /dev/null +++ b/src/components/utils/useOnClickOutside/__tests__/useOnClickOutside.test.tsx @@ -0,0 +1,20 @@ +import React from 'react'; + +import {render, screen} from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import {Demo} from './Demo'; + +test('Check useOnClickOutside correct work', async () => { + render(); + + expect(screen.getByRole('heading')).toHaveTextContent('0'); + + await userEvent.click(screen.getByText('Target')); + + expect(screen.getByRole('heading')).toHaveTextContent('1'); + + await userEvent.click(screen.getByText('Outside')); + + expect(screen.getByRole('heading')).toHaveTextContent('0'); +}); diff --git a/src/components/utils/useOnClickOutside/index.ts b/src/components/utils/useOnClickOutside/index.ts index e69de29bb2..bd065fd97e 100644 --- a/src/components/utils/useOnClickOutside/index.ts +++ b/src/components/utils/useOnClickOutside/index.ts @@ -0,0 +1,2 @@ +export {useOnClickOutside} from './useOnClickOutside'; +export type {UseOnClickOutsideType} from './useOnClickOutside'; From 7a7062efc63150eb6c5add82b9112e65e3fb0408 Mon Sep 17 00:00:00 2001 From: ngorin Date: Wed, 13 Sep 2023 18:24:54 +0300 Subject: [PATCH 3/9] feat: add jsdoc for useOnClickOutside --- .../utils/useOnClickOutside/useOnClickOutside.tsx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/components/utils/useOnClickOutside/useOnClickOutside.tsx b/src/components/utils/useOnClickOutside/useOnClickOutside.tsx index e3be395fcb..7bd9c3bc79 100644 --- a/src/components/utils/useOnClickOutside/useOnClickOutside.tsx +++ b/src/components/utils/useOnClickOutside/useOnClickOutside.tsx @@ -9,6 +9,15 @@ export type UseOnClickOutsideType = ({ handleCallback?: () => void; }) => void; +/** + * Hook for observing clicks outside a given target + * + * @param observedRef - purpose of observation + * @param enabled - enabled/disable flag + * @param handleCallback - callback when a click is triggered outside the observation target + * + * @return - nothing + */ export const useOnClickOutside: UseOnClickOutsideType = ({ observedRef, enabled = true, From 672456591f66ad6211134bd516634de7b7ab3656 Mon Sep 17 00:00:00 2001 From: ngorin Date: Thu, 14 Sep 2023 11:35:42 +0300 Subject: [PATCH 4/9] feat: fix review useOutsideClick --- .../utils/useOnClickOutside/index.ts | 2 - .../useOnClickOutside/useOnClickOutside.tsx | 45 ------------------- .../__tests__/Demo.tsx | 9 ++-- .../__tests__/useOutsideClick.test.tsx} | 2 +- src/components/utils/useOutsideClick/index.ts | 2 + .../utils/useOutsideClick/useOutsideClick.tsx | 40 +++++++++++++++++ 6 files changed, 47 insertions(+), 53 deletions(-) delete mode 100644 src/components/utils/useOnClickOutside/index.ts delete mode 100644 src/components/utils/useOnClickOutside/useOnClickOutside.tsx rename src/components/utils/{useOnClickOutside => useOutsideClick}/__tests__/Demo.tsx (76%) rename src/components/utils/{useOnClickOutside/__tests__/useOnClickOutside.test.tsx => useOutsideClick/__tests__/useOutsideClick.test.tsx} (89%) create mode 100644 src/components/utils/useOutsideClick/index.ts create mode 100644 src/components/utils/useOutsideClick/useOutsideClick.tsx diff --git a/src/components/utils/useOnClickOutside/index.ts b/src/components/utils/useOnClickOutside/index.ts deleted file mode 100644 index bd065fd97e..0000000000 --- a/src/components/utils/useOnClickOutside/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export {useOnClickOutside} from './useOnClickOutside'; -export type {UseOnClickOutsideType} from './useOnClickOutside'; diff --git a/src/components/utils/useOnClickOutside/useOnClickOutside.tsx b/src/components/utils/useOnClickOutside/useOnClickOutside.tsx deleted file mode 100644 index 7bd9c3bc79..0000000000 --- a/src/components/utils/useOnClickOutside/useOnClickOutside.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import React from 'react'; - -export type UseOnClickOutsideType = ({ - observedRef, - enabled, -}: { - observedRef: React.RefObject; - enabled?: boolean; - handleCallback?: () => void; -}) => void; - -/** - * Hook for observing clicks outside a given target - * - * @param observedRef - purpose of observation - * @param enabled - enabled/disable flag - * @param handleCallback - callback when a click is triggered outside the observation target - * - * @return - nothing - */ -export const useOnClickOutside: UseOnClickOutsideType = ({ - observedRef, - enabled = true, - handleCallback, -}) => { - React.useEffect(() => { - if (enabled && observedRef) { - const handler = (e: MouseEvent) => { - const elem = observedRef?.current; - - if (elem && !elem.contains(e.target as Node) && handleCallback) { - handleCallback(); - } - }; - - window.addEventListener('click', handler, {capture: true}); - - return () => { - window.removeEventListener('click', handler, {capture: true}); - }; - } - - return undefined; - }, [enabled, handleCallback, observedRef]); -}; diff --git a/src/components/utils/useOnClickOutside/__tests__/Demo.tsx b/src/components/utils/useOutsideClick/__tests__/Demo.tsx similarity index 76% rename from src/components/utils/useOnClickOutside/__tests__/Demo.tsx rename to src/components/utils/useOutsideClick/__tests__/Demo.tsx index 3d583ba75f..6c09acf949 100644 --- a/src/components/utils/useOnClickOutside/__tests__/Demo.tsx +++ b/src/components/utils/useOutsideClick/__tests__/Demo.tsx @@ -1,7 +1,7 @@ /* eslint-disable jsx-a11y/click-events-have-key-events */ import React from 'react'; -import {useOnClickOutside} from '../useOnClickOutside'; +import {useOutsideClick} from '../useOutsideClick'; export const Demo = () => { const observerRef = React.useRef(null); @@ -15,10 +15,9 @@ export const Demo = () => { setStatus(1); }; - useOnClickOutside({ - observedRef: observerRef, - enabled: true, - handleCallback: handleOutsideClick, + useOutsideClick({ + ref: observerRef, + handler: handleOutsideClick, }); return ( diff --git a/src/components/utils/useOnClickOutside/__tests__/useOnClickOutside.test.tsx b/src/components/utils/useOutsideClick/__tests__/useOutsideClick.test.tsx similarity index 89% rename from src/components/utils/useOnClickOutside/__tests__/useOnClickOutside.test.tsx rename to src/components/utils/useOutsideClick/__tests__/useOutsideClick.test.tsx index ff396575b1..ffbe05b77f 100644 --- a/src/components/utils/useOnClickOutside/__tests__/useOnClickOutside.test.tsx +++ b/src/components/utils/useOutsideClick/__tests__/useOutsideClick.test.tsx @@ -5,7 +5,7 @@ import userEvent from '@testing-library/user-event'; import {Demo} from './Demo'; -test('Check useOnClickOutside correct work', async () => { +test('Check useOutsideClick correct work', async () => { render(); expect(screen.getByRole('heading')).toHaveTextContent('0'); diff --git a/src/components/utils/useOutsideClick/index.ts b/src/components/utils/useOutsideClick/index.ts new file mode 100644 index 0000000000..62f7bdebf0 --- /dev/null +++ b/src/components/utils/useOutsideClick/index.ts @@ -0,0 +1,2 @@ +export {useOutsideClick} from './useOutsideClick'; +export type {UseOutsideClickProps} from './useOutsideClick'; diff --git a/src/components/utils/useOutsideClick/useOutsideClick.tsx b/src/components/utils/useOutsideClick/useOutsideClick.tsx new file mode 100644 index 0000000000..7e22ceb0bb --- /dev/null +++ b/src/components/utils/useOutsideClick/useOutsideClick.tsx @@ -0,0 +1,40 @@ +import React from 'react'; + +export interface UseOutsideClickProps { + ref: React.RefObject; + handler?: () => void; +} + +type UseOutsideClickType = (props: UseOutsideClickProps) => void; + +/** + * Hook for observing clicks outside a given target + * + * @param ref - purpose of observation + * @param handler - callback when a click is triggered outside the observation target + * + * @return - nothing + */ +export const useOutsideClick: UseOutsideClickType = ({ref, handler}) => { + React.useEffect(() => { + if (ref) { + const callback = (e: MouseEvent | TouchEvent) => { + const elem = ref?.current; + + if (elem && !elem.contains(e.target as Node) && handler) { + handler(); + } + }; + + window.addEventListener('click', callback, {capture: true}); + window.addEventListener('touchstart', callback, {capture: true}); + + return () => { + window.removeEventListener('click', callback, {capture: true}); + window.removeEventListener('touchstart', callback, {capture: true}); + }; + } + + return undefined; + }, [handler, ref]); +}; From 24b4a85b11af35724012c16afd3f72adeaecc444 Mon Sep 17 00:00:00 2001 From: ngorin Date: Thu, 14 Sep 2023 17:21:43 +0300 Subject: [PATCH 5/9] feat: fix ref useOutsideClick --- .../utils/useOutsideClick/useOutsideClick.tsx | 28 ++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/src/components/utils/useOutsideClick/useOutsideClick.tsx b/src/components/utils/useOutsideClick/useOutsideClick.tsx index 7e22ceb0bb..0c54c8afbf 100644 --- a/src/components/utils/useOutsideClick/useOutsideClick.tsx +++ b/src/components/utils/useOutsideClick/useOutsideClick.tsx @@ -17,24 +17,20 @@ type UseOutsideClickType = (props: UseOutsideClickProps { React.useEffect(() => { - if (ref) { - const callback = (e: MouseEvent | TouchEvent) => { - const elem = ref?.current; + const callback = (e: MouseEvent | TouchEvent) => { + const elem = ref?.current; - if (elem && !elem.contains(e.target as Node) && handler) { - handler(); - } - }; + if (elem && !elem.contains(e.target as Node) && handler) { + handler(); + } + }; - window.addEventListener('click', callback, {capture: true}); - window.addEventListener('touchstart', callback, {capture: true}); + window.addEventListener('click', callback, {capture: true}); + window.addEventListener('touchstart', callback, {capture: true}); - return () => { - window.removeEventListener('click', callback, {capture: true}); - window.removeEventListener('touchstart', callback, {capture: true}); - }; - } - - return undefined; + return () => { + window.removeEventListener('click', callback, {capture: true}); + window.removeEventListener('touchstart', callback, {capture: true}); + }; }, [handler, ref]); }; From dc3e57aa2547380e4e584b84f5c11bdbb8d385fd Mon Sep 17 00:00:00 2001 From: ngorin Date: Fri, 15 Sep 2023 10:21:34 +0300 Subject: [PATCH 6/9] feat: fix touch event name --- src/components/utils/useOutsideClick/useOutsideClick.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/utils/useOutsideClick/useOutsideClick.tsx b/src/components/utils/useOutsideClick/useOutsideClick.tsx index 0c54c8afbf..68a8351758 100644 --- a/src/components/utils/useOutsideClick/useOutsideClick.tsx +++ b/src/components/utils/useOutsideClick/useOutsideClick.tsx @@ -26,11 +26,11 @@ export const useOutsideClick: UseOutsideClickType = ({ref, handler}) => { }; window.addEventListener('click', callback, {capture: true}); - window.addEventListener('touchstart', callback, {capture: true}); + window.addEventListener('touchend', callback, {capture: true}); return () => { window.removeEventListener('click', callback, {capture: true}); - window.removeEventListener('touchstart', callback, {capture: true}); + window.removeEventListener('touchend', callback, {capture: true}); }; }, [handler, ref]); }; From e6314165273b459157800506322ce2dc559db33a Mon Sep 17 00:00:00 2001 From: ngorin Date: Sat, 16 Sep 2023 02:16:52 +0300 Subject: [PATCH 7/9] feat: add reexport in main --- src/components/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/index.ts b/src/components/index.ts index 3b820c3d7a..99bb2799ed 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -69,3 +69,4 @@ export * from './utils/useFileInput/useFileInput'; export {useActionHandlers} from './utils/useActionHandlers'; export {useUniqId} from './utils/useUniqId'; export {getLayersCount} from './utils/LayerManager'; +export * from './utils/useOutsideClick'; From 49de79d9fa0671097e00aac753de3988df4bd259 Mon Sep 17 00:00:00 2001 From: ngorin Date: Mon, 18 Sep 2023 15:49:17 +0300 Subject: [PATCH 8/9] feat: reafctor mouse event for web --- src/components/utils/useOutsideClick/useOutsideClick.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/utils/useOutsideClick/useOutsideClick.tsx b/src/components/utils/useOutsideClick/useOutsideClick.tsx index 68a8351758..29fcc95763 100644 --- a/src/components/utils/useOutsideClick/useOutsideClick.tsx +++ b/src/components/utils/useOutsideClick/useOutsideClick.tsx @@ -25,11 +25,11 @@ export const useOutsideClick: UseOutsideClickType = ({ref, handler}) => { } }; - window.addEventListener('click', callback, {capture: true}); + window.addEventListener('mousedown', callback, {capture: true}); window.addEventListener('touchend', callback, {capture: true}); return () => { - window.removeEventListener('click', callback, {capture: true}); + window.removeEventListener('mousedown', callback, {capture: true}); window.removeEventListener('touchend', callback, {capture: true}); }; }, [handler, ref]); From ec50e1ac8f9d648b8a00941908b3c6bed257081b Mon Sep 17 00:00:00 2001 From: ngorin Date: Tue, 19 Sep 2023 11:01:03 +0300 Subject: [PATCH 9/9] feat: mousedown -> mouseup --- src/components/utils/useOutsideClick/useOutsideClick.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/utils/useOutsideClick/useOutsideClick.tsx b/src/components/utils/useOutsideClick/useOutsideClick.tsx index 29fcc95763..6f7b2c5b44 100644 --- a/src/components/utils/useOutsideClick/useOutsideClick.tsx +++ b/src/components/utils/useOutsideClick/useOutsideClick.tsx @@ -25,11 +25,11 @@ export const useOutsideClick: UseOutsideClickType = ({ref, handler}) => { } }; - window.addEventListener('mousedown', callback, {capture: true}); + window.addEventListener('mouseup', callback, {capture: true}); window.addEventListener('touchend', callback, {capture: true}); return () => { - window.removeEventListener('mousedown', callback, {capture: true}); + window.removeEventListener('mouseup', callback, {capture: true}); window.removeEventListener('touchend', callback, {capture: true}); }; }, [handler, ref]);