@checkflow/sdk 1.1.3 → 1.1.4

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/dist/index.mjs CHANGED
@@ -2,9 +2,6 @@ import {
2
2
  captureScreenshot,
3
3
  clearScreenshot
4
4
  } from "./chunk-6EVTRC5X.mjs";
5
- import {
6
- startHighlighting
7
- } from "./chunk-CD33QAA6.mjs";
8
5
 
9
6
  // src/collector.ts
10
7
  function collectContext() {
@@ -179,6 +176,1088 @@ function clearNetworkLogs() {
179
176
  logs2 = [];
180
177
  }
181
178
 
179
+ // src/annotation/types.ts
180
+ var DEFAULT_STYLE = {
181
+ strokeColor: "#FF3B30",
182
+ strokeWidth: 3,
183
+ fillColor: "transparent",
184
+ opacity: 1,
185
+ fontSize: 16,
186
+ fontFamily: "system-ui, -apple-system, sans-serif"
187
+ };
188
+ var HIGHLIGHT_STYLE = {
189
+ strokeColor: "transparent",
190
+ fillColor: "#FFEB3B",
191
+ opacity: 0.4
192
+ };
193
+ var BLUR_STYLE = {
194
+ strokeColor: "#666666",
195
+ fillColor: "#666666",
196
+ opacity: 0.8
197
+ };
198
+ var COLOR_PALETTE = [
199
+ "#FF3B30",
200
+ // Red
201
+ "#FF9500",
202
+ // Orange
203
+ "#FFCC00",
204
+ // Yellow
205
+ "#34C759",
206
+ // Green
207
+ "#007AFF",
208
+ // Blue
209
+ "#5856D6",
210
+ // Purple
211
+ "#AF52DE",
212
+ // Pink
213
+ "#000000",
214
+ // Black
215
+ "#FFFFFF"
216
+ // White
217
+ ];
218
+ var STROKE_WIDTHS = [1, 2, 3, 5, 8];
219
+
220
+ // src/annotation/toolbar.ts
221
+ var TOOL_ICONS = {
222
+ select: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 3l7.07 16.97 2.51-7.39 7.39-2.51L3 3z"/><path d="M13 13l6 6"/></svg>`,
223
+ rectangle: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/></svg>`,
224
+ ellipse: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><ellipse cx="12" cy="12" rx="9" ry="6"/></svg>`,
225
+ arrow: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg>`,
226
+ line: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="5" y1="19" x2="19" y2="5"/></svg>`,
227
+ highlight: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M15.5 14h-.79l-.28-.27A6.471 6.471 0 0016 9.5 6.5 6.5 0 109.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/></svg>`,
228
+ blur: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M6 13c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm0 4c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm0-8c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm-3 .5c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zM6 5c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm15 5.5c.28 0 .5-.22.5-.5s-.22-.5-.5-.5-.5.22-.5.5.22.5.5.5zM14 7c.55 0 1-.45 1-1s-.45-1-1-1-1 .45-1 1 .45 1 1 1zm0-3.5c.28 0 .5-.22.5-.5s-.22-.5-.5-.5-.5.22-.5.5.22.5.5.5zm-11 10c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zm7 7c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zm0-17c.28 0 .5-.22.5-.5s-.22-.5-.5-.5-.5.22-.5.5.22.5.5.5zM10 7c.55 0 1-.45 1-1s-.45-1-1-1-1 .45-1 1 .45 1 1 1zm0 5.5c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zm8 .5c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm0 4c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm0-8c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm0-4c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm3 8.5c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zM14 17c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm0 3.5c-.28 0-.5.22-.5.5s.22.5.5.5.5-.22.5-.5-.22-.5-.5-.5zm-4-12c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zm0 8.5c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm4-4.5c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zm0-4c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5z"/></svg>`,
229
+ text: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M5 4v3h5.5v12h3V7H19V4z"/></svg>`,
230
+ freehand: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 19l7-7 3 3-7 7-3-3z"/><path d="M18 13l-1.5-7.5L2 2l3.5 14.5L13 18l5-5z"/><path d="M2 2l7.586 7.586"/><circle cx="11" cy="11" r="2"/></svg>`
231
+ };
232
+ var TOOL_LABELS = {
233
+ select: "S\xE9lection",
234
+ rectangle: "Rectangle",
235
+ ellipse: "Ellipse",
236
+ arrow: "Fl\xE8che",
237
+ line: "Ligne",
238
+ highlight: "Surbrillance",
239
+ blur: "Floutage",
240
+ text: "Texte",
241
+ freehand: "Dessin libre"
242
+ };
243
+ var AnnotationToolbar = class {
244
+ constructor(config2) {
245
+ this.colorPicker = null;
246
+ this.strokePicker = null;
247
+ this.config = config2;
248
+ this.element = this.createToolbar();
249
+ }
250
+ getElement() {
251
+ return this.element;
252
+ }
253
+ setActiveTool(tool) {
254
+ this.config.activeTool = tool;
255
+ this.updateActiveState();
256
+ }
257
+ setStyle(style) {
258
+ this.config.style = style;
259
+ this.updateStyleDisplay();
260
+ }
261
+ createToolbar() {
262
+ const toolbar = document.createElement("div");
263
+ toolbar.className = "cf-toolbar";
264
+ const toolsSection = document.createElement("div");
265
+ toolsSection.className = "cf-toolbar-section cf-toolbar-tools";
266
+ for (const tool of this.config.tools) {
267
+ const button = this.createToolButton(tool);
268
+ toolsSection.appendChild(button);
269
+ }
270
+ const separator1 = document.createElement("div");
271
+ separator1.className = "cf-toolbar-separator";
272
+ const styleSection = document.createElement("div");
273
+ styleSection.className = "cf-toolbar-section cf-toolbar-style";
274
+ const colorBtn = document.createElement("button");
275
+ colorBtn.className = "cf-toolbar-btn cf-color-btn";
276
+ colorBtn.title = "Couleur";
277
+ colorBtn.innerHTML = `<span class="cf-color-preview" style="background: ${this.config.style.strokeColor}"></span>`;
278
+ colorBtn.addEventListener("click", () => this.toggleColorPicker());
279
+ styleSection.appendChild(colorBtn);
280
+ const strokeBtn = document.createElement("button");
281
+ strokeBtn.className = "cf-toolbar-btn cf-stroke-btn";
282
+ strokeBtn.title = "\xC9paisseur";
283
+ strokeBtn.innerHTML = `<span class="cf-stroke-preview">${this.config.style.strokeWidth}px</span>`;
284
+ strokeBtn.addEventListener("click", () => this.toggleStrokePicker());
285
+ styleSection.appendChild(strokeBtn);
286
+ const separator2 = document.createElement("div");
287
+ separator2.className = "cf-toolbar-separator";
288
+ const actionsSection = document.createElement("div");
289
+ actionsSection.className = "cf-toolbar-section cf-toolbar-actions";
290
+ const undoBtn = document.createElement("button");
291
+ undoBtn.className = "cf-toolbar-btn";
292
+ undoBtn.title = "Annuler (Ctrl+Z)";
293
+ undoBtn.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 7v6h6"/><path d="M21 17a9 9 0 00-9-9 9 9 0 00-6 2.3L3 13"/></svg>`;
294
+ undoBtn.addEventListener("click", () => this.config.onUndo());
295
+ actionsSection.appendChild(undoBtn);
296
+ const clearBtn = document.createElement("button");
297
+ clearBtn.className = "cf-toolbar-btn";
298
+ clearBtn.title = "Tout effacer";
299
+ clearBtn.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h18"/><path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/></svg>`;
300
+ clearBtn.addEventListener("click", () => this.config.onClear());
301
+ actionsSection.appendChild(clearBtn);
302
+ toolbar.appendChild(toolsSection);
303
+ toolbar.appendChild(separator1);
304
+ toolbar.appendChild(styleSection);
305
+ toolbar.appendChild(separator2);
306
+ toolbar.appendChild(actionsSection);
307
+ return toolbar;
308
+ }
309
+ createToolButton(tool) {
310
+ const button = document.createElement("button");
311
+ button.className = `cf-toolbar-btn cf-tool-btn ${tool === this.config.activeTool ? "active" : ""}`;
312
+ button.dataset.tool = tool;
313
+ button.title = TOOL_LABELS[tool];
314
+ button.innerHTML = TOOL_ICONS[tool];
315
+ button.addEventListener("click", () => {
316
+ this.config.onToolChange(tool);
317
+ this.setActiveTool(tool);
318
+ });
319
+ return button;
320
+ }
321
+ updateActiveState() {
322
+ const buttons = this.element.querySelectorAll(".cf-tool-btn");
323
+ buttons.forEach((btn) => {
324
+ const button = btn;
325
+ button.classList.toggle("active", button.dataset.tool === this.config.activeTool);
326
+ });
327
+ }
328
+ updateStyleDisplay() {
329
+ const colorPreview = this.element.querySelector(".cf-color-preview");
330
+ if (colorPreview) {
331
+ colorPreview.style.background = this.config.style.strokeColor;
332
+ }
333
+ const strokePreview = this.element.querySelector(".cf-stroke-preview");
334
+ if (strokePreview) {
335
+ strokePreview.textContent = `${this.config.style.strokeWidth}px`;
336
+ }
337
+ }
338
+ toggleColorPicker() {
339
+ if (this.colorPicker) {
340
+ this.colorPicker.remove();
341
+ this.colorPicker = null;
342
+ return;
343
+ }
344
+ if (this.strokePicker) {
345
+ this.strokePicker.remove();
346
+ this.strokePicker = null;
347
+ }
348
+ this.colorPicker = document.createElement("div");
349
+ this.colorPicker.className = "cf-picker cf-color-picker";
350
+ for (const color of COLOR_PALETTE) {
351
+ const swatch = document.createElement("button");
352
+ swatch.className = `cf-color-swatch ${color === this.config.style.strokeColor ? "active" : ""}`;
353
+ swatch.style.background = color;
354
+ swatch.addEventListener("click", () => {
355
+ this.config.onStyleChange({ strokeColor: color });
356
+ this.setStyle({ ...this.config.style, strokeColor: color });
357
+ this.colorPicker?.remove();
358
+ this.colorPicker = null;
359
+ });
360
+ this.colorPicker.appendChild(swatch);
361
+ }
362
+ const colorBtn = this.element.querySelector(".cf-color-btn");
363
+ colorBtn?.appendChild(this.colorPicker);
364
+ }
365
+ toggleStrokePicker() {
366
+ if (this.strokePicker) {
367
+ this.strokePicker.remove();
368
+ this.strokePicker = null;
369
+ return;
370
+ }
371
+ if (this.colorPicker) {
372
+ this.colorPicker.remove();
373
+ this.colorPicker = null;
374
+ }
375
+ this.strokePicker = document.createElement("div");
376
+ this.strokePicker.className = "cf-picker cf-stroke-picker";
377
+ for (const width of STROKE_WIDTHS) {
378
+ const option = document.createElement("button");
379
+ option.className = `cf-stroke-option ${width === this.config.style.strokeWidth ? "active" : ""}`;
380
+ option.innerHTML = `<span style="height: ${width}px"></span> ${width}px`;
381
+ option.addEventListener("click", () => {
382
+ this.config.onStyleChange({ strokeWidth: width });
383
+ this.setStyle({ ...this.config.style, strokeWidth: width });
384
+ this.strokePicker?.remove();
385
+ this.strokePicker = null;
386
+ });
387
+ this.strokePicker.appendChild(option);
388
+ }
389
+ const strokeBtn = this.element.querySelector(".cf-stroke-btn");
390
+ strokeBtn?.appendChild(this.strokePicker);
391
+ }
392
+ };
393
+
394
+ // src/annotation/styles.ts
395
+ var STYLES = `
396
+ /* Annotation Overlay */
397
+ .cf-annotation-overlay {
398
+ position: fixed;
399
+ top: 0;
400
+ left: 0;
401
+ right: 0;
402
+ bottom: 0;
403
+ z-index: 999999;
404
+ background: rgba(0, 0, 0, 0.85);
405
+ display: flex;
406
+ align-items: center;
407
+ justify-content: center;
408
+ animation: cf-fade-in 0.2s ease-out;
409
+ }
410
+
411
+ @keyframes cf-fade-in {
412
+ from { opacity: 0; }
413
+ to { opacity: 1; }
414
+ }
415
+
416
+ /* Editor Wrapper */
417
+ .cf-annotation-wrapper {
418
+ display: flex;
419
+ flex-direction: column;
420
+ max-width: 95vw;
421
+ max-height: 95vh;
422
+ background: #1a1a1a;
423
+ border-radius: 12px;
424
+ overflow: hidden;
425
+ box-shadow: 0 25px 50px rgba(0, 0, 0, 0.5);
426
+ }
427
+
428
+ /* Canvas Container */
429
+ .cf-annotation-canvas-container {
430
+ flex: 1;
431
+ display: flex;
432
+ align-items: center;
433
+ justify-content: center;
434
+ overflow: auto;
435
+ padding: 16px;
436
+ background: #0d0d0d;
437
+ }
438
+
439
+ .cf-annotation-canvas {
440
+ max-width: 100%;
441
+ max-height: calc(95vh - 140px);
442
+ border-radius: 4px;
443
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
444
+ cursor: crosshair;
445
+ }
446
+
447
+ /* Toolbar */
448
+ .cf-toolbar {
449
+ display: flex;
450
+ align-items: center;
451
+ gap: 8px;
452
+ padding: 12px 16px;
453
+ background: #2a2a2a;
454
+ border-bottom: 1px solid #3a3a3a;
455
+ }
456
+
457
+ .cf-toolbar-section {
458
+ display: flex;
459
+ align-items: center;
460
+ gap: 4px;
461
+ }
462
+
463
+ .cf-toolbar-separator {
464
+ width: 1px;
465
+ height: 24px;
466
+ background: #4a4a4a;
467
+ margin: 0 8px;
468
+ }
469
+
470
+ .cf-toolbar-btn {
471
+ display: flex;
472
+ align-items: center;
473
+ justify-content: center;
474
+ width: 36px;
475
+ height: 36px;
476
+ padding: 0;
477
+ border: none;
478
+ border-radius: 8px;
479
+ background: transparent;
480
+ color: #999;
481
+ cursor: pointer;
482
+ transition: all 0.15s ease;
483
+ }
484
+
485
+ .cf-toolbar-btn:hover {
486
+ background: #3a3a3a;
487
+ color: #fff;
488
+ }
489
+
490
+ .cf-toolbar-btn.active {
491
+ background: #007AFF;
492
+ color: #fff;
493
+ }
494
+
495
+ .cf-toolbar-btn svg {
496
+ width: 20px;
497
+ height: 20px;
498
+ }
499
+
500
+ /* Color Button */
501
+ .cf-color-btn {
502
+ position: relative;
503
+ }
504
+
505
+ .cf-color-preview {
506
+ width: 20px;
507
+ height: 20px;
508
+ border-radius: 50%;
509
+ border: 2px solid #fff;
510
+ box-shadow: 0 0 0 1px rgba(0,0,0,0.2);
511
+ }
512
+
513
+ /* Stroke Button */
514
+ .cf-stroke-btn {
515
+ width: auto;
516
+ padding: 0 12px;
517
+ }
518
+
519
+ .cf-stroke-preview {
520
+ font-size: 12px;
521
+ font-weight: 500;
522
+ color: inherit;
523
+ }
524
+
525
+ /* Pickers */
526
+ .cf-picker {
527
+ position: absolute;
528
+ top: 100%;
529
+ left: 50%;
530
+ transform: translateX(-50%);
531
+ margin-top: 8px;
532
+ padding: 8px;
533
+ background: #2a2a2a;
534
+ border-radius: 8px;
535
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
536
+ z-index: 10;
537
+ }
538
+
539
+ .cf-color-picker {
540
+ display: grid;
541
+ grid-template-columns: repeat(5, 1fr);
542
+ gap: 4px;
543
+ width: 140px;
544
+ }
545
+
546
+ .cf-color-swatch {
547
+ width: 24px;
548
+ height: 24px;
549
+ border-radius: 50%;
550
+ border: 2px solid transparent;
551
+ cursor: pointer;
552
+ transition: transform 0.15s ease;
553
+ }
554
+
555
+ .cf-color-swatch:hover {
556
+ transform: scale(1.15);
557
+ }
558
+
559
+ .cf-color-swatch.active {
560
+ border-color: #fff;
561
+ }
562
+
563
+ .cf-stroke-picker {
564
+ display: flex;
565
+ flex-direction: column;
566
+ gap: 4px;
567
+ min-width: 80px;
568
+ }
569
+
570
+ .cf-stroke-option {
571
+ display: flex;
572
+ align-items: center;
573
+ gap: 8px;
574
+ padding: 6px 10px;
575
+ border: none;
576
+ border-radius: 4px;
577
+ background: transparent;
578
+ color: #999;
579
+ font-size: 12px;
580
+ cursor: pointer;
581
+ transition: all 0.15s ease;
582
+ }
583
+
584
+ .cf-stroke-option:hover {
585
+ background: #3a3a3a;
586
+ color: #fff;
587
+ }
588
+
589
+ .cf-stroke-option.active {
590
+ background: #007AFF;
591
+ color: #fff;
592
+ }
593
+
594
+ .cf-stroke-option span {
595
+ width: 24px;
596
+ background: currentColor;
597
+ border-radius: 2px;
598
+ }
599
+
600
+ /* Action Buttons */
601
+ .cf-annotation-actions {
602
+ display: flex;
603
+ justify-content: flex-end;
604
+ gap: 12px;
605
+ padding: 12px 16px;
606
+ background: #2a2a2a;
607
+ border-top: 1px solid #3a3a3a;
608
+ }
609
+
610
+ .cf-btn {
611
+ display: flex;
612
+ align-items: center;
613
+ justify-content: center;
614
+ padding: 10px 20px;
615
+ border: none;
616
+ border-radius: 8px;
617
+ font-size: 14px;
618
+ font-weight: 500;
619
+ cursor: pointer;
620
+ transition: all 0.15s ease;
621
+ }
622
+
623
+ .cf-btn-primary {
624
+ background: #007AFF;
625
+ color: #fff;
626
+ }
627
+
628
+ .cf-btn-primary:hover {
629
+ background: #0066DD;
630
+ }
631
+
632
+ .cf-btn-secondary {
633
+ background: #3a3a3a;
634
+ color: #fff;
635
+ }
636
+
637
+ .cf-btn-secondary:hover {
638
+ background: #4a4a4a;
639
+ }
640
+
641
+ /* Text Input */
642
+ .cf-annotation-text-input {
643
+ position: fixed;
644
+ z-index: 1000000;
645
+ padding: 4px 8px;
646
+ border: 2px solid #007AFF;
647
+ border-radius: 4px;
648
+ background: rgba(255, 255, 255, 0.95);
649
+ font-size: 16px;
650
+ font-family: system-ui, -apple-system, sans-serif;
651
+ outline: none;
652
+ min-width: 150px;
653
+ }
654
+
655
+ /* Tool-specific cursors */
656
+ .cf-annotation-canvas[data-tool="select"] { cursor: default; }
657
+ .cf-annotation-canvas[data-tool="rectangle"] { cursor: crosshair; }
658
+ .cf-annotation-canvas[data-tool="ellipse"] { cursor: crosshair; }
659
+ .cf-annotation-canvas[data-tool="arrow"] { cursor: crosshair; }
660
+ .cf-annotation-canvas[data-tool="line"] { cursor: crosshair; }
661
+ .cf-annotation-canvas[data-tool="highlight"] { cursor: crosshair; }
662
+ .cf-annotation-canvas[data-tool="blur"] { cursor: crosshair; }
663
+ .cf-annotation-canvas[data-tool="text"] { cursor: text; }
664
+ .cf-annotation-canvas[data-tool="freehand"] { cursor: crosshair; }
665
+
666
+ /* Responsive */
667
+ @media (max-width: 768px) {
668
+ .cf-toolbar {
669
+ flex-wrap: wrap;
670
+ justify-content: center;
671
+ }
672
+
673
+ .cf-toolbar-separator {
674
+ display: none;
675
+ }
676
+
677
+ .cf-annotation-actions {
678
+ justify-content: stretch;
679
+ }
680
+
681
+ .cf-annotation-actions .cf-btn {
682
+ flex: 1;
683
+ }
684
+ }
685
+ `;
686
+ var stylesInjected = false;
687
+ function injectAnnotationStyles() {
688
+ if (stylesInjected) return;
689
+ const styleElement = document.createElement("style");
690
+ styleElement.id = "cf-annotation-styles";
691
+ styleElement.textContent = STYLES;
692
+ document.head.appendChild(styleElement);
693
+ stylesInjected = true;
694
+ }
695
+
696
+ // src/annotation/editor.ts
697
+ var AnnotationEditor = class {
698
+ constructor(config2) {
699
+ this.container = null;
700
+ this.canvas = null;
701
+ this.ctx = null;
702
+ this.toolbar = null;
703
+ this.annotations = [];
704
+ this.backgroundImage = null;
705
+ this.state = {
706
+ activeTool: "rectangle",
707
+ style: { ...DEFAULT_STYLE },
708
+ isDrawing: false,
709
+ currentAnnotation: null
710
+ };
711
+ this.startPoint = null;
712
+ this.freehandPoints = [];
713
+ this.textInput = null;
714
+ this.config = {
715
+ ...config2,
716
+ tools: config2.tools || ["select", "rectangle", "arrow", "highlight", "blur", "text", "freehand"],
717
+ defaultStyle: config2.defaultStyle || DEFAULT_STYLE
718
+ };
719
+ this.state.style = { ...this.config.defaultStyle };
720
+ }
721
+ /**
722
+ * Open the annotation editor with a screenshot
723
+ */
724
+ async open(screenshotDataUrl2) {
725
+ injectAnnotationStyles();
726
+ this.backgroundImage = await this.loadImage(screenshotDataUrl2);
727
+ this.createEditorUI();
728
+ this.setupEventListeners();
729
+ this.render();
730
+ }
731
+ /**
732
+ * Close the editor
733
+ */
734
+ close() {
735
+ if (this.container) {
736
+ this.container.remove();
737
+ this.container = null;
738
+ }
739
+ this.canvas = null;
740
+ this.ctx = null;
741
+ this.toolbar = null;
742
+ this.annotations = [];
743
+ this.backgroundImage = null;
744
+ }
745
+ /**
746
+ * Get the annotated image as data URL
747
+ */
748
+ getAnnotatedImage() {
749
+ if (!this.canvas || !this.ctx) return "";
750
+ const tempCanvas = document.createElement("canvas");
751
+ tempCanvas.width = this.canvas.width;
752
+ tempCanvas.height = this.canvas.height;
753
+ const tempCtx = tempCanvas.getContext("2d");
754
+ if (this.backgroundImage) {
755
+ tempCtx.drawImage(this.backgroundImage, 0, 0);
756
+ }
757
+ this.drawAnnotations(tempCtx);
758
+ return tempCanvas.toDataURL("image/png");
759
+ }
760
+ /**
761
+ * Get annotations data
762
+ */
763
+ getAnnotations() {
764
+ return [...this.annotations];
765
+ }
766
+ // Private methods
767
+ loadImage(src) {
768
+ return new Promise((resolve, reject) => {
769
+ const img = new Image();
770
+ img.onload = () => resolve(img);
771
+ img.onerror = reject;
772
+ img.src = src;
773
+ });
774
+ }
775
+ createEditorUI() {
776
+ this.container = document.createElement("div");
777
+ this.container.className = "cf-annotation-overlay";
778
+ const wrapper = document.createElement("div");
779
+ wrapper.className = "cf-annotation-wrapper";
780
+ const canvasContainer = document.createElement("div");
781
+ canvasContainer.className = "cf-annotation-canvas-container";
782
+ this.canvas = document.createElement("canvas");
783
+ this.canvas.className = "cf-annotation-canvas";
784
+ this.canvas.width = this.backgroundImage?.width || 1920;
785
+ this.canvas.height = this.backgroundImage?.height || 1080;
786
+ this.ctx = this.canvas.getContext("2d");
787
+ canvasContainer.appendChild(this.canvas);
788
+ this.toolbar = new AnnotationToolbar({
789
+ tools: this.config.tools,
790
+ activeTool: this.state.activeTool,
791
+ style: this.state.style,
792
+ onToolChange: (tool) => this.setActiveTool(tool),
793
+ onStyleChange: (style) => this.setStyle(style),
794
+ onUndo: () => this.undo(),
795
+ onClear: () => this.clearAll(),
796
+ onSave: () => this.save(),
797
+ onCancel: () => this.cancel()
798
+ });
799
+ const actions = document.createElement("div");
800
+ actions.className = "cf-annotation-actions";
801
+ actions.innerHTML = `
802
+ <button class="cf-btn cf-btn-secondary" data-action="cancel">Annuler</button>
803
+ <button class="cf-btn cf-btn-primary" data-action="save">Enregistrer</button>
804
+ `;
805
+ wrapper.appendChild(this.toolbar.getElement());
806
+ wrapper.appendChild(canvasContainer);
807
+ wrapper.appendChild(actions);
808
+ this.container.appendChild(wrapper);
809
+ document.body.appendChild(this.container);
810
+ actions.querySelector('[data-action="cancel"]')?.addEventListener("click", () => this.cancel());
811
+ actions.querySelector('[data-action="save"]')?.addEventListener("click", () => this.save());
812
+ }
813
+ setupEventListeners() {
814
+ if (!this.canvas) return;
815
+ this.canvas.addEventListener("mousedown", this.handleMouseDown.bind(this));
816
+ this.canvas.addEventListener("mousemove", this.handleMouseMove.bind(this));
817
+ this.canvas.addEventListener("mouseup", this.handleMouseUp.bind(this));
818
+ this.canvas.addEventListener("mouseleave", this.handleMouseUp.bind(this));
819
+ this.canvas.addEventListener("touchstart", this.handleTouchStart.bind(this));
820
+ this.canvas.addEventListener("touchmove", this.handleTouchMove.bind(this));
821
+ this.canvas.addEventListener("touchend", this.handleTouchEnd.bind(this));
822
+ document.addEventListener("keydown", this.handleKeyDown.bind(this));
823
+ }
824
+ handleMouseDown(e) {
825
+ const point = this.getCanvasPoint(e);
826
+ this.startDrawing(point);
827
+ }
828
+ handleMouseMove(e) {
829
+ const point = this.getCanvasPoint(e);
830
+ this.continueDrawing(point);
831
+ }
832
+ handleMouseUp(_e) {
833
+ this.finishDrawing();
834
+ }
835
+ handleTouchStart(e) {
836
+ e.preventDefault();
837
+ const touch = e.touches[0];
838
+ const point = this.getCanvasPointFromTouch(touch);
839
+ this.startDrawing(point);
840
+ }
841
+ handleTouchMove(e) {
842
+ e.preventDefault();
843
+ const touch = e.touches[0];
844
+ const point = this.getCanvasPointFromTouch(touch);
845
+ this.continueDrawing(point);
846
+ }
847
+ handleTouchEnd(e) {
848
+ e.preventDefault();
849
+ this.finishDrawing();
850
+ }
851
+ handleKeyDown(e) {
852
+ if ((e.ctrlKey || e.metaKey) && e.key === "z") {
853
+ e.preventDefault();
854
+ this.undo();
855
+ }
856
+ if (e.key === "Escape") {
857
+ if (this.state.isDrawing) {
858
+ this.state.isDrawing = false;
859
+ this.state.currentAnnotation = null;
860
+ this.render();
861
+ } else {
862
+ this.cancel();
863
+ }
864
+ }
865
+ if (e.key === "Enter" && !this.state.isDrawing) {
866
+ this.save();
867
+ }
868
+ }
869
+ getCanvasPoint(e) {
870
+ const rect = this.canvas.getBoundingClientRect();
871
+ const scaleX = this.canvas.width / rect.width;
872
+ const scaleY = this.canvas.height / rect.height;
873
+ return {
874
+ x: (e.clientX - rect.left) * scaleX,
875
+ y: (e.clientY - rect.top) * scaleY
876
+ };
877
+ }
878
+ getCanvasPointFromTouch(touch) {
879
+ const rect = this.canvas.getBoundingClientRect();
880
+ const scaleX = this.canvas.width / rect.width;
881
+ const scaleY = this.canvas.height / rect.height;
882
+ return {
883
+ x: (touch.clientX - rect.left) * scaleX,
884
+ y: (touch.clientY - rect.top) * scaleY
885
+ };
886
+ }
887
+ startDrawing(point) {
888
+ if (this.state.activeTool === "select") return;
889
+ this.state.isDrawing = true;
890
+ this.startPoint = point;
891
+ if (this.state.activeTool === "text") {
892
+ this.showTextInput(point);
893
+ return;
894
+ }
895
+ if (this.state.activeTool === "freehand") {
896
+ this.freehandPoints = [point];
897
+ }
898
+ }
899
+ continueDrawing(point) {
900
+ if (!this.state.isDrawing || !this.startPoint) return;
901
+ if (this.state.activeTool === "freehand") {
902
+ this.freehandPoints.push(point);
903
+ }
904
+ this.state.currentAnnotation = this.createAnnotation(this.startPoint, point);
905
+ this.render();
906
+ }
907
+ finishDrawing() {
908
+ if (!this.state.isDrawing || !this.state.currentAnnotation) {
909
+ this.state.isDrawing = false;
910
+ return;
911
+ }
912
+ if (this.isValidAnnotation(this.state.currentAnnotation)) {
913
+ this.annotations.push(this.state.currentAnnotation);
914
+ }
915
+ this.state.isDrawing = false;
916
+ this.state.currentAnnotation = null;
917
+ this.startPoint = null;
918
+ this.freehandPoints = [];
919
+ this.render();
920
+ }
921
+ isValidAnnotation(annotation) {
922
+ switch (annotation.type) {
923
+ case "rectangle":
924
+ case "ellipse":
925
+ case "highlight":
926
+ case "blur":
927
+ const bounds = annotation.bounds;
928
+ return Math.abs(bounds.width) > 5 && Math.abs(bounds.height) > 5;
929
+ case "arrow":
930
+ case "line":
931
+ const start = annotation.start;
932
+ const end = annotation.end;
933
+ const dist = Math.sqrt(Math.pow(end.x - start.x, 2) + Math.pow(end.y - start.y, 2));
934
+ return dist > 10;
935
+ case "freehand":
936
+ return annotation.points.length > 2;
937
+ case "text":
938
+ return !!annotation.text?.trim();
939
+ default:
940
+ return true;
941
+ }
942
+ }
943
+ createAnnotation(start, end) {
944
+ const id = this.generateId();
945
+ const baseStyle = { ...this.state.style };
946
+ switch (this.state.activeTool) {
947
+ case "rectangle":
948
+ return {
949
+ id,
950
+ type: "rectangle",
951
+ bounds: this.createBounds(start, end),
952
+ style: baseStyle,
953
+ timestamp: Date.now()
954
+ };
955
+ case "ellipse":
956
+ return {
957
+ id,
958
+ type: "ellipse",
959
+ bounds: this.createBounds(start, end),
960
+ style: baseStyle,
961
+ timestamp: Date.now()
962
+ };
963
+ case "arrow":
964
+ return {
965
+ id,
966
+ type: "arrow",
967
+ start: { ...start },
968
+ end: { ...end },
969
+ style: baseStyle,
970
+ timestamp: Date.now()
971
+ };
972
+ case "line":
973
+ return {
974
+ id,
975
+ type: "line",
976
+ start: { ...start },
977
+ end: { ...end },
978
+ style: baseStyle,
979
+ timestamp: Date.now()
980
+ };
981
+ case "highlight":
982
+ return {
983
+ id,
984
+ type: "highlight",
985
+ bounds: this.createBounds(start, end),
986
+ style: { ...baseStyle, ...HIGHLIGHT_STYLE },
987
+ timestamp: Date.now()
988
+ };
989
+ case "blur":
990
+ return {
991
+ id,
992
+ type: "blur",
993
+ bounds: this.createBounds(start, end),
994
+ style: { ...baseStyle, ...BLUR_STYLE },
995
+ blurAmount: 10,
996
+ timestamp: Date.now()
997
+ };
998
+ case "freehand":
999
+ return {
1000
+ id,
1001
+ type: "freehand",
1002
+ points: [...this.freehandPoints],
1003
+ style: baseStyle,
1004
+ timestamp: Date.now()
1005
+ };
1006
+ default:
1007
+ return {
1008
+ id,
1009
+ type: "rectangle",
1010
+ bounds: this.createBounds(start, end),
1011
+ style: baseStyle,
1012
+ timestamp: Date.now()
1013
+ };
1014
+ }
1015
+ }
1016
+ createBounds(start, end) {
1017
+ return {
1018
+ x: Math.min(start.x, end.x),
1019
+ y: Math.min(start.y, end.y),
1020
+ width: Math.abs(end.x - start.x),
1021
+ height: Math.abs(end.y - start.y)
1022
+ };
1023
+ }
1024
+ showTextInput(point) {
1025
+ if (this.textInput) {
1026
+ this.textInput.remove();
1027
+ }
1028
+ const rect = this.canvas.getBoundingClientRect();
1029
+ const scaleX = rect.width / this.canvas.width;
1030
+ const scaleY = rect.height / this.canvas.height;
1031
+ this.textInput = document.createElement("input");
1032
+ this.textInput.type = "text";
1033
+ this.textInput.className = "cf-annotation-text-input";
1034
+ this.textInput.style.left = `${rect.left + point.x * scaleX}px`;
1035
+ this.textInput.style.top = `${rect.top + point.y * scaleY}px`;
1036
+ this.textInput.style.color = this.state.style.strokeColor;
1037
+ this.textInput.style.fontSize = `${(this.state.style.fontSize || 16) * scaleY}px`;
1038
+ this.textInput.placeholder = "Tapez votre texte...";
1039
+ document.body.appendChild(this.textInput);
1040
+ this.textInput.focus();
1041
+ const handleTextSubmit = () => {
1042
+ if (this.textInput && this.textInput.value.trim()) {
1043
+ const textAnnotation = {
1044
+ id: this.generateId(),
1045
+ type: "text",
1046
+ position: point,
1047
+ text: this.textInput.value.trim(),
1048
+ style: { ...this.state.style },
1049
+ timestamp: Date.now()
1050
+ };
1051
+ this.annotations.push(textAnnotation);
1052
+ this.render();
1053
+ }
1054
+ this.textInput?.remove();
1055
+ this.textInput = null;
1056
+ this.state.isDrawing = false;
1057
+ };
1058
+ this.textInput.addEventListener("blur", handleTextSubmit);
1059
+ this.textInput.addEventListener("keydown", (e) => {
1060
+ if (e.key === "Enter") {
1061
+ handleTextSubmit();
1062
+ } else if (e.key === "Escape") {
1063
+ this.textInput?.remove();
1064
+ this.textInput = null;
1065
+ this.state.isDrawing = false;
1066
+ }
1067
+ });
1068
+ }
1069
+ generateId() {
1070
+ return `ann_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
1071
+ }
1072
+ setActiveTool(tool) {
1073
+ this.state.activeTool = tool;
1074
+ this.toolbar?.setActiveTool(tool);
1075
+ if (this.canvas) {
1076
+ this.canvas.style.cursor = tool === "select" ? "default" : "crosshair";
1077
+ }
1078
+ }
1079
+ setStyle(style) {
1080
+ this.state.style = { ...this.state.style, ...style };
1081
+ this.toolbar?.setStyle(this.state.style);
1082
+ }
1083
+ undo() {
1084
+ if (this.annotations.length > 0) {
1085
+ this.annotations.pop();
1086
+ this.render();
1087
+ }
1088
+ }
1089
+ clearAll() {
1090
+ this.annotations = [];
1091
+ this.render();
1092
+ }
1093
+ save() {
1094
+ const imageData = this.getAnnotatedImage();
1095
+ this.config.onSave?.(this.annotations, imageData);
1096
+ this.close();
1097
+ }
1098
+ cancel() {
1099
+ this.config.onCancel?.();
1100
+ this.close();
1101
+ }
1102
+ render() {
1103
+ if (!this.ctx || !this.canvas) return;
1104
+ this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
1105
+ if (this.backgroundImage) {
1106
+ this.ctx.drawImage(this.backgroundImage, 0, 0);
1107
+ }
1108
+ this.drawAnnotations(this.ctx);
1109
+ if (this.state.currentAnnotation) {
1110
+ this.drawAnnotation(this.ctx, this.state.currentAnnotation);
1111
+ }
1112
+ }
1113
+ drawAnnotations(ctx) {
1114
+ for (const annotation of this.annotations) {
1115
+ this.drawAnnotation(ctx, annotation);
1116
+ }
1117
+ }
1118
+ drawAnnotation(ctx, annotation) {
1119
+ ctx.save();
1120
+ ctx.globalAlpha = annotation.style.opacity;
1121
+ ctx.strokeStyle = annotation.style.strokeColor;
1122
+ ctx.fillStyle = annotation.style.fillColor;
1123
+ ctx.lineWidth = annotation.style.strokeWidth;
1124
+ ctx.lineCap = "round";
1125
+ ctx.lineJoin = "round";
1126
+ switch (annotation.type) {
1127
+ case "rectangle":
1128
+ this.drawRectangle(ctx, annotation.bounds, annotation.style);
1129
+ break;
1130
+ case "ellipse":
1131
+ this.drawEllipse(ctx, annotation.bounds, annotation.style);
1132
+ break;
1133
+ case "arrow":
1134
+ this.drawArrow(ctx, annotation.start, annotation.end, annotation.style);
1135
+ break;
1136
+ case "line":
1137
+ this.drawLine(ctx, annotation.start, annotation.end);
1138
+ break;
1139
+ case "highlight":
1140
+ this.drawHighlight(ctx, annotation.bounds, annotation.style);
1141
+ break;
1142
+ case "blur":
1143
+ this.drawBlur(ctx, annotation.bounds, annotation.blurAmount);
1144
+ break;
1145
+ case "text":
1146
+ this.drawText(ctx, annotation.position, annotation.text, annotation.style);
1147
+ break;
1148
+ case "freehand":
1149
+ this.drawFreehand(ctx, annotation.points);
1150
+ break;
1151
+ }
1152
+ ctx.restore();
1153
+ }
1154
+ drawRectangle(ctx, bounds, style) {
1155
+ if (style.fillColor !== "transparent") {
1156
+ ctx.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
1157
+ }
1158
+ ctx.strokeRect(bounds.x, bounds.y, bounds.width, bounds.height);
1159
+ }
1160
+ drawEllipse(ctx, bounds, style) {
1161
+ const centerX = bounds.x + bounds.width / 2;
1162
+ const centerY = bounds.y + bounds.height / 2;
1163
+ const radiusX = bounds.width / 2;
1164
+ const radiusY = bounds.height / 2;
1165
+ ctx.beginPath();
1166
+ ctx.ellipse(centerX, centerY, radiusX, radiusY, 0, 0, Math.PI * 2);
1167
+ if (style.fillColor !== "transparent") {
1168
+ ctx.fill();
1169
+ }
1170
+ ctx.stroke();
1171
+ }
1172
+ drawArrow(ctx, start, end, style) {
1173
+ const headLength = 15 + style.strokeWidth * 2;
1174
+ const angle = Math.atan2(end.y - start.y, end.x - start.x);
1175
+ ctx.beginPath();
1176
+ ctx.moveTo(start.x, start.y);
1177
+ ctx.lineTo(end.x, end.y);
1178
+ ctx.stroke();
1179
+ ctx.beginPath();
1180
+ ctx.moveTo(end.x, end.y);
1181
+ ctx.lineTo(
1182
+ end.x - headLength * Math.cos(angle - Math.PI / 6),
1183
+ end.y - headLength * Math.sin(angle - Math.PI / 6)
1184
+ );
1185
+ ctx.lineTo(
1186
+ end.x - headLength * Math.cos(angle + Math.PI / 6),
1187
+ end.y - headLength * Math.sin(angle + Math.PI / 6)
1188
+ );
1189
+ ctx.closePath();
1190
+ ctx.fillStyle = style.strokeColor;
1191
+ ctx.fill();
1192
+ }
1193
+ drawLine(ctx, start, end) {
1194
+ ctx.beginPath();
1195
+ ctx.moveTo(start.x, start.y);
1196
+ ctx.lineTo(end.x, end.y);
1197
+ ctx.stroke();
1198
+ }
1199
+ drawHighlight(ctx, bounds, style) {
1200
+ ctx.fillStyle = style.fillColor;
1201
+ ctx.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
1202
+ }
1203
+ drawBlur(ctx, bounds, blurAmount) {
1204
+ const pixelSize = Math.max(blurAmount, 5);
1205
+ if (this.backgroundImage) {
1206
+ const tempCanvas = document.createElement("canvas");
1207
+ tempCanvas.width = bounds.width;
1208
+ tempCanvas.height = bounds.height;
1209
+ const tempCtx = tempCanvas.getContext("2d");
1210
+ tempCtx.drawImage(
1211
+ this.backgroundImage,
1212
+ bounds.x,
1213
+ bounds.y,
1214
+ bounds.width,
1215
+ bounds.height,
1216
+ 0,
1217
+ 0,
1218
+ bounds.width,
1219
+ bounds.height
1220
+ );
1221
+ const w = tempCanvas.width;
1222
+ const h = tempCanvas.height;
1223
+ tempCtx.imageSmoothingEnabled = false;
1224
+ tempCtx.drawImage(tempCanvas, 0, 0, w, h, 0, 0, w / pixelSize, h / pixelSize);
1225
+ tempCtx.drawImage(tempCanvas, 0, 0, w / pixelSize, h / pixelSize, 0, 0, w, h);
1226
+ ctx.drawImage(tempCanvas, bounds.x, bounds.y);
1227
+ }
1228
+ ctx.strokeStyle = "#999";
1229
+ ctx.lineWidth = 1;
1230
+ ctx.setLineDash([5, 5]);
1231
+ ctx.strokeRect(bounds.x, bounds.y, bounds.width, bounds.height);
1232
+ ctx.setLineDash([]);
1233
+ }
1234
+ drawText(ctx, position, text, style) {
1235
+ ctx.font = `${style.fontSize || 16}px ${style.fontFamily || "system-ui"}`;
1236
+ ctx.fillStyle = style.strokeColor;
1237
+ ctx.textBaseline = "top";
1238
+ const metrics = ctx.measureText(text);
1239
+ const padding = 4;
1240
+ ctx.fillStyle = "rgba(255, 255, 255, 0.8)";
1241
+ ctx.fillRect(
1242
+ position.x - padding,
1243
+ position.y - padding,
1244
+ metrics.width + padding * 2,
1245
+ (style.fontSize || 16) + padding * 2
1246
+ );
1247
+ ctx.fillStyle = style.strokeColor;
1248
+ ctx.fillText(text, position.x, position.y);
1249
+ }
1250
+ drawFreehand(ctx, points) {
1251
+ if (points.length < 2) return;
1252
+ ctx.beginPath();
1253
+ ctx.moveTo(points[0].x, points[0].y);
1254
+ for (let i = 1; i < points.length; i++) {
1255
+ ctx.lineTo(points[i].x, points[i].y);
1256
+ }
1257
+ ctx.stroke();
1258
+ }
1259
+ };
1260
+
182
1261
  // src/widget.ts
183
1262
  var DEFAULT_CONFIG = {
184
1263
  position: "bottom-right",
@@ -189,12 +1268,15 @@ var DEFAULT_CONFIG = {
189
1268
  var container = null;
190
1269
  var onSubmitCallback = null;
191
1270
  var screenshotDataUrl = null;
1271
+ var annotatedImageUrl = null;
192
1272
  var annotationsData = [];
193
1273
  var isExpanded = false;
1274
+ var currentCfg = { ...DEFAULT_CONFIG };
194
1275
  function mountWidget(config2 = {}, onSubmit, opts) {
195
1276
  if (container) return;
196
1277
  onSubmitCallback = onSubmit;
197
1278
  const cfg = { ...DEFAULT_CONFIG, ...config2 };
1279
+ currentCfg = cfg;
198
1280
  container = document.createElement("div");
199
1281
  container.id = "checkflow-widget";
200
1282
  injectStyles(cfg);
@@ -203,15 +1285,22 @@ function mountWidget(config2 = {}, onSubmit, opts) {
203
1285
  const btn = container.querySelector("#cf-trigger");
204
1286
  btn?.addEventListener("click", () => openModal(cfg, opts));
205
1287
  }
1288
+ function getBackdropClass(cfg, expanded) {
1289
+ if (expanded) return "cf-backdrop--center";
1290
+ if (cfg.position === "center") return "cf-backdrop--center";
1291
+ return "cf-backdrop--corner cf-pos-" + cfg.position;
1292
+ }
206
1293
  function openModal(cfg, opts) {
207
1294
  if (!container) return;
208
1295
  const existing = document.getElementById("cf-modal-backdrop");
209
1296
  if (existing) existing.remove();
210
1297
  isExpanded = false;
211
1298
  screenshotDataUrl = null;
1299
+ annotatedImageUrl = null;
212
1300
  annotationsData = [];
213
1301
  const backdrop = document.createElement("div");
214
1302
  backdrop.id = "cf-modal-backdrop";
1303
+ backdrop.className = getBackdropClass(cfg, false);
215
1304
  backdrop.innerHTML = getModalHTML(cfg, false);
216
1305
  document.body.appendChild(backdrop);
217
1306
  backdrop.addEventListener("click", (e) => {
@@ -224,6 +1313,7 @@ function closeModal() {
224
1313
  if (backdrop) backdrop.remove();
225
1314
  isExpanded = false;
226
1315
  screenshotDataUrl = null;
1316
+ annotatedImageUrl = null;
227
1317
  annotationsData = [];
228
1318
  }
229
1319
  function expandModal(cfg, opts) {
@@ -233,6 +1323,7 @@ function expandModal(cfg, opts) {
233
1323
  const email = backdrop.querySelector("#cf-email")?.value || "";
234
1324
  const desc = backdrop.querySelector("#cf-desc")?.value || "";
235
1325
  isExpanded = true;
1326
+ backdrop.className = getBackdropClass(cfg, true);
236
1327
  backdrop.innerHTML = getModalHTML(cfg, true);
237
1328
  bindModalEvents(cfg, opts);
238
1329
  const nameEl = backdrop.querySelector("#cf-name");
@@ -250,7 +1341,9 @@ function collapseModal(cfg, opts) {
250
1341
  const desc = backdrop.querySelector("#cf-desc")?.value || "";
251
1342
  isExpanded = false;
252
1343
  screenshotDataUrl = null;
1344
+ annotatedImageUrl = null;
253
1345
  annotationsData = [];
1346
+ backdrop.className = getBackdropClass(cfg, false);
254
1347
  backdrop.innerHTML = getModalHTML(cfg, false);
255
1348
  bindModalEvents(cfg, opts);
256
1349
  const nameEl = backdrop.querySelector("#cf-name");
@@ -260,6 +1353,30 @@ function collapseModal(cfg, opts) {
260
1353
  if (emailEl) emailEl.value = email;
261
1354
  if (descEl) descEl.value = desc;
262
1355
  }
1356
+ function openAnnotationEditor(cfg, opts) {
1357
+ if (!screenshotDataUrl) return;
1358
+ const backdrop = document.getElementById("cf-modal-backdrop");
1359
+ if (backdrop) backdrop.style.display = "none";
1360
+ const editor = new AnnotationEditor({
1361
+ tools: ["rectangle", "ellipse", "arrow", "highlight", "blur", "text", "freehand"],
1362
+ defaultStyle: { strokeColor: "#FF3B30", strokeWidth: 3, fillColor: "transparent", opacity: 1, fontSize: 16, fontFamily: "system-ui, -apple-system, sans-serif" },
1363
+ onSave: (annotations, imageData) => {
1364
+ annotationsData = annotations;
1365
+ annotatedImageUrl = imageData;
1366
+ if (backdrop) {
1367
+ backdrop.style.display = "flex";
1368
+ const img = backdrop.querySelector("#cf-screenshot-img");
1369
+ if (img) img.src = annotatedImageUrl;
1370
+ const annotBtn = backdrop.querySelector("#cf-annotate-btn");
1371
+ if (annotBtn) annotBtn.textContent = `Annoter (${annotationsData.length})`;
1372
+ }
1373
+ },
1374
+ onCancel: () => {
1375
+ if (backdrop) backdrop.style.display = "flex";
1376
+ }
1377
+ });
1378
+ editor.open(screenshotDataUrl);
1379
+ }
263
1380
  function bindModalEvents(cfg, opts) {
264
1381
  const backdrop = document.getElementById("cf-modal-backdrop");
265
1382
  if (!backdrop) return;
@@ -282,33 +1399,8 @@ function bindModalEvents(cfg, opts) {
282
1399
  backdrop.querySelector("#cf-remove-screenshot")?.addEventListener("click", () => {
283
1400
  collapseModal(cfg, opts);
284
1401
  });
285
- backdrop.querySelector("#cf-highlight-btn")?.addEventListener("click", async () => {
286
- if (!opts?.onHighlight) return;
287
- backdrop.style.display = "none";
288
- try {
289
- const annotations = await opts.onHighlight();
290
- if (annotations && annotations.length > 0) {
291
- annotationsData = annotations;
292
- }
293
- } catch {
294
- }
295
- backdrop.style.display = "flex";
296
- const hlBtn = backdrop.querySelector("#cf-highlight-btn");
297
- if (hlBtn && annotationsData.length > 0) {
298
- hlBtn.textContent = `Surligner (${annotationsData.length})`;
299
- }
300
- });
301
- backdrop.querySelector("#cf-mask-btn")?.addEventListener("click", async () => {
302
- if (!opts?.onHighlight) return;
303
- backdrop.style.display = "none";
304
- try {
305
- const annotations = await opts.onHighlight();
306
- if (annotations && annotations.length > 0) {
307
- annotationsData = [...annotationsData, ...annotations];
308
- }
309
- } catch {
310
- }
311
- backdrop.style.display = "flex";
1402
+ backdrop.querySelector("#cf-annotate-btn")?.addEventListener("click", () => {
1403
+ openAnnotationEditor(cfg, opts);
312
1404
  });
313
1405
  backdrop.querySelector("#cf-submit")?.addEventListener("click", () => {
314
1406
  const desc = backdrop.querySelector("#cf-desc")?.value;
@@ -327,7 +1419,7 @@ function bindModalEvents(cfg, opts) {
327
1419
  description: desc.trim(),
328
1420
  type: "BUG",
329
1421
  priority: "MEDIUM",
330
- screenshot: screenshotDataUrl || void 0,
1422
+ screenshot: annotatedImageUrl || screenshotDataUrl || void 0,
331
1423
  annotations: annotationsData.length > 0 ? annotationsData : void 0,
332
1424
  reporter_name: name?.trim() || void 0,
333
1425
  reporter_email: email?.trim() || void 0
@@ -389,9 +1481,30 @@ function injectStyles(cfg) {
389
1481
  background:rgba(0,0,0,.45);
390
1482
  z-index:100001;
391
1483
  display:flex;
1484
+ font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
1485
+ }
1486
+ #cf-modal-backdrop.cf-backdrop--center {
392
1487
  align-items:center;
393
1488
  justify-content:center;
394
- font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
1489
+ }
1490
+ #cf-modal-backdrop.cf-backdrop--corner {
1491
+ padding:20px;
1492
+ }
1493
+ #cf-modal-backdrop.cf-pos-bottom-right {
1494
+ align-items:flex-end;
1495
+ justify-content:flex-end;
1496
+ }
1497
+ #cf-modal-backdrop.cf-pos-bottom-left {
1498
+ align-items:flex-end;
1499
+ justify-content:flex-start;
1500
+ }
1501
+ #cf-modal-backdrop.cf-pos-top-right {
1502
+ align-items:flex-start;
1503
+ justify-content:flex-end;
1504
+ }
1505
+ #cf-modal-backdrop.cf-pos-top-left {
1506
+ align-items:flex-start;
1507
+ justify-content:flex-start;
395
1508
  }
396
1509
 
397
1510
  .cf-modal {
@@ -600,6 +1713,8 @@ function getPositionCSS(pos) {
600
1713
  return "top:20px;right:20px;";
601
1714
  case "top-left":
602
1715
  return "top:20px;left:20px;";
1716
+ case "center":
1717
+ return "bottom:20px;left:50%;transform:translateX(-50%);";
603
1718
  default:
604
1719
  return "bottom:20px;right:20px;";
605
1720
  }
@@ -632,6 +1747,8 @@ function getModalHTML(cfg, expanded) {
632
1747
  </div>
633
1748
  `;
634
1749
  if (expanded && screenshotDataUrl) {
1750
+ const displayImg = annotatedImageUrl || screenshotDataUrl;
1751
+ const annotLabel = annotationsData.length > 0 ? `Annoter (${annotationsData.length})` : "Annoter";
635
1752
  return `
636
1753
  <div class="${modalClass}">
637
1754
  <div class="cf-modal-header">
@@ -640,10 +1757,9 @@ function getModalHTML(cfg, expanded) {
640
1757
  </div>
641
1758
  <div class="cf-modal-body">
642
1759
  <div class="cf-screenshot-panel">
643
- <img src="${screenshotDataUrl}" alt="Capture d'\xE9cran" />
1760
+ <img id="cf-screenshot-img" src="${displayImg}" alt="Capture d'\xE9cran" />
644
1761
  <div class="cf-screenshot-tools">
645
- <button class="cf-tool-highlight" id="cf-highlight-btn">Surligner</button>
646
- <button class="cf-tool-mask" id="cf-mask-btn">Masquer</button>
1762
+ <button class="cf-tool-highlight" id="cf-annotate-btn">${annotLabel}</button>
647
1763
  </div>
648
1764
  </div>
649
1765
  <div class="cf-form-panel">
@@ -704,8 +1820,7 @@ function init(cfg) {
704
1820
  });
705
1821
  },
706
1822
  {
707
- onScreenshot: () => captureScreenshot(),
708
- onHighlight: () => startHighlighting()
1823
+ onScreenshot: () => captureScreenshot()
709
1824
  }
710
1825
  );
711
1826
  }
@@ -776,8 +1891,7 @@ function showWidget() {
776
1891
  });
777
1892
  },
778
1893
  {
779
- onScreenshot: () => captureScreenshot(),
780
- onHighlight: () => startHighlighting()
1894
+ onScreenshot: () => captureScreenshot()
781
1895
  }
782
1896
  );
783
1897
  }
@@ -797,6 +1911,5 @@ export {
797
1911
  hideWidget,
798
1912
  init,
799
1913
  sendFeedback,
800
- showWidget,
801
- startHighlighting
1914
+ showWidget
802
1915
  };