From 9c1957fd55b7b354a8b34edfc8341fe493c198d4 Mon Sep 17 00:00:00 2001 From: Leroy Korterink Date: Fri, 31 Mar 2023 17:13:13 +0200 Subject: [PATCH] Update event listener hooks - Remove useEventListener function overloads - Remove useDocument hook - Remove useWindow hook - Add useDocument hook - Add useWindow hook - Add RefObjectOption to useEventListener for ref support #101 #102 --- src/hooks/useDocument/useDocument.mdx | 28 + src/hooks/useDocument/useDocument.test.tsx | 10 + src/hooks/useDocument/useDocument.ts | 9 + .../useDocumentEventListener.stories.mdx | 26 - .../useDocumentEventListener.stories.tsx | 38 - .../useDocumentEventListener.test.tsx | 18 - .../useDocumentEventListener.ts | 12 - .../useEventListener.stories.tsx | 7 +- .../useEventListener.test.tsx | 56 +- .../useEventListener/useEventListener.ts | 1689 +---------------- src/hooks/useHasFocus/useHasFocus.ts | 9 +- .../useMediaQuery/useMediaQuery.component.tsx | 2 +- .../useMediaQuery.playwright.tsx | 14 +- src/hooks/useMediaQuery/useMediaQuery.ts | 4 +- src/hooks/useRefValue/useRefValue.ts | 8 + src/hooks/useWindow/useWindow.mdx | 28 + src/hooks/useWindow/useWindow.test.ts | 10 + src/hooks/useWindow/useWindow.ts | 9 + .../useWindowEventListener.stories.mdx | 30 - .../useWindowEventListener.stories.tsx | 33 - .../useWindowEventListener.test.tsx | 20 - .../useWindowEventListener.ts | 12 - src/index.ts | 6 +- .../getRefObjectOption/getRefObjectOption.mdx | 53 + .../getRefObjectOption.test.ts | 21 + .../getRefObjectOption/getRefObjectOption.ts | 23 + 26 files changed, 286 insertions(+), 1889 deletions(-) create mode 100644 src/hooks/useDocument/useDocument.mdx create mode 100644 src/hooks/useDocument/useDocument.test.tsx create mode 100644 src/hooks/useDocument/useDocument.ts delete mode 100644 src/hooks/useDocumentEventListener/useDocumentEventListener.stories.mdx delete mode 100644 src/hooks/useDocumentEventListener/useDocumentEventListener.stories.tsx delete mode 100644 src/hooks/useDocumentEventListener/useDocumentEventListener.test.tsx delete mode 100644 src/hooks/useDocumentEventListener/useDocumentEventListener.ts create mode 100644 src/hooks/useRefValue/useRefValue.ts create mode 100644 src/hooks/useWindow/useWindow.mdx create mode 100644 src/hooks/useWindow/useWindow.test.ts create mode 100644 src/hooks/useWindow/useWindow.ts delete mode 100644 src/hooks/useWindowEventListener/useWindowEventListener.stories.mdx delete mode 100644 src/hooks/useWindowEventListener/useWindowEventListener.stories.tsx delete mode 100644 src/hooks/useWindowEventListener/useWindowEventListener.test.tsx delete mode 100644 src/hooks/useWindowEventListener/useWindowEventListener.ts create mode 100644 src/utils/getRefObjectOption/getRefObjectOption.mdx create mode 100644 src/utils/getRefObjectOption/getRefObjectOption.test.ts create mode 100644 src/utils/getRefObjectOption/getRefObjectOption.ts diff --git a/src/hooks/useDocument/useDocument.mdx b/src/hooks/useDocument/useDocument.mdx new file mode 100644 index 0000000..dda5251 --- /dev/null +++ b/src/hooks/useDocument/useDocument.mdx @@ -0,0 +1,28 @@ +import { Meta } from '@storybook/blocks'; + + + +# useDocument + +This hook returns the Document instance when it's availbable in the environment. If the Document is +not available, it will return `undefined`. + +## Reference + +```ts +function useDocument(): Document | undefined; +``` + +## Usage + +```tsx +function DemoComponent(): ReactNode { + const document = useDocument(ref, ':focus-within'); + + useEventListener(document, 'focusin', (event) => { + // do something + }); + + return null; +} +``` diff --git a/src/hooks/useDocument/useDocument.test.tsx b/src/hooks/useDocument/useDocument.test.tsx new file mode 100644 index 0000000..c28e9c6 --- /dev/null +++ b/src/hooks/useDocument/useDocument.test.tsx @@ -0,0 +1,10 @@ +import { renderHook } from '@testing-library/react'; +import { useDocument } from './useDocument.js'; + +describe('useEventListener', () => { + it('Should return the document', () => { + const result = renderHook(useDocument); + + expect(result.result.current).toBeInstanceOf(Document); + }); +}); diff --git a/src/hooks/useDocument/useDocument.ts b/src/hooks/useDocument/useDocument.ts new file mode 100644 index 0000000..ea3a617 --- /dev/null +++ b/src/hooks/useDocument/useDocument.ts @@ -0,0 +1,9 @@ +/** + * Returns undefined when in a server environment, otherwise returns document. + * + * Note: be careful with this hook as it returns a different value on on server + * and client. + */ +export function useDocument(): Document | undefined { + return typeof document === 'undefined' ? undefined : document; +} diff --git a/src/hooks/useDocumentEventListener/useDocumentEventListener.stories.mdx b/src/hooks/useDocumentEventListener/useDocumentEventListener.stories.mdx deleted file mode 100644 index ddfc3e8..0000000 --- a/src/hooks/useDocumentEventListener/useDocumentEventListener.stories.mdx +++ /dev/null @@ -1,26 +0,0 @@ -import { Meta } from '@storybook/blocks'; - - - -# useDocumentEventListener - -This hook allows you to add a document event listener and remove it when the component unmounts. - -## Reference - -```ts -export function useDocumentEventListener( - event: T, - callback: (event: DocumentEventMap[T]) => void, -): void; -``` - -## Usage - -```tsx -const [keydown, setKeydown] = useState>([]); - -useDocumentEventListener('keydown', (event) => { - setKeydown((previous) => [...previous, event.key]); -}); -``` diff --git a/src/hooks/useDocumentEventListener/useDocumentEventListener.stories.tsx b/src/hooks/useDocumentEventListener/useDocumentEventListener.stories.tsx deleted file mode 100644 index a85652d..0000000 --- a/src/hooks/useDocumentEventListener/useDocumentEventListener.stories.tsx +++ /dev/null @@ -1,38 +0,0 @@ -/* eslint-disable react/jsx-no-literals */ -import type { StoryObj } from '@storybook/react'; -import { type ReactElement, useState } from 'react'; -import { useDocumentEventListener } from './useDocumentEventListener.js'; - -export default { - title: 'hooks/useDocumentEventListener', -}; - -function DemoComponent(): ReactElement { - const [keydown, setKeydown] = useState>([]); - - useDocumentEventListener('keydown', (event) => { - setKeydown((previous) => [...previous, event.key]); - }); - - return ( -
-
-

Instructions!

-

Press a key to trigger the document keydown event.

-
- -
    - {keydown.map((key, index) => ( - // eslint-disable-next-line react/no-array-index-key -
  • {key}
  • - ))} -
-
- ); -} - -export const Demo: StoryObj = { - render() { - return ; - }, -}; diff --git a/src/hooks/useDocumentEventListener/useDocumentEventListener.test.tsx b/src/hooks/useDocumentEventListener/useDocumentEventListener.test.tsx deleted file mode 100644 index bc18627..0000000 --- a/src/hooks/useDocumentEventListener/useDocumentEventListener.test.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { renderHook } from '@testing-library/react'; -import { useDocumentEventListener } from './useDocumentEventListener.js'; - -describe('useDocumentEventListener', () => { - it('should not crash', () => { - renderHook( - () => { - useDocumentEventListener('focusin', () => { - // eslint-disable-next-line no-console - console.log(document.activeElement); - }); - }, - { - initialProps: undefined, - }, - ); - }); -}); diff --git a/src/hooks/useDocumentEventListener/useDocumentEventListener.ts b/src/hooks/useDocumentEventListener/useDocumentEventListener.ts deleted file mode 100644 index e7017b1..0000000 --- a/src/hooks/useDocumentEventListener/useDocumentEventListener.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { useEventListener } from '../useEventListener/useEventListener.js'; - -/** - * SSR-safe hook that adds an event listener to the document. - */ -export function useDocumentEventListener( - type: K, - listener: (this: Document, event: DocumentEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void { - useEventListener(typeof document === 'undefined' ? undefined : document, type, listener, options); -} diff --git a/src/hooks/useEventListener/useEventListener.stories.tsx b/src/hooks/useEventListener/useEventListener.stories.tsx index 186565e..c31b7ef 100644 --- a/src/hooks/useEventListener/useEventListener.stories.tsx +++ b/src/hooks/useEventListener/useEventListener.stories.tsx @@ -1,6 +1,7 @@ /* eslint-disable react/jsx-no-literals */ import type { StoryObj } from '@storybook/react'; -import { type ReactElement, useState } from 'react'; +import { useState, type ReactElement } from 'react'; +import { useDocument } from '../useDocument/useDocument.js'; import { useEventListener } from './useEventListener.js'; export default { @@ -10,7 +11,9 @@ export default { function DemoComponent(): ReactElement { const [text, setText] = useState>([]); - useEventListener(typeof document === 'undefined' ? undefined : document, 'focusin', (event) => { + const document = useDocument(); + + useEventListener(document, 'focusin', (event) => { // eslint-disable-next-line no-console setText((previous) => [ ...previous, diff --git a/src/hooks/useEventListener/useEventListener.test.tsx b/src/hooks/useEventListener/useEventListener.test.tsx index 725190d..8d210ca 100644 --- a/src/hooks/useEventListener/useEventListener.test.tsx +++ b/src/hooks/useEventListener/useEventListener.test.tsx @@ -1,18 +1,48 @@ -import { renderHook } from '@testing-library/react'; +/* eslint-disable react/no-multi-comp */ +import { jest } from '@jest/globals'; +import { render } from '@testing-library/react'; +import { createRef, useEffect, useState, type ReactElement } from 'react'; import { useEventListener } from './useEventListener.js'; describe('useEventListener', () => { - it('should not crash', () => { - renderHook( - () => { - useEventListener(typeof document === 'undefined' ? undefined : document, 'focusin', () => { - // eslint-disable-next-line no-console - console.log(document.activeElement); - }); - }, - { - initialProps: undefined, - }, - ); + it('Should listen to event attached to element from RefObject', () => { + const spy = jest.fn(); + const ref = createRef(); + + function Test(): ReactElement { + useEventListener(ref, 'click', spy); + + return
; + } + + render(); + + ref.current?.click(); + + expect(spy).toBeCalledTimes(1); + }); + + it('Should listen to event attached to element from state', async () => { + const spy = jest.fn(); + let exposedRef: HTMLDivElement | null = null; + + function Test(): ReactElement { + const [ref, setRef] = useState(null); + + useEffect(() => { + exposedRef = ref; + }, [ref]); + + useEventListener(ref, 'click', spy); + + return
; + } + + render(); + + // @ts-expect-error typescript doesn't infer type for exposedRef correctly + exposedRef?.click(); + + expect(spy).toBeCalledTimes(1); }); }); diff --git a/src/hooks/useEventListener/useEventListener.ts b/src/hooks/useEventListener/useEventListener.ts index ef52e39..afe4ce2 100644 --- a/src/hooks/useEventListener/useEventListener.ts +++ b/src/hooks/useEventListener/useEventListener.ts @@ -1,1679 +1,30 @@ -/* eslint-disable @typescript-eslint/unified-signatures */ +/* eslint-disable @typescript-eslint/unified-signatures, @typescript-eslint/no-explicit-any */ import { useEffect } from 'react'; +import { + getRefObjectOptionValue, + type RefObjectOption, +} from '../../utils/getRefObjectOption/getRefObjectOption.js'; +import { useRefValue } from '../useRefValue/useRefValue.js'; -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: AbstractWorker, event: AbstractWorkerEventMap[K]) => void, +export function useEventListener( + targetOption: RefObjectOption | null | undefined, + type: string, + listener: EventListener, options?: boolean | AddEventListenerOptions, -): void; - -// @ts-expect-error function overload is correct but typescript doesn't like it -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: Animation, event: AnimationEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends AudioBufferSourceNode, - K extends keyof AudioScheduledSourceNodeEventMap, ->( - target: T | undefined, - type: K, - listener: (this: AudioBufferSourceNode, event: AudioScheduledSourceNodeEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: AudioContext, event: BaseAudioContextEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends AudioScheduledSourceNode, - K extends keyof AudioScheduledSourceNodeEventMap, ->( - target: T | undefined, - type: K, - listener: (this: AudioScheduledSourceNode, event: AudioScheduledSourceNodeEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends AudioWorkletNode, - K extends keyof AudioWorkletNodeEventMap, ->( - target: T | undefined, - type: K, - listener: (this: AudioWorkletNode, event: AudioWorkletNodeEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends BaseAudioContext, - K extends keyof BaseAudioContextEventMap, ->( - target: T | undefined, - type: K, - listener: (this: BaseAudioContext, event: BaseAudioContextEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends BroadcastChannel, - K extends keyof BroadcastChannelEventMap, ->( - target: T | undefined, - type: K, - listener: (this: BroadcastChannel, event: BroadcastChannelEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: CSSAnimation, event: AnimationEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: CSSTransition, event: AnimationEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends CanvasCaptureMediaStreamTrack, - K extends keyof MediaStreamTrackEventMap, ->( - target: T | undefined, - type: K, - listener: (this: CanvasCaptureMediaStreamTrack, event: MediaStreamTrackEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends ConstantSourceNode, - K extends keyof AudioScheduledSourceNodeEventMap, ->( - target: T | undefined, - type: K, - listener: (this: ConstantSourceNode, event: AudioScheduledSourceNodeEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: Document, event: DocumentEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends DocumentAndElementEventHandlers, - K extends keyof DocumentAndElementEventHandlersEventMap, ->( - target: T | undefined, - type: K, - listener: ( - this: DocumentAndElementEventHandlers, - event: DocumentAndElementEventHandlersEventMap[K], - ) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: Element, event: ElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: EventSource, event: EventSourceEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: FileReader, event: FileReaderEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: FontFaceSet, event: FontFaceSetEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends GlobalEventHandlers, - K extends keyof GlobalEventHandlersEventMap, ->( - target: T | undefined, - type: K, - listener: (this: GlobalEventHandlers, event: GlobalEventHandlersEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLAnchorElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLAreaElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends HTMLAudioElement, - K extends keyof HTMLMediaElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: HTMLAudioElement, event: HTMLMediaElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLBRElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLBaseElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends HTMLBodyElement, - K extends keyof HTMLBodyElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: HTMLBodyElement, event: HTMLBodyElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLButtonElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLCanvasElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLDListElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLDataElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends HTMLDataListElement, - K extends keyof HTMLElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: HTMLDataListElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLDetailsElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLDialogElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLDivElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLEmbedElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends HTMLFieldSetElement, - K extends keyof HTMLElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: HTMLFieldSetElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLFormElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLHRElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLHeadElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLHeadingElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLHtmlElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLIFrameElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLImageElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLInputElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLLIElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLLabelElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLLegendElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLLinkElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLMapElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends HTMLMediaElement, - K extends keyof HTMLMediaElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: HTMLMediaElement, event: HTMLMediaElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLMenuElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLMetaElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLMeterElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLModElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLOListElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLObjectElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends HTMLOptGroupElement, - K extends keyof HTMLElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: HTMLOptGroupElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLOptionElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLOutputElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends HTMLParagraphElement, - K extends keyof HTMLElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: HTMLParagraphElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLPictureElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLPreElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends HTMLProgressElement, - K extends keyof HTMLElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: HTMLProgressElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLQuoteElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLScriptElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLSelectElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLSlotElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLSourceElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLSpanElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLStyleElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends HTMLTableCaptionElement, - K extends keyof HTMLElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: HTMLTableCaptionElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends HTMLTableCellElement, - K extends keyof HTMLElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: HTMLTableCellElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends HTMLTableColElement, - K extends keyof HTMLElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: HTMLTableColElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLTableElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends HTMLTableRowElement, - K extends keyof HTMLElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: HTMLTableRowElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends HTMLTableSectionElement, - K extends keyof HTMLElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: HTMLTableSectionElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends HTMLTemplateElement, - K extends keyof HTMLElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: HTMLTemplateElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends HTMLTextAreaElement, - K extends keyof HTMLElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: HTMLTextAreaElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLTimeElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLTitleElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLTrackElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLUListElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: HTMLUnknownElement, event: HTMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends HTMLVideoElement, - K extends keyof HTMLVideoElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: HTMLVideoElement, event: HTMLVideoElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: IDBDatabase, event: IDBDatabaseEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends IDBOpenDBRequest, - K extends keyof IDBOpenDBRequestEventMap, ->( - target: T | undefined, - type: K, - listener: (this: IDBOpenDBRequest, event: IDBOpenDBRequestEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: IDBRequest, event: IDBRequestEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: IDBTransaction, event: IDBTransactionEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: MathMLElement, event: MathMLElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: MediaDevices, event: MediaDevicesEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends MediaKeySession, - K extends keyof MediaKeySessionEventMap, ->( - target: T | undefined, - type: K, - listener: (this: MediaKeySession, event: MediaKeySessionEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: MediaQueryList, event: MediaQueryListEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: MediaRecorder, event: MediaRecorderEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: MediaSource, event: MediaSourceEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: MediaStream, event: MediaStreamEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends MediaStreamTrack, - K extends keyof MediaStreamTrackEventMap, ->( - target: T | undefined, - type: K, - listener: (this: MediaStreamTrack, event: MediaStreamTrackEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: MessagePort, event: MessagePortEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: Notification, event: NotificationEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends OfflineAudioContext, - K extends keyof OfflineAudioContextEventMap, ->( - target: T | undefined, - type: K, - listener: (this: OfflineAudioContext, event: OfflineAudioContextEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends OffscreenCanvas, - K extends keyof OffscreenCanvasEventMap, ->( - target: T | undefined, - type: K, - listener: (this: OffscreenCanvas, event: OffscreenCanvasEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends OscillatorNode, - K extends keyof AudioScheduledSourceNodeEventMap, ->( - target: T | undefined, - type: K, - listener: (this: OscillatorNode, event: AudioScheduledSourceNodeEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: PaymentRequest, event: PaymentRequestEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: Performance, event: PerformanceEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends PermissionStatus, - K extends keyof PermissionStatusEventMap, ->( - target: T | undefined, - type: K, - listener: (this: PermissionStatus, event: PermissionStatusEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends PictureInPictureWindow, - K extends keyof PictureInPictureWindowEventMap, ->( - target: T | undefined, - type: K, - listener: (this: PictureInPictureWindow, event: PictureInPictureWindowEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: RTCDTMFSender, event: RTCDTMFSenderEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: RTCDataChannel, event: RTCDataChannelEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends RTCDtlsTransport, - K extends keyof RTCDtlsTransportEventMap, ->( - target: T | undefined, - type: K, - listener: (this: RTCDtlsTransport, event: RTCDtlsTransportEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends RTCIceTransport, - K extends keyof RTCIceTransportEventMap, ->( - target: T | undefined, - type: K, - listener: (this: RTCIceTransport, event: RTCIceTransportEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends RTCPeerConnection, - K extends keyof RTCPeerConnectionEventMap, ->( - target: T | undefined, - type: K, - listener: (this: RTCPeerConnection, event: RTCPeerConnectionEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends RTCSctpTransport, - K extends keyof RTCSctpTransportEventMap, ->( - target: T | undefined, - type: K, - listener: (this: RTCSctpTransport, event: RTCSctpTransportEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: RemotePlayback, event: RemotePlaybackEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGAElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGAnimateElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends SVGAnimateMotionElement, - K extends keyof SVGElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: SVGAnimateMotionElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends SVGAnimateTransformElement, - K extends keyof SVGElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: SVGAnimateTransformElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGAnimationElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGCircleElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGClipPathElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends SVGComponentTransferFunctionElement, - K extends keyof SVGElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: SVGComponentTransferFunctionElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGDefsElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGDescElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGEllipseElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGFEBlendElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends SVGFEColorMatrixElement, - K extends keyof SVGElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: SVGFEColorMatrixElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends SVGFEComponentTransferElement, - K extends keyof SVGElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: SVGFEComponentTransferElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends SVGFECompositeElement, - K extends keyof SVGElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: SVGFECompositeElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends SVGFEConvolveMatrixElement, - K extends keyof SVGElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: SVGFEConvolveMatrixElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends SVGFEDiffuseLightingElement, - K extends keyof SVGElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: SVGFEDiffuseLightingElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends SVGFEDisplacementMapElement, - K extends keyof SVGElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: SVGFEDisplacementMapElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends SVGFEDistantLightElement, - K extends keyof SVGElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: SVGFEDistantLightElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends SVGFEDropShadowElement, - K extends keyof SVGElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: SVGFEDropShadowElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGFEFloodElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGFEFuncAElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGFEFuncBElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGFEFuncGElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGFEFuncRElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends SVGFEGaussianBlurElement, - K extends keyof SVGElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: SVGFEGaussianBlurElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGFEImageElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGFEMergeElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends SVGFEMergeNodeElement, - K extends keyof SVGElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: SVGFEMergeNodeElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends SVGFEMorphologyElement, - K extends keyof SVGElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: SVGFEMorphologyElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGFEOffsetElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends SVGFEPointLightElement, - K extends keyof SVGElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: SVGFEPointLightElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends SVGFESpecularLightingElement, - K extends keyof SVGElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: SVGFESpecularLightingElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends SVGFESpotLightElement, - K extends keyof SVGElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: SVGFESpotLightElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGFETileElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends SVGFETurbulenceElement, - K extends keyof SVGElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: SVGFETurbulenceElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGFilterElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends SVGForeignObjectElement, - K extends keyof SVGElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: SVGForeignObjectElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGGElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGGeometryElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGGradientElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGGraphicsElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGImageElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGLineElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends SVGLinearGradientElement, - K extends keyof SVGElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: SVGLinearGradientElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGMPathElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGMarkerElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGMaskElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGMetadataElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGPathElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGPatternElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGPolygonElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGPolylineElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends SVGRadialGradientElement, - K extends keyof SVGElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: SVGRadialGradientElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGRectElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGSVGElement, event: SVGSVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGScriptElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGSetElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGStopElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGStyleElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGSwitchElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGSymbolElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGTSpanElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends SVGTextContentElement, - K extends keyof SVGElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: SVGTextContentElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGTextElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGTextPathElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends SVGTextPositioningElement, - K extends keyof SVGElementEventMap, ->( - target: T | undefined, - type: K, - listener: (this: SVGTextPositioningElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGTitleElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGUseElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SVGViewElement, event: SVGElementEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends ScreenOrientation, - K extends keyof ScreenOrientationEventMap, ->( - target: T | undefined, - type: K, - listener: (this: ScreenOrientation, event: ScreenOrientationEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends ScriptProcessorNode, - K extends keyof ScriptProcessorNodeEventMap, ->( - target: T | undefined, - type: K, - listener: (this: ScriptProcessorNode, event: ScriptProcessorNodeEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: ServiceWorker, event: ServiceWorkerEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends ServiceWorkerContainer, - K extends keyof ServiceWorkerContainerEventMap, ->( - target: T | undefined, - type: K, - listener: (this: ServiceWorkerContainer, event: ServiceWorkerContainerEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends ServiceWorkerRegistration, - K extends keyof ServiceWorkerRegistrationEventMap, ->( - target: T | undefined, - type: K, - listener: (this: ServiceWorkerRegistration, event: ServiceWorkerRegistrationEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: ShadowRoot, event: ShadowRootEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SharedWorker, event: AbstractWorkerEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: SourceBuffer, event: SourceBufferEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends SourceBufferList, - K extends keyof SourceBufferListEventMap, ->( - target: T | undefined, - type: K, - listener: (this: SourceBufferList, event: SourceBufferListEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends SpeechSynthesis, - K extends keyof SpeechSynthesisEventMap, ->( - target: T | undefined, - type: K, - listener: (this: SpeechSynthesis, event: SpeechSynthesisEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends SpeechSynthesisUtterance, - K extends keyof SpeechSynthesisUtteranceEventMap, ->( - target: T | undefined, - type: K, - listener: (this: SpeechSynthesisUtterance, event: SpeechSynthesisUtteranceEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: TextTrack, event: TextTrackEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: TextTrackCue, event: TextTrackCueEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: TextTrackList, event: TextTrackListEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: VTTCue, event: TextTrackCueEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: VisualViewport, event: VisualViewportEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: WebSocket, event: WebSocketEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: Window, event: WindowEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener< - T extends WindowEventHandlers, - K extends keyof WindowEventHandlersEventMap, ->( - target: T | undefined, - type: K, - listener: (this: WindowEventHandlers, event: WindowEventHandlersEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: Worker, event: WorkerEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; - -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: XMLDocument, event: DocumentEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; +): void { + const listenerRef = useRefValue(listener); -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: XMLHttpRequest, event: XMLHttpRequestEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; + useEffect(() => { + const target = getRefObjectOptionValue(targetOption); -export function useEventListener< - T extends XMLHttpRequestEventTarget, - K extends keyof XMLHttpRequestEventTargetEventMap, ->( - target: T | undefined, - type: K, - listener: (this: XMLHttpRequestEventTarget, event: XMLHttpRequestEventTargetEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; + function callback(this: unknown, event: Event): void { + listenerRef.current?.(event); + } -export function useEventListener< - T extends XMLHttpRequestUpload, - K extends keyof XMLHttpRequestEventTargetEventMap, ->( - target: T | undefined, - type: K, - listener: (this: XMLHttpRequestUpload, event: XMLHttpRequestEventTargetEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void; + target?.addEventListener(type, callback, options); -export function useEventListener( - target: T | undefined, - type: K, - listener: (this: Window, event: WindowEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void { - useEffect(() => { - target?.addEventListener(type, listener, options); return () => { - target?.removeEventListener(type, listener, options); + target?.removeEventListener(type, callback, options); }; - }, [target, type, listener, options]); + }, [listenerRef, options, targetOption, type]); } diff --git a/src/hooks/useHasFocus/useHasFocus.ts b/src/hooks/useHasFocus/useHasFocus.ts index 3f00675..cfca9b1 100644 --- a/src/hooks/useHasFocus/useHasFocus.ts +++ b/src/hooks/useHasFocus/useHasFocus.ts @@ -1,5 +1,6 @@ import { useCallback, useState, type RefObject } from 'react'; -import { useDocumentEventListener } from '../useDocumentEventListener/useDocumentEventListener.js'; +import { useDocument } from '../useDocument/useDocument.js'; +import { useEventListener } from '../useEventListener/useEventListener.js'; export type FocusPseudoSelector = ':focus' | ':focus-visible' | ':focus-within'; @@ -21,8 +22,10 @@ export function useHasFocus( setState(matches()); }, [matches, setState]); - useDocumentEventListener('focusin', onUpdate); - useDocumentEventListener('focusout', onUpdate); + const document = useDocument(); + + useEventListener(document, 'focusin', onUpdate); + useEventListener(document, 'focusout', onUpdate); return state; } diff --git a/src/hooks/useMediaQuery/useMediaQuery.component.tsx b/src/hooks/useMediaQuery/useMediaQuery.component.tsx index e1e7354..bbdc87f 100644 --- a/src/hooks/useMediaQuery/useMediaQuery.component.tsx +++ b/src/hooks/useMediaQuery/useMediaQuery.component.tsx @@ -1,7 +1,7 @@ import type { ReactElement } from 'react'; import { useMediaQuery } from './useMediaQuery.js'; -export function Component(): ReactElement { +export function UseMediaQueryComponent(): ReactElement { // @ts-expect-error variable is defined in index.html const isMinWidth480px = useMediaQuery('--min-width-480'); diff --git a/src/hooks/useMediaQuery/useMediaQuery.playwright.tsx b/src/hooks/useMediaQuery/useMediaQuery.playwright.tsx index 7b48ed9..ac7038b 100644 --- a/src/hooks/useMediaQuery/useMediaQuery.playwright.tsx +++ b/src/hooks/useMediaQuery/useMediaQuery.playwright.tsx @@ -1,5 +1,5 @@ import { expect, test } from '@playwright/experimental-ct-react'; -import { Component } from './useMediaQuery.component.js'; +import { UseMediaQueryComponent } from './useMediaQuery.component.js'; test('Should detect initial screen size', async ({ page, mount }) => { await page.setViewportSize({ @@ -7,13 +7,13 @@ test('Should detect initial screen size', async ({ page, mount }) => { height: 480, }); - const component = await mount(, {}); + const component = await mount(); await expect(component).toContainText('large'); }); test('Should detect screen size changes', async ({ page, mount }) => { - await mount(, {}); + await mount(); await page.setViewportSize({ width: 250, @@ -21,16 +21,12 @@ test('Should detect screen size changes', async ({ page, mount }) => { }); // Wait for the resize to be handled by the hook - await page.getByText('small').waitFor({ - timeout: 100, - }); + await page.getByText('small').waitFor(); await page.setViewportSize({ width: 480, height: 480, }); - await page.getByText('large').waitFor({ - timeout: 100, - }); + await page.getByText('large').waitFor(); }); diff --git a/src/hooks/useMediaQuery/useMediaQuery.ts b/src/hooks/useMediaQuery/useMediaQuery.ts index 710cb90..c408175 100644 --- a/src/hooks/useMediaQuery/useMediaQuery.ts +++ b/src/hooks/useMediaQuery/useMediaQuery.ts @@ -50,7 +50,9 @@ export function useMediaQuery(mediaQueryVariableName: MediaQueries, defaultValue }, [defaultValue, mediaQueryList?.matches, mediaQueryVariableName]); useEventListener(mediaQueryList, 'change', (event) => { - setMatches(event.matches); + if (event instanceof MediaQueryListEvent) { + setMatches(event.matches); + } }); return matches ?? defaultValue; diff --git a/src/hooks/useRefValue/useRefValue.ts b/src/hooks/useRefValue/useRefValue.ts new file mode 100644 index 0000000..54ecf7b --- /dev/null +++ b/src/hooks/useRefValue/useRefValue.ts @@ -0,0 +1,8 @@ +import { type RefObject, useRef } from 'react'; + +export function useRefValue(value: T): RefObject { + const ref = useRef(value); + ref.current = value; + + return ref; +} diff --git a/src/hooks/useWindow/useWindow.mdx b/src/hooks/useWindow/useWindow.mdx new file mode 100644 index 0000000..21a36c7 --- /dev/null +++ b/src/hooks/useWindow/useWindow.mdx @@ -0,0 +1,28 @@ +import { Meta } from '@storybook/blocks'; + + + +# useWindow + +This hook returns the Window instance when it's availbable in the environment. If the Window is not +available, it will return `undefined`. + +## Reference + +```ts +function useWindow(): Window | undefined; +``` + +## Usage + +```tsx +function DemoComponent(): ReactNode { + const window = useWindow(ref, ':focus-within'); + + useEventListener(window, 'keydown', (event) => { + // do something + }); + + return null; +} +``` diff --git a/src/hooks/useWindow/useWindow.test.ts b/src/hooks/useWindow/useWindow.test.ts new file mode 100644 index 0000000..3c358c4 --- /dev/null +++ b/src/hooks/useWindow/useWindow.test.ts @@ -0,0 +1,10 @@ +import { renderHook } from '@testing-library/react'; +import { useWindow } from './useWindow.js'; + +describe('useWindow', () => { + it('Should return the window', () => { + const result = renderHook(useWindow); + + expect(result.result.current).toBe(window); + }); +}); diff --git a/src/hooks/useWindow/useWindow.ts b/src/hooks/useWindow/useWindow.ts new file mode 100644 index 0000000..4f3ce01 --- /dev/null +++ b/src/hooks/useWindow/useWindow.ts @@ -0,0 +1,9 @@ +/** + * Returns undefined when in a server environment, otherwise returns window. + * + * Note: be careful when using this hook, it returns a different value on on server + * and client. + */ +export function useWindow(): (Window & typeof globalThis) | undefined { + return typeof window === 'undefined' ? undefined : window; +} diff --git a/src/hooks/useWindowEventListener/useWindowEventListener.stories.mdx b/src/hooks/useWindowEventListener/useWindowEventListener.stories.mdx deleted file mode 100644 index 438bf8b..0000000 --- a/src/hooks/useWindowEventListener/useWindowEventListener.stories.mdx +++ /dev/null @@ -1,30 +0,0 @@ -import { Meta } from '@storybook/blocks'; - - - -# useWindowEventListener - -This hook allows you to add a window event listener and remove it when the component unmounts. - -## Reference - -```ts -function useWindowEventListener( - event: T, - callback: (event: WindowEventMap[T]) => void, -): void; -``` - -## Usage - -```tsx -function DemoComponent(): ReactElement { - const [size, setSize] = useState([0, 0]); - - useWindowEventListener('resize', () => { - setSize([window.innerWidth, window.innerHeight]); - }); - - return
{size.join('x')}
; -} -``` diff --git a/src/hooks/useWindowEventListener/useWindowEventListener.stories.tsx b/src/hooks/useWindowEventListener/useWindowEventListener.stories.tsx deleted file mode 100644 index 3e9d6be..0000000 --- a/src/hooks/useWindowEventListener/useWindowEventListener.stories.tsx +++ /dev/null @@ -1,33 +0,0 @@ -/* eslint-disable react/jsx-no-literals */ -import type { StoryObj } from '@storybook/react'; -import { type ReactElement, useState } from 'react'; -import { useWindowEventListener } from './useWindowEventListener.js'; - -export default { - title: 'hooks/useWindowEventListener', -}; - -function DemoComponent(): ReactElement { - const [size, setSize] = useState([0, 0]); - - useWindowEventListener('resize', () => { - setSize([window.innerWidth, window.innerHeight]); - }); - - return ( -
-
-

Instructions!

-

Resize the viewport to listen to the resize event on the window.

-
- -
Viewport size: {size.join('x')}
-
- ); -} - -export const Demo: StoryObj = { - render() { - return ; - }, -}; diff --git a/src/hooks/useWindowEventListener/useWindowEventListener.test.tsx b/src/hooks/useWindowEventListener/useWindowEventListener.test.tsx deleted file mode 100644 index e7d7551..0000000 --- a/src/hooks/useWindowEventListener/useWindowEventListener.test.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { jest } from '@jest/globals'; -import { renderHook } from '@testing-library/react'; -import { useWindowEventListener } from './useWindowEventListener.js'; - -describe('useWindowEventListener', () => { - it('should not crash', async () => { - const spy = jest.fn(); - - renderHook(() => { - useWindowEventListener('resize', () => { - spy(); - - // eslint-disable-next-line no-console - console.log('window resized'); - }); - }); - - expect(spy).not.toHaveBeenCalled(); - }); -}); diff --git a/src/hooks/useWindowEventListener/useWindowEventListener.ts b/src/hooks/useWindowEventListener/useWindowEventListener.ts deleted file mode 100644 index 20b7db6..0000000 --- a/src/hooks/useWindowEventListener/useWindowEventListener.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { useEventListener } from '../useEventListener/useEventListener.js'; - -/** - * SSR-safe hook that adds an event listener to the window. - */ -export function useWindowEventListener( - type: K, - listener: (this: Window, event: WindowEventMap[K]) => void, - options?: boolean | AddEventListenerOptions, -): void { - useEventListener(typeof window === 'undefined' ? undefined : window, type, listener, options); -} diff --git a/src/index.ts b/src/index.ts index 4670f9f..7baa2e7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,7 @@ /* PLOP_ADD_EXPORT */ export * from './components/AutoFill/AutoFill.js'; export * from './hocs/ensuredForwardRef/ensuredForwardRef.js'; -export * from './hooks/useDocumentEventListener/useDocumentEventListener.js'; +export * from './hooks/useDocument/useDocument.js'; export * from './hooks/useEventListener/useEventListener.js'; export * from './hooks/useHasFocus/useHasFocus.js'; export * from './hooks/useIsomorphicLayoutEffect/useIsomorphicLayoutEffect.js'; @@ -12,9 +12,11 @@ export * from './hooks/useRefs/useRefs.types.js'; export * from './hooks/useRefs/utils/assertAndUnwrapRefs/assertAndUnwrapRefs.js'; export * from './hooks/useRefs/utils/unwrapRefs/unwrapRefs.js'; export * from './hooks/useRefs/utils/unwrapRefs/unwrapRefs.types.js'; +export * from './hooks/useRefValue/useRefValue.js'; export * from './hooks/useRegisterRef/useRegisterRef.js'; export * from './hooks/useResizeObserver/useResizeObserver.js'; export * from './hooks/useToggle/useToggle.js'; export * from './hooks/useUnmount/useUnmount.js'; -export * from './hooks/useWindowEventListener/useWindowEventListener.js'; +export * from './hooks/useWindow/useWindow.js'; export * from './utils/arrayRef/arrayRef.js'; +export * from './utils/getRefObjectOption/getRefObjectOption.js'; diff --git a/src/utils/getRefObjectOption/getRefObjectOption.mdx b/src/utils/getRefObjectOption/getRefObjectOption.mdx new file mode 100644 index 0000000..556a65a --- /dev/null +++ b/src/utils/getRefObjectOption/getRefObjectOption.mdx @@ -0,0 +1,53 @@ +import { Meta } from '@storybook/blocks'; + + + +# getRefObjectOption + +This module contains utilities for working with ref objects. + +## `RefObjectOption` + +Type for a value that is optionally wrapped in a RefObject type. This is useful for cases where a +value may be passed as either a plain value or a RefObject containing the value. + +### Example usage + +```ts +type MyType = RefObjectOption; + +const plainValue = 'foo' satisfies MyType; // plain value +const refObjectValue = { current: 'bar' } satisfies MyType; // RefObject containing value +``` + +## `RefObjectOptionValue>` + +Type to get the value of a RefObjectOption. If the input type is a RefObject, it returns the type +T. Otherwise, it returns the input type itself. + +### Example usage + +```ts +type MyType = RefObjectOption; +type MyValueType = RefObjectOptionValue; // string +``` + +## `getRefObjectOptionValue(target: RefObjectOption): T | null` + +This function allows you to get the current value of a ref object, or the value as is. + +- `target` - The target value, which may be a RefObject or a plain value + +### Returns + +The current value of the ref object, or the input value if it is not a ref object. + +### Example usage + +```ts +const myRefObject = { current: 'foo' }; +getRefObjectOptionValue(myRefObject); // 'foo' + +const myValue = 'bar'; +getRefObjectOptionValue(myValue); // 'bar' +``` diff --git a/src/utils/getRefObjectOption/getRefObjectOption.test.ts b/src/utils/getRefObjectOption/getRefObjectOption.test.ts new file mode 100644 index 0000000..97d98a6 --- /dev/null +++ b/src/utils/getRefObjectOption/getRefObjectOption.test.ts @@ -0,0 +1,21 @@ +import { getRefObjectOptionValue } from './getRefObjectOption.js'; + +describe('getRefObjectOption', () => { + it('should return the current value of the ref object if it exists', () => { + const value = { current: 'hello' }; + const result = getRefObjectOptionValue(value); + + expect(result).toEqual('hello'); + }); + + it('should return the original value if it is not a ref object', () => { + const value = 'world'; + const result = getRefObjectOptionValue(value); + + expect(result).toEqual('world'); + }); + + it('should return null if the input is null', () => { + expect(null).toBeNull(); + }); +}); diff --git a/src/utils/getRefObjectOption/getRefObjectOption.ts b/src/utils/getRefObjectOption/getRefObjectOption.ts new file mode 100644 index 0000000..8ba131f --- /dev/null +++ b/src/utils/getRefObjectOption/getRefObjectOption.ts @@ -0,0 +1,23 @@ +import type { RefObject } from 'react'; + +/** + * Type for a value that is optionally wrapped in a RefObject type + */ +export type RefObjectOption = RefObject | T; + +/** + * Type to get the value of a RefObjectOption + */ +export type RefObjectOptionValue> = T extends RefObject + ? U + : T; + +/** + * This function allows you to get the current value of a ref object, or the + * value as is. + */ +export function getRefObjectOptionValue(target: RefObjectOption): T | null { + return target !== null && typeof target === 'object' && 'current' in target + ? target.current + : target; +}