hocmai-feedback-widget 0.2.0 → 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/index.d.ts +2 -0
- package/dist/support-feedback-lib.es.js +1474 -1395
- package/dist/support-feedback-lib.umd.js +10 -10
- package/dist/test-mobile-screenshot.html +211 -0
- package/dist/test-simple-capture.html +107 -0
- package/dist/utils/screenshot/device-detection.d.ts +14 -0
- package/dist/utils/screenshot/element-collector.d.ts +1 -1
- package/package.json +2 -2
- package/src/components/FeedbackWidget.tsx +1 -1
- package/src/index.ts +4 -0
- package/src/utils/screenshot/canvas-renderer.ts +143 -17
- package/src/utils/screenshot/device-detection.ts +52 -0
- package/src/utils/screenshot/element-collector.ts +60 -9
- package/src/utils/screenshot/svg-renderer.ts +23 -0
- package/src/utils/screenshot.ts +17 -2
|
@@ -2,19 +2,45 @@ import type { Rect, CaptureBounds } from './types';
|
|
|
2
2
|
|
|
3
3
|
export function calculateCaptureBounds(
|
|
4
4
|
cropRect: Rect | undefined,
|
|
5
|
-
buffer: number
|
|
5
|
+
buffer: number,
|
|
6
|
+
viewportWidth?: number,
|
|
7
|
+
viewportHeight?: number
|
|
6
8
|
): CaptureBounds {
|
|
9
|
+
// Use provided viewport dimensions or calculate them
|
|
10
|
+
const getViewportWidth = () => {
|
|
11
|
+
if (viewportWidth !== undefined) return viewportWidth;
|
|
12
|
+
if (window.visualViewport) return window.visualViewport.width;
|
|
13
|
+
return document.documentElement.clientWidth || window.innerWidth;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const getViewportHeight = () => {
|
|
17
|
+
if (viewportHeight !== undefined) return viewportHeight;
|
|
18
|
+
if (window.visualViewport) return window.visualViewport.height;
|
|
19
|
+
return document.documentElement.clientHeight || window.innerHeight;
|
|
20
|
+
};
|
|
21
|
+
|
|
7
22
|
const targetX = cropRect ? cropRect.x : 0;
|
|
8
23
|
const targetY = cropRect ? cropRect.y : 0;
|
|
9
|
-
const targetWidth = cropRect ? cropRect.width :
|
|
10
|
-
const targetHeight = cropRect ? cropRect.height :
|
|
24
|
+
const targetWidth = cropRect ? cropRect.width : getViewportWidth();
|
|
25
|
+
const targetHeight = cropRect ? cropRect.height : getViewportHeight();
|
|
11
26
|
|
|
12
|
-
|
|
27
|
+
const bounds = {
|
|
13
28
|
left: targetX - buffer,
|
|
14
29
|
top: targetY - buffer,
|
|
15
30
|
right: targetX + targetWidth + buffer,
|
|
16
31
|
bottom: targetY + targetHeight + buffer
|
|
17
32
|
};
|
|
33
|
+
|
|
34
|
+
console.log('[ElementCollector] calculateCaptureBounds:', {
|
|
35
|
+
viewportWidth: viewportWidth || 'auto',
|
|
36
|
+
viewportHeight: viewportHeight || 'auto',
|
|
37
|
+
targetWidth,
|
|
38
|
+
targetHeight,
|
|
39
|
+
bounds,
|
|
40
|
+
scroll: { x: window.scrollX, y: window.scrollY }
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
return bounds;
|
|
18
44
|
}
|
|
19
45
|
|
|
20
46
|
export function isElementInViewport(
|
|
@@ -23,12 +49,25 @@ export function isElementInViewport(
|
|
|
23
49
|
): boolean {
|
|
24
50
|
const rect = element.getBoundingClientRect();
|
|
25
51
|
|
|
52
|
+
// Convert bounds from document coordinates to viewport coordinates
|
|
53
|
+
// getBoundingClientRect() returns viewport-relative coordinates
|
|
54
|
+
// bounds are in document coordinates, so subtract scroll offset
|
|
55
|
+
const scrollX = window.scrollX || window.pageXOffset;
|
|
56
|
+
const scrollY = window.scrollY || window.pageYOffset;
|
|
57
|
+
|
|
58
|
+
const viewportBounds = {
|
|
59
|
+
left: bounds.left - scrollX,
|
|
60
|
+
top: bounds.top - scrollY,
|
|
61
|
+
right: bounds.right - scrollX,
|
|
62
|
+
bottom: bounds.bottom - scrollY
|
|
63
|
+
};
|
|
64
|
+
|
|
26
65
|
// Outside viewport
|
|
27
66
|
if (
|
|
28
|
-
rect.bottom <
|
|
29
|
-
rect.top >
|
|
30
|
-
rect.right <
|
|
31
|
-
rect.left >
|
|
67
|
+
rect.bottom < viewportBounds.top ||
|
|
68
|
+
rect.top > viewportBounds.bottom ||
|
|
69
|
+
rect.right < viewportBounds.left ||
|
|
70
|
+
rect.left > viewportBounds.right
|
|
32
71
|
) {
|
|
33
72
|
return false;
|
|
34
73
|
}
|
|
@@ -44,7 +83,7 @@ export function isElementInViewport(
|
|
|
44
83
|
export function collectVisibleElements(bounds: CaptureBounds): Element[] {
|
|
45
84
|
const all = Array.from(document.body.getElementsByTagName('*'));
|
|
46
85
|
|
|
47
|
-
|
|
86
|
+
const filtered = all.filter(element => {
|
|
48
87
|
// Skip non-HTML elements
|
|
49
88
|
if (!(element instanceof HTMLElement)) return false;
|
|
50
89
|
|
|
@@ -61,6 +100,18 @@ export function collectVisibleElements(bounds: CaptureBounds): Element[] {
|
|
|
61
100
|
|
|
62
101
|
return isElementInViewport(element as HTMLElement, bounds);
|
|
63
102
|
});
|
|
103
|
+
|
|
104
|
+
console.log('[ElementCollector] collectVisibleElements:', {
|
|
105
|
+
total: all.length,
|
|
106
|
+
filtered: filtered.length,
|
|
107
|
+
bounds,
|
|
108
|
+
sampleElements: filtered.slice(0, 5).map(el => ({
|
|
109
|
+
tag: el.tagName,
|
|
110
|
+
rect: el.getBoundingClientRect()
|
|
111
|
+
}))
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
return filtered;
|
|
64
115
|
}
|
|
65
116
|
|
|
66
117
|
export function estimateDOMComplexity(element: HTMLElement): {
|
|
@@ -21,6 +21,23 @@ function serializeSVG(svg: SVGElement): string {
|
|
|
21
21
|
return svgString;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Check if SVG contains external resources that might cause CORS issues
|
|
26
|
+
*/
|
|
27
|
+
function hasExternalResources(svgString: string): boolean {
|
|
28
|
+
// Check for external image references
|
|
29
|
+
if (svgString.includes('xlink:href="http') || svgString.includes('href="http')) {
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Check for external font references
|
|
34
|
+
if (svgString.includes('@font-face') && svgString.includes('url(http')) {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
|
|
24
41
|
/**
|
|
25
42
|
* Render SVG element to canvas
|
|
26
43
|
*/
|
|
@@ -45,6 +62,12 @@ export async function renderSVG(
|
|
|
45
62
|
// Serialize SVG to string
|
|
46
63
|
const svgString = serializeSVG(svg);
|
|
47
64
|
|
|
65
|
+
// Check for external resources that might cause CORS issues
|
|
66
|
+
if (hasExternalResources(svgString)) {
|
|
67
|
+
console.warn('[SVG] Skipping SVG with external resources to prevent canvas tainting');
|
|
68
|
+
return { success: true }; // Skip but don't fail
|
|
69
|
+
}
|
|
70
|
+
|
|
48
71
|
// Create blob URL
|
|
49
72
|
const blob = new Blob([svgString], { type: 'image/svg+xml;charset=utf-8' });
|
|
50
73
|
const url = URL.createObjectURL(blob);
|
package/src/utils/screenshot.ts
CHANGED
|
@@ -36,11 +36,26 @@ export const captureScreenshot = async (
|
|
|
36
36
|
// Tier 2: Fallback to modern-screenshot
|
|
37
37
|
console.log('[Screenshot] Tier 2: Attempting modern-screenshot...');
|
|
38
38
|
|
|
39
|
+
// Use Visual Viewport API for accurate mobile dimensions
|
|
40
|
+
const getViewportWidth = () => {
|
|
41
|
+
if (window.visualViewport) {
|
|
42
|
+
return window.visualViewport.width;
|
|
43
|
+
}
|
|
44
|
+
return document.documentElement.clientWidth || window.innerWidth;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const getViewportHeight = () => {
|
|
48
|
+
if (window.visualViewport) {
|
|
49
|
+
return window.visualViewport.height;
|
|
50
|
+
}
|
|
51
|
+
return document.documentElement.clientHeight || window.innerHeight;
|
|
52
|
+
};
|
|
53
|
+
|
|
39
54
|
const element = document.documentElement;
|
|
40
55
|
const targetX = cropRect ? cropRect.x : 0;
|
|
41
56
|
const targetY = cropRect ? cropRect.y : 0;
|
|
42
|
-
const targetWidth = cropRect ? cropRect.width :
|
|
43
|
-
const targetHeight = cropRect ? cropRect.height :
|
|
57
|
+
const targetWidth = cropRect ? cropRect.width : getViewportWidth();
|
|
58
|
+
const targetHeight = cropRect ? cropRect.height : getViewportHeight();
|
|
44
59
|
|
|
45
60
|
const scrollX = window.scrollX;
|
|
46
61
|
const scrollY = window.scrollY;
|