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.
- package/dist/core/index.cjs +306 -33
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +111 -1
- package/dist/core/index.d.ts +111 -1
- package/dist/core/index.global.js +302 -32
- package/dist/core/index.global.js.map +1 -1
- package/dist/core/index.js +302 -32
- package/dist/core/index.js.map +1 -1
- package/dist/core/style.css +29 -22
- package/dist/index.cjs +314 -34
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +77 -2
- package/dist/index.d.ts +77 -2
- package/dist/index.js +314 -34
- package/dist/index.js.map +1 -1
- package/dist/react/index.cjs +314 -34
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +77 -2
- package/dist/react/index.d.ts +77 -2
- package/dist/react/index.js +314 -34
- package/dist/react/index.js.map +1 -1
- package/dist/scripts/cli.cjs +2 -1
- package/package.json +1 -1
package/dist/react/index.d.cts
CHANGED
|
@@ -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 };
|
package/dist/react/index.d.ts
CHANGED
|
@@ -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 };
|
package/dist/react/index.js
CHANGED
|
@@ -237,7 +237,7 @@ function resolveColor(color, colorMap) {
|
|
|
237
237
|
}
|
|
238
238
|
|
|
239
239
|
// src/core/overlay.ts
|
|
240
|
-
var DEFAULT_COLOR = "#
|
|
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
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
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
|
-
|
|
2698
|
-
|
|
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-
|
|
2714
|
-
|
|
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
|
|