visual-feedback 0.3.0 → 0.3.2
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/CHANGELOG.md +13 -0
- package/README.md +2 -0
- package/dist/index.mjs +1 -1
- package/dist/react.mjs +1 -1
- package/dist/{widget-DvGTo0B4.mjs → widget-Ryu07XUU.mjs} +48 -13
- package/docs/API.md +1 -1
- package/docs/SPEC.md +6 -0
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.2 (2026-10-13)
|
|
4
|
+
|
|
5
|
+
- The README links a live demo: the playground on GitHub Pages, at
|
|
6
|
+
https://dorumrr.github.io/visual-feedback/. The widget itself is unchanged.
|
|
7
|
+
|
|
8
|
+
## 0.3.1 (2026-10-12)
|
|
9
|
+
|
|
10
|
+
- The trigger shows its name as a tooltip. With a `shortcut` set, the tooltip names it too, the way
|
|
11
|
+
the platform's keyboards do, such as `Send feedback (Option + Shift + F)` on a Mac and
|
|
12
|
+
`(Alt + Shift + F)` elsewhere (SPEC §3).
|
|
13
|
+
- A `shortcut` with no key, such as `"Alt+"`, now matches nothing: before, a key press whose code
|
|
14
|
+
was empty, as some virtual keyboards send, could open the widget.
|
|
15
|
+
|
|
3
16
|
## 0.3.0 (2026-10-11)
|
|
4
17
|
|
|
5
18
|
- Breaking: with no `--vf-*` variables set, the widget now matches the site by itself. It takes
|
package/README.md
CHANGED
|
@@ -4,6 +4,8 @@ A small button lets your users screenshot the page, mark it up and write a note.
|
|
|
4
4
|
report and store it where you want. It has no service, no tracking and no global CSS, and it fits
|
|
5
5
|
any design.
|
|
6
6
|
|
|
7
|
+
**Try it [here](https://dorumrr.github.io/visual-feedback/)**. A report you send stays in the page, as the site's own list.
|
|
8
|
+
|
|
7
9
|

|
|
8
10
|
|
|
9
11
|
```sh
|
package/dist/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
|
|
2
|
-
export { t as createFeedback, n as discardDraft } from "./widget-
|
|
2
|
+
export { t as createFeedback, n as discardDraft } from "./widget-Ryu07XUU.mjs";
|
package/dist/react.mjs
CHANGED
|
@@ -223,6 +223,41 @@ const noop = {
|
|
|
223
223
|
update() {},
|
|
224
224
|
on: () => () => {}
|
|
225
225
|
};
|
|
226
|
+
const MODIFIERS = [
|
|
227
|
+
"alt",
|
|
228
|
+
"shift",
|
|
229
|
+
"control",
|
|
230
|
+
"meta"
|
|
231
|
+
];
|
|
232
|
+
/** A shortcut's modifiers in lower case and its key as written; undefined when it can match nothing. */
|
|
233
|
+
function parse(shortcut) {
|
|
234
|
+
const parts = shortcut.split("+");
|
|
235
|
+
const key = parts.pop() ?? "";
|
|
236
|
+
const mods = parts.map((part) => part.toLowerCase());
|
|
237
|
+
return key && mods.every((mod) => MODIFIERS.includes(mod)) ? {
|
|
238
|
+
mods,
|
|
239
|
+
key
|
|
240
|
+
} : void 0;
|
|
241
|
+
}
|
|
242
|
+
/** The shortcut as this platform's keyboards name it, such as "Option + Shift + F" on a Mac. */
|
|
243
|
+
function spoken(shortcut) {
|
|
244
|
+
const parsed = parse(shortcut);
|
|
245
|
+
if (!parsed) return void 0;
|
|
246
|
+
const platform = navigator.platform;
|
|
247
|
+
const names = /mac|iphone|ipad|ipod/i.test(platform) ? {
|
|
248
|
+
alt: "Option",
|
|
249
|
+
shift: "Shift",
|
|
250
|
+
control: "Control",
|
|
251
|
+
meta: "Command"
|
|
252
|
+
} : {
|
|
253
|
+
alt: "Alt",
|
|
254
|
+
shift: "Shift",
|
|
255
|
+
control: "Ctrl",
|
|
256
|
+
meta: /^win/i.test(platform) ? "Win" : "Super"
|
|
257
|
+
};
|
|
258
|
+
const key = parsed.key.charAt(0).toUpperCase() + parsed.key.slice(1);
|
|
259
|
+
return [...new Set(parsed.mods)].map((mod) => names[mod]).concat(key).join(" + ");
|
|
260
|
+
}
|
|
226
261
|
function createFeedback(options = {}) {
|
|
227
262
|
if (!supported()) return noop;
|
|
228
263
|
const tag = options.tagName ?? "visual-feedback";
|
|
@@ -259,18 +294,12 @@ function createFeedback(options = {}) {
|
|
|
259
294
|
let label;
|
|
260
295
|
let unlisten;
|
|
261
296
|
function listen(shortcut) {
|
|
262
|
-
const
|
|
263
|
-
const key =
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
"shift",
|
|
267
|
-
"control",
|
|
268
|
-
"meta"
|
|
269
|
-
];
|
|
270
|
-
const mods = names.map((name) => parts.includes(name)).join();
|
|
271
|
-
const typing = !/control|meta/.test(shortcut.toLowerCase());
|
|
297
|
+
const parsed = parse(shortcut);
|
|
298
|
+
const key = parsed?.key.toLowerCase();
|
|
299
|
+
const mods = MODIFIERS.map((name) => parsed?.mods.includes(name)).join();
|
|
300
|
+
const typing = !parsed?.mods.some((mod) => mod === "control" || mod === "meta");
|
|
272
301
|
const on = (event) => {
|
|
273
|
-
if (event instanceof KeyboardEvent && state === "idle" && !event.repeat && !event.isComposing && !event.getModifierState("AltGraph") &&
|
|
302
|
+
if (event instanceof KeyboardEvent && state === "idle" && !event.repeat && !event.isComposing && !event.getModifierState("AltGraph") && parsed && [
|
|
274
303
|
event.altKey,
|
|
275
304
|
event.shiftKey,
|
|
276
305
|
event.ctrlKey,
|
|
@@ -322,7 +351,10 @@ function createFeedback(options = {}) {
|
|
|
322
351
|
trigger.dataset.y = y;
|
|
323
352
|
trigger.dataset.x = x;
|
|
324
353
|
const { trigger: name, triggerText } = options.strings ?? {};
|
|
325
|
-
|
|
354
|
+
const accessible = name || triggerText || "Send feedback";
|
|
355
|
+
trigger.setAttribute("aria-label", accessible);
|
|
356
|
+
const keys = options.shortcut ? spoken(options.shortcut) : void 0;
|
|
357
|
+
trigger.title = keys ? `${accessible} (${keys})` : accessible;
|
|
326
358
|
trigger.toggleAttribute("data-icon", !triggerText);
|
|
327
359
|
if (label) {
|
|
328
360
|
label.textContent = triggerText ?? "";
|
|
@@ -507,7 +539,10 @@ function createFeedback(options = {}) {
|
|
|
507
539
|
const mine = session;
|
|
508
540
|
return pending.then(() => mine === session ? widget.open() : void 0);
|
|
509
541
|
}
|
|
510
|
-
if (state !== "idle")
|
|
542
|
+
if (state !== "idle") {
|
|
543
|
+
dialog?.reopen();
|
|
544
|
+
return Promise.resolve();
|
|
545
|
+
}
|
|
511
546
|
if (restore()) return widget.open();
|
|
512
547
|
state = "opening";
|
|
513
548
|
pending = show(++session);
|
package/docs/API.md
CHANGED
|
@@ -39,7 +39,7 @@ const feedback = createFeedback({
|
|
|
39
39
|
| `headers` | `Record<string, string>` or `() => Record<string, string>` | – | Extra headers for `endpoint`. A function is called at Send; a throw shows the send error state, using the error's message. |
|
|
40
40
|
| `trigger` | `"button" \| "none"` | `"button"` | `"none"` draws no trigger; the host calls `open()`. A kept draft's bar still shows. |
|
|
41
41
|
| `position` | `"bottom-right" \| "bottom-left" \| "top-right" \| "top-left"` | `"bottom-right"` | Corner of the trigger. |
|
|
42
|
-
| `shortcut` | `string \| false` | `false` | For example `"Alt+Shift+F"`: any of `Alt`, `Shift`, `Control` and `Meta`, then one key, a letter, a digit or a key name such as `F2`, joined by `+`. The modifiers must match exactly, AltGr never matches, and any other modifier name makes it match nothing. Without `Control` or `Meta` it does not fire while focus is in a form field (`input`, `textarea`, `select`) or editable content. It does nothing while the dialog is open or a draft is kept in the draft bar. Opt-in; it is the widget's only listener on `window` or `document`, one `keydown` on `window` (SPEC §9 names what else the widget watches, and what the capture library keeps). |
|
|
42
|
+
| `shortcut` | `string \| false` | `false` | For example `"Alt+Shift+F"`: any of `Alt`, `Shift`, `Control` and `Meta`, then one key, a letter, a digit or a key name such as `F2`, joined by `+`. The modifiers must match exactly, AltGr never matches, and any other modifier name, or no key, makes it match nothing. Without `Control` or `Meta` it does not fire while focus is in a form field (`input`, `textarea`, `select`) or editable content. It does nothing while the dialog is open or a draft is kept in the draft bar. The trigger's tooltip names it (SPEC §3). Opt-in; it is the widget's only listener on `window` or `document`, one `keydown` on `window` (SPEC §9 names what else the widget watches, and what the capture library keeps). |
|
|
43
43
|
| `tools` | `("pen" \| "box" \| "arrow" \| "text" \| "select")[]` | all five | Order is the toolbar order, and the first drawing tool is the one selected at open. Repeats and unknown names are ignored; an empty list, or Select alone, shows no toolbar. The Delete button is there only with Select. |
|
|
44
44
|
| `categories` | `{ id: string; label: string }[]` | none | Shows a category picker. |
|
|
45
45
|
| `requireNote` | `boolean` | `true` | |
|
package/docs/SPEC.md
CHANGED
|
@@ -53,6 +53,12 @@ annotating, draft --Cancel (it may ask first, below) / Discard--> idle
|
|
|
53
53
|
gets that short label beside the icon, as a pill; with only `triggerText` given, that text is
|
|
54
54
|
the name too. A host that sets both keeps `triggerText` inside `trigger`, so
|
|
55
55
|
speech control can say what it sees. It has no badge, and it does not move or pulse.
|
|
56
|
+
- Its tooltip (`title`) is its name, as for the dialog's icon buttons. With a `shortcut` that can
|
|
57
|
+
match, it is the name and the keys, each modifier once, joined by ` + `, as the platform's
|
|
58
|
+
keyboards name them: Option, Command, Control and Shift on a Mac, iPhone or iPad
|
|
59
|
+
(`navigator.platform`), as in `Send feedback (Option + Shift + F)`; Alt, Win, Ctrl and Shift on
|
|
60
|
+
Windows; Alt, Super, Ctrl and Shift elsewhere. A screen reader then reads the keys as the
|
|
61
|
+
description.
|
|
56
62
|
- It sits 16 px from the edges by default, and it respects `env(safe-area-inset-*)`.
|
|
57
63
|
- It is hidden while there is a draft, whether the dialog is open or kept in the draft bar.
|
|
58
64
|
- It does not move to keep clear of the page's content. The host moves it off its own content
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "visual-feedback",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "A small button lets your users screenshot the page, mark it up and write a note. You receive one report and store it where you want.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"feedback",
|
|
@@ -66,6 +66,7 @@
|
|
|
66
66
|
"prepack": "npm run --silent build -- --logLevel warn",
|
|
67
67
|
"dev": "node tests/dev.mjs",
|
|
68
68
|
"demo": "node tests/demo.mjs",
|
|
69
|
+
"site": "node tests/site.mjs",
|
|
69
70
|
"release": "node tests/release.mjs"
|
|
70
71
|
},
|
|
71
72
|
"devDependencies": {
|