visual-feedback 0.3.2 → 0.4.0

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/AGENTS.md CHANGED
@@ -36,7 +36,10 @@ comes before the first report in step 6.
36
36
  recipe adds. In a React or Next.js app, render `<Feedback endpoint="/api/feedback" />` from
37
37
  `visual-feedback/react` instead, once, in the root layout; its props are the same options, and
38
38
  a function prop such as `onSubmit` needs a `"use client"` file (`docs/API.md`). Every option is in `docs/API.md`; the look is in
39
- `docs/THEMING.md`.
39
+ `docs/THEMING.md`. Fit the button to the app's own design: read its layout and pick
40
+ `trigger` and `position`, and `--vf-offset` where the default spot would cover something the app shows, and the
41
+ `--vf-*` colours and corners where the widget does not take them from the page already. Tell the
42
+ developer where it sits and why.
40
43
  3. On the server: copy the recipe's route as it is written. Its test runs that code, with only the
41
44
  marked parts below filled in, so do not change how it stores a report. If the app is not a Next.js App Router app, keep the logic and
42
45
  change only how the route is mounted.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.0 (2026-10-13)
4
+
5
+ - Breaking: the trigger is a tab by default, in the corner `position` names, with a flag icon. It
6
+ has no border: the text colour fills it, and on hover the colours swap. `--vf-offset` lifts the
7
+ tab along its side edge, and no longer moves it off that edge. `trigger: "button"` keeps the
8
+ round button, 16px from both edges, filled the same way (SPEC §3).
9
+ - Breaking: the trigger has no `title`. Its tooltip is the widget's own: it shows at once on hover
10
+ or keyboard focus, beside the trigger with an arrow pointing at it, and has its own part,
11
+ `trigger-tooltip`. Screen readers still hear it as the description.
12
+ - The README starts with a prompt to give a coding agent, naming the package by its npm page, and AGENTS.md asks the agent to fit the
13
+ button to the app's design.
14
+ - The demo styles the tooltip as its "Click here" hint, and fades that hint while the tooltip
15
+ shows.
16
+
17
+ ## 0.3.3 (2026-10-13)
18
+
19
+ - The trigger stands out more: a swirl icon that fills it, in place of the speech bubble, ringed
20
+ by a 1px border in the same colour (`--vf-text`). With `triggerText`, the icon stays small
21
+ beside the label.
22
+ - The demo site shows a "Click here" hint at the trigger, and its thumbnails rest at 20% colour and
23
+ fade to full colour on hover.
24
+
3
25
  ## 0.3.2 (2026-10-13)
4
26
 
5
27
  - The README links a live demo: the playground on GitHub Pages, at
package/README.md CHANGED
@@ -8,6 +8,14 @@ any design.
8
8
 
9
9
  ![The feedback dialog over a video site, with a box and an arrow drawn around the topic buttons, seven of which read Undefined](docs/demo.png)
10
10
 
11
+ Ask your coding agent:
12
+
13
+ ```text
14
+ Install the visual-feedback npm package (https://www.npmjs.com/package/visual-feedback) and add it to this app by following its AGENTS.md. Place its button where it covers nothing, and match its position, colours and corners to our design.
15
+ ```
16
+
17
+ Or the old-school way, by hand:
18
+
11
19
  ```sh
12
20
  npm install visual-feedback
13
21
  ```
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
1
 
2
- import { a as FeedbackWidget, c as CaptureOptions, i as FeedbackStrings, n as FeedbackEvents, o as createFeedback, r as FeedbackOptions, s as discardDraft, t as FeedbackEvent } from "./widget-Z1KOUKjx.mjs";
2
+ import { a as FeedbackWidget, c as CaptureOptions, i as FeedbackStrings, n as FeedbackEvents, o as createFeedback, r as FeedbackOptions, s as discardDraft, t as FeedbackEvent } from "./widget-Cxcm3h1Z.mjs";
3
3
  import { n as Mark, r as Screenshot, t as FeedbackReport } from "./report-CaJegYyg.mjs";
4
4
  export { type CaptureOptions, type FeedbackEvent, type FeedbackEvents, type FeedbackOptions, type FeedbackReport, type FeedbackStrings, type FeedbackWidget, type Mark, type Screenshot, createFeedback, discardDraft };
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
1
 
2
- export { t as createFeedback, n as discardDraft } from "./widget-Ryu07XUU.mjs";
2
+ export { t as createFeedback, n as discardDraft } from "./widget-BoohIxqp.mjs";
package/dist/react.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
 
2
- import { r as FeedbackOptions } from "./widget-Z1KOUKjx.mjs";
2
+ import { r as FeedbackOptions } from "./widget-Cxcm3h1Z.mjs";
3
3
  //#region src/react/index.d.ts
4
4
  export type FeedbackProps = FeedbackOptions;
5
5
  export declare function Feedback(props: FeedbackProps): null;
package/dist/react.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { t as createFeedback } from "./widget-Ryu07XUU.mjs";
2
+ import { t as createFeedback } from "./widget-BoohIxqp.mjs";
3
3
  import { useEffect, useRef } from "react";
4
4
  //#region src/react/index.ts
5
5
  function Feedback(props) {
@@ -162,26 +162,24 @@ function sheet(text) {
162
162
  return result;
163
163
  }
164
164
  function variables() {
165
- return `.vf{--_r:var(--vf-radius,var(--_pr,16px));--_s:var(--vf-surface,var(--_ps,Canvas));--_t:var(--vf-text,var(--_pt,CanvasText));--_m:var(--vf-muted,var(--_pm,color-mix(in srgb,var(--_t) 60%,transparent)));--_b:var(--vf-border,color-mix(in srgb,var(--_t) 15%,transparent));--_a:var(--vf-accent,var(--_pa,${CSS.supports("color", "AccentColor") ? "AccentColor" : "#0b57d0"}));--_at:var(--vf-accent-text,#fff);--_f:var(--vf-focus,var(--_a));--_mark:var(--vf-mark,#e5243b);--_bd:var(--vf-backdrop,rgb(0 0 0/45%));--_sh:var(--vf-shadow,0 12px 32px -12px rgb(0 0 0/35%));--_size:var(--vf-trigger-size,2.5em);--_off:var(--vf-offset,16px);--_z:var(--vf-z,2147483000)}`;
165
+ return `.vf{--_r:var(--vf-radius,var(--_pr,16px));--_s:var(--vf-surface,var(--_ps,Canvas));--_t:var(--vf-text,var(--_pt,CanvasText));--_m:var(--vf-muted,var(--_pm,color-mix(in srgb,var(--_t) 60%,transparent)));--_b:var(--vf-border,color-mix(in srgb,var(--_t) 15%,transparent));--_a:var(--vf-accent,var(--_pa,${CSS.supports("color", "AccentColor") ? "AccentColor" : "#0b57d0"}));--_at:var(--vf-accent-text,#fff);--_f:var(--vf-focus,var(--_a));--_mark:var(--vf-mark,#e5243b);--_bd:var(--vf-backdrop,rgb(0 0 0/45%));--_sh:var(--vf-shadow,0 12px 32px -12px rgb(0 0 0/35%));--_size:var(--vf-trigger-size,2.5em);--_off:var(--vf-offset,16px);--_toff:var(--vf-offset,0px);--_z:var(--vf-z,2147483000)}`;
166
166
  }
167
- const LAYOUT = `.vf{display:contents;font-family:var(--vf-font,inherit);color-scheme:light dark}.trigger{font:inherit;position:fixed;z-index:var(--_z);box-sizing:border-box;height:var(--_size);margin:0;padding:0 14px 0 12px;display:flex;align-items:center;gap:8px;white-space:nowrap}.trigger[data-icon]{width:var(--_size);padding:0;justify-content:center}[data-y=bottom]{bottom:calc(var(--_off) + env(safe-area-inset-bottom,0px))}[data-y=top]{top:calc(var(--_off) + env(safe-area-inset-top,0px))}[data-x=right]{right:calc(var(--_off) + env(safe-area-inset-right,0px))}[data-x=left]{left:calc(var(--_off) + env(safe-area-inset-left,0px))}.trigger svg{width:1.15em;height:1.15em}.capturing,.capturing .backdrop{visibility:hidden}.still .trigger{transition:none!important}:focus-visible{outline:2px solid var(--_f);outline-offset:2px}@media (prefers-reduced-motion:reduce){*{transition:none!important;animation:none!important}}`;
168
- const THEME = `.trigger{font-weight:600;border:1px solid var(--_b);border-radius:999px;background:var(--_s);color:var(--_t);box-shadow:var(--_sh);cursor:pointer;transition:background-color .15s}.trigger:hover{background:color-mix(in srgb,var(--_s) 90%,var(--_t))}`;
169
- function icon() {
167
+ const LAYOUT = `.vf{display:contents;font-family:var(--vf-font,inherit);color-scheme:light dark}.trigger{font:inherit;position:fixed;z-index:var(--_z);box-sizing:border-box;height:var(--_size);margin:0;padding:0 14px 0 12px;display:flex;align-items:center;gap:8px;white-space:nowrap}.trigger[data-icon]{width:var(--_size);padding:0;justify-content:center}.trigger[data-icon]>svg{width:62.5%;height:62.5%}[data-y=bottom]{bottom:calc(var(--_off) + env(safe-area-inset-bottom,0px))}[data-y=top]{top:calc(var(--_off) + env(safe-area-inset-top,0px))}[data-x=right]{right:calc(var(--_off) + env(safe-area-inset-right,0px))}[data-x=left]{left:calc(var(--_off) + env(safe-area-inset-left,0px))}.trigger:where([data-tab][data-y=bottom]){bottom:calc(var(--_toff) + env(safe-area-inset-bottom,0px))}.trigger:where([data-tab][data-y=top]){top:calc(var(--_toff) + env(safe-area-inset-top,0px))}.trigger:where([data-tab][data-x=right]){right:env(safe-area-inset-right,0px)}.trigger:where([data-tab][data-x=left]){left:env(safe-area-inset-left,0px)}.trigger>svg{width:1.15em;height:1.15em}.tip{position:absolute;top:50%;translate:0 -50%;direction:ltr;box-sizing:border-box;width:max-content;max-width:calc(100vw - 100% - 2 * var(--_off) - 10px);white-space:normal;display:flex;align-items:center;gap:4px;visibility:hidden;opacity:0;pointer-events:none;transition:opacity .15s,visibility .15s}[data-x=right]>.tip{right:calc(100% + 10px)}[data-x=left]>.tip{left:calc(100% + 10px);flex-direction:row-reverse}[data-x=left]>.tip svg{rotate:180deg}.tip svg{width:1.15em;height:1.15em;flex:none}.trigger:focus-visible>.tip{visibility:inherit;opacity:1}@media (hover:hover){.trigger:hover>.tip{visibility:inherit;opacity:1}}.capturing,.capturing .backdrop{visibility:hidden}.capturing .tip{transition:none}.still .trigger{transition:none!important}:focus-visible{outline:2px solid var(--_f);outline-offset:2px}@media (prefers-reduced-motion:reduce){*{transition:none!important;animation:none!important}}`;
168
+ const THEME = `.trigger{font-weight:600;border:0;border-radius:999px;background:var(--_t);color:var(--_s);box-shadow:var(--_sh);cursor:pointer;transition:background-color .15s,color .15s}@media (hover:hover){.trigger:hover{background:var(--_s);color:var(--_t)}}.tip{padding:6px 8px 6px 14px;border-radius:999px;background:var(--_t);color:var(--_s);font-weight:700;box-shadow:var(--_sh)}[data-x=left]>.tip{padding:6px 14px 6px 8px}.trigger:where([data-tab]){--_tr:clamp(0px,var(--_toff) * 1000,var(--_r));border-radius:var(--_r)}.trigger:where([data-tab][data-y=bottom]){border-bottom-left-radius:var(--_tr);border-bottom-right-radius:var(--_tr)}.trigger:where([data-tab][data-y=top]){border-top-left-radius:var(--_tr);border-top-right-radius:var(--_tr)}.trigger:where([data-tab][data-x=right]){border-top-right-radius:0;border-bottom-right-radius:0}.trigger:where([data-tab][data-x=left]){border-top-left-radius:0;border-bottom-left-radius:0}`;
169
+ function icon(d, attributes) {
170
170
  const svg = document.createElementNS(SVG, "svg");
171
171
  const path = document.createElementNS(SVG, "path");
172
172
  for (const [name, value] of Object.entries({
173
173
  viewBox: "0 0 24 24",
174
174
  "aria-hidden": "true",
175
- fill: "none",
176
- stroke: "currentColor",
177
- "stroke-width": "2",
178
- "stroke-linejoin": "round",
179
- part: "trigger-icon"
175
+ ...attributes
180
176
  })) svg.setAttribute(name, value);
181
- path.setAttribute("d", "M21 12a8 8 0 0 1-11.6 7.1L4 20l1.1-4.4A8 8 0 1 1 21 12z");
177
+ path.setAttribute("d", d);
182
178
  svg.append(path);
183
179
  return svg;
184
180
  }
181
+ const FLAG = "M4 3a1 1 0 0 1 2 0v18a1 1 0 0 1-2 0zM7 4c2.5-1.5 5-1.5 7 0s4 1.5 6 0v10c-2 1.5-4 1.5-6 0s-4.5-1.5-7 0z";
182
+ const ARROW = "M5 12h14m-6-6 6 6-6 6";
185
183
  function field(element) {
186
184
  return element.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(element.tagName);
187
185
  }
@@ -292,6 +290,9 @@ function createFeedback(options = {}) {
292
290
  let pending;
293
291
  let trigger;
294
292
  let label;
293
+ const tip = document.createElement("span");
294
+ tip.dir = "auto";
295
+ tip.setAttribute("part", "trigger-tooltip-text");
295
296
  let unlisten;
296
297
  function listen(shortcut) {
297
298
  const parsed = parse(shortcut);
@@ -343,18 +344,35 @@ function createFeedback(options = {}) {
343
344
  trigger.setAttribute("part", "trigger");
344
345
  label = document.createElement("span");
345
346
  label.setAttribute("part", "trigger-text");
346
- trigger.append(icon(), label);
347
+ const tooltip = document.createElement("span");
348
+ tooltip.className = "tip";
349
+ tooltip.id = "vf-tip";
350
+ tooltip.setAttribute("part", "trigger-tooltip");
351
+ tooltip.setAttribute("aria-hidden", "true");
352
+ tooltip.append(tip, icon(ARROW, {
353
+ fill: "none",
354
+ stroke: "currentColor",
355
+ "stroke-width": "2.4",
356
+ "stroke-linecap": "round",
357
+ part: "icon"
358
+ }));
359
+ trigger.setAttribute("aria-describedby", tooltip.id);
360
+ trigger.append(icon(FLAG, {
361
+ fill: "currentColor",
362
+ part: "trigger-icon"
363
+ }), label, tooltip);
347
364
  trigger.addEventListener("click", start);
348
365
  wrapper.prepend(trigger);
349
366
  }
350
367
  const [y, x] = (options.position ?? "bottom-right").split("-");
351
368
  trigger.dataset.y = y;
352
369
  trigger.dataset.x = x;
370
+ trigger.toggleAttribute("data-tab", options.trigger !== "button");
353
371
  const { trigger: name, triggerText } = options.strings ?? {};
354
372
  const accessible = name || triggerText || "Send feedback";
355
373
  trigger.setAttribute("aria-label", accessible);
356
374
  const keys = options.shortcut ? spoken(options.shortcut) : void 0;
357
- trigger.title = keys ? `${accessible} (${keys})` : accessible;
375
+ tip.textContent = keys ? `${accessible} (${keys})` : accessible;
358
376
  trigger.toggleAttribute("data-icon", !triggerText);
359
377
  if (label) {
360
378
  label.textContent = triggerText ?? "";
@@ -37,7 +37,7 @@ type FeedbackOptions = {
37
37
  requireNote?: boolean;
38
38
  redactUrl?: boolean;
39
39
  context?: () => Record<string, unknown> | Promise<Record<string, unknown>>;
40
- trigger?: "button" | "none";
40
+ trigger?: "tab" | "button" | "none";
41
41
  position?: "bottom-right" | "bottom-left" | "top-right" | "top-left";
42
42
  shortcut?: string | false;
43
43
  strings?: Partial<FeedbackStrings>;
package/docs/API.md CHANGED
@@ -37,7 +37,7 @@ const feedback = createFeedback({
37
37
  | `onSubmit` | `(report) => Promise<void>` | – | Sends the report. A throw shows the send error state, using the error's message. Give this or `endpoint`; with both, `onSubmit` is used. |
38
38
  | `endpoint` | `string` | – | POSTs `multipart/form-data` with a `report` part (JSON, each screenshot without its image) and one part per screenshot, `image0`, `image1` and on, named `<id>-<n>.webp` or `<id>-<n>.png`. An answer that is not 2xx, a redirect included, shows the send error state with the default `error` text. It is a same-origin `fetch`, so it sends cookies only to the page's own origin; another origin needs CORS on its server and a `connect-src` that allows it. |
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
- | `trigger` | `"button" \| "none"` | `"button"` | `"none"` draws no trigger; the host calls `open()`. A kept draft's bar still shows. |
40
+ | `trigger` | `"tab" \| "button" \| "none"` | `"tab"` | `"tab"` touches the side edge, `"button"` is round, in the corner (SPEC §3). `"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
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. |
package/docs/SPEC.md CHANGED
@@ -15,8 +15,8 @@ idle --open--> capturing --image ready--> annotating --Send--> sending --ok--> s
15
15
  +--Esc / Keep as draft / Add screenshot--> draft
16
16
  annotating, draft --Cancel (it may ask first, below) / Discard--> idle
17
17
  ```
18
- - **idle:** the trigger button shows (`trigger: "button"`), or nothing shows (`trigger: "none"`,
19
- where the host calls `open()`).
18
+ - **idle:** the trigger shows, a tab (`trigger: "tab"`) or a round button (`trigger: "button"`), or
19
+ nothing shows (`trigger: "none"`, where the host calls `open()`).
20
20
  - **capturing:** the trigger and any open panel are hidden, so they are not in the image. The
21
21
  screen is captured (SPEC §5), and a screen reader is told the `capturing` text.
22
22
  - **capture error:** the `captureError` text, with "Try again" (back to capturing), "Send without
@@ -48,18 +48,25 @@ annotating, draft --Cancel (it may ask first, below) / Discard--> idle
48
48
  the same question when it would throw work away, close the dialog, and the draft goes with it.
49
49
 
50
50
  ## 3. The trigger
51
- - It is a small round button, 2.5em across by default (40 px on a 16 px site), in a corner
52
- (`position`), with an icon and the `trigger` accessible name. A host that gives `triggerText`
53
- gets that short label beside the icon, as a pill; with only `triggerText` given, that text is
54
- the name too. A host that sets both keeps `triggerText` inside `trigger`, so
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.
62
- - It sits 16 px from the edges by default, and it respects `env(safe-area-inset-*)`.
51
+ - By default it is a small tab, 2.5em square (40 px on a 16 px site), in the corner `position`
52
+ names, square on both edges there, with a flag icon and the `trigger` accessible name.
53
+ `--vf-offset` lifts it along its side edge, and then its end side is rounded too.
54
+ `trigger: "button"` makes it a round button in that corner instead, 16 px from both edges.
55
+ Either has no border: the text colour fills it, and the icon and label take the background
56
+ colour. On hover, on a device that can hover, the two swap back.
57
+ A host that gives `triggerText` gets that short label beside a small icon, in a wider tab, or a
58
+ pill as a button; with only `triggerText` given, that text is the name too. A host that sets
59
+ both keeps `triggerText` inside `trigger`, so speech control can say what it sees. It has no
60
+ badge, and it does not move or pulse.
61
+ - Its tooltip is its own, not the browser's `title`: a pill beside it, on the page's side, with an
62
+ arrow pointing at it. It fades in at once on keyboard focus, or on hover where the device can
63
+ hover, and out at the same speed.
64
+ It reads the name. With a `shortcut` that can match, it is the name and the keys, each modifier
65
+ once, joined by ` + `, as the platform's keyboards name them: Option, Command, Control and Shift
66
+ on a Mac, iPhone or iPad (`navigator.platform`), as in `Send feedback (Option + Shift + F)`;
67
+ Alt, Win, Ctrl and Shift on Windows; Alt, Super, Ctrl and Shift elsewhere. A screen reader reads
68
+ the tooltip as the description.
69
+ - It respects `env(safe-area-inset-*)`.
63
70
  - It is hidden while there is a draft, whether the dialog is open or kept in the draft bar.
64
71
  - It does not move to keep clear of the page's content. The host moves it off its own content
65
72
  with `position` or `--vf-offset`.
package/docs/THEMING.md CHANGED
@@ -39,8 +39,8 @@ Set these on `visual-feedback` (or on any ancestor). Custom properties cross the
39
39
  |---|---|---|
40
40
  | `--vf-font` | `inherit` | font family of all text, such as `Inter, sans-serif` |
41
41
  | `--vf-radius` | from the page's button corners, so the widget's buttons match them up to 15px (this is 1.6 times theirs, up to `24px`), else `16px` | panel and composer, and a share of it for buttons and the image |
42
- | `--vf-surface` | with `theme: "auto"` the page's background, else `Canvas` | panel, tool rail and composer background |
43
- | `--vf-text` | with `theme: "auto"` the page's text colour, else `CanvasText` | text |
42
+ | `--vf-surface` | with `theme: "auto"` the page's background, else `Canvas` | panel, tool rail and composer background, the trigger's icon and label, its fill on hover, and the text of its tooltip |
43
+ | `--vf-text` | with `theme: "auto"` the page's text colour, else `CanvasText` | text, the trigger's fill, its icon and label on hover, and its tooltip's background |
44
44
  | `--vf-muted` | text at 60%, more on a page whose text would make that too faint | hints, counters |
45
45
  | `--vf-border` | text at 15% | hairlines |
46
46
  | `--vf-accent` | the page's accent, else `AccentColor`, or `#0b57d0` where the browser has no `AccentColor` | Send button and its menu button, "Capture this view", active tool, chosen screenshot |
@@ -48,9 +48,9 @@ Set these on `visual-feedback` (or on any ancestor). Custom properties cross the
48
48
  | `--vf-focus` | `--vf-accent` | focus ring |
49
49
  | `--vf-mark` | `#e5243b` | marks on the screenshot |
50
50
  | `--vf-backdrop` | `rgb(0 0 0 / 45%)` | overlay behind the image |
51
- | `--vf-shadow` | `0 12px 32px -12px rgb(0 0 0 / 35%)` | trigger, panel and draft bar |
51
+ | `--vf-shadow` | `0 12px 32px -12px rgb(0 0 0 / 35%)` | trigger, its tooltip, panel and draft bar |
52
52
  | `--vf-trigger-size` | `2.5em`, 40px on a 16px site | trigger height, and its width when it shows the icon alone |
53
- | `--vf-offset` | `16px` | trigger and draft bar distance from the edges |
53
+ | `--vf-offset` | `16px`, and `0px` for the tab; a length, not a percentage | draft bar and round trigger distance from the edges; the tab keeps to its side edge, and this lifts it along it |
54
54
  | `--vf-z` | `2147483000` | stacking |
55
55
 
56
56
  ```css
@@ -62,8 +62,8 @@ visual-feedback {
62
62
  ```
63
63
 
64
64
  ## 3. `::part()`
65
- Every visible element has a part name: `trigger`, `trigger-icon`, `trigger-text`, `backdrop`,
66
- `dialog`, `header`, `title`, `minimise`, `body`, `stage`, `image`, `toolbar`, `tool`, `tool-active`,
65
+ Every visible element has a part name: `trigger`, `trigger-icon`, `trigger-text`, `trigger-tooltip`,
66
+ `trigger-tooltip-text`, `backdrop`, `dialog`, `header`, `title`, `minimise`, `body`, `stage`, `image`, `toolbar`, `tool`, `tool-active`,
67
67
  `divider`, `undo`, `clear`, `remove`, `icon`, `text-input`, `selection`, `strip`, `thumb`,
68
68
  `thumb-pick`, `thumb-image`, `thumb-number`, `thumb-remove`, `add`, `label`, `composer`, `note`,
69
69
  `composer-bar`, `category`, `footer`, `split`, `send-more`, `menu`, `save-pdf`, `actions`, `cancel`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "visual-feedback",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
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",
@@ -73,7 +73,6 @@
73
73
  "@axe-core/playwright": "^4.13.0",
74
74
  "@biomejs/biome": "^2.5.15",
75
75
  "@electric-sql/pglite": "0.5.8",
76
- "@electric-sql/pglite-socket": "^0.2.11",
77
76
  "@playwright/test": "^1.64.0",
78
77
  "@size-limit/preset-small-lib": "^14.2.0",
79
78
  "@supabase/supabase-js": "^2.117.3",
@@ -81,7 +80,6 @@
81
80
  "@types/react": "^19.3.0",
82
81
  "aws4fetch": "^1.0.20",
83
82
  "next": "^16.4.0",
84
- "postgres": "^3.4.9",
85
83
  "publint": "^0.3.25",
86
84
  "react": "^19.3.0",
87
85
  "react-dom": "^19.3.0",