3.4 KiB
fallback/fallback.ts
(no submodules)
Purpose
Legacy clipboard fallback paths used when navigator.clipboard is
unavailable or rejects. Provides a hidden-textarea + execCommand("copy")
write path and a paste-event-based read path. Both return the same
ClipboardResult shape as the API path so the manager's call sites do
not branch on which mechanism actually ran. The paste-event fallback is
purpose-built for environments — older Safari, embedded WebViews, and
permission-denied contexts — where navigator.clipboard.readText
rejects but the user can paste manually.
Features
copyTextFallback(text)— synchronous textarea +execCommandcopy.readTextFallback(timeoutMs)— focus + paste-event capture with a configurable timeout.dispatchSyntheticPaste(text)— test helper that fires a syntheticpasteevent on the most recent hidden host.- SSR-safe — both copy and read paths return
CLIPBOARD.DOCUMENT_UNAVAILABLEwhendocumentis undefined. - Off-screen, non-interactive host element so users never see the textarea visibly steal focus.
Usage: Basic
import { copyTextFallback } from "@suaveplan/clipboard";
const result = copyTextFallback("hello");
if (!result.success) {
console.warn("legacy copy failed:", result.code);
}
Usage: Advanced
import {
readTextFallback,
dispatchSyntheticPaste,
} from "@suaveplan/clipboard";
async function readClipboardWithPasteFallback(): Promise<string | undefined> {
const result = await readTextFallback(2000);
return result.success ? result.data : undefined;
}
// Inside a test harness:
const promise = readTextFallback(500);
queueMicrotask(() => dispatchSyntheticPaste("from-test"));
const result = await promise;
API Reference
copyTextFallback(text: string): ClipboardResult<string>
Stages a hidden <textarea> carrying text, selects it, runs
document.execCommand("copy"), and removes the textarea on cleanup.
Returns { success: true, data, method: "fallback" } on success and a
structured failure with code: CLIPBOARD.FALLBACK_FAILED (or
CLIPBOARD.DOCUMENT_UNAVAILABLE outside the browser) otherwise.
readTextFallback(timeoutMs: number): Promise<ClipboardResult<string>>
Creates a hidden textarea, focuses it, listens for the first paste
event, and resolves with the text/plain payload carried by the event.
Resolves with code: CLIPBOARD.READ_FAILED if the timeout elapses
first and code: CLIPBOARD.DOCUMENT_UNAVAILABLE outside the browser.
dispatchSyntheticPaste(text: string): boolean
Test helper. Locates the most recent hidden host (textarea[aria-hidden="true"])
and dispatches a paste ClipboardEvent carrying text as text/plain.
Returns true when a host was found, false otherwise.
Implementation Notes
The hidden textarea is positioned at -9999px and given opacity: 0
plus aria-hidden="true" so assistive technologies skip it. The cleanup
runs in a finally block — even if execCommand throws, the textarea is
removed. The read fallback tracks a settled flag so a duplicate or
post-timeout paste event cannot resolve the promise twice. The paste
event may carry a null clipboardData on some browsers; the module
falls back to an empty string in that case rather than rejecting, since
"clipboard contained nothing" is a valid outcome. The synthetic-paste
helper is exported to keep tests deterministic — production code never
calls it.