@usefy/use-copy-to-clipboard 0.0.7

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.
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Options for useCopyToClipboard hook
3
+ */
4
+ interface UseCopyToClipboardOptions {
5
+ /**
6
+ * Time in milliseconds before the copied state resets to null.
7
+ * Set to 0 to disable auto-reset.
8
+ * @default 2000
9
+ */
10
+ timeout?: number;
11
+ /**
12
+ * Callback function called when copy succeeds
13
+ * @param text - The text that was copied
14
+ */
15
+ onSuccess?: (text: string) => void;
16
+ /**
17
+ * Callback function called when copy fails
18
+ * @param error - The error that occurred
19
+ */
20
+ onError?: (error: Error) => void;
21
+ }
22
+ /**
23
+ * Type for the copy function
24
+ */
25
+ type CopyFn = (text: string) => Promise<boolean>;
26
+ /**
27
+ * Return type for useCopyToClipboard hook
28
+ * Tuple format: [copiedText, copy]
29
+ */
30
+ type UseCopyToClipboardReturn = [
31
+ copiedText: string | null,
32
+ copy: CopyFn
33
+ ];
34
+ /**
35
+ * Copies text to clipboard using the Clipboard API with fallback support.
36
+ * Returns the copied text (or null if not copied) and a copy function.
37
+ *
38
+ * @param options - Configuration options for the hook
39
+ * @returns Tuple of [copiedText, copy]
40
+ *
41
+ * @example
42
+ * ```tsx
43
+ * function CopyButton() {
44
+ * const [copiedText, copy] = useCopyToClipboard();
45
+ *
46
+ * return (
47
+ * <button onClick={() => copy("Hello World")}>
48
+ * {copiedText ? "Copied!" : "Copy"}
49
+ * </button>
50
+ * );
51
+ * }
52
+ * ```
53
+ *
54
+ * @example
55
+ * ```tsx
56
+ * // With custom timeout
57
+ * const [copiedText, copy] = useCopyToClipboard({ timeout: 3000 });
58
+ * ```
59
+ *
60
+ * @example
61
+ * ```tsx
62
+ * // With callbacks
63
+ * const [copiedText, copy] = useCopyToClipboard({
64
+ * onSuccess: (text) => console.log(`Copied: ${text}`),
65
+ * onError: (error) => console.error(`Failed to copy: ${error.message}`),
66
+ * });
67
+ * ```
68
+ *
69
+ * @example
70
+ * ```tsx
71
+ * // Disable auto-reset
72
+ * const [copiedText, copy] = useCopyToClipboard({ timeout: 0 });
73
+ * ```
74
+ */
75
+ declare function useCopyToClipboard(options?: UseCopyToClipboardOptions): UseCopyToClipboardReturn;
76
+
77
+ export { type CopyFn, type UseCopyToClipboardOptions, type UseCopyToClipboardReturn, useCopyToClipboard };
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Options for useCopyToClipboard hook
3
+ */
4
+ interface UseCopyToClipboardOptions {
5
+ /**
6
+ * Time in milliseconds before the copied state resets to null.
7
+ * Set to 0 to disable auto-reset.
8
+ * @default 2000
9
+ */
10
+ timeout?: number;
11
+ /**
12
+ * Callback function called when copy succeeds
13
+ * @param text - The text that was copied
14
+ */
15
+ onSuccess?: (text: string) => void;
16
+ /**
17
+ * Callback function called when copy fails
18
+ * @param error - The error that occurred
19
+ */
20
+ onError?: (error: Error) => void;
21
+ }
22
+ /**
23
+ * Type for the copy function
24
+ */
25
+ type CopyFn = (text: string) => Promise<boolean>;
26
+ /**
27
+ * Return type for useCopyToClipboard hook
28
+ * Tuple format: [copiedText, copy]
29
+ */
30
+ type UseCopyToClipboardReturn = [
31
+ copiedText: string | null,
32
+ copy: CopyFn
33
+ ];
34
+ /**
35
+ * Copies text to clipboard using the Clipboard API with fallback support.
36
+ * Returns the copied text (or null if not copied) and a copy function.
37
+ *
38
+ * @param options - Configuration options for the hook
39
+ * @returns Tuple of [copiedText, copy]
40
+ *
41
+ * @example
42
+ * ```tsx
43
+ * function CopyButton() {
44
+ * const [copiedText, copy] = useCopyToClipboard();
45
+ *
46
+ * return (
47
+ * <button onClick={() => copy("Hello World")}>
48
+ * {copiedText ? "Copied!" : "Copy"}
49
+ * </button>
50
+ * );
51
+ * }
52
+ * ```
53
+ *
54
+ * @example
55
+ * ```tsx
56
+ * // With custom timeout
57
+ * const [copiedText, copy] = useCopyToClipboard({ timeout: 3000 });
58
+ * ```
59
+ *
60
+ * @example
61
+ * ```tsx
62
+ * // With callbacks
63
+ * const [copiedText, copy] = useCopyToClipboard({
64
+ * onSuccess: (text) => console.log(`Copied: ${text}`),
65
+ * onError: (error) => console.error(`Failed to copy: ${error.message}`),
66
+ * });
67
+ * ```
68
+ *
69
+ * @example
70
+ * ```tsx
71
+ * // Disable auto-reset
72
+ * const [copiedText, copy] = useCopyToClipboard({ timeout: 0 });
73
+ * ```
74
+ */
75
+ declare function useCopyToClipboard(options?: UseCopyToClipboardOptions): UseCopyToClipboardReturn;
76
+
77
+ export { type CopyFn, type UseCopyToClipboardOptions, type UseCopyToClipboardReturn, useCopyToClipboard };
package/dist/index.js ADDED
@@ -0,0 +1,126 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ useCopyToClipboard: () => useCopyToClipboard
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+
27
+ // src/useCopyToClipboard.ts
28
+ var import_react = require("react");
29
+ function fallbackCopyToClipboard(text) {
30
+ if (typeof document === "undefined") {
31
+ return false;
32
+ }
33
+ const textarea = document.createElement("textarea");
34
+ textarea.value = text;
35
+ textarea.style.cssText = "position:fixed;left:-9999px;top:-9999px;opacity:0;pointer-events:none";
36
+ textarea.setAttribute("readonly", "");
37
+ textarea.setAttribute("aria-hidden", "true");
38
+ document.body.appendChild(textarea);
39
+ textarea.focus();
40
+ textarea.select();
41
+ textarea.setSelectionRange(0, text.length);
42
+ let success = false;
43
+ try {
44
+ success = document.execCommand("copy");
45
+ } catch {
46
+ success = false;
47
+ }
48
+ document.body.removeChild(textarea);
49
+ return success;
50
+ }
51
+ function useCopyToClipboard(options = {}) {
52
+ const { timeout = 2e3, onSuccess, onError } = options;
53
+ const [copiedText, setCopiedText] = (0, import_react.useState)(null);
54
+ const timeoutRef = (0, import_react.useRef)(
55
+ void 0
56
+ );
57
+ const onSuccessRef = (0, import_react.useRef)(onSuccess);
58
+ const onErrorRef = (0, import_react.useRef)(onError);
59
+ const timeoutValueRef = (0, import_react.useRef)(timeout);
60
+ onSuccessRef.current = onSuccess;
61
+ onErrorRef.current = onError;
62
+ timeoutValueRef.current = timeout;
63
+ (0, import_react.useEffect)(() => {
64
+ return () => {
65
+ if (timeoutRef.current !== void 0) {
66
+ clearTimeout(timeoutRef.current);
67
+ }
68
+ };
69
+ }, []);
70
+ const copy = (0, import_react.useCallback)(async (text) => {
71
+ if (timeoutRef.current !== void 0) {
72
+ clearTimeout(timeoutRef.current);
73
+ timeoutRef.current = void 0;
74
+ }
75
+ if (typeof window === "undefined") {
76
+ const error = new Error("Clipboard is not available in this environment");
77
+ onErrorRef.current?.(error);
78
+ return false;
79
+ }
80
+ try {
81
+ if (navigator.clipboard && typeof navigator.clipboard.writeText === "function") {
82
+ await navigator.clipboard.writeText(text);
83
+ } else {
84
+ const success = fallbackCopyToClipboard(text);
85
+ if (!success) {
86
+ throw new Error("Failed to copy text using fallback method");
87
+ }
88
+ }
89
+ setCopiedText(text);
90
+ onSuccessRef.current?.(text);
91
+ if (timeoutValueRef.current > 0) {
92
+ timeoutRef.current = setTimeout(() => {
93
+ setCopiedText(null);
94
+ timeoutRef.current = void 0;
95
+ }, timeoutValueRef.current);
96
+ }
97
+ return true;
98
+ } catch (err) {
99
+ try {
100
+ const success = fallbackCopyToClipboard(text);
101
+ if (success) {
102
+ setCopiedText(text);
103
+ onSuccessRef.current?.(text);
104
+ if (timeoutValueRef.current > 0) {
105
+ timeoutRef.current = setTimeout(() => {
106
+ setCopiedText(null);
107
+ timeoutRef.current = void 0;
108
+ }, timeoutValueRef.current);
109
+ }
110
+ return true;
111
+ }
112
+ } catch {
113
+ }
114
+ const error = err instanceof Error ? err : new Error("Failed to copy text to clipboard");
115
+ setCopiedText(null);
116
+ onErrorRef.current?.(error);
117
+ return false;
118
+ }
119
+ }, []);
120
+ return [copiedText, copy];
121
+ }
122
+ // Annotate the CommonJS export names for ESM import in node:
123
+ 0 && (module.exports = {
124
+ useCopyToClipboard
125
+ });
126
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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":[]}
package/dist/index.mjs ADDED
@@ -0,0 +1,99 @@
1
+ // src/useCopyToClipboard.ts
2
+ import { useCallback, useEffect, useRef, useState } from "react";
3
+ function fallbackCopyToClipboard(text) {
4
+ if (typeof document === "undefined") {
5
+ return false;
6
+ }
7
+ const textarea = document.createElement("textarea");
8
+ textarea.value = text;
9
+ textarea.style.cssText = "position:fixed;left:-9999px;top:-9999px;opacity:0;pointer-events:none";
10
+ textarea.setAttribute("readonly", "");
11
+ textarea.setAttribute("aria-hidden", "true");
12
+ document.body.appendChild(textarea);
13
+ textarea.focus();
14
+ textarea.select();
15
+ textarea.setSelectionRange(0, text.length);
16
+ let success = false;
17
+ try {
18
+ success = document.execCommand("copy");
19
+ } catch {
20
+ success = false;
21
+ }
22
+ document.body.removeChild(textarea);
23
+ return success;
24
+ }
25
+ function useCopyToClipboard(options = {}) {
26
+ const { timeout = 2e3, onSuccess, onError } = options;
27
+ const [copiedText, setCopiedText] = useState(null);
28
+ const timeoutRef = useRef(
29
+ void 0
30
+ );
31
+ const onSuccessRef = useRef(onSuccess);
32
+ const onErrorRef = useRef(onError);
33
+ const timeoutValueRef = useRef(timeout);
34
+ onSuccessRef.current = onSuccess;
35
+ onErrorRef.current = onError;
36
+ timeoutValueRef.current = timeout;
37
+ useEffect(() => {
38
+ return () => {
39
+ if (timeoutRef.current !== void 0) {
40
+ clearTimeout(timeoutRef.current);
41
+ }
42
+ };
43
+ }, []);
44
+ const copy = useCallback(async (text) => {
45
+ if (timeoutRef.current !== void 0) {
46
+ clearTimeout(timeoutRef.current);
47
+ timeoutRef.current = void 0;
48
+ }
49
+ if (typeof window === "undefined") {
50
+ const error = new Error("Clipboard is not available in this environment");
51
+ onErrorRef.current?.(error);
52
+ return false;
53
+ }
54
+ try {
55
+ if (navigator.clipboard && typeof navigator.clipboard.writeText === "function") {
56
+ await navigator.clipboard.writeText(text);
57
+ } else {
58
+ const success = fallbackCopyToClipboard(text);
59
+ if (!success) {
60
+ throw new Error("Failed to copy text using fallback method");
61
+ }
62
+ }
63
+ setCopiedText(text);
64
+ onSuccessRef.current?.(text);
65
+ if (timeoutValueRef.current > 0) {
66
+ timeoutRef.current = setTimeout(() => {
67
+ setCopiedText(null);
68
+ timeoutRef.current = void 0;
69
+ }, timeoutValueRef.current);
70
+ }
71
+ return true;
72
+ } catch (err) {
73
+ try {
74
+ const success = fallbackCopyToClipboard(text);
75
+ if (success) {
76
+ setCopiedText(text);
77
+ onSuccessRef.current?.(text);
78
+ if (timeoutValueRef.current > 0) {
79
+ timeoutRef.current = setTimeout(() => {
80
+ setCopiedText(null);
81
+ timeoutRef.current = void 0;
82
+ }, timeoutValueRef.current);
83
+ }
84
+ return true;
85
+ }
86
+ } catch {
87
+ }
88
+ const error = err instanceof Error ? err : new Error("Failed to copy text to clipboard");
89
+ setCopiedText(null);
90
+ onErrorRef.current?.(error);
91
+ return false;
92
+ }
93
+ }, []);
94
+ return [copiedText, copy];
95
+ }
96
+ export {
97
+ useCopyToClipboard
98
+ };
99
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +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":[]}
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@usefy/use-copy-to-clipboard",
3
+ "version": "0.0.7",
4
+ "description": "A React hook for copying text to clipboard with fallback support",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "sideEffects": false,
19
+ "peerDependencies": {
20
+ "react": "^18.0.0 || ^19.0.0"
21
+ },
22
+ "devDependencies": {
23
+ "@testing-library/jest-dom": "^6.9.1",
24
+ "@testing-library/react": "^16.3.1",
25
+ "@testing-library/user-event": "^14.6.1",
26
+ "@types/react": "^19.0.0",
27
+ "jsdom": "^27.3.0",
28
+ "react": "^19.0.0",
29
+ "rimraf": "^6.0.1",
30
+ "tsup": "^8.0.0",
31
+ "typescript": "^5.0.0",
32
+ "vitest": "^4.0.16"
33
+ },
34
+ "publishConfig": {
35
+ "access": "public"
36
+ },
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "https://github.com/geon0529/usefy.git",
40
+ "directory": "packages/use-copy-to-clipboard"
41
+ },
42
+ "license": "MIT",
43
+ "keywords": [
44
+ "react",
45
+ "hooks",
46
+ "clipboard",
47
+ "copy",
48
+ "copy-to-clipboard"
49
+ ],
50
+ "scripts": {
51
+ "build": "tsup",
52
+ "dev": "tsup --watch",
53
+ "test": "vitest run",
54
+ "test:watch": "vitest",
55
+ "typecheck": "tsc --noEmit",
56
+ "clean": "rimraf dist"
57
+ }
58
+ }