UNPKG

@hyunjin/hooks

Version:

collection of useful hooks

585 lines (555 loc) 17.7 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/index.ts var index_exports = {}; __export(index_exports, { OverlayProvider: () => OverlayProvider, canUseDOM: () => canUseDOM, useBodyScrollLock: () => useBodyScrollLock, useBoolean: () => useBoolean, useCallbackOnce: () => useCallbackOnce, useCopyToClipboard: () => useCopyToClipboard, useDebounce: () => useDebounce, useDocumentTitle: () => useDocumentTitle, useEventListener: () => useEventListener, useFetch: () => useFetch, useForceUpdate: () => useForceUpdate, useInterval: () => useInterval, useIsClient: () => useIsClient, useIsFirstRender: () => useIsFirstRender, useIsMounted: () => useIsMounted, useIsScrolled: () => useIsScrolled, useIsomorphicLayoutEffect: () => useIsomorphicLayoutEffect, useKeyPress: () => useKeyPress, useLatestValue: () => useLatestValue, useLockScroll: () => useLockScroll, useMount: () => useMount, useOnClickOutside: () => useOnClickOutside, useOnlineStatus: () => useOnlineStatus, useOverlay: () => useOverlay, useOverlayContext: () => useOverlayContext, useSafeContext: () => useSafeContext, useToggle: () => useToggle, useUpdateEffect: () => useUpdateEffect, useUpload: () => useUpload, useWindowSize: () => useWindowSize }); module.exports = __toCommonJS(index_exports); // src/useIsomorphicLayoutEffect/useIsomorphicLayoutEffect.ts var import_react = require("react"); var canUseDOM = !!(typeof window !== "undefined" && typeof window.document !== "undefined" && typeof window.document.createElement !== "undefined"); var useIsomorphicLayoutEffect = canUseDOM ? import_react.useLayoutEffect : import_react.useEffect; // src/useBodyScrollLock/useBodyScrollLock.ts var useBodyScrollLock = (enable = true) => { useIsomorphicLayoutEffect(() => { if (enable === false) { return; } const originalStyle = window.getComputedStyle(document.body).overflow; document.body.style.overflow = "hidden"; return () => { document.body.style.overflow = originalStyle; }; }, [enable]); }; // src/useBoolean/useBoolean.ts var import_react2 = require("react"); var useBoolean = (defaultValue = false) => { if (typeof defaultValue !== "boolean") { throw new Error("defaultValue must be `true` or `false`"); } const [value, setValue] = (0, import_react2.useState)(defaultValue); const setTrue = (0, import_react2.useCallback)(() => { setValue(true); }, []); const setFalse = (0, import_react2.useCallback)(() => { setValue(false); }, []); const toggle = (0, import_react2.useCallback)(() => { setValue((x) => !x); }, []); return { value, setValue, setTrue, setFalse, toggle }; }; // src/useCallbackOnce/useCallbackOnce.ts var import_react3 = require("react"); var useCallbackOnce = (callback, deps) => { const hasFired = (0, import_react3.useRef)(false); const memoizedCallback = (0, import_react3.useCallback)((...args) => { if (hasFired.current) { return; } callback(...args); hasFired.current = true; }, deps); return memoizedCallback; }; // src/useCopyToClipboard/useCopyToClipboard.ts var import_react4 = require("react"); var useCopyToClipboard = () => { const [copiedText, setCopiedText] = (0, import_react4.useState)(null); const copy = (0, import_react4.useCallback)(async (text) => { if (!(navigator == null ? void 0 : navigator.clipboard)) { console.warn("Clipboard not supported"); return false; } try { await navigator.clipboard.writeText(text); setCopiedText(text); return true; } catch (error) { console.warn("Copy failed", error); setCopiedText(null); return false; } }, []); return { copiedText, copy }; }; // src/useDebounce/useDebounce.ts var import_react5 = require("react"); var useDebounce = (value, delay) => { const [debouncedValue, setDebouncedValue] = (0, import_react5.useState)(value); (0, import_react5.useEffect)(() => { const timer = setTimeout(() => setDebouncedValue(value), delay || 500); return () => { clearTimeout(timer); }; }, [value, delay]); return debouncedValue; }; // src/useDocumentTitle/useDocumentTitle.ts var useDocumentTitle = (title) => { useIsomorphicLayoutEffect(() => { window.document.title = title; }, [title]); }; // src/useEventListener/useEventListener.ts var import_react6 = require("react"); var useEventListener = (eventName, handler, element = globalThis, options = {}) => { const savedHandler = (0, import_react6.useRef)(handler); (0, import_react6.useEffect)(() => { savedHandler.current = handler; }, [handler]); (0, import_react6.useEffect)(() => { const isSupported = element && "addEventListener" in element; if (!isSupported) { return; } const eventListener = (event) => { if (savedHandler.current) { savedHandler.current(event); } }; const { capture, passive, once } = options; const opts = { capture, passive, once }; element.addEventListener(eventName, eventListener, opts); return () => { element.removeEventListener(eventName, eventListener, opts); }; }, [eventName, element, options.capture, options.passive, options.once]); }; // src/useFetch/useFetch.ts var import_react7 = require("react"); function useFetch(url, options) { const cache = (0, import_react7.useRef)({}); const cancelRequest = (0, import_react7.useRef)(false); const initialState = { error: void 0, data: void 0 }; const fetchReducer = (state2, action) => { switch (action.type) { case "loading": return { ...initialState }; case "fetched": return { ...initialState, data: action.payload }; case "error": return { ...initialState, error: action.payload }; default: return state2; } }; const [state, dispatch] = (0, import_react7.useReducer)(fetchReducer, initialState); (0, import_react7.useEffect)(() => { if (!url) { return; } cancelRequest.current = false; const fetchData = async () => { dispatch({ type: "loading" }); if (cache.current[url]) { dispatch({ type: "fetched", payload: cache.current[url] }); return; } try { const response = await fetch(url, options); if (!response.ok) { throw new Error(response.statusText); } const data = await response.json(); cache.current[url] = data; if (cancelRequest.current) { return; } dispatch({ type: "fetched", payload: data }); } catch (error) { if (cancelRequest.current) { return; } dispatch({ type: "error", payload: error }); } }; void fetchData(); return () => { cancelRequest.current = true; }; }, [url]); return state; } // src/useForceUpdate/useForceUpdate.ts var import_react8 = require("react"); var updater = (num) => (num + 1) % 1e6; var useForceUpdate = () => { const [, forceUpdate] = (0, import_react8.useReducer)(updater, 0); return forceUpdate; }; // src/useInterval/useInterval.ts var import_react9 = require("react"); var useInterval = (callback, delay) => { const savedCallbackRef = (0, import_react9.useRef)(callback); (0, import_react9.useEffect)(() => { savedCallbackRef.current = callback; }, [callback]); (0, import_react9.useEffect)(() => { const handler = (...args) => { var _a; return (_a = savedCallbackRef.current) == null ? void 0 : _a.call(savedCallbackRef, ...args); }; if (delay) { const id = setInterval(handler, delay); return () => clearInterval(id); } }, [delay]); }; // src/useIsClient/useIsClient.ts var import_react10 = require("react"); var useIsClient = () => { const [isClient, setClient] = (0, import_react10.useState)(false); (0, import_react10.useEffect)(() => { setClient(true); }, []); return isClient; }; // src/useIsFirstRender/useIsFirstRender.ts var import_react11 = require("react"); var useIsFirstRender = () => { const isFirst = (0, import_react11.useRef)(true); if (isFirst.current) { isFirst.current = false; return true; } return isFirst.current; }; // src/useIsMounted/useIsMounted.ts var import_react12 = require("react"); var useIsMounted = () => { const isMounted = (0, import_react12.useRef)(false); (0, import_react12.useEffect)(() => { isMounted.current = true; return () => { isMounted.current = false; }; }, []); return (0, import_react12.useCallback)(() => isMounted.current, []); }; // src/useIsScrolled/useIsScrolled.ts var import_react13 = require("react"); var useIsScrolled = (threshold = 50) => { const [isScrolled, setIsScrolled] = (0, import_react13.useState)(false); (0, import_react13.useEffect)(() => { const handleScroll = () => { const scrolled = window.scrollY > threshold; setIsScrolled(scrolled); }; window.addEventListener("scroll", handleScroll); return () => { window.removeEventListener("scroll", handleScroll); }; }, [threshold]); return isScrolled; }; // src/useKeyPress/useKeypress.ts var import_react14 = require("react"); var useKeyPress = ({ useAltKey = true, active = true, targetCode, onPress, dep = [] }) => { (0, import_react14.useEffect)(() => { const targetCodesArray = Array.isArray(targetCode) ? targetCode : [targetCode]; const downHandler = ({ code, altKey }) => { if (altKey === useAltKey && targetCodesArray.includes(code) && active) { onPress(); } }; window.addEventListener("keydown", downHandler); return () => { window.removeEventListener("keydown", downHandler); }; }, [active, useAltKey, onPress, targetCode, ...dep]); }; // src/useLatestValue/useLatestValue.ts var import_react15 = require("react"); var useLatestValue = (value) => { const cache = (0, import_react15.useRef)(value); useIsomorphicLayoutEffect(() => { cache.current = value; }, [value]); return cache; }; // src/useLockScroll/useLockScroll.ts var useLockScroll = (isOpen = true) => { useIsomorphicLayoutEffect(() => { if (isOpen) { document.body.style.overflow = "hidden"; } else { document.body.style.overflow = "unset"; } return () => { document.body.style.overflow = "unset"; }; }, [isOpen]); }; // src/useMount/useMount.ts var import_react16 = require("react"); var useMount = (callback) => { (0, import_react16.useEffect)(callback, []); }; // src/useOnClickOutside/useOnClickOutside.ts var import_react17 = require("react"); var useOnClickOutside = (ref, handler) => { (0, import_react17.useEffect)(() => { const listener = (event) => { if (!ref.current || ref.current.contains(event.target)) { return; } handler(event); }; document.addEventListener("mousedown", listener); document.addEventListener("touchstart", listener); return () => { document.removeEventListener("mousedown", listener); document.removeEventListener("touchstart", listener); }; }, [ref, handler]); }; // src/useOnlineStatus/useOnlineStatus.ts var import_react18 = require("react"); var getNetworkState = () => { return navigator.onLine; }; var subscribe = (callback) => { window.addEventListener("online", callback); window.addEventListener("offline", callback); return () => { window.removeEventListener("online", callback); window.removeEventListener("offline", callback); }; }; var getServerSnapshot = () => { return true; }; var useOnlineStatus = () => { const isOnline = (0, import_react18.useSyncExternalStore)( subscribe, getNetworkState, getServerSnapshot ); return isOnline; }; // src/useOverlay/OverlayContext.tsx var import_react19 = require("react"); var import_jsx_runtime = require("react/jsx-runtime"); var OverlayContext = (0, import_react19.createContext)(null); var OverlayProvider = ({ children }) => { const [overlays, setOverlays] = (0, import_react19.useState)(/* @__PURE__ */ new Map()); const openOverlay = (0, import_react19.useCallback)((id, element) => { setOverlays((prev) => { const cloned = new Map(prev); cloned.set(id, element); return cloned; }); }, []); const closeOverlay = (0, import_react19.useCallback)((id) => { setOverlays((prev) => { const cloned = new Map(prev); cloned.delete(id); return cloned; }); }, []); const contextValue = (0, import_react19.useMemo)( () => ({ openOverlay, closeOverlay }), [openOverlay, closeOverlay] ); return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(OverlayContext.Provider, { value: contextValue, children: [ children, /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { id: "overlay-container", children: [...overlays.entries()].map(([key, element]) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react19.Fragment, { children: element }, key)) }) ] }); }; var useOverlayContext = () => { const ctx = (0, import_react19.useContext)(OverlayContext); if (!ctx) throw new Error( "Cannot find OverlayContext. It should be wrapped within OverlayProvider." ); return ctx; }; // src/useOverlay/useOverlay.ts var import_react20 = require("react"); var overlayId = 1; var useOverlay = () => { const { openOverlay, closeOverlay } = useOverlayContext(); const [id] = (0, import_react20.useState)(() => String(overlayId++)); const open = (0, import_react20.useCallback)( (element) => openOverlay(id, element), [openOverlay, id] ); const close = (0, import_react20.useCallback)(() => closeOverlay(id), [closeOverlay, id]); return { open, close }; }; // src/useSafeContext/useSafeContext.ts var import_react21 = require("react"); var useSafeContext = (unsafeContext, customMessage) => { const context = (0, import_react21.useContext)(unsafeContext); if (!context) { const displayName = unsafeContext.displayName; let errorMessage; switch (typeof customMessage) { case "string": errorMessage = customMessage; break; case "function": errorMessage = customMessage(displayName); break; default: { const contextName = displayName || "a context"; errorMessage = `You are trying to use ${contextName} outside of the provider`; break; } } throw new Error(errorMessage); } return context; }; // src/useToggle/useToggle.ts var import_react22 = require("react"); var useToggle = (defaultValue) => { const [value, setValue] = (0, import_react22.useState)(!!defaultValue); const toggle = (0, import_react22.useCallback)(() => setValue((prev) => !prev), []); return [value, toggle, setValue]; }; // src/useUpdateEffect/useUpdateEffect.ts var import_react23 = require("react"); var useUpdateEffect = (effect, deps) => { const isFirst = useIsFirstRender(); (0, import_react23.useEffect)(() => { if (!isFirst) { return effect(); } }, deps); }; // src/useUpload/useUpload.ts var import_react24 = require("react"); var useUpload = () => { const [file, setFile] = (0, import_react24.useState)(); const upload = () => { const promise = new Promise((resolve, reject) => { const input = document.createElement("input"); input.accept = "image/*"; const timeout = setTimeout(reject, 1e3 * 60 * 3); input.type = "file"; input.onchange = () => { clearTimeout(timeout); if (!input.files) { return reject(); } const file2 = input.files[0]; setFile(file2); resolve(file2); }; input.click(); }); return promise; }; return { upload, file }; }; // src/useWindowSize/useWindowSize.ts var import_react25 = require("react"); var useWindowSize = () => { const [windowSize, setWindowSize] = (0, import_react25.useState)(); (0, import_react25.useEffect)(() => { const handleResize = () => { setWindowSize({ width: window.innerWidth, height: window.innerHeight }); }; window.addEventListener("resize", handleResize); handleResize(); return () => window.removeEventListener("resize", handleResize); }, []); return windowSize; }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { OverlayProvider, canUseDOM, useBodyScrollLock, useBoolean, useCallbackOnce, useCopyToClipboard, useDebounce, useDocumentTitle, useEventListener, useFetch, useForceUpdate, useInterval, useIsClient, useIsFirstRender, useIsMounted, useIsScrolled, useIsomorphicLayoutEffect, useKeyPress, useLatestValue, useLockScroll, useMount, useOnClickOutside, useOnlineStatus, useOverlay, useOverlayContext, useSafeContext, useToggle, useUpdateEffect, useUpload, useWindowSize });