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,91 @@
# `permissions/permissions.ts`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Probe and subscribe to clipboard permission state via the browser's
Permissions API. `checkPermissions` returns a snapshot of the
`clipboard-read` and `clipboard-write` permission states with graceful
degradation to `"prompt"` when the API is missing or a query rejects.
`onPermissionChange` subscribes a callback to `PermissionStatus.change`
events on both descriptors so UI surfaces can reflect real-time grants
and revocations without polling.
## Features
- `checkPermissions(nav)` — `{ read, write }` snapshot; never throws.
- `onPermissionChange(nav, cb)` — subscribe with one callback, unsubscribe
with a single returned function.
- Tolerant of partial API support: falls back to `PermissionStatus.onchange`
when `addEventListener` is unavailable.
- Tolerant of permission-query rejection: skips that slot without
re-throwing.
## Usage: Basic
```ts
import { checkPermissions } from "@suaveplan/clipboard";
const { read, write } = await checkPermissions(navigator);
if (read === "denied" || write === "denied") {
showFallbackUi();
}
```
## Usage: Advanced
```ts
import { onPermissionChange } from "@suaveplan/clipboard";
const unsubscribe = await onPermissionChange(
navigator,
(snapshot, descriptor) => {
console.info(
"[clipboard] permission change",
descriptor,
snapshot,
);
if (snapshot.read === "granted" && snapshot.write === "granted") {
enableCopyButtons();
}
},
);
// later — on unmount / dispose
unsubscribe();
```
## API Reference
### `checkPermissions(nav: ClipboardNavigator): Promise<PermissionResult>`
Resolves with `{ read, write }`. Each slot is one of `"granted"`,
`"denied"`, `"prompt"`. Returns `{ read: "prompt", write: "prompt" }`
when `nav.permissions` is undefined, and `"prompt"` for any individual
descriptor whose `query()` rejects or returns an unknown state.
### `onPermissionChange(nav, cb): Promise<() => void>`
Subscribes `cb` to permission changes on both `clipboard-read` and
`clipboard-write`. Returns an unsubscribe function. The callback receives
the latest `PermissionResult` snapshot plus the descriptor name whose
state changed. The change handler internally re-runs `checkPermissions`
so both `read` and `write` are always up to date on every callback.
## Implementation Notes
The Permissions API exposes two listener mechanisms — `addEventListener`
and `onchange`. Some platforms (older WebKit / mobile) support only one,
so the module attaches via `addEventListener` when available and falls
back to assigning `onchange` otherwise. The unsubscribe function clears
the corresponding mechanism for each subscription. The
`checkPermissions` helper normalises unknown state strings to `"prompt"`
so callers never have to defend against future spec values. Query
rejection is treated as `"prompt"` rather than `"denied"` because a
rejection typically signals "API not supported" rather than "user
explicitly refused" — pessimistic mapping would block correct callers in
older browsers. Tests cover both `addEventListener` and `onchange` paths,
plus the unsubscribe contract.