first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,96 @@
# `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: "<b>hi</b>",
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<T>(code, message, method): ClipboardResult<T>`
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<string, Blob>`
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<ClipboardData>`
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.