Files
skills/docs/foundation/data/clipboard/src/fallback/fallback.md
2026-05-29 08:20:56 +01:00

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 + execCommand copy.
  • readTextFallback(timeoutMs) — focus + paste-event capture with a configurable timeout.
  • dispatchSyntheticPaste(text) — test helper that fires a synthetic paste event on the most recent hidden host.
  • SSR-safe — both copy and read paths return CLIPBOARD.DOCUMENT_UNAVAILABLE when document is 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.