UNPKG

iuseful-react-hooks

Version:
873 lines (834 loc) 27.3 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/hooks/index.ts var index_exports = {}; __export(index_exports, { useArray: () => useArray, useAsync: () => useAsync, useBoolean: () => useBoolean, useBroadcastChannelListener: () => useBroadcastChannelListener, useBroadcastChannelSender: () => useBroadcastChannelSender, useClickOutSide: () => useClickOutSide, useCookie: () => useCookie, useCookiesChangeListener: () => useCookiesChangeListener, useCopyToClipboard: () => useCopyToClipboard, useDarkMode: () => useDarkMode, useDebouncedEffect: () => useDebouncedEffect, useDebouncedValue: () => useDebouncedValue, useEffectAfterMount: () => useEffectAfterMount, useEventListener: () => useEventListener, useGeolocation: () => useGeolocation, useGetAllCookies: () => useGetAllCookies, useHover: () => useHover, useIsFirstRender: () => useIsFirstRender, useIsOnline: () => useIsOnline, useLocalStorage: () => useLocalStorage, useLongPress: () => useLongPress, useMediaQuery: () => useMediaQuery, useNearScreen: () => useNearScreen, usePrevious: () => usePrevious, useScript: () => useScript, useSessionStorage: () => useSessionStorage, useSize: () => useSize, useStateWithHistory: () => useStateWithHistory, useStateWithValidation: () => useStateWithValidation, useTimeout: () => useTimeout, useWindowSize: () => useWindowSize }); module.exports = __toCommonJS(index_exports); // src/hooks/use-boolean.ts var import_react = require("react"); function useBoolean(initialValue = false) { const [value, setValue] = (0, import_react.useState)(initialValue); const toggle = (0, import_react.useCallback)(() => { setValue((prev) => !prev); }, []); const set = (0, import_react.useCallback)((param) => { setValue(param); }, []); return { set, toggle, value }; } // src/hooks/use-broadcast-channel-sender.ts var import_react3 = require("react"); // src/hooks/use-singleton-broadcast-channel.ts var import_react2 = require("react"); // src/utils/helpers/is-client.ts var isClient = !!(typeof window !== "undefined" && window.document && window.document.createElement); // src/utils/helpers/broadcast-channels.ts var cache = /* @__PURE__ */ new Map(); function acquireChannel(name) { if (!isClient || typeof BroadcastChannel === "undefined") { return null; } const current = cache.get(name); if (current) { current.count += 1; return current.channel; } const channel = new BroadcastChannel(name); cache.set(name, { channel, count: 1 }); return channel; } function releaseChannel(name, channel) { if (!channel || !isClient) return; const entry = cache.get(name); if (!entry) return; if (entry.channel === channel) { entry.count -= 1; if (entry.count <= 0) { entry.channel.close(); cache.delete(name); } } } // src/hooks/use-singleton-broadcast-channel.ts function useSingletonBroadcastChannel({ name }) { const nameRef = (0, import_react2.useRef)(name); (0, import_react2.useEffect)(() => { nameRef.current = name; }, [name]); const channel = (0, import_react2.useMemo)(() => acquireChannel(name), [name]); (0, import_react2.useEffect)(() => { return () => { releaseChannel(nameRef.current, channel); }; }, [channel]); return { channel }; } // src/hooks/use-broadcast-channel-sender.ts function useBroadcastChannelSender({ channelName }) { const { channel } = useSingletonBroadcastChannel({ name: channelName }); const send = (0, import_react3.useCallback)( (message) => { if (channel) { channel.postMessage(message); } }, [channel] ); return { send }; } // src/hooks/use-broadcast-channel-listeners.ts var import_react4 = require("react"); function useBroadcastChannelListener({ channelName, onMessage, onError = null }) { const { channel } = useSingletonBroadcastChannel({ name: channelName }); const memoizedOnMessage = (0, import_react4.useCallback)( (ev) => { onMessage(ev); }, [onMessage] ); const memoizedOnError = (0, import_react4.useCallback)( (ev) => { onError == null ? void 0 : onError(ev); }, [onError] ); (0, import_react4.useEffect)(() => { if (!channel) return; channel.addEventListener("message", memoizedOnMessage); if (memoizedOnError) { channel.addEventListener("messageerror", memoizedOnError); } return () => { channel.removeEventListener("message", memoizedOnMessage); if (memoizedOnError) { channel.removeEventListener("messageerror", memoizedOnError); } }; }, [channel]); } // src/hooks/use-is-first-render.ts var import_react5 = require("react"); function useIsFirstRender() { const isFirstRender = (0, import_react5.useRef)(true); (0, import_react5.useEffect)(() => { isFirstRender.current = false; }, []); return isFirstRender.current; } // src/hooks/use-effect-after-mount.ts var import_react6 = require("react"); function useEffectAfterMount(effect, deps) { const isFirstRender = useIsFirstRender(); (0, import_react6.useEffect)(() => { if (!isFirstRender) return effect(); }, deps); } // src/hooks/use-near-screen.ts var import_react7 = require("react"); function useNearScreen({ externalRef, once = true, rootMargin = "100px" } = {}) { const [isNearScreen, setIsNearScreen] = (0, import_react7.useState)(false); const fromRef = (0, import_react7.useRef)(null); (0, import_react7.useEffect)(() => { if (!isClient || typeof IntersectionObserver === "undefined") return; const element = externalRef ? externalRef.current : fromRef.current; const onChange = ([firstEntry], observer2) => { if (!firstEntry) return; const { isIntersecting } = firstEntry; if (!once || isIntersecting) setIsNearScreen(isIntersecting); if (isIntersecting && once) observer2.disconnect(); }; const observer = new IntersectionObserver(onChange, { rootMargin }); if (element) { observer.observe(element); } return () => { if (observer) { observer.disconnect(); } }; }, [externalRef, once, rootMargin]); return { isNearScreen, fromRef }; } // src/utils/helpers/functions.ts function isPromise(value) { return !!value && (typeof value === "object" || typeof value === "function") && typeof value.then === "function"; } function noop() { } // src/hooks/use-timeout.ts var import_react8 = require("react"); function useTimeout(onTimeout, delayInMs) { const [isCleared, setIsCleared] = (0, import_react8.useState)(false); const timeoutRef = (0, import_react8.useRef)(null); const cbRef = (0, import_react8.useRef)(onTimeout); (0, import_react8.useEffect)(() => { cbRef.current = onTimeout; }, [onTimeout]); const clear = (0, import_react8.useCallback)(() => { timeoutRef.current && clearTimeout(timeoutRef.current); timeoutRef.current = null; setIsCleared(true); }, []); const schedule = (0, import_react8.useCallback)(() => { if (timeoutRef.current != null) { clearTimeout(timeoutRef.current); timeoutRef.current = null; } timeoutRef.current = setTimeout(() => { const result = cbRef.current(); if (isPromise(result)) result.catch(noop); }, delayInMs); setIsCleared(false); }, [delayInMs]); const reset = (0, import_react8.useCallback)(() => { clear(); schedule(); }, [clear, schedule]); (0, import_react8.useEffect)(() => { schedule(); return clear; }, [schedule, clear]); return { isCleared, clear, reset }; } // src/hooks/use-debounced-value.ts var import_react9 = require("react"); function useDebouncedValue(value, { delayInMs = 300 } = {}) { const [debouncedValue, setDebouncedValue] = (0, import_react9.useState)(value); (0, import_react9.useEffect)(() => { const handler = setTimeout(() => { setDebouncedValue(value); }, delayInMs); return () => { clearTimeout(handler); }; }, [value, delayInMs]); return debouncedValue; } // src/hooks/use-debounced-effect.ts var import_react10 = require("react"); function useDebouncedEffect(callback, delayInMs = 150, deps = [], { immediate = true } = {}) { const isFirstRender = useIsFirstRender(); const { reset, clear } = useTimeout(callback, delayInMs); (0, import_react10.useEffect)(() => { if (isFirstRender && immediate) { callback(); } reset(); }, [...deps, reset]); (0, import_react10.useEffect)(clear, []); } // src/hooks/use-array.ts var import_react11 = require("react"); function useArray(initialValue) { const [array, setArray] = (0, import_react11.useState)(initialValue); const set = (0, import_react11.useCallback)((newArray) => { setArray(newArray); }, []); const push = (0, import_react11.useCallback)((item) => { setArray((prevArray) => [...prevArray, item]); }, []); const pop = (0, import_react11.useCallback)(() => { setArray((prevArray) => prevArray.slice(0, -1)); }, []); const remove = (0, import_react11.useCallback)((index) => { setArray((prevArray) => prevArray.filter((_, i) => i !== index)); }, []); const clear = (0, import_react11.useCallback)(() => { setArray([]); }, []); const update = (0, import_react11.useCallback)((index, value) => { setArray((prevArray) => prevArray.with(index, value)); }, []); const filter = (0, import_react11.useCallback)((...args) => { setArray((prevArray) => prevArray.filter(...args)); }, []); const concat = (0, import_react11.useCallback)((array2) => { setArray((prevArray) => [...prevArray, ...array2]); }, []); const merge = (0, import_react11.useCallback)((...arrays) => { setArray((prevArray) => [...prevArray, ...arrays.flat()]); }, []); const shift = (0, import_react11.useCallback)(() => { setArray((prevArray) => prevArray.slice(1)); }, []); const sort = (0, import_react11.useCallback)((compareFn) => { setArray((prevArray) => prevArray.toSorted(compareFn)); }, []); return { array, set, push, pop, remove, clear, update, filter, concat, merge, shift, sort }; } // src/hooks/use-previous.ts var import_react12 = require("react"); function usePrevious(value) { const prevRef = (0, import_react12.useRef)(null); const currentRef = (0, import_react12.useRef)(value); if (currentRef.current !== value) { prevRef.current = currentRef.current; currentRef.current = value; } return prevRef.current; } // src/hooks/use-state-with-history.ts var import_react13 = require("react"); function useStateWithHistory(initialState, { historyLimit = 10 } = {}) { const [value, setValue] = (0, import_react13.useState)(initialState); const historyRef = (0, import_react13.useRef)([value]); const pointerRef = (0, import_react13.useRef)(0); const set = (0, import_react13.useCallback)( (newValue) => { const isUpdateFunction = typeof newValue === "function" && newValue.length === 1; const resolvedValue = isUpdateFunction ? newValue(value) : newValue; if (historyRef.current[pointerRef.current] === resolvedValue) return; if (pointerRef.current < historyRef.current.length - 1) { historyRef.current.splice(pointerRef.current + 1); } historyRef.current.push(resolvedValue); while (historyRef.current.length > historyLimit) { historyRef.current.shift(); } pointerRef.current = historyRef.current.length - 1; setValue(resolvedValue); }, [value, historyLimit] ); const go = (0, import_react13.useCallback)((pointer) => { if (pointer < 0 || pointer >= historyRef.current.length) return; pointerRef.current = pointer; setValue(historyRef.current[pointer]); }, []); const back = (0, import_react13.useCallback)(() => { if (pointerRef.current <= 0) return; pointerRef.current--; setValue(historyRef.current[pointerRef.current]); }, []); const forward = (0, import_react13.useCallback)(() => { if (pointerRef.current >= historyRef.current.length - 1) return; pointerRef.current++; setValue(historyRef.current[pointerRef.current]); }, []); return [ value, set, { history: historyRef.current, pointer: pointerRef.current, go, back, forward } ]; } // src/hooks/use-storage.ts var import_react14 = require("react"); function useLocalStorage(key, defaultValue) { return useStorage(key, defaultValue, isClient ? window.localStorage : null); } function useSessionStorage(key, defaultValue) { return useStorage(key, defaultValue, isClient ? window.sessionStorage : null); } function useStorage(key, defaultValue, storageObject) { const [value, setValue] = (0, import_react14.useState)(() => { if (!storageObject) { return typeof defaultValue === "function" ? defaultValue() : defaultValue; } const jsonValue = storageObject.getItem(key); if (jsonValue != null) return JSON.parse(jsonValue); return typeof defaultValue === "function" ? defaultValue() : defaultValue; }); (0, import_react14.useEffect)(() => { if (!storageObject) return; if (value === void 0) return storageObject.removeItem(key); storageObject.setItem(key, JSON.stringify(value)); }, [key, value, storageObject]); const remove = (0, import_react14.useCallback)(() => { setValue(void 0); }, []); return [value, setValue, remove]; } // src/hooks/use-async.ts var import_react15 = require("react"); function useAsync(cb, dependencies = []) { const [value, setValue] = (0, import_react15.useState)(null); const [error, setError] = (0, import_react15.useState)(null); const [loading, setLoading] = (0, import_react15.useState)(true); const refresh = (0, import_react15.useCallback)(() => { setLoading(true); setValue(null); setError(null); cb().then(setValue).catch(setError).finally(() => setLoading(false)); }, dependencies); (0, import_react15.useEffect)(refresh, [refresh]); return { value, error, loading, refresh, setValue }; } // src/hooks/use-script.ts function useScript(url) { return useAsync(() => { if (!isClient) { return Promise.resolve(); } const existing = document.querySelector(`script[src="${url}"]`); if (existing) return Promise.resolve(); const script = document.createElement("script"); script.src = url; script.async = true; document.body.appendChild(script); return new Promise((resolve, reject) => { script.onload = resolve; script.onerror = reject; }); }, [url]); } // src/hooks/use-event-listener.ts var import_react16 = require("react"); function useEventListener(type, listener, target, options) { const listenerRef = (0, import_react16.useRef)(listener); (0, import_react16.useEffect)(() => { listenerRef.current = listener; }, [listener]); (0, import_react16.useEffect)(() => { const resolvedTarget = target != null ? target : isClient ? window : null; if (!resolvedTarget) return; const handler = (event) => { const currentListener = listenerRef.current; if (typeof currentListener === "function") { currentListener.call(resolvedTarget, event); } else if (currentListener && typeof currentListener === "object" && "handleEvent" in currentListener) { currentListener.handleEvent(event); } }; resolvedTarget == null ? void 0 : resolvedTarget.addEventListener(type, handler, options); return () => { resolvedTarget == null ? void 0 : resolvedTarget.removeEventListener(type, handler, options); }; }, [type, target, options]); } // src/hooks/use-window-size.ts var import_react17 = require("react"); var INITIAL_STATE = { width: isClient ? window.innerWidth : 0, height: isClient ? window.innerHeight : 0 }; function useWindowSize() { const [windowSize, setWindowSize] = (0, import_react17.useState)(INITIAL_STATE); useEventListener("resize", () => { if (isClient) { setWindowSize({ width: window.innerWidth, height: window.innerHeight }); } }); return windowSize; } // src/hooks/use-media-query.ts var import_react18 = require("react"); function useMediaQuery(query) { const [isMatch, setIsMatch] = (0, import_react18.useState)(false); const [mediaQueryList, setMediaQueryList] = (0, import_react18.useState)(null); (0, import_react18.useEffect)(() => { if (isClient) { const mql = window.matchMedia(query); setMediaQueryList(mql); setIsMatch(mql.matches); } }, [query]); useEventListener("change", (e) => setIsMatch(e.matches), mediaQueryList); return isMatch; } // src/hooks/use-geolocation.ts var import_react19 = require("react"); function useGeolocation(options) { const [loading, setLoading] = (0, import_react19.useState)(true); const [error, setError] = (0, import_react19.useState)(null); const [data, setData] = (0, import_react19.useState)(null); (0, import_react19.useEffect)(() => { if (!isClient) { setLoading(false); return; } const successHandler = (position) => { setLoading(false); setError(null); setData(position.coords); }; const errorHandler = (error2) => { setError(error2); setLoading(false); }; navigator.geolocation.getCurrentPosition(successHandler, errorHandler, options); const watchId = navigator.geolocation.watchPosition(successHandler, errorHandler, options); return () => navigator.geolocation.clearWatch(watchId); }, [options]); return { loading, error, data }; } // src/hooks/use-state-with-validation.ts var import_react20 = require("react"); function useStateWithValidation(validationFn, initialValue) { const [value, setValue] = (0, import_react20.useState)(initialValue); const [isValid, setIsValid] = (0, import_react20.useState)(true); const set = (0, import_react20.useCallback)( (nextValue) => { const resolvedValue = typeof nextValue === "function" ? nextValue(value) : nextValue; setValue(resolvedValue); setIsValid(validationFn(resolvedValue)); }, [validationFn, value] ); return [value, set, isValid]; } // src/hooks/use-size.ts var import_react21 = require("react"); function useSize(externalRef) { const fromRef = (0, import_react21.useRef)(null); const [size, setSize] = (0, import_react21.useState)({}); (0, import_react21.useEffect)(() => { var _a; if (!isClient || typeof ResizeObserver === "undefined") return; const element = (_a = externalRef == null ? void 0 : externalRef.current) != null ? _a : fromRef.current; if (!element) return; const observer = new ResizeObserver(([entry]) => setSize(entry.contentRect)); observer.observe(element); return () => observer.disconnect(); }, [externalRef]); if (externalRef) return { size }; return { size, fromRef }; } // src/hooks/use-click-outside.ts var import_react22 = require("react"); function useClickOutSide({ onClickOutside, externalRef } = {}) { const fromRef = (0, import_react22.useRef)(null); useEventListener( "click", (e) => { var _a; const element = (_a = externalRef == null ? void 0 : externalRef.current) != null ? _a : fromRef.current; if (element == null || element.contains(e.target)) return; onClickOutside == null ? void 0 : onClickOutside(e); }, isClient ? document : void 0 ); if (externalRef !== void 0) return; return { fromRef }; } // src/hooks/use-dark-mode.ts var import_react23 = require("react"); function useDarkMode(darkModeKey = "dark") { const [darkMode, setDarkMode] = useLocalStorage(darkModeKey); const prefersDarkmode = useMediaQuery("(prefers-color-scheme: dark)"); const enabled = darkMode != null ? darkMode : prefersDarkmode; (0, import_react23.useEffect)(() => { if (isClient) { document.body.classList.toggle(darkModeKey, enabled); } }, [enabled, darkModeKey]); return [enabled, setDarkMode]; } // src/utils/helpers/navigator.ts var copyToClipboard = async (text) => { if (!isClient || !navigator.clipboard) { return Promise.reject(new Error("Clipboard API not available")); } await navigator.clipboard.writeText(text); }; // src/hooks/use-copy-to-clipboard.ts var import_react24 = require("react"); function useCopyToClipboard() { const [value, setValue] = (0, import_react24.useState)(null); const [success, setSuccess] = (0, import_react24.useState)(null); const [error, setError] = (0, import_react24.useState)(null); const [isCopying, setIsCopying] = (0, import_react24.useState)(false); const copy = (0, import_react24.useCallback)((text) => { setIsCopying(true); setError(null); setValue(null); setSuccess(null); copyToClipboard(text).then(() => { setValue(text); setSuccess(true); }).catch((err) => { setError(err); setSuccess(false); }).finally(() => { setIsCopying(false); }); }, []); return [copy, { value, success, error, isCopying }]; } // src/hooks/use-cookies.ts var import_react25 = require("react"); // src/utils/helpers/cookies.ts var getCookie = async (cookieName, defaultValue) => { const cookie = await cookieStore.get(cookieName); if (cookie) return cookie; if (defaultValue) { await cookieStore.set({ ...defaultValue, name: cookieName }); return { name: cookieName, value: defaultValue.value }; } return null; }; // src/hooks/use-cookies.ts function useCookie(cookieName, defaultValue) { const { value: cookie, setValue: setCookie, loading: loadingCookie, error: errorLoadingCookie } = useAsync( async () => await getCookie(cookieName, defaultValue) ); const [loadingCookieAction, setLoadingCookieAction] = (0, import_react25.useState)(false); const [errorOnCookieAction, setErrorOnCookieAction] = (0, import_react25.useState)(null); const updateCookie = (0, import_react25.useCallback)( (newValue) => { setLoadingCookieAction(true); cookieStore.set({ ...newValue, name: cookieName }).then( () => setCookie({ name: cookieName, value: newValue.value }) ).catch((error) => setErrorOnCookieAction(error)).finally(() => setLoadingCookieAction(false)); }, [cookieName] ); const deleteCookie = (0, import_react25.useCallback)(() => { setLoadingCookieAction(true); cookieStore.delete(cookieName).then(() => setCookie(null)).catch((error) => setErrorOnCookieAction(error)).finally(() => setLoadingCookieAction(false)); }, [cookieName]); return [ cookie, { updateCookie, deleteCookie, loadingCookie, errorLoadingCookie, loadingCookieAction, errorOnCookieAction } ]; } var INITIAL_COOKIE_CHANGES = { changed: [], deleted: [] }; function useCookiesChangeListener(onCookieChanges) { const [cookiesChanges, setCookiesChanges] = (0, import_react25.useState)(INITIAL_COOKIE_CHANGES); const cbRef = (0, import_react25.useRef)(onCookieChanges); (0, import_react25.useEffect)(() => { cbRef.current = onCookieChanges; }, [onCookieChanges]); useEventListener( "change", (e) => { setCookiesChanges({ changed: e.changed, deleted: e.deleted }); cbRef.current(e); }, cookieStore ); return cookiesChanges; } function useGetAllCookies(params) { const { value: cookies, loading, error } = useAsync(async () => { if (typeof params === "string") return await cookieStore.getAll(params); return await cookieStore.getAll(params); }); return { cookies, loading, error }; } // src/hooks/use-is-online.ts var import_react26 = require("react"); function useIsOnline() { const [isOnline, setIsOnline] = (0, import_react26.useState)(isClient ? navigator.onLine : true); const handleOnlineStatus = (0, import_react26.useCallback)(() => { if (isClient) { setIsOnline(navigator.onLine); } }, []); useEventListener("online", handleOnlineStatus); useEventListener("offline", handleOnlineStatus); return isOnline; } // src/hooks/use-long-press.ts var import_react27 = require("react"); function useLongPress({ externalRef, onLongPress, delay = 300 }) { var _a, _b, _c, _d, _e; const fromRef = (0, import_react27.useRef)(null); const { reset, clear } = useTimeout(onLongPress, delay); (0, import_react27.useEffect)(clear, []); useEventListener( "mousedown", reset, (_a = externalRef == null ? void 0 : externalRef.current) != null ? _a : fromRef.current ); useEventListener( "touchstart", reset, (_b = externalRef == null ? void 0 : externalRef.current) != null ? _b : fromRef.current ); useEventListener( "mouseup", clear, (_c = externalRef == null ? void 0 : externalRef.current) != null ? _c : fromRef.current ); useEventListener( "touchend", clear, (_d = externalRef == null ? void 0 : externalRef.current) != null ? _d : fromRef.current ); useEventListener( "mouseleave", clear, (_e = externalRef == null ? void 0 : externalRef.current) != null ? _e : fromRef.current ); if (externalRef == null) return { fromRef }; } // src/hooks/use-hover.ts var import_react28 = require("react"); function useHover({ onHover, externalRef, onUnhover } = {}) { const { value: isHovered, set: setIsHovered } = useBoolean(); const fromRef = (0, import_react28.useRef)(null); const target = externalRef ? externalRef.current : fromRef.current; useEventListener( "mouseenter", (event) => { setIsHovered(true); onHover == null ? void 0 : onHover(event); }, target ); useEventListener( "mouseout", (event) => { setIsHovered(false); onUnhover == null ? void 0 : onUnhover(event); }, target ); if (externalRef === void 0) return { isHovered, fromRef }; return { isHovered }; } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { useArray, useAsync, useBoolean, useBroadcastChannelListener, useBroadcastChannelSender, useClickOutSide, useCookie, useCookiesChangeListener, useCopyToClipboard, useDarkMode, useDebouncedEffect, useDebouncedValue, useEffectAfterMount, useEventListener, useGeolocation, useGetAllCookies, useHover, useIsFirstRender, useIsOnline, useLocalStorage, useLongPress, useMediaQuery, useNearScreen, usePrevious, useScript, useSessionStorage, useSize, useStateWithHistory, useStateWithValidation, useTimeout, useWindowSize });