@ngockhoi96/ctc 0.1.0

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 @@
1
+ {"version":3,"file":"clipboard-Bs9DV14p.cjs","names":[],"sources":["../src/lib/env.ts","../src/lib/errors.ts","../src/clipboard/copy.ts","../src/clipboard/detect.ts","../src/clipboard/fallback.ts","../src/clipboard/read.ts"],"sourcesContent":["/**\n * Check if code is running in a browser environment.\n *\n * @returns `true` if `navigator` and `window` are defined\n */\nexport function isBrowser(): boolean {\n\treturn typeof navigator !== 'undefined' && typeof window !== 'undefined'\n}\n\n/**\n * Check if the current context is secure (HTTPS or localhost).\n *\n * Clipboard API requires a secure context in modern browsers.\n *\n * @returns `true` if running in a browser with a secure context\n */\nexport function isSecureContext(): boolean {\n\treturn isBrowser() && window.isSecureContext === true\n}\n","import type { BrowserUtilsError, ErrorCode, OnErrorCallback } from './types.ts'\n\n/**\n * Error codes that represent expected, recoverable failures.\n *\n * Expected errors are logged with `console.warn`.\n * Unexpected errors are logged with `console.error`.\n *\n * @internal\n */\nconst EXPECTED_ERROR_CODES = new Set<ErrorCode>([\n\t'CLIPBOARD_NOT_SUPPORTED',\n\t'INSECURE_CONTEXT',\n\t'CLIPBOARD_PERMISSION_DENIED',\n])\n\n/**\n * Create a structured browser utils error.\n *\n * @param code - Error code identifying the failure type\n * @param message - Human-readable error description\n * @param cause - Original error that caused this failure\n * @returns A structured BrowserUtilsError object\n */\nexport function createError(\n\tcode: ErrorCode,\n\tmessage: string,\n\tcause?: unknown,\n): BrowserUtilsError {\n\treturn { code, message, cause }\n}\n\n/**\n * Invoke the onError callback if provided, otherwise log a warning or error.\n *\n * Expected failures (CLIPBOARD_NOT_SUPPORTED, INSECURE_CONTEXT,\n * CLIPBOARD_PERMISSION_DENIED) are logged with `console.warn`.\n * Unexpected failures (CLIPBOARD_WRITE_FAILED, CLIPBOARD_READ_FAILED) are\n * logged with `console.error` and include the original cause for debugging.\n *\n * @param error - The structured error to handle\n * @param onError - Optional callback for error reporting\n */\nexport function handleError(\n\terror: BrowserUtilsError,\n\tonError?: OnErrorCallback,\n): void {\n\tif (onError) {\n\t\ttry {\n\t\t\tonError(error)\n\t\t} catch {\n\t\t\t// Consumer callback errors must not escape — the no-throw contract\n\t\t\t// applies to the full call stack originating from clipboard functions.\n\t\t}\n\t\treturn\n\t}\n\n\tconst prefix = '[ngockhoi96]'\n\n\tif (EXPECTED_ERROR_CODES.has(error.code)) {\n\t\tconsole.warn(`${prefix} ${error.code}: ${error.message}`)\n\t} else {\n\t\tconsole.error(`${prefix} ${error.code}: ${error.message}`, error.cause)\n\t}\n}\n","import { isBrowser, isSecureContext } from '../lib/env.ts'\nimport { createError, handleError } from '../lib/errors.ts'\nimport type { ClipboardOptions } from './types.ts'\n\n/**\n * Copy text to the clipboard using the modern Clipboard API.\n *\n * Requires a secure context (HTTPS or localhost) and must be called from\n * within a user gesture handler (click, keydown, etc.).\n *\n * @param text - The text to copy to the clipboard\n * @param options - Optional configuration including `onError` callback\n * @returns `true` on success, `false` on any failure (never throws)\n *\n * @remarks\n * **User gesture requirement:** Must be called synchronously within a user\n * gesture handler. Programmatic calls from timers or microtasks will be\n * rejected by the browser with `CLIPBOARD_PERMISSION_DENIED`.\n *\n * **Secure context:** Returns `false` on HTTP pages with `INSECURE_CONTEXT`\n * error code. Use `copyToClipboardLegacy()` for HTTP environments.\n *\n * **Safari:** Calling any async operation before `writeText()` in the same\n * microtask may break Safari's user activation window. Keep the call\n * synchronous within the click handler.\n *\n * @example\n * ```ts\n * button.addEventListener('click', async () => {\n * const success = await copyToClipboard('Hello, world!')\n * if (!success) {\n * showError('Copy failed — check HTTPS and permissions')\n * }\n * })\n * ```\n */\nexport async function copyToClipboard(\n\ttext: string,\n\toptions?: ClipboardOptions,\n): Promise<boolean> {\n\tif (!isBrowser()) {\n\t\thandleError(\n\t\t\tcreateError('CLIPBOARD_NOT_SUPPORTED', 'Not in a browser environment'),\n\t\t\toptions?.onError,\n\t\t)\n\t\treturn false\n\t}\n\n\tif (!isSecureContext()) {\n\t\thandleError(\n\t\t\tcreateError(\n\t\t\t\t'INSECURE_CONTEXT',\n\t\t\t\t'Clipboard API requires a secure context (HTTPS)',\n\t\t\t),\n\t\t\toptions?.onError,\n\t\t)\n\t\treturn false\n\t}\n\n\tif (typeof navigator.clipboard?.writeText !== 'function') {\n\t\thandleError(\n\t\t\tcreateError('CLIPBOARD_NOT_SUPPORTED', 'Clipboard API not available'),\n\t\t\toptions?.onError,\n\t\t)\n\t\treturn false\n\t}\n\n\ttry {\n\t\tawait navigator.clipboard.writeText(text)\n\t\treturn true\n\t} catch (error) {\n\t\tconst isPermissionDenied =\n\t\t\terror instanceof DOMException && error.name === 'NotAllowedError'\n\n\t\tif (isPermissionDenied) {\n\t\t\thandleError(\n\t\t\t\tcreateError(\n\t\t\t\t\t'CLIPBOARD_PERMISSION_DENIED',\n\t\t\t\t\t'Clipboard write permission denied',\n\t\t\t\t\terror,\n\t\t\t\t),\n\t\t\t\toptions?.onError,\n\t\t\t)\n\t\t} else {\n\t\t\thandleError(\n\t\t\t\tcreateError(\n\t\t\t\t\t'CLIPBOARD_WRITE_FAILED',\n\t\t\t\t\t'Failed to write to clipboard',\n\t\t\t\t\terror,\n\t\t\t\t),\n\t\t\t\toptions?.onError,\n\t\t\t)\n\t\t}\n\t\treturn false\n\t}\n}\n","import { isBrowser, isSecureContext } from '../lib/env.ts'\n\n/**\n * Check if the Clipboard API is available and usable in the current context.\n *\n * Returns `true` only when `navigator.clipboard.writeText` exists AND the\n * page is running in a secure context (HTTPS or localhost). Returns `false`\n * in SSR environments, on HTTP pages, or when the Clipboard API is absent.\n *\n * @returns `true` if clipboard write operations are supported\n *\n * @remarks\n * Permission state is not checked — a `true` result does not guarantee the\n * user has granted clipboard access. Permission denial is surfaced at call\n * time via the `CLIPBOARD_PERMISSION_DENIED` error code on `copyToClipboard`.\n *\n * @example\n * ```ts\n * if (isClipboardSupported()) {\n * await copyToClipboard(text)\n * } else {\n * copyToClipboardLegacy(text)\n * }\n * ```\n */\nexport function isClipboardSupported(): boolean {\n\treturn (\n\t\tisBrowser() &&\n\t\tisSecureContext() &&\n\t\ttypeof navigator.clipboard?.writeText === 'function'\n\t)\n}\n\n/**\n * Check if clipboard read operations are available and usable in the current context.\n *\n * Returns `true` only when `navigator.clipboard.readText` exists AND the\n * page is running in a secure context (HTTPS or localhost). Returns `false`\n * in SSR environments, on HTTP pages, or when the read API is absent.\n *\n * @returns `true` if clipboard read operations are supported\n *\n * @remarks\n * Permission state is not checked — a `true` result does not guarantee the\n * user has granted clipboard read access. Permission denial is surfaced at\n * call time via the `CLIPBOARD_PERMISSION_DENIED` error code on `readFromClipboard`.\n *\n * Firefox does not support the Permissions API `clipboard-read` query. This\n * function uses synchronous feature detection only — no async permission\n * queries are performed.\n *\n * @example\n * ```ts\n * if (isClipboardReadSupported()) {\n * const text = await readFromClipboard()\n * }\n * ```\n */\nexport function isClipboardReadSupported(): boolean {\n\treturn (\n\t\tisBrowser() &&\n\t\tisSecureContext() &&\n\t\ttypeof navigator.clipboard?.readText === 'function'\n\t)\n}\n","import { isBrowser } from '../lib/env.ts'\nimport { createError, handleError } from '../lib/errors.ts'\nimport type { ClipboardOptions } from './types.ts'\n\n/**\n * Copy text to the clipboard using the legacy `document.execCommand` API.\n *\n * Use this function when the modern Clipboard API is unavailable — for\n * example, on HTTP pages (non-HTTPS) or in browsers that do not support\n * `navigator.clipboard`. For HTTPS pages, prefer `copyToClipboard()`.\n *\n * @param text - The text to copy to the clipboard\n * @param options - Optional configuration including `onError` callback\n * @returns `true` on success, `false` on any failure (never throws)\n *\n * @remarks\n * This function uses the deprecated `document.execCommand('copy')` API,\n * which is synchronous and text-only. It temporarily creates and removes a\n * textarea element in the DOM to perform the copy operation.\n *\n * **No secure context requirement:** This function works on HTTP pages.\n * This is by design — it exists for environments where `copyToClipboard()`\n * is unavailable due to missing secure context.\n *\n * **iOS Safari:** `execCommand` copy is not reliably supported on iOS.\n * This function may return `false` on iOS Safari without a usable fallback.\n *\n * @example\n * ```ts\n * if (isClipboardSupported()) {\n * await copyToClipboard(text)\n * } else {\n * const success = copyToClipboardLegacy(text)\n * if (!success) {\n * showManualCopyInstructions()\n * }\n * }\n * ```\n */\nexport function copyToClipboardLegacy(\n\ttext: string,\n\toptions?: ClipboardOptions,\n): boolean {\n\tif (!isBrowser()) {\n\t\thandleError(\n\t\t\tcreateError('CLIPBOARD_NOT_SUPPORTED', 'Not in a browser environment'),\n\t\t\toptions?.onError,\n\t\t)\n\t\treturn false\n\t}\n\n\tif (!document.body) {\n\t\thandleError(\n\t\t\tcreateError('CLIPBOARD_NOT_SUPPORTED', 'document.body is not available'),\n\t\t\toptions?.onError,\n\t\t)\n\t\treturn false\n\t}\n\n\tconst textarea = document.createElement('textarea')\n\n\t// Position off-screen but visible to the browser — required for text selection on iOS.\n\t// display:none or visibility:hidden prevents the browser from selecting the content.\n\ttextarea.style.position = 'fixed'\n\ttextarea.style.top = '0'\n\ttextarea.style.left = '0'\n\ttextarea.style.opacity = '0'\n\ttextarea.style.pointerEvents = 'none'\n\t// Prevent iOS Safari from auto-zooming when the textarea is focused\n\ttextarea.style.fontSize = '16px'\n\t// Prevent the mobile keyboard from appearing during the copy operation\n\ttextarea.readOnly = true\n\ttextarea.value = text\n\n\tdocument.body.appendChild(textarea)\n\n\ttry {\n\t\ttextarea.focus()\n\t\t// setSelectionRange is more reliable than .select() on mobile browsers (Pitfall 3)\n\t\ttextarea.setSelectionRange(0, text.length)\n\n\t\tconst success = document.execCommand('copy')\n\n\t\tif (!success) {\n\t\t\thandleError(\n\t\t\t\tcreateError(\n\t\t\t\t\t'CLIPBOARD_WRITE_FAILED',\n\t\t\t\t\t'execCommand copy returned false',\n\t\t\t\t),\n\t\t\t\toptions?.onError,\n\t\t\t)\n\t\t\treturn false\n\t\t}\n\n\t\treturn true\n\t} catch (error) {\n\t\thandleError(\n\t\t\tcreateError(\n\t\t\t\t'CLIPBOARD_WRITE_FAILED',\n\t\t\t\t'execCommand copy threw an error',\n\t\t\t\terror,\n\t\t\t),\n\t\t\toptions?.onError,\n\t\t)\n\t\treturn false\n\t} finally {\n\t\t// Guard with isConnected before removing — prevents NotFoundError if a\n\t\t// framework (e.g., React 18 Strict Mode) or MutationObserver removed the\n\t\t// node between appendChild and here. textarea.remove() does not throw on\n\t\t// already-detached nodes. (Pitfall 4)\n\t\tif (textarea.isConnected) {\n\t\t\ttextarea.remove()\n\t\t}\n\t}\n}\n","import { isBrowser, isSecureContext } from '../lib/env.ts'\nimport { createError, handleError } from '../lib/errors.ts'\nimport type { ClipboardOptions } from './types.ts'\n\n/**\n * Read text from the clipboard using the modern Clipboard API.\n *\n * Requires a secure context (HTTPS or localhost). The browser may prompt\n * the user for permission on the first call.\n *\n * @param options - Optional configuration including `onError` callback\n * @returns The clipboard text on success, `null` on any failure (never throws)\n *\n * @remarks\n * **Permission prompt:** Chrome prompts for `clipboard-read` permission on\n * the first call. Firefox and Safari show a system-level paste prompt.\n * Permission denial is reported via `CLIPBOARD_PERMISSION_DENIED`.\n *\n * **Non-text content:** If the clipboard contains only non-text content\n * (e.g., an image), the browser throws `NotFoundError` which is reported\n * as `CLIPBOARD_READ_FAILED`.\n *\n * @example\n * ```ts\n * button.addEventListener('click', async () => {\n * const text = await readFromClipboard()\n * if (text !== null) {\n * input.value = text\n * }\n * })\n * ```\n */\nexport async function readFromClipboard(\n\toptions?: ClipboardOptions,\n): Promise<string | null> {\n\tif (!isBrowser()) {\n\t\thandleError(\n\t\t\tcreateError('CLIPBOARD_NOT_SUPPORTED', 'Not in a browser environment'),\n\t\t\toptions?.onError,\n\t\t)\n\t\treturn null\n\t}\n\n\tif (!isSecureContext()) {\n\t\thandleError(\n\t\t\tcreateError(\n\t\t\t\t'INSECURE_CONTEXT',\n\t\t\t\t'Clipboard API requires a secure context (HTTPS)',\n\t\t\t),\n\t\t\toptions?.onError,\n\t\t)\n\t\treturn null\n\t}\n\n\tif (typeof navigator.clipboard?.readText !== 'function') {\n\t\thandleError(\n\t\t\tcreateError(\n\t\t\t\t'CLIPBOARD_NOT_SUPPORTED',\n\t\t\t\t'Clipboard read API not available',\n\t\t\t),\n\t\t\toptions?.onError,\n\t\t)\n\t\treturn null\n\t}\n\n\ttry {\n\t\treturn await navigator.clipboard.readText()\n\t} catch (error) {\n\t\tconst isPermissionDenied =\n\t\t\terror instanceof DOMException && error.name === 'NotAllowedError'\n\n\t\tif (isPermissionDenied) {\n\t\t\thandleError(\n\t\t\t\tcreateError(\n\t\t\t\t\t'CLIPBOARD_PERMISSION_DENIED',\n\t\t\t\t\t'Clipboard read permission denied',\n\t\t\t\t\terror,\n\t\t\t\t),\n\t\t\t\toptions?.onError,\n\t\t\t)\n\t\t} else {\n\t\t\thandleError(\n\t\t\t\tcreateError(\n\t\t\t\t\t'CLIPBOARD_READ_FAILED',\n\t\t\t\t\t'Failed to read from clipboard',\n\t\t\t\t\terror,\n\t\t\t\t),\n\t\t\t\toptions?.onError,\n\t\t\t)\n\t\t}\n\t\treturn null\n\t}\n}\n"],"mappings":"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"}
@@ -0,0 +1,309 @@
1
+ //#region src/lib/env.ts
2
+ /**
3
+ * Check if code is running in a browser environment.
4
+ *
5
+ * @returns `true` if `navigator` and `window` are defined
6
+ */
7
+ function isBrowser() {
8
+ return typeof navigator !== "undefined" && typeof window !== "undefined";
9
+ }
10
+ /**
11
+ * Check if the current context is secure (HTTPS or localhost).
12
+ *
13
+ * Clipboard API requires a secure context in modern browsers.
14
+ *
15
+ * @returns `true` if running in a browser with a secure context
16
+ */
17
+ function isSecureContext() {
18
+ return isBrowser() && window.isSecureContext === true;
19
+ }
20
+ //#endregion
21
+ //#region src/lib/errors.ts
22
+ /**
23
+ * Error codes that represent expected, recoverable failures.
24
+ *
25
+ * Expected errors are logged with `console.warn`.
26
+ * Unexpected errors are logged with `console.error`.
27
+ *
28
+ * @internal
29
+ */
30
+ const EXPECTED_ERROR_CODES = new Set([
31
+ "CLIPBOARD_NOT_SUPPORTED",
32
+ "INSECURE_CONTEXT",
33
+ "CLIPBOARD_PERMISSION_DENIED"
34
+ ]);
35
+ /**
36
+ * Create a structured browser utils error.
37
+ *
38
+ * @param code - Error code identifying the failure type
39
+ * @param message - Human-readable error description
40
+ * @param cause - Original error that caused this failure
41
+ * @returns A structured BrowserUtilsError object
42
+ */
43
+ function createError(code, message, cause) {
44
+ return {
45
+ code,
46
+ message,
47
+ cause
48
+ };
49
+ }
50
+ /**
51
+ * Invoke the onError callback if provided, otherwise log a warning or error.
52
+ *
53
+ * Expected failures (CLIPBOARD_NOT_SUPPORTED, INSECURE_CONTEXT,
54
+ * CLIPBOARD_PERMISSION_DENIED) are logged with `console.warn`.
55
+ * Unexpected failures (CLIPBOARD_WRITE_FAILED, CLIPBOARD_READ_FAILED) are
56
+ * logged with `console.error` and include the original cause for debugging.
57
+ *
58
+ * @param error - The structured error to handle
59
+ * @param onError - Optional callback for error reporting
60
+ */
61
+ function handleError(error, onError) {
62
+ if (onError) {
63
+ try {
64
+ onError(error);
65
+ } catch {}
66
+ return;
67
+ }
68
+ const prefix = "[ngockhoi96]";
69
+ if (EXPECTED_ERROR_CODES.has(error.code)) console.warn(`${prefix} ${error.code}: ${error.message}`);
70
+ else console.error(`${prefix} ${error.code}: ${error.message}`, error.cause);
71
+ }
72
+ //#endregion
73
+ //#region src/clipboard/copy.ts
74
+ /**
75
+ * Copy text to the clipboard using the modern Clipboard API.
76
+ *
77
+ * Requires a secure context (HTTPS or localhost) and must be called from
78
+ * within a user gesture handler (click, keydown, etc.).
79
+ *
80
+ * @param text - The text to copy to the clipboard
81
+ * @param options - Optional configuration including `onError` callback
82
+ * @returns `true` on success, `false` on any failure (never throws)
83
+ *
84
+ * @remarks
85
+ * **User gesture requirement:** Must be called synchronously within a user
86
+ * gesture handler. Programmatic calls from timers or microtasks will be
87
+ * rejected by the browser with `CLIPBOARD_PERMISSION_DENIED`.
88
+ *
89
+ * **Secure context:** Returns `false` on HTTP pages with `INSECURE_CONTEXT`
90
+ * error code. Use `copyToClipboardLegacy()` for HTTP environments.
91
+ *
92
+ * **Safari:** Calling any async operation before `writeText()` in the same
93
+ * microtask may break Safari's user activation window. Keep the call
94
+ * synchronous within the click handler.
95
+ *
96
+ * @example
97
+ * ```ts
98
+ * button.addEventListener('click', async () => {
99
+ * const success = await copyToClipboard('Hello, world!')
100
+ * if (!success) {
101
+ * showError('Copy failed — check HTTPS and permissions')
102
+ * }
103
+ * })
104
+ * ```
105
+ */
106
+ async function copyToClipboard(text, options) {
107
+ if (!isBrowser()) {
108
+ handleError(createError("CLIPBOARD_NOT_SUPPORTED", "Not in a browser environment"), options?.onError);
109
+ return false;
110
+ }
111
+ if (!isSecureContext()) {
112
+ handleError(createError("INSECURE_CONTEXT", "Clipboard API requires a secure context (HTTPS)"), options?.onError);
113
+ return false;
114
+ }
115
+ if (typeof navigator.clipboard?.writeText !== "function") {
116
+ handleError(createError("CLIPBOARD_NOT_SUPPORTED", "Clipboard API not available"), options?.onError);
117
+ return false;
118
+ }
119
+ try {
120
+ await navigator.clipboard.writeText(text);
121
+ return true;
122
+ } catch (error) {
123
+ if (error instanceof DOMException && error.name === "NotAllowedError") handleError(createError("CLIPBOARD_PERMISSION_DENIED", "Clipboard write permission denied", error), options?.onError);
124
+ else handleError(createError("CLIPBOARD_WRITE_FAILED", "Failed to write to clipboard", error), options?.onError);
125
+ return false;
126
+ }
127
+ }
128
+ //#endregion
129
+ //#region src/clipboard/detect.ts
130
+ /**
131
+ * Check if the Clipboard API is available and usable in the current context.
132
+ *
133
+ * Returns `true` only when `navigator.clipboard.writeText` exists AND the
134
+ * page is running in a secure context (HTTPS or localhost). Returns `false`
135
+ * in SSR environments, on HTTP pages, or when the Clipboard API is absent.
136
+ *
137
+ * @returns `true` if clipboard write operations are supported
138
+ *
139
+ * @remarks
140
+ * Permission state is not checked — a `true` result does not guarantee the
141
+ * user has granted clipboard access. Permission denial is surfaced at call
142
+ * time via the `CLIPBOARD_PERMISSION_DENIED` error code on `copyToClipboard`.
143
+ *
144
+ * @example
145
+ * ```ts
146
+ * if (isClipboardSupported()) {
147
+ * await copyToClipboard(text)
148
+ * } else {
149
+ * copyToClipboardLegacy(text)
150
+ * }
151
+ * ```
152
+ */
153
+ function isClipboardSupported() {
154
+ return isBrowser() && isSecureContext() && typeof navigator.clipboard?.writeText === "function";
155
+ }
156
+ /**
157
+ * Check if clipboard read operations are available and usable in the current context.
158
+ *
159
+ * Returns `true` only when `navigator.clipboard.readText` exists AND the
160
+ * page is running in a secure context (HTTPS or localhost). Returns `false`
161
+ * in SSR environments, on HTTP pages, or when the read API is absent.
162
+ *
163
+ * @returns `true` if clipboard read operations are supported
164
+ *
165
+ * @remarks
166
+ * Permission state is not checked — a `true` result does not guarantee the
167
+ * user has granted clipboard read access. Permission denial is surfaced at
168
+ * call time via the `CLIPBOARD_PERMISSION_DENIED` error code on `readFromClipboard`.
169
+ *
170
+ * Firefox does not support the Permissions API `clipboard-read` query. This
171
+ * function uses synchronous feature detection only — no async permission
172
+ * queries are performed.
173
+ *
174
+ * @example
175
+ * ```ts
176
+ * if (isClipboardReadSupported()) {
177
+ * const text = await readFromClipboard()
178
+ * }
179
+ * ```
180
+ */
181
+ function isClipboardReadSupported() {
182
+ return isBrowser() && isSecureContext() && typeof navigator.clipboard?.readText === "function";
183
+ }
184
+ //#endregion
185
+ //#region src/clipboard/fallback.ts
186
+ /**
187
+ * Copy text to the clipboard using the legacy `document.execCommand` API.
188
+ *
189
+ * Use this function when the modern Clipboard API is unavailable — for
190
+ * example, on HTTP pages (non-HTTPS) or in browsers that do not support
191
+ * `navigator.clipboard`. For HTTPS pages, prefer `copyToClipboard()`.
192
+ *
193
+ * @param text - The text to copy to the clipboard
194
+ * @param options - Optional configuration including `onError` callback
195
+ * @returns `true` on success, `false` on any failure (never throws)
196
+ *
197
+ * @remarks
198
+ * This function uses the deprecated `document.execCommand('copy')` API,
199
+ * which is synchronous and text-only. It temporarily creates and removes a
200
+ * textarea element in the DOM to perform the copy operation.
201
+ *
202
+ * **No secure context requirement:** This function works on HTTP pages.
203
+ * This is by design — it exists for environments where `copyToClipboard()`
204
+ * is unavailable due to missing secure context.
205
+ *
206
+ * **iOS Safari:** `execCommand` copy is not reliably supported on iOS.
207
+ * This function may return `false` on iOS Safari without a usable fallback.
208
+ *
209
+ * @example
210
+ * ```ts
211
+ * if (isClipboardSupported()) {
212
+ * await copyToClipboard(text)
213
+ * } else {
214
+ * const success = copyToClipboardLegacy(text)
215
+ * if (!success) {
216
+ * showManualCopyInstructions()
217
+ * }
218
+ * }
219
+ * ```
220
+ */
221
+ function copyToClipboardLegacy(text, options) {
222
+ if (!isBrowser()) {
223
+ handleError(createError("CLIPBOARD_NOT_SUPPORTED", "Not in a browser environment"), options?.onError);
224
+ return false;
225
+ }
226
+ if (!document.body) {
227
+ handleError(createError("CLIPBOARD_NOT_SUPPORTED", "document.body is not available"), options?.onError);
228
+ return false;
229
+ }
230
+ const textarea = document.createElement("textarea");
231
+ textarea.style.position = "fixed";
232
+ textarea.style.top = "0";
233
+ textarea.style.left = "0";
234
+ textarea.style.opacity = "0";
235
+ textarea.style.pointerEvents = "none";
236
+ textarea.style.fontSize = "16px";
237
+ textarea.readOnly = true;
238
+ textarea.value = text;
239
+ document.body.appendChild(textarea);
240
+ try {
241
+ textarea.focus();
242
+ textarea.setSelectionRange(0, text.length);
243
+ if (!document.execCommand("copy")) {
244
+ handleError(createError("CLIPBOARD_WRITE_FAILED", "execCommand copy returned false"), options?.onError);
245
+ return false;
246
+ }
247
+ return true;
248
+ } catch (error) {
249
+ handleError(createError("CLIPBOARD_WRITE_FAILED", "execCommand copy threw an error", error), options?.onError);
250
+ return false;
251
+ } finally {
252
+ if (textarea.isConnected) textarea.remove();
253
+ }
254
+ }
255
+ //#endregion
256
+ //#region src/clipboard/read.ts
257
+ /**
258
+ * Read text from the clipboard using the modern Clipboard API.
259
+ *
260
+ * Requires a secure context (HTTPS or localhost). The browser may prompt
261
+ * the user for permission on the first call.
262
+ *
263
+ * @param options - Optional configuration including `onError` callback
264
+ * @returns The clipboard text on success, `null` on any failure (never throws)
265
+ *
266
+ * @remarks
267
+ * **Permission prompt:** Chrome prompts for `clipboard-read` permission on
268
+ * the first call. Firefox and Safari show a system-level paste prompt.
269
+ * Permission denial is reported via `CLIPBOARD_PERMISSION_DENIED`.
270
+ *
271
+ * **Non-text content:** If the clipboard contains only non-text content
272
+ * (e.g., an image), the browser throws `NotFoundError` which is reported
273
+ * as `CLIPBOARD_READ_FAILED`.
274
+ *
275
+ * @example
276
+ * ```ts
277
+ * button.addEventListener('click', async () => {
278
+ * const text = await readFromClipboard()
279
+ * if (text !== null) {
280
+ * input.value = text
281
+ * }
282
+ * })
283
+ * ```
284
+ */
285
+ async function readFromClipboard(options) {
286
+ if (!isBrowser()) {
287
+ handleError(createError("CLIPBOARD_NOT_SUPPORTED", "Not in a browser environment"), options?.onError);
288
+ return null;
289
+ }
290
+ if (!isSecureContext()) {
291
+ handleError(createError("INSECURE_CONTEXT", "Clipboard API requires a secure context (HTTPS)"), options?.onError);
292
+ return null;
293
+ }
294
+ if (typeof navigator.clipboard?.readText !== "function") {
295
+ handleError(createError("CLIPBOARD_NOT_SUPPORTED", "Clipboard read API not available"), options?.onError);
296
+ return null;
297
+ }
298
+ try {
299
+ return await navigator.clipboard.readText();
300
+ } catch (error) {
301
+ if (error instanceof DOMException && error.name === "NotAllowedError") handleError(createError("CLIPBOARD_PERMISSION_DENIED", "Clipboard read permission denied", error), options?.onError);
302
+ else handleError(createError("CLIPBOARD_READ_FAILED", "Failed to read from clipboard", error), options?.onError);
303
+ return null;
304
+ }
305
+ }
306
+ //#endregion
307
+ export { copyToClipboard as a, isClipboardSupported as i, copyToClipboardLegacy as n, isClipboardReadSupported as r, readFromClipboard as t };
308
+
309
+ //# sourceMappingURL=clipboard-OTP55cvN.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clipboard-OTP55cvN.mjs","names":[],"sources":["../src/lib/env.ts","../src/lib/errors.ts","../src/clipboard/copy.ts","../src/clipboard/detect.ts","../src/clipboard/fallback.ts","../src/clipboard/read.ts"],"sourcesContent":["/**\n * Check if code is running in a browser environment.\n *\n * @returns `true` if `navigator` and `window` are defined\n */\nexport function isBrowser(): boolean {\n\treturn typeof navigator !== 'undefined' && typeof window !== 'undefined'\n}\n\n/**\n * Check if the current context is secure (HTTPS or localhost).\n *\n * Clipboard API requires a secure context in modern browsers.\n *\n * @returns `true` if running in a browser with a secure context\n */\nexport function isSecureContext(): boolean {\n\treturn isBrowser() && window.isSecureContext === true\n}\n","import type { BrowserUtilsError, ErrorCode, OnErrorCallback } from './types.ts'\n\n/**\n * Error codes that represent expected, recoverable failures.\n *\n * Expected errors are logged with `console.warn`.\n * Unexpected errors are logged with `console.error`.\n *\n * @internal\n */\nconst EXPECTED_ERROR_CODES = new Set<ErrorCode>([\n\t'CLIPBOARD_NOT_SUPPORTED',\n\t'INSECURE_CONTEXT',\n\t'CLIPBOARD_PERMISSION_DENIED',\n])\n\n/**\n * Create a structured browser utils error.\n *\n * @param code - Error code identifying the failure type\n * @param message - Human-readable error description\n * @param cause - Original error that caused this failure\n * @returns A structured BrowserUtilsError object\n */\nexport function createError(\n\tcode: ErrorCode,\n\tmessage: string,\n\tcause?: unknown,\n): BrowserUtilsError {\n\treturn { code, message, cause }\n}\n\n/**\n * Invoke the onError callback if provided, otherwise log a warning or error.\n *\n * Expected failures (CLIPBOARD_NOT_SUPPORTED, INSECURE_CONTEXT,\n * CLIPBOARD_PERMISSION_DENIED) are logged with `console.warn`.\n * Unexpected failures (CLIPBOARD_WRITE_FAILED, CLIPBOARD_READ_FAILED) are\n * logged with `console.error` and include the original cause for debugging.\n *\n * @param error - The structured error to handle\n * @param onError - Optional callback for error reporting\n */\nexport function handleError(\n\terror: BrowserUtilsError,\n\tonError?: OnErrorCallback,\n): void {\n\tif (onError) {\n\t\ttry {\n\t\t\tonError(error)\n\t\t} catch {\n\t\t\t// Consumer callback errors must not escape — the no-throw contract\n\t\t\t// applies to the full call stack originating from clipboard functions.\n\t\t}\n\t\treturn\n\t}\n\n\tconst prefix = '[ngockhoi96]'\n\n\tif (EXPECTED_ERROR_CODES.has(error.code)) {\n\t\tconsole.warn(`${prefix} ${error.code}: ${error.message}`)\n\t} else {\n\t\tconsole.error(`${prefix} ${error.code}: ${error.message}`, error.cause)\n\t}\n}\n","import { isBrowser, isSecureContext } from '../lib/env.ts'\nimport { createError, handleError } from '../lib/errors.ts'\nimport type { ClipboardOptions } from './types.ts'\n\n/**\n * Copy text to the clipboard using the modern Clipboard API.\n *\n * Requires a secure context (HTTPS or localhost) and must be called from\n * within a user gesture handler (click, keydown, etc.).\n *\n * @param text - The text to copy to the clipboard\n * @param options - Optional configuration including `onError` callback\n * @returns `true` on success, `false` on any failure (never throws)\n *\n * @remarks\n * **User gesture requirement:** Must be called synchronously within a user\n * gesture handler. Programmatic calls from timers or microtasks will be\n * rejected by the browser with `CLIPBOARD_PERMISSION_DENIED`.\n *\n * **Secure context:** Returns `false` on HTTP pages with `INSECURE_CONTEXT`\n * error code. Use `copyToClipboardLegacy()` for HTTP environments.\n *\n * **Safari:** Calling any async operation before `writeText()` in the same\n * microtask may break Safari's user activation window. Keep the call\n * synchronous within the click handler.\n *\n * @example\n * ```ts\n * button.addEventListener('click', async () => {\n * const success = await copyToClipboard('Hello, world!')\n * if (!success) {\n * showError('Copy failed — check HTTPS and permissions')\n * }\n * })\n * ```\n */\nexport async function copyToClipboard(\n\ttext: string,\n\toptions?: ClipboardOptions,\n): Promise<boolean> {\n\tif (!isBrowser()) {\n\t\thandleError(\n\t\t\tcreateError('CLIPBOARD_NOT_SUPPORTED', 'Not in a browser environment'),\n\t\t\toptions?.onError,\n\t\t)\n\t\treturn false\n\t}\n\n\tif (!isSecureContext()) {\n\t\thandleError(\n\t\t\tcreateError(\n\t\t\t\t'INSECURE_CONTEXT',\n\t\t\t\t'Clipboard API requires a secure context (HTTPS)',\n\t\t\t),\n\t\t\toptions?.onError,\n\t\t)\n\t\treturn false\n\t}\n\n\tif (typeof navigator.clipboard?.writeText !== 'function') {\n\t\thandleError(\n\t\t\tcreateError('CLIPBOARD_NOT_SUPPORTED', 'Clipboard API not available'),\n\t\t\toptions?.onError,\n\t\t)\n\t\treturn false\n\t}\n\n\ttry {\n\t\tawait navigator.clipboard.writeText(text)\n\t\treturn true\n\t} catch (error) {\n\t\tconst isPermissionDenied =\n\t\t\terror instanceof DOMException && error.name === 'NotAllowedError'\n\n\t\tif (isPermissionDenied) {\n\t\t\thandleError(\n\t\t\t\tcreateError(\n\t\t\t\t\t'CLIPBOARD_PERMISSION_DENIED',\n\t\t\t\t\t'Clipboard write permission denied',\n\t\t\t\t\terror,\n\t\t\t\t),\n\t\t\t\toptions?.onError,\n\t\t\t)\n\t\t} else {\n\t\t\thandleError(\n\t\t\t\tcreateError(\n\t\t\t\t\t'CLIPBOARD_WRITE_FAILED',\n\t\t\t\t\t'Failed to write to clipboard',\n\t\t\t\t\terror,\n\t\t\t\t),\n\t\t\t\toptions?.onError,\n\t\t\t)\n\t\t}\n\t\treturn false\n\t}\n}\n","import { isBrowser, isSecureContext } from '../lib/env.ts'\n\n/**\n * Check if the Clipboard API is available and usable in the current context.\n *\n * Returns `true` only when `navigator.clipboard.writeText` exists AND the\n * page is running in a secure context (HTTPS or localhost). Returns `false`\n * in SSR environments, on HTTP pages, or when the Clipboard API is absent.\n *\n * @returns `true` if clipboard write operations are supported\n *\n * @remarks\n * Permission state is not checked — a `true` result does not guarantee the\n * user has granted clipboard access. Permission denial is surfaced at call\n * time via the `CLIPBOARD_PERMISSION_DENIED` error code on `copyToClipboard`.\n *\n * @example\n * ```ts\n * if (isClipboardSupported()) {\n * await copyToClipboard(text)\n * } else {\n * copyToClipboardLegacy(text)\n * }\n * ```\n */\nexport function isClipboardSupported(): boolean {\n\treturn (\n\t\tisBrowser() &&\n\t\tisSecureContext() &&\n\t\ttypeof navigator.clipboard?.writeText === 'function'\n\t)\n}\n\n/**\n * Check if clipboard read operations are available and usable in the current context.\n *\n * Returns `true` only when `navigator.clipboard.readText` exists AND the\n * page is running in a secure context (HTTPS or localhost). Returns `false`\n * in SSR environments, on HTTP pages, or when the read API is absent.\n *\n * @returns `true` if clipboard read operations are supported\n *\n * @remarks\n * Permission state is not checked — a `true` result does not guarantee the\n * user has granted clipboard read access. Permission denial is surfaced at\n * call time via the `CLIPBOARD_PERMISSION_DENIED` error code on `readFromClipboard`.\n *\n * Firefox does not support the Permissions API `clipboard-read` query. This\n * function uses synchronous feature detection only — no async permission\n * queries are performed.\n *\n * @example\n * ```ts\n * if (isClipboardReadSupported()) {\n * const text = await readFromClipboard()\n * }\n * ```\n */\nexport function isClipboardReadSupported(): boolean {\n\treturn (\n\t\tisBrowser() &&\n\t\tisSecureContext() &&\n\t\ttypeof navigator.clipboard?.readText === 'function'\n\t)\n}\n","import { isBrowser } from '../lib/env.ts'\nimport { createError, handleError } from '../lib/errors.ts'\nimport type { ClipboardOptions } from './types.ts'\n\n/**\n * Copy text to the clipboard using the legacy `document.execCommand` API.\n *\n * Use this function when the modern Clipboard API is unavailable — for\n * example, on HTTP pages (non-HTTPS) or in browsers that do not support\n * `navigator.clipboard`. For HTTPS pages, prefer `copyToClipboard()`.\n *\n * @param text - The text to copy to the clipboard\n * @param options - Optional configuration including `onError` callback\n * @returns `true` on success, `false` on any failure (never throws)\n *\n * @remarks\n * This function uses the deprecated `document.execCommand('copy')` API,\n * which is synchronous and text-only. It temporarily creates and removes a\n * textarea element in the DOM to perform the copy operation.\n *\n * **No secure context requirement:** This function works on HTTP pages.\n * This is by design — it exists for environments where `copyToClipboard()`\n * is unavailable due to missing secure context.\n *\n * **iOS Safari:** `execCommand` copy is not reliably supported on iOS.\n * This function may return `false` on iOS Safari without a usable fallback.\n *\n * @example\n * ```ts\n * if (isClipboardSupported()) {\n * await copyToClipboard(text)\n * } else {\n * const success = copyToClipboardLegacy(text)\n * if (!success) {\n * showManualCopyInstructions()\n * }\n * }\n * ```\n */\nexport function copyToClipboardLegacy(\n\ttext: string,\n\toptions?: ClipboardOptions,\n): boolean {\n\tif (!isBrowser()) {\n\t\thandleError(\n\t\t\tcreateError('CLIPBOARD_NOT_SUPPORTED', 'Not in a browser environment'),\n\t\t\toptions?.onError,\n\t\t)\n\t\treturn false\n\t}\n\n\tif (!document.body) {\n\t\thandleError(\n\t\t\tcreateError('CLIPBOARD_NOT_SUPPORTED', 'document.body is not available'),\n\t\t\toptions?.onError,\n\t\t)\n\t\treturn false\n\t}\n\n\tconst textarea = document.createElement('textarea')\n\n\t// Position off-screen but visible to the browser — required for text selection on iOS.\n\t// display:none or visibility:hidden prevents the browser from selecting the content.\n\ttextarea.style.position = 'fixed'\n\ttextarea.style.top = '0'\n\ttextarea.style.left = '0'\n\ttextarea.style.opacity = '0'\n\ttextarea.style.pointerEvents = 'none'\n\t// Prevent iOS Safari from auto-zooming when the textarea is focused\n\ttextarea.style.fontSize = '16px'\n\t// Prevent the mobile keyboard from appearing during the copy operation\n\ttextarea.readOnly = true\n\ttextarea.value = text\n\n\tdocument.body.appendChild(textarea)\n\n\ttry {\n\t\ttextarea.focus()\n\t\t// setSelectionRange is more reliable than .select() on mobile browsers (Pitfall 3)\n\t\ttextarea.setSelectionRange(0, text.length)\n\n\t\tconst success = document.execCommand('copy')\n\n\t\tif (!success) {\n\t\t\thandleError(\n\t\t\t\tcreateError(\n\t\t\t\t\t'CLIPBOARD_WRITE_FAILED',\n\t\t\t\t\t'execCommand copy returned false',\n\t\t\t\t),\n\t\t\t\toptions?.onError,\n\t\t\t)\n\t\t\treturn false\n\t\t}\n\n\t\treturn true\n\t} catch (error) {\n\t\thandleError(\n\t\t\tcreateError(\n\t\t\t\t'CLIPBOARD_WRITE_FAILED',\n\t\t\t\t'execCommand copy threw an error',\n\t\t\t\terror,\n\t\t\t),\n\t\t\toptions?.onError,\n\t\t)\n\t\treturn false\n\t} finally {\n\t\t// Guard with isConnected before removing — prevents NotFoundError if a\n\t\t// framework (e.g., React 18 Strict Mode) or MutationObserver removed the\n\t\t// node between appendChild and here. textarea.remove() does not throw on\n\t\t// already-detached nodes. (Pitfall 4)\n\t\tif (textarea.isConnected) {\n\t\t\ttextarea.remove()\n\t\t}\n\t}\n}\n","import { isBrowser, isSecureContext } from '../lib/env.ts'\nimport { createError, handleError } from '../lib/errors.ts'\nimport type { ClipboardOptions } from './types.ts'\n\n/**\n * Read text from the clipboard using the modern Clipboard API.\n *\n * Requires a secure context (HTTPS or localhost). The browser may prompt\n * the user for permission on the first call.\n *\n * @param options - Optional configuration including `onError` callback\n * @returns The clipboard text on success, `null` on any failure (never throws)\n *\n * @remarks\n * **Permission prompt:** Chrome prompts for `clipboard-read` permission on\n * the first call. Firefox and Safari show a system-level paste prompt.\n * Permission denial is reported via `CLIPBOARD_PERMISSION_DENIED`.\n *\n * **Non-text content:** If the clipboard contains only non-text content\n * (e.g., an image), the browser throws `NotFoundError` which is reported\n * as `CLIPBOARD_READ_FAILED`.\n *\n * @example\n * ```ts\n * button.addEventListener('click', async () => {\n * const text = await readFromClipboard()\n * if (text !== null) {\n * input.value = text\n * }\n * })\n * ```\n */\nexport async function readFromClipboard(\n\toptions?: ClipboardOptions,\n): Promise<string | null> {\n\tif (!isBrowser()) {\n\t\thandleError(\n\t\t\tcreateError('CLIPBOARD_NOT_SUPPORTED', 'Not in a browser environment'),\n\t\t\toptions?.onError,\n\t\t)\n\t\treturn null\n\t}\n\n\tif (!isSecureContext()) {\n\t\thandleError(\n\t\t\tcreateError(\n\t\t\t\t'INSECURE_CONTEXT',\n\t\t\t\t'Clipboard API requires a secure context (HTTPS)',\n\t\t\t),\n\t\t\toptions?.onError,\n\t\t)\n\t\treturn null\n\t}\n\n\tif (typeof navigator.clipboard?.readText !== 'function') {\n\t\thandleError(\n\t\t\tcreateError(\n\t\t\t\t'CLIPBOARD_NOT_SUPPORTED',\n\t\t\t\t'Clipboard read API not available',\n\t\t\t),\n\t\t\toptions?.onError,\n\t\t)\n\t\treturn null\n\t}\n\n\ttry {\n\t\treturn await navigator.clipboard.readText()\n\t} catch (error) {\n\t\tconst isPermissionDenied =\n\t\t\terror instanceof DOMException && error.name === 'NotAllowedError'\n\n\t\tif (isPermissionDenied) {\n\t\t\thandleError(\n\t\t\t\tcreateError(\n\t\t\t\t\t'CLIPBOARD_PERMISSION_DENIED',\n\t\t\t\t\t'Clipboard read permission denied',\n\t\t\t\t\terror,\n\t\t\t\t),\n\t\t\t\toptions?.onError,\n\t\t\t)\n\t\t} else {\n\t\t\thandleError(\n\t\t\t\tcreateError(\n\t\t\t\t\t'CLIPBOARD_READ_FAILED',\n\t\t\t\t\t'Failed to read from clipboard',\n\t\t\t\t\terror,\n\t\t\t\t),\n\t\t\t\toptions?.onError,\n\t\t\t)\n\t\t}\n\t\treturn null\n\t}\n}\n"],"mappings":";;;;;;AAKA,SAAgB,YAAqB;AACpC,QAAO,OAAO,cAAc,eAAe,OAAO,WAAW;;;;;;;;;AAU9D,SAAgB,kBAA2B;AAC1C,QAAO,WAAW,IAAI,OAAO,oBAAoB;;;;;;;;;;;;ACPlD,MAAM,uBAAuB,IAAI,IAAe;CAC/C;CACA;CACA;CACA,CAAC;;;;;;;;;AAUF,SAAgB,YACf,MACA,SACA,OACoB;AACpB,QAAO;EAAE;EAAM;EAAS;EAAO;;;;;;;;;;;;;AAchC,SAAgB,YACf,OACA,SACO;AACP,KAAI,SAAS;AACZ,MAAI;AACH,WAAQ,MAAM;UACP;AAIR;;CAGD,MAAM,SAAS;AAEf,KAAI,qBAAqB,IAAI,MAAM,KAAK,CACvC,SAAQ,KAAK,GAAG,OAAO,GAAG,MAAM,KAAK,IAAI,MAAM,UAAU;KAEzD,SAAQ,MAAM,GAAG,OAAO,GAAG,MAAM,KAAK,IAAI,MAAM,WAAW,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC1BzE,eAAsB,gBACrB,MACA,SACmB;AACnB,KAAI,CAAC,WAAW,EAAE;AACjB,cACC,YAAY,2BAA2B,+BAA+B,EACtE,SAAS,QACT;AACD,SAAO;;AAGR,KAAI,CAAC,iBAAiB,EAAE;AACvB,cACC,YACC,oBACA,kDACA,EACD,SAAS,QACT;AACD,SAAO;;AAGR,KAAI,OAAO,UAAU,WAAW,cAAc,YAAY;AACzD,cACC,YAAY,2BAA2B,8BAA8B,EACrE,SAAS,QACT;AACD,SAAO;;AAGR,KAAI;AACH,QAAM,UAAU,UAAU,UAAU,KAAK;AACzC,SAAO;UACC,OAAO;AAIf,MAFC,iBAAiB,gBAAgB,MAAM,SAAS,kBAGhD,aACC,YACC,+BACA,qCACA,MACA,EACD,SAAS,QACT;MAED,aACC,YACC,0BACA,gCACA,MACA,EACD,SAAS,QACT;AAEF,SAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACpET,SAAgB,uBAAgC;AAC/C,QACC,WAAW,IACX,iBAAiB,IACjB,OAAO,UAAU,WAAW,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6B5C,SAAgB,2BAAoC;AACnD,QACC,WAAW,IACX,iBAAiB,IACjB,OAAO,UAAU,WAAW,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACvB3C,SAAgB,sBACf,MACA,SACU;AACV,KAAI,CAAC,WAAW,EAAE;AACjB,cACC,YAAY,2BAA2B,+BAA+B,EACtE,SAAS,QACT;AACD,SAAO;;AAGR,KAAI,CAAC,SAAS,MAAM;AACnB,cACC,YAAY,2BAA2B,iCAAiC,EACxE,SAAS,QACT;AACD,SAAO;;CAGR,MAAM,WAAW,SAAS,cAAc,WAAW;AAInD,UAAS,MAAM,WAAW;AAC1B,UAAS,MAAM,MAAM;AACrB,UAAS,MAAM,OAAO;AACtB,UAAS,MAAM,UAAU;AACzB,UAAS,MAAM,gBAAgB;AAE/B,UAAS,MAAM,WAAW;AAE1B,UAAS,WAAW;AACpB,UAAS,QAAQ;AAEjB,UAAS,KAAK,YAAY,SAAS;AAEnC,KAAI;AACH,WAAS,OAAO;AAEhB,WAAS,kBAAkB,GAAG,KAAK,OAAO;AAI1C,MAAI,CAFY,SAAS,YAAY,OAAO,EAE9B;AACb,eACC,YACC,0BACA,kCACA,EACD,SAAS,QACT;AACD,UAAO;;AAGR,SAAO;UACC,OAAO;AACf,cACC,YACC,0BACA,mCACA,MACA,EACD,SAAS,QACT;AACD,SAAO;WACE;AAKT,MAAI,SAAS,YACZ,UAAS,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC/EpB,eAAsB,kBACrB,SACyB;AACzB,KAAI,CAAC,WAAW,EAAE;AACjB,cACC,YAAY,2BAA2B,+BAA+B,EACtE,SAAS,QACT;AACD,SAAO;;AAGR,KAAI,CAAC,iBAAiB,EAAE;AACvB,cACC,YACC,oBACA,kDACA,EACD,SAAS,QACT;AACD,SAAO;;AAGR,KAAI,OAAO,UAAU,WAAW,aAAa,YAAY;AACxD,cACC,YACC,2BACA,mCACA,EACD,SAAS,QACT;AACD,SAAO;;AAGR,KAAI;AACH,SAAO,MAAM,UAAU,UAAU,UAAU;UACnC,OAAO;AAIf,MAFC,iBAAiB,gBAAgB,MAAM,SAAS,kBAGhD,aACC,YACC,+BACA,oCACA,MACA,EACD,SAAS,QACT;MAED,aACC,YACC,yBACA,iCACA,MACA,EACD,SAAS,QACT;AAEF,SAAO"}
@@ -0,0 +1,193 @@
1
+ //#region src/lib/types.d.ts
2
+ /**
3
+ * Error codes for browser utility operations.
4
+ */
5
+ type ErrorCode = "CLIPBOARD_NOT_SUPPORTED" | "CLIPBOARD_PERMISSION_DENIED" | "CLIPBOARD_WRITE_FAILED" | "CLIPBOARD_READ_FAILED" | "INSECURE_CONTEXT";
6
+ /**
7
+ * Structured error for browser utility operations.
8
+ *
9
+ * All clipboard functions accept an optional `onError` callback
10
+ * that receives this type with a specific error code.
11
+ */
12
+ interface BrowserUtilsError {
13
+ code: ErrorCode;
14
+ message: string;
15
+ cause?: unknown;
16
+ }
17
+ /**
18
+ * Callback invoked when a browser utility operation fails.
19
+ *
20
+ * @param error - Structured error with code and message
21
+ */
22
+ type OnErrorCallback = (error: BrowserUtilsError) => void;
23
+ //#endregion
24
+ //#region src/clipboard/types.d.ts
25
+ /**
26
+ * Options for clipboard operations.
27
+ */
28
+ interface ClipboardOptions {
29
+ /**
30
+ * Callback invoked when the clipboard operation fails.
31
+ * Receives a structured error with a specific error code.
32
+ */
33
+ onError?: OnErrorCallback | undefined;
34
+ }
35
+ //#endregion
36
+ //#region src/clipboard/copy.d.ts
37
+ /**
38
+ * Copy text to the clipboard using the modern Clipboard API.
39
+ *
40
+ * Requires a secure context (HTTPS or localhost) and must be called from
41
+ * within a user gesture handler (click, keydown, etc.).
42
+ *
43
+ * @param text - The text to copy to the clipboard
44
+ * @param options - Optional configuration including `onError` callback
45
+ * @returns `true` on success, `false` on any failure (never throws)
46
+ *
47
+ * @remarks
48
+ * **User gesture requirement:** Must be called synchronously within a user
49
+ * gesture handler. Programmatic calls from timers or microtasks will be
50
+ * rejected by the browser with `CLIPBOARD_PERMISSION_DENIED`.
51
+ *
52
+ * **Secure context:** Returns `false` on HTTP pages with `INSECURE_CONTEXT`
53
+ * error code. Use `copyToClipboardLegacy()` for HTTP environments.
54
+ *
55
+ * **Safari:** Calling any async operation before `writeText()` in the same
56
+ * microtask may break Safari's user activation window. Keep the call
57
+ * synchronous within the click handler.
58
+ *
59
+ * @example
60
+ * ```ts
61
+ * button.addEventListener('click', async () => {
62
+ * const success = await copyToClipboard('Hello, world!')
63
+ * if (!success) {
64
+ * showError('Copy failed — check HTTPS and permissions')
65
+ * }
66
+ * })
67
+ * ```
68
+ */
69
+ declare function copyToClipboard(text: string, options?: ClipboardOptions): Promise<boolean>;
70
+ //#endregion
71
+ //#region src/clipboard/detect.d.ts
72
+ /**
73
+ * Check if the Clipboard API is available and usable in the current context.
74
+ *
75
+ * Returns `true` only when `navigator.clipboard.writeText` exists AND the
76
+ * page is running in a secure context (HTTPS or localhost). Returns `false`
77
+ * in SSR environments, on HTTP pages, or when the Clipboard API is absent.
78
+ *
79
+ * @returns `true` if clipboard write operations are supported
80
+ *
81
+ * @remarks
82
+ * Permission state is not checked — a `true` result does not guarantee the
83
+ * user has granted clipboard access. Permission denial is surfaced at call
84
+ * time via the `CLIPBOARD_PERMISSION_DENIED` error code on `copyToClipboard`.
85
+ *
86
+ * @example
87
+ * ```ts
88
+ * if (isClipboardSupported()) {
89
+ * await copyToClipboard(text)
90
+ * } else {
91
+ * copyToClipboardLegacy(text)
92
+ * }
93
+ * ```
94
+ */
95
+ declare function isClipboardSupported(): boolean;
96
+ /**
97
+ * Check if clipboard read operations are available and usable in the current context.
98
+ *
99
+ * Returns `true` only when `navigator.clipboard.readText` exists AND the
100
+ * page is running in a secure context (HTTPS or localhost). Returns `false`
101
+ * in SSR environments, on HTTP pages, or when the read API is absent.
102
+ *
103
+ * @returns `true` if clipboard read operations are supported
104
+ *
105
+ * @remarks
106
+ * Permission state is not checked — a `true` result does not guarantee the
107
+ * user has granted clipboard read access. Permission denial is surfaced at
108
+ * call time via the `CLIPBOARD_PERMISSION_DENIED` error code on `readFromClipboard`.
109
+ *
110
+ * Firefox does not support the Permissions API `clipboard-read` query. This
111
+ * function uses synchronous feature detection only — no async permission
112
+ * queries are performed.
113
+ *
114
+ * @example
115
+ * ```ts
116
+ * if (isClipboardReadSupported()) {
117
+ * const text = await readFromClipboard()
118
+ * }
119
+ * ```
120
+ */
121
+ declare function isClipboardReadSupported(): boolean;
122
+ //#endregion
123
+ //#region src/clipboard/fallback.d.ts
124
+ /**
125
+ * Copy text to the clipboard using the legacy `document.execCommand` API.
126
+ *
127
+ * Use this function when the modern Clipboard API is unavailable — for
128
+ * example, on HTTP pages (non-HTTPS) or in browsers that do not support
129
+ * `navigator.clipboard`. For HTTPS pages, prefer `copyToClipboard()`.
130
+ *
131
+ * @param text - The text to copy to the clipboard
132
+ * @param options - Optional configuration including `onError` callback
133
+ * @returns `true` on success, `false` on any failure (never throws)
134
+ *
135
+ * @remarks
136
+ * This function uses the deprecated `document.execCommand('copy')` API,
137
+ * which is synchronous and text-only. It temporarily creates and removes a
138
+ * textarea element in the DOM to perform the copy operation.
139
+ *
140
+ * **No secure context requirement:** This function works on HTTP pages.
141
+ * This is by design — it exists for environments where `copyToClipboard()`
142
+ * is unavailable due to missing secure context.
143
+ *
144
+ * **iOS Safari:** `execCommand` copy is not reliably supported on iOS.
145
+ * This function may return `false` on iOS Safari without a usable fallback.
146
+ *
147
+ * @example
148
+ * ```ts
149
+ * if (isClipboardSupported()) {
150
+ * await copyToClipboard(text)
151
+ * } else {
152
+ * const success = copyToClipboardLegacy(text)
153
+ * if (!success) {
154
+ * showManualCopyInstructions()
155
+ * }
156
+ * }
157
+ * ```
158
+ */
159
+ declare function copyToClipboardLegacy(text: string, options?: ClipboardOptions): boolean;
160
+ //#endregion
161
+ //#region src/clipboard/read.d.ts
162
+ /**
163
+ * Read text from the clipboard using the modern Clipboard API.
164
+ *
165
+ * Requires a secure context (HTTPS or localhost). The browser may prompt
166
+ * the user for permission on the first call.
167
+ *
168
+ * @param options - Optional configuration including `onError` callback
169
+ * @returns The clipboard text on success, `null` on any failure (never throws)
170
+ *
171
+ * @remarks
172
+ * **Permission prompt:** Chrome prompts for `clipboard-read` permission on
173
+ * the first call. Firefox and Safari show a system-level paste prompt.
174
+ * Permission denial is reported via `CLIPBOARD_PERMISSION_DENIED`.
175
+ *
176
+ * **Non-text content:** If the clipboard contains only non-text content
177
+ * (e.g., an image), the browser throws `NotFoundError` which is reported
178
+ * as `CLIPBOARD_READ_FAILED`.
179
+ *
180
+ * @example
181
+ * ```ts
182
+ * button.addEventListener('click', async () => {
183
+ * const text = await readFromClipboard()
184
+ * if (text !== null) {
185
+ * input.value = text
186
+ * }
187
+ * })
188
+ * ```
189
+ */
190
+ declare function readFromClipboard(options?: ClipboardOptions): Promise<string | null>;
191
+ //#endregion
192
+ export { copyToClipboard as a, ErrorCode as c, isClipboardSupported as i, OnErrorCallback as l, copyToClipboardLegacy as n, ClipboardOptions as o, isClipboardReadSupported as r, BrowserUtilsError as s, readFromClipboard as t };
193
+ //# sourceMappingURL=index-B7FFRy7e.d.mts.map