# `manager/helpers.ts` ## Purpose Pure helpers shared between `manager.ts` and `operations.ts`. Keeps both files under the line budget by housing the navigator / window resolution, the structured failure builder, the focus-error detector, and the `ClipboardItem` build + read pipelines in a single, side-effect-free module that tests can exercise directly. ## Features - `resolveNavigator(nav?)` — caller, global, or empty shim. - `resolveWindow(win?)` — caller, global, or `undefined`. - `failure(code, message, method)` — typed `ClipboardResult` failure. - `isFocusError(cause)` — detects the canonical focus-required rejection. - `buildClipboardItems(data)` — MIME → `Blob` map for `ClipboardItem`. - `readClipboardItems(items, sanitizer?)` — pulls text / html / image / blobs out of a list of `ClipboardItem` instances. ## Usage: Basic ```ts import { failure, CLIPBOARD_CODES } from "@suaveplan/clipboard"; const r = failure(CLIPBOARD_CODES.NO_CONTENT, "empty", "api"); ``` ## Usage: Advanced ```ts import { buildClipboardItems, readClipboardItems, } from "@suaveplan/clipboard"; const items = buildClipboardItems({ text: "hello", html: "hi", image: pngBlob, blobs: { "application/json": jsonBlob }, }); const clipboardItem = new ClipboardItem(items); await navigator.clipboard.write([clipboardItem]); // Round-trip back: const read = await navigator.clipboard.read(); const data = await readClipboardItems(read, (html) => html.trim()); ``` ## API Reference ### `resolveNavigator(nav?: ClipboardNavigator): ClipboardNavigator` Returns `nav` when defined, otherwise the global `navigator`, otherwise the empty object `{}`. ### `resolveWindow(win?: ClipboardWindowLike): ClipboardWindowLike | undefined` Returns `win` when defined, otherwise the global `window`, otherwise `undefined`. ### `failure(code, message, method): ClipboardResult` Returns `{ success: false, error: message, code, method }`. Generic over `T` so call sites compose with their own result types. ### `isFocusError(cause: unknown): boolean` Returns `true` when `cause` is an `Error` whose `name === "NotAllowedError"` and whose `message` (case-insensitively) contains `"focus"`. The browser signals "Clipboard needs page focus" with this exact shape. ### `buildClipboardItems(data: ClipboardData): Record` Builds a MIME → Blob map for `new ClipboardItem(...)`. Skips empty `text` and `html`. Defaults a typeless image blob to `image/png`. ### `readClipboardItems(items, sanitizer?): Promise` Iterates every entry of every `ClipboardItem`, populating `text`, `html`, `image`, and `blobs`. The first image-typed entry wins. The `sanitizer` (when supplied) is applied to the raw `text/html` payload before returning. ## Implementation Notes The helpers are intentionally side-effect-free and free of telemetry calls so the same code runs from `runCopyText`, `runCopyRich`, etc., without double-recording counters. `buildClipboardItems` and `readClipboardItems` are mirror images of each other — every MIME slot that `buildClipboardItems` writes is read back by `readClipboardItems` into the same `ClipboardData` shape. The focus-error detector matches both Chromium ("Document is not focused.") and WebKit ("Document not focused.") wording.