@usefy/use-copy-to-clipboard 0.0.7 → 0.0.8

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.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/useCopyToClipboard.ts"],"sourcesContent":["export {\r\n useCopyToClipboard,\r\n type UseCopyToClipboardOptions,\r\n type UseCopyToClipboardReturn,\r\n type CopyFn,\r\n} from \"./useCopyToClipboard\";\r\n","import { useCallback, useEffect, useRef, useState } from \"react\";\r\n\r\n/**\r\n * Options for useCopyToClipboard hook\r\n */\r\nexport interface UseCopyToClipboardOptions {\r\n /**\r\n * Time in milliseconds before the copied state resets to null.\r\n * Set to 0 to disable auto-reset.\r\n * @default 2000\r\n */\r\n timeout?: number;\r\n /**\r\n * Callback function called when copy succeeds\r\n * @param text - The text that was copied\r\n */\r\n onSuccess?: (text: string) => void;\r\n /**\r\n * Callback function called when copy fails\r\n * @param error - The error that occurred\r\n */\r\n onError?: (error: Error) => void;\r\n}\r\n\r\n/**\r\n * Type for the copy function\r\n */\r\nexport type CopyFn = (text: string) => Promise<boolean>;\r\n\r\n/**\r\n * Return type for useCopyToClipboard hook\r\n * Tuple format: [copiedText, copy]\r\n */\r\nexport type UseCopyToClipboardReturn = [\r\n copiedText: string | null,\r\n copy: CopyFn\r\n];\r\n\r\n/**\r\n * Fallback copy function for browsers that don't support the Clipboard API\r\n * @param text - Text to copy to clipboard\r\n * @returns Whether the copy was successful\r\n */\r\nfunction fallbackCopyToClipboard(text: string): boolean {\r\n // Check if we're in a browser environment\r\n if (typeof document === \"undefined\") {\r\n return false;\r\n }\r\n\r\n const textarea = document.createElement(\"textarea\");\r\n textarea.value = text;\r\n\r\n // Make the textarea invisible but still functional\r\n textarea.style.cssText =\r\n \"position:fixed;left:-9999px;top:-9999px;opacity:0;pointer-events:none\";\r\n textarea.setAttribute(\"readonly\", \"\");\r\n textarea.setAttribute(\"aria-hidden\", \"true\");\r\n\r\n document.body.appendChild(textarea);\r\n\r\n // Select the text\r\n textarea.focus();\r\n textarea.select();\r\n\r\n // For mobile devices\r\n textarea.setSelectionRange(0, text.length);\r\n\r\n let success = false;\r\n try {\r\n success = document.execCommand(\"copy\");\r\n } catch {\r\n success = false;\r\n }\r\n\r\n document.body.removeChild(textarea);\r\n return success;\r\n}\r\n\r\n/**\r\n * Copies text to clipboard using the Clipboard API with fallback support.\r\n * Returns the copied text (or null if not copied) and a copy function.\r\n *\r\n * @param options - Configuration options for the hook\r\n * @returns Tuple of [copiedText, copy]\r\n *\r\n * @example\r\n * ```tsx\r\n * function CopyButton() {\r\n * const [copiedText, copy] = useCopyToClipboard();\r\n *\r\n * return (\r\n * <button onClick={() => copy(\"Hello World\")}>\r\n * {copiedText ? \"Copied!\" : \"Copy\"}\r\n * </button>\r\n * );\r\n * }\r\n * ```\r\n *\r\n * @example\r\n * ```tsx\r\n * // With custom timeout\r\n * const [copiedText, copy] = useCopyToClipboard({ timeout: 3000 });\r\n * ```\r\n *\r\n * @example\r\n * ```tsx\r\n * // With callbacks\r\n * const [copiedText, copy] = useCopyToClipboard({\r\n * onSuccess: (text) => console.log(`Copied: ${text}`),\r\n * onError: (error) => console.error(`Failed to copy: ${error.message}`),\r\n * });\r\n * ```\r\n *\r\n * @example\r\n * ```tsx\r\n * // Disable auto-reset\r\n * const [copiedText, copy] = useCopyToClipboard({ timeout: 0 });\r\n * ```\r\n */\r\nexport function useCopyToClipboard(\r\n options: UseCopyToClipboardOptions = {}\r\n): UseCopyToClipboardReturn {\r\n const { timeout = 2000, onSuccess, onError } = options;\r\n\r\n const [copiedText, setCopiedText] = useState<string | null>(null);\r\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | undefined>(\r\n undefined\r\n );\r\n\r\n // Store callbacks in refs to avoid dependency issues\r\n const onSuccessRef = useRef(onSuccess);\r\n const onErrorRef = useRef(onError);\r\n const timeoutValueRef = useRef(timeout);\r\n\r\n // Update refs when options change\r\n onSuccessRef.current = onSuccess;\r\n onErrorRef.current = onError;\r\n timeoutValueRef.current = timeout;\r\n\r\n // Cleanup timeout on unmount\r\n useEffect(() => {\r\n return () => {\r\n if (timeoutRef.current !== undefined) {\r\n clearTimeout(timeoutRef.current);\r\n }\r\n };\r\n }, []);\r\n\r\n const copy: CopyFn = useCallback(async (text: string): Promise<boolean> => {\r\n // Clear any existing timeout\r\n if (timeoutRef.current !== undefined) {\r\n clearTimeout(timeoutRef.current);\r\n timeoutRef.current = undefined;\r\n }\r\n\r\n // Check for SSR\r\n if (typeof window === \"undefined\") {\r\n const error = new Error(\"Clipboard is not available in this environment\");\r\n onErrorRef.current?.(error);\r\n return false;\r\n }\r\n\r\n try {\r\n // Try the modern Clipboard API first\r\n if (navigator.clipboard && typeof navigator.clipboard.writeText === \"function\") {\r\n await navigator.clipboard.writeText(text);\r\n } else {\r\n // Fall back to execCommand\r\n const success = fallbackCopyToClipboard(text);\r\n if (!success) {\r\n throw new Error(\"Failed to copy text using fallback method\");\r\n }\r\n }\r\n\r\n // Success\r\n setCopiedText(text);\r\n onSuccessRef.current?.(text);\r\n\r\n // Set timeout for auto-reset if enabled\r\n if (timeoutValueRef.current > 0) {\r\n timeoutRef.current = setTimeout(() => {\r\n setCopiedText(null);\r\n timeoutRef.current = undefined;\r\n }, timeoutValueRef.current);\r\n }\r\n\r\n return true;\r\n } catch (err) {\r\n // Try fallback if Clipboard API failed\r\n try {\r\n const success = fallbackCopyToClipboard(text);\r\n if (success) {\r\n setCopiedText(text);\r\n onSuccessRef.current?.(text);\r\n\r\n if (timeoutValueRef.current > 0) {\r\n timeoutRef.current = setTimeout(() => {\r\n setCopiedText(null);\r\n timeoutRef.current = undefined;\r\n }, timeoutValueRef.current);\r\n }\r\n\r\n return true;\r\n }\r\n } catch {\r\n // Fallback also failed\r\n }\r\n\r\n // Both methods failed\r\n const error =\r\n err instanceof Error ? err : new Error(\"Failed to copy text to clipboard\");\r\n setCopiedText(null);\r\n onErrorRef.current?.(error);\r\n return false;\r\n }\r\n }, []);\r\n\r\n return [copiedText, copy];\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,mBAAyD;AA2CzD,SAAS,wBAAwB,MAAuB;AAEtD,MAAI,OAAO,aAAa,aAAa;AACnC,WAAO;AAAA,EACT;AAEA,QAAM,WAAW,SAAS,cAAc,UAAU;AAClD,WAAS,QAAQ;AAGjB,WAAS,MAAM,UACb;AACF,WAAS,aAAa,YAAY,EAAE;AACpC,WAAS,aAAa,eAAe,MAAM;AAE3C,WAAS,KAAK,YAAY,QAAQ;AAGlC,WAAS,MAAM;AACf,WAAS,OAAO;AAGhB,WAAS,kBAAkB,GAAG,KAAK,MAAM;AAEzC,MAAI,UAAU;AACd,MAAI;AACF,cAAU,SAAS,YAAY,MAAM;AAAA,EACvC,QAAQ;AACN,cAAU;AAAA,EACZ;AAEA,WAAS,KAAK,YAAY,QAAQ;AAClC,SAAO;AACT;AA2CO,SAAS,mBACd,UAAqC,CAAC,GACZ;AAC1B,QAAM,EAAE,UAAU,KAAM,WAAW,QAAQ,IAAI;AAE/C,QAAM,CAAC,YAAY,aAAa,QAAI,uBAAwB,IAAI;AAChE,QAAM,iBAAa;AAAA,IACjB;AAAA,EACF;AAGA,QAAM,mBAAe,qBAAO,SAAS;AACrC,QAAM,iBAAa,qBAAO,OAAO;AACjC,QAAM,sBAAkB,qBAAO,OAAO;AAGtC,eAAa,UAAU;AACvB,aAAW,UAAU;AACrB,kBAAgB,UAAU;AAG1B,8BAAU,MAAM;AACd,WAAO,MAAM;AACX,UAAI,WAAW,YAAY,QAAW;AACpC,qBAAa,WAAW,OAAO;AAAA,MACjC;AAAA,IACF;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,QAAM,WAAe,0BAAY,OAAO,SAAmC;AAEzE,QAAI,WAAW,YAAY,QAAW;AACpC,mBAAa,WAAW,OAAO;AAC/B,iBAAW,UAAU;AAAA,IACvB;AAGA,QAAI,OAAO,WAAW,aAAa;AACjC,YAAM,QAAQ,IAAI,MAAM,gDAAgD;AACxE,iBAAW,UAAU,KAAK;AAC1B,aAAO;AAAA,IACT;AAEA,QAAI;AAEF,UAAI,UAAU,aAAa,OAAO,UAAU,UAAU,cAAc,YAAY;AAC9E,cAAM,UAAU,UAAU,UAAU,IAAI;AAAA,MAC1C,OAAO;AAEL,cAAM,UAAU,wBAAwB,IAAI;AAC5C,YAAI,CAAC,SAAS;AACZ,gBAAM,IAAI,MAAM,2CAA2C;AAAA,QAC7D;AAAA,MACF;AAGA,oBAAc,IAAI;AAClB,mBAAa,UAAU,IAAI;AAG3B,UAAI,gBAAgB,UAAU,GAAG;AAC/B,mBAAW,UAAU,WAAW,MAAM;AACpC,wBAAc,IAAI;AAClB,qBAAW,UAAU;AAAA,QACvB,GAAG,gBAAgB,OAAO;AAAA,MAC5B;AAEA,aAAO;AAAA,IACT,SAAS,KAAK;AAEZ,UAAI;AACF,cAAM,UAAU,wBAAwB,IAAI;AAC5C,YAAI,SAAS;AACX,wBAAc,IAAI;AAClB,uBAAa,UAAU,IAAI;AAE3B,cAAI,gBAAgB,UAAU,GAAG;AAC/B,uBAAW,UAAU,WAAW,MAAM;AACpC,4BAAc,IAAI;AAClB,yBAAW,UAAU;AAAA,YACvB,GAAG,gBAAgB,OAAO;AAAA,UAC5B;AAEA,iBAAO;AAAA,QACT;AAAA,MACF,QAAQ;AAAA,MAER;AAGA,YAAM,QACJ,eAAe,QAAQ,MAAM,IAAI,MAAM,kCAAkC;AAC3E,oBAAc,IAAI;AAClB,iBAAW,UAAU,KAAK;AAC1B,aAAO;AAAA,IACT;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,SAAO,CAAC,YAAY,IAAI;AAC1B;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/useCopyToClipboard.ts"],"sourcesContent":["export {\n useCopyToClipboard,\n type UseCopyToClipboardOptions,\n type UseCopyToClipboardReturn,\n type CopyFn,\n} from \"./useCopyToClipboard\";\n","import { useCallback, useEffect, useRef, useState } from \"react\";\n\n/**\n * Options for useCopyToClipboard hook\n */\nexport interface UseCopyToClipboardOptions {\n /**\n * Time in milliseconds before the copied state resets to null.\n * Set to 0 to disable auto-reset.\n * @default 2000\n */\n timeout?: number;\n /**\n * Callback function called when copy succeeds\n * @param text - The text that was copied\n */\n onSuccess?: (text: string) => void;\n /**\n * Callback function called when copy fails\n * @param error - The error that occurred\n */\n onError?: (error: Error) => void;\n}\n\n/**\n * Type for the copy function\n */\nexport type CopyFn = (text: string) => Promise<boolean>;\n\n/**\n * Return type for useCopyToClipboard hook\n * Tuple format: [copiedText, copy]\n */\nexport type UseCopyToClipboardReturn = [\n copiedText: string | null,\n copy: CopyFn\n];\n\n/**\n * Fallback copy function for browsers that don't support the Clipboard API\n * @param text - Text to copy to clipboard\n * @returns Whether the copy was successful\n */\nfunction fallbackCopyToClipboard(text: string): boolean {\n // Check if we're in a browser environment\n if (typeof document === \"undefined\") {\n return false;\n }\n\n const textarea = document.createElement(\"textarea\");\n textarea.value = text;\n\n // Make the textarea invisible but still functional\n textarea.style.cssText =\n \"position:fixed;left:-9999px;top:-9999px;opacity:0;pointer-events:none\";\n textarea.setAttribute(\"readonly\", \"\");\n textarea.setAttribute(\"aria-hidden\", \"true\");\n\n document.body.appendChild(textarea);\n\n // Select the text\n textarea.focus();\n textarea.select();\n\n // For mobile devices\n textarea.setSelectionRange(0, text.length);\n\n let success = false;\n try {\n success = document.execCommand(\"copy\");\n } catch {\n success = false;\n }\n\n document.body.removeChild(textarea);\n return success;\n}\n\n/**\n * Copies text to clipboard using the Clipboard API with fallback support.\n * Returns the copied text (or null if not copied) and a copy function.\n *\n * @param options - Configuration options for the hook\n * @returns Tuple of [copiedText, copy]\n *\n * @example\n * ```tsx\n * function CopyButton() {\n * const [copiedText, copy] = useCopyToClipboard();\n *\n * return (\n * <button onClick={() => copy(\"Hello World\")}>\n * {copiedText ? \"Copied!\" : \"Copy\"}\n * </button>\n * );\n * }\n * ```\n *\n * @example\n * ```tsx\n * // With custom timeout\n * const [copiedText, copy] = useCopyToClipboard({ timeout: 3000 });\n * ```\n *\n * @example\n * ```tsx\n * // With callbacks\n * const [copiedText, copy] = useCopyToClipboard({\n * onSuccess: (text) => console.log(`Copied: ${text}`),\n * onError: (error) => console.error(`Failed to copy: ${error.message}`),\n * });\n * ```\n *\n * @example\n * ```tsx\n * // Disable auto-reset\n * const [copiedText, copy] = useCopyToClipboard({ timeout: 0 });\n * ```\n */\nexport function useCopyToClipboard(\n options: UseCopyToClipboardOptions = {}\n): UseCopyToClipboardReturn {\n const { timeout = 2000, onSuccess, onError } = options;\n\n const [copiedText, setCopiedText] = useState<string | null>(null);\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | undefined>(\n undefined\n );\n\n // Store callbacks in refs to avoid dependency issues\n const onSuccessRef = useRef(onSuccess);\n const onErrorRef = useRef(onError);\n const timeoutValueRef = useRef(timeout);\n\n // Update refs when options change\n onSuccessRef.current = onSuccess;\n onErrorRef.current = onError;\n timeoutValueRef.current = timeout;\n\n // Cleanup timeout on unmount\n useEffect(() => {\n return () => {\n if (timeoutRef.current !== undefined) {\n clearTimeout(timeoutRef.current);\n }\n };\n }, []);\n\n const copy: CopyFn = useCallback(async (text: string): Promise<boolean> => {\n // Clear any existing timeout\n if (timeoutRef.current !== undefined) {\n clearTimeout(timeoutRef.current);\n timeoutRef.current = undefined;\n }\n\n // Check for SSR\n if (typeof window === \"undefined\") {\n const error = new Error(\"Clipboard is not available in this environment\");\n onErrorRef.current?.(error);\n return false;\n }\n\n try {\n // Try the modern Clipboard API first\n if (navigator.clipboard && typeof navigator.clipboard.writeText === \"function\") {\n await navigator.clipboard.writeText(text);\n } else {\n // Fall back to execCommand\n const success = fallbackCopyToClipboard(text);\n if (!success) {\n throw new Error(\"Failed to copy text using fallback method\");\n }\n }\n\n // Success\n setCopiedText(text);\n onSuccessRef.current?.(text);\n\n // Set timeout for auto-reset if enabled\n if (timeoutValueRef.current > 0) {\n timeoutRef.current = setTimeout(() => {\n setCopiedText(null);\n timeoutRef.current = undefined;\n }, timeoutValueRef.current);\n }\n\n return true;\n } catch (err) {\n // Try fallback if Clipboard API failed\n try {\n const success = fallbackCopyToClipboard(text);\n if (success) {\n setCopiedText(text);\n onSuccessRef.current?.(text);\n\n if (timeoutValueRef.current > 0) {\n timeoutRef.current = setTimeout(() => {\n setCopiedText(null);\n timeoutRef.current = undefined;\n }, timeoutValueRef.current);\n }\n\n return true;\n }\n } catch {\n // Fallback also failed\n }\n\n // Both methods failed\n const error =\n err instanceof Error ? err : new Error(\"Failed to copy text to clipboard\");\n setCopiedText(null);\n onErrorRef.current?.(error);\n return false;\n }\n }, []);\n\n return [copiedText, copy];\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,mBAAyD;AA2CzD,SAAS,wBAAwB,MAAuB;AAEtD,MAAI,OAAO,aAAa,aAAa;AACnC,WAAO;AAAA,EACT;AAEA,QAAM,WAAW,SAAS,cAAc,UAAU;AAClD,WAAS,QAAQ;AAGjB,WAAS,MAAM,UACb;AACF,WAAS,aAAa,YAAY,EAAE;AACpC,WAAS,aAAa,eAAe,MAAM;AAE3C,WAAS,KAAK,YAAY,QAAQ;AAGlC,WAAS,MAAM;AACf,WAAS,OAAO;AAGhB,WAAS,kBAAkB,GAAG,KAAK,MAAM;AAEzC,MAAI,UAAU;AACd,MAAI;AACF,cAAU,SAAS,YAAY,MAAM;AAAA,EACvC,QAAQ;AACN,cAAU;AAAA,EACZ;AAEA,WAAS,KAAK,YAAY,QAAQ;AAClC,SAAO;AACT;AA2CO,SAAS,mBACd,UAAqC,CAAC,GACZ;AAC1B,QAAM,EAAE,UAAU,KAAM,WAAW,QAAQ,IAAI;AAE/C,QAAM,CAAC,YAAY,aAAa,QAAI,uBAAwB,IAAI;AAChE,QAAM,iBAAa;AAAA,IACjB;AAAA,EACF;AAGA,QAAM,mBAAe,qBAAO,SAAS;AACrC,QAAM,iBAAa,qBAAO,OAAO;AACjC,QAAM,sBAAkB,qBAAO,OAAO;AAGtC,eAAa,UAAU;AACvB,aAAW,UAAU;AACrB,kBAAgB,UAAU;AAG1B,8BAAU,MAAM;AACd,WAAO,MAAM;AACX,UAAI,WAAW,YAAY,QAAW;AACpC,qBAAa,WAAW,OAAO;AAAA,MACjC;AAAA,IACF;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,QAAM,WAAe,0BAAY,OAAO,SAAmC;AAEzE,QAAI,WAAW,YAAY,QAAW;AACpC,mBAAa,WAAW,OAAO;AAC/B,iBAAW,UAAU;AAAA,IACvB;AAGA,QAAI,OAAO,WAAW,aAAa;AACjC,YAAM,QAAQ,IAAI,MAAM,gDAAgD;AACxE,iBAAW,UAAU,KAAK;AAC1B,aAAO;AAAA,IACT;AAEA,QAAI;AAEF,UAAI,UAAU,aAAa,OAAO,UAAU,UAAU,cAAc,YAAY;AAC9E,cAAM,UAAU,UAAU,UAAU,IAAI;AAAA,MAC1C,OAAO;AAEL,cAAM,UAAU,wBAAwB,IAAI;AAC5C,YAAI,CAAC,SAAS;AACZ,gBAAM,IAAI,MAAM,2CAA2C;AAAA,QAC7D;AAAA,MACF;AAGA,oBAAc,IAAI;AAClB,mBAAa,UAAU,IAAI;AAG3B,UAAI,gBAAgB,UAAU,GAAG;AAC/B,mBAAW,UAAU,WAAW,MAAM;AACpC,wBAAc,IAAI;AAClB,qBAAW,UAAU;AAAA,QACvB,GAAG,gBAAgB,OAAO;AAAA,MAC5B;AAEA,aAAO;AAAA,IACT,SAAS,KAAK;AAEZ,UAAI;AACF,cAAM,UAAU,wBAAwB,IAAI;AAC5C,YAAI,SAAS;AACX,wBAAc,IAAI;AAClB,uBAAa,UAAU,IAAI;AAE3B,cAAI,gBAAgB,UAAU,GAAG;AAC/B,uBAAW,UAAU,WAAW,MAAM;AACpC,4BAAc,IAAI;AAClB,yBAAW,UAAU;AAAA,YACvB,GAAG,gBAAgB,OAAO;AAAA,UAC5B;AAEA,iBAAO;AAAA,QACT;AAAA,MACF,QAAQ;AAAA,MAER;AAGA,YAAM,QACJ,eAAe,QAAQ,MAAM,IAAI,MAAM,kCAAkC;AAC3E,oBAAc,IAAI;AAClB,iBAAW,UAAU,KAAK;AAC1B,aAAO;AAAA,IACT;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,SAAO,CAAC,YAAY,IAAI;AAC1B;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/useCopyToClipboard.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\r\n\r\n/**\r\n * Options for useCopyToClipboard hook\r\n */\r\nexport interface UseCopyToClipboardOptions {\r\n /**\r\n * Time in milliseconds before the copied state resets to null.\r\n * Set to 0 to disable auto-reset.\r\n * @default 2000\r\n */\r\n timeout?: number;\r\n /**\r\n * Callback function called when copy succeeds\r\n * @param text - The text that was copied\r\n */\r\n onSuccess?: (text: string) => void;\r\n /**\r\n * Callback function called when copy fails\r\n * @param error - The error that occurred\r\n */\r\n onError?: (error: Error) => void;\r\n}\r\n\r\n/**\r\n * Type for the copy function\r\n */\r\nexport type CopyFn = (text: string) => Promise<boolean>;\r\n\r\n/**\r\n * Return type for useCopyToClipboard hook\r\n * Tuple format: [copiedText, copy]\r\n */\r\nexport type UseCopyToClipboardReturn = [\r\n copiedText: string | null,\r\n copy: CopyFn\r\n];\r\n\r\n/**\r\n * Fallback copy function for browsers that don't support the Clipboard API\r\n * @param text - Text to copy to clipboard\r\n * @returns Whether the copy was successful\r\n */\r\nfunction fallbackCopyToClipboard(text: string): boolean {\r\n // Check if we're in a browser environment\r\n if (typeof document === \"undefined\") {\r\n return false;\r\n }\r\n\r\n const textarea = document.createElement(\"textarea\");\r\n textarea.value = text;\r\n\r\n // Make the textarea invisible but still functional\r\n textarea.style.cssText =\r\n \"position:fixed;left:-9999px;top:-9999px;opacity:0;pointer-events:none\";\r\n textarea.setAttribute(\"readonly\", \"\");\r\n textarea.setAttribute(\"aria-hidden\", \"true\");\r\n\r\n document.body.appendChild(textarea);\r\n\r\n // Select the text\r\n textarea.focus();\r\n textarea.select();\r\n\r\n // For mobile devices\r\n textarea.setSelectionRange(0, text.length);\r\n\r\n let success = false;\r\n try {\r\n success = document.execCommand(\"copy\");\r\n } catch {\r\n success = false;\r\n }\r\n\r\n document.body.removeChild(textarea);\r\n return success;\r\n}\r\n\r\n/**\r\n * Copies text to clipboard using the Clipboard API with fallback support.\r\n * Returns the copied text (or null if not copied) and a copy function.\r\n *\r\n * @param options - Configuration options for the hook\r\n * @returns Tuple of [copiedText, copy]\r\n *\r\n * @example\r\n * ```tsx\r\n * function CopyButton() {\r\n * const [copiedText, copy] = useCopyToClipboard();\r\n *\r\n * return (\r\n * <button onClick={() => copy(\"Hello World\")}>\r\n * {copiedText ? \"Copied!\" : \"Copy\"}\r\n * </button>\r\n * );\r\n * }\r\n * ```\r\n *\r\n * @example\r\n * ```tsx\r\n * // With custom timeout\r\n * const [copiedText, copy] = useCopyToClipboard({ timeout: 3000 });\r\n * ```\r\n *\r\n * @example\r\n * ```tsx\r\n * // With callbacks\r\n * const [copiedText, copy] = useCopyToClipboard({\r\n * onSuccess: (text) => console.log(`Copied: ${text}`),\r\n * onError: (error) => console.error(`Failed to copy: ${error.message}`),\r\n * });\r\n * ```\r\n *\r\n * @example\r\n * ```tsx\r\n * // Disable auto-reset\r\n * const [copiedText, copy] = useCopyToClipboard({ timeout: 0 });\r\n * ```\r\n */\r\nexport function useCopyToClipboard(\r\n options: UseCopyToClipboardOptions = {}\r\n): UseCopyToClipboardReturn {\r\n const { timeout = 2000, onSuccess, onError } = options;\r\n\r\n const [copiedText, setCopiedText] = useState<string | null>(null);\r\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | undefined>(\r\n undefined\r\n );\r\n\r\n // Store callbacks in refs to avoid dependency issues\r\n const onSuccessRef = useRef(onSuccess);\r\n const onErrorRef = useRef(onError);\r\n const timeoutValueRef = useRef(timeout);\r\n\r\n // Update refs when options change\r\n onSuccessRef.current = onSuccess;\r\n onErrorRef.current = onError;\r\n timeoutValueRef.current = timeout;\r\n\r\n // Cleanup timeout on unmount\r\n useEffect(() => {\r\n return () => {\r\n if (timeoutRef.current !== undefined) {\r\n clearTimeout(timeoutRef.current);\r\n }\r\n };\r\n }, []);\r\n\r\n const copy: CopyFn = useCallback(async (text: string): Promise<boolean> => {\r\n // Clear any existing timeout\r\n if (timeoutRef.current !== undefined) {\r\n clearTimeout(timeoutRef.current);\r\n timeoutRef.current = undefined;\r\n }\r\n\r\n // Check for SSR\r\n if (typeof window === \"undefined\") {\r\n const error = new Error(\"Clipboard is not available in this environment\");\r\n onErrorRef.current?.(error);\r\n return false;\r\n }\r\n\r\n try {\r\n // Try the modern Clipboard API first\r\n if (navigator.clipboard && typeof navigator.clipboard.writeText === \"function\") {\r\n await navigator.clipboard.writeText(text);\r\n } else {\r\n // Fall back to execCommand\r\n const success = fallbackCopyToClipboard(text);\r\n if (!success) {\r\n throw new Error(\"Failed to copy text using fallback method\");\r\n }\r\n }\r\n\r\n // Success\r\n setCopiedText(text);\r\n onSuccessRef.current?.(text);\r\n\r\n // Set timeout for auto-reset if enabled\r\n if (timeoutValueRef.current > 0) {\r\n timeoutRef.current = setTimeout(() => {\r\n setCopiedText(null);\r\n timeoutRef.current = undefined;\r\n }, timeoutValueRef.current);\r\n }\r\n\r\n return true;\r\n } catch (err) {\r\n // Try fallback if Clipboard API failed\r\n try {\r\n const success = fallbackCopyToClipboard(text);\r\n if (success) {\r\n setCopiedText(text);\r\n onSuccessRef.current?.(text);\r\n\r\n if (timeoutValueRef.current > 0) {\r\n timeoutRef.current = setTimeout(() => {\r\n setCopiedText(null);\r\n timeoutRef.current = undefined;\r\n }, timeoutValueRef.current);\r\n }\r\n\r\n return true;\r\n }\r\n } catch {\r\n // Fallback also failed\r\n }\r\n\r\n // Both methods failed\r\n const error =\r\n err instanceof Error ? err : new Error(\"Failed to copy text to clipboard\");\r\n setCopiedText(null);\r\n onErrorRef.current?.(error);\r\n return false;\r\n }\r\n }, []);\r\n\r\n return [copiedText, copy];\r\n}\r\n"],"mappings":";AAAA,SAAS,aAAa,WAAW,QAAQ,gBAAgB;AA2CzD,SAAS,wBAAwB,MAAuB;AAEtD,MAAI,OAAO,aAAa,aAAa;AACnC,WAAO;AAAA,EACT;AAEA,QAAM,WAAW,SAAS,cAAc,UAAU;AAClD,WAAS,QAAQ;AAGjB,WAAS,MAAM,UACb;AACF,WAAS,aAAa,YAAY,EAAE;AACpC,WAAS,aAAa,eAAe,MAAM;AAE3C,WAAS,KAAK,YAAY,QAAQ;AAGlC,WAAS,MAAM;AACf,WAAS,OAAO;AAGhB,WAAS,kBAAkB,GAAG,KAAK,MAAM;AAEzC,MAAI,UAAU;AACd,MAAI;AACF,cAAU,SAAS,YAAY,MAAM;AAAA,EACvC,QAAQ;AACN,cAAU;AAAA,EACZ;AAEA,WAAS,KAAK,YAAY,QAAQ;AAClC,SAAO;AACT;AA2CO,SAAS,mBACd,UAAqC,CAAC,GACZ;AAC1B,QAAM,EAAE,UAAU,KAAM,WAAW,QAAQ,IAAI;AAE/C,QAAM,CAAC,YAAY,aAAa,IAAI,SAAwB,IAAI;AAChE,QAAM,aAAa;AAAA,IACjB;AAAA,EACF;AAGA,QAAM,eAAe,OAAO,SAAS;AACrC,QAAM,aAAa,OAAO,OAAO;AACjC,QAAM,kBAAkB,OAAO,OAAO;AAGtC,eAAa,UAAU;AACvB,aAAW,UAAU;AACrB,kBAAgB,UAAU;AAG1B,YAAU,MAAM;AACd,WAAO,MAAM;AACX,UAAI,WAAW,YAAY,QAAW;AACpC,qBAAa,WAAW,OAAO;AAAA,MACjC;AAAA,IACF;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,QAAM,OAAe,YAAY,OAAO,SAAmC;AAEzE,QAAI,WAAW,YAAY,QAAW;AACpC,mBAAa,WAAW,OAAO;AAC/B,iBAAW,UAAU;AAAA,IACvB;AAGA,QAAI,OAAO,WAAW,aAAa;AACjC,YAAM,QAAQ,IAAI,MAAM,gDAAgD;AACxE,iBAAW,UAAU,KAAK;AAC1B,aAAO;AAAA,IACT;AAEA,QAAI;AAEF,UAAI,UAAU,aAAa,OAAO,UAAU,UAAU,cAAc,YAAY;AAC9E,cAAM,UAAU,UAAU,UAAU,IAAI;AAAA,MAC1C,OAAO;AAEL,cAAM,UAAU,wBAAwB,IAAI;AAC5C,YAAI,CAAC,SAAS;AACZ,gBAAM,IAAI,MAAM,2CAA2C;AAAA,QAC7D;AAAA,MACF;AAGA,oBAAc,IAAI;AAClB,mBAAa,UAAU,IAAI;AAG3B,UAAI,gBAAgB,UAAU,GAAG;AAC/B,mBAAW,UAAU,WAAW,MAAM;AACpC,wBAAc,IAAI;AAClB,qBAAW,UAAU;AAAA,QACvB,GAAG,gBAAgB,OAAO;AAAA,MAC5B;AAEA,aAAO;AAAA,IACT,SAAS,KAAK;AAEZ,UAAI;AACF,cAAM,UAAU,wBAAwB,IAAI;AAC5C,YAAI,SAAS;AACX,wBAAc,IAAI;AAClB,uBAAa,UAAU,IAAI;AAE3B,cAAI,gBAAgB,UAAU,GAAG;AAC/B,uBAAW,UAAU,WAAW,MAAM;AACpC,4BAAc,IAAI;AAClB,yBAAW,UAAU;AAAA,YACvB,GAAG,gBAAgB,OAAO;AAAA,UAC5B;AAEA,iBAAO;AAAA,QACT;AAAA,MACF,QAAQ;AAAA,MAER;AAGA,YAAM,QACJ,eAAe,QAAQ,MAAM,IAAI,MAAM,kCAAkC;AAC3E,oBAAc,IAAI;AAClB,iBAAW,UAAU,KAAK;AAC1B,aAAO;AAAA,IACT;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,SAAO,CAAC,YAAY,IAAI;AAC1B;","names":[]}
1
+ {"version":3,"sources":["../src/useCopyToClipboard.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\n\n/**\n * Options for useCopyToClipboard hook\n */\nexport interface UseCopyToClipboardOptions {\n /**\n * Time in milliseconds before the copied state resets to null.\n * Set to 0 to disable auto-reset.\n * @default 2000\n */\n timeout?: number;\n /**\n * Callback function called when copy succeeds\n * @param text - The text that was copied\n */\n onSuccess?: (text: string) => void;\n /**\n * Callback function called when copy fails\n * @param error - The error that occurred\n */\n onError?: (error: Error) => void;\n}\n\n/**\n * Type for the copy function\n */\nexport type CopyFn = (text: string) => Promise<boolean>;\n\n/**\n * Return type for useCopyToClipboard hook\n * Tuple format: [copiedText, copy]\n */\nexport type UseCopyToClipboardReturn = [\n copiedText: string | null,\n copy: CopyFn\n];\n\n/**\n * Fallback copy function for browsers that don't support the Clipboard API\n * @param text - Text to copy to clipboard\n * @returns Whether the copy was successful\n */\nfunction fallbackCopyToClipboard(text: string): boolean {\n // Check if we're in a browser environment\n if (typeof document === \"undefined\") {\n return false;\n }\n\n const textarea = document.createElement(\"textarea\");\n textarea.value = text;\n\n // Make the textarea invisible but still functional\n textarea.style.cssText =\n \"position:fixed;left:-9999px;top:-9999px;opacity:0;pointer-events:none\";\n textarea.setAttribute(\"readonly\", \"\");\n textarea.setAttribute(\"aria-hidden\", \"true\");\n\n document.body.appendChild(textarea);\n\n // Select the text\n textarea.focus();\n textarea.select();\n\n // For mobile devices\n textarea.setSelectionRange(0, text.length);\n\n let success = false;\n try {\n success = document.execCommand(\"copy\");\n } catch {\n success = false;\n }\n\n document.body.removeChild(textarea);\n return success;\n}\n\n/**\n * Copies text to clipboard using the Clipboard API with fallback support.\n * Returns the copied text (or null if not copied) and a copy function.\n *\n * @param options - Configuration options for the hook\n * @returns Tuple of [copiedText, copy]\n *\n * @example\n * ```tsx\n * function CopyButton() {\n * const [copiedText, copy] = useCopyToClipboard();\n *\n * return (\n * <button onClick={() => copy(\"Hello World\")}>\n * {copiedText ? \"Copied!\" : \"Copy\"}\n * </button>\n * );\n * }\n * ```\n *\n * @example\n * ```tsx\n * // With custom timeout\n * const [copiedText, copy] = useCopyToClipboard({ timeout: 3000 });\n * ```\n *\n * @example\n * ```tsx\n * // With callbacks\n * const [copiedText, copy] = useCopyToClipboard({\n * onSuccess: (text) => console.log(`Copied: ${text}`),\n * onError: (error) => console.error(`Failed to copy: ${error.message}`),\n * });\n * ```\n *\n * @example\n * ```tsx\n * // Disable auto-reset\n * const [copiedText, copy] = useCopyToClipboard({ timeout: 0 });\n * ```\n */\nexport function useCopyToClipboard(\n options: UseCopyToClipboardOptions = {}\n): UseCopyToClipboardReturn {\n const { timeout = 2000, onSuccess, onError } = options;\n\n const [copiedText, setCopiedText] = useState<string | null>(null);\n const timeoutRef = useRef<ReturnType<typeof setTimeout> | undefined>(\n undefined\n );\n\n // Store callbacks in refs to avoid dependency issues\n const onSuccessRef = useRef(onSuccess);\n const onErrorRef = useRef(onError);\n const timeoutValueRef = useRef(timeout);\n\n // Update refs when options change\n onSuccessRef.current = onSuccess;\n onErrorRef.current = onError;\n timeoutValueRef.current = timeout;\n\n // Cleanup timeout on unmount\n useEffect(() => {\n return () => {\n if (timeoutRef.current !== undefined) {\n clearTimeout(timeoutRef.current);\n }\n };\n }, []);\n\n const copy: CopyFn = useCallback(async (text: string): Promise<boolean> => {\n // Clear any existing timeout\n if (timeoutRef.current !== undefined) {\n clearTimeout(timeoutRef.current);\n timeoutRef.current = undefined;\n }\n\n // Check for SSR\n if (typeof window === \"undefined\") {\n const error = new Error(\"Clipboard is not available in this environment\");\n onErrorRef.current?.(error);\n return false;\n }\n\n try {\n // Try the modern Clipboard API first\n if (navigator.clipboard && typeof navigator.clipboard.writeText === \"function\") {\n await navigator.clipboard.writeText(text);\n } else {\n // Fall back to execCommand\n const success = fallbackCopyToClipboard(text);\n if (!success) {\n throw new Error(\"Failed to copy text using fallback method\");\n }\n }\n\n // Success\n setCopiedText(text);\n onSuccessRef.current?.(text);\n\n // Set timeout for auto-reset if enabled\n if (timeoutValueRef.current > 0) {\n timeoutRef.current = setTimeout(() => {\n setCopiedText(null);\n timeoutRef.current = undefined;\n }, timeoutValueRef.current);\n }\n\n return true;\n } catch (err) {\n // Try fallback if Clipboard API failed\n try {\n const success = fallbackCopyToClipboard(text);\n if (success) {\n setCopiedText(text);\n onSuccessRef.current?.(text);\n\n if (timeoutValueRef.current > 0) {\n timeoutRef.current = setTimeout(() => {\n setCopiedText(null);\n timeoutRef.current = undefined;\n }, timeoutValueRef.current);\n }\n\n return true;\n }\n } catch {\n // Fallback also failed\n }\n\n // Both methods failed\n const error =\n err instanceof Error ? err : new Error(\"Failed to copy text to clipboard\");\n setCopiedText(null);\n onErrorRef.current?.(error);\n return false;\n }\n }, []);\n\n return [copiedText, copy];\n}\n"],"mappings":";AAAA,SAAS,aAAa,WAAW,QAAQ,gBAAgB;AA2CzD,SAAS,wBAAwB,MAAuB;AAEtD,MAAI,OAAO,aAAa,aAAa;AACnC,WAAO;AAAA,EACT;AAEA,QAAM,WAAW,SAAS,cAAc,UAAU;AAClD,WAAS,QAAQ;AAGjB,WAAS,MAAM,UACb;AACF,WAAS,aAAa,YAAY,EAAE;AACpC,WAAS,aAAa,eAAe,MAAM;AAE3C,WAAS,KAAK,YAAY,QAAQ;AAGlC,WAAS,MAAM;AACf,WAAS,OAAO;AAGhB,WAAS,kBAAkB,GAAG,KAAK,MAAM;AAEzC,MAAI,UAAU;AACd,MAAI;AACF,cAAU,SAAS,YAAY,MAAM;AAAA,EACvC,QAAQ;AACN,cAAU;AAAA,EACZ;AAEA,WAAS,KAAK,YAAY,QAAQ;AAClC,SAAO;AACT;AA2CO,SAAS,mBACd,UAAqC,CAAC,GACZ;AAC1B,QAAM,EAAE,UAAU,KAAM,WAAW,QAAQ,IAAI;AAE/C,QAAM,CAAC,YAAY,aAAa,IAAI,SAAwB,IAAI;AAChE,QAAM,aAAa;AAAA,IACjB;AAAA,EACF;AAGA,QAAM,eAAe,OAAO,SAAS;AACrC,QAAM,aAAa,OAAO,OAAO;AACjC,QAAM,kBAAkB,OAAO,OAAO;AAGtC,eAAa,UAAU;AACvB,aAAW,UAAU;AACrB,kBAAgB,UAAU;AAG1B,YAAU,MAAM;AACd,WAAO,MAAM;AACX,UAAI,WAAW,YAAY,QAAW;AACpC,qBAAa,WAAW,OAAO;AAAA,MACjC;AAAA,IACF;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,QAAM,OAAe,YAAY,OAAO,SAAmC;AAEzE,QAAI,WAAW,YAAY,QAAW;AACpC,mBAAa,WAAW,OAAO;AAC/B,iBAAW,UAAU;AAAA,IACvB;AAGA,QAAI,OAAO,WAAW,aAAa;AACjC,YAAM,QAAQ,IAAI,MAAM,gDAAgD;AACxE,iBAAW,UAAU,KAAK;AAC1B,aAAO;AAAA,IACT;AAEA,QAAI;AAEF,UAAI,UAAU,aAAa,OAAO,UAAU,UAAU,cAAc,YAAY;AAC9E,cAAM,UAAU,UAAU,UAAU,IAAI;AAAA,MAC1C,OAAO;AAEL,cAAM,UAAU,wBAAwB,IAAI;AAC5C,YAAI,CAAC,SAAS;AACZ,gBAAM,IAAI,MAAM,2CAA2C;AAAA,QAC7D;AAAA,MACF;AAGA,oBAAc,IAAI;AAClB,mBAAa,UAAU,IAAI;AAG3B,UAAI,gBAAgB,UAAU,GAAG;AAC/B,mBAAW,UAAU,WAAW,MAAM;AACpC,wBAAc,IAAI;AAClB,qBAAW,UAAU;AAAA,QACvB,GAAG,gBAAgB,OAAO;AAAA,MAC5B;AAEA,aAAO;AAAA,IACT,SAAS,KAAK;AAEZ,UAAI;AACF,cAAM,UAAU,wBAAwB,IAAI;AAC5C,YAAI,SAAS;AACX,wBAAc,IAAI;AAClB,uBAAa,UAAU,IAAI;AAE3B,cAAI,gBAAgB,UAAU,GAAG;AAC/B,uBAAW,UAAU,WAAW,MAAM;AACpC,4BAAc,IAAI;AAClB,yBAAW,UAAU;AAAA,YACvB,GAAG,gBAAgB,OAAO;AAAA,UAC5B;AAEA,iBAAO;AAAA,QACT;AAAA,MACF,QAAQ;AAAA,MAER;AAGA,YAAM,QACJ,eAAe,QAAQ,MAAM,IAAI,MAAM,kCAAkC;AAC3E,oBAAc,IAAI;AAClB,iBAAW,UAAU,KAAK;AAC1B,aAAO;AAAA,IACT;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,SAAO,CAAC,YAAY,IAAI;AAC1B;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usefy/use-copy-to-clipboard",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "description": "A React hook for copying text to clipboard with fallback support",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",