pushfeedback 0.1.83 → 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.
@@ -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
- this.annotations = [...this.annotations, this.currentAnnotation];
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
- const A = annotation.endY - annotation.startY;
739
- const B = annotation.startX - annotation.endX;
740
- const C = annotation.endX * annotation.startY - annotation.startX * annotation.endY;
741
- const distance = Math.abs(A * x + B * y + C) / Math.sqrt(A * A + B * B);
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
- console.log('Screenshot captured successfully using Screen Capture API');
864
- resolve(dataUrl);
961
+ finish(() => {
962
+ stopStream();
963
+ resolve(dataUrl);
964
+ });
865
965
  }
866
966
  catch (error) {
867
- stream.getTracks().forEach((track) => track.stop());
868
- reject(error);
967
+ finish(() => {
968
+ stopStream();
969
+ reject(error);
970
+ });
869
971
  }
870
972
  }, 100);
871
973
  };
872
974
  video.onerror = () => {
873
- stream.getTracks().forEach((track) => track.stop());
874
- reject(new Error('Failed to load video for screenshot capture'));
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", null, 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' ||
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,8 +1042,11 @@ const FeedbackButton = class {
920
1042
  this.sessionId = '';
921
1043
  this.metadata = '';
922
1044
  this.submit = false;
1045
+ this.apiKey = '';
1046
+ this.clickOutsideClose = true;
923
1047
  this.customFont = false;
924
1048
  this.emailAddress = '';
1049
+ this.historyClose = true;
925
1050
  this.isEmailRequired = false;
926
1051
  this.fetchData = true;
927
1052
  this.hideEmail = false;
@@ -934,6 +1059,7 @@ const FeedbackButton = class {
934
1059
  this.ratingMode = 'thumbs';
935
1060
  this.emailPlaceholder = 'Email address (optional)';
936
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.';
937
1063
  this.errorMessage403 = 'The request URL does not match the one defined in PushFeedback for this project.';
938
1064
  this.errorMessage404 = 'We could not find the provided project id in PushFeedback.';
939
1065
  this.footerText = '';
@@ -979,13 +1105,19 @@ const FeedbackButton = class {
979
1105
  }
980
1106
  }
981
1107
  componentDidLoad() {
982
- if (this.buttonPosition === 'center-right') {
1108
+ if (this.buttonPosition === 'center-right' || this.buttonPosition === 'center-left') {
983
1109
  const buttonContent = this.el.shadowRoot.querySelector('.feedback-button-content');
984
1110
  let adjustement = 0;
985
1111
  if (this.isSafariBrowser()) {
986
1112
  adjustement = 5;
987
1113
  }
988
- buttonContent.style.right = `${((buttonContent.offsetWidth + adjustement) / 2) * -1}px`;
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
+ }
989
1121
  }
990
1122
  if (!this.customFont) {
991
1123
  this.loadInterFont();
@@ -994,9 +1126,12 @@ const FeedbackButton = class {
994
1126
  connectedCallback() {
995
1127
  this.feedbackModal = document.createElement('feedback-modal');
996
1128
  const props = [
1129
+ 'apiKey',
1130
+ 'clickOutsideClose',
997
1131
  'customFont',
998
1132
  'emailAddress',
999
1133
  'fetchData',
1134
+ 'historyClose',
1000
1135
  'hideEmail',
1001
1136
  'hidePrivacyPolicy',
1002
1137
  'hideRating',
@@ -1022,6 +1157,7 @@ const FeedbackButton = class {
1022
1157
  'screenshotErrorUnexpected',
1023
1158
  'emailPlaceholder',
1024
1159
  'errorMessage',
1160
+ 'errorMessage401',
1025
1161
  'errorMessage403',
1026
1162
  'errorMessage404',
1027
1163
  'footerText',
@@ -1086,12 +1222,16 @@ const FeedbackButton = class {
1086
1222
  }
1087
1223
  }
1088
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 }));
1089
- const res = await fetch('https://app.pushfeedback.com/api/feedback/', {
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/', {
1090
1232
  method: 'POST',
1091
1233
  body: JSON.stringify(body),
1092
- headers: {
1093
- 'Content-Type': 'application/json',
1094
- },
1234
+ headers,
1095
1235
  });
1096
1236
  if (res.status === 201) {
1097
1237
  const feedback_with_id = Object.assign(Object.assign({}, body), { id: await res.json() });
@@ -1122,19 +1262,139 @@ const FeedbackButton = class {
1122
1262
  }
1123
1263
  }
1124
1264
  render() {
1125
- return (index.h(index.Host, null, index.h("a", { 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))));
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))));
1126
1266
  }
1127
1267
  get el() { return index.getElement(this); }
1128
1268
  };
1129
1269
  FeedbackButton.style = feedbackButtonCss;
1130
1270
 
1131
- 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}}";
1132
1363
 
1133
1364
  const FeedbackModal = class {
1134
1365
  constructor(hostRef) {
1135
1366
  index.registerInstance(this, hostRef);
1136
1367
  this.feedbackSent = index.createEvent(this, "feedbackSent", 7);
1137
1368
  this.feedbackError = index.createEvent(this, "feedbackError", 7);
1369
+ // Track the history entry pushed while the modal is open
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
+ };
1389
+ this.handlePopState = () => {
1390
+ // Back button pressed while the modal is open: close it and consume our entry
1391
+ if (this.historyEntryPushed) {
1392
+ this.historyEntryPushed = false;
1393
+ if (this.showModal) {
1394
+ this.close();
1395
+ }
1396
+ }
1397
+ };
1138
1398
  this.onScrollDebounced = () => {
1139
1399
  clearTimeout(this.scrollTimeout);
1140
1400
  this.scrollTimeout = setTimeout(() => {
@@ -1169,12 +1429,16 @@ const FeedbackModal = class {
1169
1429
  }
1170
1430
  }
1171
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 }));
1172
- const res = await fetch('https://app.pushfeedback.com/api/feedback/', {
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/', {
1173
1439
  method: 'POST',
1174
1440
  body: JSON.stringify(body),
1175
- headers: {
1176
- 'Content-Type': 'application/json',
1177
- },
1441
+ headers,
1178
1442
  });
1179
1443
  if (res.status === 201) {
1180
1444
  const feedback_with_id = Object.assign(Object.assign({}, body), { id: await res.json() });
@@ -1219,14 +1483,22 @@ const FeedbackModal = class {
1219
1483
  }
1220
1484
  };
1221
1485
  this.close = () => {
1486
+ this.doClose(false);
1487
+ };
1488
+ // Closing by clicking outside keeps the typed input, so an accidental
1489
+ // click does not throw away a half-written message or screenshot
1490
+ this.handleOverlayClick = () => {
1491
+ this.doClose(!this.formSuccess);
1492
+ };
1493
+ this.doClose = (preserveInput) => {
1222
1494
  this.isAnimating = false;
1495
+ this.popHistoryEntry();
1223
1496
  setTimeout(() => {
1224
1497
  this.sending = false;
1225
1498
  this.showModal = false;
1226
1499
  this.showScreenshotMode = false;
1227
1500
  this.showScreenshotTopBar = false;
1228
1501
  this.hasSelectedElement = false;
1229
- this.encodedScreenshot = null;
1230
1502
  // Remove highlight from ALL selected elements
1231
1503
  document.querySelectorAll('.feedback-modal-element-selected').forEach((el) => {
1232
1504
  el.classList.remove('feedback-modal-element-selected');
@@ -1235,9 +1507,19 @@ const FeedbackModal = class {
1235
1507
  this.formSuccess = false;
1236
1508
  this.formError = false;
1237
1509
  this.formErrorStatus = 500;
1238
- this.formMessage = '';
1239
- this.formEmail = '';
1510
+ if (!preserveInput) {
1511
+ this.encodedScreenshot = null;
1512
+ this.formMessage = '';
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;
1520
+ }
1240
1521
  this.resetOverflow();
1522
+ this.restoreFocus();
1241
1523
  }, 200);
1242
1524
  };
1243
1525
  // Handle screenshot events from canvas editor
@@ -1247,12 +1529,16 @@ const FeedbackModal = class {
1247
1529
  this.takingScreenshot = false;
1248
1530
  this.showCanvasEditor = false;
1249
1531
  this.autoStartCapture = false;
1532
+ this.focusModal();
1250
1533
  };
1251
1534
  this.handleScreenshotCancelled = () => {
1252
1535
  this.showModal = true;
1253
1536
  this.takingScreenshot = false;
1254
1537
  this.showCanvasEditor = false;
1255
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();
1256
1542
  };
1257
1543
  this.handleScreenshotError = (event) => {
1258
1544
  console.error('Screenshot error:', event.detail.error);
@@ -1264,6 +1550,7 @@ const FeedbackModal = class {
1264
1550
  this.takingScreenshot = false;
1265
1551
  this.showCanvasEditor = false;
1266
1552
  this.autoStartCapture = false;
1553
+ this.focusModal();
1267
1554
  // Auto-hide error after 8 seconds
1268
1555
  setTimeout(() => {
1269
1556
  this.showScreenshotError = false;
@@ -1305,6 +1592,7 @@ const FeedbackModal = class {
1305
1592
  this.screenshotError = '';
1306
1593
  this.showCanvasEditor = false;
1307
1594
  this.autoStartCapture = false;
1595
+ this.apiKey = '';
1308
1596
  this.customFont = false;
1309
1597
  this.emailAddress = '';
1310
1598
  this.hideEmail = false;
@@ -1322,8 +1610,12 @@ const FeedbackModal = class {
1322
1610
  this.metadata = undefined;
1323
1611
  this.fetchData = true;
1324
1612
  this.embedded = false;
1613
+ this.clickOutsideClose = true;
1614
+ this.historyClose = true;
1615
+ this.escapeClose = true;
1325
1616
  this.emailPlaceholder = 'Email address (optional)';
1326
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.';
1327
1619
  this.errorMessage403 = 'The request URL does not match the one defined in PushFeedback for this project.';
1328
1620
  this.errorMessage404 = 'We could not find the provided project ID in PushFeedback.';
1329
1621
  this.messagePlaceholder = 'Share your thoughts...';
@@ -1336,6 +1628,10 @@ const FeedbackModal = class {
1336
1628
  this.ratingPlaceholder = 'Was this page helpful?';
1337
1629
  this.ratingStarsPlaceholder = 'How would you rate this page?';
1338
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';
1339
1635
  this.successMessage = '';
1340
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.';
1341
1637
  this.screenshotAttachedText = 'Screenshot attached';
@@ -1356,16 +1652,98 @@ const FeedbackModal = class {
1356
1652
  this.screenshotErrorBrowserNotSupported = 'Your browser does not support screen capture. Please use a browser like Chrome, Firefox, or Safari on desktop.';
1357
1653
  this.screenshotErrorUnexpected = 'An unexpected error occurred. Please try again.';
1358
1654
  }
1655
+ connectedCallback() {
1656
+ window.addEventListener('popstate', this.handlePopState);
1657
+ document.addEventListener('keydown', this.handleKeyDown);
1658
+ }
1659
+ disconnectedCallback() {
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;
1741
+ }
1359
1742
  componentWillLoad() {
1360
1743
  if (this.fetchData)
1361
1744
  this.fetchProjectData();
1362
1745
  this.formEmail = this.emailAddress;
1363
- if (this.rating) {
1364
- this.selectedRating = this.rating;
1365
- }
1366
- if (this.ratingMode == 'thumbs' && this.rating == 0) {
1367
- this.selectedRating = 5;
1368
- }
1746
+ this.selectedRating = this.initialRating();
1369
1747
  // Automatically show modal when embedded
1370
1748
  if (this.embedded) {
1371
1749
  this.showModal = true;
@@ -1373,7 +1751,9 @@ const FeedbackModal = class {
1373
1751
  }
1374
1752
  async fetchProjectData() {
1375
1753
  try {
1376
- const response = await fetch('https://app.pushfeedback.com/api/projects/' + this.project + '/');
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 + '/');
1377
1757
  const data = await response.json();
1378
1758
  this.whitelabel = data.whitelabel;
1379
1759
  this.recaptchaEnabled = data.recaptcha_enabled || false;
@@ -1446,6 +1826,12 @@ const FeedbackModal = class {
1446
1826
  handleEmailInput(event) {
1447
1827
  this.formEmail = event.target.value;
1448
1828
  }
1829
+ popHistoryEntry() {
1830
+ if (this.historyEntryPushed) {
1831
+ this.historyEntryPushed = false;
1832
+ history.back();
1833
+ }
1834
+ }
1449
1835
  handleCheckboxChange(event) {
1450
1836
  this.isPrivacyChecked = event.target.checked;
1451
1837
  }
@@ -1456,28 +1842,28 @@ const FeedbackModal = class {
1456
1842
  this.selectedRating = newRating;
1457
1843
  }
1458
1844
  render() {
1459
- 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" })), 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)
1460
1846
  ? 'feedback-modal-header--no-content'
1461
- : ''}`, 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: "Yes", class: `feedback-modal-rating-button feedback-modal-rating-button--positive ${this.selectedRating === 1
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
1462
1848
  ? 'feedback-modal-rating-button--selected'
1463
1849
  : ''}`, part: "rating-button rating-button-positive", onClick: (event) => {
1464
1850
  event.preventDefault();
1465
1851
  this.handleRatingChange(1);
1466
- } }, 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: "No", class: `feedback-modal-rating-button feedback-modal-rating-button--negative ${this.selectedRating === 5
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
1467
1853
  ? 'feedback-modal-rating-button--selected'
1468
1854
  : ''}`, part: "rating-button rating-button-negative", onClick: (event) => {
1469
1855
  event.preventDefault();
1470
1856
  this.handleRatingChange(5);
1471
- } }, 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
1472
1858
  ? 'feedback-modal-rating-button--selected'
1473
1859
  : ''}`, part: "rating-button", onClick: (event) => {
1474
1860
  event.preventDefault();
1475
1861
  this.handleRatingChange(rating);
1476
- } }, 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
1477
1863
  ? this.screenshotTakingText
1478
1864
  : this.encodedScreenshot
1479
1865
  ? this.screenshotAttachedText
1480
- : 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) }))))))));
1481
1867
  }
1482
1868
  componentDidRender() {
1483
1869
  if (this.showModal) {
@@ -1487,7 +1873,17 @@ const FeedbackModal = class {
1487
1873
  }
1488
1874
  }
1489
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
+ }
1490
1881
  this.showModal = true;
1882
+ this.focusModal();
1883
+ if (this.historyClose && !this.embedded && !this.historyEntryPushed) {
1884
+ history.pushState({ feedbackModal: true }, '');
1885
+ this.historyEntryPushed = true;
1886
+ }
1491
1887
  requestAnimationFrame(() => {
1492
1888
  requestAnimationFrame(() => {
1493
1889
  this.isAnimating = true;