uidex 0.1.1 → 0.2.1

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 };
@@ -237,7 +237,7 @@ function resolveColor(color, colorMap) {
237
237
  }
238
238
 
239
239
  // src/core/overlay.ts
240
- var DEFAULT_COLOR = "#e5e5e5";
240
+ var DEFAULT_COLOR = "#3b82f6";
241
241
  var DEFAULT_BORDER_STYLE = "solid";
242
242
  var DEFAULT_BORDER_WIDTH = 2;
243
243
  var DEFAULT_LABEL_POSITION = "top-left";
@@ -388,6 +388,23 @@ var Overlay = class {
388
388
  this.element.style.left = `${rect.left}px`;
389
389
  this.element.style.width = `${rect.width}px`;
390
390
  this.element.style.height = `${rect.height}px`;
391
+ this.clampLabel(rect);
392
+ }
393
+ /** Move the label inside the overlay when there is no room outside. */
394
+ clampLabel(rect) {
395
+ if (!this.labelElement || this.labelElement.style.display === "none") return;
396
+ const {
397
+ labelPosition = DEFAULT_LABEL_POSITION
398
+ } = this.options;
399
+ const isTop = labelPosition === "top-left" || labelPosition === "top-right";
400
+ const labelHeight = 20;
401
+ if (isTop && rect.top < labelHeight) {
402
+ this.labelElement.style.top = "4px";
403
+ this.labelElement.style.transform = "";
404
+ } else if (!isTop && window.innerHeight - rect.bottom < labelHeight) {
405
+ this.labelElement.style.bottom = "4px";
406
+ this.labelElement.style.transform = "";
407
+ }
391
408
  }
392
409
  addListeners() {
393
410
  window.addEventListener("resize", this.boundUpdatePosition);
@@ -528,6 +545,154 @@ var Inspector = class {
528
545
  }
529
546
  };
530
547
 
548
+ // src/core/ingest.ts
549
+ var MAX_CONSOLE_LOGS = 50;
550
+ var MAX_NETWORK_ERRORS = 20;
551
+ var nativeFetch = null;
552
+ function safeStringify(value) {
553
+ if (typeof value === "string") return value;
554
+ try {
555
+ return JSON.stringify(value);
556
+ } catch {
557
+ return String(value);
558
+ }
559
+ }
560
+ var IngestCapture = class {
561
+ constructor(captureConsole, captureNetwork) {
562
+ this.captureConsole = captureConsole;
563
+ this.captureNetwork = captureNetwork;
564
+ }
565
+ consoleLogs = [];
566
+ networkErrors = [];
567
+ originalConsoleWarn = null;
568
+ originalConsoleError = null;
569
+ originalFetch = null;
570
+ start() {
571
+ this.consoleLogs = [];
572
+ this.networkErrors = [];
573
+ if (this.captureConsole) this.interceptConsole();
574
+ if (this.captureNetwork) this.interceptNetwork();
575
+ }
576
+ stop() {
577
+ this.restoreConsole();
578
+ this.restoreNetwork();
579
+ }
580
+ getConsoleLogs() {
581
+ return [...this.consoleLogs];
582
+ }
583
+ getNetworkErrors() {
584
+ return [...this.networkErrors];
585
+ }
586
+ interceptConsole() {
587
+ if (this.originalConsoleWarn) return;
588
+ this.originalConsoleWarn = console.warn;
589
+ this.originalConsoleError = console.error;
590
+ console.warn = (...args) => {
591
+ this.addConsoleLog("warn", args);
592
+ this.originalConsoleWarn.apply(console, args);
593
+ };
594
+ console.error = (...args) => {
595
+ this.addConsoleLog("error", args);
596
+ this.originalConsoleError.apply(console, args);
597
+ };
598
+ }
599
+ addConsoleLog(level, args) {
600
+ this.consoleLogs.push({
601
+ level,
602
+ message: args.map(safeStringify).join(" "),
603
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
604
+ });
605
+ if (this.consoleLogs.length > MAX_CONSOLE_LOGS) {
606
+ this.consoleLogs.shift();
607
+ }
608
+ }
609
+ restoreConsole() {
610
+ if (this.originalConsoleWarn) {
611
+ console.warn = this.originalConsoleWarn;
612
+ this.originalConsoleWarn = null;
613
+ }
614
+ if (this.originalConsoleError) {
615
+ console.error = this.originalConsoleError;
616
+ this.originalConsoleError = null;
617
+ }
618
+ }
619
+ interceptNetwork() {
620
+ if (this.originalFetch) return;
621
+ this.originalFetch = window.fetch;
622
+ if (!nativeFetch) nativeFetch = this.originalFetch;
623
+ window.fetch = async (...args) => {
624
+ try {
625
+ const response = await this.originalFetch.apply(window, args);
626
+ if (!response.ok) {
627
+ this.addNetworkError(args[0], args[1]?.method, response.status, response.statusText);
628
+ }
629
+ return response;
630
+ } catch (error) {
631
+ this.addNetworkError(
632
+ args[0],
633
+ args[1]?.method,
634
+ null,
635
+ error instanceof Error ? error.message : "Network error"
636
+ );
637
+ throw error;
638
+ }
639
+ };
640
+ }
641
+ addNetworkError(input, method, status, statusText) {
642
+ let url;
643
+ if (typeof input === "string") {
644
+ url = input;
645
+ } else if (input instanceof URL) {
646
+ url = input.href;
647
+ } else {
648
+ url = input.url;
649
+ method ??= input.method;
650
+ }
651
+ this.networkErrors.push({
652
+ url,
653
+ method: method ?? "GET",
654
+ status,
655
+ statusText,
656
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
657
+ });
658
+ if (this.networkErrors.length > MAX_NETWORK_ERRORS) {
659
+ this.networkErrors.shift();
660
+ }
661
+ }
662
+ restoreNetwork() {
663
+ if (this.originalFetch) {
664
+ window.fetch = this.originalFetch;
665
+ this.originalFetch = null;
666
+ }
667
+ }
668
+ };
669
+ function generateSessionId() {
670
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
671
+ return crypto.randomUUID();
672
+ }
673
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
674
+ const r = Math.random() * 16 | 0;
675
+ const v = c === "x" ? r : r & 3 | 8;
676
+ return v.toString(16);
677
+ });
678
+ }
679
+ async function submitFeedback(endpoint, apiKey, report) {
680
+ const fetchFn = nativeFetch ?? fetch;
681
+ const response = await fetchFn(endpoint, {
682
+ method: "POST",
683
+ headers: {
684
+ "Content-Type": "application/json",
685
+ Authorization: `Bearer ${apiKey}`
686
+ },
687
+ body: JSON.stringify(report)
688
+ });
689
+ if (!response.ok) {
690
+ const text = await response.text().catch(() => "");
691
+ throw new Error(`Ingest failed (${response.status}): ${text}`);
692
+ }
693
+ return response.json();
694
+ }
695
+
531
696
  // src/core/modal.ts
532
697
  var Modal = class _Modal {
533
698
  backdrop = null;
@@ -1510,6 +1675,14 @@ var Modal = class _Modal {
1510
1675
  "medium"
1511
1676
  );
1512
1677
  form.appendChild(severitySelect.group);
1678
+ const titleGroup = this.createFormGroup("Title");
1679
+ const titleInput = document.createElement("input");
1680
+ titleInput.type = "text";
1681
+ titleInput.className = "uidex-form-input";
1682
+ titleInput.placeholder = "Brief summary (optional)";
1683
+ titleInput.maxLength = 200;
1684
+ titleGroup.appendChild(titleInput);
1685
+ form.appendChild(titleGroup);
1513
1686
  const descGroup = this.createFormGroup("Description");
1514
1687
  const textarea = document.createElement("textarea");
1515
1688
  textarea.className = "uidex-form-textarea";
@@ -1517,6 +1690,22 @@ var Modal = class _Modal {
1517
1690
  textarea.rows = 4;
1518
1691
  descGroup.appendChild(textarea);
1519
1692
  form.appendChild(descGroup);
1693
+ let emailInput;
1694
+ let nameInput;
1695
+ if (this.options.ingest && !this.options.ingest.reporter) {
1696
+ const reporterGroup = this.createFormGroup("Reporter");
1697
+ nameInput = document.createElement("input");
1698
+ nameInput.type = "text";
1699
+ nameInput.className = "uidex-form-input";
1700
+ nameInput.placeholder = "Name (optional)";
1701
+ reporterGroup.appendChild(nameInput);
1702
+ emailInput = document.createElement("input");
1703
+ emailInput.type = "email";
1704
+ emailInput.className = "uidex-form-input";
1705
+ emailInput.placeholder = "Email (optional)";
1706
+ reporterGroup.appendChild(emailInput);
1707
+ form.appendChild(reporterGroup);
1708
+ }
1520
1709
  const screenshotGroup = document.createElement("div");
1521
1710
  screenshotGroup.className = "uidex-form-group";
1522
1711
  const screenshotLabel = document.createElement("label");
@@ -1552,9 +1741,14 @@ var Modal = class _Modal {
1552
1741
  }
1553
1742
  const env = this.collectEnv();
1554
1743
  const page = this.data.pages.find((p) => p.componentIds.includes(id));
1744
+ const { ingest } = this.options;
1745
+ const reporterEmail = ingest?.reporter?.email || emailInput?.value.trim() || void 0;
1746
+ const reporterName = ingest?.reporter?.name || nameInput?.value.trim() || void 0;
1747
+ const titleValue = titleInput.value.trim();
1555
1748
  const report = {
1556
1749
  type: typeSelect.select.value,
1557
1750
  severity: severitySelect.select.value,
1751
+ ...titleValue ? { title: titleValue } : {},
1558
1752
  description: textarea.value.trim(),
1559
1753
  componentId: id,
1560
1754
  element: element ? this.describeElement(element) : null,
@@ -1563,17 +1757,66 @@ var Modal = class _Modal {
1563
1757
  path: window.location.pathname,
1564
1758
  route: page?.dir ?? null,
1565
1759
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1760
+ pageTitle: document.title,
1761
+ locale: navigator.language,
1762
+ sessionId: this.options.sessionId ?? "",
1566
1763
  viewport: env.viewport,
1567
1764
  screen: env.screen,
1568
1765
  userAgent: env.userAgent,
1569
- screenshot
1766
+ screenshot,
1767
+ ...reporterEmail ? { reporterEmail } : {},
1768
+ ...reporterName ? { reporterName } : {},
1769
+ ...ingest?.environment ? { environment: ingest.environment } : {},
1770
+ ...ingest?.appVersion ? { appVersion: ingest.appVersion } : {},
1771
+ ...ingest?.metadata ? { metadata: ingest.metadata } : {}
1772
+ };
1773
+ const consoleLogs = this.options.getConsoleLogs?.();
1774
+ if (consoleLogs && consoleLogs.length > 0) {
1775
+ report.consoleLogs = consoleLogs;
1776
+ }
1777
+ const networkErrors = this.options.getNetworkErrors?.();
1778
+ if (networkErrors && networkErrors.length > 0) {
1779
+ report.networkErrors = networkErrors;
1780
+ }
1781
+ const showSuccess = (autoClose) => {
1782
+ submitBtn.textContent = "Submitted!";
1783
+ if (autoClose) {
1784
+ setTimeout(() => this.hide(), 1500);
1785
+ } else {
1786
+ setTimeout(() => {
1787
+ submitBtn.textContent = "Submit";
1788
+ submitBtn.disabled = false;
1789
+ }, 1500);
1790
+ }
1570
1791
  };
1571
- console.log("[uidex] Feedback submitted:", report);
1572
- submitBtn.textContent = "Submitted!";
1573
- setTimeout(() => {
1574
- submitBtn.textContent = "Submit";
1575
- submitBtn.disabled = false;
1576
- }, 1500);
1792
+ if (ingest) {
1793
+ submitBtn.textContent = "Submitting\u2026";
1794
+ try {
1795
+ const serverResult = await submitFeedback(
1796
+ ingest.endpoint,
1797
+ ingest.apiKey,
1798
+ report
1799
+ );
1800
+ this.options.onSubmit?.(report, {
1801
+ ok: true,
1802
+ id: serverResult.id,
1803
+ sequenceNumber: serverResult.sequenceNumber
1804
+ });
1805
+ showSuccess(true);
1806
+ } catch (err) {
1807
+ const errorMessage = err instanceof Error ? err.message : "Unknown error";
1808
+ console.warn("[uidex] Feedback submission failed:", errorMessage);
1809
+ this.options.onSubmit?.(report, { ok: false, error: errorMessage });
1810
+ submitBtn.textContent = "Failed \u2014 retry?";
1811
+ submitBtn.disabled = false;
1812
+ }
1813
+ } else {
1814
+ if (!this.options.onSubmit) {
1815
+ console.log("[uidex] Feedback submitted:", report);
1816
+ }
1817
+ this.options.onSubmit?.(report, { ok: true, id: "", sequenceNumber: 0 });
1818
+ showSuccess(false);
1819
+ }
1577
1820
  });
1578
1821
  form.appendChild(submitBtn);
1579
1822
  this.mainContent.appendChild(form);
@@ -2693,49 +2936,56 @@ body.uidex-inspecting * {
2693
2936
  color: var(--uidex-color-text-muted);
2694
2937
  }
2695
2938
 
2696
- .uidex-form-select {
2697
- appearance: none;
2698
- padding: 6px 10px;
2939
+ .uidex-form-select,
2940
+ .uidex-form-input,
2941
+ .uidex-form-textarea {
2699
2942
  border: 1px solid var(--uidex-color-border);
2700
2943
  border-radius: 0;
2701
2944
  background: transparent;
2702
2945
  color: var(--uidex-color-text);
2703
2946
  font-size: var(--uidex-font-size-sm);
2704
2947
  font-family: var(--uidex-font-mono);
2705
- cursor: pointer;
2706
2948
  outline: none;
2949
+ }
2950
+
2951
+ .uidex-form-select:focus,
2952
+ .uidex-form-input:focus,
2953
+ .uidex-form-textarea:focus {
2954
+ border-color: rgba(255, 255, 255, 0.3);
2955
+ }
2956
+
2957
+ .uidex-form-input::placeholder,
2958
+ .uidex-form-textarea::placeholder {
2959
+ color: var(--uidex-color-text-muted);
2960
+ }
2961
+
2962
+ .uidex-form-select {
2963
+ appearance: none;
2964
+ padding: 6px 10px;
2965
+ cursor: pointer;
2707
2966
  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
2967
  background-repeat: no-repeat;
2709
2968
  background-position: right 8px center;
2710
2969
  padding-right: 26px;
2711
2970
  }
2712
2971
 
2713
- .uidex-form-select:focus {
2714
- border-color: rgba(255, 255, 255, 0.3);
2972
+ .uidex-form-input {
2973
+ padding: 6px 10px;
2974
+ width: 100%;
2975
+ box-sizing: border-box;
2976
+ }
2977
+
2978
+ .uidex-form-input + .uidex-form-input {
2979
+ margin-top: 6px;
2715
2980
  }
2716
2981
 
2717
2982
  .uidex-form-textarea {
2718
2983
  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
2984
  line-height: 1.5;
2726
2985
  resize: vertical;
2727
- outline: none;
2728
2986
  min-height: 80px;
2729
2987
  }
2730
2988
 
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
2989
  .uidex-form-submit {
2740
2990
  padding: 6px 16px;
2741
2991
  border: 1px solid var(--uidex-color-border);
@@ -2754,7 +3004,7 @@ body.uidex-inspecting * {
2754
3004
  background: var(--uidex-color-primary-hover);
2755
3005
  }
2756
3006
 
2757
- .uidex-form-submit:active {
3007
+ .uidex-form-submit:not(:disabled):active {
2758
3008
  transform: translateY(1px);
2759
3009
  }
2760
3010
 
@@ -2898,8 +3148,17 @@ var UidexUI = class {
2898
3148
  copyTimer = null;
2899
3149
  currentPresentIds = [];
2900
3150
  activeMode = null;
3151
+ sessionId;
3152
+ capture = null;
2901
3153
  constructor(options = {}) {
2902
3154
  this.options = options;
3155
+ this.sessionId = generateSessionId();
3156
+ if (options.ingest?.captureConsole || options.ingest?.captureNetwork) {
3157
+ this.capture = new IngestCapture(
3158
+ options.ingest.captureConsole ?? false,
3159
+ options.ingest.captureNetwork ?? false
3160
+ );
3161
+ }
2903
3162
  this.overlay = new Overlay({
2904
3163
  color: options.config?.defaults?.color,
2905
3164
  borderStyle: options.config?.defaults?.borderStyle,
@@ -2916,7 +3175,12 @@ var UidexUI = class {
2916
3175
  }
2917
3176
  this.options.onSelect?.(id);
2918
3177
  },
2919
- elementGetter: (id) => this.findElement(id)
3178
+ elementGetter: (id) => this.findElement(id),
3179
+ ingest: options.ingest,
3180
+ onSubmit: options.onSubmit,
3181
+ sessionId: this.sessionId,
3182
+ getConsoleLogs: () => this.capture?.getConsoleLogs() ?? [],
3183
+ getNetworkErrors: () => this.capture?.getNetworkErrors() ?? []
2920
3184
  });
2921
3185
  this.menu = new Menu({
2922
3186
  onInspectToggle: () => this.toggleMode("inspect"),
@@ -2955,6 +3219,7 @@ var UidexUI = class {
2955
3219
  this.shadowHost.style.width = "0";
2956
3220
  this.shadowHost.style.height = "0";
2957
3221
  this.shadowHost.style.pointerEvents = "none";
3222
+ this.shadowHost.style.zIndex = "2147483646";
2958
3223
  this.shadowRoot = this.shadowHost.attachShadow({ mode: "open" });
2959
3224
  injectStyles(this.shadowRoot);
2960
3225
  const presentIds = this.scanPresentIds(componentIds);
@@ -2967,12 +3232,14 @@ var UidexUI = class {
2967
3232
  this.modal.setShadowRoot(this.shadowRoot);
2968
3233
  this.updateModalData(presentIds);
2969
3234
  this.inspector?.mount();
3235
+ this.capture?.start();
2970
3236
  this.startObserving();
2971
3237
  this.mounted = true;
2972
3238
  }
2973
3239
  destroy() {
2974
3240
  if (!this.mounted) return;
2975
3241
  this.stopObserving();
3242
+ this.capture?.stop();
2976
3243
  if (this.copyTimer !== null) {
2977
3244
  clearTimeout(this.copyTimer);
2978
3245
  this.copyTimer = null;
@@ -3162,7 +3429,9 @@ function UidexDevtools({
3162
3429
  buttonPosition = "bottom-right",
3163
3430
  disabled = false,
3164
3431
  onSelect,
3165
- inspectShortcut
3432
+ inspectShortcut,
3433
+ ingest,
3434
+ onSubmit
3166
3435
  }) {
3167
3436
  const uiRef = useRef(null);
3168
3437
  const stableShortcut = useMemo(
@@ -3172,6 +3441,15 @@ function UidexDevtools({
3172
3441
  inspectShortcut === false ? false : `${inspectShortcut?.key}:${inspectShortcut?.ctrlKey}:${inspectShortcut?.shiftKey}:${inspectShortcut?.altKey}:${inspectShortcut?.metaKey}`
3173
3442
  ]
3174
3443
  );
3444
+ const stableIngest = useMemo(
3445
+ () => ingest,
3446
+ // eslint-disable-next-line react-hooks/exhaustive-deps
3447
+ [
3448
+ ingest ? `${ingest.endpoint}:${ingest.apiKey}:${ingest.environment}:${ingest.appVersion}:${ingest.captureConsole}:${ingest.captureNetwork}:${ingest.reporter?.email}:${ingest.reporter?.name}` : void 0
3449
+ ]
3450
+ );
3451
+ const onSubmitRef = useRef(onSubmit);
3452
+ onSubmitRef.current = onSubmit;
3175
3453
  useEffect(() => {
3176
3454
  if (disabled) {
3177
3455
  return;
@@ -3181,7 +3459,9 @@ function UidexDevtools({
3181
3459
  config,
3182
3460
  buttonPosition,
3183
3461
  onSelect,
3184
- inspectShortcut: stableShortcut
3462
+ inspectShortcut: stableShortcut,
3463
+ ingest: stableIngest,
3464
+ onSubmit: (...args) => onSubmitRef.current?.(...args)
3185
3465
  });
3186
3466
  ui.mount();
3187
3467
  uiRef.current = ui;
@@ -3189,7 +3469,7 @@ function UidexDevtools({
3189
3469
  ui.destroy();
3190
3470
  uiRef.current = null;
3191
3471
  };
3192
- }, [components, config, buttonPosition, disabled, onSelect, stableShortcut]);
3472
+ }, [components, config, buttonPosition, disabled, onSelect, stableShortcut, stableIngest]);
3193
3473
  return null;
3194
3474
  }
3195
3475