rooks
Version:
Essential React custom hooks ⚓ to super charge your components!
1,735 lines (1,671 loc) • 113 kB
JavaScript
// src/hooks/useOnLongHover.ts
import { useCallback as useCallback2, useEffect as useEffect4, useState } from "react";
// src/hooks/useFreshCallback.ts
import { useCallback } from "react";
// src/hooks/useFreshRef.ts
import { useEffect as useEffect2, useRef } from "react";
// src/hooks/useIsomorphicEffect.ts
import { useEffect, useLayoutEffect } from "react";
var useIsomorphicEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
// src/hooks/useFreshRef.ts
function useFreshRef(value, preferLayoutEffect = false) {
const useEffectToUse = preferLayoutEffect ? useIsomorphicEffect : useEffect2;
const ref = useRef(value);
useEffectToUse(() => {
ref.current = value;
});
return ref;
}
// src/hooks/useFreshCallback.ts
function useFreshCallback(callback) {
const freshRef = useFreshRef(callback);
const tick = useCallback(
(...args) => {
return freshRef.current(...args);
},
[freshRef]
);
return tick;
}
// src/hooks/useTimeoutWhen.ts
import { useEffect as useEffect3 } from "react";
// src/utils/noop.ts
var noop = () => {
};
// src/hooks/useTimeoutWhen.ts
function useTimeoutWhen(callback, timeoutDelayMs = 0, when = true, key = 0) {
const freshCallback = useFreshCallback(callback);
useEffect3(() => {
if (when) {
let internalCallback2 = function() {
freshCallback();
};
var internalCallback = internalCallback2;
if (typeof window !== "undefined") {
const timeout = window.setTimeout(internalCallback2, timeoutDelayMs);
return () => {
window.clearTimeout(timeout);
};
} else {
console.warn("useTimeoutWhen: window is undefined.");
}
}
return noop;
}, [timeoutDelayMs, when, key, freshCallback]);
}
// src/hooks/useOnLongHover.ts
var useOnLongHover = (callback, { duration = 300 } = {}) => {
const [targetNode, setTargetNode] = useState(null);
const ref = useCallback2((node) => {
setTargetNode(node);
}, []);
const [isHovering, setIsHovering] = useState(false);
const freshCallback = useFreshCallback(callback);
useTimeoutWhen(
() => {
freshCallback();
},
duration,
isHovering
);
const start = useCallback2(() => {
setIsHovering(true);
}, []);
const handleOnMouseLeave = useCallback2((_) => {
setIsHovering(false);
}, []);
useEffect4(() => {
if (targetNode) {
targetNode.addEventListener("mouseenter", start);
targetNode.addEventListener("mouseleave", handleOnMouseLeave);
}
return () => {
if (targetNode) {
targetNode.removeEventListener("mouseenter", start);
targetNode.removeEventListener("mouseleave", handleOnMouseLeave);
}
};
}, [start, handleOnMouseLeave, targetNode]);
return ref;
};
// src/hooks/useOnLongPress.ts
import { useCallback as useCallback3, useEffect as useEffect5, useState as useState2 } from "react";
var defaultOnClick = () => {
};
var useOnLongPress = (callback, { onClick, duration = 300 } = {}) => {
const [targetNode, setTargetNode] = useState2(null);
const ref = useCallback3((node) => {
setTargetNode(node);
}, []);
const [isPressing, setIsPressing] = useState2(false);
const freshCallback = useFreshCallback(callback);
const freshClick = useFreshCallback(onClick ?? defaultOnClick);
useTimeoutWhen(freshCallback, duration, isPressing);
const start = useCallback3((_) => {
setIsPressing(true);
}, []);
const handleOnClick = useCallback3(
(event) => {
setIsPressing(false);
freshClick(event);
},
[freshClick]
);
useEffect5(() => {
if (targetNode) {
targetNode.addEventListener("mousedown", start);
targetNode.addEventListener("mouseup", handleOnClick);
targetNode.addEventListener("mouseleave", handleOnClick);
targetNode.addEventListener("touchstart", start);
targetNode.addEventListener("touchend", handleOnClick);
targetNode.addEventListener("touchcancel", handleOnClick);
}
return () => {
if (targetNode) {
targetNode.removeEventListener("mousedown", start);
targetNode.removeEventListener("mouseup", handleOnClick);
targetNode.removeEventListener("mouseleave", handleOnClick);
targetNode.removeEventListener("touchstart", start);
targetNode.removeEventListener("touchend", handleOnClick);
targetNode.removeEventListener("touchcancel", handleOnClick);
}
};
}, [start, handleOnClick, targetNode]);
return ref;
};
// src/hooks/useMapState.ts
import { useCallback as useCallback4, useState as useState3 } from "react";
function useMapState(initialValue) {
const [map, setMap] = useState3(initialValue);
const set = useCallback4((key, value) => {
setMap((currentMap) => ({
...currentMap,
[key]: value
}));
}, []);
const has = useCallback4(
(key) => {
return typeof map[key] !== "undefined";
},
[map]
);
const setMultiple = useCallback4((nextMap) => {
setMap((currentMap) => ({
...currentMap,
...nextMap
}));
}, []);
const removeMultiple = useCallback4(
(...keys) => {
setMap((currentMap) => {
const nextMap = { ...currentMap };
for (const key of keys) {
delete nextMap[key];
}
return nextMap;
});
},
[setMap]
);
const remove = useCallback4(
(key) => {
setMap((currentMap) => {
const nextMap = { ...currentMap };
delete nextMap[key];
return nextMap;
});
},
[setMap]
);
const removeAll = useCallback4(() => {
setMap((currentMap) => {
const nextMap = { ...currentMap };
for (const key in nextMap) {
if (nextMap.hasOwnProperty(key)) {
delete nextMap[key];
}
}
return nextMap;
});
}, [setMap]);
return [
map,
{
has,
remove,
removeAll,
removeMultiple,
set,
setMultiple
}
];
}
// src/hooks/useArrayState.ts
import { useCallback as useCallback5, useMemo, useState as useState4 } from "react";
function useArrayState(initial) {
const [array, setArray] = useState4(initial ?? []);
const push = useCallback5(
(value) => {
setArray([...array, value]);
},
[array]
);
const pop = useCallback5(() => {
setArray(array.slice(0, array.length - 1));
}, [array]);
const clear = useCallback5(() => {
setArray([]);
}, []);
const unshift = useCallback5(
(value) => {
setArray([value, ...array]);
},
[array]
);
const shift = useCallback5(() => {
setArray(array.slice(1));
}, [array]);
const reverse = useCallback5(() => {
setArray([...array].reverse());
}, [array]);
const concat = useCallback5(
(value) => {
setArray([...array, ...value]);
},
[array]
);
const fill = useCallback5(
(value, start, end) => {
setArray([...array].fill(value, start, end));
},
[array]
);
const updateItemAtIndex = useCallback5(
(index, value) => {
setArray((prevArray) => {
const newArray = [...prevArray];
newArray[index] = value;
return newArray;
});
},
[setArray]
);
const splice = useCallback5(
(...args) => {
setArray((prevArray) => {
const newArray = [...prevArray];
newArray.splice(...args);
return newArray;
});
},
[setArray]
);
const removeItemAtIndex = useCallback5(
(index) => {
setArray((prevArray) => {
const newArray = [...prevArray];
newArray.splice(index, 1);
return newArray;
});
},
[setArray]
);
const replaceItemAtIndex = useCallback5(
(index, value) => {
setArray((prevArray) => {
const newArray = [...prevArray];
newArray.splice(index, 1, value);
return newArray;
});
},
[setArray]
);
const insertItemAtIndex = useCallback5(
(index, value) => {
setArray((prevArray) => {
const newArray = [...prevArray];
newArray.splice(index, 0, value);
return newArray;
});
},
[setArray]
);
const sort = useCallback5(
(compareFn) => {
setArray([...array].sort(compareFn));
},
[array]
);
const controls = useMemo(() => {
return {
push,
pop,
clear,
unshift,
shift,
reverse,
concat,
fill,
updateItemAtIndex,
setArray,
splice,
removeItemAtIndex,
replaceItemAtIndex,
insertItemAtIndex,
sort
};
}, [
push,
pop,
clear,
unshift,
shift,
reverse,
concat,
fill,
updateItemAtIndex,
setArray,
splice,
removeItemAtIndex,
replaceItemAtIndex,
insertItemAtIndex,
sort
]);
const returnValue = useMemo(() => {
return [array, controls];
}, [array, controls]);
return returnValue;
}
// src/hooks/useAsyncEffect.ts
import { useEffect as useEffect7, useRef as useRef3, useCallback as useCallback7 } from "react";
// src/hooks/useGetIsMounted.ts
import { useCallback as useCallback6, useEffect as useEffect6, useRef as useRef2 } from "react";
var useGetIsMounted = () => {
const isMountedRef = useRef2(false);
const get = useCallback6(() => isMountedRef.current, []);
useEffect6(() => {
isMountedRef.current = true;
return () => {
isMountedRef.current = false;
};
}, []);
return get;
};
// src/hooks/useAsyncEffect.ts
function useAsyncEffect(effect, deps, cleanup) {
const lastCallId = useRef3(0);
const getIsMounted = useGetIsMounted();
const effectRef = useFreshRef(effect);
const callback = useCallback7(async () => {
const callId = ++lastCallId.current;
const shouldContinueEffect = () => {
return getIsMounted() && callId === lastCallId.current;
};
try {
return await effectRef.current(shouldContinueEffect);
} catch (error) {
throw error;
}
}, [getIsMounted, ...deps]);
useEffect7(() => {
let result;
callback().then((value) => {
result = value;
});
return () => {
cleanup?.(result);
};
}, [callback, cleanup]);
}
// src/hooks/useAudio.ts
import { useState as useState5, useEffect as useEffect8 } from "react";
var noop2 = () => {
};
function useAudio(options = {}, callbacks = {}) {
const { autoPlay = false, isMuted: initialIsMuted = false } = options;
const [isPlaying, setIsPlaying] = useState5(autoPlay);
const [isMuted, setIsMuted] = useState5(initialIsMuted);
const [audioNode, setAudioNode] = useState5(null);
const onPlay = useFreshCallback(callbacks.onPlay ?? noop2);
const onPause = useFreshCallback(callbacks.onPause ?? noop2);
const onMute = useFreshCallback(callbacks.onMute ?? noop2);
const onUnmute = useFreshCallback(callbacks.onUnmute ?? noop2);
const onLoadedMetadata = useFreshCallback(callbacks.onLoadedMetadata ?? noop2);
const audioCallbackRef = (node) => {
if (node !== null) {
setAudioNode(node);
}
};
useEffect8(() => {
if (!audioNode) return;
if (isPlaying) {
audioNode.play();
} else {
audioNode.pause();
}
}, [audioNode, isPlaying]);
useEffect8(() => {
if (!audioNode) return;
audioNode.muted = isMuted;
}, [audioNode, isMuted]);
useEffect8(() => {
if (!audioNode) return;
const handleLoadedMetadata = () => {
if (autoPlay) {
audioNode?.play();
}
onLoadedMetadata();
};
const handlePlay = () => {
onPlay();
};
const handlePause = () => {
onPause();
};
const handleEnded = () => {
setIsPlaying(false);
};
audioNode?.addEventListener("loadedmetadata", handleLoadedMetadata);
audioNode?.addEventListener("play", handlePlay);
audioNode?.addEventListener("pause", handlePause);
audioNode?.addEventListener("ended", handleEnded);
return () => {
audioNode?.removeEventListener("loadedmetadata", handleLoadedMetadata);
audioNode?.removeEventListener("play", handlePlay);
audioNode?.removeEventListener("pause", handlePause);
audioNode?.removeEventListener("ended", handleEnded);
};
}, [autoPlay, onLoadedMetadata, onPlay, onPause, audioNode]);
const play = () => {
setIsPlaying(true);
};
const pause = () => {
setIsPlaying(false);
};
const togglePlay = () => {
setIsPlaying(!isPlaying);
};
const mute = () => {
setIsMuted(true);
onMute();
};
const unmute = () => {
setIsMuted(false);
onUnmute();
};
const toggleMute = () => {
if (isMuted) {
unmute();
} else {
mute();
}
};
const controls = {
play,
pause,
togglePlay,
mute,
unmute,
toggleMute
};
const state = {
isPlaying,
isMuted
};
return [audioCallbackRef, state, controls];
}
// src/hooks/useBoundingclientrect.ts
import { useState as useState6, useCallback as useCallback8 } from "react";
// src/hooks/useDidMount.ts
import { useEffect as useEffect9 } from "react";
function useDidMount(callback) {
useEffect9(() => {
if (typeof callback === "function") {
callback();
}
}, []);
}
// src/hooks/useMutationObserver.ts
import { useEffect as useEffect10 } from "react";
var config = {
attributes: true,
characterData: true,
childList: true,
subtree: true
};
function useMutationObserver(ref, callback, options = config) {
useEffect10(() => {
if (ref.current) {
const observer = new MutationObserver(callback);
observer.observe(ref.current, options);
return () => {
observer.disconnect();
};
}
return noop;
}, [callback, options, ref]);
}
// src/hooks/useBoundingclientrect.ts
function getBoundingClientRect(element) {
return element.getBoundingClientRect();
}
function useBoundingclientrect(ref) {
const [domRect, setDomRect] = useState6(null);
const update = useCallback8(() => {
setDomRect(ref.current ? getBoundingClientRect(ref.current) : null);
}, [ref]);
useDidMount(() => {
update();
});
useMutationObserver(ref, update);
return domRect;
}
// src/hooks/useBoundingclientrectRef.ts
import { useState as useState8, useEffect as useEffect12, useCallback as useCallback10 } from "react";
// src/hooks/useForkRef.ts
import { useMemo as useMemo2 } from "react";
function setRef(ref, value) {
if (typeof ref === "function") {
ref(value);
} else if (ref !== null && ref !== void 0) {
ref.current = value;
}
}
function useForkRef(refA, refB) {
return useMemo2(() => {
if (refA === null && refB === null) {
return null;
}
return (refValue) => {
setRef(refA, refValue);
setRef(refB, refValue);
};
}, [refA, refB]);
}
// src/hooks/useMutationObserverRef.ts
import { useEffect as useEffect11, useCallback as useCallback9, useState as useState7 } from "react";
var config2 = {
attributes: true,
characterData: true,
childList: true,
subtree: true
};
function useMutationObserverRef(callback, options = config2) {
const [node, setNode] = useState7(null);
useEffect11(() => {
if (node) {
const observer = new MutationObserver(callback);
observer.observe(node, options);
return () => {
observer.disconnect();
};
}
return noop;
}, [node, callback, options]);
const ref = useCallback9((nodeElement) => {
setNode(nodeElement);
}, []);
return [ref];
}
// src/hooks/useBoundingclientrectRef.ts
function getBoundingClientRect2(element) {
return element.getBoundingClientRect();
}
function useBoundingclientrectRef() {
const [domRect, setDomRect] = useState8(null);
const [node, setNode] = useState8(null);
const update = useCallback10(() => {
setDomRect(node ? getBoundingClientRect2(node) : null);
}, [node]);
useEffect12(() => {
update();
}, [update]);
const ref = useCallback10((nodeElement) => {
setNode(nodeElement);
}, []);
const [mutationObserverRef] = useMutationObserverRef(update);
const forkedRef = useForkRef(ref, mutationObserverRef);
return [forkedRef, domRect, update];
}
// src/hooks/useCountdown.ts
import { useState as useState9 } from "react";
// src/hooks/useIntervalWhen.ts
import { useRef as useRef4, useEffect as useEffect13 } from "react";
function useIntervalWhen(callback, intervalDurationMs = 0, when = true, startImmediate = false) {
const savedRefCallback = useRef4(void 0);
useEffect13(() => {
savedRefCallback.current = callback;
});
useEffect13(() => {
if (when) {
let internalCallback2 = function() {
savedRefCallback.current?.();
};
var internalCallback = internalCallback2;
if (startImmediate) {
internalCallback2();
}
const interval = window.setInterval(internalCallback2, intervalDurationMs);
return () => {
window.clearInterval(interval);
};
}
return noop;
}, [when, intervalDurationMs, startImmediate]);
}
// src/hooks/useCountdown.ts
function useCountdown(endTime, options = {}) {
const { interval = 1e3, onDown, onEnd } = options;
const [time, setTime] = useState9(() => /* @__PURE__ */ new Date());
const restTime = endTime.getTime() - time.getTime();
const count = restTime > 0 ? Math.ceil(restTime / interval) : 0;
useIntervalWhen(onTick, count ? interval : void 0, true, true);
return count;
function onTick() {
const newTime = /* @__PURE__ */ new Date();
if (newTime > endTime) {
if (onEnd) {
onEnd(newTime);
}
setTime(endTime);
return;
}
if (onDown) {
onDown(restTime, newTime);
}
setTime(newTime);
}
}
// src/hooks/useCounter.ts
import { useCallback as useCallback11, useState as useState10 } from "react";
function useCounter(initialValue) {
const [counter, setCounter] = useState10(initialValue);
const incrementBy = useCallback11((incrAmount) => {
setCounter((currentCounter) => currentCounter + incrAmount);
}, []);
const decrementBy = useCallback11(
(decrAmount) => {
incrementBy(-decrAmount);
},
[incrementBy]
);
const increment = useCallback11(() => {
incrementBy(1);
}, [incrementBy]);
const decrement = useCallback11(() => {
incrementBy(-1);
}, [incrementBy]);
const reset = useCallback11(() => {
setCounter(initialValue);
}, [initialValue]);
return {
decrement,
decrementBy,
increment,
incrementBy,
reset,
value: counter
};
}
// src/hooks/useDebounce.ts
import debounce from "lodash.debounce";
import { useRef as useRef5, useCallback as useCallback12 } from "react";
// src/hooks/useWillUnmount.ts
import { useEffect as useEffect14 } from "react";
function useWillUnmount(callback) {
useEffect14(() => {
return callback;
}, []);
}
// src/hooks/useDebounce.ts
function useDebounce(callback, wait, options) {
const createDebouncedCallback = useCallback12(
(function_) => {
return debounce(function_, wait, options);
},
[wait, options]
);
const freshCallback = useFreshRef(callback);
function tick(...args) {
freshCallback.current?.(...args);
}
const debouncedCallbackRef = useRef5(
createDebouncedCallback(tick)
);
useWillUnmount(() => {
debouncedCallbackRef.current?.cancel();
});
return debouncedCallbackRef.current;
}
// src/hooks/useDebounceFn.ts
import { useRef as useRef6, useCallback as useCallback13, useState as useState11 } from "react";
function useDebounceFn(func, delay, options = { leading: false, trailing: true }) {
const { leading, trailing, maxWait } = options;
if (!leading && !trailing) {
throw new Error("leading and trailing cannot both be false");
} else if (typeof maxWait !== "undefined" && maxWait < delay) {
throw new Error("maxWait cannot be less than delay");
}
const funcRef = useFreshCallback(func);
const [isTimeoutEnabled, setIsTimeoutEnabled] = useState11(false);
const [key, setKey] = useState11(0);
const lastExecutionTimeRef = useRef6(0);
const argsRef = useRef6(void 0);
const debouncedFn = useCallback13(
(...args) => {
argsRef.current = args;
const overrideTimeout = typeof maxWait !== "undefined" && lastExecutionTimeRef.current && Date.now() - lastExecutionTimeRef.current > maxWait;
if (leading) {
if (isTimeoutEnabled && overrideTimeout) {
setKey((prevKey) => prevKey + 1);
lastExecutionTimeRef.current = Date.now();
try {
funcRef(...args);
} catch (error) {
console.warn(error);
}
} else if (overrideTimeout && !isTimeoutEnabled) {
setIsTimeoutEnabled(true);
lastExecutionTimeRef.current = Date.now();
try {
funcRef(...args);
} catch (error) {
console.warn(error);
}
} else if (isTimeoutEnabled && !overrideTimeout) {
} else {
setIsTimeoutEnabled(true);
lastExecutionTimeRef.current = Date.now();
try {
funcRef(...args);
} catch (error) {
console.warn(error);
}
}
}
if (trailing) {
if (isTimeoutEnabled) {
setKey((prevKey) => prevKey + 1);
} else {
setIsTimeoutEnabled(true);
}
}
},
[maxWait, isTimeoutEnabled, leading, trailing, funcRef]
);
useTimeoutWhen(
() => {
if (typeof maxWait !== "undefined" && trailing) {
if (!argsRef.current) return;
lastExecutionTimeRef.current = Date.now();
try {
funcRef(...argsRef.current);
} catch (error) {
console.warn(error);
}
}
},
maxWait ?? Infinity,
isTimeoutEnabled && typeof maxWait !== "undefined" && trailing,
key
);
useTimeoutWhen(
() => {
if (trailing) {
if (!argsRef.current) return;
lastExecutionTimeRef.current = Date.now();
try {
funcRef(...argsRef.current);
} catch (error) {
console.warn(error);
}
}
setIsTimeoutEnabled(false);
},
delay,
isTimeoutEnabled && trailing,
key
);
const freshDebouncedFn = useFreshCallback(debouncedFn);
return [freshDebouncedFn, isTimeoutEnabled];
}
// src/hooks/useDebouncedValue.ts
import { useState as useState12 } from "react";
// src/hooks/useDidUpdate.ts
import { useEffect as useEffect15, useMemo as useMemo3, useRef as useRef7 } from "react";
function useDidUpdate(callback, conditions) {
const hasMountedRef = useRef7(false);
const internalConditions = useMemo3(() => {
if (typeof conditions !== "undefined" && !Array.isArray(conditions)) {
return [conditions];
} else if (Array.isArray(conditions) && conditions.length === 0) {
console.warn(
"Using [] as the second argument makes useDidUpdate a noop. The second argument should either be `undefined` or an array of length greater than 0."
);
}
return conditions;
}, [conditions]);
useEffect15(() => {
if (hasMountedRef.current) {
callback();
}
}, internalConditions);
useDidMount(() => {
hasMountedRef.current = true;
});
useWillUnmount(() => {
hasMountedRef.current = false;
});
}
// src/hooks/useDebouncedValue.ts
var defaultUseDebounceValueOptions = {
initializeWithNull: false
};
var useDebouncedValue = (value, timeout, options = {}) => {
const { initializeWithNull } = Object.assign(
{},
defaultUseDebounceValueOptions,
options
);
const [updatedValue, setUpdatedValue] = useState12(
initializeWithNull ? null : value
);
const debouncedSetUpdatedValue = useDebounce(setUpdatedValue, timeout);
useDidMount(() => {
if (initializeWithNull) {
debouncedSetUpdatedValue(value);
}
});
useDidUpdate(() => {
debouncedSetUpdatedValue(value);
}, [value]);
return [updatedValue, setUpdatedValue];
};
// src/hooks/useDeepCompareEffect.ts
import { useEffect as useEffect16, useRef as useRef8 } from "react";
import isEqual from "fast-deep-equal";
// src/hooks/warning.ts
var isDevelopmentEnvironment = process.env.NODE_ENV !== "production";
var warning = () => {
};
if (isDevelopmentEnvironment) {
const printWarning = (actualMessage) => {
const message = `Warning: ${actualMessage}`;
if (typeof console !== "undefined") {
console.error(message);
}
try {
throw new Error(message);
} catch {
}
};
warning = function(condition, actualMessage) {
if (!condition) {
printWarning(actualMessage);
}
};
}
// src/hooks/useDeepCompareEffect.ts
function isPrimitive(value) {
const valueType = typeof value;
return valueType === "string" || valueType === "number" || valueType === "bigint" || valueType === "boolean" || valueType === "undefined" || valueType === "symbol";
}
function useDeepCompareEffect(callback, dependencies) {
const previousDeps = useRef8(dependencies);
if (!Array.isArray(dependencies)) {
throw new Error(
"useDeepCompareEffect should be used with an array of dependencies"
);
}
const hasPrimitives = dependencies.every(isPrimitive);
warning(
!hasPrimitives,
"useDeepCompareEffect should not be used with primitive values as dependencies"
);
if (!isEqual(previousDeps.current, dependencies)) {
previousDeps.current = dependencies;
}
useEffect16(callback, previousDeps.current);
}
// src/hooks/useDimensionsRef.ts
import { useState as useState13, useCallback as useCallback14, useLayoutEffect as useLayoutEffect2 } from "react";
// src/hooks/useGlobalObjectEventListener.ts
import { useEffect as useEffect17 } from "react";
// src/hooks/useFreshTick.ts
function useFreshTick(callback) {
const freshRef = useFreshRef(callback);
function tick(...args) {
if (typeof freshRef.current === "function") {
freshRef.current(...args);
}
}
return tick;
}
// src/hooks/useGlobalObjectEventListener.ts
function useGlobalObjectEventListener(globalObject, eventName, callback, listenerOptions = {}, when = true, isLayoutEffect = false) {
const freshCallback = useFreshTick(callback);
const useEffectToRun = isLayoutEffect ? useIsomorphicEffect : useEffect17;
useEffectToRun(() => {
warning(
typeof globalObject !== "undefined",
"[useGlobalObjectEventListener]: Cannot attach event handlers to undefined."
);
if (typeof globalObject !== "undefined" && when) {
globalObject.addEventListener(eventName, freshCallback, listenerOptions);
return () => {
globalObject.removeEventListener(
eventName,
freshCallback,
listenerOptions
);
};
}
return () => {
};
}, [eventName, listenerOptions]);
}
// src/hooks/useOnWindowResize.ts
function useOnWindowResize(callback, when = true, isLayoutEffect = false) {
useGlobalObjectEventListener(
global.window,
"resize",
callback,
{ passive: true },
when,
isLayoutEffect
);
}
// src/hooks/useOnWindowScroll.ts
function useOnWindowScroll(callback, when = true, isLayoutEffect = false) {
useGlobalObjectEventListener(
global.window,
"scroll",
callback,
{ passive: true },
when,
isLayoutEffect
);
}
// src/hooks/useDimensionsRef.ts
var getDimensionObject = (node) => {
const rect = node.getBoundingClientRect();
return {
bottom: rect.bottom,
height: rect.height,
left: rect.left,
right: rect.right,
top: rect.top,
width: rect.width,
x: rect.left,
y: rect.top
};
};
var noWindowReturnValue = [void 0, null, null];
var useDimensionsRef = ({
updateOnScroll = true,
updateOnResize = true
} = {}) => {
const [dimensions, setDimensions] = useState13(null);
const [node, setNode] = useState13(null);
const ref = useCallback14((nodeFromCallback) => {
setNode(nodeFromCallback);
}, []);
const measure = useCallback14(() => {
window.requestAnimationFrame(() => {
if (node) {
setDimensions(getDimensionObject(node));
}
});
}, [node]);
useLayoutEffect2(() => {
measure();
}, [measure]);
useOnWindowResize(
() => {
measure();
},
updateOnResize,
true
);
useOnWindowScroll(
() => {
measure();
},
updateOnScroll,
true
);
if (typeof window === "undefined") {
console.warn("useDimensionsRef: window is undefined.");
return noWindowReturnValue;
}
return [ref, dimensions, node];
};
// src/hooks/useDocumentEventListener.ts
function useDocumentEventListener(eventName, callback, listenerOptions = {}, isLayoutEffect = false) {
useGlobalObjectEventListener(
global.document,
eventName,
callback,
listenerOptions,
true,
isLayoutEffect
);
}
// src/hooks/useDocumentTitle.ts
import { useEffect as useEffect18, useRef as useRef9 } from "react";
function useDocumentTitle(title, options = {}) {
const isBrowser = typeof window !== "undefined";
const prevTitleRef = useRef9(isBrowser ? document.title : "");
const { resetOnUnmount = false } = options;
useEffect18(() => {
if (isBrowser) {
document.title = title;
const lastTitle = prevTitleRef.current;
if (resetOnUnmount) {
return () => {
document.title = lastTitle;
};
}
}
return noop;
}, [title, isBrowser, resetOnUnmount]);
}
// src/hooks/useDocumentVisibilityState.ts
import { useCallback as useCallback15, useState as useState14 } from "react";
function useDocumentVisibilityState() {
const [visibilityState, setVisibilityState] = useState14(
document ? document.visibilityState : null
);
const handleVisibilityChange = useCallback15(() => {
setVisibilityState(document ? document.visibilityState : null);
}, []);
useGlobalObjectEventListener(
global.document,
"visibilitychange",
handleVisibilityChange,
{},
true,
true
);
return visibilityState;
}
// src/hooks/useEffectOnceWhen.ts
import { useEffect as useEffect19, useRef as useRef10 } from "react";
function useEffectOnceWhen(callback, when = true) {
const hasRunOnceRef = useRef10(false);
const callbackRef = useRef10(callback);
useEffect19(() => {
callbackRef.current = callback;
});
useEffect19(() => {
if (when && !hasRunOnceRef.current) {
callbackRef.current();
hasRunOnceRef.current = true;
}
}, [when]);
}
// src/hooks/useEventListenerRef.ts
import { useEffect as useEffect20 } from "react";
// src/hooks/useRefElement.ts
import { useCallback as useCallback16, useState as useState15 } from "react";
function useRefElement() {
const [refElement, setRefElement] = useState15(null);
const ref = useCallback16(
(element) => {
setRefElement(element);
},
[]
);
return [ref, refElement];
}
// src/hooks/useEventListenerRef.ts
function useEventListenerRef(eventName, callback, listenerOptions = {}, isLayoutEffect = false) {
const [ref, element] = useRefElement();
const freshCallback = useFreshTick(callback);
const useEffectToRun = isLayoutEffect ? useIsomorphicEffect : useEffect20;
useEffectToRun(() => {
if (!element?.addEventListener) {
return noop;
}
element.addEventListener(eventName, freshCallback, listenerOptions);
return () => {
element.removeEventListener(eventName, freshCallback, listenerOptions);
};
}, [element, eventName, listenerOptions]);
return ref;
}
// src/hooks/useFileDropRef.ts
import { useCallback as useCallback17, useState as useState16, useEffect as useEffect21 } from "react";
function useFileDropRef(options = {}, callbacks = {}) {
const { accept, maxFileSize, maxFiles } = options;
const {
onDrop = noop,
onFileAccepted = noop,
onFileRejected = noop,
onDragEnter = noop,
onDragLeave = noop
} = callbacks;
const [targetNode, setTargetNode] = useState16(null);
const freshOnDrop = useFreshCallback(onDrop);
const freshOnFileAccepted = useFreshCallback(onFileAccepted);
const freshOnFileRejected = useFreshCallback(onFileRejected);
const freshOnDragEnter = useFreshCallback(onDragEnter);
const freshOnDragLeave = useFreshCallback(onDragLeave);
useCallback17((node) => {
setTargetNode(node);
}, []);
const fileIsValid = useCallback17(
(file) => {
if (accept && !accept.includes(file.type)) {
return { valid: false, reason: "File type not allowed" };
}
if (maxFileSize && file.size > maxFileSize) {
return { valid: false, reason: "File size exceeds the limit" };
}
return { valid: true };
},
[accept, maxFileSize]
);
const handleDrop = useCallback17(
(event) => {
event.preventDefault();
const files = Array.from(event.dataTransfer?.files || []);
const acceptedFiles = [];
const rejectedFiles = [];
if (maxFiles && files.length > maxFiles) {
for (const file of files) {
freshOnFileRejected(file, "Exceeded maximum number of files");
}
} else {
files.forEach((file) => {
const validationResult = fileIsValid(file);
if (validationResult.valid) {
acceptedFiles.push(file);
freshOnFileAccepted(file);
} else {
rejectedFiles.push(file);
freshOnFileRejected(
file,
validationResult.reason || "Unknown reason"
);
}
});
}
freshOnDrop(acceptedFiles, rejectedFiles);
},
[
fileIsValid,
freshOnFileAccepted,
freshOnFileRejected,
maxFiles,
freshOnDrop
]
);
const handleDragOver = useCallback17((event) => {
event.preventDefault();
}, []);
useEffect21(() => {
if (targetNode) {
targetNode.addEventListener("drop", handleDrop);
targetNode.addEventListener("dragover", handleDragOver);
targetNode.addEventListener("dragenter", freshOnDragEnter);
targetNode.addEventListener("dragleave", freshOnDragLeave);
return () => {
targetNode.removeEventListener("drop", handleDrop);
targetNode.removeEventListener("dragover", handleDragOver);
targetNode.removeEventListener("dragenter", freshOnDragEnter);
targetNode.removeEventListener("dragleave", freshOnDragLeave);
};
} else {
return () => {
};
}
}, [
targetNode,
handleDrop,
handleDragOver,
freshOnDragEnter,
freshOnDragLeave
]);
return useCallback17((node) => {
setTargetNode(node);
}, []);
}
// src/hooks/useFullscreen.ts
import { useState as useState17, useCallback as useCallback18, useEffect as useEffect22 } from "react";
var FullscreenApi = {
DOM_UNAVAILABLE_ERROR: "DOM is unavailable server-side. Please use this method client-side only.",
FULLSCREEN_UNSUPPORTED_ERROR: "Your browser does not support Fullscreen API.",
getEventsNames() {
if (typeof document === "undefined") return null;
const _document = document;
if ("exitFullscreen" in _document)
return ["fullscreenchange", "fullscreenerror"];
if ("webkitExitFullscreen" in _document)
return ["webkitfullscreenchange", "webkitfullscreenerror"];
if ("webkitCancelFullScreen" in _document)
return ["webkitfullscreenchange", "webkitfullscreenerror"];
if ("mozCancelFullScreen" in _document)
return ["mozfullscreenchange", "mozfullscreenerror"];
if ("msExitFullscreen" in _document)
return ["MSFullscreenChange", "MSFullscreenError"];
return null;
},
getEventName(eventType) {
const eventsNames = this.getEventsNames();
if (!eventsNames) return null;
if (eventType === "change") return eventsNames[0];
return eventsNames[1];
},
get fullscreenEnabled() {
if (typeof document === "undefined") return false;
const _document = document;
return _document.fullscreenEnabled || _document.webkitFullscreenEnabled || !!_document.webkitCancelFullScreen || _document.mozFullScreenEnabled || _document.msFullscreenEnabled || false;
},
get fullscreenElement() {
if (typeof document === "undefined") return null;
const _document = document;
return _document.fullscreenElement || _document.webkitFullscreenElement || _document.webkitCurrentFullScreenElement || _document.mozFullScreenElement || _document.msFullscreenElement || null;
},
requestFullscreen(element, options) {
if (typeof document === "undefined")
throw new Error(this.DOM_UNAVAILABLE_ERROR);
const target = element ?? document.documentElement;
const method = target.requestFullscreen || target.webkitRequestFullscreen || target.webkitRequestFullScreen || target.mozRequestFullScreen || target.msRequestFullscreen;
if (!method) throw new Error(this.FULLSCREEN_UNSUPPORTED_ERROR);
return method.call(target, options);
},
exitFullscreen() {
if (typeof document === "undefined")
throw new Error(this.DOM_UNAVAILABLE_ERROR);
const _document = document;
const method = _document.exitFullscreen || _document.webkitExitFullscreen || _document.webkitCancelFullScreen || _document.mozCancelFullScreen || _document.msExitFullscreen;
if (!method) throw new Error(this.FULLSCREEN_UNSUPPORTED_ERROR);
return method.call(_document);
},
on(eventType, callback) {
const eventName = this.getEventName(eventType);
if (!eventName) return;
document.addEventListener(eventName, callback);
},
off(eventType, callback) {
const eventName = this.getEventName(eventType);
if (!eventName) return;
document.removeEventListener(eventName, callback);
}
};
function useFullscreen(props = {}) {
const { target, onChange, onError, requestFullScreenOptions } = props;
const [isFullscreenAvailable, setIsFullscreenAvailable] = useState17(false);
const [fullscreenElement, setFullscreenElement] = useState17(
null
);
const [isFullscreenEnabled, setIsFullscreenEnabled] = useState17(false);
const enableFullscreen = useCallback18(() => {
return FullscreenApi.requestFullscreen(
target?.current || null,
requestFullScreenOptions
);
}, [target, requestFullScreenOptions]);
const disableFullscreen = useCallback18(() => {
return FullscreenApi.exitFullscreen();
}, []);
const toggleFullscreen = useCallback18(() => {
if (!!FullscreenApi.fullscreenElement) return disableFullscreen();
return enableFullscreen();
}, [enableFullscreen, disableFullscreen]);
const onChangeHandler = useCallback18(
(event) => {
const fullscreenElement2 = FullscreenApi.fullscreenElement;
setFullscreenElement(fullscreenElement2);
setIsFullscreenEnabled(!!fullscreenElement2);
onChange?.(event);
},
[onChange]
);
const onErrorHandler = useCallback18(
(event) => {
const fullscreenElement2 = FullscreenApi.fullscreenElement;
setFullscreenElement(fullscreenElement2);
setIsFullscreenEnabled(!!fullscreenElement2);
onError?.(event);
},
[onError]
);
useEffect22(() => {
setIsFullscreenAvailable(FullscreenApi.fullscreenEnabled);
}, []);
useEffect22(() => {
FullscreenApi.on("change", onChangeHandler);
FullscreenApi.on("error", onErrorHandler);
return () => {
FullscreenApi.off("change", onChangeHandler);
FullscreenApi.off("error", onErrorHandler);
};
}, [onChangeHandler, onErrorHandler]);
return {
isFullscreenAvailable,
fullscreenElement,
isFullscreenEnabled,
enableFullscreen,
disableFullscreen,
toggleFullscreen
};
}
// src/hooks/useFocus.ts
import { useCallback as useCallback19 } from "react";
var useFocus = (props) => {
const {
onBlur: propsOnBlur,
onFocus: propsOnFocus,
onFocusChange: propsOnFocusChange
} = props;
const onBlur = useCallback19(
(e) => {
if (e.target === e.currentTarget) {
if (propsOnBlur) propsOnBlur(e);
if (propsOnFocusChange) propsOnFocusChange(false);
}
},
[propsOnBlur, propsOnFocusChange]
);
const onFocus = useCallback19(
(e) => {
if (e.target === e.currentTarget) {
if (propsOnFocus) propsOnFocus(e);
if (propsOnFocusChange) propsOnFocusChange(true);
}
},
[propsOnFocusChange, propsOnFocus]
);
return {
focusProps: {
onFocus,
onBlur
}
};
};
// src/hooks/useFocusWithin.ts
import { useCallback as useCallback20, useRef as useRef11 } from "react";
var useFocusWithin = (props) => {
const { onBlurWithin, onFocusWithin, onFocusWithinChange } = props;
const state = useRef11({
isFocusWithin: false
});
const onBlur = useCallback20(
(e) => {
if (state.current.isFocusWithin && !e.currentTarget.contains(e.relatedTarget)) {
state.current.isFocusWithin = false;
if (onBlurWithin) onBlurWithin(e);
if (onFocusWithinChange) onFocusWithinChange(false);
}
},
[onBlurWithin, onFocusWithinChange]
);
const onFocus = useCallback20(
(e) => {
if (!state.current.isFocusWithin) {
if (onFocusWithin) onFocusWithin(e);
if (onFocusWithinChange) onFocusWithinChange(true);
state.current.isFocusWithin = true;
}
},
[onFocusWithin, onFocusWithinChange]
);
return {
focusWithinProps: {
onFocus,
onBlur
}
};
};
// src/hooks/useGeolocation.ts
import { useEffect as useEffect23, useState as useState18 } from "react";
function getGeoLocation(options) {
return new Promise((resolve, reject) => {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
(position) => {
const { coords } = position;
const { latitude, longitude } = coords;
resolve({
isError: false,
lat: latitude,
lng: longitude,
message: ""
});
},
(error) => {
resolve({
isError: true,
message: error && typeof error === "object" && "message" in error ? error.message : "Geolocation error"
});
},
options
);
} else {
resolve({
isError: true,
message: "Geolocation is not supported for this Browser/OS."
});
}
});
}
var defaultGeoLocationOptions = {
enableHighAccuracy: false,
maximumAge: 0,
timeout: Number.POSITIVE_INFINITY,
when: true
};
var useGeolocation = (geoLocationOptions = defaultGeoLocationOptions) => {
const [geoObject, setGeoObject] = useState18(
null
);
const { when, enableHighAccuracy, timeout, maximumAge } = geoLocationOptions;
const getIsMounted = useGetIsMounted();
useEffect23(() => {
async function getGeoCode() {
try {
const value = await getGeoLocation({
enableHighAccuracy,
maximumAge,
timeout,
when
});
if (getIsMounted()) {
setGeoObject(value);
}
} catch (error) {
if (getIsMounted()) {
setGeoObject({
isError: true,
message: error instanceof Error ? error.message : String(error)
});
}
}
}
if (when) {
void getGeoCode();
}
}, [when, enableHighAccuracy, timeout, maximumAge, getIsMounted]);
return geoObject;
};
// src/hooks/useInput.ts
import { useState as useState19, useEffect as useEffect24, useCallback as useCallback21 } from "react";
var defaultOptions = {};
function useInput(initialValue = "", options = defaultOptions) {
const [value, setValue] = useState19(initialValue);
const onChange = useCallback21(
(event) => {
const newValue = event.target.value;
let shouldUpdate = true;
if (typeof options.validate === "function") {
shouldUpdate = options.validate(newValue, value);
}
if (shouldUpdate) {
setValue(newValue);
}
},
[options, value]
);
useEffect24(() => {
setValue(initialValue);
}, [initialValue]);
const handler = {
onChange,
value
};
return handler;
}
// src/hooks/useIntersectionObserverRef.ts
import { useEffect as useEffect25, useCallback as useCallback22, useState as useState20, useRef as useRef12 } from "react";
var config3 = {
root: null,
rootMargin: "0px 0px 0px 0px",
threshold: [0, 1]
};
function useIntersectionObserverRef(callback, options = config3) {
const { root = null, rootMargin, threshold } = options;
const [node, setNode] = useState20(null);
const callbackRef = useRef12(callback);
useEffect25(() => {
callbackRef.current = callback;
});
const handleIntersectionObserver = useCallback22(
(...args) => {
return callbackRef.current?.(...args);
},
[]
);
useEffect25(() => {
if (node) {
const observer = new IntersectionObserver(handleIntersectionObserver, {
root,
rootMargin,
threshold
});
observer.observe(node);
return () => {
observer.disconnect();
};
}
return noop;
}, [node, handleIntersectionObserver, root, rootMargin, threshold]);
const ref = useCallback22((nodeElement) => {
setNode(nodeElement);
}, []);
return [ref];
}
// src/hooks/useInViewRef.ts
import { useEffect as useEffect26, useCallback as useCallback23, useState as useState21 } from "react";
var config4 = {
root: null,
rootMargin: "0px 0px 0px 0px",
threshold: [0, 1]
};
function useInViewRef(callbackOrOptions, options) {
const callback = typeof callbackOrOptions === "function" ? callbackOrOptions : noop;
const opts = typeof callbackOrOptions === "object" ? callbackOrOptions : options || config4;
const { root = null, rootMargin, threshold } = opts;
const [node, setNode] = useState21(null);
const [inView, setInView] = useState21(false);
useEffect26(() => {
if (node) {
const observer = new IntersectionObserver((entries, observerRef) => {
for (const { isIntersecting } of entries) setInView(isIntersecting);
callback(entries, observerRef);
}, opts);
observer.observe(node);
return () => {
observer.disconnect();
};
}
return noop;
}, [node, callback, root, rootMargin, threshold, opts]);
const ref = useCallback23((nodeElement) => {
setNode(nodeElement);
}, []);
return [ref, inView];
}
// src/hooks/useIsDroppingFiles.ts
import { useState as useState22, useEffect as useEffect27, useCallback as useCallback24 } from "react";
function useIsDroppingFiles(isTargetWindow = false) {
const [targetNode, setTargetNode] = useState22(null);
const ref = useCallback24((node) => {
setTargetNode(node);
}, []);
const [isDroppingFiles, setIsDroppingFiles] = useState22(false);
const handleDragEnter = useCallback24(
(e) => {
e.preventDefault();
if (e.dataTransfer?.types.includes("Files")) {
if (!isDroppingFiles) {
setIsDroppingFiles(true);
}
} else {
setIsDroppingFiles(false);
}
},
[isDroppingFiles]
);
const handleDragOver = useCallback24(
(e) => {
e.preventDefault();
if (e.dataTransfer?.types.includes("Files")) {
if (!isDroppingFiles) {
setIsDroppingFiles(true);
}
} else {
setIsDroppingFiles(false);
}
},
[isDroppingFiles]
);
const handleDragLeave = useCallback24((e) => {
e.preventDefault();
setIsDroppingFiles(false);
}, []);
const handleDrop = useCallback24((e) => {
e.preventDefault();
setIsDroppingFiles(false);
}, []);
const freshHandleDragEnter = useFreshCallback(handleDragEnter);
const freshHandleDragOver = useFreshCallback(handleDragOver);
const freshHandleDragLeave = useFreshCallback(handleDragLeave);
const freshHandleDrop = useFreshCallback(handleDrop);
useEffect27(() => {
const target = isTargetWindow ? window : targetNode;
if (target) {
target.addEventListener("dragenter", freshHandleDragEnter);
target.addEventListener("dragover", freshHandleDragOver);
target.addEventListener("dragleave", freshHandleDragLeave);
target.addEventListener("drop", freshHandleDrop);
}
return () => {
if (target) {
target.removeEventListener("dragenter", freshHandleDragEnter);
target.removeEventListener("dragover", freshHandleDragOver);
target.removeEventListener("dragleave", freshHandleDragLeave);
target.removeEventListener("drop", freshHandleDrop);
}
};
}, [
freshHandleDragEnter,
freshHandleDragLeave,
freshHandleDragOver,
freshHandleDrop,
isDroppingFiles,
isTargetWindow,
targetNode
]);
if (isTargetWindow) {
return isDroppingFiles;
}
return [ref, isDroppingFiles];
}
// src/hooks/useKey.ts
import { useEffect as useEffect28, useCallback as useCallback25, useRef as useRef13, useMemo as useMemo4 } from "react";
// src/utils/doesIdentifierMatchKeyboardEvent.ts
var doesIdentifierMatchKeyboardEvent = (error, identifier) => {
if (error.key === identifier || error.code === identifier || error.keyCode === identifier || error.which === identifier || error.charCode === identifier) {
return true;
}
return false;
};
// src/hooks/useKey.ts
var defaultOptions2 = {
eventTypes: ["keydown"],
when: true
};
function useKey(keys, callback, options) {
const keyList = useMemo4(() => {
if (Array.isArray(keys)) {
return keys;
} else {
return [keys];
}
}, [keys]);
const internalOptions = useMemo4(() => {
return { ...defaultOptions2, ...options };
}, [options]);
const { when, eventTypes } = internalOptions;
const callbackRef = useRef13(callback);
const { target } = internalOptions;
useEffect28(() => {
callbackRef.current = callback;
});
const handle = useCallback25(
(event) => {
if (keyList.some(
(identifier) => doesIdentifierMatchKeyboardEvent(event, identifier)
)) {
callbackRef.current(event);
}
},
[keyList]
);
useEffect28(() => {
if (when && typeof window !== "undefined") {
if (target) {
const targetNode = target.current;
if (targ