@qaiddev/thumbs-embed 1.5.0 → 1.6.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/README.md CHANGED
@@ -26,13 +26,21 @@ const feedback = new QaidEmbed({
26
26
 
27
27
  ```html
28
28
  <script
29
- src="https://unpkg.com/@qaiddev/thumbs-embed/dist/qaid.umd.cjs"
29
+ src="https://unpkg.com/@qaiddev/thumbs-embed/dist/embed.js"
30
30
  data-endpoint="https://qaid.dev/api/feedback"
31
31
  data-api-key="YOUR_API_KEY"
32
32
  ></script>
33
33
  ```
34
34
 
35
- The embed auto-initializes when it detects a `data-endpoint` attribute on its script tag.
35
+ `embed.js` is a tiny classic loader that works in every browser: it feature-detects
36
+ ES-module support and pulls in the right build automatically — the **progressive
37
+ loader** (`loader.js`, a small core with screenshot/video/annotation/targeting loading
38
+ on demand) on modern browsers, or the all-in-one UMD bundle (`qaid.umd.cjs`) on older
39
+ ones. The injected build auto-initializes when it detects a `data-endpoint` attribute.
40
+
41
+ > Prefer to skip the loader's feature-detect hop? Point straight at
42
+ > `dist/loader.js` with `type="module"` (modern browsers only), or
43
+ > `dist/qaid.umd.cjs` as a classic script (works everywhere, no code-splitting).
36
44
 
37
45
  ### JSON Config (Script Tag)
38
46
 
@@ -55,7 +63,7 @@ For complex configurations, use a separate JSON config element:
55
63
  }
56
64
  }
57
65
  </script>
58
- <script src="https://unpkg.com/@qaiddev/thumbs-embed/dist/qaid.umd.cjs"></script>
66
+ <script src="https://unpkg.com/@qaiddev/thumbs-embed/dist/embed.js"></script>
59
67
  ```
60
68
 
61
69
  ## How It Works
@@ -42,6 +42,7 @@ export interface AnnotationLabels {
42
42
  undo: string;
43
43
  skip: string;
44
44
  done: string;
45
+ colors: string;
45
46
  }
46
47
  /**
47
48
  * Normalise two drag corners into a top-left origin plus positive width/height,
@@ -78,6 +79,8 @@ export interface AnnotationEditorOptions {
78
79
  quality?: number;
79
80
  /** Default stroke colour for rectangle/arrow/pen. */
80
81
  color?: string;
82
+ /** Colour swatches offered in the toolbar. The active `color` is ensured present. */
83
+ palette?: string[];
81
84
  /** Default stroke width. */
82
85
  strokeWidth?: number;
83
86
  /** Label overrides. */
@@ -104,7 +107,8 @@ export declare class AnnotationEditor {
104
107
  tool: ShapeType;
105
108
  private readonly opts;
106
109
  private readonly labels;
107
- private readonly color;
110
+ private color;
111
+ private readonly palette;
108
112
  private readonly strokeWidth;
109
113
  private readonly uid;
110
114
  private container;
@@ -121,6 +125,8 @@ export declare class AnnotationEditor {
121
125
  /** Mount the editor and resolve when the user commits (Done) or skips. */
122
126
  open(): Promise<string | null>;
123
127
  private toolButton;
128
+ /** A round colour swatch button for the toolbar's colour group. */
129
+ private swatchButton;
124
130
  private actionButton;
125
131
  private mount;
126
132
  /** Convert a pointer event's client coordinates into natural image pixels. */
@@ -129,6 +135,8 @@ export declare class AnnotationEditor {
129
135
  redraw(): void;
130
136
  /** Select the active drawing tool and reflect it on the toolbar. */
131
137
  selectTool(tool: ShapeType): void;
138
+ /** Set the active drawing colour; subsequent shapes use it. */
139
+ selectColor(color: string): void;
132
140
  private onToolbarClick;
133
141
  onPointerDown: (e: PointerEvent) => void;
134
142
  onPointerMove: (e: PointerEvent) => void;
@@ -1,44 +1,46 @@
1
- const O = `<svg xmlns="http://www.w3.org/2000/svg" class="qaid-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
1
+ const j = `<svg xmlns="http://www.w3.org/2000/svg" class="qaid-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
2
2
  <path stroke-linecap="round" stroke-linejoin="round" d="M14 10h4.764a2 2 0 011.789 2.894l-3.5 7A2 2 0 0115.263 21h-4.017c-.163 0-.326-.02-.485-.06L7 20m7-10V5a2 2 0 00-2-2h-.095c-.5 0-.905.405-.905.905 0 .714-.211 1.412-.608 2.006L7 11v9m7-10h-2M7 20H5a2 2 0 01-2-2v-6a2 2 0 012-2h2.5"/>
3
- </svg>`, N = `<svg xmlns="http://www.w3.org/2000/svg" class="qaid-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
3
+ </svg>`, W = `<svg xmlns="http://www.w3.org/2000/svg" class="qaid-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
4
4
  <path stroke-linecap="round" stroke-linejoin="round" d="M10 14H5.236a2 2 0 01-1.789-2.894l3.5-7A2 2 0 018.737 3h4.018a2 2 0 01.485.06l3.76.94m-7 10v5a2 2 0 002 2h.095c.5 0 .905-.405.905-.905 0-.714.211-1.412.608-2.006L17 13V4m-7 10h2m5-10h2a2 2 0 012 2v6a2 2 0 01-2 2h-2.5"/>
5
- </svg>`, S = `<svg xmlns="http://www.w3.org/2000/svg" class="qaid-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
5
+ </svg>`, $ = `<svg xmlns="http://www.w3.org/2000/svg" class="qaid-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
6
6
  <path stroke-linecap="round" stroke-linejoin="round" d="M15.75 10.5l4.72-4.72a.75.75 0 011.28.53v11.38a.75.75 0 01-1.28.53l-4.72-4.72M4.5 18.75h9a2.25 2.25 0 002.25-2.25v-9a2.25 2.25 0 00-2.25-2.25h-9A2.25 2.25 0 002.25 7.5v9a2.25 2.25 0 002.25 2.25z"/>
7
- </svg>`, f = `<svg xmlns="http://www.w3.org/2000/svg" class="qaid-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
7
+ </svg>`, H = `<svg xmlns="http://www.w3.org/2000/svg" class="qaid-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
8
+ <path stroke-linecap="round" stroke-linejoin="round" d="M8 10h8M8 14h5m-9 6l3.5-3.5H18a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v14z"/>
9
+ </svg>`, m = `<svg xmlns="http://www.w3.org/2000/svg" class="qaid-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
8
10
  <rect x="4" y="6" width="16" height="12" rx="1" stroke-linejoin="round"/>
9
- </svg>`, k = `<svg xmlns="http://www.w3.org/2000/svg" class="qaid-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
11
+ </svg>`, C = `<svg xmlns="http://www.w3.org/2000/svg" class="qaid-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
10
12
  <path stroke-linecap="round" stroke-linejoin="round" d="M5 19L19 5m0 0h-8m8 0v8"/>
11
- </svg>`, m = `<svg xmlns="http://www.w3.org/2000/svg" class="qaid-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
12
- <path stroke-linecap="round" stroke-linejoin="round" d="M12 20h9M16.5 3.5a2.121 2.121 0 013 3L7 19l-4 1 1-4L16.5 3.5z"/>
13
13
  </svg>`, y = `<svg xmlns="http://www.w3.org/2000/svg" class="qaid-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
14
+ <path stroke-linecap="round" stroke-linejoin="round" d="M12 20h9M16.5 3.5a2.121 2.121 0 013 3L7 19l-4 1 1-4L16.5 3.5z"/>
15
+ </svg>`, q = `<svg xmlns="http://www.w3.org/2000/svg" class="qaid-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
14
16
  <path stroke-linecap="round" stroke-linejoin="round" d="M12 3s6 6.4 6 10a6 6 0 01-12 0c0-3.6 6-10 6-10z"/>
15
- </svg>`, C = `<svg xmlns="http://www.w3.org/2000/svg" class="qaid-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
17
+ </svg>`, E = `<svg xmlns="http://www.w3.org/2000/svg" class="qaid-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
16
18
  <path stroke-linecap="round" stroke-linejoin="round" d="M9 14L4 9l5-5M4 9h11a5 5 0 010 10h-4"/>
17
- </svg>`, q = `<svg xmlns="http://www.w3.org/2000/svg" class="qaid-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
19
+ </svg>`, L = `<svg xmlns="http://www.w3.org/2000/svg" class="qaid-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
18
20
  <path stroke-linecap="round" stroke-linejoin="round" d="M20 6L9 17l-5-5"/>
19
- </svg>`, E = `<svg xmlns="http://www.w3.org/2000/svg" class="qaid-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
21
+ </svg>`, M = `<svg xmlns="http://www.w3.org/2000/svg" class="qaid-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
20
22
  <path stroke-linecap="round" stroke-linejoin="round" d="M6 6l12 12M18 6L6 18"/>
21
23
  </svg>`;
22
24
  function r(n, t, e) {
23
25
  const o = document.createElement(n);
24
26
  if (t)
25
- for (const [i, s] of Object.entries(t))
26
- i === "className" ? o.className = s : o.setAttribute(i, s);
27
+ for (const [s, i] of Object.entries(t))
28
+ s === "className" ? o.className = i : o.setAttribute(s, i);
27
29
  return o;
28
30
  }
29
- function U(n = 640) {
31
+ function F(n = 640) {
30
32
  return typeof window < "u" && window.innerWidth < n;
31
33
  }
32
- function W(n, t, e) {
33
- const o = e.map((s) => s.style.visibility);
34
- e.forEach((s) => s.style.visibility = "hidden");
35
- const i = document.elementFromPoint(n, t);
36
- return e.forEach((s, a) => s.style.visibility = o[a]), i;
34
+ function K(n, t, e) {
35
+ const o = e.map((i) => i.style.visibility);
36
+ e.forEach((i) => i.style.visibility = "hidden");
37
+ const s = document.elementFromPoint(n, t);
38
+ return e.forEach((i, a) => i.style.visibility = o[a]), s;
37
39
  }
38
- function j(n) {
40
+ function z(n) {
39
41
  return n ? n.hasAttribute("data-qaid-embed") || n.hasAttribute("data-qaid-embed-overlay") ? !0 : !!n.closest("[data-qaid-embed], [data-qaid-embed-overlay]") : !1;
40
42
  }
41
- function H(n, t = 0) {
43
+ function V(n, t = 0) {
42
44
  const e = n.getBoundingClientRect();
43
45
  return {
44
46
  x: e.left - t,
@@ -47,7 +49,19 @@ function H(n, t = 0) {
47
49
  height: e.height + t * 2
48
50
  };
49
51
  }
50
- const l = 12, M = "#ef4444", B = 4, L = 0.8, A = {
52
+ const h = 12, A = "#ef4444", B = ["#ef4444", "#f59e0b", "#22c55e", "#3b82f6", "#111827", "#ffffff"], T = {
53
+ "#ef4444": "red",
54
+ "#f59e0b": "amber",
55
+ "#22c55e": "green",
56
+ "#3b82f6": "blue",
57
+ "#111827": "black",
58
+ "#ffffff": "white",
59
+ "#6366f1": "indigo"
60
+ };
61
+ function w(n) {
62
+ return T[n.toLowerCase()] ?? n;
63
+ }
64
+ const I = 4, D = 0.8, P = {
51
65
  title: "Annotate screenshot",
52
66
  instructions: "Draw on the screenshot to highlight or hide details, then choose Done to attach it or Skip to send the original.",
53
67
  rectangle: "Rectangle",
@@ -56,7 +70,8 @@ const l = 12, M = "#ef4444", B = 4, L = 0.8, A = {
56
70
  blur: "Blur or redact",
57
71
  undo: "Undo last",
58
72
  skip: "Skip annotation",
59
- done: "Done"
73
+ done: "Done",
74
+ colors: "Drawing colour"
60
75
  };
61
76
  function u(n, t) {
62
77
  return {
@@ -66,23 +81,23 @@ function u(n, t) {
66
81
  h: Math.abs(n.y - t.y)
67
82
  };
68
83
  }
69
- function I(n) {
84
+ function x(n) {
70
85
  if (n.type === "pen") return n.points.length < 2;
71
86
  const t = u(n.points[0], n.points[1]);
72
87
  return t.w < 3 && t.h < 3;
73
88
  }
74
- function T(n, t, e, o) {
89
+ function _(n, t, e, o) {
75
90
  n.beginPath(), n.moveTo(t.x, t.y), n.lineTo(e.x, e.y), n.stroke();
76
- const i = Math.atan2(e.y - t.y, e.x - t.x), s = Math.max(10, o * 3);
91
+ const s = Math.atan2(e.y - t.y, e.x - t.x), i = Math.max(10, o * 3);
77
92
  n.beginPath(), n.moveTo(e.x, e.y), n.lineTo(
78
- e.x - s * Math.cos(i - Math.PI / 6),
79
- e.y - s * Math.sin(i - Math.PI / 6)
93
+ e.x - i * Math.cos(s - Math.PI / 6),
94
+ e.y - i * Math.sin(s - Math.PI / 6)
80
95
  ), n.moveTo(e.x, e.y), n.lineTo(
81
- e.x - s * Math.cos(i + Math.PI / 6),
82
- e.y - s * Math.sin(i + Math.PI / 6)
96
+ e.x - i * Math.cos(s + Math.PI / 6),
97
+ e.y - i * Math.sin(s + Math.PI / 6)
83
98
  ), n.stroke();
84
99
  }
85
- function P(n, t) {
100
+ function O(n, t) {
86
101
  if (t.length !== 0) {
87
102
  n.beginPath(), n.moveTo(t[0].x, t[0].y);
88
103
  for (let e = 1; e < t.length; e++)
@@ -90,15 +105,15 @@ function P(n, t) {
90
105
  n.stroke();
91
106
  }
92
107
  }
93
- function x(n, t, e, o, i) {
94
- n.fillStyle = "rgb(15, 23, 42)", n.fillRect(t, e, o, i), n.fillStyle = "rgb(37, 47, 63)";
95
- for (let s = e; s < e + i; s += l)
96
- for (let a = t; a < t + o; a += l)
97
- (Math.floor((a - t) / l) + Math.floor((s - e) / l)) % 2 === 0 && n.fillRect(
108
+ function R(n, t, e, o, s) {
109
+ n.fillStyle = "rgb(15, 23, 42)", n.fillRect(t, e, o, s), n.fillStyle = "rgb(37, 47, 63)";
110
+ for (let i = e; i < e + s; i += h)
111
+ for (let a = t; a < t + o; a += h)
112
+ (Math.floor((a - t) / h) + Math.floor((i - e) / h)) % 2 === 0 && n.fillRect(
98
113
  a,
99
- s,
100
- Math.min(l, t + o - a),
101
- Math.min(l, e + i - s)
114
+ i,
115
+ Math.min(h, t + o - a),
116
+ Math.min(h, e + s - i)
102
117
  );
103
118
  }
104
119
  function p(n, t) {
@@ -109,51 +124,52 @@ function p(n, t) {
109
124
  break;
110
125
  }
111
126
  case "arrow":
112
- T(n, t.points[0], t.points[1], t.strokeWidth);
127
+ _(n, t.points[0], t.points[1], t.strokeWidth);
113
128
  break;
114
129
  case "pen":
115
- P(n, t.points);
130
+ O(n, t.points);
116
131
  break;
117
132
  case "blur": {
118
133
  const e = u(t.points[0], t.points[1]);
119
- x(n, e.x, e.y, e.w, e.h);
134
+ R(n, e.x, e.y, e.w, e.h);
120
135
  break;
121
136
  }
122
137
  }
123
138
  n.restore();
124
139
  }
125
- function w(n) {
140
+ function v(n) {
126
141
  return new Promise((t, e) => {
127
142
  const o = new Image();
128
143
  o.onload = () => t(o), o.onerror = () => e(new Error("Failed to load screenshot")), o.src = n;
129
144
  });
130
145
  }
131
- async function v(n, t, e = {}) {
132
- const o = e.quality ?? L;
133
- let i;
146
+ async function g(n, t, e = {}) {
147
+ const o = e.quality ?? D;
148
+ let s;
134
149
  try {
135
- i = await w(n);
150
+ s = await v(n);
136
151
  } catch {
137
152
  return n;
138
153
  }
139
- const s = document.createElement("canvas");
140
- s.width = i.naturalWidth || i.width || 1, s.height = i.naturalHeight || i.height || 1;
141
- const a = s.getContext("2d");
154
+ const i = document.createElement("canvas");
155
+ i.width = s.naturalWidth || s.width || 1, i.height = s.naturalHeight || s.height || 1;
156
+ const a = i.getContext("2d");
142
157
  if (!a) return n;
143
- a.drawImage(i, 0, 0, s.width, s.height);
144
- for (const d of t)
145
- p(a, d);
146
- return s.toDataURL("image/webp", o);
158
+ a.drawImage(s, 0, 0, i.width, i.height);
159
+ for (const c of t)
160
+ p(a, c);
161
+ return i.toDataURL("image/webp", o);
147
162
  }
148
- let D = 0;
149
- class g {
163
+ let N = 0;
164
+ class f {
150
165
  shapes = [];
151
166
  tool = "rect";
152
167
  opts;
153
168
  labels;
154
169
  color;
170
+ palette;
155
171
  strokeWidth;
156
- uid = `qaid-annotate-${++D}`;
172
+ uid = `qaid-annotate-${++N}`;
157
173
  container;
158
174
  canvas;
159
175
  ctx = null;
@@ -167,7 +183,7 @@ class g {
167
183
  // after the editor is mounted).
168
184
  resolveResult;
169
185
  constructor(t) {
170
- this.opts = t, this.labels = { ...A, ...t.labels ?? {} }, this.color = t.color ?? M, this.strokeWidth = t.strokeWidth ?? B;
186
+ this.opts = t, this.labels = { ...P, ...t.labels ?? {} }, this.color = t.color ?? A, this.palette = (t.palette && t.palette.length ? t.palette : B).slice(), this.palette.some((e) => e.toLowerCase() === this.color.toLowerCase()) || this.palette.unshift(this.color), this.strokeWidth = t.strokeWidth ?? I;
171
187
  }
172
188
  /** Mount the editor and resolve when the user commits (Done) or skips. */
173
189
  open() {
@@ -177,7 +193,7 @@ class g {
177
193
  return this.mount(), t;
178
194
  }
179
195
  toolButton(t, e, o) {
180
- const i = r("button", {
196
+ const s = r("button", {
181
197
  type: "button",
182
198
  class: "qaid-annotate-tool",
183
199
  "data-qaid-tool": t,
@@ -185,17 +201,29 @@ class g {
185
201
  "aria-label": `${o} tool`,
186
202
  title: o
187
203
  });
188
- return i.innerHTML = e, i;
204
+ return s.innerHTML = `${e}<span class="qaid-annotate-btn-text">${o}</span>`, s;
189
205
  }
190
- actionButton(t, e, o, i) {
191
- const s = r("button", {
206
+ /** A round colour swatch button for the toolbar's colour group. */
207
+ swatchButton(t) {
208
+ const e = w(t), o = r("button", {
209
+ type: "button",
210
+ class: "qaid-annotate-swatch",
211
+ "data-qaid-color": t,
212
+ "aria-pressed": t.toLowerCase() === this.color.toLowerCase() ? "true" : "false",
213
+ "aria-label": `Draw in ${e}`,
214
+ title: e
215
+ });
216
+ return o.style.setProperty("--qaid-swatch", t), o;
217
+ }
218
+ actionButton(t, e, o, s) {
219
+ const i = r("button", {
192
220
  type: "button",
193
- class: `qaid-annotate-action ${i}`,
221
+ class: `qaid-annotate-action ${s}`,
194
222
  "data-qaid-action": t,
195
223
  "aria-label": o,
196
224
  title: o
197
225
  });
198
- return s.innerHTML = `${e}<span class="qaid-annotate-btn-text">${o}</span>`, s;
226
+ return i.innerHTML = `${e}<span class="qaid-annotate-btn-text">${o}</span>`, i;
199
227
  }
200
228
  mount() {
201
229
  const t = `${this.uid}-title`, e = `${this.uid}-desc`;
@@ -205,35 +233,42 @@ class g {
205
233
  class: "qaid-annotate-title"
206
234
  });
207
235
  o.textContent = this.labels.title;
208
- const i = r("p", {
236
+ const s = r("p", {
209
237
  id: e,
210
238
  class: "qaid-annotate-desc"
211
239
  });
212
- i.textContent = this.labels.instructions;
213
- const s = r("div", { class: "qaid-annotate-stage" });
214
- this.canvas = document.createElement("canvas"), this.canvas.className = "qaid-annotate-canvas", this.canvas.width = 1, this.canvas.height = 1, this.canvas.setAttribute("role", "img"), this.canvas.setAttribute("aria-label", this.labels.title), this.ctx = this.canvas.getContext("2d"), s.appendChild(this.canvas), this.toolbar = r("div", {
240
+ s.textContent = this.labels.instructions;
241
+ const i = r("div", { class: "qaid-annotate-stage" });
242
+ this.canvas = document.createElement("canvas"), this.canvas.className = "qaid-annotate-canvas", this.canvas.width = 1, this.canvas.height = 1, this.canvas.setAttribute("role", "img"), this.canvas.setAttribute("aria-label", this.labels.title), this.ctx = this.canvas.getContext("2d"), i.appendChild(this.canvas), this.toolbar = r("div", {
215
243
  class: "qaid-annotate-toolbar",
216
244
  role: "toolbar",
217
245
  "aria-label": this.labels.title
218
246
  });
219
247
  const a = r("div", { class: "qaid-annotate-tools" });
220
- a.appendChild(this.toolButton("rect", f, this.labels.rectangle)), a.appendChild(this.toolButton("arrow", k, this.labels.arrow)), a.appendChild(this.toolButton("pen", m, this.labels.pen)), a.appendChild(this.toolButton("blur", y, this.labels.blur)), this.undoBtn = this.actionButton("undo", C, this.labels.undo, "qaid-annotate-undo"), this.undoBtn.disabled = !0;
221
- const d = this.actionButton("skip", E, this.labels.skip, "qaid-annotate-skip"), b = this.actionButton("done", q, this.labels.done, "qaid-annotate-done"), c = r("div", { class: "qaid-annotate-actions" });
222
- c.appendChild(this.undoBtn), c.appendChild(d), c.appendChild(b), this.toolbar.appendChild(a), this.toolbar.appendChild(c), this.container.appendChild(o), this.container.appendChild(i), this.container.appendChild(s), this.container.appendChild(this.toolbar), this.opts.root.appendChild(this.container), this.toolbar.addEventListener("click", this.onToolbarClick), this.canvas.addEventListener("pointerdown", this.onPointerDown), this.container.addEventListener("pointermove", this.onPointerMove), this.container.addEventListener("pointerup", this.onPointerUp), this.container.addEventListener("keydown", this.onKeyDown), this.opts.openDialog?.(this.container, {
248
+ a.appendChild(this.toolButton("rect", m, this.labels.rectangle)), a.appendChild(this.toolButton("arrow", C, this.labels.arrow)), a.appendChild(this.toolButton("pen", y, this.labels.pen)), a.appendChild(this.toolButton("blur", q, this.labels.blur));
249
+ const c = r("div", {
250
+ class: "qaid-annotate-colors",
251
+ role: "group",
252
+ "aria-label": this.labels.colors
253
+ });
254
+ for (const l of this.palette) c.appendChild(this.swatchButton(l));
255
+ this.undoBtn = this.actionButton("undo", E, this.labels.undo, "qaid-annotate-undo"), this.undoBtn.disabled = !0;
256
+ const b = this.actionButton("skip", M, this.labels.skip, "qaid-annotate-skip"), k = this.actionButton("done", L, this.labels.done, "qaid-annotate-done"), d = r("div", { class: "qaid-annotate-actions" });
257
+ d.appendChild(this.undoBtn), d.appendChild(b), d.appendChild(k), this.toolbar.appendChild(a), this.toolbar.appendChild(c), this.toolbar.appendChild(d), this.container.appendChild(o), this.container.appendChild(s), this.container.appendChild(i), this.container.appendChild(this.toolbar), this.opts.root.appendChild(this.container), this.toolbar.addEventListener("click", this.onToolbarClick), this.canvas.addEventListener("pointerdown", this.onPointerDown), this.container.addEventListener("pointermove", this.onPointerMove), this.container.addEventListener("pointerup", this.onPointerUp), this.container.addEventListener("keydown", this.onKeyDown), this.opts.openDialog?.(this.container, {
223
258
  labelledbyId: t,
224
259
  describedbyId: e
225
- }), this.opts.announce?.(this.labels.instructions), w(this.opts.dataUrl).then((h) => {
226
- this.img = h, this.canvas.width = h.naturalWidth || h.width || 1, this.canvas.height = h.naturalHeight || h.height || 1, this.redraw();
260
+ }), this.opts.announce?.(this.labels.instructions), v(this.opts.dataUrl).then((l) => {
261
+ this.img = l, this.canvas.width = l.naturalWidth || l.width || 1, this.canvas.height = l.naturalHeight || l.height || 1, this.redraw();
227
262
  }).catch(() => {
228
263
  this.img = null, this.redraw();
229
264
  });
230
265
  }
231
266
  /** Convert a pointer event's client coordinates into natural image pixels. */
232
267
  toImageCoords(t) {
233
- const e = this.canvas.getBoundingClientRect(), o = this.canvas.width / (e.width || this.canvas.width), i = this.canvas.height / (e.height || this.canvas.height);
268
+ const e = this.canvas.getBoundingClientRect(), o = this.canvas.width / (e.width || this.canvas.width), s = this.canvas.height / (e.height || this.canvas.height);
234
269
  return {
235
270
  x: (t.clientX - e.left) * o,
236
- y: (t.clientY - e.top) * i
271
+ y: (t.clientY - e.top) * s
237
272
  };
238
273
  }
239
274
  /** Repaint the display canvas: base image (if loaded) then every shape. */
@@ -248,25 +283,41 @@ class g {
248
283
  }
249
284
  /** Select the active drawing tool and reflect it on the toolbar. */
250
285
  selectTool(t) {
251
- this.tool = t, this.toolbar.querySelectorAll("[data-qaid-tool]").forEach((i) => {
252
- i.setAttribute(
286
+ this.tool = t, this.toolbar.querySelectorAll("[data-qaid-tool]").forEach((s) => {
287
+ s.setAttribute(
253
288
  "aria-pressed",
254
- i.getAttribute("data-qaid-tool") === t ? "true" : "false"
289
+ s.getAttribute("data-qaid-tool") === t ? "true" : "false"
255
290
  );
256
291
  });
257
- const o = this.labels[_[t]];
292
+ const o = this.labels[S[t]];
258
293
  this.opts.announce?.(`${o} tool selected`);
259
294
  }
295
+ /** Set the active drawing colour; subsequent shapes use it. */
296
+ selectColor(t) {
297
+ this.color = t, this.toolbar.querySelectorAll("[data-qaid-color]").forEach((o) => {
298
+ o.setAttribute(
299
+ "aria-pressed",
300
+ (o.getAttribute("data-qaid-color") ?? "").toLowerCase() === t.toLowerCase() ? "true" : "false"
301
+ );
302
+ }), this.opts.announce?.(`${w(t)} colour selected`);
303
+ }
260
304
  onToolbarClick = (t) => {
261
- const o = t.target?.closest("[data-qaid-tool],[data-qaid-action]");
305
+ const o = t.target?.closest(
306
+ "[data-qaid-tool],[data-qaid-action],[data-qaid-color]"
307
+ );
262
308
  if (!o) return;
309
+ const s = o.getAttribute("data-qaid-color");
310
+ if (s) {
311
+ this.selectColor(s);
312
+ return;
313
+ }
263
314
  const i = o.getAttribute("data-qaid-tool");
264
315
  if (i) {
265
316
  this.selectTool(i);
266
317
  return;
267
318
  }
268
- const s = o.getAttribute("data-qaid-action");
269
- s === "undo" ? this.undo() : s === "skip" ? this.skip() : s === "done" && this.done();
319
+ const a = o.getAttribute("data-qaid-action");
320
+ a === "undo" ? this.undo() : a === "skip" ? this.skip() : a === "done" && this.done();
270
321
  };
271
322
  onPointerDown = (t) => {
272
323
  t.preventDefault?.(), this.drawing = !0;
@@ -287,7 +338,7 @@ class g {
287
338
  if (!this.drawing || !this.current) return;
288
339
  this.drawing = !1;
289
340
  const t = this.current;
290
- if (this.current = null, I(t)) {
341
+ if (this.current = null, x(t)) {
291
342
  this.redraw();
292
343
  return;
293
344
  }
@@ -316,7 +367,7 @@ class g {
316
367
  /** Composite the annotations and resolve with the new WebP data URL. */
317
368
  async done() {
318
369
  if (this.settled) return;
319
- const t = await v(this.opts.dataUrl, this.shapes, {
370
+ const t = await g(this.opts.dataUrl, this.shapes, {
320
371
  quality: this.opts.quality
321
372
  });
322
373
  this.opts.announce?.("Annotated screenshot attached"), this.settle(t);
@@ -332,36 +383,37 @@ class g {
332
383
  this.toolbar.removeEventListener("click", this.onToolbarClick), this.canvas.removeEventListener("pointerdown", this.onPointerDown), this.container.removeEventListener("pointermove", this.onPointerMove), this.container.removeEventListener("pointerup", this.onPointerUp), this.container.removeEventListener("keydown", this.onKeyDown), this.opts.closeDialog?.(), this.container.remove();
333
384
  }
334
385
  }
335
- const _ = {
386
+ const S = {
336
387
  rect: "rectangle",
337
388
  arrow: "arrow",
338
389
  pen: "pen",
339
390
  blur: "blur"
340
391
  };
341
- function R(n) {
342
- return new g(n).open();
392
+ function U(n) {
393
+ return new f(n).open();
343
394
  }
344
- const $ = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
395
+ const Y = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
345
396
  __proto__: null,
346
- AnnotationEditor: g,
347
- compositeAnnotations: v,
397
+ AnnotationEditor: f,
398
+ compositeAnnotations: g,
348
399
  drawShape: p,
349
- openAnnotationEditor: R,
400
+ openAnnotationEditor: U,
350
401
  rectFromPoints: u
351
402
  }, Symbol.toStringTag, { value: "Module" }));
352
403
  export {
353
- g as A,
354
- S as R,
355
- O as T,
356
- N as a,
357
- j as b,
358
- v as c,
404
+ f as A,
405
+ H as F,
406
+ $ as R,
407
+ j as T,
408
+ W as a,
409
+ z as b,
410
+ g as c,
359
411
  p as d,
360
- W as e,
361
- $ as f,
362
- H as g,
363
- U as i,
364
- R as o,
412
+ K as e,
413
+ Y as f,
414
+ V as g,
415
+ F as i,
416
+ U as o,
365
417
  u as r
366
418
  };
367
- //# sourceMappingURL=annotate-BNibaDg3.js.map
419
+ //# sourceMappingURL=annotate-pSxJNPPL.js.map