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.
@@ -2,12 +2,59 @@ import { r as registerInstance, c as createEvent, h, H as Host, g as getElement
2
2
 
3
3
  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}}";
4
4
 
5
+ // Upper bound on waiting for the capture stream to produce video metadata,
6
+ // after which the stream is stopped so it cannot leak
7
+ const CAPTURE_TIMEOUT_MS = 10000;
5
8
  const CanvasEditor = class {
6
9
  constructor(hostRef) {
7
10
  registerInstance(this, hostRef);
8
11
  this.screenshotReady = createEvent(this, "screenshotReady", 7);
9
12
  this.screenshotCancelled = createEvent(this, "screenshotCancelled", 7);
10
13
  this.screenshotFailed = createEvent(this, "screenshotFailed", 7);
14
+ this.handleKeyDown = (event) => {
15
+ if (!this.showCanvasEditor || !this.escapeClose || event.key !== 'Escape') {
16
+ return;
17
+ }
18
+ // While the browser is capturing the screen, the picker owns the keyboard
19
+ if (this.takingScreenshot) {
20
+ return;
21
+ }
22
+ event.preventDefault();
23
+ // The colour picker is a popup on top of the editor, so Escape dismisses
24
+ // that first and leaves the editor (and any annotations) in place
25
+ if (this.showColorPicker) {
26
+ this.showColorPicker = false;
27
+ this.editingColorIndex = -1;
28
+ return;
29
+ }
30
+ // Deselect before closing, the way drawing tools do. Closing throws away
31
+ // every annotation, so someone who has just selected one gets a cheap
32
+ // Escape that only drops the selection rather than their work
33
+ if (this.selectedAnnotation) {
34
+ this.selectedAnnotation = null;
35
+ this.redrawAnnotations();
36
+ return;
37
+ }
38
+ // Same as the Cancel button: discards annotations and returns to the form
39
+ this.closeCanvasEditor();
40
+ };
41
+ // Tracks whether the press that started this click landed on the backdrop.
42
+ // Dragging an annotation and releasing past the edge of the editor fires a
43
+ // click on the backdrop too, and that must not be read as "clicked outside".
44
+ this.pressStartedOnOverlay = false;
45
+ this.handleOverlayMouseDown = (event) => {
46
+ this.pressStartedOnOverlay = event.target === event.currentTarget;
47
+ };
48
+ this.handleOverlayClick = (event) => {
49
+ const startedOutside = this.pressStartedOnOverlay;
50
+ this.pressStartedOnOverlay = false;
51
+ if (!this.clickOutsideClose || !startedOutside || event.target !== event.currentTarget) {
52
+ return;
53
+ }
54
+ // Closes the editor only. The feedback form reopens behind it, so a stray
55
+ // click never dismisses the whole widget
56
+ this.closeCanvasEditor();
57
+ };
11
58
  this.handleWindowResize = () => {
12
59
  // Debounce resize events
13
60
  if (this.resizeTimeout) {
@@ -687,8 +734,16 @@ const CanvasEditor = class {
687
734
  if (!this.isDrawing || !this.currentAnnotation)
688
735
  return;
689
736
  this.isDrawing = false;
690
- this.annotations = [...this.annotations, this.currentAnnotation];
737
+ const annotation = this.currentAnnotation;
691
738
  this.currentAnnotation = null;
739
+ // A click with no drag never reaches mousemove, so the shape has no end
740
+ // point or size. Committing it added an invisible annotation that could
741
+ // not be selected, dragged or deleted, and it still consumed an undo step.
742
+ if (this.isDegenerateAnnotation(annotation)) {
743
+ this.redrawAnnotations();
744
+ return;
745
+ }
746
+ this.annotations = [...this.annotations, annotation];
692
747
  this.redrawAnnotations();
693
748
  };
694
749
  // Convert screen coordinates to canvas coordinates
@@ -730,11 +785,28 @@ const CanvasEditor = class {
730
785
  }
731
786
  case 'line':
732
787
  case 'arrow': {
733
- // Distance from point to line
734
- const A = annotation.endY - annotation.startY;
735
- const B = annotation.startX - annotation.endX;
736
- const C = annotation.endX * annotation.startY - annotation.startX * annotation.endY;
737
- const distance = Math.abs(A * x + B * y + C) / Math.sqrt(A * A + B * B);
788
+ // Distance from the point to the line *segment*, not to the infinite
789
+ // line through it. The previous formula measured the infinite line, so
790
+ // a click far past either end still counted as a hit, and a zero-length
791
+ // segment divided by zero and gave NaN, leaving that annotation
792
+ // impossible to select, drag or delete.
793
+ // A click with no drag never reaches mousemove, so endX/endY are
794
+ // undefined rather than equal to the start. Treat that as a segment
795
+ // collapsed to its start point instead of letting NaN propagate.
796
+ const endX = Number.isFinite(annotation.endX) ? annotation.endX : annotation.startX;
797
+ const endY = Number.isFinite(annotation.endY) ? annotation.endY : annotation.startY;
798
+ const dx = endX - annotation.startX;
799
+ const dy = endY - annotation.startY;
800
+ const lengthSquared = dx * dx + dy * dy;
801
+ // Zero-length segment: t stays 0 and we measure to the single point
802
+ let t = 0;
803
+ if (lengthSquared > 0) {
804
+ t = ((x - annotation.startX) * dx + (y - annotation.startY) * dy) / lengthSquared;
805
+ t = Math.max(0, Math.min(1, t));
806
+ }
807
+ const closestX = annotation.startX + t * dx;
808
+ const closestY = annotation.startY + t * dy;
809
+ const distance = Math.hypot(x - closestX, y - closestY);
738
810
  return distance <= tolerance;
739
811
  }
740
812
  case 'text': {
@@ -759,6 +831,8 @@ const CanvasEditor = class {
759
831
  this.screenshotTakingText = 'Taking screenshot...';
760
832
  this.screenshotAttachedText = 'Screenshot attached';
761
833
  this.screenshotButtonText = 'Add a screenshot';
834
+ this.escapeClose = true;
835
+ this.clickOutsideClose = true;
762
836
  this.autoStartScreenshot = false;
763
837
  this.existingScreenshot = '';
764
838
  this.editTextButtonText = 'Edit Text';
@@ -814,10 +888,12 @@ const CanvasEditor = class {
814
888
  // Add window resize listener for canvas adaptation
815
889
  this.handleWindowResize = this.handleWindowResize.bind(this);
816
890
  window.addEventListener('resize', this.handleWindowResize);
891
+ document.addEventListener('keydown', this.handleKeyDown);
817
892
  }
818
893
  disconnectedCallback() {
819
894
  // Clean up resize listener
820
895
  window.removeEventListener('resize', this.handleWindowResize);
896
+ document.removeEventListener('keydown', this.handleKeyDown);
821
897
  }
822
898
  async captureViewportScreenshot() {
823
899
  try {
@@ -841,6 +917,30 @@ const CanvasEditor = class {
841
917
  video.autoplay = true;
842
918
  video.muted = true;
843
919
  return new Promise((resolve, reject) => {
920
+ // If neither onloadedmetadata nor onerror ever fires, the promise would
921
+ // never settle: the capture stream would stay live with the browser's
922
+ // recording indicator on, and the widget would sit in "Taking
923
+ // screenshot..." forever with no way back short of a page reload.
924
+ let settled = false;
925
+ const stopStream = () => {
926
+ stream.getTracks().forEach((track) => track.stop());
927
+ };
928
+ const finish = (fn) => {
929
+ if (settled) {
930
+ return;
931
+ }
932
+ settled = true;
933
+ clearTimeout(watchdog);
934
+ video.onloadedmetadata = null;
935
+ video.onerror = null;
936
+ fn();
937
+ };
938
+ const watchdog = setTimeout(() => {
939
+ finish(() => {
940
+ stopStream();
941
+ reject(new Error('Screenshot capture timed out'));
942
+ });
943
+ }, CAPTURE_TIMEOUT_MS);
844
944
  video.onloadedmetadata = () => {
845
945
  video.play();
846
946
  // Wait a moment for video to stabilize
@@ -852,22 +952,26 @@ const CanvasEditor = class {
852
952
  canvas.height = video.videoHeight;
853
953
  const ctx = canvas.getContext('2d');
854
954
  ctx.drawImage(video, 0, 0);
855
- // Stop the stream
856
- stream.getTracks().forEach((track) => track.stop());
857
955
  // Convert to data URL
858
956
  const dataUrl = canvas.toDataURL('image/png');
859
- console.log('Screenshot captured successfully using Screen Capture API');
860
- resolve(dataUrl);
957
+ finish(() => {
958
+ stopStream();
959
+ resolve(dataUrl);
960
+ });
861
961
  }
862
962
  catch (error) {
863
- stream.getTracks().forEach((track) => track.stop());
864
- reject(error);
963
+ finish(() => {
964
+ stopStream();
965
+ reject(error);
966
+ });
865
967
  }
866
968
  }, 100);
867
969
  };
868
970
  video.onerror = () => {
869
- stream.getTracks().forEach((track) => track.stop());
870
- reject(new Error('Failed to load video for screenshot capture'));
971
+ finish(() => {
972
+ stopStream();
973
+ reject(new Error('Failed to load video for screenshot capture'));
974
+ });
871
975
  };
872
976
  });
873
977
  }
@@ -876,9 +980,27 @@ const CanvasEditor = class {
876
980
  throw error;
877
981
  }
878
982
  }
983
+ // Whether a freshly drawn shape is too small to be worth keeping
984
+ isDegenerateAnnotation(annotation) {
985
+ const MIN_SIZE = 2; // canvas pixels
986
+ if (annotation.type === 'line' || annotation.type === 'arrow') {
987
+ if (!Number.isFinite(annotation.endX) || !Number.isFinite(annotation.endY)) {
988
+ return true;
989
+ }
990
+ return (Math.hypot(annotation.endX - annotation.startX, annotation.endY - annotation.startY) <
991
+ MIN_SIZE);
992
+ }
993
+ if (annotation.type === 'rectangle') {
994
+ if (!Number.isFinite(annotation.width) || !Number.isFinite(annotation.height)) {
995
+ return true;
996
+ }
997
+ return Math.abs(annotation.width) < MIN_SIZE && Math.abs(annotation.height) < MIN_SIZE;
998
+ }
999
+ return false;
1000
+ }
879
1001
  render() {
880
1002
  var _a, _b, _c, _d, _e, _f;
881
- return (h("div", { class: "canvas-editor-wrapper" }, this.showCanvasEditor && (h("div", { class: "canvas-editor-overlay", part: "overlay" }, h("div", { class: "canvas-editor-modal", part: "modal" }, h("div", { class: "canvas-editor-header", part: "header" }, h("div", { class: "canvas-editor-title", part: "title" }, h("h3", null, this.canvasEditorTitle)), h("div", { class: "canvas-editor-toolbar", part: "toolbar" }, h("div", { class: "toolbar-section" }, h("div", { class: "tool-group" }, h("button", { class: `tool-btn ${this.canvasDrawingTool === 'rectangle' ? 'active' : ''}`, part: "tool-button", onClick: () => (this.canvasDrawingTool = 'rectangle'), title: "Rectangle" }, 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" }, h("rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }))), h("button", { class: `tool-btn ${this.canvasDrawingTool === 'line' ? 'active' : ''}`, part: "tool-button", onClick: () => (this.canvasDrawingTool = 'line'), title: "Line" }, 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" }, h("path", { d: "M5 12h14" }))), h("button", { class: `tool-btn ${this.canvasDrawingTool === 'arrow' ? 'active' : ''}`, part: "tool-button", onClick: () => (this.canvasDrawingTool = 'arrow'), title: "Arrow" }, 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" }, h("path", { d: "M13 5H19V11" }), h("path", { d: "M19 5L5 19" }))), h("button", { class: `tool-btn ${this.canvasDrawingTool === 'text' ? 'active' : ''}`, part: "tool-button", onClick: () => (this.canvasDrawingTool = 'text'), title: "Text" }, 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" }, h("path", { d: "M12 4v16" }), h("path", { d: "M4 7V5a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v2" }), h("path", { d: "M9 20h6" }))), h("div", { class: "toolbar-divider" }), h("button", { class: "tool-btn undo-btn", part: "tool-button", onClick: this.undoLastAnnotation, disabled: this.annotations.length === 0, title: "Undo" }, 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" }, h("path", { d: "M3 7v6h6" }), h("path", { d: "M21 17a9 9 0 0 0-9-9 9 9 0 0 0-6 2.3L3 13" }))), this.selectedAnnotation && (h("button", { class: "tool-btn delete-btn", part: "tool-button", onClick: this.deleteSelectedAnnotation, title: "Delete" }, 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" }, h("path", { d: "M3 6h18" }), h("path", { d: "M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" }), h("path", { d: "M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" }), h("line", { x1: "10", x2: "10", y1: "11", y2: "17" }), h("line", { x1: "14", x2: "14", y1: "11", y2: "17" })))))), h("div", { class: "toolbar-section" }, h("div", { class: "color-palette" }, this.defaultColors.map((color, index) => (h("div", { class: "color-slot-wrapper" }, h("button", { class: `color-btn ${this.canvasDrawingColor === color ? 'active' : ''} ${this.editingColorIndex === index ? 'editing' : ''}`, style: { backgroundColor: color }, onClick: () => this.handleColorSlotClick(index), title: `Color ${index + 1} - Click to customize` }, this.editingColorIndex === index && (h("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "white", "stroke-width": "2" }, 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 && this.showColorPicker && (h("div", { class: "color-picker-dropdown" }, h("input", { type: "color", value: color, onInput: (e) => this.handleColorPickerInput(e), onClick: (e) => this.handleColorPickerClick(e) })))))))), (this.selectedAnnotation || this.canvasDrawingTool) && (h("div", { class: "toolbar-section selected-annotation-controls" }, (((_a = this.selectedAnnotation) === null || _a === void 0 ? void 0 : _a.type) === 'text' ||
1003
+ return (h("div", { class: "canvas-editor-wrapper" }, this.showCanvasEditor && (h("div", { class: "canvas-editor-overlay", part: "overlay", onMouseDown: this.handleOverlayMouseDown, onClick: this.handleOverlayClick }, h("div", { class: "canvas-editor-modal", part: "modal", role: "dialog", "aria-modal": "true", "aria-labelledby": "canvas-editor-title" }, h("div", { class: "canvas-editor-header", part: "header" }, h("div", { class: "canvas-editor-title", part: "title" }, h("h3", { id: "canvas-editor-title" }, this.canvasEditorTitle)), h("div", { class: "canvas-editor-toolbar", part: "toolbar" }, h("div", { class: "toolbar-section" }, h("div", { class: "tool-group" }, h("button", { class: `tool-btn ${this.canvasDrawingTool === 'rectangle' ? 'active' : ''}`, part: "tool-button", onClick: () => (this.canvasDrawingTool = 'rectangle'), title: "Rectangle" }, 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" }, h("rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }))), h("button", { class: `tool-btn ${this.canvasDrawingTool === 'line' ? 'active' : ''}`, part: "tool-button", onClick: () => (this.canvasDrawingTool = 'line'), title: "Line" }, 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" }, h("path", { d: "M5 12h14" }))), h("button", { class: `tool-btn ${this.canvasDrawingTool === 'arrow' ? 'active' : ''}`, part: "tool-button", onClick: () => (this.canvasDrawingTool = 'arrow'), title: "Arrow" }, 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" }, h("path", { d: "M13 5H19V11" }), h("path", { d: "M19 5L5 19" }))), h("button", { class: `tool-btn ${this.canvasDrawingTool === 'text' ? 'active' : ''}`, part: "tool-button", onClick: () => (this.canvasDrawingTool = 'text'), title: "Text" }, 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" }, h("path", { d: "M12 4v16" }), h("path", { d: "M4 7V5a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v2" }), h("path", { d: "M9 20h6" }))), h("div", { class: "toolbar-divider" }), h("button", { class: "tool-btn undo-btn", part: "tool-button", onClick: this.undoLastAnnotation, disabled: this.annotations.length === 0, title: "Undo" }, 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" }, h("path", { d: "M3 7v6h6" }), h("path", { d: "M21 17a9 9 0 0 0-9-9 9 9 0 0 0-6 2.3L3 13" }))), this.selectedAnnotation && (h("button", { class: "tool-btn delete-btn", part: "tool-button", onClick: this.deleteSelectedAnnotation, title: "Delete" }, 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" }, h("path", { d: "M3 6h18" }), h("path", { d: "M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" }), h("path", { d: "M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" }), h("line", { x1: "10", x2: "10", y1: "11", y2: "17" }), h("line", { x1: "14", x2: "14", y1: "11", y2: "17" })))))), h("div", { class: "toolbar-section" }, h("div", { class: "color-palette" }, this.defaultColors.map((color, index) => (h("div", { class: "color-slot-wrapper" }, h("button", { class: `color-btn ${this.canvasDrawingColor === color ? 'active' : ''} ${this.editingColorIndex === index ? 'editing' : ''}`, style: { backgroundColor: color }, onClick: () => this.handleColorSlotClick(index), title: `Color ${index + 1} - Click to customize` }, this.editingColorIndex === index && (h("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "white", "stroke-width": "2" }, 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 && this.showColorPicker && (h("div", { class: "color-picker-dropdown" }, h("input", { type: "color", value: color, onInput: (e) => this.handleColorPickerInput(e), onClick: (e) => this.handleColorPickerClick(e) })))))))), (this.selectedAnnotation || this.canvasDrawingTool) && (h("div", { class: "toolbar-section selected-annotation-controls" }, (((_a = this.selectedAnnotation) === null || _a === void 0 ? void 0 : _a.type) === 'text' ||
882
1004
  (!this.selectedAnnotation && this.canvasDrawingTool === 'text')) && (h("div", { class: "text-controls" }, h("div", { class: "font-size-control" }, h("label", null, this.sizeLabelText), h("input", { type: "range", min: "8", max: "72", value: ((_b = this.selectedAnnotation) === null || _b === void 0 ? void 0 : _b.fontSize) || this.canvasTextSize, onInput: (e) => {
883
1005
  const newSize = parseInt(e.target.value);
884
1006
  if (this.selectedAnnotation) {
@@ -902,7 +1024,7 @@ const CanvasEditor = class {
902
1024
  };
903
1025
  CanvasEditor.style = canvasEditorCss;
904
1026
 
905
- 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}}";
1027
+ 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}}";
906
1028
 
907
1029
  const FeedbackButton = class {
908
1030
  constructor(hostRef) {
@@ -916,8 +1038,11 @@ const FeedbackButton = class {
916
1038
  this.sessionId = '';
917
1039
  this.metadata = '';
918
1040
  this.submit = false;
1041
+ this.apiKey = '';
1042
+ this.clickOutsideClose = true;
919
1043
  this.customFont = false;
920
1044
  this.emailAddress = '';
1045
+ this.historyClose = true;
921
1046
  this.isEmailRequired = false;
922
1047
  this.fetchData = true;
923
1048
  this.hideEmail = false;
@@ -930,6 +1055,7 @@ const FeedbackButton = class {
930
1055
  this.ratingMode = 'thumbs';
931
1056
  this.emailPlaceholder = 'Email address (optional)';
932
1057
  this.errorMessage = 'Please try again later.';
1058
+ this.errorMessage401 = 'This project requires a valid API key to submit feedback. Check the api-key attribute.';
933
1059
  this.errorMessage403 = 'The request URL does not match the one defined in PushFeedback for this project.';
934
1060
  this.errorMessage404 = 'We could not find the provided project id in PushFeedback.';
935
1061
  this.footerText = '';
@@ -975,13 +1101,19 @@ const FeedbackButton = class {
975
1101
  }
976
1102
  }
977
1103
  componentDidLoad() {
978
- if (this.buttonPosition === 'center-right') {
1104
+ if (this.buttonPosition === 'center-right' || this.buttonPosition === 'center-left') {
979
1105
  const buttonContent = this.el.shadowRoot.querySelector('.feedback-button-content');
980
1106
  let adjustement = 0;
981
1107
  if (this.isSafariBrowser()) {
982
1108
  adjustement = 5;
983
1109
  }
984
- buttonContent.style.right = `${((buttonContent.offsetWidth + adjustement) / 2) * -1}px`;
1110
+ const offset = `${((buttonContent.offsetWidth + adjustement) / 2) * -1}px`;
1111
+ if (this.buttonPosition === 'center-right') {
1112
+ buttonContent.style.right = offset;
1113
+ }
1114
+ else {
1115
+ buttonContent.style.left = offset;
1116
+ }
985
1117
  }
986
1118
  if (!this.customFont) {
987
1119
  this.loadInterFont();
@@ -990,9 +1122,12 @@ const FeedbackButton = class {
990
1122
  connectedCallback() {
991
1123
  this.feedbackModal = document.createElement('feedback-modal');
992
1124
  const props = [
1125
+ 'apiKey',
1126
+ 'clickOutsideClose',
993
1127
  'customFont',
994
1128
  'emailAddress',
995
1129
  'fetchData',
1130
+ 'historyClose',
996
1131
  'hideEmail',
997
1132
  'hidePrivacyPolicy',
998
1133
  'hideRating',
@@ -1018,6 +1153,7 @@ const FeedbackButton = class {
1018
1153
  'screenshotErrorUnexpected',
1019
1154
  'emailPlaceholder',
1020
1155
  'errorMessage',
1156
+ 'errorMessage401',
1021
1157
  'errorMessage403',
1022
1158
  'errorMessage404',
1023
1159
  'footerText',
@@ -1082,12 +1218,16 @@ const FeedbackButton = class {
1082
1218
  }
1083
1219
  }
1084
1220
  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 }));
1085
- const res = await fetch('https://app.pushfeedback.com/api/feedback/', {
1221
+ const headers = {
1222
+ 'Content-Type': 'application/json',
1223
+ };
1224
+ if (this.apiKey) {
1225
+ headers['Authorization'] = `Api-Key ${this.apiKey}`;
1226
+ }
1227
+ const res = await fetch('https://app.pushfeedback.com/api/v1/feedback/', {
1086
1228
  method: 'POST',
1087
1229
  body: JSON.stringify(body),
1088
- headers: {
1089
- 'Content-Type': 'application/json',
1090
- },
1230
+ headers,
1091
1231
  });
1092
1232
  if (res.status === 201) {
1093
1233
  const feedback_with_id = Object.assign(Object.assign({}, body), { id: await res.json() });
@@ -1118,19 +1258,139 @@ const FeedbackButton = class {
1118
1258
  }
1119
1259
  }
1120
1260
  render() {
1121
- return (h(Host, null, 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' && (h("span", { class: "feedback-button-content-icon", part: "icon" }, 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" }, h("path", { d: "M12 20h9" }), h("path", { d: "M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z" })))), h("slot", null))));
1261
+ return (h(Host, null, 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' && (h("span", { class: "feedback-button-content-icon", part: "icon" }, 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" }, h("path", { d: "M12 20h9" }), h("path", { d: "M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z" })))), h("slot", null))));
1122
1262
  }
1123
1263
  get el() { return getElement(this); }
1124
1264
  };
1125
1265
  FeedbackButton.style = feedbackButtonCss;
1126
1266
 
1127
- 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}}";
1267
+ // Tags allowed in the rich-text props (privacy policy, reCAPTCHA notice,
1268
+ // footer). These only ever need links and light inline emphasis.
1269
+ const ALLOWED_TAGS = new Set([
1270
+ 'A',
1271
+ 'B',
1272
+ 'BR',
1273
+ 'CODE',
1274
+ 'EM',
1275
+ 'I',
1276
+ 'P',
1277
+ 'SMALL',
1278
+ 'SPAN',
1279
+ 'STRONG',
1280
+ 'U',
1281
+ ]);
1282
+ // No `class`: an injected class name cannot reach the host page's CSS (this
1283
+ // renders inside a shadow root), but it could match the widget's own internal
1284
+ // styles. The default prop values do not use classes, so dropping it is free.
1285
+ const ALLOWED_ATTRS = new Set(['href', 'target', 'rel', 'title']);
1286
+ // Schemes permitted in href. Anything else, notably javascript:, is dropped.
1287
+ const SAFE_URL = /^(https?:|mailto:|tel:|#|\/|\.\/|\.\.\/)/i;
1288
+ /**
1289
+ * Removes anything that can execute from a fragment: script and style
1290
+ * elements, event-handler attributes, javascript: URLs, and any tag not on the
1291
+ * allowlist. Text inside a disallowed tag is kept, so stripping a wrapper does
1292
+ * not silently delete the words inside it.
1293
+ */
1294
+ function scrub(root) {
1295
+ const elements = Array.from(root.querySelectorAll('*'));
1296
+ for (const el of elements) {
1297
+ // The node may already have been removed along with an ancestor
1298
+ if (!root.contains(el)) {
1299
+ continue;
1300
+ }
1301
+ const tag = el.tagName.toUpperCase();
1302
+ if (tag === 'SCRIPT' || tag === 'STYLE' || tag === 'IFRAME' || tag === 'OBJECT') {
1303
+ el.remove();
1304
+ continue;
1305
+ }
1306
+ if (!ALLOWED_TAGS.has(tag)) {
1307
+ // Unwrap: keep the text, drop the element
1308
+ el.replaceWith(...Array.from(el.childNodes));
1309
+ continue;
1310
+ }
1311
+ for (const attr of Array.from(el.attributes)) {
1312
+ const name = attr.name.toLowerCase();
1313
+ if (name.startsWith('on') || !ALLOWED_ATTRS.has(name)) {
1314
+ el.removeAttribute(attr.name);
1315
+ continue;
1316
+ }
1317
+ if (name === 'href' && !SAFE_URL.test(attr.value.trim())) {
1318
+ el.removeAttribute(attr.name);
1319
+ }
1320
+ }
1321
+ // Links opening a new tab must not hand the opener over
1322
+ if (tag === 'A' && el.getAttribute('target') === '_blank') {
1323
+ el.setAttribute('rel', 'noopener noreferrer');
1324
+ }
1325
+ }
1326
+ }
1327
+ /**
1328
+ * Sanitizes a fragment of HTML for use with innerHTML.
1329
+ *
1330
+ * Prefers the browser's native Element.setHTML() where available, which parses
1331
+ * without executing. Otherwise parses in an inert document (no scripts run, no
1332
+ * images or other subresources are fetched) and scrubs the result.
1333
+ */
1334
+ function sanitizeHtml(html) {
1335
+ if (!html) {
1336
+ return '';
1337
+ }
1338
+ const template = document.createElement('template');
1339
+ // Native sanitizer, when the browser has it
1340
+ const withSetHtml = template;
1341
+ if (typeof withSetHtml.setHTML === 'function') {
1342
+ try {
1343
+ withSetHtml.setHTML(html);
1344
+ scrub(template.content);
1345
+ return template.innerHTML;
1346
+ }
1347
+ catch (_a) {
1348
+ // Fall through to the manual path
1349
+ }
1350
+ }
1351
+ // Inert parsing: content inside <template> is not rendered, so scripts do not
1352
+ // run and no subresource requests are made while we clean it up.
1353
+ template.innerHTML = html;
1354
+ scrub(template.content);
1355
+ return template.innerHTML;
1356
+ }
1357
+
1358
+ 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}}";
1128
1359
 
1129
1360
  const FeedbackModal = class {
1130
1361
  constructor(hostRef) {
1131
1362
  registerInstance(this, hostRef);
1132
1363
  this.feedbackSent = createEvent(this, "feedbackSent", 7);
1133
1364
  this.feedbackError = createEvent(this, "feedbackError", 7);
1365
+ // Track the history entry pushed while the modal is open
1366
+ this.historyEntryPushed = false;
1367
+ // Element that had focus before the modal opened, so it can be restored on close
1368
+ this.previouslyFocused = null;
1369
+ this.handleKeyDown = (event) => {
1370
+ // The canvas editor renders its own overlay and handles its own keys
1371
+ if (!this.showModal || this.embedded || this.showCanvasEditor) {
1372
+ return;
1373
+ }
1374
+ if (event.key === 'Escape' && this.escapeClose) {
1375
+ event.preventDefault();
1376
+ // Escape is an accidental-dismissal path like clicking outside, so keep
1377
+ // whatever the user already typed
1378
+ this.doClose(!this.formSuccess);
1379
+ return;
1380
+ }
1381
+ if (event.key === 'Tab') {
1382
+ this.trapFocus(event);
1383
+ }
1384
+ };
1385
+ this.handlePopState = () => {
1386
+ // Back button pressed while the modal is open: close it and consume our entry
1387
+ if (this.historyEntryPushed) {
1388
+ this.historyEntryPushed = false;
1389
+ if (this.showModal) {
1390
+ this.close();
1391
+ }
1392
+ }
1393
+ };
1134
1394
  this.onScrollDebounced = () => {
1135
1395
  clearTimeout(this.scrollTimeout);
1136
1396
  this.scrollTimeout = setTimeout(() => {
@@ -1165,12 +1425,16 @@ const FeedbackModal = class {
1165
1425
  }
1166
1426
  }
1167
1427
  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 }));
1168
- const res = await fetch('https://app.pushfeedback.com/api/feedback/', {
1428
+ const headers = {
1429
+ 'Content-Type': 'application/json',
1430
+ };
1431
+ if (this.apiKey) {
1432
+ headers['Authorization'] = `Api-Key ${this.apiKey}`;
1433
+ }
1434
+ const res = await fetch('https://app.pushfeedback.com/api/v1/feedback/', {
1169
1435
  method: 'POST',
1170
1436
  body: JSON.stringify(body),
1171
- headers: {
1172
- 'Content-Type': 'application/json',
1173
- },
1437
+ headers,
1174
1438
  });
1175
1439
  if (res.status === 201) {
1176
1440
  const feedback_with_id = Object.assign(Object.assign({}, body), { id: await res.json() });
@@ -1215,14 +1479,22 @@ const FeedbackModal = class {
1215
1479
  }
1216
1480
  };
1217
1481
  this.close = () => {
1482
+ this.doClose(false);
1483
+ };
1484
+ // Closing by clicking outside keeps the typed input, so an accidental
1485
+ // click does not throw away a half-written message or screenshot
1486
+ this.handleOverlayClick = () => {
1487
+ this.doClose(!this.formSuccess);
1488
+ };
1489
+ this.doClose = (preserveInput) => {
1218
1490
  this.isAnimating = false;
1491
+ this.popHistoryEntry();
1219
1492
  setTimeout(() => {
1220
1493
  this.sending = false;
1221
1494
  this.showModal = false;
1222
1495
  this.showScreenshotMode = false;
1223
1496
  this.showScreenshotTopBar = false;
1224
1497
  this.hasSelectedElement = false;
1225
- this.encodedScreenshot = null;
1226
1498
  // Remove highlight from ALL selected elements
1227
1499
  document.querySelectorAll('.feedback-modal-element-selected').forEach((el) => {
1228
1500
  el.classList.remove('feedback-modal-element-selected');
@@ -1231,9 +1503,19 @@ const FeedbackModal = class {
1231
1503
  this.formSuccess = false;
1232
1504
  this.formError = false;
1233
1505
  this.formErrorStatus = 500;
1234
- this.formMessage = '';
1235
- this.formEmail = '';
1506
+ if (!preserveInput) {
1507
+ this.encodedScreenshot = null;
1508
+ this.formMessage = '';
1509
+ this.formEmail = '';
1510
+ // The rating and the privacy checkbox are part of the same in-progress
1511
+ // input. Leaving the rating set meant reopening the modal showed the
1512
+ // previous session's choice still highlighted, and submitting then sent
1513
+ // that stale rating instead of one chosen for this submission.
1514
+ this.selectedRating = this.initialRating();
1515
+ this.isPrivacyChecked = false;
1516
+ }
1236
1517
  this.resetOverflow();
1518
+ this.restoreFocus();
1237
1519
  }, 200);
1238
1520
  };
1239
1521
  // Handle screenshot events from canvas editor
@@ -1243,12 +1525,16 @@ const FeedbackModal = class {
1243
1525
  this.takingScreenshot = false;
1244
1526
  this.showCanvasEditor = false;
1245
1527
  this.autoStartCapture = false;
1528
+ this.focusModal();
1246
1529
  };
1247
1530
  this.handleScreenshotCancelled = () => {
1248
1531
  this.showModal = true;
1249
1532
  this.takingScreenshot = false;
1250
1533
  this.showCanvasEditor = false;
1251
1534
  this.autoStartCapture = false;
1535
+ // Focus was inside the editor that just went away, so bring it back to the
1536
+ // form rather than letting it fall to the top of the page
1537
+ this.focusModal();
1252
1538
  };
1253
1539
  this.handleScreenshotError = (event) => {
1254
1540
  console.error('Screenshot error:', event.detail.error);
@@ -1260,6 +1546,7 @@ const FeedbackModal = class {
1260
1546
  this.takingScreenshot = false;
1261
1547
  this.showCanvasEditor = false;
1262
1548
  this.autoStartCapture = false;
1549
+ this.focusModal();
1263
1550
  // Auto-hide error after 8 seconds
1264
1551
  setTimeout(() => {
1265
1552
  this.showScreenshotError = false;
@@ -1301,6 +1588,7 @@ const FeedbackModal = class {
1301
1588
  this.screenshotError = '';
1302
1589
  this.showCanvasEditor = false;
1303
1590
  this.autoStartCapture = false;
1591
+ this.apiKey = '';
1304
1592
  this.customFont = false;
1305
1593
  this.emailAddress = '';
1306
1594
  this.hideEmail = false;
@@ -1318,8 +1606,12 @@ const FeedbackModal = class {
1318
1606
  this.metadata = undefined;
1319
1607
  this.fetchData = true;
1320
1608
  this.embedded = false;
1609
+ this.clickOutsideClose = true;
1610
+ this.historyClose = true;
1611
+ this.escapeClose = true;
1321
1612
  this.emailPlaceholder = 'Email address (optional)';
1322
1613
  this.errorMessage = 'Please try again later.';
1614
+ this.errorMessage401 = 'This project requires a valid API key to submit feedback. Check the api-key attribute.';
1323
1615
  this.errorMessage403 = 'The request URL does not match the one defined in PushFeedback for this project.';
1324
1616
  this.errorMessage404 = 'We could not find the provided project ID in PushFeedback.';
1325
1617
  this.messagePlaceholder = 'Share your thoughts...';
@@ -1332,6 +1624,10 @@ const FeedbackModal = class {
1332
1624
  this.ratingPlaceholder = 'Was this page helpful?';
1333
1625
  this.ratingStarsPlaceholder = 'How would you rate this page?';
1334
1626
  this.sendButtonText = 'Send';
1627
+ this.closeButtonLabel = 'Close feedback form';
1628
+ this.ratingPositiveLabel = 'Yes';
1629
+ this.ratingNegativeLabel = 'No';
1630
+ this.ratingStarLabel = 'Rate {rating} out of 5';
1335
1631
  this.successMessage = '';
1336
1632
  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.';
1337
1633
  this.screenshotAttachedText = 'Screenshot attached';
@@ -1352,16 +1648,98 @@ const FeedbackModal = class {
1352
1648
  this.screenshotErrorBrowserNotSupported = 'Your browser does not support screen capture. Please use a browser like Chrome, Firefox, or Safari on desktop.';
1353
1649
  this.screenshotErrorUnexpected = 'An unexpected error occurred. Please try again.';
1354
1650
  }
1651
+ connectedCallback() {
1652
+ window.addEventListener('popstate', this.handlePopState);
1653
+ document.addEventListener('keydown', this.handleKeyDown);
1654
+ }
1655
+ disconnectedCallback() {
1656
+ window.removeEventListener('popstate', this.handlePopState);
1657
+ document.removeEventListener('keydown', this.handleKeyDown);
1658
+ }
1659
+ // Keeps Tab cycling inside the dialog while it is open
1660
+ trapFocus(event) {
1661
+ var _a, _b;
1662
+ const focusable = this.getFocusableElements();
1663
+ if (focusable.length === 0) {
1664
+ return;
1665
+ }
1666
+ const first = focusable[0];
1667
+ const last = focusable[focusable.length - 1];
1668
+ const active = (_a = this.modalContent) === null || _a === void 0 ? void 0 : _a.getRootNode();
1669
+ const current = active === null || active === void 0 ? void 0 : active.activeElement;
1670
+ if (current === this.modalContent) {
1671
+ // Focus is on the dialog container itself (where it lands on open).
1672
+ // Tab falls through to the first control natively; Shift+Tab would
1673
+ // escape the dialog, so wrap it to the last one.
1674
+ if (event.shiftKey) {
1675
+ event.preventDefault();
1676
+ last.focus();
1677
+ }
1678
+ }
1679
+ else if (event.shiftKey && current === first) {
1680
+ event.preventDefault();
1681
+ last.focus();
1682
+ }
1683
+ else if (!event.shiftKey && current === last) {
1684
+ event.preventDefault();
1685
+ first.focus();
1686
+ }
1687
+ else if (!current || !((_b = this.modalContent) === null || _b === void 0 ? void 0 : _b.contains(current))) {
1688
+ // Focus escaped the dialog (or never entered it) - pull it back in
1689
+ event.preventDefault();
1690
+ first.focus();
1691
+ }
1692
+ }
1693
+ // offsetParent is not usable here: it is null for `position: fixed` elements
1694
+ // even when they are perfectly visible, and it is non-null for
1695
+ // `visibility: hidden` ones, which cannot be focused. getClientRects covers
1696
+ // display:none and detached nodes, so visibility is the only extra check.
1697
+ isFocusable(el) {
1698
+ if (el.hidden || el.getClientRects().length === 0) {
1699
+ return false;
1700
+ }
1701
+ return getComputedStyle(el).visibility !== 'hidden';
1702
+ }
1703
+ getFocusableElements() {
1704
+ if (!this.modalContent) {
1705
+ return [];
1706
+ }
1707
+ const selector = 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
1708
+ return Array.from(this.modalContent.querySelectorAll(selector)).filter((el) => this.isFocusable(el));
1709
+ }
1710
+ // Moves focus to the dialog container once it has rendered, so screen readers
1711
+ // announce the dialog title rather than jumping straight to the first control
1712
+ focusModal() {
1713
+ requestAnimationFrame(() => {
1714
+ if (this.modalContent) {
1715
+ this.modalContent.focus();
1716
+ }
1717
+ });
1718
+ }
1719
+ restoreFocus() {
1720
+ const target = this.previouslyFocused;
1721
+ this.previouslyFocused = null;
1722
+ if (target && typeof target.focus === 'function' && target.isConnected) {
1723
+ target.focus();
1724
+ }
1725
+ }
1726
+ // The rating the widget starts from. Closing restores this rather than
1727
+ // blanking to neutral, so a consumer's configured `rating` default survives
1728
+ // the modal being closed and reopened.
1729
+ initialRating() {
1730
+ if (this.ratingMode == 'thumbs' && this.rating == 0) {
1731
+ return 5;
1732
+ }
1733
+ if (this.rating) {
1734
+ return this.rating;
1735
+ }
1736
+ return -1;
1737
+ }
1355
1738
  componentWillLoad() {
1356
1739
  if (this.fetchData)
1357
1740
  this.fetchProjectData();
1358
1741
  this.formEmail = this.emailAddress;
1359
- if (this.rating) {
1360
- this.selectedRating = this.rating;
1361
- }
1362
- if (this.ratingMode == 'thumbs' && this.rating == 0) {
1363
- this.selectedRating = 5;
1364
- }
1742
+ this.selectedRating = this.initialRating();
1365
1743
  // Automatically show modal when embedded
1366
1744
  if (this.embedded) {
1367
1745
  this.showModal = true;
@@ -1369,7 +1747,9 @@ const FeedbackModal = class {
1369
1747
  }
1370
1748
  async fetchProjectData() {
1371
1749
  try {
1372
- const response = await fetch('https://app.pushfeedback.com/api/projects/' + this.project + '/');
1750
+ // Public config endpoint: never send the API key here, it is only
1751
+ // needed (and only allowed to travel) on the feedback POST
1752
+ const response = await fetch('https://app.pushfeedback.com/api/v1/projects/' + this.project + '/');
1373
1753
  const data = await response.json();
1374
1754
  this.whitelabel = data.whitelabel;
1375
1755
  this.recaptchaEnabled = data.recaptcha_enabled || false;
@@ -1442,6 +1822,12 @@ const FeedbackModal = class {
1442
1822
  handleEmailInput(event) {
1443
1823
  this.formEmail = event.target.value;
1444
1824
  }
1825
+ popHistoryEntry() {
1826
+ if (this.historyEntryPushed) {
1827
+ this.historyEntryPushed = false;
1828
+ history.back();
1829
+ }
1830
+ }
1445
1831
  handleCheckboxChange(event) {
1446
1832
  this.isPrivacyChecked = event.target.checked;
1447
1833
  }
@@ -1452,28 +1838,28 @@ const FeedbackModal = class {
1452
1838
  this.selectedRating = newRating;
1453
1839
  }
1454
1840
  render() {
1455
- return (h("div", { class: `feedback-modal-wrapper ${this.customFont ? 'feedback-modal-wrapper--custom-font' : ''} ${this.embedded ? 'feedback-modal-wrapper--embedded' : ''}`, part: "wrapper" }, this.showCanvasEditor && (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 && (h("div", { class: "screenshot-error-notification" }, h("div", { class: "screenshot-error-content" }, h("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, h("circle", { cx: "12", cy: "12", r: "10" }), h("line", { x1: "15", y1: "9", x2: "9", y2: "15" }), h("line", { x1: "9", y1: "9", x2: "15", y2: "15" })), h("span", null, this.screenshotError), h("button", { class: "error-close-btn", onClick: () => (this.showScreenshotError = false), title: "Close" }, h("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, h("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), h("line", { x1: "6", y1: "6", x2: "18", y2: "18" })))))), this.showModal && !this.embedded && (h("div", { class: `feedback-overlay ${this.isAnimating ? 'feedback-overlay--visible' : ''}`, part: "overlay" })), this.showModal && (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) }, h("div", { class: `feedback-modal-header ${(this.formSuccess && !this.successMessage) || (this.formError && !this.errorMessage)
1841
+ return (h("div", { class: `feedback-modal-wrapper ${this.customFont ? 'feedback-modal-wrapper--custom-font' : ''} ${this.embedded ? 'feedback-modal-wrapper--embedded' : ''}`, part: "wrapper" }, this.showCanvasEditor && (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 && (h("div", { class: "screenshot-error-notification" }, h("div", { class: "screenshot-error-content" }, h("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, h("circle", { cx: "12", cy: "12", r: "10" }), h("line", { x1: "15", y1: "9", x2: "9", y2: "15" }), h("line", { x1: "9", y1: "9", x2: "15", y2: "15" })), h("span", null, this.screenshotError), h("button", { class: "error-close-btn", onClick: () => (this.showScreenshotError = false), title: "Close" }, h("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, h("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), h("line", { x1: "6", y1: "6", x2: "18", y2: "18" })))))), this.showModal && !this.embedded && (h("div", { class: `feedback-overlay ${this.isAnimating ? 'feedback-overlay--visible' : ''}`, part: "overlay", "aria-hidden": "true", onClick: this.clickOutsideClose ? this.handleOverlayClick : undefined })), this.showModal && (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) }, h("div", { class: `feedback-modal-header ${(this.formSuccess && !this.successMessage) || (this.formError && !this.errorMessage)
1456
1842
  ? 'feedback-modal-header--no-content'
1457
- : ''}`, part: "header" }, h("div", { class: "feedback-modal-header-content" }, !this.formSuccess && !this.formError ? (h("div", { class: "feedback-modal-header-text" }, h("span", { class: "feedback-modal-title", part: "title" }, this.modalTitle), !this.whitelabel && (h("span", { class: "feedback-modal-powered-by", part: "powered-by" }, "Powered by", ' ', h("a", { target: "_blank", href: "https://pushfeedback.com" }, "PushFeedback"))))) : this.formSuccess ? (h("span", { class: "feedback-modal-title", part: "title" }, this.modalTitleSuccess)) : (h("span", { class: "feedback-modal-title", part: "title" }, this.modalTitleError))), !this.embedded && (h("button", { class: "feedback-modal-close", part: "close-button", onClick: this.close }, 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" }, h("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), h("line", { x1: "6", y1: "6", x2: "18", y2: "18" }))))), h("div", { class: "feedback-modal-body", part: "body" }, !this.formSuccess && !this.formError ? (h("form", { part: "form", onSubmit: this.handleSubmit }, !this.hideRating && (h("div", { class: "feedback-modal-rating", part: "rating" }, this.ratingMode === 'thumbs' ? (h("div", { class: "feedback-modal-rating-content" }, h("span", { class: "feedback-modal-input-heading", part: "rating-title" }, this.ratingPlaceholder), h("div", { class: "feedback-modal-rating-buttons feedback-modal-rating-buttons--thumbs", part: "rating-buttons" }, h("button", { title: "Yes", class: `feedback-modal-rating-button feedback-modal-rating-button--positive ${this.selectedRating === 1
1843
+ : ''}`, part: "header" }, h("div", { class: "feedback-modal-header-content" }, !this.formSuccess && !this.formError ? (h("div", { class: "feedback-modal-header-text" }, h("span", { id: "feedback-modal-title", class: "feedback-modal-title", part: "title" }, this.modalTitle), !this.whitelabel && (h("span", { class: "feedback-modal-powered-by", part: "powered-by" }, "Powered by", ' ', h("a", { target: "_blank", href: "https://pushfeedback.com" }, "PushFeedback"))))) : this.formSuccess ? (h("span", { id: "feedback-modal-title", class: "feedback-modal-title", part: "title" }, this.modalTitleSuccess)) : (h("span", { id: "feedback-modal-title", class: "feedback-modal-title", part: "title" }, this.modalTitleError))), !this.embedded && (h("button", { class: "feedback-modal-close", part: "close-button", type: "button", "aria-label": this.closeButtonLabel, onClick: this.close }, 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" }, h("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), h("line", { x1: "6", y1: "6", x2: "18", y2: "18" }))))), h("div", { class: "feedback-modal-body", part: "body" }, !this.formSuccess && !this.formError ? (h("form", { part: "form", onSubmit: this.handleSubmit }, !this.hideRating && (h("div", { class: "feedback-modal-rating", part: "rating" }, this.ratingMode === 'thumbs' ? (h("div", { class: "feedback-modal-rating-content" }, h("span", { class: "feedback-modal-input-heading", part: "rating-title" }, this.ratingPlaceholder), h("div", { class: "feedback-modal-rating-buttons feedback-modal-rating-buttons--thumbs", part: "rating-buttons" }, 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
1458
1844
  ? 'feedback-modal-rating-button--selected'
1459
1845
  : ''}`, part: "rating-button rating-button-positive", onClick: (event) => {
1460
1846
  event.preventDefault();
1461
1847
  this.handleRatingChange(1);
1462
- } }, 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" }, 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" }))), h("button", { title: "No", class: `feedback-modal-rating-button feedback-modal-rating-button--negative ${this.selectedRating === 5
1848
+ } }, 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" }, 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" }))), 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
1463
1849
  ? 'feedback-modal-rating-button--selected'
1464
1850
  : ''}`, part: "rating-button rating-button-negative", onClick: (event) => {
1465
1851
  event.preventDefault();
1466
1852
  this.handleRatingChange(5);
1467
- } }, 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" }, 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" })))))) : (h("div", { class: "feedback-modal-rating-content" }, h("span", { class: "feedback-modal-input-heading", part: "rating-title" }, this.ratingStarsPlaceholder), h("div", { class: "feedback-modal-rating-buttons feedback-modal-rating-buttons--stars", part: "rating-buttons" }, [1, 2, 3, 4, 5].map((rating) => (h("button", { key: rating, class: `feedback-modal-rating-button ${this.selectedRating >= rating
1853
+ } }, 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" }, 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" })))))) : (h("div", { class: "feedback-modal-rating-content" }, h("span", { class: "feedback-modal-input-heading", part: "rating-title" }, this.ratingStarsPlaceholder), h("div", { class: "feedback-modal-rating-buttons feedback-modal-rating-buttons--stars", part: "rating-buttons" }, [1, 2, 3, 4, 5].map((rating) => (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
1468
1854
  ? 'feedback-modal-rating-button--selected'
1469
1855
  : ''}`, part: "rating-button", onClick: (event) => {
1470
1856
  event.preventDefault();
1471
1857
  this.handleRatingChange(rating);
1472
- } }, 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" }, 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" })))))))))), h("div", { class: "feedback-modal-text" }, h("textarea", { part: "message-input", placeholder: this.messagePlaceholder, value: this.formMessage, onInput: (event) => this.handleMessageInput(event) })), !this.hideEmail && (h("div", { class: "feedback-modal-email" }, h("input", { part: "email-input", placeholder: this.emailPlaceholder, type: "email", onInput: (event) => this.handleEmailInput(event), value: this.formEmail, required: this.isEmailRequired }))), h("div", { class: "feedback-verification" }, h("input", { type: "text", name: "verification", style: { display: 'none' }, onInput: (event) => this.handleVerification(event), value: this.formVerification })), !this.hidePrivacyPolicy && (h("div", { class: "feedback-modal-privacy", part: "privacy" }, h("input", { type: "checkbox", id: "privacyPolicy", onChange: (ev) => this.handleCheckboxChange(ev), required: true }), h("span", { innerHTML: this.privacyPolicyText }))), h("div", { class: `feedback-modal-buttons ${this.hideScreenshotButton ? 'single' : ''}`, part: "buttons" }, !this.hideScreenshotButton && (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 && (h("div", { class: "screenshot-preview", onClick: this.openCanvasEditor }, h("img", { src: this.encodedScreenshot, alt: "Screenshot Preview" }))), !this.encodedScreenshot && !this.takingScreenshot && (h("svg", { xmlns: "http://www.w3.org/2000/svg", height: "24", viewBox: "0 -960 960 960", width: "24" }, h("path", { d: "M680-80v-120H560v-80h120v-120h80v120h120v80H760v120h-80ZM200-200v-200h80v120h120v80H200Zm0-360v-200h200v80H280v120h-80Zm480 0v-120H560v-80h200v200h-80Z" }))), this.takingScreenshot && (h("div", { class: "screenshot-loading" }, 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" }, h("line", { x1: "12", y1: "2", x2: "12", y2: "6" }), h("line", { x1: "12", y1: "18", x2: "12", y2: "22" }), h("line", { x1: "4.93", y1: "4.93", x2: "7.76", y2: "7.76" }), h("line", { x1: "16.24", y1: "16.24", x2: "19.07", y2: "19.07" }), h("line", { x1: "2", y1: "12", x2: "6", y2: "12" }), h("line", { x1: "18", y1: "12", x2: "22", y2: "12" }), h("line", { x1: "4.93", y1: "19.07", x2: "7.76", y2: "16.24" }), h("line", { x1: "16.24", y1: "7.76", x2: "19.07", y2: "4.93" })))), this.takingScreenshot
1858
+ } }, 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" }, 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" })))))))))), h("div", { class: "feedback-modal-text" }, h("textarea", { part: "message-input", placeholder: this.messagePlaceholder, value: this.formMessage, onInput: (event) => this.handleMessageInput(event) })), !this.hideEmail && (h("div", { class: "feedback-modal-email" }, h("input", { part: "email-input", placeholder: this.emailPlaceholder, type: "email", onInput: (event) => this.handleEmailInput(event), value: this.formEmail, required: this.isEmailRequired }))), h("div", { class: "feedback-verification" }, h("input", { type: "text", name: "verification", style: { display: 'none' }, onInput: (event) => this.handleVerification(event), value: this.formVerification })), !this.hidePrivacyPolicy && (h("div", { class: "feedback-modal-privacy", part: "privacy" }, h("input", { type: "checkbox", id: "privacyPolicy", onChange: (ev) => this.handleCheckboxChange(ev), required: true }), h("span", { innerHTML: sanitizeHtml(this.privacyPolicyText) }))), h("div", { class: `feedback-modal-buttons ${this.hideScreenshotButton ? 'single' : ''}`, part: "buttons" }, !this.hideScreenshotButton && (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 && (h("div", { class: "screenshot-preview", onClick: this.openCanvasEditor }, h("img", { src: this.encodedScreenshot, alt: "Screenshot Preview" }))), !this.encodedScreenshot && !this.takingScreenshot && (h("svg", { xmlns: "http://www.w3.org/2000/svg", height: "24", viewBox: "0 -960 960 960", width: "24" }, h("path", { d: "M680-80v-120H560v-80h120v-120h80v120h120v80H760v120h-80ZM200-200v-200h80v120h120v80H200Zm0-360v-200h200v80H280v120h-80Zm480 0v-120H560v-80h200v200h-80Z" }))), this.takingScreenshot && (h("div", { class: "screenshot-loading" }, 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" }, h("line", { x1: "12", y1: "2", x2: "12", y2: "6" }), h("line", { x1: "12", y1: "18", x2: "12", y2: "22" }), h("line", { x1: "4.93", y1: "4.93", x2: "7.76", y2: "7.76" }), h("line", { x1: "16.24", y1: "16.24", x2: "19.07", y2: "19.07" }), h("line", { x1: "2", y1: "12", x2: "6", y2: "12" }), h("line", { x1: "18", y1: "12", x2: "22", y2: "12" }), h("line", { x1: "4.93", y1: "19.07", x2: "7.76", y2: "16.24" }), h("line", { x1: "16.24", y1: "7.76", x2: "19.07", y2: "4.93" })))), this.takingScreenshot
1473
1859
  ? this.screenshotTakingText
1474
1860
  : this.encodedScreenshot
1475
1861
  ? this.screenshotAttachedText
1476
- : this.screenshotButtonText)), h("button", { class: "feedback-modal-button feedback-modal-button--submit", part: "submit-button", type: "submit", disabled: this.sending }, this.sendButtonText)))) : this.formSuccess && !this.formError ? (h("div", { class: "feedback-modal-success" }, h("p", { class: "feedback-modal-message", part: "success-message" }, this.successMessage))) : this.formError && this.formErrorStatus == 404 ? (h("p", { class: "feedback-modal-message", part: "error-message" }, this.errorMessage404)) : this.formError && this.formErrorStatus == 403 ? (h("p", { class: "feedback-modal-message", part: "error-message" }, this.errorMessage403)) : this.formError ? (h("p", { class: "feedback-modal-message", part: "error-message" }, this.errorMessage)) : (h("span", null))), !this.formSuccess && !this.formError && (this.recaptchaEnabled || this.footerText) && (h("div", { class: "feedback-modal-footer", part: "footer" }, h("div", { class: "feedback-footer-combined" }, this.recaptchaEnabled && h("span", { innerHTML: this.recaptchaText }), this.recaptchaEnabled && this.footerText && ' ', this.footerText && h("span", { innerHTML: this.footerText }))))))));
1862
+ : this.screenshotButtonText)), h("button", { class: "feedback-modal-button feedback-modal-button--submit", part: "submit-button", type: "submit", disabled: this.sending }, this.sendButtonText)))) : this.formSuccess && !this.formError ? (h("div", { class: "feedback-modal-success" }, h("p", { class: "feedback-modal-message", part: "success-message" }, this.successMessage))) : this.formError && this.formErrorStatus == 404 ? (h("p", { class: "feedback-modal-message", part: "error-message" }, this.errorMessage404)) : this.formError && this.formErrorStatus == 401 ? (h("p", { class: "feedback-modal-message", part: "error-message" }, this.errorMessage401)) : this.formError && this.formErrorStatus == 403 ? (h("p", { class: "feedback-modal-message", part: "error-message" }, this.errorMessage403)) : this.formError ? (h("p", { class: "feedback-modal-message", part: "error-message" }, this.errorMessage)) : (h("span", null))), !this.formSuccess && !this.formError && (this.recaptchaEnabled || this.footerText) && (h("div", { class: "feedback-modal-footer", part: "footer" }, h("div", { class: "feedback-footer-combined" }, this.recaptchaEnabled && (h("span", { innerHTML: sanitizeHtml(this.recaptchaText) })), this.recaptchaEnabled && this.footerText && ' ', this.footerText && h("span", { innerHTML: sanitizeHtml(this.footerText) }))))))));
1477
1863
  }
1478
1864
  componentDidRender() {
1479
1865
  if (this.showModal) {
@@ -1483,7 +1869,17 @@ const FeedbackModal = class {
1483
1869
  }
1484
1870
  }
1485
1871
  async openModal() {
1872
+ // Remember the trigger so focus can return there when the modal closes
1873
+ if (!this.embedded) {
1874
+ const active = document.activeElement;
1875
+ this.previouslyFocused = active && active !== document.body ? active : null;
1876
+ }
1486
1877
  this.showModal = true;
1878
+ this.focusModal();
1879
+ if (this.historyClose && !this.embedded && !this.historyEntryPushed) {
1880
+ history.pushState({ feedbackModal: true }, '');
1881
+ this.historyEntryPushed = true;
1882
+ }
1487
1883
  requestAnimationFrame(() => {
1488
1884
  requestAnimationFrame(() => {
1489
1885
  this.isAnimating = true;