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