diff options
Diffstat (limited to 'src/utils/hooks/use-autofocus')
| -rw-r--r-- | src/utils/hooks/use-autofocus/index.ts | 1 | ||||
| -rw-r--r-- | src/utils/hooks/use-autofocus/use-autofocus.test.ts | 79 | ||||
| -rw-r--r-- | src/utils/hooks/use-autofocus/use-autofocus.ts | 40 |
3 files changed, 120 insertions, 0 deletions
diff --git a/src/utils/hooks/use-autofocus/index.ts b/src/utils/hooks/use-autofocus/index.ts new file mode 100644 index 0000000..bb23089 --- /dev/null +++ b/src/utils/hooks/use-autofocus/index.ts @@ -0,0 +1 @@ +export * from './use-autofocus'; diff --git a/src/utils/hooks/use-autofocus/use-autofocus.test.ts b/src/utils/hooks/use-autofocus/use-autofocus.test.ts new file mode 100644 index 0000000..1a9a3be --- /dev/null +++ b/src/utils/hooks/use-autofocus/use-autofocus.test.ts @@ -0,0 +1,79 @@ +import { + afterEach, + beforeEach, + describe, + expect, + it, + jest, +} from '@jest/globals'; +import { renderHook, screen as rtlScreen } from '@testing-library/react'; +import { useAutofocus } from './use-autofocus'; + +describe('useAutofocus', () => { + // When less than 1ms, setTimeout use 1. Default delay is 0ms. + const defaultTimeoutDelay = 1; + const input = document.createElement('input'); + input.type = 'text'; + + beforeEach(() => { + document.body.append(input); + jest.useFakeTimers(); + }); + + afterEach(() => { + document.body.removeChild(input); + jest.runOnlyPendingTimers(); + jest.useRealTimers(); + }); + + it('gives focus to the element without condition', () => { + const { result } = renderHook(() => useAutofocus<HTMLInputElement>()); + result.current.current = input; + + jest.advanceTimersByTime(defaultTimeoutDelay); + + expect(rtlScreen.getByRole('textbox')).toHaveFocus(); + }); + + it('can give focus to the element with custom delay', () => { + const delay = 2000; + const { result } = renderHook(() => + useAutofocus<HTMLInputElement>({ delay }) + ); + result.current.current = input; + + jest.advanceTimersByTime(defaultTimeoutDelay); + + expect(rtlScreen.getByRole('textbox')).not.toHaveFocus(); + + jest.advanceTimersByTime(delay); + + expect(rtlScreen.getByRole('textbox')).toHaveFocus(); + }); + + it('can give focus to the element when the condition is met', () => { + const condition = jest.fn(() => true); + const { result } = renderHook(() => + useAutofocus<HTMLInputElement>({ condition }) + ); + result.current.current = input; + + jest.advanceTimersByTime(defaultTimeoutDelay); + + expect(rtlScreen.getByRole('textbox')).toHaveFocus(); + expect(condition).toHaveBeenCalledTimes(1); + }); + + it('does not give focus to the element when the condition is not met', () => { + const condition = jest.fn(() => false); + const { result } = renderHook(() => + useAutofocus<HTMLInputElement>({ condition }) + ); + result.current.current = input; + + jest.advanceTimersByTime(defaultTimeoutDelay); + + expect(rtlScreen.getByRole('textbox')).not.toHaveFocus(); + expect(condition).toHaveBeenCalledTimes(1); + }); +}); diff --git a/src/utils/hooks/use-autofocus/use-autofocus.ts b/src/utils/hooks/use-autofocus/use-autofocus.ts new file mode 100644 index 0000000..0d21a59 --- /dev/null +++ b/src/utils/hooks/use-autofocus/use-autofocus.ts @@ -0,0 +1,40 @@ +import { useCallback, useRef, type MutableRefObject } from 'react'; +import { useTimeout } from '../use-timeout'; + +export type UseAutofocusCondition = () => boolean; + +export type UseAutofocusConfig = { + /** + * A condition to met before giving focus to the element. + */ + condition?: UseAutofocusCondition; + /** + * A delay in ms before giving focus to the element. + */ + delay?: number; +}; + +/** + * React hook to give focus to an element automatically. + * + * @param {UseAutofocusConfig} [config] - A configuration object. + * @returns {RefObject<T>} The element reference. + */ +export const useAutofocus = <T extends HTMLElement>( + config?: UseAutofocusConfig +): MutableRefObject<T | null> => { + const { condition, delay } = config ?? {}; + const ref = useRef<T | null>(null); + + const setFocus = useCallback(() => { + const shouldFocus = condition ? condition() : true; + + if (ref.current && shouldFocus) { + ref.current.focus(); + } + }, [condition]); + + useTimeout(setFocus, delay); + + return ref; +}; |
