diff --git a/packages/components/src/hooks/index.js b/packages/components/src/hooks/index.ts similarity index 100% rename from packages/components/src/hooks/index.js rename to packages/components/src/hooks/index.ts diff --git a/packages/components/src/hooks/use-blockscroll.js b/packages/components/src/hooks/use-blockscroll.ts similarity index 79% rename from packages/components/src/hooks/use-blockscroll.js rename to packages/components/src/hooks/use-blockscroll.ts index 7f8866d3e437..d22b7915ea95 100644 --- a/packages/components/src/hooks/use-blockscroll.js +++ b/packages/components/src/hooks/use-blockscroll.ts @@ -1,10 +1,12 @@ -import React from 'react'; +import React, { RefObject } from 'react'; -export const useBlockScroll = target_ref => { +export const useBlockScroll = (target_ref: RefObject) => { React.useEffect(() => { if (!target_ref) return undefined; - const getScrollableParentElement = elem => { + const getScrollableParentElement: (prop: HTMLElement | null) => HTMLElement | null = ( + elem: HTMLElement | null + ) => { if (!elem) return null; if (elem.classList.contains('dc-themed-scrollbars') && elem.scrollHeight > elem.clientHeight) return elem; return getScrollableParentElement(elem.parentElement); diff --git a/packages/components/src/hooks/use-constructor.js b/packages/components/src/hooks/use-constructor.ts similarity index 69% rename from packages/components/src/hooks/use-constructor.js rename to packages/components/src/hooks/use-constructor.ts index 1b5f85750ebc..a3738c19c003 100644 --- a/packages/components/src/hooks/use-constructor.js +++ b/packages/components/src/hooks/use-constructor.ts @@ -1,6 +1,6 @@ import React from 'react'; -export const useConstructor = (callBack = () => {}) => { +export const useConstructor = (callBack: () => void = () => undefined) => { const is_called_ref = React.useRef(false); if (!is_called_ref.current) { callBack(); diff --git a/packages/components/src/hooks/use-deep-effect.js b/packages/components/src/hooks/use-deep-effect.ts similarity index 68% rename from packages/components/src/hooks/use-deep-effect.js rename to packages/components/src/hooks/use-deep-effect.ts index 2aa73faba7e4..a808d6cfc169 100644 --- a/packages/components/src/hooks/use-deep-effect.js +++ b/packages/components/src/hooks/use-deep-effect.ts @@ -3,8 +3,8 @@ import { isDeepEqual } from '@deriv/shared'; // Note: Do not use this effect on huge objects or objects with // circular references as performance may suffer. -export const useDeepEffect = (callback, dependencies) => { - const prev_dependencies = React.useRef(null); +export const useDeepEffect = (callback: () => void, dependencies: unknown[]) => { + const prev_dependencies = React.useRef(null); if (!isDeepEqual(prev_dependencies, dependencies)) { prev_dependencies.current = dependencies; diff --git a/packages/components/src/hooks/use-hover.js b/packages/components/src/hooks/use-hover.ts similarity index 89% rename from packages/components/src/hooks/use-hover.js rename to packages/components/src/hooks/use-hover.ts index 7fd3d0c1b390..6a4318755160 100644 --- a/packages/components/src/hooks/use-hover.js +++ b/packages/components/src/hooks/use-hover.ts @@ -1,6 +1,6 @@ -import React from 'react'; +import React, { RefObject } from 'react'; -export const useHover = (refSetter, should_prevent_bubbling) => { +export const useHover = (refSetter: RefObject | null, should_prevent_bubbling: boolean) => { const [value, setValue] = React.useState(false); const default_ref = React.useRef(null); const ref = refSetter || default_ref; @@ -40,10 +40,10 @@ export const useHoverCallback = () => { const handleMouseOver = React.useCallback(() => setValue(true), []); const handleMouseOut = React.useCallback(() => setValue(false), []); - const ref = React.useRef(); + const ref = React.useRef(null); const callbackRef = React.useCallback( - node => { + (node: HTMLElement) => { if (ref.current) { ref.current.removeEventListener('mouseover', handleMouseOver); ref.current.removeEventListener('mouseout', handleMouseOut); diff --git a/packages/components/src/hooks/use-interval.js b/packages/components/src/hooks/use-interval.ts similarity index 67% rename from packages/components/src/hooks/use-interval.js rename to packages/components/src/hooks/use-interval.ts index c8160a06532d..ece6c2c11fee 100644 --- a/packages/components/src/hooks/use-interval.js +++ b/packages/components/src/hooks/use-interval.ts @@ -1,14 +1,14 @@ import React from 'react'; -export const useInterval = (callback, delay) => { - const savedCallback = React.useRef(); +export const useInterval = (callback: () => void, delay: number) => { + const savedCallback = React.useRef<() => void | undefined>(); React.useEffect(() => { savedCallback.current = callback; }, [callback]); React.useEffect(() => { function tick() { - savedCallback.current(); + savedCallback.current?.(); } if (delay !== null) { const id = setInterval(tick, delay); diff --git a/packages/components/src/hooks/use-on-scroll.js b/packages/components/src/hooks/use-on-scroll.ts similarity index 72% rename from packages/components/src/hooks/use-on-scroll.js rename to packages/components/src/hooks/use-on-scroll.ts index bcb254930bf5..33ec728aaa94 100644 --- a/packages/components/src/hooks/use-on-scroll.js +++ b/packages/components/src/hooks/use-on-scroll.ts @@ -1,8 +1,8 @@ -import React from 'react'; +import React, { RefObject } from 'react'; -export const useOnScroll = (ref, callback) => { +export const useOnScroll = (ref: RefObject, callback: EventListener) => { // Allow consumer to prematurely dispose this scroll listener. - const remover = React.useRef(null); + const remover = React.useRef<(() => void) | null>(null); const has_removed = React.useRef(false); const diposeListener = () => { @@ -19,7 +19,7 @@ export const useOnScroll = (ref, callback) => { ref.current.addEventListener('scroll', callback); remover.current = () => { - ref.current.removeEventListener('scroll', callback); + ref.current!.removeEventListener('scroll', callback); }; } diff --git a/packages/components/src/hooks/use-onclickoutside.js b/packages/components/src/hooks/use-onclickoutside.js deleted file mode 100644 index 94d732e27d74..000000000000 --- a/packages/components/src/hooks/use-onclickoutside.js +++ /dev/null @@ -1,21 +0,0 @@ -import React from 'react'; - -export const useOnClickOutside = (ref, handler, validationFn) => { - React.useEffect(() => { - const listener = event => { - const path = event.path ?? event.composedPath?.(); - - // When component is isolated (e.g, iframe, shadow DOM) event.target refers to whole container not the component. path[0] is the node that the event originated from, it does not need to walk the array - if (ref && ref.current && !ref.current.contains(event.target) && !ref.current.contains(path && path[0])) { - if (validationFn && !validationFn(event)) return; - handler(event); - } - }; - - document.addEventListener('mousedown', listener); - - return () => { - document.removeEventListener('mousedown', listener); - }; - }, [ref, handler, validationFn]); -}; diff --git a/packages/components/src/hooks/use-onclickoutside.ts b/packages/components/src/hooks/use-onclickoutside.ts new file mode 100644 index 000000000000..ba95fac82b8a --- /dev/null +++ b/packages/components/src/hooks/use-onclickoutside.ts @@ -0,0 +1,29 @@ +import React, { RefObject } from 'react'; + +export const useOnClickOutside = ( + ref: RefObject, + handler: (event: MouseEvent) => void, + validationFn: (event: MouseEvent) => boolean +) => { + React.useEffect(() => { + const listener = (event: MouseEvent) => { + const path = event.composedPath?.()[0] ?? (event as MouseEvent & { path: HTMLElement }).path; //event.path is non-standard and will be deprecated + // When component is isolated (e.g, iframe, shadow DOM) event.target refers to whole container not the component. path[0] is the node that the event originated from, it does not need to walk the array + if ( + ref && + ref.current && + !ref.current.contains(event.target as HTMLElement) && + !ref.current.contains(path as HTMLElement) + ) { + if (validationFn && !validationFn(event)) return; + handler(event); + } + }; + + document.addEventListener('mousedown', listener); + + return () => { + document.removeEventListener('mousedown', listener); + }; + }, [ref, handler, validationFn]); +}; diff --git a/packages/components/src/hooks/use-onlongpress.js b/packages/components/src/hooks/use-onlongpress.ts similarity index 80% rename from packages/components/src/hooks/use-onlongpress.js rename to packages/components/src/hooks/use-onlongpress.ts index 6ebd213a2bb0..ab92b7edf64a 100644 --- a/packages/components/src/hooks/use-onlongpress.js +++ b/packages/components/src/hooks/use-onlongpress.ts @@ -1,20 +1,20 @@ import React from 'react'; export const useLongPress = ( - callback = () => { + callback: () => void = () => { /** empty function */ }, ms = 300 ) => { const [startLongPress, setStartLongPress] = React.useState(false); - const preventDefaults = e => { + const preventDefaults = (e: Event) => { e.preventDefault(); e.stopPropagation(); }; React.useEffect(() => { - let timer; + let timer: ReturnType | undefined; if (startLongPress) { timer = setTimeout(callback, ms); } else if (timer) { @@ -28,13 +28,13 @@ export const useLongPress = ( }, [startLongPress]); return { - onMouseDown: e => { + onMouseDown: (e: MouseEvent) => { preventDefaults(e); setStartLongPress(true); }, onMouseUp: () => setStartLongPress(false), onMouseLeave: () => setStartLongPress(false), - onTouchStart: e => { + onTouchStart: (e: TouchEvent) => { preventDefaults(e); setStartLongPress(true); }, diff --git a/packages/components/src/hooks/use-prevent-ios-zoom.js b/packages/components/src/hooks/use-prevent-ios-zoom.ts similarity index 90% rename from packages/components/src/hooks/use-prevent-ios-zoom.js rename to packages/components/src/hooks/use-prevent-ios-zoom.ts index 5348424e0767..4526ed880252 100644 --- a/packages/components/src/hooks/use-prevent-ios-zoom.js +++ b/packages/components/src/hooks/use-prevent-ios-zoom.ts @@ -3,7 +3,7 @@ import React from 'react'; export const usePreventIOSZoom = () => { React.useEffect(() => { // Fix to prevent iOS from zooming in erratically on quick taps - const preventIOSZoom = event => { + const preventIOSZoom = (event: TouchEvent) => { if (event.touches.length > 1) { event.preventDefault(); event.stopPropagation(); diff --git a/packages/components/src/hooks/use-previous.js b/packages/components/src/hooks/use-previous.ts similarity index 58% rename from packages/components/src/hooks/use-previous.js rename to packages/components/src/hooks/use-previous.ts index 3ef31e621c38..e8d4d168015e 100644 --- a/packages/components/src/hooks/use-previous.js +++ b/packages/components/src/hooks/use-previous.ts @@ -1,7 +1,7 @@ import React from 'react'; -export const usePrevious = value => { - const ref = React.useRef(); +export const usePrevious = (value: T) => { + const ref = React.useRef(); React.useEffect(() => { ref.current = value; }, [value]); diff --git a/packages/components/src/hooks/use-safe-state.js b/packages/components/src/hooks/use-safe-state.ts similarity index 62% rename from packages/components/src/hooks/use-safe-state.js rename to packages/components/src/hooks/use-safe-state.ts index 8947dcd1a95e..670fd5242e95 100644 --- a/packages/components/src/hooks/use-safe-state.js +++ b/packages/components/src/hooks/use-safe-state.ts @@ -1,23 +1,25 @@ import * as React from 'react'; -export const useSafeState = (initial_state, optIsMountedFunc = null) => { +export const useSafeState = (initial_state: T, optIsMountedFunc: () => void) => { const [state, setState] = React.useState(initial_state); const is_mounted = React.useRef(false); React.useLayoutEffect(() => { is_mounted.current = true; - return () => (is_mounted.current = false); + return () => { + is_mounted.current = false; + }; }, []); const isMounted = () => { - if (typeof optIsMountedFunc === 'function') { + if (optIsMountedFunc && typeof optIsMountedFunc === 'function') { return optIsMountedFunc(); } return is_mounted.current === true; }; - const wrappedSetState = value => { + const wrappedSetState = (value: T) => { if (isMounted()) { setState(value); } diff --git a/packages/components/src/hooks/use-state-callback.js b/packages/components/src/hooks/use-state-callback.ts similarity index 63% rename from packages/components/src/hooks/use-state-callback.js rename to packages/components/src/hooks/use-state-callback.ts index cafbbb063038..b6c8a640b107 100644 --- a/packages/components/src/hooks/use-state-callback.js +++ b/packages/components/src/hooks/use-state-callback.ts @@ -1,11 +1,11 @@ import React from 'react'; // this hook mimics this.setState({ state: value, ... }, () => callbackFunc()); -export const useStateCallback = initial_state => { - const [state, setState] = React.useState(initial_state); - const callbackRef = React.useRef(null); // a mutable ref to store existing callback +export const useStateCallback = (initial_state: T) => { + const [state, setState] = React.useState(initial_state); + const callbackRef = React.useRef<((param: T) => void) | null>(null); // a mutable ref to store existing callback - const setStateCallback = React.useCallback((current_state, cb) => { + const setStateCallback = React.useCallback((current_state: T, cb: (param: T) => void) => { callbackRef.current = cb; // store the passed callback to the ref setState(current_state); }, []);