uidex 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -35,6 +35,77 @@ interface KeyboardShortcut {
35
35
  altKey?: boolean;
36
36
  metaKey?: boolean;
37
37
  }
38
+ interface IngestConfig {
39
+ endpoint: string;
40
+ apiKey: string;
41
+ environment?: string;
42
+ appVersion?: string;
43
+ reporter?: {
44
+ email?: string;
45
+ name?: string;
46
+ };
47
+ metadata?: Record<string, string>;
48
+ captureConsole?: boolean;
49
+ captureNetwork?: boolean;
50
+ }
51
+ type FeedbackResult = {
52
+ ok: true;
53
+ id: string;
54
+ sequenceNumber: number;
55
+ } | {
56
+ ok: false;
57
+ error: string;
58
+ };
59
+ type FeedbackType = 'bug' | 'feature' | 'improvement' | 'question';
60
+ type FeedbackSeverity = 'low' | 'medium' | 'high' | 'critical';
61
+ interface ConsoleLogEntry {
62
+ level: 'warn' | 'error';
63
+ message: string;
64
+ timestamp: string;
65
+ }
66
+ interface NetworkErrorEntry {
67
+ url: string;
68
+ method: string;
69
+ status: number | null;
70
+ statusText: string;
71
+ timestamp: string;
72
+ }
73
+ interface FeedbackReport {
74
+ type: FeedbackType;
75
+ severity: FeedbackSeverity;
76
+ title?: string;
77
+ description: string;
78
+ componentId: string;
79
+ element: string | null;
80
+ sources: {
81
+ filePath: string;
82
+ line: number;
83
+ }[];
84
+ url: string;
85
+ path: string;
86
+ route: string | null;
87
+ timestamp: string;
88
+ pageTitle: string;
89
+ locale: string;
90
+ sessionId: string;
91
+ viewport: {
92
+ width: number;
93
+ height: number;
94
+ };
95
+ screen: {
96
+ width: number;
97
+ height: number;
98
+ };
99
+ userAgent: string;
100
+ screenshot?: string;
101
+ reporterEmail?: string;
102
+ reporterName?: string;
103
+ environment?: string;
104
+ appVersion?: string;
105
+ metadata?: Record<string, string>;
106
+ consoleLogs?: ConsoleLogEntry[];
107
+ networkErrors?: NetworkErrorEntry[];
108
+ }
38
109
 
39
110
  interface UidexDevtoolsProps {
40
111
  components?: UidexMap;
@@ -44,8 +115,12 @@ interface UidexDevtoolsProps {
44
115
  onSelect?: (id: string) => void;
45
116
  /** Keyboard shortcut to toggle inspect mode. Default: Shift+Cmd+U. Set to false to disable. */
46
117
  inspectShortcut?: KeyboardShortcut | false;
118
+ /** Ingest configuration for submitting feedback to a server. */
119
+ ingest?: IngestConfig;
120
+ /** Called after feedback submission with the report and result. */
121
+ onSubmit?: (report: FeedbackReport, result: FeedbackResult) => void;
47
122
  }
48
- declare function UidexDevtools({ components, config, buttonPosition, disabled, onSelect, inspectShortcut, }: UidexDevtoolsProps): null;
123
+ declare function UidexDevtools({ components, config, buttonPosition, disabled, onSelect, inspectShortcut, ingest, onSubmit, }: UidexDevtoolsProps): null;
49
124
 
50
125
  interface UidexOverlayProps {
51
126
  target: HTMLElement | null;
@@ -71,4 +146,4 @@ declare function getContrastColor(hexColor: string): string;
71
146
  declare function hexToRgba(hex: string, alpha: number): string;
72
147
  declare function resolveColor(color: string | undefined, colorMap?: Record<string, string>): string | undefined;
73
148
 
74
- export { type BorderStyle, type ButtonPosition, type KeyboardShortcut, type LabelPosition, type UidexConfig, type UidexDefaults, UidexDevtools, type UidexDevtoolsProps, type UidexFeature, type UidexLocation, type UidexMap, UidexOverlay, type UidexOverlayProps, type UidexPage, classNames, getComponents, getContrastColor, getFeatures, getPages, hexToRgba, registerComponents, registerFeatures, registerPages, resolveColor };
149
+ export { type BorderStyle, type ButtonPosition, type FeedbackReport, type FeedbackResult, type FeedbackSeverity, type FeedbackType, type IngestConfig, type KeyboardShortcut, type LabelPosition, type UidexConfig, type UidexDefaults, UidexDevtools, type UidexDevtoolsProps, type UidexFeature, type UidexLocation, type UidexMap, UidexOverlay, type UidexOverlayProps, type UidexPage, classNames, getComponents, getContrastColor, getFeatures, getPages, hexToRgba, registerComponents, registerFeatures, registerPages, resolveColor };
@@ -35,6 +35,77 @@ interface KeyboardShortcut {
35
35
  altKey?: boolean;
36
36
  metaKey?: boolean;
37
37
  }
38
+ interface IngestConfig {
39
+ endpoint: string;
40
+ apiKey: string;
41
+ environment?: string;
42
+ appVersion?: string;
43
+ reporter?: {
44
+ email?: string;
45
+ name?: string;
46
+ };
47
+ metadata?: Record<string, string>;
48
+ captureConsole?: boolean;
49
+ captureNetwork?: boolean;
50
+ }
51
+ type FeedbackResult = {
52
+ ok: true;
53
+ id: string;
54
+ sequenceNumber: number;
55
+ } | {
56
+ ok: false;
57
+ error: string;
58
+ };
59
+ type FeedbackType = 'bug' | 'feature' | 'improvement' | 'question';
60
+ type FeedbackSeverity = 'low' | 'medium' | 'high' | 'critical';
61
+ interface ConsoleLogEntry {
62
+ level: 'warn' | 'error';
63
+ message: string;
64
+ timestamp: string;
65
+ }
66
+ interface NetworkErrorEntry {
67
+ url: string;
68
+ method: string;
69
+ status: number | null;
70
+ statusText: string;
71
+ timestamp: string;
72
+ }
73
+ interface FeedbackReport {
74
+ type: FeedbackType;
75
+ severity: FeedbackSeverity;
76
+ title?: string;
77
+ description: string;
78
+ componentId: string;
79
+ element: string | null;
80
+ sources: {
81
+ filePath: string;
82
+ line: number;
83
+ }[];
84
+ url: string;
85
+ path: string;
86
+ route: string | null;
87
+ timestamp: string;
88
+ pageTitle: string;
89
+ locale: string;
90
+ sessionId: string;
91
+ viewport: {
92
+ width: number;
93
+ height: number;
94
+ };
95
+ screen: {
96
+ width: number;
97
+ height: number;
98
+ };
99
+ userAgent: string;
100
+ screenshot?: string;
101
+ reporterEmail?: string;
102
+ reporterName?: string;
103
+ environment?: string;
104
+ appVersion?: string;
105
+ metadata?: Record<string, string>;
106
+ consoleLogs?: ConsoleLogEntry[];
107
+ networkErrors?: NetworkErrorEntry[];
108
+ }
38
109
 
39
110
  interface UidexDevtoolsProps {
40
111
  components?: UidexMap;
@@ -44,8 +115,12 @@ interface UidexDevtoolsProps {
44
115
  onSelect?: (id: string) => void;
45
116
  /** Keyboard shortcut to toggle inspect mode. Default: Shift+Cmd+U. Set to false to disable. */
46
117
  inspectShortcut?: KeyboardShortcut | false;
118
+ /** Ingest configuration for submitting feedback to a server. */
119
+ ingest?: IngestConfig;
120
+ /** Called after feedback submission with the report and result. */
121
+ onSubmit?: (report: FeedbackReport, result: FeedbackResult) => void;
47
122
  }
48
- declare function UidexDevtools({ components, config, buttonPosition, disabled, onSelect, inspectShortcut, }: UidexDevtoolsProps): null;
123
+ declare function UidexDevtools({ components, config, buttonPosition, disabled, onSelect, inspectShortcut, ingest, onSubmit, }: UidexDevtoolsProps): null;
49
124
 
50
125
  interface UidexOverlayProps {
51
126
  target: HTMLElement | null;
@@ -71,4 +146,4 @@ declare function getContrastColor(hexColor: string): string;
71
146
  declare function hexToRgba(hex: string, alpha: number): string;
72
147
  declare function resolveColor(color: string | undefined, colorMap?: Record<string, string>): string | undefined;
73
148
 
74
- export { type BorderStyle, type ButtonPosition, type KeyboardShortcut, type LabelPosition, type UidexConfig, type UidexDefaults, UidexDevtools, type UidexDevtoolsProps, type UidexFeature, type UidexLocation, type UidexMap, UidexOverlay, type UidexOverlayProps, type UidexPage, classNames, getComponents, getContrastColor, getFeatures, getPages, hexToRgba, registerComponents, registerFeatures, registerPages, resolveColor };
149
+ export { type BorderStyle, type ButtonPosition, type FeedbackReport, type FeedbackResult, type FeedbackSeverity, type FeedbackType, type IngestConfig, type KeyboardShortcut, type LabelPosition, type UidexConfig, type UidexDefaults, UidexDevtools, type UidexDevtoolsProps, type UidexFeature, type UidexLocation, type UidexMap, UidexOverlay, type UidexOverlayProps, type UidexPage, classNames, getComponents, getContrastColor, getFeatures, getPages, hexToRgba, registerComponents, registerFeatures, registerPages, resolveColor };
@@ -528,6 +528,154 @@ var Inspector = class {
528
528
  }
529
529
  };
530
530
 
531
+ // src/core/ingest.ts
532
+ var MAX_CONSOLE_LOGS = 50;
533
+ var MAX_NETWORK_ERRORS = 20;
534
+ var nativeFetch = null;
535
+ function safeStringify(value) {
536
+ if (typeof value === "string") return value;
537
+ try {
538
+ return JSON.stringify(value);
539
+ } catch {
540
+ return String(value);
541
+ }
542
+ }
543
+ var IngestCapture = class {
544
+ constructor(captureConsole, captureNetwork) {
545
+ this.captureConsole = captureConsole;
546
+ this.captureNetwork = captureNetwork;
547
+ }
548
+ consoleLogs = [];
549
+ networkErrors = [];
550
+ originalConsoleWarn = null;
551
+ originalConsoleError = null;
552
+ originalFetch = null;
553
+ start() {
554
+ this.consoleLogs = [];
555
+ this.networkErrors = [];
556
+ if (this.captureConsole) this.interceptConsole();
557
+ if (this.captureNetwork) this.interceptNetwork();
558
+ }
559
+ stop() {
560
+ this.restoreConsole();
561
+ this.restoreNetwork();
562
+ }
563
+ getConsoleLogs() {
564
+ return [...this.consoleLogs];
565
+ }
566
+ getNetworkErrors() {
567
+ return [...this.networkErrors];
568
+ }
569
+ interceptConsole() {
570
+ if (this.originalConsoleWarn) return;
571
+ this.originalConsoleWarn = console.warn;
572
+ this.originalConsoleError = console.error;
573
+ console.warn = (...args) => {
574
+ this.addConsoleLog("warn", args);
575
+ this.originalConsoleWarn.apply(console, args);
576
+ };
577
+ console.error = (...args) => {
578
+ this.addConsoleLog("error", args);
579
+ this.originalConsoleError.apply(console, args);
580
+ };
581
+ }
582
+ addConsoleLog(level, args) {
583
+ this.consoleLogs.push({
584
+ level,
585
+ message: args.map(safeStringify).join(" "),
586
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
587
+ });
588
+ if (this.consoleLogs.length > MAX_CONSOLE_LOGS) {
589
+ this.consoleLogs.shift();
590
+ }
591
+ }
592
+ restoreConsole() {
593
+ if (this.originalConsoleWarn) {
594
+ console.warn = this.originalConsoleWarn;
595
+ this.originalConsoleWarn = null;
596
+ }
597
+ if (this.originalConsoleError) {
598
+ console.error = this.originalConsoleError;
599
+ this.originalConsoleError = null;
600
+ }
601
+ }
602
+ interceptNetwork() {
603
+ if (this.originalFetch) return;
604
+ this.originalFetch = window.fetch;
605
+ if (!nativeFetch) nativeFetch = this.originalFetch;
606
+ window.fetch = async (...args) => {
607
+ try {
608
+ const response = await this.originalFetch.apply(window, args);
609
+ if (!response.ok) {
610
+ this.addNetworkError(args[0], args[1]?.method, response.status, response.statusText);
611
+ }
612
+ return response;
613
+ } catch (error) {
614
+ this.addNetworkError(
615
+ args[0],
616
+ args[1]?.method,
617
+ null,
618
+ error instanceof Error ? error.message : "Network error"
619
+ );
620
+ throw error;
621
+ }
622
+ };
623
+ }
624
+ addNetworkError(input, method, status, statusText) {
625
+ let url;
626
+ if (typeof input === "string") {
627
+ url = input;
628
+ } else if (input instanceof URL) {
629
+ url = input.href;
630
+ } else {
631
+ url = input.url;
632
+ method ??= input.method;
633
+ }
634
+ this.networkErrors.push({
635
+ url,
636
+ method: method ?? "GET",
637
+ status,
638
+ statusText,
639
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
640
+ });
641
+ if (this.networkErrors.length > MAX_NETWORK_ERRORS) {
642
+ this.networkErrors.shift();
643
+ }
644
+ }
645
+ restoreNetwork() {
646
+ if (this.originalFetch) {
647
+ window.fetch = this.originalFetch;
648
+ this.originalFetch = null;
649
+ }
650
+ }
651
+ };
652
+ function generateSessionId() {
653
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
654
+ return crypto.randomUUID();
655
+ }
656
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
657
+ const r = Math.random() * 16 | 0;
658
+ const v = c === "x" ? r : r & 3 | 8;
659
+ return v.toString(16);
660
+ });
661
+ }
662
+ async function submitFeedback(endpoint, apiKey, report) {
663
+ const fetchFn = nativeFetch ?? fetch;
664
+ const response = await fetchFn(endpoint, {
665
+ method: "POST",
666
+ headers: {
667
+ "Content-Type": "application/json",
668
+ Authorization: `Bearer ${apiKey}`
669
+ },
670
+ body: JSON.stringify(report)
671
+ });
672
+ if (!response.ok) {
673
+ const text = await response.text().catch(() => "");
674
+ throw new Error(`Ingest failed (${response.status}): ${text}`);
675
+ }
676
+ return response.json();
677
+ }
678
+
531
679
  // src/core/modal.ts
532
680
  var Modal = class _Modal {
533
681
  backdrop = null;
@@ -1510,6 +1658,14 @@ var Modal = class _Modal {
1510
1658
  "medium"
1511
1659
  );
1512
1660
  form.appendChild(severitySelect.group);
1661
+ const titleGroup = this.createFormGroup("Title");
1662
+ const titleInput = document.createElement("input");
1663
+ titleInput.type = "text";
1664
+ titleInput.className = "uidex-form-input";
1665
+ titleInput.placeholder = "Brief summary (optional)";
1666
+ titleInput.maxLength = 200;
1667
+ titleGroup.appendChild(titleInput);
1668
+ form.appendChild(titleGroup);
1513
1669
  const descGroup = this.createFormGroup("Description");
1514
1670
  const textarea = document.createElement("textarea");
1515
1671
  textarea.className = "uidex-form-textarea";
@@ -1517,6 +1673,22 @@ var Modal = class _Modal {
1517
1673
  textarea.rows = 4;
1518
1674
  descGroup.appendChild(textarea);
1519
1675
  form.appendChild(descGroup);
1676
+ let emailInput;
1677
+ let nameInput;
1678
+ if (this.options.ingest && !this.options.ingest.reporter) {
1679
+ const reporterGroup = this.createFormGroup("Reporter");
1680
+ nameInput = document.createElement("input");
1681
+ nameInput.type = "text";
1682
+ nameInput.className = "uidex-form-input";
1683
+ nameInput.placeholder = "Name (optional)";
1684
+ reporterGroup.appendChild(nameInput);
1685
+ emailInput = document.createElement("input");
1686
+ emailInput.type = "email";
1687
+ emailInput.className = "uidex-form-input";
1688
+ emailInput.placeholder = "Email (optional)";
1689
+ reporterGroup.appendChild(emailInput);
1690
+ form.appendChild(reporterGroup);
1691
+ }
1520
1692
  const screenshotGroup = document.createElement("div");
1521
1693
  screenshotGroup.className = "uidex-form-group";
1522
1694
  const screenshotLabel = document.createElement("label");
@@ -1552,9 +1724,14 @@ var Modal = class _Modal {
1552
1724
  }
1553
1725
  const env = this.collectEnv();
1554
1726
  const page = this.data.pages.find((p) => p.componentIds.includes(id));
1727
+ const { ingest } = this.options;
1728
+ const reporterEmail = ingest?.reporter?.email || emailInput?.value.trim() || void 0;
1729
+ const reporterName = ingest?.reporter?.name || nameInput?.value.trim() || void 0;
1730
+ const titleValue = titleInput.value.trim();
1555
1731
  const report = {
1556
1732
  type: typeSelect.select.value,
1557
1733
  severity: severitySelect.select.value,
1734
+ ...titleValue ? { title: titleValue } : {},
1558
1735
  description: textarea.value.trim(),
1559
1736
  componentId: id,
1560
1737
  element: element ? this.describeElement(element) : null,
@@ -1563,17 +1740,66 @@ var Modal = class _Modal {
1563
1740
  path: window.location.pathname,
1564
1741
  route: page?.dir ?? null,
1565
1742
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1743
+ pageTitle: document.title,
1744
+ locale: navigator.language,
1745
+ sessionId: this.options.sessionId ?? "",
1566
1746
  viewport: env.viewport,
1567
1747
  screen: env.screen,
1568
1748
  userAgent: env.userAgent,
1569
- screenshot
1749
+ screenshot,
1750
+ ...reporterEmail ? { reporterEmail } : {},
1751
+ ...reporterName ? { reporterName } : {},
1752
+ ...ingest?.environment ? { environment: ingest.environment } : {},
1753
+ ...ingest?.appVersion ? { appVersion: ingest.appVersion } : {},
1754
+ ...ingest?.metadata ? { metadata: ingest.metadata } : {}
1570
1755
  };
1571
- console.log("[uidex] Feedback submitted:", report);
1572
- submitBtn.textContent = "Submitted!";
1573
- setTimeout(() => {
1574
- submitBtn.textContent = "Submit";
1575
- submitBtn.disabled = false;
1576
- }, 1500);
1756
+ const consoleLogs = this.options.getConsoleLogs?.();
1757
+ if (consoleLogs && consoleLogs.length > 0) {
1758
+ report.consoleLogs = consoleLogs;
1759
+ }
1760
+ const networkErrors = this.options.getNetworkErrors?.();
1761
+ if (networkErrors && networkErrors.length > 0) {
1762
+ report.networkErrors = networkErrors;
1763
+ }
1764
+ const showSuccess = (autoClose) => {
1765
+ submitBtn.textContent = "Submitted!";
1766
+ if (autoClose) {
1767
+ setTimeout(() => this.hide(), 1500);
1768
+ } else {
1769
+ setTimeout(() => {
1770
+ submitBtn.textContent = "Submit";
1771
+ submitBtn.disabled = false;
1772
+ }, 1500);
1773
+ }
1774
+ };
1775
+ if (ingest) {
1776
+ submitBtn.textContent = "Submitting\u2026";
1777
+ try {
1778
+ const serverResult = await submitFeedback(
1779
+ ingest.endpoint,
1780
+ ingest.apiKey,
1781
+ report
1782
+ );
1783
+ this.options.onSubmit?.(report, {
1784
+ ok: true,
1785
+ id: serverResult.id,
1786
+ sequenceNumber: serverResult.sequenceNumber
1787
+ });
1788
+ showSuccess(true);
1789
+ } catch (err) {
1790
+ const errorMessage = err instanceof Error ? err.message : "Unknown error";
1791
+ console.warn("[uidex] Feedback submission failed:", errorMessage);
1792
+ this.options.onSubmit?.(report, { ok: false, error: errorMessage });
1793
+ submitBtn.textContent = "Failed \u2014 retry?";
1794
+ submitBtn.disabled = false;
1795
+ }
1796
+ } else {
1797
+ if (!this.options.onSubmit) {
1798
+ console.log("[uidex] Feedback submitted:", report);
1799
+ }
1800
+ this.options.onSubmit?.(report, { ok: true, id: "", sequenceNumber: 0 });
1801
+ showSuccess(false);
1802
+ }
1577
1803
  });
1578
1804
  form.appendChild(submitBtn);
1579
1805
  this.mainContent.appendChild(form);
@@ -2693,49 +2919,56 @@ body.uidex-inspecting * {
2693
2919
  color: var(--uidex-color-text-muted);
2694
2920
  }
2695
2921
 
2696
- .uidex-form-select {
2697
- appearance: none;
2698
- padding: 6px 10px;
2922
+ .uidex-form-select,
2923
+ .uidex-form-input,
2924
+ .uidex-form-textarea {
2699
2925
  border: 1px solid var(--uidex-color-border);
2700
2926
  border-radius: 0;
2701
2927
  background: transparent;
2702
2928
  color: var(--uidex-color-text);
2703
2929
  font-size: var(--uidex-font-size-sm);
2704
2930
  font-family: var(--uidex-font-mono);
2705
- cursor: pointer;
2706
2931
  outline: none;
2932
+ }
2933
+
2934
+ .uidex-form-select:focus,
2935
+ .uidex-form-input:focus,
2936
+ .uidex-form-textarea:focus {
2937
+ border-color: rgba(255, 255, 255, 0.3);
2938
+ }
2939
+
2940
+ .uidex-form-input::placeholder,
2941
+ .uidex-form-textarea::placeholder {
2942
+ color: var(--uidex-color-text-muted);
2943
+ }
2944
+
2945
+ .uidex-form-select {
2946
+ appearance: none;
2947
+ padding: 6px 10px;
2948
+ cursor: pointer;
2707
2949
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23ababab' d='M3 5l3 3 3-3'/%3E%3C/svg%3E");
2708
2950
  background-repeat: no-repeat;
2709
2951
  background-position: right 8px center;
2710
2952
  padding-right: 26px;
2711
2953
  }
2712
2954
 
2713
- .uidex-form-select:focus {
2714
- border-color: rgba(255, 255, 255, 0.3);
2955
+ .uidex-form-input {
2956
+ padding: 6px 10px;
2957
+ width: 100%;
2958
+ box-sizing: border-box;
2959
+ }
2960
+
2961
+ .uidex-form-input + .uidex-form-input {
2962
+ margin-top: 6px;
2715
2963
  }
2716
2964
 
2717
2965
  .uidex-form-textarea {
2718
2966
  padding: 8px 10px;
2719
- border: 1px solid var(--uidex-color-border);
2720
- border-radius: 0;
2721
- background: transparent;
2722
- color: var(--uidex-color-text);
2723
- font-size: var(--uidex-font-size-sm);
2724
- font-family: var(--uidex-font-mono);
2725
2967
  line-height: 1.5;
2726
2968
  resize: vertical;
2727
- outline: none;
2728
2969
  min-height: 80px;
2729
2970
  }
2730
2971
 
2731
- .uidex-form-textarea::placeholder {
2732
- color: var(--uidex-color-text-muted);
2733
- }
2734
-
2735
- .uidex-form-textarea:focus {
2736
- border-color: rgba(255, 255, 255, 0.3);
2737
- }
2738
-
2739
2972
  .uidex-form-submit {
2740
2973
  padding: 6px 16px;
2741
2974
  border: 1px solid var(--uidex-color-border);
@@ -2754,7 +2987,7 @@ body.uidex-inspecting * {
2754
2987
  background: var(--uidex-color-primary-hover);
2755
2988
  }
2756
2989
 
2757
- .uidex-form-submit:active {
2990
+ .uidex-form-submit:not(:disabled):active {
2758
2991
  transform: translateY(1px);
2759
2992
  }
2760
2993
 
@@ -2898,8 +3131,17 @@ var UidexUI = class {
2898
3131
  copyTimer = null;
2899
3132
  currentPresentIds = [];
2900
3133
  activeMode = null;
3134
+ sessionId;
3135
+ capture = null;
2901
3136
  constructor(options = {}) {
2902
3137
  this.options = options;
3138
+ this.sessionId = generateSessionId();
3139
+ if (options.ingest?.captureConsole || options.ingest?.captureNetwork) {
3140
+ this.capture = new IngestCapture(
3141
+ options.ingest.captureConsole ?? false,
3142
+ options.ingest.captureNetwork ?? false
3143
+ );
3144
+ }
2903
3145
  this.overlay = new Overlay({
2904
3146
  color: options.config?.defaults?.color,
2905
3147
  borderStyle: options.config?.defaults?.borderStyle,
@@ -2916,7 +3158,12 @@ var UidexUI = class {
2916
3158
  }
2917
3159
  this.options.onSelect?.(id);
2918
3160
  },
2919
- elementGetter: (id) => this.findElement(id)
3161
+ elementGetter: (id) => this.findElement(id),
3162
+ ingest: options.ingest,
3163
+ onSubmit: options.onSubmit,
3164
+ sessionId: this.sessionId,
3165
+ getConsoleLogs: () => this.capture?.getConsoleLogs() ?? [],
3166
+ getNetworkErrors: () => this.capture?.getNetworkErrors() ?? []
2920
3167
  });
2921
3168
  this.menu = new Menu({
2922
3169
  onInspectToggle: () => this.toggleMode("inspect"),
@@ -2967,12 +3214,14 @@ var UidexUI = class {
2967
3214
  this.modal.setShadowRoot(this.shadowRoot);
2968
3215
  this.updateModalData(presentIds);
2969
3216
  this.inspector?.mount();
3217
+ this.capture?.start();
2970
3218
  this.startObserving();
2971
3219
  this.mounted = true;
2972
3220
  }
2973
3221
  destroy() {
2974
3222
  if (!this.mounted) return;
2975
3223
  this.stopObserving();
3224
+ this.capture?.stop();
2976
3225
  if (this.copyTimer !== null) {
2977
3226
  clearTimeout(this.copyTimer);
2978
3227
  this.copyTimer = null;
@@ -3162,7 +3411,9 @@ function UidexDevtools({
3162
3411
  buttonPosition = "bottom-right",
3163
3412
  disabled = false,
3164
3413
  onSelect,
3165
- inspectShortcut
3414
+ inspectShortcut,
3415
+ ingest,
3416
+ onSubmit
3166
3417
  }) {
3167
3418
  const uiRef = useRef(null);
3168
3419
  const stableShortcut = useMemo(
@@ -3172,6 +3423,15 @@ function UidexDevtools({
3172
3423
  inspectShortcut === false ? false : `${inspectShortcut?.key}:${inspectShortcut?.ctrlKey}:${inspectShortcut?.shiftKey}:${inspectShortcut?.altKey}:${inspectShortcut?.metaKey}`
3173
3424
  ]
3174
3425
  );
3426
+ const stableIngest = useMemo(
3427
+ () => ingest,
3428
+ // eslint-disable-next-line react-hooks/exhaustive-deps
3429
+ [
3430
+ ingest ? `${ingest.endpoint}:${ingest.apiKey}:${ingest.environment}:${ingest.appVersion}:${ingest.captureConsole}:${ingest.captureNetwork}:${ingest.reporter?.email}:${ingest.reporter?.name}` : void 0
3431
+ ]
3432
+ );
3433
+ const onSubmitRef = useRef(onSubmit);
3434
+ onSubmitRef.current = onSubmit;
3175
3435
  useEffect(() => {
3176
3436
  if (disabled) {
3177
3437
  return;
@@ -3181,7 +3441,9 @@ function UidexDevtools({
3181
3441
  config,
3182
3442
  buttonPosition,
3183
3443
  onSelect,
3184
- inspectShortcut: stableShortcut
3444
+ inspectShortcut: stableShortcut,
3445
+ ingest: stableIngest,
3446
+ onSubmit: (...args) => onSubmitRef.current?.(...args)
3185
3447
  });
3186
3448
  ui.mount();
3187
3449
  uiRef.current = ui;
@@ -3189,7 +3451,7 @@ function UidexDevtools({
3189
3451
  ui.destroy();
3190
3452
  uiRef.current = null;
3191
3453
  };
3192
- }, [components, config, buttonPosition, disabled, onSelect, stableShortcut]);
3454
+ }, [components, config, buttonPosition, disabled, onSelect, stableShortcut, stableIngest]);
3193
3455
  return null;
3194
3456
  }
3195
3457