pushfeedback 0.1.84 → 0.1.85
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/cjs/canvas-editor_3.cjs.entry.js +393 -41
- package/dist/cjs/loader.cjs.js +1 -1
- package/dist/cjs/pushfeedback.cjs.js +1 -1
- package/dist/collection/components/canvas-editor/canvas-editor.js +173 -15
- package/dist/collection/components/feedback-button/feedback-button.css +45 -1
- package/dist/collection/components/feedback-button/feedback-button.js +58 -8
- package/dist/collection/components/feedback-modal/feedback-modal.css +6 -0
- package/dist/collection/components/feedback-modal/feedback-modal.js +272 -17
- package/dist/collection/utils/sanitize-html.js +90 -0
- package/dist/components/canvas-editor2.js +139 -15
- package/dist/components/feedback-button.js +24 -8
- package/dist/components/feedback-modal2.js +241 -18
- package/dist/esm/canvas-editor_3.entry.js +393 -41
- package/dist/esm/loader.js +1 -1
- package/dist/esm/pushfeedback.js +1 -1
- package/dist/pushfeedback/p-17acbd1c.entry.js +1 -0
- package/dist/pushfeedback/pushfeedback.esm.js +1 -1
- package/dist/types/components/canvas-editor/canvas-editor.d.ts +7 -0
- package/dist/types/components/feedback-button/feedback-button.d.ts +14 -0
- package/dist/types/components/feedback-modal/feedback-modal.d.ts +24 -0
- package/dist/types/components.d.ts +40 -0
- package/dist/types/utils/sanitize-html.d.ts +8 -0
- package/package.json +2 -1
- package/dist/pushfeedback/p-06bb93cb.entry.js +0 -1
|
@@ -6,12 +6,59 @@ const index = require('./index-9a8f4784.js');
|
|
|
6
6
|
|
|
7
7
|
const canvasEditorCss = ":host{display:block}.canvas-editor-wrapper{position:relative}.canvas-editor-overlay{position:fixed;top:0;left:0;width:100vw;height:100vh;background:rgba(0, 0, 0, 0.8);display:flex;align-items:center;justify-content:center;z-index:9999}.canvas-editor-modal{width:95vw;height:95vh;max-width:none;max-height:none;background:var(--feedback-canvas-editor-bg-color, #ffffff);border-radius:12px;border:1px solid rgba(0, 0, 0, 0.08);display:flex;flex-direction:column;overflow:hidden;box-shadow:0 0 0 1px rgba(0, 0, 0, 0.02),\n 0 8px 16px rgba(0, 0, 0, 0.08),\n 0 16px 32px rgba(0, 0, 0, 0.08),\n 0 32px 64px rgba(0, 0, 0, 0.06)}.canvas-editor-header{background:var(--feedback-canvas-editor-header-bg-color, #fafafa);border-bottom:1px solid rgba(0, 0, 0, 0.06);padding:16px 20px;display:flex;flex-direction:column;gap:16px}.canvas-editor-title h3{margin:0;font-size:18px;font-weight:600;color:var(--feedback-canvas-editor-tool-text-color, #1a1a1a);letter-spacing:-0.02em}.canvas-editor-toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:16px}.toolbar-section{display:flex;align-items:center;gap:8px}.tool-group{display:flex;align-items:center;gap:4px}.tool-btn{width:36px;height:36px;display:flex;align-items:center;justify-content:center;background:var(--feedback-canvas-editor-tool-bg-color, #ffffff);border:1.5px solid rgba(0, 0, 0, 0.08);border-radius:8px;cursor:pointer;transition:all 0.2s cubic-bezier(0.4, 0, 0.2, 1);padding:0;box-shadow:0 1px 2px rgba(0, 0, 0, 0.04)}.tool-btn svg{width:18px;height:18px;color:var(--feedback-canvas-editor-tool-text-color, #333)}.tool-btn:hover:not(:disabled){background:var(--feedback-canvas-editor-tool-bg-hover, #f5f5f5);border-color:rgba(0, 0, 0, 0.12);transform:translateY(-1px);box-shadow:0 2px 4px rgba(0, 0, 0, 0.08)}.tool-btn.active,.tool-btn.active:hover{background:var(--feedback-canvas-editor-tool-bg-active, #0070f4);color:var(--feedback-canvas-editor-tool-text-active, #ffffff);border-color:var(--feedback-canvas-editor-tool-bg-active, #0070f4);box-shadow:0 2px 8px rgba(0, 112, 244, 0.3)}.tool-btn.active svg{color:var(--feedback-canvas-editor-tool-text-active, #ffffff)}.tool-btn:disabled{opacity:0.4;cursor:not-allowed;color:var(--feedback-canvas-editor-tool-text-color, #333)}.toolbar-divider{width:1px;height:24px;background:var(--feedback-canvas-editor-divider-color, #e0e0e0);margin:0 4px}.undo-btn,.delete-btn{background:var(--feedback-canvas-editor-tool-bg-color, #ffffff) !important;border:1px solid var(--feedback-canvas-editor-border-color, #e0e0e0) !important}.undo-btn:hover:not(:disabled),.delete-btn:hover:not(:disabled){background:var(--feedback-canvas-editor-tool-bg-hover, #f0f0f0) !important}.undo-btn:disabled,.delete-btn:disabled{opacity:0.3;cursor:not-allowed}.delete-btn:hover:not(:disabled){background:#fee !important;border-color:#fcc !important}.delete-btn:hover:not(:disabled) svg{color:#c53030}.color-palette{display:flex;align-items:center;gap:6px}.color-slot-wrapper{position:relative;display:flex;align-items:center}.color-btn{width:32px;height:32px;border-radius:8px;border:2px solid transparent;cursor:pointer;transition:all 0.2s cubic-bezier(0.4, 0, 0.2, 1);display:flex;align-items:center;justify-content:center;background:var(--feedback-canvas-editor-tool-bg-color, #ffffff);border:2px solid rgba(0, 0, 0, 0.08);box-shadow:0 1px 2px rgba(0, 0, 0, 0.04)}.color-btn:hover{transform:translateY(-2px) scale(1.05);box-shadow:0 4px 8px rgba(0, 0, 0, 0.12)}.color-btn.active{border-color:var(--feedback-primary-color, #0070f4);box-shadow:0 0 0 2px rgba(0, 112, 244, 0.2)}.color-btn.editing{border-color:var(--feedback-primary-color, #0070f4)}.color-picker-dropdown{position:absolute;top:100%;left:0;z-index:1000;margin-top:4px;background:var(--feedback-canvas-editor-tool-bg-color, #ffffff);border:1px solid var(--feedback-canvas-editor-border-color, #e0e0e0);border-radius:6px;padding:8px;box-shadow:0 4px 12px rgba(0, 0, 0, 0.1)}.color-picker-dropdown input[type=\"color\"]{width:40px;height:40px;border:none;border-radius:4px;cursor:pointer}.size-control{display:flex;align-items:center;gap:8px;background:var(--feedback-canvas-editor-tool-bg-color, #ffffff);border:1px solid var(--feedback-canvas-editor-border-color, #e0e0e0);border-radius:6px;padding:6px 12px}.size-slider{width:80px;height:14px;-webkit-appearance:none;appearance:none;background:transparent;border-radius:2px;outline:none;cursor:pointer;border:none;position:relative}.size-slider::before{content:'';position:absolute;top:50%;left:0;right:0;height:4px;background:rgba(0, 0, 0, 0.15);border-radius:2px;transform:translateY(-50%);pointer-events:none}.size-slider::-webkit-slider-track{width:100%;height:4px;background:transparent;border-radius:2px;border:none}.size-slider::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:14px;height:14px;background:var(--feedback-primary-color, #0070f4);border-radius:50%;cursor:pointer;position:relative;z-index:1}.size-slider::-moz-range-track{width:100%;height:4px;background:transparent;border-radius:2px;border:none}.size-slider::-moz-range-thumb{width:14px;height:14px;background:var(--feedback-primary-color, #0070f4);border-radius:50%;border:none;cursor:pointer}.size-slider::-ms-track{width:100%;height:4px;background:transparent;border-radius:2px;border:none;color:transparent}.size-slider::-ms-thumb{width:14px;height:14px;background:var(--feedback-primary-color, #0070f4);border-radius:50%;border:none;cursor:pointer}.size-value{font-weight:500;color:var(--feedback-canvas-editor-tool-text-color, #333);font-size:12px;min-width:30px}.selected-annotation-controls{border-left:2px solid var(--feedback-canvas-editor-divider-color, #e0e0e0);padding-left:12px;margin-left:8px;min-width:200px}.text-controls{display:flex;flex-direction:row;align-items:center;gap:12px}.font-size-control,.border-width-control{display:flex;align-items:center;gap:6px}.font-size-control label,.border-width-control label{font-size:12px;color:var(--feedback-canvas-editor-tool-text-color, #333);font-weight:500;min-width:35px}.action-btn{display:flex;align-items:center;gap:8px;padding:0 20px;border:1.5px solid rgba(0, 0, 0, 0.08);border-radius:8px;cursor:pointer;font-size:14px;font-weight:500;transition:all 0.2s cubic-bezier(0.4, 0, 0.2, 1);min-width:80px;justify-content:center;height:40px;box-shadow:0 1px 2px rgba(0, 0, 0, 0.04)}.action-btn.secondary{background:var(--feedback-canvas-editor-tool-bg-color, #ffffff);color:var(--feedback-canvas-editor-tool-text-color, #333);border-color:rgba(0, 0, 0, 0.08)}.action-btn.secondary:hover{background:var(--feedback-canvas-editor-tool-bg-hover, #f5f5f5);border-color:rgba(0, 0, 0, 0.12);transform:translateY(-1px);box-shadow:0 4px 8px rgba(0, 0, 0, 0.08)}.action-btn.primary{background:var(--feedback-primary-color, #0070f4);color:#ffffff;border-color:var(--feedback-primary-color, #0070f4);box-shadow:0 2px 4px rgba(0, 112, 244, 0.2)}.action-btn.primary:hover{background:#0056cc;border-color:#0056cc;transform:translateY(-1px);box-shadow:0 6px 12px rgba(0, 112, 244, 0.3)}.action-btn:active{transform:translateY(0)}.action-btn.small{height:28px;padding:4px 8px;font-size:12px;min-width:65px}.shape-controls{display:flex;flex-direction:column;gap:8px}.canvas-editor-content{flex:1;display:flex;align-items:center;justify-content:center;padding:24px;background:var(--feedback-canvas-editor-content-bg, #f5f5f5);overflow:hidden;min-height:0;min-width:0;position:relative}.canvas-editor-content::-webkit-scrollbar{width:8px;height:8px}.canvas-editor-content::-webkit-scrollbar-track{background:transparent}.canvas-editor-content::-webkit-scrollbar-thumb{background:rgba(0, 0, 0, 0.2);border-radius:4px}.canvas-editor-content::-webkit-scrollbar-thumb:hover{background:rgba(0, 0, 0, 0.3)}.annotation-canvas{max-width:calc(100% - 48px);max-height:calc(100% - 48px);width:auto !important;height:auto !important;cursor:crosshair;border-radius:8px;box-shadow:0 0 0 1px rgba(0, 0, 0, 0.04),\n 0 4px 6px rgba(0, 0, 0, 0.06),\n 0 8px 16px rgba(0, 0, 0, 0.08);background:#ffffff;transition:box-shadow 0.3s cubic-bezier(0.4, 0, 0.2, 1);object-fit:contain;display:block;margin:auto}.annotation-canvas:hover{box-shadow:0 0 0 1px rgba(0, 0, 0, 0.06),\n 0 6px 10px rgba(0, 0, 0, 0.08),\n 0 12px 24px rgba(0, 0, 0, 0.1)}@media screen and (min-width: 1920px){.canvas-editor-modal{width:90vw;height:90vh}.canvas-editor-content{padding:32px}}@media screen and (min-width: 1200px) and (max-width: 1919px){.canvas-editor-modal{width:92vw;height:92vh}.canvas-editor-content{padding:28px}}@media screen and (min-width: 769px) and (max-width: 1199px){.canvas-editor-modal{width:95vw;height:95vh}}@media screen and (max-width: 768px){.canvas-editor-modal{width:100vw;height:100vh;border-radius:0}.canvas-editor-toolbar{flex-direction:column;align-items:stretch;gap:8px}.toolbar-section{justify-content:center}.selected-annotation-controls{border-left:none;border-top:2px solid var(--feedback-canvas-editor-divider-color, #e0e0e0);padding-left:0;padding-top:8px;margin-left:0;margin-top:8px;min-width:auto}.canvas-editor-content{padding:16px}}";
|
|
8
8
|
|
|
9
|
+
// Upper bound on waiting for the capture stream to produce video metadata,
|
|
10
|
+
// after which the stream is stopped so it cannot leak
|
|
11
|
+
const CAPTURE_TIMEOUT_MS = 10000;
|
|
9
12
|
const CanvasEditor = class {
|
|
10
13
|
constructor(hostRef) {
|
|
11
14
|
index.registerInstance(this, hostRef);
|
|
12
15
|
this.screenshotReady = index.createEvent(this, "screenshotReady", 7);
|
|
13
16
|
this.screenshotCancelled = index.createEvent(this, "screenshotCancelled", 7);
|
|
14
17
|
this.screenshotFailed = index.createEvent(this, "screenshotFailed", 7);
|
|
18
|
+
this.handleKeyDown = (event) => {
|
|
19
|
+
if (!this.showCanvasEditor || !this.escapeClose || event.key !== 'Escape') {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
// While the browser is capturing the screen, the picker owns the keyboard
|
|
23
|
+
if (this.takingScreenshot) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
event.preventDefault();
|
|
27
|
+
// The colour picker is a popup on top of the editor, so Escape dismisses
|
|
28
|
+
// that first and leaves the editor (and any annotations) in place
|
|
29
|
+
if (this.showColorPicker) {
|
|
30
|
+
this.showColorPicker = false;
|
|
31
|
+
this.editingColorIndex = -1;
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
// Deselect before closing, the way drawing tools do. Closing throws away
|
|
35
|
+
// every annotation, so someone who has just selected one gets a cheap
|
|
36
|
+
// Escape that only drops the selection rather than their work
|
|
37
|
+
if (this.selectedAnnotation) {
|
|
38
|
+
this.selectedAnnotation = null;
|
|
39
|
+
this.redrawAnnotations();
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
// Same as the Cancel button: discards annotations and returns to the form
|
|
43
|
+
this.closeCanvasEditor();
|
|
44
|
+
};
|
|
45
|
+
// Tracks whether the press that started this click landed on the backdrop.
|
|
46
|
+
// Dragging an annotation and releasing past the edge of the editor fires a
|
|
47
|
+
// click on the backdrop too, and that must not be read as "clicked outside".
|
|
48
|
+
this.pressStartedOnOverlay = false;
|
|
49
|
+
this.handleOverlayMouseDown = (event) => {
|
|
50
|
+
this.pressStartedOnOverlay = event.target === event.currentTarget;
|
|
51
|
+
};
|
|
52
|
+
this.handleOverlayClick = (event) => {
|
|
53
|
+
const startedOutside = this.pressStartedOnOverlay;
|
|
54
|
+
this.pressStartedOnOverlay = false;
|
|
55
|
+
if (!this.clickOutsideClose || !startedOutside || event.target !== event.currentTarget) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
// Closes the editor only. The feedback form reopens behind it, so a stray
|
|
59
|
+
// click never dismisses the whole widget
|
|
60
|
+
this.closeCanvasEditor();
|
|
61
|
+
};
|
|
15
62
|
this.handleWindowResize = () => {
|
|
16
63
|
// Debounce resize events
|
|
17
64
|
if (this.resizeTimeout) {
|
|
@@ -691,8 +738,16 @@ const CanvasEditor = class {
|
|
|
691
738
|
if (!this.isDrawing || !this.currentAnnotation)
|
|
692
739
|
return;
|
|
693
740
|
this.isDrawing = false;
|
|
694
|
-
|
|
741
|
+
const annotation = this.currentAnnotation;
|
|
695
742
|
this.currentAnnotation = null;
|
|
743
|
+
// A click with no drag never reaches mousemove, so the shape has no end
|
|
744
|
+
// point or size. Committing it added an invisible annotation that could
|
|
745
|
+
// not be selected, dragged or deleted, and it still consumed an undo step.
|
|
746
|
+
if (this.isDegenerateAnnotation(annotation)) {
|
|
747
|
+
this.redrawAnnotations();
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
this.annotations = [...this.annotations, annotation];
|
|
696
751
|
this.redrawAnnotations();
|
|
697
752
|
};
|
|
698
753
|
// Convert screen coordinates to canvas coordinates
|
|
@@ -734,11 +789,28 @@ const CanvasEditor = class {
|
|
|
734
789
|
}
|
|
735
790
|
case 'line':
|
|
736
791
|
case 'arrow': {
|
|
737
|
-
// Distance from point to line
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
792
|
+
// Distance from the point to the line *segment*, not to the infinite
|
|
793
|
+
// line through it. The previous formula measured the infinite line, so
|
|
794
|
+
// a click far past either end still counted as a hit, and a zero-length
|
|
795
|
+
// segment divided by zero and gave NaN, leaving that annotation
|
|
796
|
+
// impossible to select, drag or delete.
|
|
797
|
+
// A click with no drag never reaches mousemove, so endX/endY are
|
|
798
|
+
// undefined rather than equal to the start. Treat that as a segment
|
|
799
|
+
// collapsed to its start point instead of letting NaN propagate.
|
|
800
|
+
const endX = Number.isFinite(annotation.endX) ? annotation.endX : annotation.startX;
|
|
801
|
+
const endY = Number.isFinite(annotation.endY) ? annotation.endY : annotation.startY;
|
|
802
|
+
const dx = endX - annotation.startX;
|
|
803
|
+
const dy = endY - annotation.startY;
|
|
804
|
+
const lengthSquared = dx * dx + dy * dy;
|
|
805
|
+
// Zero-length segment: t stays 0 and we measure to the single point
|
|
806
|
+
let t = 0;
|
|
807
|
+
if (lengthSquared > 0) {
|
|
808
|
+
t = ((x - annotation.startX) * dx + (y - annotation.startY) * dy) / lengthSquared;
|
|
809
|
+
t = Math.max(0, Math.min(1, t));
|
|
810
|
+
}
|
|
811
|
+
const closestX = annotation.startX + t * dx;
|
|
812
|
+
const closestY = annotation.startY + t * dy;
|
|
813
|
+
const distance = Math.hypot(x - closestX, y - closestY);
|
|
742
814
|
return distance <= tolerance;
|
|
743
815
|
}
|
|
744
816
|
case 'text': {
|
|
@@ -763,6 +835,8 @@ const CanvasEditor = class {
|
|
|
763
835
|
this.screenshotTakingText = 'Taking screenshot...';
|
|
764
836
|
this.screenshotAttachedText = 'Screenshot attached';
|
|
765
837
|
this.screenshotButtonText = 'Add a screenshot';
|
|
838
|
+
this.escapeClose = true;
|
|
839
|
+
this.clickOutsideClose = true;
|
|
766
840
|
this.autoStartScreenshot = false;
|
|
767
841
|
this.existingScreenshot = '';
|
|
768
842
|
this.editTextButtonText = 'Edit Text';
|
|
@@ -818,10 +892,12 @@ const CanvasEditor = class {
|
|
|
818
892
|
// Add window resize listener for canvas adaptation
|
|
819
893
|
this.handleWindowResize = this.handleWindowResize.bind(this);
|
|
820
894
|
window.addEventListener('resize', this.handleWindowResize);
|
|
895
|
+
document.addEventListener('keydown', this.handleKeyDown);
|
|
821
896
|
}
|
|
822
897
|
disconnectedCallback() {
|
|
823
898
|
// Clean up resize listener
|
|
824
899
|
window.removeEventListener('resize', this.handleWindowResize);
|
|
900
|
+
document.removeEventListener('keydown', this.handleKeyDown);
|
|
825
901
|
}
|
|
826
902
|
async captureViewportScreenshot() {
|
|
827
903
|
try {
|
|
@@ -845,6 +921,30 @@ const CanvasEditor = class {
|
|
|
845
921
|
video.autoplay = true;
|
|
846
922
|
video.muted = true;
|
|
847
923
|
return new Promise((resolve, reject) => {
|
|
924
|
+
// If neither onloadedmetadata nor onerror ever fires, the promise would
|
|
925
|
+
// never settle: the capture stream would stay live with the browser's
|
|
926
|
+
// recording indicator on, and the widget would sit in "Taking
|
|
927
|
+
// screenshot..." forever with no way back short of a page reload.
|
|
928
|
+
let settled = false;
|
|
929
|
+
const stopStream = () => {
|
|
930
|
+
stream.getTracks().forEach((track) => track.stop());
|
|
931
|
+
};
|
|
932
|
+
const finish = (fn) => {
|
|
933
|
+
if (settled) {
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
settled = true;
|
|
937
|
+
clearTimeout(watchdog);
|
|
938
|
+
video.onloadedmetadata = null;
|
|
939
|
+
video.onerror = null;
|
|
940
|
+
fn();
|
|
941
|
+
};
|
|
942
|
+
const watchdog = setTimeout(() => {
|
|
943
|
+
finish(() => {
|
|
944
|
+
stopStream();
|
|
945
|
+
reject(new Error('Screenshot capture timed out'));
|
|
946
|
+
});
|
|
947
|
+
}, CAPTURE_TIMEOUT_MS);
|
|
848
948
|
video.onloadedmetadata = () => {
|
|
849
949
|
video.play();
|
|
850
950
|
// Wait a moment for video to stabilize
|
|
@@ -856,22 +956,26 @@ const CanvasEditor = class {
|
|
|
856
956
|
canvas.height = video.videoHeight;
|
|
857
957
|
const ctx = canvas.getContext('2d');
|
|
858
958
|
ctx.drawImage(video, 0, 0);
|
|
859
|
-
// Stop the stream
|
|
860
|
-
stream.getTracks().forEach((track) => track.stop());
|
|
861
959
|
// Convert to data URL
|
|
862
960
|
const dataUrl = canvas.toDataURL('image/png');
|
|
863
|
-
|
|
864
|
-
|
|
961
|
+
finish(() => {
|
|
962
|
+
stopStream();
|
|
963
|
+
resolve(dataUrl);
|
|
964
|
+
});
|
|
865
965
|
}
|
|
866
966
|
catch (error) {
|
|
867
|
-
|
|
868
|
-
|
|
967
|
+
finish(() => {
|
|
968
|
+
stopStream();
|
|
969
|
+
reject(error);
|
|
970
|
+
});
|
|
869
971
|
}
|
|
870
972
|
}, 100);
|
|
871
973
|
};
|
|
872
974
|
video.onerror = () => {
|
|
873
|
-
|
|
874
|
-
|
|
975
|
+
finish(() => {
|
|
976
|
+
stopStream();
|
|
977
|
+
reject(new Error('Failed to load video for screenshot capture'));
|
|
978
|
+
});
|
|
875
979
|
};
|
|
876
980
|
});
|
|
877
981
|
}
|
|
@@ -880,9 +984,27 @@ const CanvasEditor = class {
|
|
|
880
984
|
throw error;
|
|
881
985
|
}
|
|
882
986
|
}
|
|
987
|
+
// Whether a freshly drawn shape is too small to be worth keeping
|
|
988
|
+
isDegenerateAnnotation(annotation) {
|
|
989
|
+
const MIN_SIZE = 2; // canvas pixels
|
|
990
|
+
if (annotation.type === 'line' || annotation.type === 'arrow') {
|
|
991
|
+
if (!Number.isFinite(annotation.endX) || !Number.isFinite(annotation.endY)) {
|
|
992
|
+
return true;
|
|
993
|
+
}
|
|
994
|
+
return (Math.hypot(annotation.endX - annotation.startX, annotation.endY - annotation.startY) <
|
|
995
|
+
MIN_SIZE);
|
|
996
|
+
}
|
|
997
|
+
if (annotation.type === 'rectangle') {
|
|
998
|
+
if (!Number.isFinite(annotation.width) || !Number.isFinite(annotation.height)) {
|
|
999
|
+
return true;
|
|
1000
|
+
}
|
|
1001
|
+
return Math.abs(annotation.width) < MIN_SIZE && Math.abs(annotation.height) < MIN_SIZE;
|
|
1002
|
+
}
|
|
1003
|
+
return false;
|
|
1004
|
+
}
|
|
883
1005
|
render() {
|
|
884
1006
|
var _a, _b, _c, _d, _e, _f;
|
|
885
|
-
return (index.h("div", { class: "canvas-editor-wrapper" }, this.showCanvasEditor && (index.h("div", { class: "canvas-editor-overlay", part: "overlay" }, index.h("div", { class: "canvas-editor-modal", part: "modal" }, index.h("div", { class: "canvas-editor-header", part: "header" }, index.h("div", { class: "canvas-editor-title", part: "title" }, index.h("h3",
|
|
1007
|
+
return (index.h("div", { class: "canvas-editor-wrapper" }, this.showCanvasEditor && (index.h("div", { class: "canvas-editor-overlay", part: "overlay", onMouseDown: this.handleOverlayMouseDown, onClick: this.handleOverlayClick }, index.h("div", { class: "canvas-editor-modal", part: "modal", role: "dialog", "aria-modal": "true", "aria-labelledby": "canvas-editor-title" }, index.h("div", { class: "canvas-editor-header", part: "header" }, index.h("div", { class: "canvas-editor-title", part: "title" }, index.h("h3", { id: "canvas-editor-title" }, this.canvasEditorTitle)), index.h("div", { class: "canvas-editor-toolbar", part: "toolbar" }, index.h("div", { class: "toolbar-section" }, index.h("div", { class: "tool-group" }, index.h("button", { class: `tool-btn ${this.canvasDrawingTool === 'rectangle' ? 'active' : ''}`, part: "tool-button", onClick: () => (this.canvasDrawingTool = 'rectangle'), title: "Rectangle" }, index.h("svg", { xmlns: "http://www.w3.org/2000/svg", width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", class: "lucide lucide-square" }, index.h("rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }))), index.h("button", { class: `tool-btn ${this.canvasDrawingTool === 'line' ? 'active' : ''}`, part: "tool-button", onClick: () => (this.canvasDrawingTool = 'line'), title: "Line" }, index.h("svg", { xmlns: "http://www.w3.org/2000/svg", width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", class: "lucide lucide-minus" }, index.h("path", { d: "M5 12h14" }))), index.h("button", { class: `tool-btn ${this.canvasDrawingTool === 'arrow' ? 'active' : ''}`, part: "tool-button", onClick: () => (this.canvasDrawingTool = 'arrow'), title: "Arrow" }, index.h("svg", { xmlns: "http://www.w3.org/2000/svg", width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", class: "lucide lucide-move-up-right" }, index.h("path", { d: "M13 5H19V11" }), index.h("path", { d: "M19 5L5 19" }))), index.h("button", { class: `tool-btn ${this.canvasDrawingTool === 'text' ? 'active' : ''}`, part: "tool-button", onClick: () => (this.canvasDrawingTool = 'text'), title: "Text" }, index.h("svg", { xmlns: "http://www.w3.org/2000/svg", width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", class: "lucide lucide-type" }, index.h("path", { d: "M12 4v16" }), index.h("path", { d: "M4 7V5a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v2" }), index.h("path", { d: "M9 20h6" }))), index.h("div", { class: "toolbar-divider" }), index.h("button", { class: "tool-btn undo-btn", part: "tool-button", onClick: this.undoLastAnnotation, disabled: this.annotations.length === 0, title: "Undo" }, index.h("svg", { xmlns: "http://www.w3.org/2000/svg", width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", class: "lucide lucide-undo" }, index.h("path", { d: "M3 7v6h6" }), index.h("path", { d: "M21 17a9 9 0 0 0-9-9 9 9 0 0 0-6 2.3L3 13" }))), this.selectedAnnotation && (index.h("button", { class: "tool-btn delete-btn", part: "tool-button", onClick: this.deleteSelectedAnnotation, title: "Delete" }, index.h("svg", { xmlns: "http://www.w3.org/2000/svg", width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", class: "lucide lucide-trash-2" }, index.h("path", { d: "M3 6h18" }), index.h("path", { d: "M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" }), index.h("path", { d: "M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" }), index.h("line", { x1: "10", x2: "10", y1: "11", y2: "17" }), index.h("line", { x1: "14", x2: "14", y1: "11", y2: "17" })))))), index.h("div", { class: "toolbar-section" }, index.h("div", { class: "color-palette" }, this.defaultColors.map((color, index$1) => (index.h("div", { class: "color-slot-wrapper" }, index.h("button", { class: `color-btn ${this.canvasDrawingColor === color ? 'active' : ''} ${this.editingColorIndex === index$1 ? 'editing' : ''}`, style: { backgroundColor: color }, onClick: () => this.handleColorSlotClick(index$1), title: `Color ${index$1 + 1} - Click to customize` }, this.editingColorIndex === index$1 && (index.h("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "white", "stroke-width": "2" }, index.h("path", { d: "M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z" })))), this.editingColorIndex === index$1 && this.showColorPicker && (index.h("div", { class: "color-picker-dropdown" }, index.h("input", { type: "color", value: color, onInput: (e) => this.handleColorPickerInput(e), onClick: (e) => this.handleColorPickerClick(e) })))))))), (this.selectedAnnotation || this.canvasDrawingTool) && (index.h("div", { class: "toolbar-section selected-annotation-controls" }, (((_a = this.selectedAnnotation) === null || _a === void 0 ? void 0 : _a.type) === 'text' ||
|
|
886
1008
|
(!this.selectedAnnotation && this.canvasDrawingTool === 'text')) && (index.h("div", { class: "text-controls" }, index.h("div", { class: "font-size-control" }, index.h("label", null, this.sizeLabelText), index.h("input", { type: "range", min: "8", max: "72", value: ((_b = this.selectedAnnotation) === null || _b === void 0 ? void 0 : _b.fontSize) || this.canvasTextSize, onInput: (e) => {
|
|
887
1009
|
const newSize = parseInt(e.target.value);
|
|
888
1010
|
if (this.selectedAnnotation) {
|
|
@@ -906,7 +1028,7 @@ const CanvasEditor = class {
|
|
|
906
1028
|
};
|
|
907
1029
|
CanvasEditor.style = canvasEditorCss;
|
|
908
1030
|
|
|
909
|
-
const feedbackButtonCss = ".feedback-button-content{cursor:pointer;max-width:fit-content;letter-spacing:var(--feedback-button-letter-spacing);z-index:var(--feedback-button-z-index);font-family:var(--feedback-font-family)}.feedback-button-content--custom-font{font-family:inherit}.feedback-button-content--light{align-items:center;background-color:var(--feedback-button-light-bg-color);border-radius:var(--feedback-button-border-radius);box-shadow:rgba(60, 64, 67, 0.3) 0px 1px 2px 0px, rgba(60, 64, 67, 0.15) 0px 2px 6px 2px;box-sizing:border-box;color:var(--feedback-button-light-text-color);display:flex;font-size:var(--feedback-button-text-font-size);font-weight:var(--feedback-button-text-font-weight);padding:8px 15px}.feedback-button-content--dark{align-items:center;background-color:var(--feedback-button-dark-bg-color);border-radius:var(--feedback-button-border-radius);box-shadow:rgba(60, 64, 67, 0.3) 0px 1px 2px 0px, rgba(60, 64, 67, 0.15) 0px 2px 6px 2px;box-sizing:border-box;color:var(--feedback-button-dark-text-color);display:flex;font-weight:var(--feedback-button-text-font-weight);font-size:var(--feedback-button-text-font-size);padding:8px 15px}.icon-edit{stroke:var(--feedback-button-light-icon-color)}.feedback-button-content--dark .icon-edit{stroke:var(--feedback-button-dark-icon-color)}.feedback-button-content--bottom-right{bottom:10px;position:fixed;right:10px}.feedback-button-content--center-right{position:fixed;transform:rotate(-90deg) translateY(-50%);top:50%}.feedback-button-content--center-right.feedback-button-content--dark,.feedback-button-content--center-right.feedback-button-content--light{border-radius:4px;border-bottom-left-radius:0px;border-bottom-right-radius:0px}.feedback-button-content-icon{height:16px;margin-right:5px;width:16px}.feedback-button-content--center-right .feedback-button-content-icon{rotate:90deg}@media screen and (max-width: 767px){.feedback-button-content--hide-mobile{display:none}}";
|
|
1031
|
+
const feedbackButtonCss = ".feedback-button-content{cursor:pointer;max-width:fit-content;letter-spacing:var(--feedback-button-letter-spacing);z-index:var(--feedback-button-z-index);font-family:var(--feedback-font-family);background:none;border:none;margin:0;padding:0;color:inherit;font-size:inherit;font-weight:inherit;line-height:inherit;text-align:inherit}.feedback-button-content--custom-font{font-family:inherit}.feedback-button-content--light{align-items:center;background-color:var(--feedback-button-light-bg-color);border-radius:var(--feedback-button-border-radius);box-shadow:rgba(60, 64, 67, 0.3) 0px 1px 2px 0px, rgba(60, 64, 67, 0.15) 0px 2px 6px 2px;box-sizing:border-box;color:var(--feedback-button-light-text-color);display:flex;font-size:var(--feedback-button-text-font-size);font-weight:var(--feedback-button-text-font-weight);padding:8px 15px}.feedback-button-content--dark{align-items:center;background-color:var(--feedback-button-dark-bg-color);border-radius:var(--feedback-button-border-radius);box-shadow:rgba(60, 64, 67, 0.3) 0px 1px 2px 0px, rgba(60, 64, 67, 0.15) 0px 2px 6px 2px;box-sizing:border-box;color:var(--feedback-button-dark-text-color);display:flex;font-weight:var(--feedback-button-text-font-weight);font-size:var(--feedback-button-text-font-size);padding:8px 15px}.icon-edit{stroke:var(--feedback-button-light-icon-color)}.feedback-button-content--dark .icon-edit{stroke:var(--feedback-button-dark-icon-color)}.feedback-button-content--bottom-right{bottom:10px;position:fixed;right:10px}.feedback-button-content--bottom-left{bottom:10px;left:10px;position:fixed}.feedback-button-content--top-right{position:fixed;right:10px;top:10px}.feedback-button-content--top-left{left:10px;position:fixed;top:10px}.feedback-button-content--center-right{position:fixed;transform:rotate(-90deg) translateY(-50%);top:50%}.feedback-button-content--center-left{position:fixed;transform:rotate(90deg) translateY(-50%);top:50%}.feedback-button-content--center-right.feedback-button-content--dark,.feedback-button-content--center-right.feedback-button-content--light,.feedback-button-content--center-left.feedback-button-content--dark,.feedback-button-content--center-left.feedback-button-content--light{border-radius:4px;border-bottom-left-radius:0px;border-bottom-right-radius:0px}.feedback-button-content-icon{height:16px;margin-right:5px;width:16px}.feedback-button-content--center-right .feedback-button-content-icon{rotate:90deg}.feedback-button-content--center-left .feedback-button-content-icon{rotate:-90deg}@media screen and (max-width: 767px){.feedback-button-content--hide-mobile{display:none}}";
|
|
910
1032
|
|
|
911
1033
|
const FeedbackButton = class {
|
|
912
1034
|
constructor(hostRef) {
|
|
@@ -920,6 +1042,7 @@ const FeedbackButton = class {
|
|
|
920
1042
|
this.sessionId = '';
|
|
921
1043
|
this.metadata = '';
|
|
922
1044
|
this.submit = false;
|
|
1045
|
+
this.apiKey = '';
|
|
923
1046
|
this.clickOutsideClose = true;
|
|
924
1047
|
this.customFont = false;
|
|
925
1048
|
this.emailAddress = '';
|
|
@@ -936,6 +1059,7 @@ const FeedbackButton = class {
|
|
|
936
1059
|
this.ratingMode = 'thumbs';
|
|
937
1060
|
this.emailPlaceholder = 'Email address (optional)';
|
|
938
1061
|
this.errorMessage = 'Please try again later.';
|
|
1062
|
+
this.errorMessage401 = 'This project requires a valid API key to submit feedback. Check the api-key attribute.';
|
|
939
1063
|
this.errorMessage403 = 'The request URL does not match the one defined in PushFeedback for this project.';
|
|
940
1064
|
this.errorMessage404 = 'We could not find the provided project id in PushFeedback.';
|
|
941
1065
|
this.footerText = '';
|
|
@@ -981,13 +1105,19 @@ const FeedbackButton = class {
|
|
|
981
1105
|
}
|
|
982
1106
|
}
|
|
983
1107
|
componentDidLoad() {
|
|
984
|
-
if (this.buttonPosition === 'center-right') {
|
|
1108
|
+
if (this.buttonPosition === 'center-right' || this.buttonPosition === 'center-left') {
|
|
985
1109
|
const buttonContent = this.el.shadowRoot.querySelector('.feedback-button-content');
|
|
986
1110
|
let adjustement = 0;
|
|
987
1111
|
if (this.isSafariBrowser()) {
|
|
988
1112
|
adjustement = 5;
|
|
989
1113
|
}
|
|
990
|
-
|
|
1114
|
+
const offset = `${((buttonContent.offsetWidth + adjustement) / 2) * -1}px`;
|
|
1115
|
+
if (this.buttonPosition === 'center-right') {
|
|
1116
|
+
buttonContent.style.right = offset;
|
|
1117
|
+
}
|
|
1118
|
+
else {
|
|
1119
|
+
buttonContent.style.left = offset;
|
|
1120
|
+
}
|
|
991
1121
|
}
|
|
992
1122
|
if (!this.customFont) {
|
|
993
1123
|
this.loadInterFont();
|
|
@@ -996,6 +1126,7 @@ const FeedbackButton = class {
|
|
|
996
1126
|
connectedCallback() {
|
|
997
1127
|
this.feedbackModal = document.createElement('feedback-modal');
|
|
998
1128
|
const props = [
|
|
1129
|
+
'apiKey',
|
|
999
1130
|
'clickOutsideClose',
|
|
1000
1131
|
'customFont',
|
|
1001
1132
|
'emailAddress',
|
|
@@ -1026,6 +1157,7 @@ const FeedbackButton = class {
|
|
|
1026
1157
|
'screenshotErrorUnexpected',
|
|
1027
1158
|
'emailPlaceholder',
|
|
1028
1159
|
'errorMessage',
|
|
1160
|
+
'errorMessage401',
|
|
1029
1161
|
'errorMessage403',
|
|
1030
1162
|
'errorMessage404',
|
|
1031
1163
|
'footerText',
|
|
@@ -1090,12 +1222,16 @@ const FeedbackButton = class {
|
|
|
1090
1222
|
}
|
|
1091
1223
|
}
|
|
1092
1224
|
const body = Object.assign({ url: window.location.href, project: this.project, rating: this.rating || -1, ratingMode: this.ratingMode, message: '', metadata: this.metadata, session: localStorage.getItem('pushfeedback_sessionid') || '' }, (recaptchaToken && { recaptchaToken }));
|
|
1093
|
-
const
|
|
1225
|
+
const headers = {
|
|
1226
|
+
'Content-Type': 'application/json',
|
|
1227
|
+
};
|
|
1228
|
+
if (this.apiKey) {
|
|
1229
|
+
headers['Authorization'] = `Api-Key ${this.apiKey}`;
|
|
1230
|
+
}
|
|
1231
|
+
const res = await fetch('https://app.pushfeedback.com/api/v1/feedback/', {
|
|
1094
1232
|
method: 'POST',
|
|
1095
1233
|
body: JSON.stringify(body),
|
|
1096
|
-
headers
|
|
1097
|
-
'Content-Type': 'application/json',
|
|
1098
|
-
},
|
|
1234
|
+
headers,
|
|
1099
1235
|
});
|
|
1100
1236
|
if (res.status === 201) {
|
|
1101
1237
|
const feedback_with_id = Object.assign(Object.assign({}, body), { id: await res.json() });
|
|
@@ -1126,13 +1262,104 @@ const FeedbackButton = class {
|
|
|
1126
1262
|
}
|
|
1127
1263
|
}
|
|
1128
1264
|
render() {
|
|
1129
|
-
return (index.h(index.Host, null, index.h("
|
|
1265
|
+
return (index.h(index.Host, null, index.h("button", { type: "button", class: `feedback-button-content feedback-button-content--${this.buttonStyle} feedback-button-content--${this.buttonPosition} ${this.customFont ? 'feedback-button-content--custom-font' : ''} ${this.hideMobile ? 'feedback-button-content--hide-mobile' : ''}`, part: "button", onClick: () => this.showModal() }, !this.hideIcon && this.buttonStyle != 'default' && (index.h("span", { class: "feedback-button-content-icon", part: "icon" }, index.h("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "#fff", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", class: "icon-edit" }, index.h("path", { d: "M12 20h9" }), index.h("path", { d: "M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z" })))), index.h("slot", null))));
|
|
1130
1266
|
}
|
|
1131
1267
|
get el() { return index.getElement(this); }
|
|
1132
1268
|
};
|
|
1133
1269
|
FeedbackButton.style = feedbackButtonCss;
|
|
1134
1270
|
|
|
1135
|
-
const feedbackModalCss = ".text-center{flex-grow:1;text-align:center}.feedback-modal-wrapper *{font-family:var(--feedback-font-family)}.feedback-modal-wrapper--custom-font *{font-family:inherit}.feedback-modal-wrapper{position:absolute;z-index:var(--feedback-modal-modal-wrapper-z-index)}.feedback-overlay{background-color:var(--feedback-modal-screenshot-bg-color);height:100%;left:0;opacity:0;position:fixed;top:0;width:100%;z-index:var(--feedback-modal-screnshot-z-index);transition:opacity 0.25s cubic-bezier(0.4, 0, 0.2, 1);backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px)}.feedback-overlay--visible{opacity:1}.feedback-modal{display:inline-block;position:relative}.feedback-modal-content{background-color:var(--feedback-modal-content-bg-color);border:1px solid rgba(0, 0, 0, 0.08);border-radius:var(--feedback-modal-content-border-radius);box-shadow:0px 0px 0px 1px rgba(0, 0, 0, 0.02),\n 0px 2px 4px rgba(0, 0, 0, 0.04),\n 0px 8px 16px rgba(0, 0, 0, 0.06),\n 0px 16px 32px rgba(0, 0, 0, 0.04);box-sizing:border-box;color:var(--feedback-modal-content-text-color);display:flex;flex-direction:column;left:50%;max-width:90%;padding:24px;position:fixed;top:50%;transform:translate(-50%, -50%) scale(0.96);opacity:0;width:100%;z-index:var(--feedback-modal-content-z-index);transition:transform 0.25s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.25s cubic-bezier(0.4, 0, 0.2, 1)}.feedback-modal-content--open{transform:translate(-50%, -50%) scale(1);opacity:1}.feedback-modal-header{color:var(--feedback-modal-header-text-color);display:flex;font-size:var(--feedback-modal-header-font-size);font-weight:var(--feedback-modal-header-font-weight);justify-content:space-between;align-items:flex-start;margin-bottom:24px;letter-spacing:-0.01em}.feedback-modal-header-content{flex:1}.feedback-modal-header-text{display:flex;flex-direction:column;gap:6px}.feedback-modal-title{display:block}.feedback-modal-powered-by{display:block;font-size:13px;font-weight:400;color:var(--feedback-color-gray-500);line-height:1.4}.feedback-modal-powered-by a{color:var(--feedback-modal-header-text-color);text-decoration:none;font-weight:500}.feedback-modal-powered-by a:hover{text-decoration:underline}.feedback-modal-header--no-content{margin-bottom:0}.feedback-modal-rating-buttons{width:100%;margin-bottom:24px;display:flex;gap:4px}.feedback-modal-rating-button{padding:0;background-color:transparent;border:transparent;margin-right:8px;cursor:pointer}.feedback-modal-rating-buttons--stars .feedback-modal-rating-button{transition:transform 0.15s ease, opacity 0.15s ease}.feedback-modal-rating-buttons--stars .feedback-modal-rating-button:hover{transform:scale(1.1)}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button{border:1.5px solid var(--feedback-modal-button-border-color);border-radius:var(--feedback-modal-button-border-radius);color:var(--feedback-modal-button-text-color);font-size:var(--feedback-modal-button-font-size);font-weight:500;margin-right:12px;justify-content:center;padding:10px 16px;transition:all 0.2s cubic-bezier(0.4, 0, 0.2, 1);box-shadow:0 1px 2px rgba(0, 0, 0, 0.04)}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--positive{background-color:var(--feedback-modal-rating-button-positive-bg-color);border-color:var(--feedback-modal-rating-button-positive-border-color)}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--negative{background-color:var(--feedback-modal-rating-button-negative-bg-color);border-color:var(--feedback-modal-rating-button-negative-border-color)}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button:hover{transform:translateY(-2px);box-shadow:0 4px 8px rgba(0, 0, 0, 0.12)}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button:hover,.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--selected{background-color:var(--feedback-modal-button-bg-color-active);border:1.5px solid var(--feedback-modal-button-border-color-active);color:var(--feedback-modal-button-text-color-active)}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button:hover svg,.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--selected svg{stroke:var(--feedback-modal-rating-button-selected-color)}.feedback-modal-rating-buttons svg{stroke:var(--feedback-modal-rating-button-color);cursor:pointer}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--positive svg{stroke:var(--feedback-modal-rating-button-positive-color)}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--negative svg{stroke:var(--feedback-modal-rating-button-negative-color)}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--positive:hover,.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--positive.feedback-modal-rating-button--selected{background-color:var(--feedback-modal-rating-button-positive-selected-bg-color);border-color:var(--feedback-modal-rating-button-positive-selected-border-color)}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--negative:hover,.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--negative.feedback-modal-rating-button--selected{background-color:var(--feedback-modal-rating-button-negative-selected-bg-color);border-color:var(--feedback-modal-rating-button-negative-selected-border-color)}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--positive:hover svg,.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--positive.feedback-modal-rating-button--selected svg{stroke:var(--feedback-modal-rating-button-positive-selected-color)}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--negative:hover svg,.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--negative.feedback-modal-rating-button--selected svg{stroke:var(--feedback-modal-rating-button-negative-selected-color)}.feedback-modal-rating-buttons--stars .feedback-modal-rating-button--selected svg{fill:var(--feedback-modal-rating-button-stars-selected-color);stroke:var(--feedback-modal-rating-button-stars-selected-color)}.feedback-modal-text textarea{background-color:var(--feedback-modal-input-bg-color);border:1.5px solid var(--feedback-modal-input-border-color);border-radius:var(--feedback-modal-input-border-radius);box-sizing:border-box;color:var(--feedback-modal-input-text-color);font-size:var(--feedback-modal-input-font-size);margin-bottom:20px;height:100px;min-height:100px;padding:12px;resize:vertical;width:100%;transition:border-color 0.2s ease, box-shadow 0.2s ease;line-height:1.5}.feedback-modal-text textarea:hover{border-color:var(--feedback-modal-input-border-color-hover)}.feedback-modal-email input{background-color:var(--feedback-modal-input-bg-color);border:1.5px solid var(--feedback-modal-input-border-color);border-radius:var(--feedback-modal-input-border-radius);box-sizing:border-box;color:var(--feedback-modal-input-text-color);font-size:var(--feedback-modal-input-font-size);margin-bottom:20px;height:44px;padding:12px;width:100%;transition:border-color 0.2s ease, box-shadow 0.2s ease}.feedback-modal-email input:hover{border-color:var(--feedback-modal-input-border-color-hover)}.feedback-modal-text textarea:-webkit-autofill,.feedback-modal-email input:-webkit-autofill,.feedback-modal-email input:-webkit-autofill:hover,.feedback-modal-email input:-webkit-autofill:focus,.feedback-modal-email input:-webkit-autofill:active{-webkit-box-shadow:0 0 0 1000px var(--feedback-modal-input-bg-color) inset !important;-webkit-text-fill-color:var(--feedback-modal-input-text-color) !important;transition:background-color 5000s ease-in-out 0s}.feedback-modal-privacy{font-size:var(--feedback-modal-input-font-size);margin-bottom:20px}.feedback-modal-text textarea:focus,.feedback-modal-email input:focus{border:1.5px solid var(--feedback-modal-input-border-color-focused);outline:none;box-shadow:0 0 0 3px rgba(59, 130, 246, 0.1)}.feedback-modal-buttons{display:flex;flex-direction:column}.feedback-modal-buttons .feedback-modal-button{margin-bottom:20px}.feedback-modal-button{align-items:center;background-color:transparent;border:1.5px solid var(--feedback-modal-button-border-color);border-radius:var(--feedback-modal-button-border-radius);color:var(--feedback-modal-button-text-color);cursor:pointer;display:flex;font-size:var(--feedback-modal-button-font-size);font-weight:500;justify-content:center;min-height:44px;padding:10px 16px;transition:all 0.2s cubic-bezier(0.4, 0, 0.2, 1);box-shadow:0 1px 2px rgba(0, 0, 0, 0.04)}.feedback-modal-button svg{margin-right:6px}.feedback-modal-button path{fill:var(--feedback-modal-button-icon-color)}.feedback-modal-button:hover path,.feedback-modal-button--active path{fill:var(--feedback-modal-button-icon-color-active)}.feedback-modal-button--submit{background-color:var(--feedback-modal-button-submit-bg-color);border:1.5px solid var(--feedback-modal-button-border-color-active);color:var(--feedback-modal-button-submit-text-color);box-shadow:0 2px 4px rgba(0, 0, 0, 0.1)}.feedback-modal-button:hover,.feedback-modal-button--active{background-color:var(--feedback-modal-button-bg-color-active);border:1.5px solid var(--feedback-modal-button-border-color-active);color:var(--feedback-modal-button-text-color-active);transform:translateY(-1px);box-shadow:0 4px 8px rgba(0, 0, 0, 0.12)}.feedback-modal-button--submit:hover{background-color:var(--feedback-modal-button-submit-bg-color-hover);border:1.5px solid var(--feedback-modal-button-submit-border-color-hover);color:var(--feedback-modal-button-submit-text-color-hover);transform:translateY(-1px);box-shadow:0 6px 12px rgba(0, 0, 0, 0.15)}.feedback-modal-button--submit:active{transform:translateY(0);box-shadow:0 2px 4px rgba(0, 0, 0, 0.1)}.feedback-modal-input-heading{display:block;font-size:14px;font-weight:500;padding-bottom:12px;color:var(--feedback-modal-header-text-color);letter-spacing:-0.01em}.feedback-modal-footer{font-size:12px;text-align:center}.feedback-modal-footer a{color:var(--feedback-modal-footer-link);font-weight:500;text-decoration:none}.feedback-logo,.feedback-footer-text{display:block;text-align:center;margin-top:5px}.feedback-footer-text{margin-top:10px;line-height:1.5}.feedback-footer-combined{display:block;text-align:center;font-size:11px;color:#666;line-height:1.5}.feedback-footer-combined a{color:#666;text-decoration:underline}.feedback-recaptcha-notice{display:block;text-align:center;margin-bottom:10px;font-size:11px;color:#666;line-height:1.4}.feedback-recaptcha-notice a{color:#666;text-decoration:underline}.feedback-modal-close{background-color:var(--feedback-modal-close-bg-color);border:0;border-radius:6px;cursor:pointer;height:32px;width:32px;margin-left:auto;padding:0;display:flex;align-items:center;justify-content:center;transition:all 0.2s ease}.feedback-modal-close:hover{background-color:rgba(0, 0, 0, 0.06);transform:scale(1.05)}.feedback-modal-close:active{transform:scale(0.95)}.feedback-modal-close svg{stroke:var(--feedback-modal-close-color)}.feedback-modal-screenshot{background-color:var(--feedback-modal-screenshot-bg-color);height:100%;left:0;position:fixed;top:0;width:100%;z-index:var(--feedback-modal-screnshot-z-index)}.feedback-modal-screenshot-header{align-items:center;background-color:var(--feedback-modal-screenshot-header-bg-color);border-radius:var(--feedback-modal-content-border-radius);box-shadow:0px 1px 2px 0px rgba(60, 64, 67, .30), 0px 2px 6px 2px rgba(60, 64, 67, .15);box-sizing:border-box;color:var(--feedback-modal-screenshot-header-text-color);cursor:pointer;display:flex;left:50%;top:20px;transform:translateX(-50%);padding:10px;position:fixed;width:max-content;z-index:var(--feedback-modal-screenshot-header-z-index)}.feedback-modal-screenshot-close{height:24px;padding-left:10px;width:24px}.feedback-modal-screenshot-close svg{stroke:var(--feedback-modal-close-color)}.feedback-modal-message{font-size:var(--feedback-modal-message-font-size);margin:0;line-height:1.6;color:var(--feedback-modal-content-text-color)}.feedback-modal-element-hover{background-color:transparent;cursor:pointer;border:1px solid var(--feedback-modal-element-hover-border-color)}.feedback-modal-element-selected{background-color:transparent;border:3px solid var(--feedback-modal-element-selected-border-color) !important;box-shadow:0 0 0 2px rgba(0, 123, 255, 0.3) !important}.screenshot-preview{display:inline-block;width:32px;height:32px;overflow:hidden;border-radius:6px;margin-right:10px;box-shadow:0 2px 4px rgba(0, 0, 0, 0.1);cursor:pointer;transition:all 0.2s cubic-bezier(0.4, 0, 0.2, 1);border:2px solid rgba(255, 255, 255, 0.8)}.screenshot-preview:hover{transform:scale(1.15);box-shadow:0 4px 8px rgba(0, 0, 0, 0.15)}.screenshot-preview img{width:100%;height:100%;object-fit:cover}.screenshot-loading{display:inline-flex;align-items:center;margin-right:8px}@media screen and (min-width: 768px){.feedback-modal-content{max-width:var(--feedback-modal-content-max-width)}.feedback-modal-content.feedback-modal-content--bottom-right{bottom:var(--feedback-modal-content-position-bottom);left:initial;right:var(--feedback-modal-content-position-right);top:initial;transform:initial}.feedback-modal-content.feedback-modal-content--bottom-left{bottom:var(--feedback-modal-content-position-bottom);left:var(--feedback-modal-content-position-left);top:initial;transform:initial}.feedback-modal-content.feedback-modal-content--top-right{right:var(--feedback-modal-content-position-right);top:var(--feedback-modal-content-position-top);transform:initial}.feedback-modal-content.feedback-modal-content--top-left{left:var(--feedback-modal-content-position-left);top:var(--feedback-modal-content-position-top);transform:initial}.feedback-modal-content.feedback-modal-content--center-left{left:5px;right:auto;top:50%;transform:translateY(-50%)}.feedback-modal-content.feedback-modal-content--center-right{left:auto;right:5px;top:50%;transform:translateY(-50%)}.feedback-modal-content.feedback-modal-content--sidebar-left.feedback-modal-content--open,.feedback-modal-content.feedback-modal-content--sidebar-right.feedback-modal-content--open{transform:translateX(0)}.feedback-modal-content.feedback-modal-content--sidebar-left{max-width:var(--feedback-modal-content-sidebar-max-width);left:0;right:auto;height:100vh;top:0;transform:translateX(-100%);transition:transform 0.5s ease-in-out;border-radius:0}.feedback-modal-content.feedback-modal-content--sidebar-right{max-width:var(--feedback-modal-content-sidebar-max-width);left:auto;right:0;height:100vh;top:0;transform:translateX(100%);transition:transform 0.5s ease-in-out;border-radius:0}.feedback-modal-text textarea{height:150px;min-height:150px}.feedback-modal-content.feedback-modal-content--bottom-right{transform:translateY(20px)}.feedback-modal-content.feedback-modal-content--bottom-right.feedback-modal-content--open{transform:translateY(0)}.feedback-modal-content.feedback-modal-content--bottom-left{transform:translateY(20px)}.feedback-modal-content.feedback-modal-content--bottom-left.feedback-modal-content--open{transform:translateY(0)}.feedback-modal-content.feedback-modal-content--top-right{transform:translateY(-20px)}.feedback-modal-content.feedback-modal-content--top-right.feedback-modal-content--open{transform:translateY(0)}.feedback-modal-content.feedback-modal-content--top-left{transform:translateY(-20px)}.feedback-modal-content.feedback-modal-content--top-left.feedback-modal-content--open{transform:translateY(0)}}@keyframes feather-spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.feather-loader{animation:feather-spin 1s linear infinite;display:block}.screenshot-error-notification{position:fixed;top:20px;left:50%;transform:translateX(-50%);z-index:10001;max-width:500px;width:90%;animation:slideDown 0.3s ease-out}@keyframes slideDown{from{opacity:0;transform:translateX(-50%) translateY(-20px)}to{opacity:1;transform:translateX(-50%) translateY(0)}}.screenshot-error-content{background:#fee;border:1.5px solid #fcc;border-radius:10px;padding:14px 18px;display:flex;align-items:center;gap:12px;box-shadow:0 0 0 1px rgba(197, 48, 48, 0.05),\n 0 4px 6px rgba(0, 0, 0, 0.07),\n 0 10px 20px rgba(0, 0, 0, 0.1);color:#c53030}.screenshot-error-content svg:first-child{color:#e53e3e;flex-shrink:0}.screenshot-error-content span{flex:1;font-size:14px;line-height:1.4;font-weight:500}.error-close-btn{background:none;border:none;cursor:pointer;padding:6px;border-radius:6px;color:#c53030;flex-shrink:0;transition:all 0.2s ease;display:flex;align-items:center;justify-content:center}.error-close-btn:hover{background:rgba(197, 48, 48, 0.15);transform:scale(1.1)}.error-close-btn:active{transform:scale(0.95)}@media screen and (max-width: 768px){.feedback-modal-content{width:100vw;height:100dvh;max-width:none;border-radius:0;top:0;left:0;transform:scale(0.95);padding:24px 20px;display:flex;flex-direction:column}.feedback-modal-content--open{transform:scale(1)}.feedback-modal-buttons{display:flex;flex-direction:column;gap:12px}.feedback-modal-button--screenshot{display:none !important}.feedback-modal-button--submit{width:100%}.feedback-modal-text textarea{flex:1;min-height:120px;resize:none}}.feedback-modal-wrapper--embedded{position:relative;z-index:auto;display:block;width:100%}.feedback-modal-content--embedded{position:relative !important;left:auto !important;right:auto !important;top:auto !important;bottom:auto !important;transform:none !important;opacity:1 !important;max-width:100%;margin:0 auto;box-shadow:0 2px 8px rgba(0, 0, 0, 0.1);text-align:left}.feedback-modal-content--embedded.feedback-modal-content--open{transform:none !important}@media screen and (max-width: 768px){.feedback-modal-content--embedded{width:100%;height:auto;border-radius:var(--feedback-modal-content-border-radius);padding:24px}.feedback-modal-content--embedded .feedback-modal-button--screenshot{display:flex !important}}";
|
|
1271
|
+
// Tags allowed in the rich-text props (privacy policy, reCAPTCHA notice,
|
|
1272
|
+
// footer). These only ever need links and light inline emphasis.
|
|
1273
|
+
const ALLOWED_TAGS = new Set([
|
|
1274
|
+
'A',
|
|
1275
|
+
'B',
|
|
1276
|
+
'BR',
|
|
1277
|
+
'CODE',
|
|
1278
|
+
'EM',
|
|
1279
|
+
'I',
|
|
1280
|
+
'P',
|
|
1281
|
+
'SMALL',
|
|
1282
|
+
'SPAN',
|
|
1283
|
+
'STRONG',
|
|
1284
|
+
'U',
|
|
1285
|
+
]);
|
|
1286
|
+
// No `class`: an injected class name cannot reach the host page's CSS (this
|
|
1287
|
+
// renders inside a shadow root), but it could match the widget's own internal
|
|
1288
|
+
// styles. The default prop values do not use classes, so dropping it is free.
|
|
1289
|
+
const ALLOWED_ATTRS = new Set(['href', 'target', 'rel', 'title']);
|
|
1290
|
+
// Schemes permitted in href. Anything else, notably javascript:, is dropped.
|
|
1291
|
+
const SAFE_URL = /^(https?:|mailto:|tel:|#|\/|\.\/|\.\.\/)/i;
|
|
1292
|
+
/**
|
|
1293
|
+
* Removes anything that can execute from a fragment: script and style
|
|
1294
|
+
* elements, event-handler attributes, javascript: URLs, and any tag not on the
|
|
1295
|
+
* allowlist. Text inside a disallowed tag is kept, so stripping a wrapper does
|
|
1296
|
+
* not silently delete the words inside it.
|
|
1297
|
+
*/
|
|
1298
|
+
function scrub(root) {
|
|
1299
|
+
const elements = Array.from(root.querySelectorAll('*'));
|
|
1300
|
+
for (const el of elements) {
|
|
1301
|
+
// The node may already have been removed along with an ancestor
|
|
1302
|
+
if (!root.contains(el)) {
|
|
1303
|
+
continue;
|
|
1304
|
+
}
|
|
1305
|
+
const tag = el.tagName.toUpperCase();
|
|
1306
|
+
if (tag === 'SCRIPT' || tag === 'STYLE' || tag === 'IFRAME' || tag === 'OBJECT') {
|
|
1307
|
+
el.remove();
|
|
1308
|
+
continue;
|
|
1309
|
+
}
|
|
1310
|
+
if (!ALLOWED_TAGS.has(tag)) {
|
|
1311
|
+
// Unwrap: keep the text, drop the element
|
|
1312
|
+
el.replaceWith(...Array.from(el.childNodes));
|
|
1313
|
+
continue;
|
|
1314
|
+
}
|
|
1315
|
+
for (const attr of Array.from(el.attributes)) {
|
|
1316
|
+
const name = attr.name.toLowerCase();
|
|
1317
|
+
if (name.startsWith('on') || !ALLOWED_ATTRS.has(name)) {
|
|
1318
|
+
el.removeAttribute(attr.name);
|
|
1319
|
+
continue;
|
|
1320
|
+
}
|
|
1321
|
+
if (name === 'href' && !SAFE_URL.test(attr.value.trim())) {
|
|
1322
|
+
el.removeAttribute(attr.name);
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
// Links opening a new tab must not hand the opener over
|
|
1326
|
+
if (tag === 'A' && el.getAttribute('target') === '_blank') {
|
|
1327
|
+
el.setAttribute('rel', 'noopener noreferrer');
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
/**
|
|
1332
|
+
* Sanitizes a fragment of HTML for use with innerHTML.
|
|
1333
|
+
*
|
|
1334
|
+
* Prefers the browser's native Element.setHTML() where available, which parses
|
|
1335
|
+
* without executing. Otherwise parses in an inert document (no scripts run, no
|
|
1336
|
+
* images or other subresources are fetched) and scrubs the result.
|
|
1337
|
+
*/
|
|
1338
|
+
function sanitizeHtml(html) {
|
|
1339
|
+
if (!html) {
|
|
1340
|
+
return '';
|
|
1341
|
+
}
|
|
1342
|
+
const template = document.createElement('template');
|
|
1343
|
+
// Native sanitizer, when the browser has it
|
|
1344
|
+
const withSetHtml = template;
|
|
1345
|
+
if (typeof withSetHtml.setHTML === 'function') {
|
|
1346
|
+
try {
|
|
1347
|
+
withSetHtml.setHTML(html);
|
|
1348
|
+
scrub(template.content);
|
|
1349
|
+
return template.innerHTML;
|
|
1350
|
+
}
|
|
1351
|
+
catch (_a) {
|
|
1352
|
+
// Fall through to the manual path
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
// Inert parsing: content inside <template> is not rendered, so scripts do not
|
|
1356
|
+
// run and no subresource requests are made while we clean it up.
|
|
1357
|
+
template.innerHTML = html;
|
|
1358
|
+
scrub(template.content);
|
|
1359
|
+
return template.innerHTML;
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
const feedbackModalCss = ".text-center{flex-grow:1;text-align:center}.feedback-modal-wrapper *{font-family:var(--feedback-font-family)}.feedback-modal-wrapper--custom-font *{font-family:inherit}.feedback-modal-wrapper{position:absolute;z-index:var(--feedback-modal-modal-wrapper-z-index)}.feedback-overlay{background-color:var(--feedback-modal-screenshot-bg-color);height:100%;left:0;opacity:0;position:fixed;top:0;width:100%;z-index:var(--feedback-modal-screnshot-z-index);transition:opacity 0.25s cubic-bezier(0.4, 0, 0.2, 1);backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px)}.feedback-overlay--visible{opacity:1}.feedback-modal{display:inline-block;position:relative}.feedback-modal-content{background-color:var(--feedback-modal-content-bg-color);border:1px solid rgba(0, 0, 0, 0.08);border-radius:var(--feedback-modal-content-border-radius);box-shadow:0px 0px 0px 1px rgba(0, 0, 0, 0.02),\n 0px 2px 4px rgba(0, 0, 0, 0.04),\n 0px 8px 16px rgba(0, 0, 0, 0.06),\n 0px 16px 32px rgba(0, 0, 0, 0.04);box-sizing:border-box;color:var(--feedback-modal-content-text-color);display:flex;flex-direction:column;left:50%;max-width:90%;padding:24px;position:fixed;top:50%;transform:translate(-50%, -50%) scale(0.96);opacity:0;width:100%;z-index:var(--feedback-modal-content-z-index);transition:transform 0.25s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.25s cubic-bezier(0.4, 0, 0.2, 1)}.feedback-modal-content--open{transform:translate(-50%, -50%) scale(1);opacity:1}.feedback-modal-content:focus{outline:none}.feedback-modal-header{color:var(--feedback-modal-header-text-color);display:flex;font-size:var(--feedback-modal-header-font-size);font-weight:var(--feedback-modal-header-font-weight);justify-content:space-between;align-items:flex-start;margin-bottom:24px;letter-spacing:-0.01em}.feedback-modal-header-content{flex:1}.feedback-modal-header-text{display:flex;flex-direction:column;gap:6px}.feedback-modal-title{display:block}.feedback-modal-powered-by{display:block;font-size:13px;font-weight:400;color:var(--feedback-color-gray-500);line-height:1.4}.feedback-modal-powered-by a{color:var(--feedback-modal-header-text-color);text-decoration:none;font-weight:500}.feedback-modal-powered-by a:hover{text-decoration:underline}.feedback-modal-header--no-content{margin-bottom:0}.feedback-modal-rating-buttons{width:100%;margin-bottom:24px;display:flex;gap:4px}.feedback-modal-rating-button{padding:0;background-color:transparent;border:transparent;margin-right:8px;cursor:pointer}.feedback-modal-rating-buttons--stars .feedback-modal-rating-button{transition:transform 0.15s ease, opacity 0.15s ease}.feedback-modal-rating-buttons--stars .feedback-modal-rating-button:hover{transform:scale(1.1)}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button{border:1.5px solid var(--feedback-modal-button-border-color);border-radius:var(--feedback-modal-button-border-radius);color:var(--feedback-modal-button-text-color);font-size:var(--feedback-modal-button-font-size);font-weight:500;margin-right:12px;justify-content:center;padding:10px 16px;transition:all 0.2s cubic-bezier(0.4, 0, 0.2, 1);box-shadow:0 1px 2px rgba(0, 0, 0, 0.04)}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--positive{background-color:var(--feedback-modal-rating-button-positive-bg-color);border-color:var(--feedback-modal-rating-button-positive-border-color)}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--negative{background-color:var(--feedback-modal-rating-button-negative-bg-color);border-color:var(--feedback-modal-rating-button-negative-border-color)}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button:hover{transform:translateY(-2px);box-shadow:0 4px 8px rgba(0, 0, 0, 0.12)}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button:hover,.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--selected{background-color:var(--feedback-modal-button-bg-color-active);border:1.5px solid var(--feedback-modal-button-border-color-active);color:var(--feedback-modal-button-text-color-active)}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button:hover svg,.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--selected svg{stroke:var(--feedback-modal-rating-button-selected-color)}.feedback-modal-rating-buttons svg{stroke:var(--feedback-modal-rating-button-color);cursor:pointer}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--positive svg{stroke:var(--feedback-modal-rating-button-positive-color)}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--negative svg{stroke:var(--feedback-modal-rating-button-negative-color)}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--positive:hover,.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--positive.feedback-modal-rating-button--selected{background-color:var(--feedback-modal-rating-button-positive-selected-bg-color);border-color:var(--feedback-modal-rating-button-positive-selected-border-color)}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--negative:hover,.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--negative.feedback-modal-rating-button--selected{background-color:var(--feedback-modal-rating-button-negative-selected-bg-color);border-color:var(--feedback-modal-rating-button-negative-selected-border-color)}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--positive:hover svg,.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--positive.feedback-modal-rating-button--selected svg{stroke:var(--feedback-modal-rating-button-positive-selected-color)}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--negative:hover svg,.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button--negative.feedback-modal-rating-button--selected svg{stroke:var(--feedback-modal-rating-button-negative-selected-color)}.feedback-modal-rating-buttons--stars .feedback-modal-rating-button--selected svg{fill:var(--feedback-modal-rating-button-stars-selected-color);stroke:var(--feedback-modal-rating-button-stars-selected-color)}.feedback-modal-text textarea{background-color:var(--feedback-modal-input-bg-color);border:1.5px solid var(--feedback-modal-input-border-color);border-radius:var(--feedback-modal-input-border-radius);box-sizing:border-box;color:var(--feedback-modal-input-text-color);font-size:var(--feedback-modal-input-font-size);margin-bottom:20px;height:100px;min-height:100px;padding:12px;resize:vertical;width:100%;transition:border-color 0.2s ease, box-shadow 0.2s ease;line-height:1.5}.feedback-modal-text textarea:hover{border-color:var(--feedback-modal-input-border-color-hover)}.feedback-modal-email input{background-color:var(--feedback-modal-input-bg-color);border:1.5px solid var(--feedback-modal-input-border-color);border-radius:var(--feedback-modal-input-border-radius);box-sizing:border-box;color:var(--feedback-modal-input-text-color);font-size:var(--feedback-modal-input-font-size);margin-bottom:20px;height:44px;padding:12px;width:100%;transition:border-color 0.2s ease, box-shadow 0.2s ease}.feedback-modal-email input:hover{border-color:var(--feedback-modal-input-border-color-hover)}.feedback-modal-text textarea:-webkit-autofill,.feedback-modal-email input:-webkit-autofill,.feedback-modal-email input:-webkit-autofill:hover,.feedback-modal-email input:-webkit-autofill:focus,.feedback-modal-email input:-webkit-autofill:active{-webkit-box-shadow:0 0 0 1000px var(--feedback-modal-input-bg-color) inset !important;-webkit-text-fill-color:var(--feedback-modal-input-text-color) !important;transition:background-color 5000s ease-in-out 0s}.feedback-modal-privacy{font-size:var(--feedback-modal-input-font-size);margin-bottom:20px}.feedback-modal-text textarea:focus,.feedback-modal-email input:focus{border:1.5px solid var(--feedback-modal-input-border-color-focused);outline:none;box-shadow:0 0 0 3px rgba(59, 130, 246, 0.1)}.feedback-modal-buttons{display:flex;flex-direction:column}.feedback-modal-buttons .feedback-modal-button{margin-bottom:20px}.feedback-modal-button{align-items:center;background-color:transparent;border:1.5px solid var(--feedback-modal-button-border-color);border-radius:var(--feedback-modal-button-border-radius);color:var(--feedback-modal-button-text-color);cursor:pointer;display:flex;font-size:var(--feedback-modal-button-font-size);font-weight:500;justify-content:center;min-height:44px;padding:10px 16px;transition:all 0.2s cubic-bezier(0.4, 0, 0.2, 1);box-shadow:0 1px 2px rgba(0, 0, 0, 0.04)}.feedback-modal-button svg{margin-right:6px}.feedback-modal-button path{fill:var(--feedback-modal-button-icon-color)}.feedback-modal-button:hover path,.feedback-modal-button--active path{fill:var(--feedback-modal-button-icon-color-active)}.feedback-modal-button--submit{background-color:var(--feedback-modal-button-submit-bg-color);border:1.5px solid var(--feedback-modal-button-border-color-active);color:var(--feedback-modal-button-submit-text-color);box-shadow:0 2px 4px rgba(0, 0, 0, 0.1)}.feedback-modal-button:hover,.feedback-modal-button--active{background-color:var(--feedback-modal-button-bg-color-active);border:1.5px solid var(--feedback-modal-button-border-color-active);color:var(--feedback-modal-button-text-color-active);transform:translateY(-1px);box-shadow:0 4px 8px rgba(0, 0, 0, 0.12)}.feedback-modal-button--submit:hover{background-color:var(--feedback-modal-button-submit-bg-color-hover);border:1.5px solid var(--feedback-modal-button-submit-border-color-hover);color:var(--feedback-modal-button-submit-text-color-hover);transform:translateY(-1px);box-shadow:0 6px 12px rgba(0, 0, 0, 0.15)}.feedback-modal-button--submit:active{transform:translateY(0);box-shadow:0 2px 4px rgba(0, 0, 0, 0.1)}.feedback-modal-input-heading{display:block;font-size:14px;font-weight:500;padding-bottom:12px;color:var(--feedback-modal-header-text-color);letter-spacing:-0.01em}.feedback-modal-footer{font-size:12px;text-align:center}.feedback-modal-footer a{color:var(--feedback-modal-footer-link);font-weight:500;text-decoration:none}.feedback-logo,.feedback-footer-text{display:block;text-align:center;margin-top:5px}.feedback-footer-text{margin-top:10px;line-height:1.5}.feedback-footer-combined{display:block;text-align:center;font-size:11px;color:#666;line-height:1.5}.feedback-footer-combined a{color:#666;text-decoration:underline}.feedback-recaptcha-notice{display:block;text-align:center;margin-bottom:10px;font-size:11px;color:#666;line-height:1.4}.feedback-recaptcha-notice a{color:#666;text-decoration:underline}.feedback-modal-close{background-color:var(--feedback-modal-close-bg-color);border:0;border-radius:6px;cursor:pointer;height:32px;width:32px;margin-left:auto;padding:0;display:flex;align-items:center;justify-content:center;transition:all 0.2s ease}.feedback-modal-close:hover{background-color:rgba(0, 0, 0, 0.06);transform:scale(1.05)}.feedback-modal-close:active{transform:scale(0.95)}.feedback-modal-close svg{stroke:var(--feedback-modal-close-color)}.feedback-modal-screenshot{background-color:var(--feedback-modal-screenshot-bg-color);height:100%;left:0;position:fixed;top:0;width:100%;z-index:var(--feedback-modal-screnshot-z-index)}.feedback-modal-screenshot-header{align-items:center;background-color:var(--feedback-modal-screenshot-header-bg-color);border-radius:var(--feedback-modal-content-border-radius);box-shadow:0px 1px 2px 0px rgba(60, 64, 67, .30), 0px 2px 6px 2px rgba(60, 64, 67, .15);box-sizing:border-box;color:var(--feedback-modal-screenshot-header-text-color);cursor:pointer;display:flex;left:50%;top:20px;transform:translateX(-50%);padding:10px;position:fixed;width:max-content;z-index:var(--feedback-modal-screenshot-header-z-index)}.feedback-modal-screenshot-close{height:24px;padding-left:10px;width:24px}.feedback-modal-screenshot-close svg{stroke:var(--feedback-modal-close-color)}.feedback-modal-message{font-size:var(--feedback-modal-message-font-size);margin:0;line-height:1.6;color:var(--feedback-modal-content-text-color)}.feedback-modal-element-hover{background-color:transparent;cursor:pointer;border:1px solid var(--feedback-modal-element-hover-border-color)}.feedback-modal-element-selected{background-color:transparent;border:3px solid var(--feedback-modal-element-selected-border-color) !important;box-shadow:0 0 0 2px rgba(0, 123, 255, 0.3) !important}.screenshot-preview{display:inline-block;width:32px;height:32px;overflow:hidden;border-radius:6px;margin-right:10px;box-shadow:0 2px 4px rgba(0, 0, 0, 0.1);cursor:pointer;transition:all 0.2s cubic-bezier(0.4, 0, 0.2, 1);border:2px solid rgba(255, 255, 255, 0.8)}.screenshot-preview:hover{transform:scale(1.15);box-shadow:0 4px 8px rgba(0, 0, 0, 0.15)}.screenshot-preview img{width:100%;height:100%;object-fit:cover}.screenshot-loading{display:inline-flex;align-items:center;margin-right:8px}@media screen and (min-width: 768px){.feedback-modal-content{max-width:var(--feedback-modal-content-max-width)}.feedback-modal-content.feedback-modal-content--bottom-right{bottom:var(--feedback-modal-content-position-bottom);left:initial;right:var(--feedback-modal-content-position-right);top:initial;transform:initial}.feedback-modal-content.feedback-modal-content--bottom-left{bottom:var(--feedback-modal-content-position-bottom);left:var(--feedback-modal-content-position-left);top:initial;transform:initial}.feedback-modal-content.feedback-modal-content--top-right{right:var(--feedback-modal-content-position-right);top:var(--feedback-modal-content-position-top);transform:initial}.feedback-modal-content.feedback-modal-content--top-left{left:var(--feedback-modal-content-position-left);top:var(--feedback-modal-content-position-top);transform:initial}.feedback-modal-content.feedback-modal-content--center-left{left:5px;right:auto;top:50%;transform:translateY(-50%)}.feedback-modal-content.feedback-modal-content--center-right{left:auto;right:5px;top:50%;transform:translateY(-50%)}.feedback-modal-content.feedback-modal-content--sidebar-left.feedback-modal-content--open,.feedback-modal-content.feedback-modal-content--sidebar-right.feedback-modal-content--open{transform:translateX(0)}.feedback-modal-content.feedback-modal-content--sidebar-left{max-width:var(--feedback-modal-content-sidebar-max-width);left:0;right:auto;height:100vh;top:0;transform:translateX(-100%);transition:transform 0.5s ease-in-out;border-radius:0}.feedback-modal-content.feedback-modal-content--sidebar-right{max-width:var(--feedback-modal-content-sidebar-max-width);left:auto;right:0;height:100vh;top:0;transform:translateX(100%);transition:transform 0.5s ease-in-out;border-radius:0}.feedback-modal-text textarea{height:150px;min-height:150px}.feedback-modal-content.feedback-modal-content--bottom-right{transform:translateY(20px)}.feedback-modal-content.feedback-modal-content--bottom-right.feedback-modal-content--open{transform:translateY(0)}.feedback-modal-content.feedback-modal-content--bottom-left{transform:translateY(20px)}.feedback-modal-content.feedback-modal-content--bottom-left.feedback-modal-content--open{transform:translateY(0)}.feedback-modal-content.feedback-modal-content--top-right{transform:translateY(-20px)}.feedback-modal-content.feedback-modal-content--top-right.feedback-modal-content--open{transform:translateY(0)}.feedback-modal-content.feedback-modal-content--top-left{transform:translateY(-20px)}.feedback-modal-content.feedback-modal-content--top-left.feedback-modal-content--open{transform:translateY(0)}}@keyframes feather-spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.feather-loader{animation:feather-spin 1s linear infinite;display:block}.screenshot-error-notification{position:fixed;top:20px;left:50%;transform:translateX(-50%);z-index:10001;max-width:500px;width:90%;animation:slideDown 0.3s ease-out}@keyframes slideDown{from{opacity:0;transform:translateX(-50%) translateY(-20px)}to{opacity:1;transform:translateX(-50%) translateY(0)}}.screenshot-error-content{background:#fee;border:1.5px solid #fcc;border-radius:10px;padding:14px 18px;display:flex;align-items:center;gap:12px;box-shadow:0 0 0 1px rgba(197, 48, 48, 0.05),\n 0 4px 6px rgba(0, 0, 0, 0.07),\n 0 10px 20px rgba(0, 0, 0, 0.1);color:#c53030}.screenshot-error-content svg:first-child{color:#e53e3e;flex-shrink:0}.screenshot-error-content span{flex:1;font-size:14px;line-height:1.4;font-weight:500}.error-close-btn{background:none;border:none;cursor:pointer;padding:6px;border-radius:6px;color:#c53030;flex-shrink:0;transition:all 0.2s ease;display:flex;align-items:center;justify-content:center}.error-close-btn:hover{background:rgba(197, 48, 48, 0.15);transform:scale(1.1)}.error-close-btn:active{transform:scale(0.95)}@media screen and (max-width: 768px){.feedback-modal-content{width:100vw;height:100dvh;max-width:none;border-radius:0;top:0;left:0;transform:scale(0.95);padding:24px 20px;display:flex;flex-direction:column}.feedback-modal-content--open{transform:scale(1)}.feedback-modal-buttons{display:flex;flex-direction:column;gap:12px}.feedback-modal-button--screenshot{display:none !important}.feedback-modal-button--submit{width:100%}.feedback-modal-text textarea{flex:1;min-height:120px;resize:none}}.feedback-modal-wrapper--embedded{position:relative;z-index:auto;display:block;width:100%}.feedback-modal-content--embedded{position:relative !important;left:auto !important;right:auto !important;top:auto !important;bottom:auto !important;transform:none !important;opacity:1 !important;max-width:100%;margin:0 auto;box-shadow:0 2px 8px rgba(0, 0, 0, 0.1);text-align:left}.feedback-modal-content--embedded.feedback-modal-content--open{transform:none !important}@media screen and (max-width: 768px){.feedback-modal-content--embedded{width:100%;height:auto;border-radius:var(--feedback-modal-content-border-radius);padding:24px}.feedback-modal-content--embedded .feedback-modal-button--screenshot{display:flex !important}}";
|
|
1136
1363
|
|
|
1137
1364
|
const FeedbackModal = class {
|
|
1138
1365
|
constructor(hostRef) {
|
|
@@ -1141,6 +1368,24 @@ const FeedbackModal = class {
|
|
|
1141
1368
|
this.feedbackError = index.createEvent(this, "feedbackError", 7);
|
|
1142
1369
|
// Track the history entry pushed while the modal is open
|
|
1143
1370
|
this.historyEntryPushed = false;
|
|
1371
|
+
// Element that had focus before the modal opened, so it can be restored on close
|
|
1372
|
+
this.previouslyFocused = null;
|
|
1373
|
+
this.handleKeyDown = (event) => {
|
|
1374
|
+
// The canvas editor renders its own overlay and handles its own keys
|
|
1375
|
+
if (!this.showModal || this.embedded || this.showCanvasEditor) {
|
|
1376
|
+
return;
|
|
1377
|
+
}
|
|
1378
|
+
if (event.key === 'Escape' && this.escapeClose) {
|
|
1379
|
+
event.preventDefault();
|
|
1380
|
+
// Escape is an accidental-dismissal path like clicking outside, so keep
|
|
1381
|
+
// whatever the user already typed
|
|
1382
|
+
this.doClose(!this.formSuccess);
|
|
1383
|
+
return;
|
|
1384
|
+
}
|
|
1385
|
+
if (event.key === 'Tab') {
|
|
1386
|
+
this.trapFocus(event);
|
|
1387
|
+
}
|
|
1388
|
+
};
|
|
1144
1389
|
this.handlePopState = () => {
|
|
1145
1390
|
// Back button pressed while the modal is open: close it and consume our entry
|
|
1146
1391
|
if (this.historyEntryPushed) {
|
|
@@ -1184,12 +1429,16 @@ const FeedbackModal = class {
|
|
|
1184
1429
|
}
|
|
1185
1430
|
}
|
|
1186
1431
|
const body = Object.assign({ url: window.location.href, message: this.formMessage, email: this.formEmail, project: this.project, screenshot: this.encodedScreenshot, rating: this.selectedRating, ratingMode: this.ratingMode, metadata: this.metadata, verification: this.formVerification, session: localStorage.getItem('pushfeedback_sessionid') || '' }, (recaptchaToken && { recaptchaToken }));
|
|
1187
|
-
const
|
|
1432
|
+
const headers = {
|
|
1433
|
+
'Content-Type': 'application/json',
|
|
1434
|
+
};
|
|
1435
|
+
if (this.apiKey) {
|
|
1436
|
+
headers['Authorization'] = `Api-Key ${this.apiKey}`;
|
|
1437
|
+
}
|
|
1438
|
+
const res = await fetch('https://app.pushfeedback.com/api/v1/feedback/', {
|
|
1188
1439
|
method: 'POST',
|
|
1189
1440
|
body: JSON.stringify(body),
|
|
1190
|
-
headers
|
|
1191
|
-
'Content-Type': 'application/json',
|
|
1192
|
-
},
|
|
1441
|
+
headers,
|
|
1193
1442
|
});
|
|
1194
1443
|
if (res.status === 201) {
|
|
1195
1444
|
const feedback_with_id = Object.assign(Object.assign({}, body), { id: await res.json() });
|
|
@@ -1262,8 +1511,15 @@ const FeedbackModal = class {
|
|
|
1262
1511
|
this.encodedScreenshot = null;
|
|
1263
1512
|
this.formMessage = '';
|
|
1264
1513
|
this.formEmail = '';
|
|
1514
|
+
// The rating and the privacy checkbox are part of the same in-progress
|
|
1515
|
+
// input. Leaving the rating set meant reopening the modal showed the
|
|
1516
|
+
// previous session's choice still highlighted, and submitting then sent
|
|
1517
|
+
// that stale rating instead of one chosen for this submission.
|
|
1518
|
+
this.selectedRating = this.initialRating();
|
|
1519
|
+
this.isPrivacyChecked = false;
|
|
1265
1520
|
}
|
|
1266
1521
|
this.resetOverflow();
|
|
1522
|
+
this.restoreFocus();
|
|
1267
1523
|
}, 200);
|
|
1268
1524
|
};
|
|
1269
1525
|
// Handle screenshot events from canvas editor
|
|
@@ -1273,12 +1529,16 @@ const FeedbackModal = class {
|
|
|
1273
1529
|
this.takingScreenshot = false;
|
|
1274
1530
|
this.showCanvasEditor = false;
|
|
1275
1531
|
this.autoStartCapture = false;
|
|
1532
|
+
this.focusModal();
|
|
1276
1533
|
};
|
|
1277
1534
|
this.handleScreenshotCancelled = () => {
|
|
1278
1535
|
this.showModal = true;
|
|
1279
1536
|
this.takingScreenshot = false;
|
|
1280
1537
|
this.showCanvasEditor = false;
|
|
1281
1538
|
this.autoStartCapture = false;
|
|
1539
|
+
// Focus was inside the editor that just went away, so bring it back to the
|
|
1540
|
+
// form rather than letting it fall to the top of the page
|
|
1541
|
+
this.focusModal();
|
|
1282
1542
|
};
|
|
1283
1543
|
this.handleScreenshotError = (event) => {
|
|
1284
1544
|
console.error('Screenshot error:', event.detail.error);
|
|
@@ -1290,6 +1550,7 @@ const FeedbackModal = class {
|
|
|
1290
1550
|
this.takingScreenshot = false;
|
|
1291
1551
|
this.showCanvasEditor = false;
|
|
1292
1552
|
this.autoStartCapture = false;
|
|
1553
|
+
this.focusModal();
|
|
1293
1554
|
// Auto-hide error after 8 seconds
|
|
1294
1555
|
setTimeout(() => {
|
|
1295
1556
|
this.showScreenshotError = false;
|
|
@@ -1331,6 +1592,7 @@ const FeedbackModal = class {
|
|
|
1331
1592
|
this.screenshotError = '';
|
|
1332
1593
|
this.showCanvasEditor = false;
|
|
1333
1594
|
this.autoStartCapture = false;
|
|
1595
|
+
this.apiKey = '';
|
|
1334
1596
|
this.customFont = false;
|
|
1335
1597
|
this.emailAddress = '';
|
|
1336
1598
|
this.hideEmail = false;
|
|
@@ -1350,8 +1612,10 @@ const FeedbackModal = class {
|
|
|
1350
1612
|
this.embedded = false;
|
|
1351
1613
|
this.clickOutsideClose = true;
|
|
1352
1614
|
this.historyClose = true;
|
|
1615
|
+
this.escapeClose = true;
|
|
1353
1616
|
this.emailPlaceholder = 'Email address (optional)';
|
|
1354
1617
|
this.errorMessage = 'Please try again later.';
|
|
1618
|
+
this.errorMessage401 = 'This project requires a valid API key to submit feedback. Check the api-key attribute.';
|
|
1355
1619
|
this.errorMessage403 = 'The request URL does not match the one defined in PushFeedback for this project.';
|
|
1356
1620
|
this.errorMessage404 = 'We could not find the provided project ID in PushFeedback.';
|
|
1357
1621
|
this.messagePlaceholder = 'Share your thoughts...';
|
|
@@ -1364,6 +1628,10 @@ const FeedbackModal = class {
|
|
|
1364
1628
|
this.ratingPlaceholder = 'Was this page helpful?';
|
|
1365
1629
|
this.ratingStarsPlaceholder = 'How would you rate this page?';
|
|
1366
1630
|
this.sendButtonText = 'Send';
|
|
1631
|
+
this.closeButtonLabel = 'Close feedback form';
|
|
1632
|
+
this.ratingPositiveLabel = 'Yes';
|
|
1633
|
+
this.ratingNegativeLabel = 'No';
|
|
1634
|
+
this.ratingStarLabel = 'Rate {rating} out of 5';
|
|
1367
1635
|
this.successMessage = '';
|
|
1368
1636
|
this.recaptchaText = 'This form is protected by reCAPTCHA and the Google <a href="https://policies.google.com/privacy" target="_blank" rel="noopener noreferrer">Privacy Policy</a> and <a href="https://policies.google.com/terms" target="_blank" rel="noopener noreferrer">Terms of Service</a> apply.';
|
|
1369
1637
|
this.screenshotAttachedText = 'Screenshot attached';
|
|
@@ -1386,20 +1654,96 @@ const FeedbackModal = class {
|
|
|
1386
1654
|
}
|
|
1387
1655
|
connectedCallback() {
|
|
1388
1656
|
window.addEventListener('popstate', this.handlePopState);
|
|
1657
|
+
document.addEventListener('keydown', this.handleKeyDown);
|
|
1389
1658
|
}
|
|
1390
1659
|
disconnectedCallback() {
|
|
1391
1660
|
window.removeEventListener('popstate', this.handlePopState);
|
|
1661
|
+
document.removeEventListener('keydown', this.handleKeyDown);
|
|
1662
|
+
}
|
|
1663
|
+
// Keeps Tab cycling inside the dialog while it is open
|
|
1664
|
+
trapFocus(event) {
|
|
1665
|
+
var _a, _b;
|
|
1666
|
+
const focusable = this.getFocusableElements();
|
|
1667
|
+
if (focusable.length === 0) {
|
|
1668
|
+
return;
|
|
1669
|
+
}
|
|
1670
|
+
const first = focusable[0];
|
|
1671
|
+
const last = focusable[focusable.length - 1];
|
|
1672
|
+
const active = (_a = this.modalContent) === null || _a === void 0 ? void 0 : _a.getRootNode();
|
|
1673
|
+
const current = active === null || active === void 0 ? void 0 : active.activeElement;
|
|
1674
|
+
if (current === this.modalContent) {
|
|
1675
|
+
// Focus is on the dialog container itself (where it lands on open).
|
|
1676
|
+
// Tab falls through to the first control natively; Shift+Tab would
|
|
1677
|
+
// escape the dialog, so wrap it to the last one.
|
|
1678
|
+
if (event.shiftKey) {
|
|
1679
|
+
event.preventDefault();
|
|
1680
|
+
last.focus();
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
else if (event.shiftKey && current === first) {
|
|
1684
|
+
event.preventDefault();
|
|
1685
|
+
last.focus();
|
|
1686
|
+
}
|
|
1687
|
+
else if (!event.shiftKey && current === last) {
|
|
1688
|
+
event.preventDefault();
|
|
1689
|
+
first.focus();
|
|
1690
|
+
}
|
|
1691
|
+
else if (!current || !((_b = this.modalContent) === null || _b === void 0 ? void 0 : _b.contains(current))) {
|
|
1692
|
+
// Focus escaped the dialog (or never entered it) - pull it back in
|
|
1693
|
+
event.preventDefault();
|
|
1694
|
+
first.focus();
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
// offsetParent is not usable here: it is null for `position: fixed` elements
|
|
1698
|
+
// even when they are perfectly visible, and it is non-null for
|
|
1699
|
+
// `visibility: hidden` ones, which cannot be focused. getClientRects covers
|
|
1700
|
+
// display:none and detached nodes, so visibility is the only extra check.
|
|
1701
|
+
isFocusable(el) {
|
|
1702
|
+
if (el.hidden || el.getClientRects().length === 0) {
|
|
1703
|
+
return false;
|
|
1704
|
+
}
|
|
1705
|
+
return getComputedStyle(el).visibility !== 'hidden';
|
|
1706
|
+
}
|
|
1707
|
+
getFocusableElements() {
|
|
1708
|
+
if (!this.modalContent) {
|
|
1709
|
+
return [];
|
|
1710
|
+
}
|
|
1711
|
+
const selector = 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
|
1712
|
+
return Array.from(this.modalContent.querySelectorAll(selector)).filter((el) => this.isFocusable(el));
|
|
1713
|
+
}
|
|
1714
|
+
// Moves focus to the dialog container once it has rendered, so screen readers
|
|
1715
|
+
// announce the dialog title rather than jumping straight to the first control
|
|
1716
|
+
focusModal() {
|
|
1717
|
+
requestAnimationFrame(() => {
|
|
1718
|
+
if (this.modalContent) {
|
|
1719
|
+
this.modalContent.focus();
|
|
1720
|
+
}
|
|
1721
|
+
});
|
|
1722
|
+
}
|
|
1723
|
+
restoreFocus() {
|
|
1724
|
+
const target = this.previouslyFocused;
|
|
1725
|
+
this.previouslyFocused = null;
|
|
1726
|
+
if (target && typeof target.focus === 'function' && target.isConnected) {
|
|
1727
|
+
target.focus();
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
// The rating the widget starts from. Closing restores this rather than
|
|
1731
|
+
// blanking to neutral, so a consumer's configured `rating` default survives
|
|
1732
|
+
// the modal being closed and reopened.
|
|
1733
|
+
initialRating() {
|
|
1734
|
+
if (this.ratingMode == 'thumbs' && this.rating == 0) {
|
|
1735
|
+
return 5;
|
|
1736
|
+
}
|
|
1737
|
+
if (this.rating) {
|
|
1738
|
+
return this.rating;
|
|
1739
|
+
}
|
|
1740
|
+
return -1;
|
|
1392
1741
|
}
|
|
1393
1742
|
componentWillLoad() {
|
|
1394
1743
|
if (this.fetchData)
|
|
1395
1744
|
this.fetchProjectData();
|
|
1396
1745
|
this.formEmail = this.emailAddress;
|
|
1397
|
-
|
|
1398
|
-
this.selectedRating = this.rating;
|
|
1399
|
-
}
|
|
1400
|
-
if (this.ratingMode == 'thumbs' && this.rating == 0) {
|
|
1401
|
-
this.selectedRating = 5;
|
|
1402
|
-
}
|
|
1746
|
+
this.selectedRating = this.initialRating();
|
|
1403
1747
|
// Automatically show modal when embedded
|
|
1404
1748
|
if (this.embedded) {
|
|
1405
1749
|
this.showModal = true;
|
|
@@ -1407,7 +1751,9 @@ const FeedbackModal = class {
|
|
|
1407
1751
|
}
|
|
1408
1752
|
async fetchProjectData() {
|
|
1409
1753
|
try {
|
|
1410
|
-
|
|
1754
|
+
// Public config endpoint: never send the API key here, it is only
|
|
1755
|
+
// needed (and only allowed to travel) on the feedback POST
|
|
1756
|
+
const response = await fetch('https://app.pushfeedback.com/api/v1/projects/' + this.project + '/');
|
|
1411
1757
|
const data = await response.json();
|
|
1412
1758
|
this.whitelabel = data.whitelabel;
|
|
1413
1759
|
this.recaptchaEnabled = data.recaptcha_enabled || false;
|
|
@@ -1496,28 +1842,28 @@ const FeedbackModal = class {
|
|
|
1496
1842
|
this.selectedRating = newRating;
|
|
1497
1843
|
}
|
|
1498
1844
|
render() {
|
|
1499
|
-
return (index.h("div", { class: `feedback-modal-wrapper ${this.customFont ? 'feedback-modal-wrapper--custom-font' : ''} ${this.embedded ? 'feedback-modal-wrapper--embedded' : ''}`, part: "wrapper" }, this.showCanvasEditor && (index.h("canvas-editor", { ref: (el) => (this.canvasEditorRef = el), exportparts: "overlay: canvas-editor-overlay, modal: canvas-editor-modal, header: canvas-editor-header, title: canvas-editor-title, toolbar: canvas-editor-toolbar, tool-button: canvas-editor-tool-button, cancel-button: canvas-editor-cancel-button, save-button: canvas-editor-save-button, content: canvas-editor-content, canvas: canvas-editor-canvas", "canvas-editor-title": this.screenshotEditorTitle, "canvas-editor-cancel-text": this.screenshotEditorCancelText, "canvas-editor-save-text": this.screenshotEditorSaveText, "screenshot-taking-text": this.screenshotTakingText, "screenshot-attached-text": this.screenshotAttachedText, "screenshot-button-text": this.screenshotButtonText, "auto-start-screenshot": this.autoStartCapture, "existing-screenshot": this.encodedScreenshot || '', "edit-text-button-text": this.screenshotEditTextButtonText, "size-label-text": this.screenshotSizeLabelText, "border-label-text": this.screenshotBorderLabelText, "edit-text-prompt-text": this.screenshotEditTextPromptText, "screenshot-error-general": this.screenshotErrorGeneral, "screenshot-error-permission": this.screenshotErrorPermission, "screenshot-error-not-supported": this.screenshotErrorNotSupported, "screenshot-error-not-found": this.screenshotErrorNotFound, "screenshot-error-cancelled": this.screenshotErrorCancelled, "screenshot-error-browser-not-supported": this.screenshotErrorBrowserNotSupported, "screenshot-error-unexpected": this.screenshotErrorUnexpected, onScreenshotReady: this.handleScreenshotReady, onScreenshotCancelled: this.handleScreenshotCancelled, onScreenshotFailed: this.handleScreenshotError })), this.showScreenshotError && (index.h("div", { class: "screenshot-error-notification" }, index.h("div", { class: "screenshot-error-content" }, index.h("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, index.h("circle", { cx: "12", cy: "12", r: "10" }), index.h("line", { x1: "15", y1: "9", x2: "9", y2: "15" }), index.h("line", { x1: "9", y1: "9", x2: "15", y2: "15" })), index.h("span", null, this.screenshotError), index.h("button", { class: "error-close-btn", onClick: () => (this.showScreenshotError = false), title: "Close" }, index.h("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, index.h("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), index.h("line", { x1: "6", y1: "6", x2: "18", y2: "18" })))))), this.showModal && !this.embedded && (index.h("div", { class: `feedback-overlay ${this.isAnimating ? 'feedback-overlay--visible' : ''}`, part: "overlay", onClick: this.clickOutsideClose ? this.handleOverlayClick : undefined })), this.showModal && (index.h("div", { class: `feedback-modal-content feedback-modal-content--${this.modalPosition} ${this.isAnimating ? 'feedback-modal-content--open' : ''} ${this.embedded ? 'feedback-modal-content--embedded' : ''}`, part: "modal", ref: (el) => (this.modalContent = el) }, index.h("div", { class: `feedback-modal-header ${(this.formSuccess && !this.successMessage) || (this.formError && !this.errorMessage)
|
|
1845
|
+
return (index.h("div", { class: `feedback-modal-wrapper ${this.customFont ? 'feedback-modal-wrapper--custom-font' : ''} ${this.embedded ? 'feedback-modal-wrapper--embedded' : ''}`, part: "wrapper" }, this.showCanvasEditor && (index.h("canvas-editor", { ref: (el) => (this.canvasEditorRef = el), exportparts: "overlay: canvas-editor-overlay, modal: canvas-editor-modal, header: canvas-editor-header, title: canvas-editor-title, toolbar: canvas-editor-toolbar, tool-button: canvas-editor-tool-button, cancel-button: canvas-editor-cancel-button, save-button: canvas-editor-save-button, content: canvas-editor-content, canvas: canvas-editor-canvas", "canvas-editor-title": this.screenshotEditorTitle, "canvas-editor-cancel-text": this.screenshotEditorCancelText, "canvas-editor-save-text": this.screenshotEditorSaveText, "screenshot-taking-text": this.screenshotTakingText, "screenshot-attached-text": this.screenshotAttachedText, "screenshot-button-text": this.screenshotButtonText, "escape-close": this.escapeClose ? 'true' : 'false', "click-outside-close": this.clickOutsideClose ? 'true' : 'false', "auto-start-screenshot": this.autoStartCapture, "existing-screenshot": this.encodedScreenshot || '', "edit-text-button-text": this.screenshotEditTextButtonText, "size-label-text": this.screenshotSizeLabelText, "border-label-text": this.screenshotBorderLabelText, "edit-text-prompt-text": this.screenshotEditTextPromptText, "screenshot-error-general": this.screenshotErrorGeneral, "screenshot-error-permission": this.screenshotErrorPermission, "screenshot-error-not-supported": this.screenshotErrorNotSupported, "screenshot-error-not-found": this.screenshotErrorNotFound, "screenshot-error-cancelled": this.screenshotErrorCancelled, "screenshot-error-browser-not-supported": this.screenshotErrorBrowserNotSupported, "screenshot-error-unexpected": this.screenshotErrorUnexpected, onScreenshotReady: this.handleScreenshotReady, onScreenshotCancelled: this.handleScreenshotCancelled, onScreenshotFailed: this.handleScreenshotError })), this.showScreenshotError && (index.h("div", { class: "screenshot-error-notification" }, index.h("div", { class: "screenshot-error-content" }, index.h("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, index.h("circle", { cx: "12", cy: "12", r: "10" }), index.h("line", { x1: "15", y1: "9", x2: "9", y2: "15" }), index.h("line", { x1: "9", y1: "9", x2: "15", y2: "15" })), index.h("span", null, this.screenshotError), index.h("button", { class: "error-close-btn", onClick: () => (this.showScreenshotError = false), title: "Close" }, index.h("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, index.h("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), index.h("line", { x1: "6", y1: "6", x2: "18", y2: "18" })))))), this.showModal && !this.embedded && (index.h("div", { class: `feedback-overlay ${this.isAnimating ? 'feedback-overlay--visible' : ''}`, part: "overlay", "aria-hidden": "true", onClick: this.clickOutsideClose ? this.handleOverlayClick : undefined })), this.showModal && (index.h("div", { class: `feedback-modal-content feedback-modal-content--${this.modalPosition} ${this.isAnimating ? 'feedback-modal-content--open' : ''} ${this.embedded ? 'feedback-modal-content--embedded' : ''}`, part: "modal", role: this.embedded ? 'region' : 'dialog', "aria-modal": this.embedded ? undefined : 'true', "aria-labelledby": "feedback-modal-title", tabindex: "-1", ref: (el) => (this.modalContent = el) }, index.h("div", { class: `feedback-modal-header ${(this.formSuccess && !this.successMessage) || (this.formError && !this.errorMessage)
|
|
1500
1846
|
? 'feedback-modal-header--no-content'
|
|
1501
|
-
: ''}`, part: "header" }, index.h("div", { class: "feedback-modal-header-content" }, !this.formSuccess && !this.formError ? (index.h("div", { class: "feedback-modal-header-text" }, index.h("span", { class: "feedback-modal-title", part: "title" }, this.modalTitle), !this.whitelabel && (index.h("span", { class: "feedback-modal-powered-by", part: "powered-by" }, "Powered by", ' ', index.h("a", { target: "_blank", href: "https://pushfeedback.com" }, "PushFeedback"))))) : this.formSuccess ? (index.h("span", { class: "feedback-modal-title", part: "title" }, this.modalTitleSuccess)) : (index.h("span", { class: "feedback-modal-title", part: "title" }, this.modalTitleError))), !this.embedded && (index.h("button", { class: "feedback-modal-close", part: "close-button", onClick: this.close }, index.h("svg", { xmlns: "http://www.w3.org/2000/svg", width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", stroke: "#191919", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", class: "feather feather-x" }, index.h("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), index.h("line", { x1: "6", y1: "6", x2: "18", y2: "18" }))))), index.h("div", { class: "feedback-modal-body", part: "body" }, !this.formSuccess && !this.formError ? (index.h("form", { part: "form", onSubmit: this.handleSubmit }, !this.hideRating && (index.h("div", { class: "feedback-modal-rating", part: "rating" }, this.ratingMode === 'thumbs' ? (index.h("div", { class: "feedback-modal-rating-content" }, index.h("span", { class: "feedback-modal-input-heading", part: "rating-title" }, this.ratingPlaceholder), index.h("div", { class: "feedback-modal-rating-buttons feedback-modal-rating-buttons--thumbs", part: "rating-buttons" }, index.h("button", { title: "
|
|
1847
|
+
: ''}`, part: "header" }, index.h("div", { class: "feedback-modal-header-content" }, !this.formSuccess && !this.formError ? (index.h("div", { class: "feedback-modal-header-text" }, index.h("span", { id: "feedback-modal-title", class: "feedback-modal-title", part: "title" }, this.modalTitle), !this.whitelabel && (index.h("span", { class: "feedback-modal-powered-by", part: "powered-by" }, "Powered by", ' ', index.h("a", { target: "_blank", href: "https://pushfeedback.com" }, "PushFeedback"))))) : this.formSuccess ? (index.h("span", { id: "feedback-modal-title", class: "feedback-modal-title", part: "title" }, this.modalTitleSuccess)) : (index.h("span", { id: "feedback-modal-title", class: "feedback-modal-title", part: "title" }, this.modalTitleError))), !this.embedded && (index.h("button", { class: "feedback-modal-close", part: "close-button", type: "button", "aria-label": this.closeButtonLabel, onClick: this.close }, index.h("svg", { xmlns: "http://www.w3.org/2000/svg", width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", stroke: "#191919", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", class: "feather feather-x" }, index.h("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), index.h("line", { x1: "6", y1: "6", x2: "18", y2: "18" }))))), index.h("div", { class: "feedback-modal-body", part: "body" }, !this.formSuccess && !this.formError ? (index.h("form", { part: "form", onSubmit: this.handleSubmit }, !this.hideRating && (index.h("div", { class: "feedback-modal-rating", part: "rating" }, this.ratingMode === 'thumbs' ? (index.h("div", { class: "feedback-modal-rating-content" }, index.h("span", { class: "feedback-modal-input-heading", part: "rating-title" }, this.ratingPlaceholder), index.h("div", { class: "feedback-modal-rating-buttons feedback-modal-rating-buttons--thumbs", part: "rating-buttons" }, index.h("button", { type: "button", title: this.ratingPositiveLabel, "aria-label": this.ratingPositiveLabel, "aria-pressed": this.selectedRating === 1 ? 'true' : 'false', class: `feedback-modal-rating-button feedback-modal-rating-button--positive ${this.selectedRating === 1
|
|
1502
1848
|
? 'feedback-modal-rating-button--selected'
|
|
1503
1849
|
: ''}`, part: "rating-button rating-button-positive", onClick: (event) => {
|
|
1504
1850
|
event.preventDefault();
|
|
1505
1851
|
this.handleRatingChange(1);
|
|
1506
|
-
} }, index.h("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "#5F6368", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, index.h("path", { d: "M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3" }))), index.h("button", { title: "
|
|
1852
|
+
} }, index.h("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "#5F6368", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, index.h("path", { d: "M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3" }))), index.h("button", { type: "button", title: this.ratingNegativeLabel, "aria-label": this.ratingNegativeLabel, "aria-pressed": this.selectedRating === 5 ? 'true' : 'false', class: `feedback-modal-rating-button feedback-modal-rating-button--negative ${this.selectedRating === 5
|
|
1507
1853
|
? 'feedback-modal-rating-button--selected'
|
|
1508
1854
|
: ''}`, part: "rating-button rating-button-negative", onClick: (event) => {
|
|
1509
1855
|
event.preventDefault();
|
|
1510
1856
|
this.handleRatingChange(5);
|
|
1511
|
-
} }, index.h("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "#5F6368", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, index.h("path", { d: "M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3zm7-13h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17" })))))) : (index.h("div", { class: "feedback-modal-rating-content" }, index.h("span", { class: "feedback-modal-input-heading", part: "rating-title" }, this.ratingStarsPlaceholder), index.h("div", { class: "feedback-modal-rating-buttons feedback-modal-rating-buttons--stars", part: "rating-buttons" }, [1, 2, 3, 4, 5].map((rating) => (index.h("button", { key: rating, class: `feedback-modal-rating-button ${this.selectedRating >= rating
|
|
1857
|
+
} }, index.h("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "#5F6368", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, index.h("path", { d: "M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3zm7-13h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17" })))))) : (index.h("div", { class: "feedback-modal-rating-content" }, index.h("span", { class: "feedback-modal-input-heading", part: "rating-title" }, this.ratingStarsPlaceholder), index.h("div", { class: "feedback-modal-rating-buttons feedback-modal-rating-buttons--stars", part: "rating-buttons" }, [1, 2, 3, 4, 5].map((rating) => (index.h("button", { key: rating, type: "button", "aria-label": this.ratingStarLabel.replace('{rating}', String(rating)), "aria-pressed": this.selectedRating === rating ? 'true' : 'false', class: `feedback-modal-rating-button ${this.selectedRating >= rating
|
|
1512
1858
|
? 'feedback-modal-rating-button--selected'
|
|
1513
1859
|
: ''}`, part: "rating-button", onClick: (event) => {
|
|
1514
1860
|
event.preventDefault();
|
|
1515
1861
|
this.handleRatingChange(rating);
|
|
1516
|
-
} }, index.h("svg", { xmlns: "http://www.w3.org/2000/svg", width: "28", height: "28", viewBox: "0 0 24 24", fill: "none", stroke: "#5F6368", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, index.h("polygon", { points: "12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" })))))))))), index.h("div", { class: "feedback-modal-text" }, index.h("textarea", { part: "message-input", placeholder: this.messagePlaceholder, value: this.formMessage, onInput: (event) => this.handleMessageInput(event) })), !this.hideEmail && (index.h("div", { class: "feedback-modal-email" }, index.h("input", { part: "email-input", placeholder: this.emailPlaceholder, type: "email", onInput: (event) => this.handleEmailInput(event), value: this.formEmail, required: this.isEmailRequired }))), index.h("div", { class: "feedback-verification" }, index.h("input", { type: "text", name: "verification", style: { display: 'none' }, onInput: (event) => this.handleVerification(event), value: this.formVerification })), !this.hidePrivacyPolicy && (index.h("div", { class: "feedback-modal-privacy", part: "privacy" }, index.h("input", { type: "checkbox", id: "privacyPolicy", onChange: (ev) => this.handleCheckboxChange(ev), required: true }), index.h("span", { innerHTML: this.privacyPolicyText }))), index.h("div", { class: `feedback-modal-buttons ${this.hideScreenshotButton ? 'single' : ''}`, part: "buttons" }, !this.hideScreenshotButton && (index.h("button", { type: "button", class: `feedback-modal-button feedback-modal-button--screenshot ${this.encodedScreenshot ? 'feedback-modal-button--active' : ''}`, part: "screenshot-button", onClick: this.openScreenShot, disabled: this.sending || this.takingScreenshot }, this.encodedScreenshot && (index.h("div", { class: "screenshot-preview", onClick: this.openCanvasEditor }, index.h("img", { src: this.encodedScreenshot, alt: "Screenshot Preview" }))), !this.encodedScreenshot && !this.takingScreenshot && (index.h("svg", { xmlns: "http://www.w3.org/2000/svg", height: "24", viewBox: "0 -960 960 960", width: "24" }, index.h("path", { d: "M680-80v-120H560v-80h120v-120h80v120h120v80H760v120h-80ZM200-200v-200h80v120h120v80H200Zm0-360v-200h200v80H280v120h-80Zm480 0v-120H560v-80h200v200h-80Z" }))), this.takingScreenshot && (index.h("div", { class: "screenshot-loading" }, index.h("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "#666", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", class: "feather-loader" }, index.h("line", { x1: "12", y1: "2", x2: "12", y2: "6" }), index.h("line", { x1: "12", y1: "18", x2: "12", y2: "22" }), index.h("line", { x1: "4.93", y1: "4.93", x2: "7.76", y2: "7.76" }), index.h("line", { x1: "16.24", y1: "16.24", x2: "19.07", y2: "19.07" }), index.h("line", { x1: "2", y1: "12", x2: "6", y2: "12" }), index.h("line", { x1: "18", y1: "12", x2: "22", y2: "12" }), index.h("line", { x1: "4.93", y1: "19.07", x2: "7.76", y2: "16.24" }), index.h("line", { x1: "16.24", y1: "7.76", x2: "19.07", y2: "4.93" })))), this.takingScreenshot
|
|
1862
|
+
} }, index.h("svg", { xmlns: "http://www.w3.org/2000/svg", width: "28", height: "28", viewBox: "0 0 24 24", fill: "none", stroke: "#5F6368", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, index.h("polygon", { points: "12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" })))))))))), index.h("div", { class: "feedback-modal-text" }, index.h("textarea", { part: "message-input", placeholder: this.messagePlaceholder, value: this.formMessage, onInput: (event) => this.handleMessageInput(event) })), !this.hideEmail && (index.h("div", { class: "feedback-modal-email" }, index.h("input", { part: "email-input", placeholder: this.emailPlaceholder, type: "email", onInput: (event) => this.handleEmailInput(event), value: this.formEmail, required: this.isEmailRequired }))), index.h("div", { class: "feedback-verification" }, index.h("input", { type: "text", name: "verification", style: { display: 'none' }, onInput: (event) => this.handleVerification(event), value: this.formVerification })), !this.hidePrivacyPolicy && (index.h("div", { class: "feedback-modal-privacy", part: "privacy" }, index.h("input", { type: "checkbox", id: "privacyPolicy", onChange: (ev) => this.handleCheckboxChange(ev), required: true }), index.h("span", { innerHTML: sanitizeHtml(this.privacyPolicyText) }))), index.h("div", { class: `feedback-modal-buttons ${this.hideScreenshotButton ? 'single' : ''}`, part: "buttons" }, !this.hideScreenshotButton && (index.h("button", { type: "button", class: `feedback-modal-button feedback-modal-button--screenshot ${this.encodedScreenshot ? 'feedback-modal-button--active' : ''}`, part: "screenshot-button", onClick: this.openScreenShot, disabled: this.sending || this.takingScreenshot }, this.encodedScreenshot && (index.h("div", { class: "screenshot-preview", onClick: this.openCanvasEditor }, index.h("img", { src: this.encodedScreenshot, alt: "Screenshot Preview" }))), !this.encodedScreenshot && !this.takingScreenshot && (index.h("svg", { xmlns: "http://www.w3.org/2000/svg", height: "24", viewBox: "0 -960 960 960", width: "24" }, index.h("path", { d: "M680-80v-120H560v-80h120v-120h80v120h120v80H760v120h-80ZM200-200v-200h80v120h120v80H200Zm0-360v-200h200v80H280v120h-80Zm480 0v-120H560v-80h200v200h-80Z" }))), this.takingScreenshot && (index.h("div", { class: "screenshot-loading" }, index.h("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "#666", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", class: "feather-loader" }, index.h("line", { x1: "12", y1: "2", x2: "12", y2: "6" }), index.h("line", { x1: "12", y1: "18", x2: "12", y2: "22" }), index.h("line", { x1: "4.93", y1: "4.93", x2: "7.76", y2: "7.76" }), index.h("line", { x1: "16.24", y1: "16.24", x2: "19.07", y2: "19.07" }), index.h("line", { x1: "2", y1: "12", x2: "6", y2: "12" }), index.h("line", { x1: "18", y1: "12", x2: "22", y2: "12" }), index.h("line", { x1: "4.93", y1: "19.07", x2: "7.76", y2: "16.24" }), index.h("line", { x1: "16.24", y1: "7.76", x2: "19.07", y2: "4.93" })))), this.takingScreenshot
|
|
1517
1863
|
? this.screenshotTakingText
|
|
1518
1864
|
: this.encodedScreenshot
|
|
1519
1865
|
? this.screenshotAttachedText
|
|
1520
|
-
: this.screenshotButtonText)), index.h("button", { class: "feedback-modal-button feedback-modal-button--submit", part: "submit-button", type: "submit", disabled: this.sending }, this.sendButtonText)))) : this.formSuccess && !this.formError ? (index.h("div", { class: "feedback-modal-success" }, index.h("p", { class: "feedback-modal-message", part: "success-message" }, this.successMessage))) : this.formError && this.formErrorStatus == 404 ? (index.h("p", { class: "feedback-modal-message", part: "error-message" }, this.errorMessage404)) : this.formError && this.formErrorStatus == 403 ? (index.h("p", { class: "feedback-modal-message", part: "error-message" }, this.errorMessage403)) : this.formError ? (index.h("p", { class: "feedback-modal-message", part: "error-message" }, this.errorMessage)) : (index.h("span", null))), !this.formSuccess && !this.formError && (this.recaptchaEnabled || this.footerText) && (index.h("div", { class: "feedback-modal-footer", part: "footer" }, index.h("div", { class: "feedback-footer-combined" }, this.recaptchaEnabled && index.h("span", { innerHTML: this.recaptchaText }), this.recaptchaEnabled && this.footerText && ' ', this.footerText && index.h("span", { innerHTML: this.footerText }))))))));
|
|
1866
|
+
: this.screenshotButtonText)), index.h("button", { class: "feedback-modal-button feedback-modal-button--submit", part: "submit-button", type: "submit", disabled: this.sending }, this.sendButtonText)))) : this.formSuccess && !this.formError ? (index.h("div", { class: "feedback-modal-success" }, index.h("p", { class: "feedback-modal-message", part: "success-message" }, this.successMessage))) : this.formError && this.formErrorStatus == 404 ? (index.h("p", { class: "feedback-modal-message", part: "error-message" }, this.errorMessage404)) : this.formError && this.formErrorStatus == 401 ? (index.h("p", { class: "feedback-modal-message", part: "error-message" }, this.errorMessage401)) : this.formError && this.formErrorStatus == 403 ? (index.h("p", { class: "feedback-modal-message", part: "error-message" }, this.errorMessage403)) : this.formError ? (index.h("p", { class: "feedback-modal-message", part: "error-message" }, this.errorMessage)) : (index.h("span", null))), !this.formSuccess && !this.formError && (this.recaptchaEnabled || this.footerText) && (index.h("div", { class: "feedback-modal-footer", part: "footer" }, index.h("div", { class: "feedback-footer-combined" }, this.recaptchaEnabled && (index.h("span", { innerHTML: sanitizeHtml(this.recaptchaText) })), this.recaptchaEnabled && this.footerText && ' ', this.footerText && index.h("span", { innerHTML: sanitizeHtml(this.footerText) }))))))));
|
|
1521
1867
|
}
|
|
1522
1868
|
componentDidRender() {
|
|
1523
1869
|
if (this.showModal) {
|
|
@@ -1527,7 +1873,13 @@ const FeedbackModal = class {
|
|
|
1527
1873
|
}
|
|
1528
1874
|
}
|
|
1529
1875
|
async openModal() {
|
|
1876
|
+
// Remember the trigger so focus can return there when the modal closes
|
|
1877
|
+
if (!this.embedded) {
|
|
1878
|
+
const active = document.activeElement;
|
|
1879
|
+
this.previouslyFocused = active && active !== document.body ? active : null;
|
|
1880
|
+
}
|
|
1530
1881
|
this.showModal = true;
|
|
1882
|
+
this.focusModal();
|
|
1531
1883
|
if (this.historyClose && !this.embedded && !this.historyEntryPushed) {
|
|
1532
1884
|
history.pushState({ feedbackModal: true }, '');
|
|
1533
1885
|
this.historyEntryPushed = true;
|