@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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ngockhoi96 contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,302 @@
1
+ # @ngockhoi96/ctc
2
+
3
+ Modular, tree-shakeable browser utilities library. Zero dependencies, framework-agnostic.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@ngockhoi96/ctc)](https://www.npmjs.com/package/@ngockhoi96/ctc)
6
+ [![Bundle Size](https://img.shields.io/bundlephobia/minzip/@ngockhoi96/ctc)](https://bundlephobia.com/package/@ngockhoi96/ctc)
7
+ [![CI](https://github.com/anIcedAntFA/cttc/actions/workflows/ci.yml/badge.svg)](https://github.com/anIcedAntFA/cttc/actions/workflows/ci.yml)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
9
+
10
+ > All functions are SSR-safe -- safe to import in Next.js, Nuxt, or any server-side environment without crashing.
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ # pnpm
16
+ pnpm add @ngockhoi96/ctc
17
+
18
+ # npm
19
+ npm install @ngockhoi96/ctc
20
+
21
+ # yarn
22
+ yarn add @ngockhoi96/ctc
23
+ ```
24
+
25
+ ## Quick Start
26
+
27
+ ```typescript
28
+ import { copyToClipboard, isClipboardSupported } from '@ngockhoi96/ctc/clipboard'
29
+
30
+ button.addEventListener('click', async () => {
31
+ if (isClipboardSupported()) {
32
+ const success = await copyToClipboard('Hello, world!')
33
+ console.log(success ? 'Copied!' : 'Copy failed')
34
+ }
35
+ })
36
+ ```
37
+
38
+ ## API Reference
39
+
40
+ ### `copyToClipboard(text, options?)`
41
+
42
+ Copy text to the clipboard using the modern Clipboard API.
43
+
44
+ ```typescript
45
+ function copyToClipboard(text: string, options?: ClipboardOptions): Promise<boolean>
46
+ ```
47
+
48
+ **Returns:** `true` on success, `false` on any failure (never throws).
49
+
50
+ **Browser support:** Requires HTTPS (secure context) and a user gesture (click, keydown, etc.). Chrome 66+, Firefox 63+, Safari 13.1+.
51
+
52
+ ```typescript
53
+ import { copyToClipboard } from '@ngockhoi96/ctc/clipboard'
54
+
55
+ button.addEventListener('click', async () => {
56
+ const success = await copyToClipboard('Hello, world!')
57
+ if (!success) {
58
+ showError('Copy failed -- check HTTPS and permissions')
59
+ }
60
+ })
61
+ ```
62
+
63
+ **Remarks:**
64
+
65
+ - Must be called synchronously within a user gesture handler. Programmatic calls from timers or microtasks will be rejected by the browser with `CLIPBOARD_PERMISSION_DENIED`.
66
+ - Returns `false` on HTTP pages with `INSECURE_CONTEXT` error code. Use `copyToClipboardLegacy()` for HTTP environments.
67
+ - On Safari, calling any async operation before `writeText()` in the same microtask may break the user activation window. Keep the call synchronous within the click handler.
68
+
69
+ ---
70
+
71
+ ### `readFromClipboard(options?)`
72
+
73
+ Read text from the clipboard using the modern Clipboard API.
74
+
75
+ ```typescript
76
+ function readFromClipboard(options?: ClipboardOptions): Promise<string | null>
77
+ ```
78
+
79
+ **Returns:** The clipboard text string on success, `null` on failure (never throws).
80
+
81
+ **Browser support:** Requires HTTPS (secure context) and a user gesture. May trigger a permission prompt on first call. Chrome 66+, Firefox 63+, Safari 13.1+.
82
+
83
+ ```typescript
84
+ import { readFromClipboard } from '@ngockhoi96/ctc/clipboard'
85
+
86
+ pasteButton.addEventListener('click', async () => {
87
+ const text = await readFromClipboard()
88
+ if (text !== null) {
89
+ input.value = text
90
+ }
91
+ })
92
+ ```
93
+
94
+ **Remarks:**
95
+
96
+ - Chrome prompts for `clipboard-read` permission on the first call. Firefox and Safari show a system-level paste prompt.
97
+ - If the clipboard contains only non-text content (e.g., an image), the read operation fails with `CLIPBOARD_READ_FAILED`.
98
+
99
+ ---
100
+
101
+ ### `isClipboardSupported()`
102
+
103
+ Check if the Clipboard API is available and usable for write operations in the current context.
104
+
105
+ ```typescript
106
+ function isClipboardSupported(): boolean
107
+ ```
108
+
109
+ **Returns:** `true` if clipboard write operations are supported. Returns `false` in SSR environments, on HTTP pages, or when the Clipboard API is absent.
110
+
111
+ **Browser support:** Synchronous check, no permissions needed. Chrome 66+, Firefox 63+, Safari 13.1+. Returns `false` on HTTP and in SSR.
112
+
113
+ ```typescript
114
+ import { isClipboardSupported, copyToClipboardLegacy, copyToClipboard } from '@ngockhoi96/ctc/clipboard'
115
+
116
+ if (isClipboardSupported()) {
117
+ await copyToClipboard(text)
118
+ } else {
119
+ copyToClipboardLegacy(text)
120
+ }
121
+ ```
122
+
123
+ **Remarks:**
124
+
125
+ - Permission state is not checked. A `true` result does not guarantee the user has granted clipboard access. Permission denial is surfaced at call time via `CLIPBOARD_PERMISSION_DENIED`.
126
+
127
+ ---
128
+
129
+ ### `isClipboardReadSupported()`
130
+
131
+ Check if clipboard read operations are available and usable in the current context.
132
+
133
+ ```typescript
134
+ function isClipboardReadSupported(): boolean
135
+ ```
136
+
137
+ **Returns:** `true` if clipboard read operations are supported. Returns `false` in SSR environments, on HTTP pages, or when the read API is absent.
138
+
139
+ **Browser support:** Synchronous check, no permissions needed. Chrome 66+, Firefox 63+, Safari 13.1+. Returns `false` on HTTP and in SSR.
140
+
141
+ ```typescript
142
+ import { isClipboardReadSupported, readFromClipboard } from '@ngockhoi96/ctc/clipboard'
143
+
144
+ if (isClipboardReadSupported()) {
145
+ const text = await readFromClipboard()
146
+ }
147
+ ```
148
+
149
+ **Remarks:**
150
+
151
+ - Firefox does not support the Permissions API `clipboard-read` query. This function uses synchronous feature detection only.
152
+
153
+ ---
154
+
155
+ ### `copyToClipboardLegacy(text, options?)`
156
+
157
+ Copy text to the clipboard using the deprecated `document.execCommand('copy')` API.
158
+
159
+ ```typescript
160
+ function copyToClipboardLegacy(text: string, options?: ClipboardOptions): boolean
161
+ ```
162
+
163
+ **Returns:** `true` on success, `false` on failure (synchronous, never throws).
164
+
165
+ **Browser support:** Works on HTTP pages via deprecated `execCommand`. Supported in all desktop browsers. Not reliable on iOS Safari. Use only when the modern Clipboard API is unavailable.
166
+
167
+ ```typescript
168
+ import { isClipboardSupported, copyToClipboard, copyToClipboardLegacy } from '@ngockhoi96/ctc/clipboard'
169
+
170
+ async function copyText(text: string): Promise<boolean> {
171
+ if (isClipboardSupported()) {
172
+ return copyToClipboard(text)
173
+ }
174
+ return copyToClipboardLegacy(text)
175
+ }
176
+ ```
177
+
178
+ **Remarks:**
179
+
180
+ - Uses the deprecated `document.execCommand('copy')` API, which is synchronous and text-only.
181
+ - No secure context requirement. This function exists for environments where `copyToClipboard()` is unavailable due to HTTP.
182
+ - Temporarily creates and removes a textarea element in the DOM to perform the copy operation.
183
+ - `execCommand` copy is not reliably supported on iOS Safari. This function may return `false` on iOS Safari without a usable fallback.
184
+
185
+ ## Error Handling
186
+
187
+ All clipboard functions accept an optional `onError` callback that receives a structured `BrowserUtilsError` object. Functions never throw -- they return `false` or `null` on failure.
188
+
189
+ ### `BrowserUtilsError`
190
+
191
+ ```typescript
192
+ interface BrowserUtilsError {
193
+ code: ErrorCode
194
+ message: string
195
+ cause?: unknown
196
+ }
197
+
198
+ type ErrorCode =
199
+ | 'CLIPBOARD_NOT_SUPPORTED'
200
+ | 'CLIPBOARD_PERMISSION_DENIED'
201
+ | 'CLIPBOARD_WRITE_FAILED'
202
+ | 'CLIPBOARD_READ_FAILED'
203
+ | 'INSECURE_CONTEXT'
204
+ ```
205
+
206
+ ### `ClipboardOptions`
207
+
208
+ ```typescript
209
+ interface ClipboardOptions {
210
+ onError?: (error: BrowserUtilsError) => void
211
+ }
212
+ ```
213
+
214
+ ### Error Codes
215
+
216
+ | Code | Description |
217
+ |------|-------------|
218
+ | `CLIPBOARD_NOT_SUPPORTED` | Clipboard API is unavailable (SSR environment, very old browser, or `navigator.clipboard` missing) |
219
+ | `INSECURE_CONTEXT` | Page is served over HTTP (not HTTPS). The Clipboard API requires a secure context. Use `copyToClipboardLegacy()` instead |
220
+ | `CLIPBOARD_PERMISSION_DENIED` | User denied clipboard permission, or the call was made outside a user gesture |
221
+ | `CLIPBOARD_WRITE_FAILED` | Write operation failed for an unexpected reason |
222
+ | `CLIPBOARD_READ_FAILED` | Read operation failed for an unexpected reason |
223
+
224
+ ### Example: Using `onError`
225
+
226
+ ```typescript
227
+ import { copyToClipboard } from '@ngockhoi96/ctc/clipboard'
228
+
229
+ await copyToClipboard('text', {
230
+ onError: (err) => {
231
+ switch (err.code) {
232
+ case 'CLIPBOARD_PERMISSION_DENIED':
233
+ showPermissionPrompt()
234
+ break
235
+ case 'INSECURE_CONTEXT':
236
+ // Fall back to legacy method on HTTP pages
237
+ copyToClipboardLegacy(text)
238
+ break
239
+ default:
240
+ console.error('Clipboard error:', err.message)
241
+ }
242
+ }
243
+ })
244
+ ```
245
+
246
+ Without `onError`, expected failures (`CLIPBOARD_NOT_SUPPORTED`, `INSECURE_CONTEXT`, `CLIPBOARD_PERMISSION_DENIED`) are logged with `console.warn`. Unexpected failures (`CLIPBOARD_WRITE_FAILED`, `CLIPBOARD_READ_FAILED`) are logged with `console.error`.
247
+
248
+ ## Browser Support
249
+
250
+ | Function | Chrome | Firefox | Safari | HTTP Support |
251
+ |----------|--------|---------|--------|--------------|
252
+ | `copyToClipboard` | 66+ | 63+ | 13.1+ | No (use legacy) |
253
+ | `readFromClipboard` | 66+ | 63+ | 13.1+ | No |
254
+ | `isClipboardSupported` | 66+ | 63+ | 13.1+ | Returns `false` |
255
+ | `isClipboardReadSupported` | 66+ | 63+ | 13.1+ | Returns `false` |
256
+ | `copyToClipboardLegacy` | All | All | Partial | Yes |
257
+
258
+ All functions require ES2020+ (>95% global browser support).
259
+
260
+ All functions are SSR-safe and will return `false` or `null` when called in a server-side environment (Node.js, Deno, Bun) without crashing.
261
+
262
+ ## Contributing
263
+
264
+ Contributions are welcome! Please open an [issue](https://github.com/anIcedAntFA/cttc/issues) for bug reports or feature requests.
265
+
266
+ ### Development
267
+
268
+ ```bash
269
+ # Install dependencies
270
+ pnpm install
271
+
272
+ # Run checks before committing
273
+ pnpm lint && pnpm test && pnpm build
274
+ ```
275
+
276
+ ### Commit Format
277
+
278
+ This project uses [conventional commits](https://www.conventionalcommits.org/) enforced by commitlint:
279
+
280
+ ```
281
+ feat(clipboard): add new utility function
282
+ fix(clipboard): handle edge case in Safari
283
+ chore: update dependencies
284
+ ```
285
+
286
+ ### Versioning
287
+
288
+ This library follows [semantic versioning](https://semver.org/). While pre-1.0 (`0.x.y`), minor version bumps may include breaking changes. Pin to a specific minor version if stability is critical:
289
+
290
+ ```bash
291
+ pnpm add @ngockhoi96/ctc@~0.1.0
292
+ ```
293
+
294
+ ### Publishing
295
+
296
+ Releases are automated via [changesets](https://github.com/changesets/changesets). Merging a changeset to `main` triggers a Version PR. Merging the Version PR publishes to npm automatically.
297
+
298
+ Maintainers: an `NPM_TOKEN` secret must be configured in GitHub repository settings for automated npm publishing.
299
+
300
+ ## License
301
+
302
+ [MIT](./LICENSE)
@@ -0,0 +1,7 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_clipboard = require("../clipboard-Bs9DV14p.cjs");
3
+ exports.copyToClipboard = require_clipboard.copyToClipboard;
4
+ exports.copyToClipboardLegacy = require_clipboard.copyToClipboardLegacy;
5
+ exports.isClipboardReadSupported = require_clipboard.isClipboardReadSupported;
6
+ exports.isClipboardSupported = require_clipboard.isClipboardSupported;
7
+ exports.readFromClipboard = require_clipboard.readFromClipboard;
@@ -0,0 +1,2 @@
1
+ import { a as copyToClipboard, c as ErrorCode, i as isClipboardSupported, l as OnErrorCallback, n as copyToClipboardLegacy, o as ClipboardOptions, r as isClipboardReadSupported, s as BrowserUtilsError, t as readFromClipboard } from "../index-CHCHjqWe.cjs";
2
+ export { BrowserUtilsError, ClipboardOptions, ErrorCode, OnErrorCallback, copyToClipboard, copyToClipboardLegacy, isClipboardReadSupported, isClipboardSupported, readFromClipboard };
@@ -0,0 +1,2 @@
1
+ import { a as copyToClipboard, c as ErrorCode, i as isClipboardSupported, l as OnErrorCallback, n as copyToClipboardLegacy, o as ClipboardOptions, r as isClipboardReadSupported, s as BrowserUtilsError, t as readFromClipboard } from "../index-B7FFRy7e.mjs";
2
+ export { BrowserUtilsError, ClipboardOptions, ErrorCode, OnErrorCallback, copyToClipboard, copyToClipboardLegacy, isClipboardReadSupported, isClipboardSupported, readFromClipboard };
@@ -0,0 +1,2 @@
1
+ import { a as copyToClipboard, i as isClipboardSupported, n as copyToClipboardLegacy, r as isClipboardReadSupported, t as readFromClipboard } from "../clipboard-OTP55cvN.mjs";
2
+ export { copyToClipboard, copyToClipboardLegacy, isClipboardReadSupported, isClipboardSupported, readFromClipboard };
@@ -0,0 +1,338 @@
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
+ Object.defineProperty(exports, "copyToClipboard", {
308
+ enumerable: true,
309
+ get: function() {
310
+ return copyToClipboard;
311
+ }
312
+ });
313
+ Object.defineProperty(exports, "copyToClipboardLegacy", {
314
+ enumerable: true,
315
+ get: function() {
316
+ return copyToClipboardLegacy;
317
+ }
318
+ });
319
+ Object.defineProperty(exports, "isClipboardReadSupported", {
320
+ enumerable: true,
321
+ get: function() {
322
+ return isClipboardReadSupported;
323
+ }
324
+ });
325
+ Object.defineProperty(exports, "isClipboardSupported", {
326
+ enumerable: true,
327
+ get: function() {
328
+ return isClipboardSupported;
329
+ }
330
+ });
331
+ Object.defineProperty(exports, "readFromClipboard", {
332
+ enumerable: true,
333
+ get: function() {
334
+ return readFromClipboard;
335
+ }
336
+ });
337
+
338
+ //# sourceMappingURL=clipboard-Bs9DV14p.cjs.map