jg-clipboard 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/API.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # <a id="api-reference"></a>jg-clipboard API reference
2
2
 
3
- Every export of `jg-clipboard@0.1.0`, 10 in all, with its signature, its errors, and an example. It's generated from the package's TSDoc.
3
+ Every export of `jg-clipboard@0.1.1`, 10 in all, with its signature, its errors, and an example. It's generated from the package's TSDoc.
4
4
 
5
5
  - Functions: [`copyHtml`](#api-copyHtml), [`copyImage`](#api-copyImage), [`copyText`](#api-copyText), [`getClipboardSupport`](#api-getClipboardSupport), [`readClipboard`](#api-readClipboard), [`readText`](#api-readText)
6
6
  - Constants: [`ERRORS`](#api-ERRORS)
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.1
4
+
5
+ ### Added
6
+
7
+ - `RECIPES.md`, patterns for apps that use the package: a copy button that explains a refusal, and a paste button for text or an image. Each block is typechecked against the release.
8
+
3
9
  ## 0.1.0
4
10
 
5
11
  The first release.
package/README.md CHANGED
@@ -91,6 +91,8 @@ Every failure is an `Exception` with one of these codes, and the browser's error
91
91
 
92
92
  The full API reference ships in the package as `API.md`, generated from its TSDoc for the installed version, and is at https://jg-packages.jesusgraterol.dev/packages/jg-clipboard/.
93
93
 
94
+ Recipes for common jobs ship as `RECIPES.md`, with every block typechecked against the release, and are at https://jg-packages.jesusgraterol.dev/packages/jg-clipboard/recipes/.
95
+
94
96
  ## License
95
97
 
96
98
  [MIT](https://github.com/jesusgraterol/jg-packages/blob/main/packages/jg-clipboard/LICENSE)
package/RECIPES.md ADDED
@@ -0,0 +1,107 @@
1
+ # jg-clipboard recipes
2
+
3
+ Patterns for copy and paste buttons that work across browsers, and say what to do when the browser refuses.
4
+
5
+ ## A copy button that explains a refusal
6
+
7
+ Use this for a button that copies a code snippet, a share link, or a key. Browsers refuse a copy for several reasons, and jg-clipboard names each one with a code, so the message can tell the person what to do next, and how to get the text another way:
8
+
9
+ ```ts
10
+ import { copyText, ERRORS } from 'jg-clipboard';
11
+ import { hasErrorCode } from 'jg-exception';
12
+
13
+ // the element whose text the button copies
14
+ declare const snippet: HTMLElement;
15
+
16
+ // a status line, such as an element with role="status", so screen readers announce the outcome
17
+ declare const copyStatus: HTMLElement;
18
+
19
+ // says what to do after a copy failed, so the text can still be copied by hand
20
+ const describeCopyFailure = (error: unknown): string => {
21
+ if (hasErrorCode(error, ERRORS.CLIPBOARD_PERMISSION_DENIED)) {
22
+ return 'Your browser blocked copying. Select the text and copy it manually.';
23
+ }
24
+ if (hasErrorCode(error, ERRORS.CLIPBOARD_NO_USER_GESTURE)) {
25
+ return 'Your browser only copies right after a click. Click Copy again.';
26
+ }
27
+ if (hasErrorCode(error, ERRORS.CLIPBOARD_NOT_FOCUSED)) {
28
+ return 'Copying needs this tab to be in focus. Click the page, then click Copy again.';
29
+ }
30
+ if (hasErrorCode(error, ERRORS.CLIPBOARD_UNSUPPORTED)) {
31
+ return "This browser can't copy from this page. Select the text and copy it manually.";
32
+ }
33
+ return 'Copying failed. Select the text and copy it manually.';
34
+ };
35
+
36
+ button.addEventListener('click', async () => {
37
+ // cleared first, so an earlier outcome never shows during this attempt
38
+ copyStatus.textContent = '';
39
+ try {
40
+ // nothing awaits before copyText, so the browser still sees the click
41
+ await copyText(snippet.textContent ?? '');
42
+ copyStatus.textContent = 'Copied';
43
+ } catch (error) {
44
+ copyStatus.textContent = describeCopyFailure(error);
45
+ }
46
+ });
47
+ ```
48
+
49
+ `CLIPBOARD_WRITE_FAILED`, and anything unexpected, gets the general message. A `CLIPBOARD_PERMISSION_DENIED` error's `data.permission` holds the Permissions API's state, or `null` where the browser can't say; `'denied'` means the site's settings block the clipboard, which no second click fixes. To hide a button that can never work, check `getClipboardSupport().copyText` first, and keep the messages, because a supported copy can still be refused.
50
+
51
+ ## A paste button for text or an image
52
+
53
+ Use this for a paste button in a composer that takes a screenshot or text. `readClipboard` reads every kind of content with one call, so the person sees one paste prompt at most, and returns an image as PNG, whatever type it was copied as. A browser that reads only text gets `readText` instead:
54
+
55
+ ```ts
56
+ import { ERRORS, getClipboardSupport, readClipboard, readText } from 'jg-clipboard';
57
+ import { hasErrorCode } from 'jg-exception';
58
+
59
+ // a status line that says why a paste failed
60
+ declare const pasteStatus: HTMLElement;
61
+
62
+ const support = getClipboardSupport();
63
+
64
+ // a browser that can't read the clipboard gets no button, and the keyboard still pastes there
65
+ pasteButton.hidden = !support.read && !support.readText;
66
+
67
+ // shows a pasted image, and releases the previous one, so repeated pastes don't keep it in memory
68
+ const showImage = (image: Blob): void => {
69
+ if (preview.src.startsWith('blob:')) {
70
+ URL.revokeObjectURL(preview.src);
71
+ }
72
+ preview.src = URL.createObjectURL(image);
73
+ };
74
+
75
+ const describePasteFailure = (error: unknown): string => {
76
+ if (hasErrorCode(error, ERRORS.CLIPBOARD_PERMISSION_DENIED)) {
77
+ return 'Your browser blocked pasting. Paste with Ctrl+V, or Cmd+V on a Mac, instead.';
78
+ }
79
+ if (hasErrorCode(error, ERRORS.CLIPBOARD_INVALID_IMAGE)) {
80
+ return "The copied image can't be read. Save it, then attach the file instead.";
81
+ }
82
+ return 'Pasting failed. Paste with Ctrl+V, or Cmd+V on a Mac, instead.';
83
+ };
84
+
85
+ pasteButton.addEventListener('click', async () => {
86
+ pasteStatus.textContent = '';
87
+ try {
88
+ // nothing awaits before the read, which Safari needs
89
+ if (!support.read) {
90
+ input.value = await readText();
91
+ return;
92
+ }
93
+ const { text, image } = await readClipboard();
94
+ if (image !== null) {
95
+ showImage(image);
96
+ } else if (text !== null) {
97
+ input.value = text;
98
+ } else {
99
+ pasteStatus.textContent = 'The clipboard holds no text or image to paste.';
100
+ }
101
+ } catch (error) {
102
+ pasteStatus.textContent = describePasteFailure(error);
103
+ }
104
+ });
105
+ ```
106
+
107
+ Safari and Firefox show a paste prompt on every read, and Chromium asks for the clipboard-read permission once, so read only when the person clicks. Which content wins when the clipboard holds both depends on the app: copied spreadsheet cells can come with a picture of the cells, so a field for tables would check `text` first. `CLIPBOARD_INVALID_IMAGE` means the clipboard held an image the browser can't decode, and `readClipboard` then fails as a whole, even when there was text too.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jg-clipboard",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Copy and read text, HTML, and images on the browser clipboard, with support detection and coded errors that say why the browser refused.",
5
5
  "author": "Jesus Graterol",
6
6
  "license": "MIT",
@@ -16,7 +16,8 @@
16
16
  "files": [
17
17
  "dist",
18
18
  "CHANGELOG.md",
19
- "API.md"
19
+ "API.md",
20
+ "RECIPES.md"
20
21
  ],
21
22
  "sideEffects": false,
22
23
  "engines": {
@@ -42,7 +43,7 @@
42
43
  "clipboard-api"
43
44
  ],
44
45
  "dependencies": {
45
- "jg-exception": "^1.1.0"
46
+ "jg-exception": "^1.2.0"
46
47
  },
47
48
  "devDependencies": {
48
49
  "@jg-packages/eslint-config": "0.0.0",