@checkflow/sdk 1.1.2 → 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/react.js CHANGED
@@ -135,79 +135,1350 @@ var init_console_interceptor = __esm({
135
135
  }
136
136
  });
137
137
 
138
+ // src/network-interceptor.ts
139
+ function installNetworkInterceptor() {
140
+ if (installed2 || typeof window === "undefined") return;
141
+ installed2 = true;
142
+ const origFetch = window.fetch;
143
+ window.fetch = async function(...args) {
144
+ const start = Date.now();
145
+ const req = new Request(...args);
146
+ const entry = {
147
+ method: req.method,
148
+ url: req.url,
149
+ type: "fetch",
150
+ timestamp: start
151
+ };
152
+ try {
153
+ const res = await origFetch.apply(this, args);
154
+ entry.status = res.status;
155
+ entry.duration = Date.now() - start;
156
+ entry.response_type = res.headers.get("content-type") || void 0;
157
+ pushLog(entry);
158
+ return res;
159
+ } catch (err) {
160
+ entry.duration = Date.now() - start;
161
+ entry.error = err.message || "Network error";
162
+ pushLog(entry);
163
+ throw err;
164
+ }
165
+ };
166
+ const origOpen = XMLHttpRequest.prototype.open;
167
+ const origSend = XMLHttpRequest.prototype.send;
168
+ XMLHttpRequest.prototype.open = function(method, url, ...rest) {
169
+ this.__cf_method = method;
170
+ this.__cf_url = String(url);
171
+ return origOpen.apply(this, [method, url, ...rest]);
172
+ };
173
+ XMLHttpRequest.prototype.send = function(...args) {
174
+ const start = Date.now();
175
+ const xhr = this;
176
+ xhr.addEventListener("loadend", () => {
177
+ const entry = {
178
+ method: xhr.__cf_method || "GET",
179
+ url: xhr.__cf_url || "",
180
+ status: xhr.status,
181
+ duration: Date.now() - start,
182
+ type: "xhr",
183
+ timestamp: start,
184
+ response_type: xhr.getResponseHeader("content-type") || void 0
185
+ };
186
+ if (xhr.status === 0) entry.error = "Request failed";
187
+ pushLog(entry);
188
+ });
189
+ return origSend.apply(this, args);
190
+ };
191
+ }
192
+ function pushLog(entry) {
193
+ if (entry.url.includes("/sdk/feedback")) return;
194
+ logs2.push(entry);
195
+ if (logs2.length > MAX_LOGS2) logs2.shift();
196
+ }
197
+ function getNetworkLogs() {
198
+ return [...logs2];
199
+ }
200
+ function clearNetworkLogs() {
201
+ logs2 = [];
202
+ }
203
+ var MAX_LOGS2, logs2, installed2;
204
+ var init_network_interceptor = __esm({
205
+ "src/network-interceptor.ts"() {
206
+ "use strict";
207
+ MAX_LOGS2 = 50;
208
+ logs2 = [];
209
+ installed2 = false;
210
+ }
211
+ });
212
+
213
+ // src/annotation/types.ts
214
+ var DEFAULT_STYLE, HIGHLIGHT_STYLE, BLUR_STYLE, COLOR_PALETTE, STROKE_WIDTHS;
215
+ var init_types = __esm({
216
+ "src/annotation/types.ts"() {
217
+ "use strict";
218
+ DEFAULT_STYLE = {
219
+ strokeColor: "#FF3B30",
220
+ strokeWidth: 3,
221
+ fillColor: "transparent",
222
+ opacity: 1,
223
+ fontSize: 16,
224
+ fontFamily: "system-ui, -apple-system, sans-serif"
225
+ };
226
+ HIGHLIGHT_STYLE = {
227
+ strokeColor: "transparent",
228
+ fillColor: "#FFEB3B",
229
+ opacity: 0.4
230
+ };
231
+ BLUR_STYLE = {
232
+ strokeColor: "#666666",
233
+ fillColor: "#666666",
234
+ opacity: 0.8
235
+ };
236
+ COLOR_PALETTE = [
237
+ "#FF3B30",
238
+ // Red
239
+ "#FF9500",
240
+ // Orange
241
+ "#FFCC00",
242
+ // Yellow
243
+ "#34C759",
244
+ // Green
245
+ "#007AFF",
246
+ // Blue
247
+ "#5856D6",
248
+ // Purple
249
+ "#AF52DE",
250
+ // Pink
251
+ "#000000",
252
+ // Black
253
+ "#FFFFFF"
254
+ // White
255
+ ];
256
+ STROKE_WIDTHS = [1, 2, 3, 5, 8];
257
+ }
258
+ });
259
+
260
+ // src/annotation/toolbar.ts
261
+ var TOOL_ICONS, TOOL_LABELS, AnnotationToolbar;
262
+ var init_toolbar = __esm({
263
+ "src/annotation/toolbar.ts"() {
264
+ "use strict";
265
+ init_types();
266
+ TOOL_ICONS = {
267
+ 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>`,
268
+ 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>`,
269
+ ellipse: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><ellipse cx="12" cy="12" rx="9" ry="6"/></svg>`,
270
+ 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>`,
271
+ line: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="5" y1="19" x2="19" y2="5"/></svg>`,
272
+ 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>`,
273
+ 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>`,
274
+ text: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M5 4v3h5.5v12h3V7H19V4z"/></svg>`,
275
+ 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>`
276
+ };
277
+ TOOL_LABELS = {
278
+ select: "S\xE9lection",
279
+ rectangle: "Rectangle",
280
+ ellipse: "Ellipse",
281
+ arrow: "Fl\xE8che",
282
+ line: "Ligne",
283
+ highlight: "Surbrillance",
284
+ blur: "Floutage",
285
+ text: "Texte",
286
+ freehand: "Dessin libre"
287
+ };
288
+ AnnotationToolbar = class {
289
+ constructor(config2) {
290
+ this.colorPicker = null;
291
+ this.strokePicker = null;
292
+ this.config = config2;
293
+ this.element = this.createToolbar();
294
+ }
295
+ getElement() {
296
+ return this.element;
297
+ }
298
+ setActiveTool(tool) {
299
+ this.config.activeTool = tool;
300
+ this.updateActiveState();
301
+ }
302
+ setStyle(style) {
303
+ this.config.style = style;
304
+ this.updateStyleDisplay();
305
+ }
306
+ createToolbar() {
307
+ const toolbar = document.createElement("div");
308
+ toolbar.className = "cf-toolbar";
309
+ const toolsSection = document.createElement("div");
310
+ toolsSection.className = "cf-toolbar-section cf-toolbar-tools";
311
+ for (const tool of this.config.tools) {
312
+ const button = this.createToolButton(tool);
313
+ toolsSection.appendChild(button);
314
+ }
315
+ const separator1 = document.createElement("div");
316
+ separator1.className = "cf-toolbar-separator";
317
+ const styleSection = document.createElement("div");
318
+ styleSection.className = "cf-toolbar-section cf-toolbar-style";
319
+ const colorBtn = document.createElement("button");
320
+ colorBtn.className = "cf-toolbar-btn cf-color-btn";
321
+ colorBtn.title = "Couleur";
322
+ colorBtn.innerHTML = `<span class="cf-color-preview" style="background: ${this.config.style.strokeColor}"></span>`;
323
+ colorBtn.addEventListener("click", () => this.toggleColorPicker());
324
+ styleSection.appendChild(colorBtn);
325
+ const strokeBtn = document.createElement("button");
326
+ strokeBtn.className = "cf-toolbar-btn cf-stroke-btn";
327
+ strokeBtn.title = "\xC9paisseur";
328
+ strokeBtn.innerHTML = `<span class="cf-stroke-preview">${this.config.style.strokeWidth}px</span>`;
329
+ strokeBtn.addEventListener("click", () => this.toggleStrokePicker());
330
+ styleSection.appendChild(strokeBtn);
331
+ const separator2 = document.createElement("div");
332
+ separator2.className = "cf-toolbar-separator";
333
+ const actionsSection = document.createElement("div");
334
+ actionsSection.className = "cf-toolbar-section cf-toolbar-actions";
335
+ const undoBtn = document.createElement("button");
336
+ undoBtn.className = "cf-toolbar-btn";
337
+ undoBtn.title = "Annuler (Ctrl+Z)";
338
+ 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>`;
339
+ undoBtn.addEventListener("click", () => this.config.onUndo());
340
+ actionsSection.appendChild(undoBtn);
341
+ const clearBtn = document.createElement("button");
342
+ clearBtn.className = "cf-toolbar-btn";
343
+ clearBtn.title = "Tout effacer";
344
+ 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>`;
345
+ clearBtn.addEventListener("click", () => this.config.onClear());
346
+ actionsSection.appendChild(clearBtn);
347
+ toolbar.appendChild(toolsSection);
348
+ toolbar.appendChild(separator1);
349
+ toolbar.appendChild(styleSection);
350
+ toolbar.appendChild(separator2);
351
+ toolbar.appendChild(actionsSection);
352
+ return toolbar;
353
+ }
354
+ createToolButton(tool) {
355
+ const button = document.createElement("button");
356
+ button.className = `cf-toolbar-btn cf-tool-btn ${tool === this.config.activeTool ? "active" : ""}`;
357
+ button.dataset.tool = tool;
358
+ button.title = TOOL_LABELS[tool];
359
+ button.innerHTML = TOOL_ICONS[tool];
360
+ button.addEventListener("click", () => {
361
+ this.config.onToolChange(tool);
362
+ this.setActiveTool(tool);
363
+ });
364
+ return button;
365
+ }
366
+ updateActiveState() {
367
+ const buttons = this.element.querySelectorAll(".cf-tool-btn");
368
+ buttons.forEach((btn) => {
369
+ const button = btn;
370
+ button.classList.toggle("active", button.dataset.tool === this.config.activeTool);
371
+ });
372
+ }
373
+ updateStyleDisplay() {
374
+ const colorPreview = this.element.querySelector(".cf-color-preview");
375
+ if (colorPreview) {
376
+ colorPreview.style.background = this.config.style.strokeColor;
377
+ }
378
+ const strokePreview = this.element.querySelector(".cf-stroke-preview");
379
+ if (strokePreview) {
380
+ strokePreview.textContent = `${this.config.style.strokeWidth}px`;
381
+ }
382
+ }
383
+ toggleColorPicker() {
384
+ if (this.colorPicker) {
385
+ this.colorPicker.remove();
386
+ this.colorPicker = null;
387
+ return;
388
+ }
389
+ if (this.strokePicker) {
390
+ this.strokePicker.remove();
391
+ this.strokePicker = null;
392
+ }
393
+ this.colorPicker = document.createElement("div");
394
+ this.colorPicker.className = "cf-picker cf-color-picker";
395
+ for (const color of COLOR_PALETTE) {
396
+ const swatch = document.createElement("button");
397
+ swatch.className = `cf-color-swatch ${color === this.config.style.strokeColor ? "active" : ""}`;
398
+ swatch.style.background = color;
399
+ swatch.addEventListener("click", () => {
400
+ this.config.onStyleChange({ strokeColor: color });
401
+ this.setStyle({ ...this.config.style, strokeColor: color });
402
+ this.colorPicker?.remove();
403
+ this.colorPicker = null;
404
+ });
405
+ this.colorPicker.appendChild(swatch);
406
+ }
407
+ const colorBtn = this.element.querySelector(".cf-color-btn");
408
+ colorBtn?.appendChild(this.colorPicker);
409
+ }
410
+ toggleStrokePicker() {
411
+ if (this.strokePicker) {
412
+ this.strokePicker.remove();
413
+ this.strokePicker = null;
414
+ return;
415
+ }
416
+ if (this.colorPicker) {
417
+ this.colorPicker.remove();
418
+ this.colorPicker = null;
419
+ }
420
+ this.strokePicker = document.createElement("div");
421
+ this.strokePicker.className = "cf-picker cf-stroke-picker";
422
+ for (const width of STROKE_WIDTHS) {
423
+ const option = document.createElement("button");
424
+ option.className = `cf-stroke-option ${width === this.config.style.strokeWidth ? "active" : ""}`;
425
+ option.innerHTML = `<span style="height: ${width}px"></span> ${width}px`;
426
+ option.addEventListener("click", () => {
427
+ this.config.onStyleChange({ strokeWidth: width });
428
+ this.setStyle({ ...this.config.style, strokeWidth: width });
429
+ this.strokePicker?.remove();
430
+ this.strokePicker = null;
431
+ });
432
+ this.strokePicker.appendChild(option);
433
+ }
434
+ const strokeBtn = this.element.querySelector(".cf-stroke-btn");
435
+ strokeBtn?.appendChild(this.strokePicker);
436
+ }
437
+ };
438
+ }
439
+ });
440
+
441
+ // src/annotation/styles.ts
442
+ function injectAnnotationStyles() {
443
+ if (stylesInjected) return;
444
+ const styleElement = document.createElement("style");
445
+ styleElement.id = "cf-annotation-styles";
446
+ styleElement.textContent = STYLES;
447
+ document.head.appendChild(styleElement);
448
+ stylesInjected = true;
449
+ }
450
+ var STYLES, stylesInjected;
451
+ var init_styles = __esm({
452
+ "src/annotation/styles.ts"() {
453
+ "use strict";
454
+ STYLES = `
455
+ /* Annotation Overlay */
456
+ .cf-annotation-overlay {
457
+ position: fixed;
458
+ top: 0;
459
+ left: 0;
460
+ right: 0;
461
+ bottom: 0;
462
+ z-index: 999999;
463
+ background: rgba(0, 0, 0, 0.85);
464
+ display: flex;
465
+ align-items: center;
466
+ justify-content: center;
467
+ animation: cf-fade-in 0.2s ease-out;
468
+ }
469
+
470
+ @keyframes cf-fade-in {
471
+ from { opacity: 0; }
472
+ to { opacity: 1; }
473
+ }
474
+
475
+ /* Editor Wrapper */
476
+ .cf-annotation-wrapper {
477
+ display: flex;
478
+ flex-direction: column;
479
+ max-width: 95vw;
480
+ max-height: 95vh;
481
+ background: #1a1a1a;
482
+ border-radius: 12px;
483
+ overflow: hidden;
484
+ box-shadow: 0 25px 50px rgba(0, 0, 0, 0.5);
485
+ }
486
+
487
+ /* Canvas Container */
488
+ .cf-annotation-canvas-container {
489
+ flex: 1;
490
+ display: flex;
491
+ align-items: center;
492
+ justify-content: center;
493
+ overflow: auto;
494
+ padding: 16px;
495
+ background: #0d0d0d;
496
+ }
497
+
498
+ .cf-annotation-canvas {
499
+ max-width: 100%;
500
+ max-height: calc(95vh - 140px);
501
+ border-radius: 4px;
502
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
503
+ cursor: crosshair;
504
+ }
505
+
506
+ /* Toolbar */
507
+ .cf-toolbar {
508
+ display: flex;
509
+ align-items: center;
510
+ gap: 8px;
511
+ padding: 12px 16px;
512
+ background: #2a2a2a;
513
+ border-bottom: 1px solid #3a3a3a;
514
+ }
515
+
516
+ .cf-toolbar-section {
517
+ display: flex;
518
+ align-items: center;
519
+ gap: 4px;
520
+ }
521
+
522
+ .cf-toolbar-separator {
523
+ width: 1px;
524
+ height: 24px;
525
+ background: #4a4a4a;
526
+ margin: 0 8px;
527
+ }
528
+
529
+ .cf-toolbar-btn {
530
+ display: flex;
531
+ align-items: center;
532
+ justify-content: center;
533
+ width: 36px;
534
+ height: 36px;
535
+ padding: 0;
536
+ border: none;
537
+ border-radius: 8px;
538
+ background: transparent;
539
+ color: #999;
540
+ cursor: pointer;
541
+ transition: all 0.15s ease;
542
+ }
543
+
544
+ .cf-toolbar-btn:hover {
545
+ background: #3a3a3a;
546
+ color: #fff;
547
+ }
548
+
549
+ .cf-toolbar-btn.active {
550
+ background: #007AFF;
551
+ color: #fff;
552
+ }
553
+
554
+ .cf-toolbar-btn svg {
555
+ width: 20px;
556
+ height: 20px;
557
+ }
558
+
559
+ /* Color Button */
560
+ .cf-color-btn {
561
+ position: relative;
562
+ }
563
+
564
+ .cf-color-preview {
565
+ width: 20px;
566
+ height: 20px;
567
+ border-radius: 50%;
568
+ border: 2px solid #fff;
569
+ box-shadow: 0 0 0 1px rgba(0,0,0,0.2);
570
+ }
571
+
572
+ /* Stroke Button */
573
+ .cf-stroke-btn {
574
+ width: auto;
575
+ padding: 0 12px;
576
+ }
577
+
578
+ .cf-stroke-preview {
579
+ font-size: 12px;
580
+ font-weight: 500;
581
+ color: inherit;
582
+ }
583
+
584
+ /* Pickers */
585
+ .cf-picker {
586
+ position: absolute;
587
+ top: 100%;
588
+ left: 50%;
589
+ transform: translateX(-50%);
590
+ margin-top: 8px;
591
+ padding: 8px;
592
+ background: #2a2a2a;
593
+ border-radius: 8px;
594
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
595
+ z-index: 10;
596
+ }
597
+
598
+ .cf-color-picker {
599
+ display: grid;
600
+ grid-template-columns: repeat(5, 1fr);
601
+ gap: 4px;
602
+ width: 140px;
603
+ }
604
+
605
+ .cf-color-swatch {
606
+ width: 24px;
607
+ height: 24px;
608
+ border-radius: 50%;
609
+ border: 2px solid transparent;
610
+ cursor: pointer;
611
+ transition: transform 0.15s ease;
612
+ }
613
+
614
+ .cf-color-swatch:hover {
615
+ transform: scale(1.15);
616
+ }
617
+
618
+ .cf-color-swatch.active {
619
+ border-color: #fff;
620
+ }
621
+
622
+ .cf-stroke-picker {
623
+ display: flex;
624
+ flex-direction: column;
625
+ gap: 4px;
626
+ min-width: 80px;
627
+ }
628
+
629
+ .cf-stroke-option {
630
+ display: flex;
631
+ align-items: center;
632
+ gap: 8px;
633
+ padding: 6px 10px;
634
+ border: none;
635
+ border-radius: 4px;
636
+ background: transparent;
637
+ color: #999;
638
+ font-size: 12px;
639
+ cursor: pointer;
640
+ transition: all 0.15s ease;
641
+ }
642
+
643
+ .cf-stroke-option:hover {
644
+ background: #3a3a3a;
645
+ color: #fff;
646
+ }
647
+
648
+ .cf-stroke-option.active {
649
+ background: #007AFF;
650
+ color: #fff;
651
+ }
652
+
653
+ .cf-stroke-option span {
654
+ width: 24px;
655
+ background: currentColor;
656
+ border-radius: 2px;
657
+ }
658
+
659
+ /* Action Buttons */
660
+ .cf-annotation-actions {
661
+ display: flex;
662
+ justify-content: flex-end;
663
+ gap: 12px;
664
+ padding: 12px 16px;
665
+ background: #2a2a2a;
666
+ border-top: 1px solid #3a3a3a;
667
+ }
668
+
669
+ .cf-btn {
670
+ display: flex;
671
+ align-items: center;
672
+ justify-content: center;
673
+ padding: 10px 20px;
674
+ border: none;
675
+ border-radius: 8px;
676
+ font-size: 14px;
677
+ font-weight: 500;
678
+ cursor: pointer;
679
+ transition: all 0.15s ease;
680
+ }
681
+
682
+ .cf-btn-primary {
683
+ background: #007AFF;
684
+ color: #fff;
685
+ }
686
+
687
+ .cf-btn-primary:hover {
688
+ background: #0066DD;
689
+ }
690
+
691
+ .cf-btn-secondary {
692
+ background: #3a3a3a;
693
+ color: #fff;
694
+ }
695
+
696
+ .cf-btn-secondary:hover {
697
+ background: #4a4a4a;
698
+ }
699
+
700
+ /* Text Input */
701
+ .cf-annotation-text-input {
702
+ position: fixed;
703
+ z-index: 1000000;
704
+ padding: 4px 8px;
705
+ border: 2px solid #007AFF;
706
+ border-radius: 4px;
707
+ background: rgba(255, 255, 255, 0.95);
708
+ font-size: 16px;
709
+ font-family: system-ui, -apple-system, sans-serif;
710
+ outline: none;
711
+ min-width: 150px;
712
+ }
713
+
714
+ /* Tool-specific cursors */
715
+ .cf-annotation-canvas[data-tool="select"] { cursor: default; }
716
+ .cf-annotation-canvas[data-tool="rectangle"] { cursor: crosshair; }
717
+ .cf-annotation-canvas[data-tool="ellipse"] { cursor: crosshair; }
718
+ .cf-annotation-canvas[data-tool="arrow"] { cursor: crosshair; }
719
+ .cf-annotation-canvas[data-tool="line"] { cursor: crosshair; }
720
+ .cf-annotation-canvas[data-tool="highlight"] { cursor: crosshair; }
721
+ .cf-annotation-canvas[data-tool="blur"] { cursor: crosshair; }
722
+ .cf-annotation-canvas[data-tool="text"] { cursor: text; }
723
+ .cf-annotation-canvas[data-tool="freehand"] { cursor: crosshair; }
724
+
725
+ /* Responsive */
726
+ @media (max-width: 768px) {
727
+ .cf-toolbar {
728
+ flex-wrap: wrap;
729
+ justify-content: center;
730
+ }
731
+
732
+ .cf-toolbar-separator {
733
+ display: none;
734
+ }
735
+
736
+ .cf-annotation-actions {
737
+ justify-content: stretch;
738
+ }
739
+
740
+ .cf-annotation-actions .cf-btn {
741
+ flex: 1;
742
+ }
743
+ }
744
+ `;
745
+ stylesInjected = false;
746
+ }
747
+ });
748
+
749
+ // src/annotation/editor.ts
750
+ var AnnotationEditor;
751
+ var init_editor = __esm({
752
+ "src/annotation/editor.ts"() {
753
+ "use strict";
754
+ init_types();
755
+ init_toolbar();
756
+ init_styles();
757
+ AnnotationEditor = class {
758
+ constructor(config2) {
759
+ this.container = null;
760
+ this.canvas = null;
761
+ this.ctx = null;
762
+ this.toolbar = null;
763
+ this.annotations = [];
764
+ this.backgroundImage = null;
765
+ this.state = {
766
+ activeTool: "rectangle",
767
+ style: { ...DEFAULT_STYLE },
768
+ isDrawing: false,
769
+ currentAnnotation: null
770
+ };
771
+ this.startPoint = null;
772
+ this.freehandPoints = [];
773
+ this.textInput = null;
774
+ this.config = {
775
+ ...config2,
776
+ tools: config2.tools || ["select", "rectangle", "arrow", "highlight", "blur", "text", "freehand"],
777
+ defaultStyle: config2.defaultStyle || DEFAULT_STYLE
778
+ };
779
+ this.state.style = { ...this.config.defaultStyle };
780
+ }
781
+ /**
782
+ * Open the annotation editor with a screenshot
783
+ */
784
+ async open(screenshotDataUrl2) {
785
+ injectAnnotationStyles();
786
+ this.backgroundImage = await this.loadImage(screenshotDataUrl2);
787
+ this.createEditorUI();
788
+ this.setupEventListeners();
789
+ this.render();
790
+ }
791
+ /**
792
+ * Close the editor
793
+ */
794
+ close() {
795
+ if (this.container) {
796
+ this.container.remove();
797
+ this.container = null;
798
+ }
799
+ this.canvas = null;
800
+ this.ctx = null;
801
+ this.toolbar = null;
802
+ this.annotations = [];
803
+ this.backgroundImage = null;
804
+ }
805
+ /**
806
+ * Get the annotated image as data URL
807
+ */
808
+ getAnnotatedImage() {
809
+ if (!this.canvas || !this.ctx) return "";
810
+ const tempCanvas = document.createElement("canvas");
811
+ tempCanvas.width = this.canvas.width;
812
+ tempCanvas.height = this.canvas.height;
813
+ const tempCtx = tempCanvas.getContext("2d");
814
+ if (this.backgroundImage) {
815
+ tempCtx.drawImage(this.backgroundImage, 0, 0);
816
+ }
817
+ this.drawAnnotations(tempCtx);
818
+ return tempCanvas.toDataURL("image/png");
819
+ }
820
+ /**
821
+ * Get annotations data
822
+ */
823
+ getAnnotations() {
824
+ return [...this.annotations];
825
+ }
826
+ // Private methods
827
+ loadImage(src) {
828
+ return new Promise((resolve, reject) => {
829
+ const img = new Image();
830
+ img.onload = () => resolve(img);
831
+ img.onerror = reject;
832
+ img.src = src;
833
+ });
834
+ }
835
+ createEditorUI() {
836
+ this.container = document.createElement("div");
837
+ this.container.className = "cf-annotation-overlay";
838
+ const wrapper = document.createElement("div");
839
+ wrapper.className = "cf-annotation-wrapper";
840
+ const canvasContainer = document.createElement("div");
841
+ canvasContainer.className = "cf-annotation-canvas-container";
842
+ this.canvas = document.createElement("canvas");
843
+ this.canvas.className = "cf-annotation-canvas";
844
+ this.canvas.width = this.backgroundImage?.width || 1920;
845
+ this.canvas.height = this.backgroundImage?.height || 1080;
846
+ this.ctx = this.canvas.getContext("2d");
847
+ canvasContainer.appendChild(this.canvas);
848
+ this.toolbar = new AnnotationToolbar({
849
+ tools: this.config.tools,
850
+ activeTool: this.state.activeTool,
851
+ style: this.state.style,
852
+ onToolChange: (tool) => this.setActiveTool(tool),
853
+ onStyleChange: (style) => this.setStyle(style),
854
+ onUndo: () => this.undo(),
855
+ onClear: () => this.clearAll(),
856
+ onSave: () => this.save(),
857
+ onCancel: () => this.cancel()
858
+ });
859
+ const actions = document.createElement("div");
860
+ actions.className = "cf-annotation-actions";
861
+ actions.innerHTML = `
862
+ <button class="cf-btn cf-btn-secondary" data-action="cancel">Annuler</button>
863
+ <button class="cf-btn cf-btn-primary" data-action="save">Enregistrer</button>
864
+ `;
865
+ wrapper.appendChild(this.toolbar.getElement());
866
+ wrapper.appendChild(canvasContainer);
867
+ wrapper.appendChild(actions);
868
+ this.container.appendChild(wrapper);
869
+ document.body.appendChild(this.container);
870
+ actions.querySelector('[data-action="cancel"]')?.addEventListener("click", () => this.cancel());
871
+ actions.querySelector('[data-action="save"]')?.addEventListener("click", () => this.save());
872
+ }
873
+ setupEventListeners() {
874
+ if (!this.canvas) return;
875
+ this.canvas.addEventListener("mousedown", this.handleMouseDown.bind(this));
876
+ this.canvas.addEventListener("mousemove", this.handleMouseMove.bind(this));
877
+ this.canvas.addEventListener("mouseup", this.handleMouseUp.bind(this));
878
+ this.canvas.addEventListener("mouseleave", this.handleMouseUp.bind(this));
879
+ this.canvas.addEventListener("touchstart", this.handleTouchStart.bind(this));
880
+ this.canvas.addEventListener("touchmove", this.handleTouchMove.bind(this));
881
+ this.canvas.addEventListener("touchend", this.handleTouchEnd.bind(this));
882
+ document.addEventListener("keydown", this.handleKeyDown.bind(this));
883
+ }
884
+ handleMouseDown(e) {
885
+ const point = this.getCanvasPoint(e);
886
+ this.startDrawing(point);
887
+ }
888
+ handleMouseMove(e) {
889
+ const point = this.getCanvasPoint(e);
890
+ this.continueDrawing(point);
891
+ }
892
+ handleMouseUp(_e) {
893
+ this.finishDrawing();
894
+ }
895
+ handleTouchStart(e) {
896
+ e.preventDefault();
897
+ const touch = e.touches[0];
898
+ const point = this.getCanvasPointFromTouch(touch);
899
+ this.startDrawing(point);
900
+ }
901
+ handleTouchMove(e) {
902
+ e.preventDefault();
903
+ const touch = e.touches[0];
904
+ const point = this.getCanvasPointFromTouch(touch);
905
+ this.continueDrawing(point);
906
+ }
907
+ handleTouchEnd(e) {
908
+ e.preventDefault();
909
+ this.finishDrawing();
910
+ }
911
+ handleKeyDown(e) {
912
+ if ((e.ctrlKey || e.metaKey) && e.key === "z") {
913
+ e.preventDefault();
914
+ this.undo();
915
+ }
916
+ if (e.key === "Escape") {
917
+ if (this.state.isDrawing) {
918
+ this.state.isDrawing = false;
919
+ this.state.currentAnnotation = null;
920
+ this.render();
921
+ } else {
922
+ this.cancel();
923
+ }
924
+ }
925
+ if (e.key === "Enter" && !this.state.isDrawing) {
926
+ this.save();
927
+ }
928
+ }
929
+ getCanvasPoint(e) {
930
+ const rect = this.canvas.getBoundingClientRect();
931
+ const scaleX = this.canvas.width / rect.width;
932
+ const scaleY = this.canvas.height / rect.height;
933
+ return {
934
+ x: (e.clientX - rect.left) * scaleX,
935
+ y: (e.clientY - rect.top) * scaleY
936
+ };
937
+ }
938
+ getCanvasPointFromTouch(touch) {
939
+ const rect = this.canvas.getBoundingClientRect();
940
+ const scaleX = this.canvas.width / rect.width;
941
+ const scaleY = this.canvas.height / rect.height;
942
+ return {
943
+ x: (touch.clientX - rect.left) * scaleX,
944
+ y: (touch.clientY - rect.top) * scaleY
945
+ };
946
+ }
947
+ startDrawing(point) {
948
+ if (this.state.activeTool === "select") return;
949
+ this.state.isDrawing = true;
950
+ this.startPoint = point;
951
+ if (this.state.activeTool === "text") {
952
+ this.showTextInput(point);
953
+ return;
954
+ }
955
+ if (this.state.activeTool === "freehand") {
956
+ this.freehandPoints = [point];
957
+ }
958
+ }
959
+ continueDrawing(point) {
960
+ if (!this.state.isDrawing || !this.startPoint) return;
961
+ if (this.state.activeTool === "freehand") {
962
+ this.freehandPoints.push(point);
963
+ }
964
+ this.state.currentAnnotation = this.createAnnotation(this.startPoint, point);
965
+ this.render();
966
+ }
967
+ finishDrawing() {
968
+ if (!this.state.isDrawing || !this.state.currentAnnotation) {
969
+ this.state.isDrawing = false;
970
+ return;
971
+ }
972
+ if (this.isValidAnnotation(this.state.currentAnnotation)) {
973
+ this.annotations.push(this.state.currentAnnotation);
974
+ }
975
+ this.state.isDrawing = false;
976
+ this.state.currentAnnotation = null;
977
+ this.startPoint = null;
978
+ this.freehandPoints = [];
979
+ this.render();
980
+ }
981
+ isValidAnnotation(annotation) {
982
+ switch (annotation.type) {
983
+ case "rectangle":
984
+ case "ellipse":
985
+ case "highlight":
986
+ case "blur":
987
+ const bounds = annotation.bounds;
988
+ return Math.abs(bounds.width) > 5 && Math.abs(bounds.height) > 5;
989
+ case "arrow":
990
+ case "line":
991
+ const start = annotation.start;
992
+ const end = annotation.end;
993
+ const dist = Math.sqrt(Math.pow(end.x - start.x, 2) + Math.pow(end.y - start.y, 2));
994
+ return dist > 10;
995
+ case "freehand":
996
+ return annotation.points.length > 2;
997
+ case "text":
998
+ return !!annotation.text?.trim();
999
+ default:
1000
+ return true;
1001
+ }
1002
+ }
1003
+ createAnnotation(start, end) {
1004
+ const id = this.generateId();
1005
+ const baseStyle = { ...this.state.style };
1006
+ switch (this.state.activeTool) {
1007
+ case "rectangle":
1008
+ return {
1009
+ id,
1010
+ type: "rectangle",
1011
+ bounds: this.createBounds(start, end),
1012
+ style: baseStyle,
1013
+ timestamp: Date.now()
1014
+ };
1015
+ case "ellipse":
1016
+ return {
1017
+ id,
1018
+ type: "ellipse",
1019
+ bounds: this.createBounds(start, end),
1020
+ style: baseStyle,
1021
+ timestamp: Date.now()
1022
+ };
1023
+ case "arrow":
1024
+ return {
1025
+ id,
1026
+ type: "arrow",
1027
+ start: { ...start },
1028
+ end: { ...end },
1029
+ style: baseStyle,
1030
+ timestamp: Date.now()
1031
+ };
1032
+ case "line":
1033
+ return {
1034
+ id,
1035
+ type: "line",
1036
+ start: { ...start },
1037
+ end: { ...end },
1038
+ style: baseStyle,
1039
+ timestamp: Date.now()
1040
+ };
1041
+ case "highlight":
1042
+ return {
1043
+ id,
1044
+ type: "highlight",
1045
+ bounds: this.createBounds(start, end),
1046
+ style: { ...baseStyle, ...HIGHLIGHT_STYLE },
1047
+ timestamp: Date.now()
1048
+ };
1049
+ case "blur":
1050
+ return {
1051
+ id,
1052
+ type: "blur",
1053
+ bounds: this.createBounds(start, end),
1054
+ style: { ...baseStyle, ...BLUR_STYLE },
1055
+ blurAmount: 10,
1056
+ timestamp: Date.now()
1057
+ };
1058
+ case "freehand":
1059
+ return {
1060
+ id,
1061
+ type: "freehand",
1062
+ points: [...this.freehandPoints],
1063
+ style: baseStyle,
1064
+ timestamp: Date.now()
1065
+ };
1066
+ default:
1067
+ return {
1068
+ id,
1069
+ type: "rectangle",
1070
+ bounds: this.createBounds(start, end),
1071
+ style: baseStyle,
1072
+ timestamp: Date.now()
1073
+ };
1074
+ }
1075
+ }
1076
+ createBounds(start, end) {
1077
+ return {
1078
+ x: Math.min(start.x, end.x),
1079
+ y: Math.min(start.y, end.y),
1080
+ width: Math.abs(end.x - start.x),
1081
+ height: Math.abs(end.y - start.y)
1082
+ };
1083
+ }
1084
+ showTextInput(point) {
1085
+ if (this.textInput) {
1086
+ this.textInput.remove();
1087
+ }
1088
+ const rect = this.canvas.getBoundingClientRect();
1089
+ const scaleX = rect.width / this.canvas.width;
1090
+ const scaleY = rect.height / this.canvas.height;
1091
+ this.textInput = document.createElement("input");
1092
+ this.textInput.type = "text";
1093
+ this.textInput.className = "cf-annotation-text-input";
1094
+ this.textInput.style.left = `${rect.left + point.x * scaleX}px`;
1095
+ this.textInput.style.top = `${rect.top + point.y * scaleY}px`;
1096
+ this.textInput.style.color = this.state.style.strokeColor;
1097
+ this.textInput.style.fontSize = `${(this.state.style.fontSize || 16) * scaleY}px`;
1098
+ this.textInput.placeholder = "Tapez votre texte...";
1099
+ document.body.appendChild(this.textInput);
1100
+ this.textInput.focus();
1101
+ const handleTextSubmit = () => {
1102
+ if (this.textInput && this.textInput.value.trim()) {
1103
+ const textAnnotation = {
1104
+ id: this.generateId(),
1105
+ type: "text",
1106
+ position: point,
1107
+ text: this.textInput.value.trim(),
1108
+ style: { ...this.state.style },
1109
+ timestamp: Date.now()
1110
+ };
1111
+ this.annotations.push(textAnnotation);
1112
+ this.render();
1113
+ }
1114
+ this.textInput?.remove();
1115
+ this.textInput = null;
1116
+ this.state.isDrawing = false;
1117
+ };
1118
+ this.textInput.addEventListener("blur", handleTextSubmit);
1119
+ this.textInput.addEventListener("keydown", (e) => {
1120
+ if (e.key === "Enter") {
1121
+ handleTextSubmit();
1122
+ } else if (e.key === "Escape") {
1123
+ this.textInput?.remove();
1124
+ this.textInput = null;
1125
+ this.state.isDrawing = false;
1126
+ }
1127
+ });
1128
+ }
1129
+ generateId() {
1130
+ return `ann_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
1131
+ }
1132
+ setActiveTool(tool) {
1133
+ this.state.activeTool = tool;
1134
+ this.toolbar?.setActiveTool(tool);
1135
+ if (this.canvas) {
1136
+ this.canvas.style.cursor = tool === "select" ? "default" : "crosshair";
1137
+ }
1138
+ }
1139
+ setStyle(style) {
1140
+ this.state.style = { ...this.state.style, ...style };
1141
+ this.toolbar?.setStyle(this.state.style);
1142
+ }
1143
+ undo() {
1144
+ if (this.annotations.length > 0) {
1145
+ this.annotations.pop();
1146
+ this.render();
1147
+ }
1148
+ }
1149
+ clearAll() {
1150
+ this.annotations = [];
1151
+ this.render();
1152
+ }
1153
+ save() {
1154
+ const imageData = this.getAnnotatedImage();
1155
+ this.config.onSave?.(this.annotations, imageData);
1156
+ this.close();
1157
+ }
1158
+ cancel() {
1159
+ this.config.onCancel?.();
1160
+ this.close();
1161
+ }
1162
+ render() {
1163
+ if (!this.ctx || !this.canvas) return;
1164
+ this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
1165
+ if (this.backgroundImage) {
1166
+ this.ctx.drawImage(this.backgroundImage, 0, 0);
1167
+ }
1168
+ this.drawAnnotations(this.ctx);
1169
+ if (this.state.currentAnnotation) {
1170
+ this.drawAnnotation(this.ctx, this.state.currentAnnotation);
1171
+ }
1172
+ }
1173
+ drawAnnotations(ctx) {
1174
+ for (const annotation of this.annotations) {
1175
+ this.drawAnnotation(ctx, annotation);
1176
+ }
1177
+ }
1178
+ drawAnnotation(ctx, annotation) {
1179
+ ctx.save();
1180
+ ctx.globalAlpha = annotation.style.opacity;
1181
+ ctx.strokeStyle = annotation.style.strokeColor;
1182
+ ctx.fillStyle = annotation.style.fillColor;
1183
+ ctx.lineWidth = annotation.style.strokeWidth;
1184
+ ctx.lineCap = "round";
1185
+ ctx.lineJoin = "round";
1186
+ switch (annotation.type) {
1187
+ case "rectangle":
1188
+ this.drawRectangle(ctx, annotation.bounds, annotation.style);
1189
+ break;
1190
+ case "ellipse":
1191
+ this.drawEllipse(ctx, annotation.bounds, annotation.style);
1192
+ break;
1193
+ case "arrow":
1194
+ this.drawArrow(ctx, annotation.start, annotation.end, annotation.style);
1195
+ break;
1196
+ case "line":
1197
+ this.drawLine(ctx, annotation.start, annotation.end);
1198
+ break;
1199
+ case "highlight":
1200
+ this.drawHighlight(ctx, annotation.bounds, annotation.style);
1201
+ break;
1202
+ case "blur":
1203
+ this.drawBlur(ctx, annotation.bounds, annotation.blurAmount);
1204
+ break;
1205
+ case "text":
1206
+ this.drawText(ctx, annotation.position, annotation.text, annotation.style);
1207
+ break;
1208
+ case "freehand":
1209
+ this.drawFreehand(ctx, annotation.points);
1210
+ break;
1211
+ }
1212
+ ctx.restore();
1213
+ }
1214
+ drawRectangle(ctx, bounds, style) {
1215
+ if (style.fillColor !== "transparent") {
1216
+ ctx.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
1217
+ }
1218
+ ctx.strokeRect(bounds.x, bounds.y, bounds.width, bounds.height);
1219
+ }
1220
+ drawEllipse(ctx, bounds, style) {
1221
+ const centerX = bounds.x + bounds.width / 2;
1222
+ const centerY = bounds.y + bounds.height / 2;
1223
+ const radiusX = bounds.width / 2;
1224
+ const radiusY = bounds.height / 2;
1225
+ ctx.beginPath();
1226
+ ctx.ellipse(centerX, centerY, radiusX, radiusY, 0, 0, Math.PI * 2);
1227
+ if (style.fillColor !== "transparent") {
1228
+ ctx.fill();
1229
+ }
1230
+ ctx.stroke();
1231
+ }
1232
+ drawArrow(ctx, start, end, style) {
1233
+ const headLength = 15 + style.strokeWidth * 2;
1234
+ const angle = Math.atan2(end.y - start.y, end.x - start.x);
1235
+ ctx.beginPath();
1236
+ ctx.moveTo(start.x, start.y);
1237
+ ctx.lineTo(end.x, end.y);
1238
+ ctx.stroke();
1239
+ ctx.beginPath();
1240
+ ctx.moveTo(end.x, end.y);
1241
+ ctx.lineTo(
1242
+ end.x - headLength * Math.cos(angle - Math.PI / 6),
1243
+ end.y - headLength * Math.sin(angle - Math.PI / 6)
1244
+ );
1245
+ ctx.lineTo(
1246
+ end.x - headLength * Math.cos(angle + Math.PI / 6),
1247
+ end.y - headLength * Math.sin(angle + Math.PI / 6)
1248
+ );
1249
+ ctx.closePath();
1250
+ ctx.fillStyle = style.strokeColor;
1251
+ ctx.fill();
1252
+ }
1253
+ drawLine(ctx, start, end) {
1254
+ ctx.beginPath();
1255
+ ctx.moveTo(start.x, start.y);
1256
+ ctx.lineTo(end.x, end.y);
1257
+ ctx.stroke();
1258
+ }
1259
+ drawHighlight(ctx, bounds, style) {
1260
+ ctx.fillStyle = style.fillColor;
1261
+ ctx.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
1262
+ }
1263
+ drawBlur(ctx, bounds, blurAmount) {
1264
+ const pixelSize = Math.max(blurAmount, 5);
1265
+ if (this.backgroundImage) {
1266
+ const tempCanvas = document.createElement("canvas");
1267
+ tempCanvas.width = bounds.width;
1268
+ tempCanvas.height = bounds.height;
1269
+ const tempCtx = tempCanvas.getContext("2d");
1270
+ tempCtx.drawImage(
1271
+ this.backgroundImage,
1272
+ bounds.x,
1273
+ bounds.y,
1274
+ bounds.width,
1275
+ bounds.height,
1276
+ 0,
1277
+ 0,
1278
+ bounds.width,
1279
+ bounds.height
1280
+ );
1281
+ const w = tempCanvas.width;
1282
+ const h = tempCanvas.height;
1283
+ tempCtx.imageSmoothingEnabled = false;
1284
+ tempCtx.drawImage(tempCanvas, 0, 0, w, h, 0, 0, w / pixelSize, h / pixelSize);
1285
+ tempCtx.drawImage(tempCanvas, 0, 0, w / pixelSize, h / pixelSize, 0, 0, w, h);
1286
+ ctx.drawImage(tempCanvas, bounds.x, bounds.y);
1287
+ }
1288
+ ctx.strokeStyle = "#999";
1289
+ ctx.lineWidth = 1;
1290
+ ctx.setLineDash([5, 5]);
1291
+ ctx.strokeRect(bounds.x, bounds.y, bounds.width, bounds.height);
1292
+ ctx.setLineDash([]);
1293
+ }
1294
+ drawText(ctx, position, text, style) {
1295
+ ctx.font = `${style.fontSize || 16}px ${style.fontFamily || "system-ui"}`;
1296
+ ctx.fillStyle = style.strokeColor;
1297
+ ctx.textBaseline = "top";
1298
+ const metrics = ctx.measureText(text);
1299
+ const padding = 4;
1300
+ ctx.fillStyle = "rgba(255, 255, 255, 0.8)";
1301
+ ctx.fillRect(
1302
+ position.x - padding,
1303
+ position.y - padding,
1304
+ metrics.width + padding * 2,
1305
+ (style.fontSize || 16) + padding * 2
1306
+ );
1307
+ ctx.fillStyle = style.strokeColor;
1308
+ ctx.fillText(text, position.x, position.y);
1309
+ }
1310
+ drawFreehand(ctx, points) {
1311
+ if (points.length < 2) return;
1312
+ ctx.beginPath();
1313
+ ctx.moveTo(points[0].x, points[0].y);
1314
+ for (let i = 1; i < points.length; i++) {
1315
+ ctx.lineTo(points[i].x, points[i].y);
1316
+ }
1317
+ ctx.stroke();
1318
+ }
1319
+ };
1320
+ }
1321
+ });
1322
+
138
1323
  // src/widget.ts
139
1324
  function mountWidget(config2 = {}, onSubmit, opts) {
140
1325
  if (container) return;
141
1326
  onSubmitCallback = onSubmit;
142
1327
  const cfg = { ...DEFAULT_CONFIG, ...config2 };
1328
+ currentCfg = cfg;
143
1329
  container = document.createElement("div");
144
1330
  container.id = "checkflow-widget";
145
- container.innerHTML = getWidgetHTML(cfg);
1331
+ injectStyles(cfg);
1332
+ container.innerHTML = getTriggerHTML(cfg);
146
1333
  document.body.appendChild(container);
147
1334
  const btn = container.querySelector("#cf-trigger");
148
- const form = container.querySelector("#cf-form");
149
- const closeBtn = container.querySelector("#cf-close");
150
- const submitBtn = container.querySelector("#cf-submit");
151
- const screenshotBtn = container.querySelector("#cf-screenshot");
152
- const highlightBtn = container.querySelector("#cf-highlight");
153
- const screenshotPreview = container.querySelector("#cf-screenshot-preview");
154
- btn?.addEventListener("click", () => {
155
- form.style.display = form.style.display === "none" ? "flex" : "none";
156
- screenshotDataUrl = null;
157
- annotationsData = [];
158
- if (screenshotPreview) screenshotPreview.style.display = "none";
1335
+ btn?.addEventListener("click", () => openModal(cfg, opts));
1336
+ }
1337
+ function getBackdropClass(cfg, expanded) {
1338
+ if (expanded) return "cf-backdrop--center";
1339
+ if (cfg.position === "center") return "cf-backdrop--center";
1340
+ return "cf-backdrop--corner cf-pos-" + cfg.position;
1341
+ }
1342
+ function openModal(cfg, opts) {
1343
+ if (!container) return;
1344
+ const existing = document.getElementById("cf-modal-backdrop");
1345
+ if (existing) existing.remove();
1346
+ isExpanded = false;
1347
+ screenshotDataUrl = null;
1348
+ annotatedImageUrl = null;
1349
+ annotationsData = [];
1350
+ const backdrop = document.createElement("div");
1351
+ backdrop.id = "cf-modal-backdrop";
1352
+ backdrop.className = getBackdropClass(cfg, false);
1353
+ backdrop.innerHTML = getModalHTML(cfg, false);
1354
+ document.body.appendChild(backdrop);
1355
+ backdrop.addEventListener("click", (e) => {
1356
+ if (e.target === backdrop) closeModal();
159
1357
  });
160
- closeBtn?.addEventListener("click", () => {
161
- form.style.display = "none";
1358
+ bindModalEvents(cfg, opts);
1359
+ }
1360
+ function closeModal() {
1361
+ const backdrop = document.getElementById("cf-modal-backdrop");
1362
+ if (backdrop) backdrop.remove();
1363
+ isExpanded = false;
1364
+ screenshotDataUrl = null;
1365
+ annotatedImageUrl = null;
1366
+ annotationsData = [];
1367
+ }
1368
+ function expandModal(cfg, opts) {
1369
+ const backdrop = document.getElementById("cf-modal-backdrop");
1370
+ if (!backdrop) return;
1371
+ const name = backdrop.querySelector("#cf-name")?.value || "";
1372
+ const email = backdrop.querySelector("#cf-email")?.value || "";
1373
+ const desc = backdrop.querySelector("#cf-desc")?.value || "";
1374
+ isExpanded = true;
1375
+ backdrop.className = getBackdropClass(cfg, true);
1376
+ backdrop.innerHTML = getModalHTML(cfg, true);
1377
+ bindModalEvents(cfg, opts);
1378
+ const nameEl = backdrop.querySelector("#cf-name");
1379
+ const emailEl = backdrop.querySelector("#cf-email");
1380
+ const descEl = backdrop.querySelector("#cf-desc");
1381
+ if (nameEl) nameEl.value = name;
1382
+ if (emailEl) emailEl.value = email;
1383
+ if (descEl) descEl.value = desc;
1384
+ }
1385
+ function collapseModal(cfg, opts) {
1386
+ const backdrop = document.getElementById("cf-modal-backdrop");
1387
+ if (!backdrop) return;
1388
+ const name = backdrop.querySelector("#cf-name")?.value || "";
1389
+ const email = backdrop.querySelector("#cf-email")?.value || "";
1390
+ const desc = backdrop.querySelector("#cf-desc")?.value || "";
1391
+ isExpanded = false;
1392
+ screenshotDataUrl = null;
1393
+ annotatedImageUrl = null;
1394
+ annotationsData = [];
1395
+ backdrop.className = getBackdropClass(cfg, false);
1396
+ backdrop.innerHTML = getModalHTML(cfg, false);
1397
+ bindModalEvents(cfg, opts);
1398
+ const nameEl = backdrop.querySelector("#cf-name");
1399
+ const emailEl = backdrop.querySelector("#cf-email");
1400
+ const descEl = backdrop.querySelector("#cf-desc");
1401
+ if (nameEl) nameEl.value = name;
1402
+ if (emailEl) emailEl.value = email;
1403
+ if (descEl) descEl.value = desc;
1404
+ }
1405
+ function openAnnotationEditor(cfg, opts) {
1406
+ if (!screenshotDataUrl) return;
1407
+ const backdrop = document.getElementById("cf-modal-backdrop");
1408
+ if (backdrop) backdrop.style.display = "none";
1409
+ const editor = new AnnotationEditor({
1410
+ tools: ["rectangle", "ellipse", "arrow", "highlight", "blur", "text", "freehand"],
1411
+ defaultStyle: { strokeColor: "#FF3B30", strokeWidth: 3, fillColor: "transparent", opacity: 1, fontSize: 16, fontFamily: "system-ui, -apple-system, sans-serif" },
1412
+ onSave: (annotations, imageData) => {
1413
+ annotationsData = annotations;
1414
+ annotatedImageUrl = imageData;
1415
+ if (backdrop) {
1416
+ backdrop.style.display = "flex";
1417
+ const img = backdrop.querySelector("#cf-screenshot-img");
1418
+ if (img) img.src = annotatedImageUrl;
1419
+ const annotBtn = backdrop.querySelector("#cf-annotate-btn");
1420
+ if (annotBtn) annotBtn.textContent = `Annoter (${annotationsData.length})`;
1421
+ }
1422
+ },
1423
+ onCancel: () => {
1424
+ if (backdrop) backdrop.style.display = "flex";
1425
+ }
162
1426
  });
163
- screenshotBtn?.addEventListener("click", async () => {
1427
+ editor.open(screenshotDataUrl);
1428
+ }
1429
+ function bindModalEvents(cfg, opts) {
1430
+ const backdrop = document.getElementById("cf-modal-backdrop");
1431
+ if (!backdrop) return;
1432
+ backdrop.querySelector("#cf-cancel")?.addEventListener("click", closeModal);
1433
+ backdrop.querySelector("#cf-screenshot-btn")?.addEventListener("click", async () => {
164
1434
  if (!opts?.onScreenshot) return;
165
- screenshotBtn.textContent = "Capturing...";
166
- form.style.display = "none";
1435
+ backdrop.style.display = "none";
167
1436
  try {
168
1437
  const data = await opts.onScreenshot();
169
1438
  if (data) {
170
1439
  screenshotDataUrl = data;
171
- if (screenshotPreview) {
172
- screenshotPreview.querySelector("img").src = data;
173
- screenshotPreview.style.display = "block";
174
- }
1440
+ backdrop.style.display = "flex";
1441
+ expandModal(cfg, opts);
1442
+ return;
175
1443
  }
176
1444
  } catch {
177
1445
  }
178
- form.style.display = "flex";
179
- screenshotBtn.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="m21 15-5-5L5 21"/></svg> Screenshot';
1446
+ backdrop.style.display = "flex";
180
1447
  });
181
- highlightBtn?.addEventListener("click", async () => {
182
- if (!opts?.onHighlight) return;
183
- form.style.display = "none";
184
- try {
185
- const annotations = await opts.onHighlight();
186
- if (annotations && annotations.length > 0) {
187
- annotationsData = annotations;
188
- highlightBtn.innerHTML = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/></svg> ${annotations.length} highlighted`;
1448
+ backdrop.querySelector("#cf-remove-screenshot")?.addEventListener("click", () => {
1449
+ collapseModal(cfg, opts);
1450
+ });
1451
+ backdrop.querySelector("#cf-annotate-btn")?.addEventListener("click", () => {
1452
+ openAnnotationEditor(cfg, opts);
1453
+ });
1454
+ backdrop.querySelector("#cf-submit")?.addEventListener("click", () => {
1455
+ const desc = backdrop.querySelector("#cf-desc")?.value;
1456
+ const name = backdrop.querySelector("#cf-name")?.value;
1457
+ const email = backdrop.querySelector("#cf-email")?.value;
1458
+ if (!desc?.trim()) {
1459
+ const descEl = backdrop.querySelector("#cf-desc");
1460
+ if (descEl) {
1461
+ descEl.style.borderColor = "#e74c3c";
1462
+ descEl.focus();
189
1463
  }
190
- } catch {
1464
+ return;
191
1465
  }
192
- form.style.display = "flex";
193
- });
194
- submitBtn?.addEventListener("click", () => {
195
- const title = container.querySelector("#cf-title")?.value;
196
- const desc = container.querySelector("#cf-desc")?.value;
197
- const type = container.querySelector("#cf-type")?.value;
198
- const priority = container.querySelector("#cf-priority")?.value;
199
- if (!title?.trim()) return;
200
- onSubmitCallback?.({ title, description: desc, type, priority, screenshot: screenshotDataUrl || void 0, annotations: annotationsData.length > 0 ? annotationsData : void 0 });
201
- container.querySelector("#cf-title").value = "";
202
- container.querySelector("#cf-desc").value = "";
203
- screenshotDataUrl = null;
204
- annotationsData = [];
205
- if (screenshotPreview) screenshotPreview.style.display = "none";
206
- form.style.display = "none";
207
- showToast("Feedback sent!");
1466
+ onSubmitCallback?.({
1467
+ title: desc.trim().slice(0, 100),
1468
+ description: desc.trim(),
1469
+ type: "BUG",
1470
+ priority: "MEDIUM",
1471
+ screenshot: annotatedImageUrl || screenshotDataUrl || void 0,
1472
+ annotations: annotationsData.length > 0 ? annotationsData : void 0,
1473
+ reporter_name: name?.trim() || void 0,
1474
+ reporter_email: email?.trim() || void 0
1475
+ });
1476
+ closeModal();
1477
+ showToast("Rapport envoy\xE9 avec succ\xE8s !");
208
1478
  });
209
1479
  }
210
1480
  function unmountWidget() {
1481
+ closeModal();
211
1482
  if (container) {
212
1483
  container.remove();
213
1484
  container = null;
@@ -216,10 +1487,273 @@ function unmountWidget() {
216
1487
  function showToast(msg) {
217
1488
  const toast = document.createElement("div");
218
1489
  toast.textContent = msg;
219
- toast.style.cssText = "position:fixed;bottom:80px;right:20px;background:#10b981;color:#fff;padding:8px 16px;border-radius:8px;font-size:13px;z-index:100001;font-family:system-ui;box-shadow:0 2px 8px rgba(0,0,0,.15);";
1490
+ toast.style.cssText = 'position:fixed;bottom:80px;right:20px;background:#10b981;color:#fff;padding:10px 20px;border-radius:10px;font-size:14px;z-index:100010;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;box-shadow:0 4px 16px rgba(0,0,0,.18);animation:cf-toast-in .3s ease;';
220
1491
  document.body.appendChild(toast);
1492
+ setTimeout(() => {
1493
+ toast.style.opacity = "0";
1494
+ toast.style.transition = "opacity .3s";
1495
+ }, 2500);
221
1496
  setTimeout(() => toast.remove(), 3e3);
222
1497
  }
1498
+ function injectStyles(cfg) {
1499
+ const style = document.createElement("style");
1500
+ style.id = "cf-widget-styles";
1501
+ style.textContent = `
1502
+ @keyframes cf-toast-in { from { opacity:0; transform:translateY(10px); } to { opacity:1; transform:translateY(0); } }
1503
+ @keyframes cf-modal-in { from { opacity:0; transform:scale(.96) translateY(8px); } to { opacity:1; transform:scale(1) translateY(0); } }
1504
+
1505
+ #cf-trigger {
1506
+ position:fixed;
1507
+ ${getPositionCSS(cfg.position)}
1508
+ z-index:100000;
1509
+ background:${cfg.color};
1510
+ color:#fff;
1511
+ border:none;
1512
+ border-radius:50px;
1513
+ padding:11px 20px;
1514
+ font-size:14px;
1515
+ font-weight:500;
1516
+ font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
1517
+ cursor:pointer;
1518
+ box-shadow:0 4px 16px rgba(0,0,0,.18);
1519
+ display:flex;
1520
+ align-items:center;
1521
+ gap:8px;
1522
+ transition:transform .15s,box-shadow .15s;
1523
+ }
1524
+ #cf-trigger:hover { transform:scale(1.04); box-shadow:0 6px 24px rgba(0,0,0,.22); }
1525
+ #cf-trigger svg { width:18px; height:18px; }
1526
+
1527
+ #cf-modal-backdrop {
1528
+ position:fixed;
1529
+ top:0;left:0;right:0;bottom:0;
1530
+ background:rgba(0,0,0,.45);
1531
+ z-index:100001;
1532
+ display:flex;
1533
+ font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
1534
+ }
1535
+ #cf-modal-backdrop.cf-backdrop--center {
1536
+ align-items:center;
1537
+ justify-content:center;
1538
+ }
1539
+ #cf-modal-backdrop.cf-backdrop--corner {
1540
+ padding:20px;
1541
+ }
1542
+ #cf-modal-backdrop.cf-pos-bottom-right {
1543
+ align-items:flex-end;
1544
+ justify-content:flex-end;
1545
+ }
1546
+ #cf-modal-backdrop.cf-pos-bottom-left {
1547
+ align-items:flex-end;
1548
+ justify-content:flex-start;
1549
+ }
1550
+ #cf-modal-backdrop.cf-pos-top-right {
1551
+ align-items:flex-start;
1552
+ justify-content:flex-end;
1553
+ }
1554
+ #cf-modal-backdrop.cf-pos-top-left {
1555
+ align-items:flex-start;
1556
+ justify-content:flex-start;
1557
+ }
1558
+
1559
+ .cf-modal {
1560
+ background:#fff;
1561
+ border-radius:16px;
1562
+ box-shadow:0 24px 80px rgba(0,0,0,.25);
1563
+ overflow:hidden;
1564
+ animation:cf-modal-in .25s ease;
1565
+ display:flex;
1566
+ flex-direction:column;
1567
+ max-height:90vh;
1568
+ }
1569
+ .cf-modal--compact { width:420px; }
1570
+ .cf-modal--expanded { width:min(1100px,92vw); flex-direction:column; }
1571
+
1572
+ .cf-modal-header {
1573
+ display:flex;
1574
+ align-items:center;
1575
+ justify-content:space-between;
1576
+ padding:20px 24px 16px;
1577
+ border-bottom:1px solid #f0f0f0;
1578
+ }
1579
+ .cf-modal-header h2 {
1580
+ margin:0;
1581
+ font-size:20px;
1582
+ font-weight:700;
1583
+ color:#1a1a2e;
1584
+ }
1585
+ .cf-modal-header .cf-logo {
1586
+ width:28px;
1587
+ height:28px;
1588
+ color:${cfg.color};
1589
+ }
1590
+
1591
+ .cf-modal-body { display:flex; flex:1; overflow:hidden; }
1592
+
1593
+ .cf-screenshot-panel {
1594
+ flex:1;
1595
+ min-width:0;
1596
+ background:#1a1a2e;
1597
+ display:flex;
1598
+ flex-direction:column;
1599
+ position:relative;
1600
+ }
1601
+ .cf-screenshot-panel img {
1602
+ width:100%;
1603
+ height:100%;
1604
+ object-fit:contain;
1605
+ max-height:60vh;
1606
+ }
1607
+ .cf-screenshot-tools {
1608
+ position:absolute;
1609
+ bottom:16px;
1610
+ left:50%;
1611
+ transform:translateX(-50%);
1612
+ display:flex;
1613
+ gap:8px;
1614
+ }
1615
+ .cf-screenshot-tools button {
1616
+ padding:8px 18px;
1617
+ border-radius:8px;
1618
+ font-size:13px;
1619
+ font-weight:500;
1620
+ cursor:pointer;
1621
+ font-family:inherit;
1622
+ border:none;
1623
+ transition:all .15s;
1624
+ }
1625
+ .cf-tool-highlight {
1626
+ background:${cfg.color};
1627
+ color:#fff;
1628
+ }
1629
+ .cf-tool-highlight:hover { opacity:.9; }
1630
+ .cf-tool-mask {
1631
+ background:#fff;
1632
+ color:#1a1a2e;
1633
+ border:1px solid #e0e0e0 !important;
1634
+ }
1635
+ .cf-tool-mask:hover { background:#f5f5f5; }
1636
+
1637
+ .cf-form-panel {
1638
+ width:100%;
1639
+ padding:20px 24px;
1640
+ display:flex;
1641
+ flex-direction:column;
1642
+ gap:14px;
1643
+ overflow-y:auto;
1644
+ }
1645
+ .cf-modal--expanded .cf-form-panel {
1646
+ width:340px;
1647
+ flex-shrink:0;
1648
+ border-left:1px solid #f0f0f0;
1649
+ }
1650
+
1651
+ .cf-field label {
1652
+ display:block;
1653
+ font-size:13px;
1654
+ font-weight:500;
1655
+ color:#1a1a2e;
1656
+ margin-bottom:6px;
1657
+ }
1658
+ .cf-field label span { font-weight:400; color:#999; }
1659
+ .cf-field input, .cf-field textarea {
1660
+ width:100%;
1661
+ padding:10px 14px;
1662
+ border:1px solid #e0e0e0;
1663
+ border-radius:10px;
1664
+ font-size:14px;
1665
+ font-family:inherit;
1666
+ box-sizing:border-box;
1667
+ outline:none;
1668
+ transition:border-color .15s,box-shadow .15s;
1669
+ color:#1a1a2e;
1670
+ background:#fff;
1671
+ }
1672
+ .cf-field input::placeholder, .cf-field textarea::placeholder { color:#bbb; }
1673
+ .cf-field input:focus, .cf-field textarea:focus {
1674
+ border-color:${cfg.color};
1675
+ box-shadow:0 0 0 3px ${cfg.color}18;
1676
+ }
1677
+ .cf-field textarea { resize:vertical; min-height:100px; }
1678
+
1679
+ .cf-screenshot-action {
1680
+ width:100%;
1681
+ padding:12px;
1682
+ border:1px solid #e0e0e0;
1683
+ border-radius:10px;
1684
+ background:#fff;
1685
+ font-size:14px;
1686
+ color:#1a1a2e;
1687
+ cursor:pointer;
1688
+ font-family:inherit;
1689
+ transition:all .15s;
1690
+ text-align:center;
1691
+ }
1692
+ .cf-screenshot-action:hover { background:#f8f8f8; border-color:#ccc; }
1693
+
1694
+ .cf-remove-screenshot {
1695
+ width:100%;
1696
+ padding:10px;
1697
+ border:1px solid #e0e0e0;
1698
+ border-radius:10px;
1699
+ background:#fff;
1700
+ font-size:13px;
1701
+ color:#666;
1702
+ cursor:pointer;
1703
+ font-family:inherit;
1704
+ transition:all .15s;
1705
+ text-align:center;
1706
+ }
1707
+ .cf-remove-screenshot:hover { background:#fff0f0; color:#e74c3c; border-color:#e74c3c; }
1708
+
1709
+ .cf-submit-btn {
1710
+ width:100%;
1711
+ padding:13px;
1712
+ border:none;
1713
+ border-radius:10px;
1714
+ background:${cfg.color};
1715
+ color:#fff;
1716
+ font-size:15px;
1717
+ font-weight:600;
1718
+ cursor:pointer;
1719
+ font-family:inherit;
1720
+ transition:all .15s;
1721
+ }
1722
+ .cf-submit-btn:hover { opacity:.92; transform:translateY(-1px); box-shadow:0 4px 12px ${cfg.color}40; }
1723
+ .cf-submit-btn:disabled { opacity:.5; cursor:not-allowed; transform:none; }
1724
+
1725
+ .cf-cancel-btn {
1726
+ width:100%;
1727
+ padding:11px;
1728
+ border:1px solid #e0e0e0;
1729
+ border-radius:10px;
1730
+ background:#fff;
1731
+ color:#1a1a2e;
1732
+ font-size:14px;
1733
+ font-weight:500;
1734
+ cursor:pointer;
1735
+ font-family:inherit;
1736
+ transition:all .15s;
1737
+ text-align:center;
1738
+ }
1739
+ .cf-cancel-btn:hover { background:#f8f8f8; }
1740
+
1741
+ .cf-footer {
1742
+ text-align:center;
1743
+ padding:12px;
1744
+ border-top:1px solid #f0f0f0;
1745
+ font-size:11px;
1746
+ color:#bbb;
1747
+ }
1748
+ .cf-footer a {
1749
+ color:#999;
1750
+ text-decoration:none;
1751
+ font-weight:500;
1752
+ }
1753
+ .cf-footer a:hover { color:${cfg.color}; }
1754
+ `;
1755
+ document.head.appendChild(style);
1756
+ }
223
1757
  function getPositionCSS(pos) {
224
1758
  switch (pos) {
225
1759
  case "bottom-left":
@@ -228,88 +1762,87 @@ function getPositionCSS(pos) {
228
1762
  return "top:20px;right:20px;";
229
1763
  case "top-left":
230
1764
  return "top:20px;left:20px;";
1765
+ case "center":
1766
+ return "bottom:20px;left:50%;transform:translateX(-50%);";
231
1767
  default:
232
1768
  return "bottom:20px;right:20px;";
233
1769
  }
234
1770
  }
235
- function getFormPositionCSS(pos) {
236
- switch (pos) {
237
- case "bottom-left":
238
- return "bottom:70px;left:20px;";
239
- case "top-right":
240
- return "top:70px;right:20px;";
241
- case "top-left":
242
- return "top:70px;left:20px;";
243
- default:
244
- return "bottom:70px;right:20px;";
1771
+ function getTriggerHTML(cfg) {
1772
+ return `
1773
+ <button id="cf-trigger">
1774
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
1775
+ ${cfg.text}
1776
+ </button>
1777
+ `;
1778
+ }
1779
+ function getCheckflowLogo() {
1780
+ return `<svg class="cf-logo" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12C2 6.5 6.5 2 12 2a10 10 0 0 1 8 4"/><path d="M5 19.5C5.5 18 6 15 6 12c0-.7.12-1.37.34-2"/><path d="M17.29 21.02c.12-.6.43-2.3.5-3.02"/><path d="M12 10a2 2 0 0 0-2 2c0 1.02-.1 2.51-.26 4"/><path d="M8.65 22c.21-.66.45-1.32.57-2"/><path d="M14 13.12c0 2.38 0 6.38-1 8.88"/><path d="M2 16h.01"/><path d="M21.8 16c.2-2 .131-5.354 0-6"/><path d="M9 6.8a6 6 0 0 1 9 5.2v2"/></svg>`;
1781
+ }
1782
+ function getModalHTML(cfg, expanded) {
1783
+ const modalClass = expanded ? "cf-modal cf-modal--expanded" : "cf-modal cf-modal--compact";
1784
+ const formFields = `
1785
+ <div class="cf-field">
1786
+ <label>Nom <span>(obligatoire)</span></label>
1787
+ <input id="cf-name" type="text" placeholder="Votre nom" />
1788
+ </div>
1789
+ <div class="cf-field">
1790
+ <label>Email <span>(obligatoire)</span></label>
1791
+ <input id="cf-email" type="email" placeholder="votre.email@exemple.com" />
1792
+ </div>
1793
+ <div class="cf-field">
1794
+ <label>Description <span>(obligatoire)</span></label>
1795
+ <textarea id="cf-desc" placeholder="Quel est le probl\xE8me ? Que vous attendiez-vous \xE0 voir ?"></textarea>
1796
+ </div>
1797
+ `;
1798
+ if (expanded && screenshotDataUrl) {
1799
+ const displayImg = annotatedImageUrl || screenshotDataUrl;
1800
+ const annotLabel = annotationsData.length > 0 ? `Annoter (${annotationsData.length})` : "Annoter";
1801
+ return `
1802
+ <div class="${modalClass}">
1803
+ <div class="cf-modal-header">
1804
+ <h2>Envoyer le rapport</h2>
1805
+ ${getCheckflowLogo()}
1806
+ </div>
1807
+ <div class="cf-modal-body">
1808
+ <div class="cf-screenshot-panel">
1809
+ <img id="cf-screenshot-img" src="${displayImg}" alt="Capture d'\xE9cran" />
1810
+ <div class="cf-screenshot-tools">
1811
+ <button class="cf-tool-highlight" id="cf-annotate-btn">${annotLabel}</button>
1812
+ </div>
1813
+ </div>
1814
+ <div class="cf-form-panel">
1815
+ ${formFields}
1816
+ <button class="cf-remove-screenshot" id="cf-remove-screenshot">Supprimer la capture d'\xE9cran</button>
1817
+ <button class="cf-submit-btn" id="cf-submit">Envoyer le rapport</button>
1818
+ <button class="cf-cancel-btn" id="cf-cancel">Annuler</button>
1819
+ </div>
1820
+ </div>
1821
+ <div class="cf-footer">Powered by <a href="https://checkflow.space" target="_blank" rel="noopener">Checkflow</a></div>
1822
+ </div>
1823
+ `;
245
1824
  }
246
- }
247
- function getWidgetHTML(cfg) {
248
- const posBtn = getPositionCSS(cfg.position);
249
- const posForm = getFormPositionCSS(cfg.position);
250
1825
  return `
251
- <style>
252
- #cf-trigger{position:fixed;${posBtn}z-index:100000;background:${cfg.color};color:#fff;border:none;border-radius:50px;padding:10px 18px;font-size:13px;font-family:system-ui,-apple-system,sans-serif;cursor:pointer;box-shadow:0 4px 12px rgba(0,0,0,.15);display:flex;align-items:center;gap:6px;transition:transform .15s}
253
- #cf-trigger:hover{transform:scale(1.05)}
254
- #cf-trigger svg{width:16px;height:16px}
255
- #cf-form{position:fixed;${posForm}z-index:100000;background:#fff;border-radius:12px;box-shadow:0 8px 30px rgba(0,0,0,.12);width:360px;padding:16px;font-family:system-ui,-apple-system,sans-serif;display:none;flex-direction:column;gap:10px}
256
- #cf-form h3{margin:0;font-size:15px;font-weight:600;color:#111}
257
- #cf-form input,#cf-form textarea,#cf-form select{width:100%;padding:8px 10px;border:1px solid #e2e8f0;border-radius:8px;font-size:13px;font-family:inherit;box-sizing:border-box;outline:none;transition:border .15s}
258
- #cf-form input:focus,#cf-form textarea:focus,#cf-form select:focus{border-color:${cfg.color}}
259
- #cf-form textarea{resize:vertical;min-height:60px}
260
- .cf-row{display:flex;gap:8px}
261
- .cf-row select{flex:1}
262
- .cf-tools{display:flex;gap:6px}
263
- .cf-tool-btn{display:flex;align-items:center;gap:4px;padding:6px 10px;border:1px solid #e2e8f0;border-radius:6px;background:#f8fafc;color:#475569;font-size:11px;cursor:pointer;font-family:inherit;transition:all .15s}
264
- .cf-tool-btn:hover{background:#f1f5f9;border-color:#cbd5e1}
265
- .cf-tool-btn svg{width:14px;height:14px}
266
- #cf-submit{background:${cfg.color};color:#fff;border:none;border-radius:8px;padding:9px;font-size:13px;font-weight:500;cursor:pointer;transition:opacity .15s}
267
- #cf-submit:hover{opacity:.9}
268
- #cf-close{position:absolute;top:10px;right:12px;background:none;border:none;cursor:pointer;font-size:18px;color:#94a3b8;line-height:1}
269
- #cf-screenshot-preview{display:none;border:1px solid #e2e8f0;border-radius:8px;overflow:hidden;max-height:120px}
270
- #cf-screenshot-preview img{width:100%;height:auto;display:block}
271
- </style>
272
- <button id="cf-trigger">
273
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
274
- ${cfg.text}
275
- </button>
276
- <div id="cf-form">
277
- <button id="cf-close">&times;</button>
278
- <h3>Report Feedback</h3>
279
- <input id="cf-title" type="text" placeholder="Title *" />
280
- <textarea id="cf-desc" placeholder="Description (optional)"></textarea>
281
- <div class="cf-row">
282
- <select id="cf-type">
283
- <option value="BUG">Bug</option>
284
- <option value="FEATURE">Feature</option>
285
- <option value="IMPROVEMENT">Improvement</option>
286
- <option value="QUESTION">Question</option>
287
- </select>
288
- <select id="cf-priority">
289
- <option value="LOW">Low</option>
290
- <option value="MEDIUM" selected>Medium</option>
291
- <option value="HIGH">High</option>
292
- <option value="CRITICAL">Critical</option>
293
- </select>
294
- </div>
295
- <div class="cf-tools">
296
- <button type="button" class="cf-tool-btn" id="cf-screenshot">
297
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="m21 15-5-5L5 21"/></svg>
298
- Screenshot
299
- </button>
300
- <button type="button" class="cf-tool-btn" id="cf-highlight">
301
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/></svg>
302
- Highlight
303
- </button>
304
- </div>
305
- <div id="cf-screenshot-preview"><img src="" alt="screenshot" /></div>
306
- <button id="cf-submit">Send Feedback</button>
307
- </div>`;
1826
+ <div class="${modalClass}">
1827
+ <div class="cf-modal-header">
1828
+ <h2>Envoyer le rapport</h2>
1829
+ ${getCheckflowLogo()}
1830
+ </div>
1831
+ <div class="cf-form-panel">
1832
+ ${formFields}
1833
+ <button class="cf-screenshot-action" id="cf-screenshot-btn">Ajouter une capture d'\xE9cran</button>
1834
+ <button class="cf-submit-btn" id="cf-submit">Envoyer le rapport</button>
1835
+ <button class="cf-cancel-btn" id="cf-cancel">Annuler</button>
1836
+ </div>
1837
+ <div class="cf-footer">Powered by <a href="https://checkflow.space" target="_blank" rel="noopener">Checkflow</a></div>
1838
+ </div>
1839
+ `;
308
1840
  }
309
- var DEFAULT_CONFIG, container, onSubmitCallback, screenshotDataUrl, annotationsData;
1841
+ var DEFAULT_CONFIG, container, onSubmitCallback, screenshotDataUrl, annotatedImageUrl, annotationsData, isExpanded, currentCfg;
310
1842
  var init_widget = __esm({
311
1843
  "src/widget.ts"() {
312
1844
  "use strict";
1845
+ init_editor();
313
1846
  DEFAULT_CONFIG = {
314
1847
  position: "bottom-right",
315
1848
  color: "#1e3a5f",
@@ -319,7 +1852,10 @@ var init_widget = __esm({
319
1852
  container = null;
320
1853
  onSubmitCallback = null;
321
1854
  screenshotDataUrl = null;
1855
+ annotatedImageUrl = null;
322
1856
  annotationsData = [];
1857
+ isExpanded = false;
1858
+ currentCfg = { ...DEFAULT_CONFIG };
323
1859
  }
324
1860
  });
325
1861
 
@@ -328,59 +1864,48 @@ var screenshot_exports = {};
328
1864
  __export(screenshot_exports, {
329
1865
  captureScreenshot: () => captureScreenshot,
330
1866
  clearScreenshot: () => clearScreenshot,
331
- getLastScreenshot: () => getLastScreenshot,
332
- loadHtml2Canvas: () => loadHtml2Canvas
1867
+ getLastScreenshot: () => getLastScreenshot
333
1868
  });
334
1869
  async function captureScreenshot() {
335
1870
  try {
336
- if (typeof window.html2canvas === "function") {
337
- const canvas2 = await window.html2canvas(document.body, {
338
- useCORS: true,
339
- allowTaint: true,
340
- scale: Math.min(window.devicePixelRatio, 2),
341
- logging: false,
342
- width: window.innerWidth,
343
- height: window.innerHeight,
344
- x: window.scrollX,
345
- y: window.scrollY
1871
+ const stream = await navigator.mediaDevices.getDisplayMedia({
1872
+ video: { displaySurface: "browser" },
1873
+ preferCurrentTab: true
1874
+ });
1875
+ const track = stream.getVideoTracks()[0];
1876
+ const imageCapture = new window.ImageCapture(track);
1877
+ await new Promise((r) => setTimeout(r, 300));
1878
+ let bitmap;
1879
+ try {
1880
+ bitmap = await imageCapture.grabFrame();
1881
+ } catch {
1882
+ const video = document.createElement("video");
1883
+ video.srcObject = stream;
1884
+ video.autoplay = true;
1885
+ await new Promise((resolve) => {
1886
+ video.onloadedmetadata = () => {
1887
+ video.play();
1888
+ resolve();
1889
+ };
346
1890
  });
347
- screenshotData = canvas2.toDataURL("image/png", 0.8);
1891
+ await new Promise((r) => setTimeout(r, 200));
1892
+ const canvas2 = document.createElement("canvas");
1893
+ canvas2.width = video.videoWidth;
1894
+ canvas2.height = video.videoHeight;
1895
+ const ctx2 = canvas2.getContext("2d");
1896
+ ctx2.drawImage(video, 0, 0);
1897
+ track.stop();
1898
+ screenshotData = canvas2.toDataURL("image/png", 0.92);
348
1899
  return screenshotData;
349
1900
  }
350
1901
  const canvas = document.createElement("canvas");
1902
+ canvas.width = bitmap.width;
1903
+ canvas.height = bitmap.height;
351
1904
  const ctx = canvas.getContext("2d");
352
- if (!ctx) return null;
353
- canvas.width = window.innerWidth;
354
- canvas.height = window.innerHeight;
355
- ctx.fillStyle = "#ffffff";
356
- ctx.fillRect(0, 0, canvas.width, canvas.height);
357
- ctx.fillStyle = "#333333";
358
- ctx.font = "14px system-ui";
359
- ctx.fillText(`Page: ${window.location.href}`, 10, 30);
360
- ctx.fillText(`Viewport: ${window.innerWidth}x${window.innerHeight}`, 10, 50);
361
- ctx.fillText(`Screenshot captured at ${(/* @__PURE__ */ new Date()).toISOString()}`, 10, 70);
362
- const svgData = `
363
- <svg xmlns="http://www.w3.org/2000/svg" width="${window.innerWidth}" height="${window.innerHeight}">
364
- <foreignObject width="100%" height="100%">
365
- <div xmlns="http://www.w3.org/1999/xhtml">
366
- ${document.documentElement.outerHTML}
367
- </div>
368
- </foreignObject>
369
- </svg>`;
370
- try {
371
- const blob = new Blob([svgData], { type: "image/svg+xml;charset=utf-8" });
372
- const url = URL.createObjectURL(blob);
373
- const img = new Image();
374
- await new Promise((resolve, reject) => {
375
- img.onload = () => resolve();
376
- img.onerror = () => reject();
377
- img.src = url;
378
- });
379
- ctx.drawImage(img, 0, 0);
380
- URL.revokeObjectURL(url);
381
- } catch {
382
- }
383
- screenshotData = canvas.toDataURL("image/png", 0.8);
1905
+ ctx.drawImage(bitmap, 0, 0);
1906
+ bitmap.close();
1907
+ track.stop();
1908
+ screenshotData = canvas.toDataURL("image/png", 0.92);
384
1909
  return screenshotData;
385
1910
  } catch {
386
1911
  return null;
@@ -392,19 +1917,6 @@ function getLastScreenshot() {
392
1917
  function clearScreenshot() {
393
1918
  screenshotData = null;
394
1919
  }
395
- function loadHtml2Canvas() {
396
- return new Promise((resolve, reject) => {
397
- if (typeof window.html2canvas === "function") {
398
- resolve();
399
- return;
400
- }
401
- const script = document.createElement("script");
402
- script.src = "https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js";
403
- script.onload = () => resolve();
404
- script.onerror = () => reject(new Error("Failed to load html2canvas"));
405
- document.head.appendChild(script);
406
- });
407
- }
408
1920
  var screenshotData;
409
1921
  var init_screenshot = __esm({
410
1922
  "src/screenshot.ts"() {
@@ -413,6 +1925,143 @@ var init_screenshot = __esm({
413
1925
  }
414
1926
  });
415
1927
 
1928
+ // src/index.ts
1929
+ var index_exports = {};
1930
+ __export(index_exports, {
1931
+ captureScreenshot: () => captureScreenshot,
1932
+ destroy: () => destroy,
1933
+ hideWidget: () => hideWidget,
1934
+ init: () => init,
1935
+ sendFeedback: () => sendFeedback,
1936
+ showWidget: () => showWidget
1937
+ });
1938
+ function init(cfg) {
1939
+ config = {
1940
+ enabled: true,
1941
+ endpoint: DEFAULT_ENDPOINT,
1942
+ environment: "production",
1943
+ ...cfg
1944
+ };
1945
+ if (!config.enabled) return;
1946
+ installInterceptors();
1947
+ installNetworkInterceptor();
1948
+ if (typeof window !== "undefined" && config.widget?.showOnInit !== false) {
1949
+ mountWidget(
1950
+ config.widget,
1951
+ (data) => {
1952
+ sendFeedback({
1953
+ title: data.title,
1954
+ description: data.description,
1955
+ type: data.type,
1956
+ priority: data.priority,
1957
+ screenshot_data: data.screenshot,
1958
+ annotations: data.annotations,
1959
+ reporter_name: data.reporter_name,
1960
+ reporter_email: data.reporter_email
1961
+ });
1962
+ },
1963
+ {
1964
+ onScreenshot: () => captureScreenshot()
1965
+ }
1966
+ );
1967
+ }
1968
+ }
1969
+ async function sendFeedback(data) {
1970
+ if (!config) {
1971
+ return { success: false, error: { message: "SDK not initialized. Call init() first.", code: "NOT_INITIALIZED" } };
1972
+ }
1973
+ const context = typeof window !== "undefined" ? collectContext() : {};
1974
+ const perf = typeof window !== "undefined" ? collectPerformance() : void 0;
1975
+ const consoleLogs = getConsoleLogs();
1976
+ const jsErrors = getJavascriptErrors();
1977
+ const networkLogs = getNetworkLogs();
1978
+ const payload = {
1979
+ ...data,
1980
+ ...context,
1981
+ environment: config.environment,
1982
+ performance_metrics: perf,
1983
+ console_logs: consoleLogs.length > 0 ? consoleLogs : void 0,
1984
+ javascript_errors: jsErrors.length > 0 ? jsErrors : void 0,
1985
+ network_logs: networkLogs.length > 0 ? networkLogs : void 0,
1986
+ sdk_version: SDK_VERSION
1987
+ };
1988
+ if (data.screenshot_data) {
1989
+ payload.screenshot_data = data.screenshot_data;
1990
+ }
1991
+ if (data.annotations && data.annotations.length > 0) {
1992
+ payload.annotations = data.annotations;
1993
+ }
1994
+ if (data.reporter_name) {
1995
+ payload.reporter_name = data.reporter_name;
1996
+ }
1997
+ if (data.reporter_email) {
1998
+ payload.reporter_email = data.reporter_email;
1999
+ }
2000
+ try {
2001
+ const res = await fetch(`${config.endpoint}/sdk/feedback`, {
2002
+ method: "POST",
2003
+ headers: {
2004
+ "Content-Type": "application/json",
2005
+ "X-API-Key": config.apiKey
2006
+ },
2007
+ body: JSON.stringify(payload)
2008
+ });
2009
+ const json = await res.json();
2010
+ if (!res.ok) {
2011
+ return { success: false, error: json.error || { message: "Request failed", code: "REQUEST_FAILED" } };
2012
+ }
2013
+ return { success: true, data: json.data };
2014
+ } catch (err) {
2015
+ return { success: false, error: { message: err.message, code: "NETWORK_ERROR" } };
2016
+ }
2017
+ }
2018
+ function showWidget() {
2019
+ if (!config) return;
2020
+ mountWidget(
2021
+ config.widget,
2022
+ (data) => {
2023
+ sendFeedback({
2024
+ title: data.title,
2025
+ description: data.description,
2026
+ type: data.type,
2027
+ priority: data.priority,
2028
+ screenshot_data: data.screenshot,
2029
+ annotations: data.annotations,
2030
+ reporter_name: data.reporter_name,
2031
+ reporter_email: data.reporter_email
2032
+ });
2033
+ },
2034
+ {
2035
+ onScreenshot: () => captureScreenshot()
2036
+ }
2037
+ );
2038
+ }
2039
+ function hideWidget() {
2040
+ unmountWidget();
2041
+ }
2042
+ function destroy() {
2043
+ unmountWidget();
2044
+ clearLogs();
2045
+ clearNetworkLogs();
2046
+ clearScreenshot();
2047
+ config = null;
2048
+ }
2049
+ var SDK_VERSION, DEFAULT_ENDPOINT, config;
2050
+ var init_index = __esm({
2051
+ "src/index.ts"() {
2052
+ "use strict";
2053
+ init_collector();
2054
+ init_console_interceptor();
2055
+ init_network_interceptor();
2056
+ init_widget();
2057
+ init_screenshot();
2058
+ init_screenshot();
2059
+ SDK_VERSION = "1.1.0";
2060
+ DEFAULT_ENDPOINT = "https://api.checkflow.space/api/v1";
2061
+ config = null;
2062
+ }
2063
+ });
2064
+
416
2065
  // src/highlighter.ts
417
2066
  var highlighter_exports = {};
418
2067
  __export(highlighter_exports, {
@@ -551,137 +2200,6 @@ var init_highlighter = __esm({
551
2200
  }
552
2201
  });
553
2202
 
554
- // src/index.ts
555
- var index_exports = {};
556
- __export(index_exports, {
557
- captureScreenshot: () => captureScreenshot,
558
- destroy: () => destroy,
559
- hideWidget: () => hideWidget,
560
- init: () => init,
561
- sendFeedback: () => sendFeedback,
562
- showWidget: () => showWidget,
563
- startHighlighting: () => startHighlighting
564
- });
565
- function init(cfg) {
566
- config = {
567
- enabled: true,
568
- endpoint: DEFAULT_ENDPOINT,
569
- environment: "production",
570
- ...cfg
571
- };
572
- if (!config.enabled) return;
573
- installInterceptors();
574
- if (typeof window !== "undefined") {
575
- loadHtml2Canvas().catch(() => {
576
- });
577
- }
578
- if (typeof window !== "undefined" && config.widget?.showOnInit !== false) {
579
- mountWidget(
580
- config.widget,
581
- (data) => {
582
- sendFeedback({
583
- title: data.title,
584
- description: data.description,
585
- type: data.type,
586
- priority: data.priority,
587
- screenshot_data: data.screenshot,
588
- annotations: data.annotations
589
- });
590
- },
591
- {
592
- onScreenshot: () => captureScreenshot(),
593
- onHighlight: () => startHighlighting()
594
- }
595
- );
596
- }
597
- }
598
- async function sendFeedback(data) {
599
- if (!config) {
600
- return { success: false, error: { message: "SDK not initialized. Call init() first.", code: "NOT_INITIALIZED" } };
601
- }
602
- const context = typeof window !== "undefined" ? collectContext() : {};
603
- const perf = typeof window !== "undefined" ? collectPerformance() : void 0;
604
- const consoleLogs = getConsoleLogs();
605
- const jsErrors = getJavascriptErrors();
606
- const payload = {
607
- ...data,
608
- ...context,
609
- environment: config.environment,
610
- performance_metrics: perf,
611
- console_logs: consoleLogs.length > 0 ? consoleLogs : void 0,
612
- javascript_errors: jsErrors.length > 0 ? jsErrors : void 0,
613
- sdk_version: SDK_VERSION
614
- };
615
- if (data.screenshot_data) {
616
- payload.screenshot_data = data.screenshot_data;
617
- }
618
- if (data.annotations && data.annotations.length > 0) {
619
- payload.annotations = data.annotations;
620
- }
621
- try {
622
- const res = await fetch(`${config.endpoint}/sdk/feedback`, {
623
- method: "POST",
624
- headers: {
625
- "Content-Type": "application/json",
626
- "X-API-Key": config.apiKey
627
- },
628
- body: JSON.stringify(payload)
629
- });
630
- const json = await res.json();
631
- if (!res.ok) {
632
- return { success: false, error: json.error || { message: "Request failed", code: "REQUEST_FAILED" } };
633
- }
634
- return { success: true, data: json.data };
635
- } catch (err) {
636
- return { success: false, error: { message: err.message, code: "NETWORK_ERROR" } };
637
- }
638
- }
639
- function showWidget() {
640
- if (!config) return;
641
- mountWidget(
642
- config.widget,
643
- (data) => {
644
- sendFeedback({
645
- title: data.title,
646
- description: data.description,
647
- type: data.type,
648
- priority: data.priority,
649
- screenshot_data: data.screenshot,
650
- annotations: data.annotations
651
- });
652
- },
653
- {
654
- onScreenshot: () => captureScreenshot(),
655
- onHighlight: () => startHighlighting()
656
- }
657
- );
658
- }
659
- function hideWidget() {
660
- unmountWidget();
661
- }
662
- function destroy() {
663
- unmountWidget();
664
- clearLogs();
665
- clearScreenshot();
666
- config = null;
667
- }
668
- var SDK_VERSION, DEFAULT_ENDPOINT, config;
669
- var init_index = __esm({
670
- "src/index.ts"() {
671
- "use strict";
672
- init_collector();
673
- init_console_interceptor();
674
- init_widget();
675
- init_screenshot();
676
- init_highlighter();
677
- init_screenshot();
678
- init_highlighter();
679
- SDK_VERSION = "1.1.0";
680
- DEFAULT_ENDPOINT = "https://api.checkflow.space/api/v1";
681
- config = null;
682
- }
683
- });
684
-
685
2203
  // src/react.ts
686
2204
  var react_exports = {};
687
2205
  __export(react_exports, {