@mediamonks/react-kit 1.0.0-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +125 -0
  3. package/dist/_hooks/useStorybookLog.d.ts +21 -0
  4. package/dist/_hooks/useStorybookLog.js +29 -0
  5. package/dist/_utils/childrenAreEqual.d.ts +2 -0
  6. package/dist/_utils/childrenAreEqual.js +16 -0
  7. package/dist/_utils/getId.d.ts +1 -0
  8. package/dist/_utils/getId.js +4 -0
  9. package/dist/_utils/isNonNullableRecord/isNonNullableRecord.d.ts +9 -0
  10. package/dist/_utils/isNonNullableRecord/isNonNullableRecord.js +6 -0
  11. package/dist/_utils/trimEnd/trimEnd.d.ts +4 -0
  12. package/dist/_utils/trimEnd/trimEnd.js +9 -0
  13. package/dist/components/AutoFill/AutoFill.d.ts +13 -0
  14. package/dist/components/AutoFill/AutoFill.js +33 -0
  15. package/dist/gsap/animations.d.ts +11 -0
  16. package/dist/gsap/animations.js +35 -0
  17. package/dist/gsap/components/SplitTextWrapper/SplitTextWrapper.d.ts +24 -0
  18. package/dist/gsap/components/SplitTextWrapper/SplitTextWrapper.js +28 -0
  19. package/dist/gsap/hooks/useAnimation/useAnimation.d.ts +6 -0
  20. package/dist/gsap/hooks/useAnimation/useAnimation.js +19 -0
  21. package/dist/gsap/hooks/useExposeAnimation/useExposeAnimation.d.ts +6 -0
  22. package/dist/gsap/hooks/useExposeAnimation/useExposeAnimation.js +28 -0
  23. package/dist/gsap/hooks/useExposedAnimation/useExposedAnimation.d.ts +5 -0
  24. package/dist/gsap/hooks/useExposedAnimation/useExposedAnimation.js +13 -0
  25. package/dist/gsap/hooks/useExposedAnimations/useExposedAnimations.d.ts +5 -0
  26. package/dist/gsap/hooks/useExposedAnimations/useExposedAnimations.js +32 -0
  27. package/dist/gsap/hooks/useFlip/useFlip.d.ts +3 -0
  28. package/dist/gsap/hooks/useFlip/useFlip.js +18 -0
  29. package/dist/gsap/hooks/useScrollAnimation/useScrollAnimation.d.ts +9 -0
  30. package/dist/gsap/hooks/useScrollAnimation/useScrollAnimation.js +26 -0
  31. package/dist/gsap/utils/getAnimation/getAnimation.d.ts +4 -0
  32. package/dist/gsap/utils/getAnimation/getAnimation.js +7 -0
  33. package/dist/hocs/ensuredForwardRef/ensuredForwardRef.d.ts +11 -0
  34. package/dist/hocs/ensuredForwardRef/ensuredForwardRef.js +31 -0
  35. package/dist/hooks/useClientSideValue/useClientSideValue.d.ts +11 -0
  36. package/dist/hooks/useClientSideValue/useClientSideValue.js +19 -0
  37. package/dist/hooks/useEventListener/useEventListener.d.ts +2 -0
  38. package/dist/hooks/useEventListener/useEventListener.js +17 -0
  39. package/dist/hooks/useForceRerender/useForceRerender.d.ts +11 -0
  40. package/dist/hooks/useForceRerender/useForceRerender.js +18 -0
  41. package/dist/hooks/useHasFocus/useHasFocus.d.ts +9 -0
  42. package/dist/hooks/useHasFocus/useHasFocus.js +18 -0
  43. package/dist/hooks/useIntersectionObserver/useIntersectionObserver.d.ts +13 -0
  44. package/dist/hooks/useIntersectionObserver/useIntersectionObserver.js +38 -0
  45. package/dist/hooks/useInterval/useInterval.d.ts +8 -0
  46. package/dist/hooks/useInterval/useInterval.js +25 -0
  47. package/dist/hooks/useMediaDuration/useMediaDuration.d.ts +5 -0
  48. package/dist/hooks/useMediaDuration/useMediaDuration.js +20 -0
  49. package/dist/hooks/useMediaQuery/useMediaQuery.d.ts +33 -0
  50. package/dist/hooks/useMediaQuery/useMediaQuery.js +32 -0
  51. package/dist/hooks/useMutationObserver/useMutationObserver.d.ts +16 -0
  52. package/dist/hooks/useMutationObserver/useMutationObserver.js +36 -0
  53. package/dist/hooks/useRafCallback/useRafCallback.d.ts +5 -0
  54. package/dist/hooks/useRafCallback/useRafCallback.js +21 -0
  55. package/dist/hooks/useRefValue/useRefValue.d.ts +11 -0
  56. package/dist/hooks/useRefValue/useRefValue.js +15 -0
  57. package/dist/hooks/useRefs/useRefs.d.ts +5 -0
  58. package/dist/hooks/useRefs/useRefs.js +17 -0
  59. package/dist/hooks/useRefs/useRefs.types.d.ts +15 -0
  60. package/dist/hooks/useRefs/useRefs.types.js +1 -0
  61. package/dist/hooks/useRefs/utils/assertAndUnwrapRefs/assertAndUnwrapRefs.d.ts +14 -0
  62. package/dist/hooks/useRefs/utils/assertAndUnwrapRefs/assertAndUnwrapRefs.js +19 -0
  63. package/dist/hooks/useRefs/utils/unwrapRefs/unwrapRefs.d.ts +6 -0
  64. package/dist/hooks/useRefs/utils/unwrapRefs/unwrapRefs.js +12 -0
  65. package/dist/hooks/useRefs/utils/unwrapRefs/unwrapRefs.types.d.ts +7 -0
  66. package/dist/hooks/useRefs/utils/unwrapRefs/unwrapRefs.types.js +1 -0
  67. package/dist/hooks/useRefs/utils/validateAndUnwrapRefs/validateAndUnwrapRefs.d.ts +12 -0
  68. package/dist/hooks/useRefs/utils/validateAndUnwrapRefs/validateAndUnwrapRefs.js +17 -0
  69. package/dist/hooks/useResizeObserver/useResizeObserver.d.ts +9 -0
  70. package/dist/hooks/useResizeObserver/useResizeObserver.js +22 -0
  71. package/dist/hooks/useStaticValue/useStaticValue.d.ts +6 -0
  72. package/dist/hooks/useStaticValue/useStaticValue.js +11 -0
  73. package/dist/hooks/useToggle/useToggle.d.ts +16 -0
  74. package/dist/hooks/useToggle/useToggle.js +23 -0
  75. package/dist/index.d.ts +44 -0
  76. package/dist/index.js +46 -0
  77. package/dist/lifecycle/components/CrossFlow/CrossFlow.d.ts +3 -0
  78. package/dist/lifecycle/components/CrossFlow/CrossFlow.js +55 -0
  79. package/dist/lifecycle/components/TransitionPresence/TransitionPresence.context.d.ts +4 -0
  80. package/dist/lifecycle/components/TransitionPresence/TransitionPresence.context.js +2 -0
  81. package/dist/lifecycle/components/TransitionPresence/TransitionPresence.d.ts +13 -0
  82. package/dist/lifecycle/components/TransitionPresence/TransitionPresence.js +61 -0
  83. package/dist/lifecycle/hooks/useBeforeMount/useBeforeMount.d.ts +11 -0
  84. package/dist/lifecycle/hooks/useBeforeMount/useBeforeMount.js +21 -0
  85. package/dist/lifecycle/hooks/useBeforeUnmount/useBeforeUnmount.d.ts +11 -0
  86. package/dist/lifecycle/hooks/useBeforeUnmount/useBeforeUnmount.js +39 -0
  87. package/dist/lifecycle/hooks/useIsMounted/useIsMounted.d.ts +13 -0
  88. package/dist/lifecycle/hooks/useIsMounted/useIsMounted.js +34 -0
  89. package/dist/lifecycle/hooks/useIsMountedState/useIsMountedState.d.ts +9 -0
  90. package/dist/lifecycle/hooks/useIsMountedState/useIsMountedState.js +21 -0
  91. package/dist/lifecycle/hooks/useMount/useMount.d.ts +12 -0
  92. package/dist/lifecycle/hooks/useMount/useMount.js +16 -0
  93. package/dist/lifecycle/hooks/useUnmount/useUnmount.d.ts +12 -0
  94. package/dist/lifecycle/hooks/useUnmount/useUnmount.js +19 -0
  95. package/dist/nextjs/useIsomorphicLayoutEffect/useIsomorphicLayoutEffect.d.ts +18 -0
  96. package/dist/nextjs/useIsomorphicLayoutEffect/useIsomorphicLayoutEffect.js +18 -0
  97. package/dist/utils/arrayRef/arrayRef.d.ts +8 -0
  98. package/dist/utils/arrayRef/arrayRef.js +35 -0
  99. package/dist/utils/createTimeout/createTimeout.d.ts +5 -0
  100. package/dist/utils/createTimeout/createTimeout.js +9 -0
  101. package/dist/utils/isRefObject/isRefObject.d.ts +5 -0
  102. package/dist/utils/isRefObject/isRefObject.js +6 -0
  103. package/dist/utils/unref/unref.d.ts +13 -0
  104. package/dist/utils/unref/unref.js +7 -0
  105. package/package.json +131 -0
@@ -0,0 +1,31 @@
1
+ import { createRef, forwardRef, useMemo, } from 'react';
2
+ export function ensuredForwardRef(
3
+ // eslint-disable-next-line @typescript-eslint/naming-convention
4
+ Component) {
5
+ // eslint-disable-next-line react/display-name
6
+ return forwardRef((props, ref) => {
7
+ const refObject = useMemo(() => {
8
+ // At runtime, ref _could_ be undefined when this component is rendered
9
+ // from a framework level that doesn't leverage typescript.
10
+ // Example is Next.js with dynamic imports.
11
+ // This can't be covered with tests.
12
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
13
+ if (ref !== null && ref !== undefined && 'current' in ref) {
14
+ return ref;
15
+ }
16
+ return new Proxy(createRef(), {
17
+ set(target, prop, newValue) {
18
+ if (prop !== 'current') {
19
+ return false;
20
+ }
21
+ // Update proxy ref value
22
+ target.current = newValue;
23
+ // Call ref function when it exists
24
+ ref?.(newValue);
25
+ return true;
26
+ },
27
+ });
28
+ }, [ref]);
29
+ return Component(props, refObject);
30
+ });
31
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Hook that returns the value returned by a callback function that is only called on client-side.
3
+ *
4
+ * @example
5
+ * function MyComponent() {
6
+ * const value = useClientSideValue(Date.now, null);
7
+ *
8
+ * return <div>{value ?? 'n/a'}</div>;
9
+ * }
10
+ */
11
+ export declare function useClientSideValue<T>(callback: () => T, initialValue?: T): typeof initialValue;
@@ -0,0 +1,19 @@
1
+ import { useState } from 'react';
2
+ import { useMount } from '../../lifecycle/hooks/useMount/useMount.js';
3
+ /**
4
+ * Hook that returns the value returned by a callback function that is only called on client-side.
5
+ *
6
+ * @example
7
+ * function MyComponent() {
8
+ * const value = useClientSideValue(Date.now, null);
9
+ *
10
+ * return <div>{value ?? 'n/a'}</div>;
11
+ * }
12
+ */
13
+ export function useClientSideValue(callback, initialValue) {
14
+ const [value, setValue] = useState(initialValue);
15
+ useMount(() => {
16
+ setValue(callback);
17
+ });
18
+ return value;
19
+ }
@@ -0,0 +1,2 @@
1
+ import { type RefObject } from 'react';
2
+ export declare function useEventListener<T extends EventTarget>(targetOption: RefObject<T> | T | null | undefined, type: string, listener: EventListener, options?: boolean | AddEventListenerOptions): void;
@@ -0,0 +1,17 @@
1
+ /* eslint-disable @typescript-eslint/unified-signatures, @typescript-eslint/no-explicit-any */
2
+ import { useEffect } from 'react';
3
+ import { unref } from '../../utils/unref/unref.js';
4
+ import { useRefValue } from '../useRefValue/useRefValue.js';
5
+ export function useEventListener(targetOption, type, listener, options) {
6
+ const listenerRef = useRefValue(listener);
7
+ useEffect(() => {
8
+ const target = unref(targetOption);
9
+ function callback(event) {
10
+ listenerRef.current?.(event);
11
+ }
12
+ target?.addEventListener(type, callback, options);
13
+ return () => {
14
+ target?.removeEventListener(type, callback, options);
15
+ };
16
+ }, [listenerRef, options, targetOption, type]);
17
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Forces a rerender of the component when the returned function is called.
3
+ *
4
+ * This should only be used when there is no other state that can be used to trigger a rerender.
5
+ *
6
+ * Examples could be to force a rerender after a timeout or interval, to compare the current time
7
+ * (that changes) with the time when the component was last updated.
8
+ *
9
+ * @returns A function that can be called to force a rerender.
10
+ */
11
+ export declare function useForceRerender(): () => void;
@@ -0,0 +1,18 @@
1
+ import { useReducer } from 'react';
2
+ // use an arbitrary large number instead of a toggle boolean to avoid potential optimization issues
3
+ // when called multiple times in a single render, but have a cap to avoid overflow
4
+ const updateReducer = (value) => (value + 1) % Number.MAX_SAFE_INTEGER;
5
+ /**
6
+ * Forces a rerender of the component when the returned function is called.
7
+ *
8
+ * This should only be used when there is no other state that can be used to trigger a rerender.
9
+ *
10
+ * Examples could be to force a rerender after a timeout or interval, to compare the current time
11
+ * (that changes) with the time when the component was last updated.
12
+ *
13
+ * @returns A function that can be called to force a rerender.
14
+ */
15
+ export function useForceRerender() {
16
+ const [, update] = useReducer(updateReducer, 0);
17
+ return update;
18
+ }
@@ -0,0 +1,9 @@
1
+ import { type RefObject } from 'react';
2
+ export type FocusPseudoSelector = ':focus' | ':focus-visible' | ':focus-within';
3
+ /**
4
+ * This hook allows you to determine if an element matches the :focus, :focus-visible, or :focus-within pseudo selectors.
5
+ *
6
+ * @param ref - The ref to the element to check
7
+ * @param selector - The pseudo selector to check for
8
+ */
9
+ export declare function useHasFocus(ref: RefObject<HTMLElement>, selector?: FocusPseudoSelector): boolean;
@@ -0,0 +1,18 @@
1
+ import { useCallback, useState } from 'react';
2
+ import { useEventListener } from '../useEventListener/useEventListener.js';
3
+ /**
4
+ * This hook allows you to determine if an element matches the :focus, :focus-visible, or :focus-within pseudo selectors.
5
+ *
6
+ * @param ref - The ref to the element to check
7
+ * @param selector - The pseudo selector to check for
8
+ */
9
+ export function useHasFocus(ref, selector = ':focus') {
10
+ const matches = useCallback(() => ref.current?.matches(selector) ?? false, [ref, selector]);
11
+ const [state, setState] = useState(matches);
12
+ const onUpdate = useCallback(() => {
13
+ setState(matches());
14
+ }, [matches, setState]);
15
+ useEventListener(globalThis.document, 'focusin', onUpdate);
16
+ useEventListener(globalThis.document, 'focusout', onUpdate);
17
+ return state;
18
+ }
@@ -0,0 +1,13 @@
1
+ import { type RefObject } from 'react';
2
+ /**
3
+ * This hook creates an IntersectionObserver and uses it to observe
4
+ * the target element.
5
+ *
6
+ * For a full explanation of the IntersectionObserver API, check the docs:
7
+ * https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/IntersectionObserver
8
+ *
9
+ * @param targetOrTargets - The target or targets to observe
10
+ * @param callback - The callback to fire when the element resizes
11
+ * @param options - Optional options object
12
+ */
13
+ export declare function useIntersectionObserver(targetOrTargets: RefObject<Element | ReadonlyArray<Element | null> | null>, callback: IntersectionObserverCallback, options?: IntersectionObserverInit): void;
@@ -0,0 +1,38 @@
1
+ import { useEffect } from 'react';
2
+ import { unref } from '../../utils/unref/unref.js';
3
+ import { useClientSideValue } from '../useClientSideValue/useClientSideValue.js';
4
+ import { useRefValue } from '../useRefValue/useRefValue.js';
5
+ /**
6
+ * This hook creates an IntersectionObserver and uses it to observe
7
+ * the target element.
8
+ *
9
+ * For a full explanation of the IntersectionObserver API, check the docs:
10
+ * https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/IntersectionObserver
11
+ *
12
+ * @param targetOrTargets - The target or targets to observe
13
+ * @param callback - The callback to fire when the element resizes
14
+ * @param options - Optional options object
15
+ */
16
+ export function useIntersectionObserver(targetOrTargets, callback, options) {
17
+ const callbackRef = useRefValue(callback);
18
+ const intersectionObserverInstance = useClientSideValue(() => new IntersectionObserver((entries, observer) => callbackRef.current?.(entries, observer), options));
19
+ useEffect(() => {
20
+ const targets = Array.isArray(targetOrTargets.current)
21
+ ? targetOrTargets.current
22
+ : [targetOrTargets.current];
23
+ for (const target of targets) {
24
+ const element = unref(target);
25
+ if (element) {
26
+ intersectionObserverInstance?.observe(element);
27
+ }
28
+ }
29
+ return () => {
30
+ for (const target of targets) {
31
+ const element = unref(target);
32
+ if (element) {
33
+ intersectionObserverInstance?.unobserve(element);
34
+ }
35
+ }
36
+ };
37
+ }, [intersectionObserverInstance, targetOrTargets]);
38
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * A hook that sets up an interval to repeatedly call a callback function.
3
+ *
4
+ * @param callback - The function to be called repeatedly.
5
+ * @param ms - The number of milliseconds between each call to the callback function. Defaults to 0.
6
+ * @param enabled - Whether the interval should be enabled. Defaults to true.
7
+ */
8
+ export declare function useInterval(callback: () => void, ms?: number, enabled?: boolean): void;
@@ -0,0 +1,25 @@
1
+ import { useEffect, useRef } from 'react';
2
+ import { useRefValue } from '../useRefValue/useRefValue.js';
3
+ /**
4
+ * A hook that sets up an interval to repeatedly call a callback function.
5
+ *
6
+ * @param callback - The function to be called repeatedly.
7
+ * @param ms - The number of milliseconds between each call to the callback function. Defaults to 0.
8
+ * @param enabled - Whether the interval should be enabled. Defaults to true.
9
+ */
10
+ export function useInterval(callback, ms, enabled = true) {
11
+ const callbackRef = useRefValue(callback);
12
+ const intervalRef = useRef();
13
+ useEffect(() => {
14
+ if (!enabled) {
15
+ clearInterval(intervalRef.current);
16
+ return;
17
+ }
18
+ intervalRef.current = setInterval(() => {
19
+ callbackRef.current?.();
20
+ }, ms);
21
+ return () => {
22
+ clearInterval(intervalRef.current);
23
+ };
24
+ }, [callbackRef, enabled, ms]);
25
+ }
@@ -0,0 +1,5 @@
1
+ import { type MutableRefObject } from 'react';
2
+ /**
3
+ * Retrieves the duration of the audio / video file in seconds.
4
+ */
5
+ export declare function useMediaDuration(mediaElementRef: MutableRefObject<HTMLMediaElement | null>): number;
@@ -0,0 +1,20 @@
1
+ import { useCallback, useEffect, useState } from 'react';
2
+ /**
3
+ * Retrieves the duration of the audio / video file in seconds.
4
+ */
5
+ export function useMediaDuration(mediaElementRef) {
6
+ const [mediaDuration, setMediaDuration] = useState(Number.NaN);
7
+ const updateDuration = useCallback(() => {
8
+ setMediaDuration(mediaElementRef.current?.duration ?? Number.NaN);
9
+ }, [mediaElementRef]);
10
+ useEffect(() => {
11
+ const mediaElement = mediaElementRef.current;
12
+ mediaElement?.addEventListener('durationchange', updateDuration);
13
+ // metadata could have been loaded before listener is attached
14
+ updateDuration();
15
+ return () => {
16
+ mediaElement?.removeEventListener('durationchange', updateDuration);
17
+ };
18
+ }, [mediaElementRef, updateDuration]);
19
+ return mediaDuration;
20
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * The MediaQueryVariables type is used for the `mediaQueryOrVariableName` parameter of
3
+ * `useMediaQuery`.
4
+ *
5
+ * Augment the MediaQueryVariables interface to add the names for the CSS variables that
6
+ * describe media queries in your project.
7
+ *
8
+ * @example
9
+ * import '@mediamonks/react-kit';
10
+ *
11
+ * declare module '@mediamonks/react-kit' {
12
+ * interface MediaQueryVariables {
13
+ * '--media-query-name': never;
14
+ * }
15
+ * }
16
+ */
17
+ export interface MediaQueryVariables {
18
+ }
19
+ /**
20
+ * Enables auto-completion for the variable names that are defined in `MediaQueryVariables` and also allows custom string values.
21
+ */
22
+ type MediaQueryValues = keyof MediaQueryVariables | (string & {
23
+ __custom_query__do_not_use_this_field__?: boolean;
24
+ });
25
+ export declare function getMediaQueryList(mediaQueryOrVariableName: MediaQueryValues): MediaQueryList | undefined;
26
+ /**
27
+ * Hook that returns a boolean indicating whether the media query matches.
28
+ *
29
+ * @param mediaQueryOrVariableName - The name of the CSS variable that describes the media query.
30
+ * @param defaultValue - The default value to return if the matchMedia API is not available.
31
+ */
32
+ export declare function useMediaQuery(mediaQueryOrVariableName: MediaQueryValues, defaultValue?: boolean): boolean;
33
+ export {};
@@ -0,0 +1,32 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { useEventListener } from '../useEventListener/useEventListener.js';
3
+ export function getMediaQueryList(mediaQueryOrVariableName) {
4
+ if (typeof matchMedia === 'undefined') {
5
+ return undefined;
6
+ }
7
+ const mediaQuery = mediaQueryOrVariableName.startsWith('--')
8
+ ? getComputedStyle(document.body).getPropertyValue(mediaQueryOrVariableName)
9
+ : mediaQueryOrVariableName;
10
+ return matchMedia(mediaQuery);
11
+ }
12
+ /**
13
+ * Hook that returns a boolean indicating whether the media query matches.
14
+ *
15
+ * @param mediaQueryOrVariableName - The name of the CSS variable that describes the media query.
16
+ * @param defaultValue - The default value to return if the matchMedia API is not available.
17
+ */
18
+ export function useMediaQuery(mediaQueryOrVariableName, defaultValue = false) {
19
+ const [mediaQueryList, setMediaQueryList] = useState(() => getMediaQueryList(mediaQueryOrVariableName));
20
+ const [matches, setMatches] = useState(defaultValue);
21
+ useEffect(() => {
22
+ const newMediaQueryList = getMediaQueryList(mediaQueryOrVariableName);
23
+ setMediaQueryList(newMediaQueryList);
24
+ setMatches(newMediaQueryList?.matches);
25
+ }, [defaultValue, mediaQueryList?.matches, mediaQueryOrVariableName]);
26
+ useEventListener(mediaQueryList, 'change', (event) => {
27
+ if (event instanceof MediaQueryListEvent) {
28
+ setMatches(event.matches);
29
+ }
30
+ });
31
+ return matches ?? defaultValue;
32
+ }
@@ -0,0 +1,16 @@
1
+ import { type Unreffable } from '../../index.js';
2
+ /**
3
+ * Hook that sets up a MutationObserver to watch for changes to a DOM element.
4
+ *
5
+ * @example
6
+ * function MyComponent(): ReactElement {
7
+ * const targetRef = useRef<HTMLElement>(null);
8
+ *
9
+ * useMutationObserver(targetRef, (mutations, observer) => {
10
+ * console.log('Mutations observed:', mutations);
11
+ * }, { childList: true });
12
+ *
13
+ * return <div ref={targetRef}>Hello, world!</div>;
14
+ * }
15
+ */
16
+ export declare function useMutationObserver(target: Unreffable<Element | null>, callback: MutationCallback, options: MutationObserverInit): void;
@@ -0,0 +1,36 @@
1
+ import { useEffect, useMemo } from 'react';
2
+ import { unref, useRefValue } from '../../index.js';
3
+ /**
4
+ * Hook that sets up a MutationObserver to watch for changes to a DOM element.
5
+ *
6
+ * @example
7
+ * function MyComponent(): ReactElement {
8
+ * const targetRef = useRef<HTMLElement>(null);
9
+ *
10
+ * useMutationObserver(targetRef, (mutations, observer) => {
11
+ * console.log('Mutations observed:', mutations);
12
+ * }, { childList: true });
13
+ *
14
+ * return <div ref={targetRef}>Hello, world!</div>;
15
+ * }
16
+ */
17
+ export function useMutationObserver(target, callback, options) {
18
+ const memoizedOptions = useMemo(() => options,
19
+ // Options are serializable so we can safely use JSON.stringify for the comparison
20
+ // eslint-disable-next-line react-hooks/exhaustive-deps
21
+ [JSON.stringify(options)]);
22
+ const callbackRef = useRefValue(callback);
23
+ useEffect(() => {
24
+ const element = unref(target);
25
+ if (element === null) {
26
+ return;
27
+ }
28
+ const mutationObserverInstance = new MutationObserver((mutations, observer) => {
29
+ callbackRef.current?.(mutations, observer);
30
+ });
31
+ mutationObserverInstance.observe(element, memoizedOptions);
32
+ return () => {
33
+ mutationObserverInstance.disconnect();
34
+ };
35
+ }, [callbackRef, memoizedOptions, target]);
36
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Hook that returns a function that will be called on the next animation frame.
3
+ * This is useful for performing non-blocking updates to the DOM or other state.
4
+ */
5
+ export declare function useRafCallback(callback: FrameRequestCallback): () => number;
@@ -0,0 +1,21 @@
1
+ import { useCallback, useRef } from 'react';
2
+ import { useUnmount } from '../../lifecycle/hooks/useUnmount/useUnmount.js';
3
+ import { useRefValue } from '../useRefValue/useRefValue.js';
4
+ /**
5
+ * Hook that returns a function that will be called on the next animation frame.
6
+ * This is useful for performing non-blocking updates to the DOM or other state.
7
+ */
8
+ export function useRafCallback(callback) {
9
+ const callbackRef = useRefValue(callback);
10
+ const animationFrameRef = useRef(0);
11
+ useUnmount(() => {
12
+ cancelAnimationFrame(animationFrameRef.current);
13
+ });
14
+ return useCallback(() => {
15
+ cancelAnimationFrame(animationFrameRef.current);
16
+ animationFrameRef.current = requestAnimationFrame((time) => {
17
+ callbackRef.current?.(time);
18
+ });
19
+ return animationFrameRef.current;
20
+ }, [callbackRef]);
21
+ }
@@ -0,0 +1,11 @@
1
+ import { type RefObject } from 'react';
2
+ /**
3
+ * Keeps a ref up to date with a changing value.
4
+ *
5
+ * Normal values are captured in scopes, while refs are not. Therefore a changing value in a callback
6
+ * requires the use of a ref.
7
+ *
8
+ * @param value The value to keep in the ref.
9
+ * @returns A ref object that is updated with the value.
10
+ */
11
+ export declare function useRefValue<T>(value: T): RefObject<T>;
@@ -0,0 +1,15 @@
1
+ import { useRef } from 'react';
2
+ /**
3
+ * Keeps a ref up to date with a changing value.
4
+ *
5
+ * Normal values are captured in scopes, while refs are not. Therefore a changing value in a callback
6
+ * requires the use of a ref.
7
+ *
8
+ * @param value The value to keep in the ref.
9
+ * @returns A ref object that is updated with the value.
10
+ */
11
+ export function useRefValue(value) {
12
+ const ref = useRef(value);
13
+ ref.current = value;
14
+ return ref;
15
+ }
@@ -0,0 +1,5 @@
1
+ import { type RefObject } from 'react';
2
+ /**
3
+ * Utility to automatically create refs
4
+ */
5
+ export declare function useRefs<T extends Record<string | symbol, RefObject<unknown>>>(initialTarget?: Partial<T>): T;
@@ -0,0 +1,17 @@
1
+ import { createRef, useMemo, useRef } from 'react';
2
+ /**
3
+ * Utility to automatically create refs
4
+ */
5
+ export function useRefs(initialTarget) {
6
+ const proxyTarget = useRef(initialTarget ?? {});
7
+ return useMemo(() => new Proxy(proxyTarget.current, {
8
+ get(target, prop) {
9
+ if (target[prop] !== undefined) {
10
+ return target[prop];
11
+ }
12
+ // @ts-expect-error - the type cannot be correctly inferred from the prop name because it is a symbol or string
13
+ target[prop] = createRef();
14
+ return target[prop];
15
+ },
16
+ }), []);
17
+ }
@@ -0,0 +1,15 @@
1
+ import type { MutableRefObject, RefObject } from 'react';
2
+ type UnknownRecord = Record<string | number | symbol, unknown>;
3
+ /**
4
+ * Type utility to create an object type where all values are RefObjects
5
+ */
6
+ export type Refs<T extends UnknownRecord = UnknownRecord> = {
7
+ [P in keyof T]: RefObject<T[P] | null>;
8
+ };
9
+ /**
10
+ * Type utility to create an object type where all values are MutableRefObjects
11
+ */
12
+ export type MutableRefs<T extends UnknownRecord = UnknownRecord> = {
13
+ [P in keyof T]: MutableRefObject<T[P] | null>;
14
+ };
15
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,14 @@
1
+ import { type NonNullableRecord } from '../../../../_utils/isNonNullableRecord/isNonNullableRecord.js';
2
+ import type { Refs } from '../../useRefs.types.js';
3
+ import type { UnwrapRefs } from '../unwrapRefs/unwrapRefs.types.js';
4
+ /**
5
+ * Unwraps refs and assert every field is not null
6
+ *
7
+ * NOTE: this function asserts fields known during runtime on the refs parameter.
8
+ * The useRefs Proxy only creates a field in the getter, this means that a field's
9
+ * value will still be undefined when you never reference that field. The return
10
+ * type of this function doesn't reflect that behavior.
11
+ *
12
+ * @deprecated use `validateAndUnwrapRefs` instead to avoid errors during hot reloading
13
+ */
14
+ export declare function assertAndUnwrapRefs<T extends Refs>(refs: T): NonNullableRecord<UnwrapRefs<T>>;
@@ -0,0 +1,19 @@
1
+ import { isNonNullableRecord, } from '../../../../_utils/isNonNullableRecord/isNonNullableRecord.js';
2
+ import { unwrapRefs } from '../unwrapRefs/unwrapRefs.js';
3
+ /**
4
+ * Unwraps refs and assert every field is not null
5
+ *
6
+ * NOTE: this function asserts fields known during runtime on the refs parameter.
7
+ * The useRefs Proxy only creates a field in the getter, this means that a field's
8
+ * value will still be undefined when you never reference that field. The return
9
+ * type of this function doesn't reflect that behavior.
10
+ *
11
+ * @deprecated use `validateAndUnwrapRefs` instead to avoid errors during hot reloading
12
+ */
13
+ export function assertAndUnwrapRefs(refs) {
14
+ const unwrappedRefs = unwrapRefs(refs);
15
+ if (!isNonNullableRecord(unwrappedRefs)) {
16
+ throw new Error('"refs" contains null values', refs);
17
+ }
18
+ return unwrappedRefs;
19
+ }
@@ -0,0 +1,6 @@
1
+ import type { Refs } from '../../useRefs.types.js';
2
+ import type { UnwrapRefs } from './unwrapRefs.types.js';
3
+ /**
4
+ * Unwraps RefObjects in object
5
+ */
6
+ export declare function unwrapRefs<T extends Refs>(refs: T): UnwrapRefs<T>;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Unwraps RefObjects in object
3
+ */
4
+ export function unwrapRefs(refs) {
5
+ const unwrappedRefs = {};
6
+ for (const key in refs) {
7
+ if (Object.hasOwn(refs, key)) {
8
+ unwrappedRefs[key] = refs[key].current;
9
+ }
10
+ }
11
+ return unwrappedRefs;
12
+ }
@@ -0,0 +1,7 @@
1
+ import type { RefObject } from 'react';
2
+ /**
3
+ * Unwrap RefObject values in Record
4
+ */
5
+ export type UnwrapRefs<T extends Record<string | number | symbol, RefObject<unknown>>> = {
6
+ [K in keyof T]: T[K]['current'];
7
+ };
@@ -0,0 +1,12 @@
1
+ import { type NonNullableRecord } from '../../../../_utils/isNonNullableRecord/isNonNullableRecord.js';
2
+ import type { Refs } from '../../useRefs.types.js';
3
+ import type { UnwrapRefs } from '../unwrapRefs/unwrapRefs.types.js';
4
+ /**
5
+ * Unwraps refs and assert every field is not null
6
+ *
7
+ * NOTE: this function asserts fields known during runtime on the refs parameter.
8
+ * The useRefs Proxy only creates a field in the getter, this means that a field's
9
+ * value will still be undefined when you never reference that field. The return
10
+ * type of this function doesn't reflect that behavior.
11
+ */
12
+ export declare function validateAndUnwrapRefs<T extends Refs>(refs: T): [false, undefined] | [true, NonNullableRecord<UnwrapRefs<T>>];
@@ -0,0 +1,17 @@
1
+ import { isNonNullableRecord, } from '../../../../_utils/isNonNullableRecord/isNonNullableRecord.js';
2
+ import { unwrapRefs } from '../unwrapRefs/unwrapRefs.js';
3
+ /**
4
+ * Unwraps refs and assert every field is not null
5
+ *
6
+ * NOTE: this function asserts fields known during runtime on the refs parameter.
7
+ * The useRefs Proxy only creates a field in the getter, this means that a field's
8
+ * value will still be undefined when you never reference that field. The return
9
+ * type of this function doesn't reflect that behavior.
10
+ */
11
+ export function validateAndUnwrapRefs(refs) {
12
+ const unwrappedRefs = unwrapRefs(refs);
13
+ if (!isNonNullableRecord(unwrappedRefs)) {
14
+ return [false, undefined];
15
+ }
16
+ return [true, unwrappedRefs];
17
+ }
@@ -0,0 +1,9 @@
1
+ import { type Unreffable } from '../../utils/unref/unref.js';
2
+ /**
3
+ * This hook allows you to add a ResizeObserver for an element and remove it
4
+ * when the component unmounts.
5
+ *
6
+ * @param target - The target to observe
7
+ * @param callback - The callback to fire when the element resizes
8
+ */
9
+ export declare function useResizeObserver(target: Unreffable<Element | null>, callback: ResizeObserverCallback): void;
@@ -0,0 +1,22 @@
1
+ import { useEffect } from 'react';
2
+ import { unref } from '../../utils/unref/unref.js';
3
+ /**
4
+ * This hook allows you to add a ResizeObserver for an element and remove it
5
+ * when the component unmounts.
6
+ *
7
+ * @param target - The target to observe
8
+ * @param callback - The callback to fire when the element resizes
9
+ */
10
+ export function useResizeObserver(target, callback) {
11
+ useEffect(() => {
12
+ const element = unref(target);
13
+ if (element === null) {
14
+ return;
15
+ }
16
+ const resizeObserverInstance = new ResizeObserver(callback);
17
+ resizeObserverInstance.observe(element);
18
+ return () => {
19
+ resizeObserverInstance.disconnect();
20
+ };
21
+ }, [target, callback]);
22
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Keep track of a value that is initialized once and then never changes.
3
+ *
4
+ * This behavior is similar to `useState`, but the value is never updated.
5
+ */
6
+ export declare function useStaticValue<T>(initializeFunction: (() => T) | T): T;