pushfeedback 0.1.69 → 0.1.71

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.
Files changed (27) hide show
  1. package/dist/cjs/{feedback-button_2.cjs.entry.js → canvas-editor_3.cjs.entry.js} +883 -718
  2. package/dist/cjs/loader.cjs.js +1 -1
  3. package/dist/cjs/pushfeedback.cjs.js +1 -1
  4. package/dist/collection/collection-manifest.json +1 -0
  5. package/dist/collection/components/canvas-editor/canvas-editor.css +404 -0
  6. package/dist/collection/components/canvas-editor/canvas-editor.js +1282 -0
  7. package/dist/collection/components/feedback-button/feedback-button.js +220 -0
  8. package/dist/collection/components/feedback-modal/feedback-modal.css +2 -458
  9. package/dist/collection/components/feedback-modal/feedback-modal.js +247 -782
  10. package/dist/components/canvas-editor.d.ts +11 -0
  11. package/dist/components/canvas-editor.js +6 -0
  12. package/dist/components/canvas-editor2.js +917 -0
  13. package/dist/components/feedback-button.js +40 -1
  14. package/dist/components/feedback-modal2.js +68 -784
  15. package/dist/components/index.d.ts +1 -0
  16. package/dist/components/index.js +1 -0
  17. package/dist/esm/{feedback-button_2.entry.js → canvas-editor_3.entry.js} +883 -719
  18. package/dist/esm/loader.js +1 -1
  19. package/dist/esm/pushfeedback.js +1 -1
  20. package/dist/pushfeedback/p-2c39091c.entry.js +1 -0
  21. package/dist/pushfeedback/pushfeedback.esm.js +1 -1
  22. package/dist/types/components/canvas-editor/canvas-editor.d.ts +108 -0
  23. package/dist/types/components/feedback-button/feedback-button.d.ts +11 -0
  24. package/dist/types/components/feedback-modal/feedback-modal.d.ts +22 -79
  25. package/dist/types/components.d.ts +102 -0
  26. package/package.json +3 -4
  27. package/dist/pushfeedback/p-e7f48090.entry.js +0 -1
@@ -4,420 +4,99 @@ Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  const index = require('./index-9a8f4784.js');
6
6
 
7
- const feedbackButtonCss = ".feedback-button-content{cursor:pointer;max-width:fit-content;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}}";
7
+ const canvasEditorCss = ":host{display:block}.canvas-editor-wrapper{position:relative}.canvas-editor-overlay{position:fixed;top:0;left:0;width:100vw;height:100vh;background:rgba(0, 0, 0, 0.8);display:flex;align-items:center;justify-content:center;z-index:9999}.canvas-editor-modal{width:95vw;max-width:1400px;max-height:900px;background:var(--feedback-canvas-editor-bg-color, #ffffff);border-radius:8px;border:1px solid var(--feedback-canvas-editor-border-color, #e0e0e0);display:flex;flex-direction:column;overflow:hidden;box-shadow:0 10px 40px rgba(0, 0, 0, 0.2)}.canvas-editor-header{background:var(--feedback-canvas-editor-header-bg-color, #f5f5f5);border-bottom:1px solid var(--feedback-canvas-editor-border-color, #e0e0e0);padding:12px 16px;display:flex;flex-direction:column;gap:12px}.canvas-editor-title h3{margin:0;font-size:16px;font-weight:600;color:var(--feedback-canvas-editor-tool-text-color, #333)}.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:1px solid var(--feedback-canvas-editor-border-color, #e0e0e0);border-radius:6px;cursor:pointer;transition:all 0.2s ease;padding:0}.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, #f0f0f0)}.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)}.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{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){background:var(--feedback-canvas-editor-tool-bg-hover, #f0f0f0) !important}.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:6px;border:2px solid transparent;cursor:pointer;transition:all 0.2s ease;display:flex;align-items:center;justify-content:center;background:var(--feedback-canvas-editor-tool-bg-color, #ffffff);border:1px solid var(--feedback-canvas-editor-border-color, #e0e0e0)}.color-btn:hover{transform:scale(1.1)}.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:20px;-webkit-appearance:none;appearance:none;background:transparent;cursor:pointer}.size-slider::-webkit-slider-track{width:100%;height:4px;background:var(--feedback-canvas-editor-slider-track, #e0e0e0);border-radius:2px}.size-slider::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:16px;height:16px;background:var(--feedback-primary-color, #0070f4);border-radius:50%;cursor:pointer}.size-slider::-moz-range-track{width:100%;height:4px;background:var(--feedback-canvas-editor-slider-track, #e0e0e0);border-radius:2px;border:none}.size-slider::-moz-range-thumb{width:16px;height:16px;background:var(--feedback-primary-color, #0070f4);border-radius:50%;border:none;cursor:pointer}.size-slider::-ms-track{width:100%;height:4px;background:var(--feedback-canvas-editor-slider-track, #e0e0e0);border-radius:2px;border:none;color:transparent}.size-slider::-ms-thumb{width:16px;height:16px;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:6px;padding:5px 12px;border:1px solid var(--feedback-canvas-editor-border-color, #e0e0e0);border-radius:6px;cursor:pointer;font-size:12px;font-weight:500;transition:all 0.2s ease;min-width:65px;justify-content:center;height:36px}.action-btn.secondary{background:var(--feedback-canvas-editor-tool-bg-color, #ffffff);color:var(--feedback-canvas-editor-tool-text-color, #333);border-color:var(--feedback-canvas-editor-border-color, #e0e0e0)}.action-btn.secondary:hover{background:var(--feedback-canvas-editor-tool-bg-hover, #f0f0f0)}.action-btn.primary{background:var(--feedback-primary-color, #0070f4);color:#ffffff;border-color:var(--feedback-primary-color, #0070f4)}.action-btn.primary:hover{background:#0056cc;border-color:#0056cc}.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:16px;background:var(--feedback-canvas-editor-content-bg, #f5f5f5);overflow:hidden;min-height:0;min-width:0}.annotation-canvas{max-width:100%;max-height:100%;width:auto;height:auto;cursor:crosshair;border-radius:6px;box-shadow:0px 1px 2px 0px rgba(60, 64, 67, .30), 0px 2px 6px 2px rgba(60, 64, 67, .15);background:#ffffff;transition:box-shadow 0.3s ease;object-fit:contain;display:block}.annotation-canvas:hover{box-shadow:0px 2px 4px 0px rgba(60, 64, 67, .35), 0px 4px 12px 4px rgba(60, 64, 67, .20)}@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}}";
8
8
 
9
- const FeedbackButton = class {
9
+ const CanvasEditor = class {
10
10
  constructor(hostRef) {
11
11
  index.registerInstance(this, hostRef);
12
- this.feedbackSent = index.createEvent(this, "feedbackSent", 7);
13
- this.feedbackError = index.createEvent(this, "feedbackError", 7);
14
- this.buttonPosition = 'default';
15
- this.buttonStyle = 'default';
16
- this.hideIcon = false;
17
- this.hideMobile = false;
18
- this.sessionId = '';
19
- this.metadata = '';
20
- this.submit = false;
21
- this.customFont = false;
22
- this.emailAddress = '';
23
- this.isEmailRequired = false;
24
- this.fetchData = true;
25
- this.hideEmail = false;
26
- this.hidePrivacyPolicy = true;
27
- this.hideRating = false;
28
- this.hideScreenshotButton = false;
29
- this.modalPosition = 'center';
30
- this.project = '';
31
- this.rating = undefined;
32
- this.ratingMode = 'thumbs';
33
- this.canvasEditorTitle = 'Edit screenshot';
34
- this.canvasEditorCancelText = 'Cancel';
35
- this.canvasEditorSaveText = 'Save';
36
- this.emailPlaceholder = 'Email address (optional)';
37
- this.errorMessage = 'Please try again later.';
38
- this.errorMessage403 = 'The request URL does not match the one defined in PushFeedback for this project.';
39
- this.errorMessage404 = 'We could not find the provided project id in PushFeedback.';
40
- this.footerText = '';
41
- this.messagePlaceholder = 'Comments';
42
- this.modalTitle = 'Share your feedback';
43
- this.modalTitleError = 'Oops!';
44
- this.modalTitleSuccess = 'Thanks for your feedback!';
45
- this.privacyPolicyText = "I have read and expressly consent to the terms of the <a href='https://pushfeedback.com/privacy'>Privacy Policy</a>.";
46
- this.ratingPlaceholder = 'Was this page helpful?';
47
- this.ratingStarsPlaceholder = 'How would you rate this page?';
48
- this.screenshotAttachedText = 'Screenshot attached';
49
- this.screenshotButtonText = 'Add a screenshot';
50
- this.screenshotTakingText = 'Taking screenshot...';
51
- this.screenshotTopbarText = 'Select an element on this page';
52
- this.sendButtonText = 'Send';
53
- this.successMessage = '';
54
- }
55
- componentWillLoad() {
56
- if (!this.sessionId) {
57
- let storedSessionId = localStorage.getItem('pushfeedback_sessionid');
58
- if (!storedSessionId) {
59
- storedSessionId = this.generateRandomSessionId();
60
- localStorage.setItem('pushfeedback_sessionid', storedSessionId);
61
- this.sessionId = storedSessionId;
12
+ this.screenshotReady = index.createEvent(this, "screenshotReady", 7);
13
+ this.screenshotCancelled = index.createEvent(this, "screenshotCancelled", 7);
14
+ this.screenshotFailed = index.createEvent(this, "screenshotFailed", 7);
15
+ this.openScreenShot = async () => {
16
+ // Show loading state immediately
17
+ this.takingScreenshot = true;
18
+ // Clear any previous annotations when taking a new screenshot
19
+ this.annotations = [];
20
+ this.currentAnnotation = null;
21
+ this.isDrawing = false;
22
+ this.hoveredAnnotation = null;
23
+ // Hide any feedback buttons on the page
24
+ this.hideAllFeedbackElements();
25
+ try {
26
+ // Wait a moment for UI to update before capturing
27
+ await new Promise(resolve => setTimeout(resolve, 100));
28
+ // Capture viewport screenshot using browser API
29
+ const dataUrl = await this.captureViewportScreenshot();
30
+ this.originalImageData = dataUrl;
31
+ // Reset loading state
32
+ this.takingScreenshot = false;
33
+ // Go directly to canvas editor
34
+ this.showCanvasEditor = true;
35
+ // Restore feedback elements visibility
36
+ this.showAllFeedbackElements();
37
+ // Initialize canvas after a short delay to ensure DOM is ready
38
+ setTimeout(() => {
39
+ this.initializeCanvas();
40
+ }, 100);
62
41
  }
63
- }
64
- else {
65
- localStorage.setItem('pushfeedback_sessionid', this.sessionId);
66
- }
67
- }
68
- componentDidLoad() {
69
- if (this.buttonPosition === 'center-right') {
70
- const buttonContent = this.el.shadowRoot.querySelector('.feedback-button-content');
71
- let adjustement = 0;
72
- if (this.isSafariBrowser()) {
73
- adjustement = 5;
42
+ catch (error) {
43
+ console.error('Failed to capture screenshot:', error);
44
+ // Reset loading state on error
45
+ this.takingScreenshot = false;
46
+ // Restore feedback elements on error
47
+ this.showAllFeedbackElements();
48
+ // Show error message to user
49
+ this.handleScreenshotError(error);
74
50
  }
75
- buttonContent.style.right = `${((buttonContent.offsetWidth + adjustement) / 2) * -1}px`;
76
- }
77
- if (!this.customFont) {
78
- this.loadInterFont();
79
- }
80
- }
81
- connectedCallback() {
82
- this.feedbackModal = document.createElement('feedback-modal');
83
- const props = [
84
- 'customFont',
85
- 'emailAddress',
86
- 'fetchData',
87
- 'hideEmail',
88
- 'hidePrivacyPolicy',
89
- 'hideRating',
90
- 'hideScreenshotButton',
91
- 'isEmailRequired',
92
- 'modalPosition',
93
- 'project',
94
- 'rating',
95
- 'ratingMode',
96
- 'canvasEditorTitle',
97
- 'canvasEditorCancelText',
98
- 'canvasEditorSaveText',
99
- 'emailPlaceholder',
100
- 'errorMessage',
101
- 'errorMessage403',
102
- 'errorMessage404',
103
- 'footerText',
104
- 'messagePlaceholder',
105
- 'metadata',
106
- 'modalTitle',
107
- 'modalTitleError',
108
- 'modalTitleSuccess',
109
- 'privacyPolicyText',
110
- 'ratingPlaceholder',
111
- 'ratingStarsPlaceholder',
112
- 'screenshotAttachedText',
113
- 'screenshotButtonText',
114
- 'screenshotTakingText',
115
- 'screenshotTopbarText',
116
- 'sendButtonText',
117
- 'successMessage',
118
- ];
119
- props.forEach((prop) => {
120
- this.feedbackModal[prop] = this[prop];
121
- });
122
- document.body.appendChild(this.feedbackModal);
123
- }
124
- disconnectedCallback() {
125
- document.body.removeChild(this.feedbackModal);
126
- }
127
- generateRandomSessionId(length = 16) {
128
- return Math.random().toString(36).substr(2, length);
129
- }
130
- isSafariBrowser() {
131
- const isSafari = /safari/i.test(navigator.userAgent) && !/chrome/i.test(navigator.userAgent);
132
- return isSafari;
133
- }
134
- loadInterFont() {
135
- const link = document.createElement('link');
136
- link.href = 'https://fonts.googleapis.com/css2?family=Inter:wght@400;500&display=swap';
137
- link.rel = 'stylesheet';
138
- document.head.appendChild(link);
139
- }
140
- showModal() {
141
- if (this.submit) {
142
- this.submitRatingFeedback();
143
- }
144
- else {
145
- this.feedbackModal.openModal();
146
- }
147
- }
148
- async submitRatingFeedback() {
149
- try {
150
- const body = {
151
- url: window.location.href,
152
- project: this.project,
153
- rating: this.rating || -1,
154
- ratingMode: this.ratingMode,
155
- message: '',
156
- metadata: this.metadata,
157
- session: localStorage.getItem('pushfeedback_sessionid') || '',
158
- };
159
- const res = await fetch('https://app.pushfeedback.com/api/feedback/', {
160
- method: 'POST',
161
- body: JSON.stringify(body),
162
- headers: {
163
- 'Content-Type': 'application/json',
164
- },
51
+ };
52
+ this.hideAllFeedbackElements = () => {
53
+ // Hide all feedback buttons and modals on the page
54
+ const feedbackElements = document.querySelectorAll('feedback-button, feedback-modal');
55
+ feedbackElements.forEach(element => {
56
+ element.style.visibility = 'hidden';
165
57
  });
166
- if (res.status === 201) {
167
- const feedback_with_id = Object.assign(Object.assign({}, body), { id: await res.json() });
168
- this.feedbackSent.emit({ feedback: feedback_with_id });
58
+ };
59
+ this.showAllFeedbackElements = () => {
60
+ // Show all feedback buttons and modals on the page
61
+ const feedbackElements = document.querySelectorAll('feedback-button, feedback-modal');
62
+ feedbackElements.forEach(element => {
63
+ element.style.visibility = 'visible';
64
+ });
65
+ };
66
+ this.handleScreenshotError = (error) => {
67
+ let errorMessage = this.screenshotErrorGeneral;
68
+ if (error.name === 'NotAllowedError') {
69
+ errorMessage += ' ' + this.screenshotErrorPermission;
70
+ }
71
+ else if (error.name === 'NotSupportedError') {
72
+ errorMessage += ' ' + this.screenshotErrorNotSupported;
73
+ }
74
+ else if (error.name === 'NotFoundError') {
75
+ errorMessage += ' ' + this.screenshotErrorNotFound;
76
+ }
77
+ else if (error.name === 'AbortError') {
78
+ errorMessage += ' ' + this.screenshotErrorCancelled;
79
+ }
80
+ else if (error.message && error.message.includes('not supported')) {
81
+ errorMessage += ' ' + this.screenshotErrorBrowserNotSupported;
169
82
  }
170
83
  else {
171
- const errorText = await res.text();
172
- const response = {
173
- status: res.status,
174
- message: errorText,
175
- };
176
- this.feedbackError.emit({ error: response });
84
+ errorMessage += ' ' + this.screenshotErrorUnexpected;
177
85
  }
178
- }
179
- catch (error) {
180
- const response = {
181
- status: 500,
182
- message: error,
183
- };
184
- this.feedbackError.emit({ error: response });
185
- }
186
- }
187
- render() {
188
- return (index.h(index.Host, null, index.h("a", { class: `feedback-button-content feedback-button-content--${this.buttonStyle} feedback-button-content--${this.buttonPosition} ${this.customFont ? 'feedback-button-content--custom-font' : ''} ${this.hideMobile ? 'feedback-button-content--hide-mobile' : ''}`, onClick: () => this.showModal() }, !this.hideIcon && this.buttonStyle != 'default' && (index.h("span", { class: "feedback-button-content-icon" }, index.h("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "#fff", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", class: "icon-edit" }, index.h("path", { d: "M12 20h9" }), index.h("path", { d: "M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z" })))), index.h("slot", null))));
189
- }
190
- get el() { return index.getElement(this); }
191
- };
192
- FeedbackButton.style = feedbackButtonCss;
193
-
194
- 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.2s ease-out}.feedback-overlay--visible{opacity:1}.feedback-modal{display:inline-block;position:relative}.feedback-modal-content{background-color:var(--feedback-modal-content-bg-color);border-color:1px solid var(--feedback-modal-header-text-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-content-text-color);display:flex;flex-direction:column;left:50%;max-width:90%;padding:20px;position:fixed;top:50%;transform:translate(-50%, -50%) scale(0.95);opacity:0;width:100%;z-index:var(--feedback-modal-content-z-index);transition:transform 0.2s ease-out, opacity 0.2s ease-out}.feedback-modal-content--open{transform:translate(-50%, -50%) scale(1);opacity:1}.feedback-modal-header{align-items:center;color:var(--feedback-modal-header-text-color);display:flex;font-size:var(--feedback-header-font-size);font-weight:var(--feedback-modal-header-font-weight);justify-content:space-between;margin-bottom:20px}.feedback-modal-rating-buttons{width:100%;margin-bottom:20px}.feedback-modal-rating-button{padding:0;background-color:transparent;border:transparent;margin-right:5px;cursor:pointer}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button{border:1px 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:10px;justify-content:center;padding:5px 10px}.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:1px 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--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:1px 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:10px;resize:vertical;width:100%}.feedback-modal-email input{background-color:var(--feedback-modal-input-bg-color);border:1px 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:40px;padding:10px;width:100%;margin-bottom:20px}.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:1px solid var(--feedback-modal-input-border-color-focused);outline:none}.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:1px 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:40px;padding:5px 10px}.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:1px solid var(--feedback-modal-button-border-color-active);color:var(--feedback-modal-button-submit-text-color)}.feedback-modal-button:hover,.feedback-modal-button--active{background-color:var(--feedback-modal-button-bg-color-active);border:1px solid var(--feedback-modal-button-border-color-active);color:var(--feedback-modal-button-text-color-active)}.feedback-modal-button--submit:hover{background-color:var(--feedback-modal-button-submit-bg-color-hover);border:1px solid var(--feedback-modal-button-submit-border-color-hover);color:var(--feedback-modal-button-submit-text-color-hover)}.feedback-modal-input-heading{display:block;font-size:14px;font-weight:300;padding-bottom:10px}.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-modal-close{background-color:var(--feedback-modal-close-bg-color);border:0;border-radius:50%;cursor:pointer;height:22px;margin-left:auto;padding:0;width:22px}.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-top:0}.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:30px;height:30px;overflow:hidden;border-radius:4px;margin-right:10px;box-shadow:0 2px 4px rgba(0, 0, 0, 0.1);cursor:pointer;transition:transform 0.2s ease}.screenshot-preview:hover{transform:scale(1.1)}.screenshot-preview img{width:100%;height:100%;object-fit:cover}.screenshot-loading{display:inline-flex;align-items:center;margin-right:8px}.canvas-editor-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background-color:var(--feedback-modal-screenshot-bg-color);z-index:10001;display:flex;align-items:center;justify-content:center}.canvas-editor-modal{width:95vw;height:98vh;background:var(--feedback-canvas-editor-bg-color);border-radius:var(--feedback-modal-content-border-radius);display:flex;flex-direction:column;overflow:hidden;box-shadow:0px 1px 2px 0px rgba(60, 64, 67, .30), 0px 2px 6px 2px rgba(60, 64, 67, .15)}.canvas-editor-header{background:var(--feedback-canvas-editor-header-bg-color);border-bottom:1px solid var(--feedback-canvas-editor-border-color);padding:12px 16px;display:flex;flex-direction:column;gap:12px;flex-shrink:0}.canvas-editor-title h3{margin:0;font-size:var(--feedback-modal-header-font-size);font-weight:var(--feedback-modal-header-font-weight);color:var(--feedback-modal-header-text-color);font-family:var(--feedback-modal-header-font-family)}.canvas-editor-toolbar{display:flex;align-items:center;gap:20px;flex-wrap:wrap}.toolbar-section{display:flex;align-items:center}.toolbar-section:last-child{margin-left:auto;gap:10px}.tool-group{display:flex;align-items:center;background:var(--feedback-canvas-editor-tool-bg-color);border:1px solid var(--feedback-canvas-editor-border-color);border-radius:var(--feedback-modal-button-border-radius);padding:4px;box-shadow:0px 1px 2px 0px rgba(60, 64, 67, .10)}.tool-btn{display:flex;align-items:center;justify-content:center;width:36px;height:36px;border:none;background:none;border-radius:var(--feedback-modal-button-border-radius);cursor:pointer;color:var(--feedback-canvas-editor-tool-text-color);transition:all 0.2s ease;position:relative}.tool-btn:hover{background:var(--feedback-canvas-editor-tool-bg-hover);color:var(--feedback-modal-button-text-color-active)}.tool-btn.active{background:var(--feedback-canvas-editor-tool-bg-active);color:var(--feedback-canvas-editor-tool-text-active)}.tool-btn:disabled{opacity:0.4;cursor:not-allowed}.tool-btn:disabled:hover{background:none;color:var(--feedback-canvas-editor-tool-text-color)}.toolbar-divider{width:1px;height:20px;background:var(--feedback-canvas-editor-divider-color);margin:0 6px}.undo-btn{background:var(--feedback-canvas-editor-tool-bg-color) !important;border:1px solid var(--feedback-canvas-editor-border-color) !important}.undo-btn:hover:not(:disabled){background:var(--feedback-canvas-editor-tool-bg-hover) !important}.color-palette{display:flex;align-items:center;gap:6px;background:var(--feedback-canvas-editor-tool-bg-color);border:1px solid var(--feedback-canvas-editor-border-color);border-radius:var(--feedback-modal-button-border-radius);padding:6px;box-shadow:0px 1px 2px 0px rgba(60, 64, 67, .10)}.color-slot-wrapper{position:relative}.color-btn{width:28px;height:28px;border-radius:var(--feedback-modal-button-border-radius);border:2px solid transparent;cursor:pointer;transition:all 0.2s ease;position:relative;box-shadow:0px 1px 2px 0px rgba(60, 64, 67, .15);display:flex;align-items:center;justify-content:center}.color-btn:hover{transform:scale(1.05);box-shadow:0px 2px 4px 0px rgba(60, 64, 67, .25)}.color-btn.active{border-color:var(--feedback-primary-color);transform:scale(1.1);box-shadow:0 0 0 2px rgba(0, 112, 244, 0.2)}.color-btn.editing{border-color:var(--feedback-highlight-color);box-shadow:0 0 0 2px rgba(255, 180, 34, 0.3)}.color-btn.editing:hover{border-color:var(--feedback-highlight-color);box-shadow:0 0 0 2px rgba(255, 180, 34, 0.4)}.color-picker-dropdown{position:absolute;top:100%;left:50%;transform:translateX(-50%);margin-top:6px;background:var(--feedback-canvas-editor-tool-bg-color);border:1px solid var(--feedback-canvas-editor-border-color);border-radius:var(--feedback-modal-button-border-radius);padding:6px;box-shadow:0px 1px 2px 0px rgba(60, 64, 67, .30), 0px 2px 6px 2px rgba(60, 64, 67, .15);z-index:1000}.color-picker-dropdown input[type=\"color\"]{width:50px;height:32px;border:none;border-radius:var(--feedback-modal-button-border-radius);cursor:pointer}.size-control{display:flex;align-items:center;gap:10px;background:var(--feedback-canvas-editor-tool-bg-color);border:1px solid var(--feedback-canvas-editor-border-color);border-radius:var(--feedback-modal-button-border-radius);padding:6px 12px;box-shadow:0px 1px 2px 0px rgba(60, 64, 67, .10)}.size-slider{width:70px;height:4px;border-radius:2px;background:var(--feedback-canvas-editor-slider-track);outline:none;-webkit-appearance:none;-moz-appearance:none;appearance:none;cursor:pointer;border:none}.size-slider::-webkit-slider-track{width:100%;height:4px;cursor:pointer;background:var(--feedback-canvas-editor-slider-track);border-radius:2px;border:none;box-shadow:none}.size-slider::-webkit-slider-thumb{-webkit-appearance:none;width:14px;height:14px;border-radius:50%;background:var(--feedback-primary-color);cursor:pointer;box-shadow:0px 1px 2px 0px rgba(60, 64, 67, .20);border:none;margin-top:-5px;}.size-slider::-moz-range-track{width:100%;height:4px;cursor:pointer;background:var(--feedback-canvas-editor-slider-track);border-radius:2px;border:none;box-shadow:none}.size-slider::-moz-range-thumb{width:14px;height:14px;border-radius:50%;background:var(--feedback-primary-color);cursor:pointer;border:none;box-shadow:0px 1px 2px 0px rgba(60, 64, 67, .20)}.size-slider::-moz-range-progress{background:var(--feedback-canvas-editor-slider-track);height:4px;border-radius:2px}.size-slider::-ms-track{width:100%;height:4px;cursor:pointer;background:transparent;border-color:transparent;color:transparent}.size-slider::-ms-fill-lower{background:var(--feedback-canvas-editor-slider-track);border-radius:2px}.size-slider::-ms-fill-upper{background:var(--feedback-canvas-editor-slider-track);border-radius:2px}.size-slider::-ms-thumb{width:14px;height:14px;border-radius:50%;background:var(--feedback-primary-color);cursor:pointer;border:none}.size-value{font-weight:500;color:var(--feedback-canvas-editor-tool-text-color);font-size:var(--feedback-text-font-size);min-width:30px}.action-btn{display:flex;align-items:center;gap:6px;padding:5px 12px;border:1px solid var(--feedback-canvas-editor-action-secondary-border);border-radius:var(--feedback-modal-button-border-radius);cursor:pointer;font-size:var(--feedback-modal-button-font-size);font-weight:500;transition:all 0.2s ease;min-width:65px;justify-content:center;height:36px;font-family:var(--feedback-font-family)}.action-btn.secondary{background:var(--feedback-canvas-editor-action-secondary-bg);color:var(--feedback-canvas-editor-action-secondary-text);border-color:var(--feedback-canvas-editor-action-secondary-border)}.action-btn.secondary:hover{background:var(--feedback-canvas-editor-tool-bg-hover);color:var(--feedback-modal-button-text-color-active);border-color:var(--feedback-modal-button-border-color-active)}.action-btn.primary{background:var(--feedback-canvas-editor-action-primary-bg);color:var(--feedback-canvas-editor-action-primary-text);border-color:var(--feedback-modal-button-border-color-active)}.action-btn.primary:hover{background:var(--feedback-modal-button-submit-bg-color-hover);border-color:var(--feedback-modal-button-submit-border-color-hover)}.canvas-editor-content{flex:1;display:flex;align-items:center;justify-content:center;padding:16px;background:var(--feedback-canvas-editor-content-bg);overflow:hidden;min-height:0;min-width:0}.annotation-canvas{max-width:100%;max-height:100%;width:auto;height:auto;cursor:crosshair;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);background:var(--feedback-white-color);transition:box-shadow 0.3s ease;object-fit:contain;display:block}.annotation-canvas:hover{box-shadow:0px 2px 4px 0px rgba(60, 64, 67, .35), 0px 4px 12px 4px rgba(60, 64, 67, .20)}@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)}}@media (max-width: 768px){.canvas-editor-modal{width:100vw;height:100vh;border-radius:0}.canvas-editor-header{padding:8px 12px;gap:8px}.canvas-editor-toolbar{flex-direction:column;align-items:stretch;gap:12px}.toolbar-section{justify-content:center}.toolbar-section:last-child{margin-left:0;justify-content:stretch;gap:8px}.action-btn{flex:1;min-width:auto}.canvas-editor-content{padding:8px}.tool-group{flex-wrap:wrap;justify-content:center}.color-palette{flex-wrap:wrap;justify-content:center}.size-control{flex-direction:column;gap:6px;text-align:center}.size-slider{width:100px}}@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:1px solid #fcc;border-radius:8px;padding:12px 16px;display:flex;align-items:center;gap:12px;box-shadow:0 4px 12px rgba(0, 0, 0, 0.15);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:4px;border-radius:4px;color:#c53030;flex-shrink:0;transition:background-color 0.2s ease}.error-close-btn:hover{background:rgba(197, 48, 48, 0.1)}@media (max-width: 768px){.canvas-editor-toolbar .tool-group,.canvas-editor-toolbar .color-palette,.canvas-editor-toolbar .size-control,.canvas-editor-toolbar .toolbar-divider{display:none !important}.canvas-editor-toolbar{justify-content:center}.canvas-editor-toolbar .toolbar-section:first-child{display:none}.canvas-editor-toolbar .toolbar-section:nth-child(2){display:none}.canvas-editor-toolbar .toolbar-section:nth-child(3){display:none}.canvas-editor-title{display:none}}";
195
-
196
- const FeedbackModal = class {
197
- constructor(hostRef) {
198
- index.registerInstance(this, hostRef);
199
- this.feedbackSent = index.createEvent(this, "feedbackSent", 7);
200
- this.feedbackError = index.createEvent(this, "feedbackError", 7);
201
- this.onScrollDebounced = () => {
202
- clearTimeout(this.scrollTimeout);
203
- this.scrollTimeout = setTimeout(() => {
204
- document.documentElement.classList.remove('feedback-modal-screenshot-closing');
205
- document.documentElement.style.top = '';
206
- window.removeEventListener('scroll', this.onScrollDebounced);
207
- }, 200);
208
- };
209
- this.handleSubmit = async (event) => {
210
- event.preventDefault();
211
- if (this.isEmailRequired && !this.formEmail) {
212
- return;
213
- }
214
- this.resetOverflow();
215
- this.showScreenshotMode = false;
216
- this.showScreenshotTopBar = false;
217
- this.showModal = false;
218
- this.sending = true;
219
- try {
220
- const body = {
221
- url: window.location.href,
222
- message: this.formMessage,
223
- email: this.formEmail,
224
- project: this.project,
225
- screenshot: this.encodedScreenshot,
226
- rating: this.selectedRating,
227
- ratingMode: this.ratingMode,
228
- metadata: this.metadata,
229
- verification: this.formVerification,
230
- session: localStorage.getItem('pushfeedback_sessionid') || '',
231
- };
232
- const res = await fetch('https://app.pushfeedback.com/api/feedback/', {
233
- method: 'POST',
234
- body: JSON.stringify(body),
235
- headers: {
236
- 'Content-Type': 'application/json',
237
- },
238
- });
239
- if (res.status === 201) {
240
- const feedback_with_id = Object.assign(Object.assign({}, body), { id: await res.json() });
241
- this.feedbackSent.emit({ feedback: feedback_with_id });
242
- this.formSuccess = true;
243
- this.formError = false;
244
- }
245
- else {
246
- const errorText = await res.text();
247
- const response = {
248
- status: res.status,
249
- message: errorText,
250
- };
251
- this.feedbackError.emit({ error: response });
252
- this.formSuccess = false;
253
- this.formError = true;
254
- this.formErrorStatus = res.status;
255
- }
256
- }
257
- catch (error) {
258
- const response = {
259
- status: 500,
260
- message: error,
261
- };
262
- this.feedbackError.emit({ error: response });
263
- this.formSuccess = false;
264
- this.formError = true;
265
- this.formErrorStatus = 500;
266
- }
267
- finally {
268
- this.sending = false;
269
- this.showModal = true;
270
- }
271
- };
272
- this.close = () => {
273
- this.isAnimating = false;
274
- setTimeout(() => {
275
- this.sending = false;
276
- this.showModal = false;
277
- this.showScreenshotMode = false;
278
- this.showScreenshotTopBar = false;
279
- this.hasSelectedElement = false;
280
- this.encodedScreenshot = null;
281
- // Remove highlight from ALL selected elements
282
- document.querySelectorAll('.feedback-modal-element-selected').forEach(el => {
283
- el.classList.remove('feedback-modal-element-selected');
284
- });
285
- // Reset canvas editor states
286
- this.takingScreenshot = false;
287
- this.showPreviewModal = false;
288
- this.showCanvasEditor = false;
289
- this.annotations = [];
290
- this.currentAnnotation = null;
291
- this.isDrawing = false;
292
- this.canvasRef = null;
293
- this.canvasContext = null;
294
- this.originalImageData = null;
295
- // Reset error states
296
- this.showScreenshotError = false;
297
- this.screenshotError = '';
298
- // Reset resizing states
299
- this.isResizing = false;
300
- this.resizingAnnotation = null;
301
- this.resizeStartSize = 16;
302
- this.resizeStartDimensions = null;
303
- this.hoveredAnnotation = null;
304
- this.resizeHandle = false;
305
- // Reset form states
306
- this.formSuccess = false;
307
- this.formError = false;
308
- this.formErrorStatus = 500;
309
- this.formMessage = '';
310
- this.formEmail = '';
311
- this.resetOverflow();
312
- }, 200);
313
- };
314
- this.openScreenShot = async () => {
315
- // Show loading state immediately
316
- this.takingScreenshot = true;
317
- this.showScreenshotError = false;
318
- // Clear any previous annotations when taking a new screenshot
319
- this.annotations = [];
320
- this.currentAnnotation = null;
321
- this.isDrawing = false;
322
- this.hoveredAnnotation = null;
323
- // Hide the feedback modal temporarily to exclude it from screenshot
324
- const wasModalVisible = this.showModal;
325
- this.showModal = false;
326
- // Also hide any feedback buttons on the page
327
- this.hideAllFeedbackElements();
328
- try {
329
- // Wait a moment for UI to update before capturing
330
- await new Promise(resolve => setTimeout(resolve, 100));
331
- // Capture viewport screenshot using browser API
332
- const dataUrl = await this.captureViewportScreenshot();
333
- this.encodedScreenshot = dataUrl;
334
- this.originalImageData = dataUrl;
335
- // Reset loading state
336
- this.takingScreenshot = false;
337
- // Go directly to canvas editor (don't restore modal)
338
- this.showCanvasEditor = true;
339
- // Restore feedback elements visibility
340
- this.showAllFeedbackElements();
341
- // Initialize canvas after a short delay to ensure DOM is ready
342
- setTimeout(() => {
343
- this.initializeCanvas();
344
- }, 100);
345
- }
346
- catch (error) {
347
- console.error('Failed to capture screenshot:', error);
348
- // Reset loading state on error
349
- this.takingScreenshot = false;
350
- // Restore modal and feedback elements on error
351
- this.showModal = wasModalVisible;
352
- this.showAllFeedbackElements();
353
- // Show error message to user
354
- this.handleScreenshotError(error);
355
- }
356
- };
357
- this.hideAllFeedbackElements = () => {
358
- // Hide all feedback buttons and modals on the page
359
- const feedbackElements = document.querySelectorAll('feedback-button, feedback-modal');
360
- feedbackElements.forEach(element => {
361
- element.style.visibility = 'hidden';
362
- });
363
- };
364
- this.showAllFeedbackElements = () => {
365
- // Show all feedback buttons and modals on the page
366
- const feedbackElements = document.querySelectorAll('feedback-button, feedback-modal');
367
- feedbackElements.forEach(element => {
368
- element.style.visibility = 'visible';
369
- });
370
- };
371
- this.handleScreenshotError = (error) => {
372
- let errorMessage = 'Failed to capture screenshot. ';
373
- if (error.name === 'NotAllowedError') {
374
- errorMessage += 'Permission denied. Please allow screen sharing to take screenshots.';
375
- }
376
- else if (error.name === 'NotSupportedError') {
377
- errorMessage += 'Screen capture is not supported in this browser.';
378
- }
379
- else if (error.name === 'NotFoundError') {
380
- errorMessage += 'No screen sources available for capture.';
381
- }
382
- else if (error.name === 'AbortError') {
383
- errorMessage += 'Screenshot capture was cancelled.';
384
- }
385
- else if (error.message && error.message.includes('not supported')) {
386
- errorMessage += 'Your browser does not support screen capture. Please use a browser like Chrome, Firefox, or Safari.';
387
- }
388
- else {
389
- errorMessage += 'An unexpected error occurred. Please try again.';
390
- }
391
- this.screenshotError = errorMessage;
392
- this.showScreenshotError = true;
393
- // Auto-hide error after 5 seconds
394
- setTimeout(() => {
395
- this.showScreenshotError = false;
396
- }, 5000);
397
- };
398
- this.openCanvasEditor = (event) => {
399
- if (event) {
400
- event.stopPropagation();
401
- }
402
- this.showModal = false;
403
- this.showCanvasEditor = true;
404
- // Initialize canvas after a short delay to ensure DOM is ready
405
- setTimeout(() => {
406
- this.initializeCanvas();
407
- }, 100);
86
+ // Just emit the error to parent - don't show internal notification
87
+ this.screenshotFailed.emit({ error: errorMessage });
408
88
  };
409
89
  this.closeCanvasEditor = () => {
410
90
  this.showCanvasEditor = false;
411
- this.showModal = true;
91
+ this.screenshotCancelled.emit();
412
92
  };
413
93
  this.saveAnnotations = () => {
414
94
  if (this.canvasRef) {
415
95
  // Create final image with annotations
416
96
  const finalDataUrl = this.canvasRef.toDataURL('image/png');
417
- this.encodedScreenshot = finalDataUrl;
97
+ this.screenshotReady.emit({ screenshot: finalDataUrl });
418
98
  }
419
99
  this.showCanvasEditor = false;
420
- this.showModal = true;
421
100
  };
422
101
  this.initializeCanvas = () => {
423
102
  if (!this.canvasRef || !this.originalImageData)
@@ -429,31 +108,19 @@ const FeedbackModal = class {
429
108
  this.canvasRef.width = img.width;
430
109
  this.canvasRef.height = img.height;
431
110
  // Get available container dimensions
432
- const containerWidth = this.canvasRef.parentElement.clientWidth - 32; // Account for reduced padding (16px * 2)
111
+ const containerWidth = this.canvasRef.parentElement.clientWidth - 32;
433
112
  const containerHeight = this.canvasRef.parentElement.clientHeight - 32;
434
113
  // Calculate scale factors for both dimensions
435
114
  const scaleX = containerWidth / img.width;
436
115
  const scaleY = containerHeight / img.height;
437
116
  // Use the smaller scale to ensure complete image fits
438
- const scale = Math.min(scaleX, scaleY, 1); // Never scale up, only down
117
+ const scale = Math.min(scaleX, scaleY, 1);
439
118
  // Calculate final display dimensions
440
119
  const displayWidth = img.width * scale;
441
120
  const displayHeight = img.height * scale;
442
- // Set CSS size for display (this scales the canvas visually)
121
+ // Set CSS size for display
443
122
  this.canvasRef.style.width = `${displayWidth}px`;
444
123
  this.canvasRef.style.height = `${displayHeight}px`;
445
- console.log('Canvas initialized with complete image fit:', {
446
- originalWidth: img.width,
447
- originalHeight: img.height,
448
- displayWidth,
449
- displayHeight,
450
- scale,
451
- scaleX,
452
- scaleY,
453
- containerWidth,
454
- containerHeight,
455
- usingScale: scale === scaleX ? 'width-limited' : 'height-limited'
456
- });
457
124
  // Draw the original image at full resolution
458
125
  this.canvasContext.drawImage(img, 0, 0);
459
126
  // Redraw existing annotations
@@ -486,7 +153,11 @@ const FeedbackModal = class {
486
153
  switch (annotation.type) {
487
154
  case 'rectangle':
488
155
  this.canvasContext.strokeRect(annotation.startX, annotation.startY, annotation.width, annotation.height);
489
- // Draw resize handle if this annotation is hovered
156
+ // Draw selection indicator if this annotation is selected
157
+ if (this.selectedAnnotation === annotation) {
158
+ this.drawSelectionIndicator(annotation);
159
+ }
160
+ // Draw resize handles if this annotation is hovered
490
161
  if (this.hoveredAnnotation === annotation) {
491
162
  this.drawRectangleResizeHandles(annotation);
492
163
  }
@@ -496,6 +167,10 @@ const FeedbackModal = class {
496
167
  this.canvasContext.moveTo(annotation.startX, annotation.startY);
497
168
  this.canvasContext.lineTo(annotation.endX, annotation.endY);
498
169
  this.canvasContext.stroke();
170
+ // Draw selection indicator if this annotation is selected
171
+ if (this.selectedAnnotation === annotation) {
172
+ this.drawSelectionIndicator(annotation);
173
+ }
499
174
  // Draw resize handles if this annotation is hovered
500
175
  if (this.hoveredAnnotation === annotation) {
501
176
  this.drawLineResizeHandles(annotation);
@@ -503,38 +178,73 @@ const FeedbackModal = class {
503
178
  break;
504
179
  case 'arrow':
505
180
  this.drawArrow(annotation.startX, annotation.startY, annotation.endX, annotation.endY);
181
+ // Draw selection indicator if this annotation is selected
182
+ if (this.selectedAnnotation === annotation) {
183
+ this.drawSelectionIndicator(annotation);
184
+ }
506
185
  // Draw resize handles if this annotation is hovered
507
186
  if (this.hoveredAnnotation === annotation) {
508
- this.drawLineResizeHandles(annotation); // Same as line
187
+ this.drawLineResizeHandles(annotation);
509
188
  }
510
189
  break;
511
190
  case 'text':
512
- const fontSize = annotation.fontSize || 16;
191
+ const fontSize = annotation.fontSize || 24;
513
192
  this.canvasContext.fillStyle = annotation.color;
514
193
  this.canvasContext.font = `${fontSize}px Arial`;
515
194
  this.canvasContext.fillText(annotation.text, annotation.x, annotation.y);
516
- // Draw resize handle if this annotation is hovered
517
- if (this.hoveredAnnotation === annotation) {
518
- this.drawTextResizeHandle(annotation);
195
+ // Draw selection indicator if this annotation is selected
196
+ if (this.selectedAnnotation === annotation) {
197
+ this.drawTextSelectionIndicator(annotation);
519
198
  }
520
199
  break;
521
200
  }
522
201
  };
523
- // Draw resize handle for text annotation
524
- this.drawTextResizeHandle = (annotation) => {
525
- if (!this.canvasContext || annotation.type !== 'text')
202
+ // Draw selection indicator for shapes
203
+ this.drawSelectionIndicator = (annotation) => {
204
+ if (!this.canvasContext)
205
+ return;
206
+ // Save current context
207
+ const originalStrokeStyle = this.canvasContext.strokeStyle;
208
+ const originalLineWidth = this.canvasContext.lineWidth;
209
+ // Draw selection outline
210
+ this.canvasContext.strokeStyle = '#0070F4';
211
+ this.canvasContext.lineWidth = 2;
212
+ this.canvasContext.setLineDash([5, 5]);
213
+ switch (annotation.type) {
214
+ case 'rectangle':
215
+ this.canvasContext.strokeRect(annotation.startX - 2, annotation.startY - 2, annotation.width + 4, annotation.height + 4);
216
+ break;
217
+ case 'line':
218
+ case 'arrow':
219
+ this.canvasContext.beginPath();
220
+ this.canvasContext.moveTo(annotation.startX, annotation.startY);
221
+ this.canvasContext.lineTo(annotation.endX, annotation.endY);
222
+ this.canvasContext.stroke();
223
+ break;
224
+ }
225
+ // Restore context
226
+ this.canvasContext.setLineDash([]);
227
+ this.canvasContext.strokeStyle = originalStrokeStyle;
228
+ this.canvasContext.lineWidth = originalLineWidth;
229
+ };
230
+ // Draw selection indicator for text
231
+ this.drawTextSelectionIndicator = (annotation) => {
232
+ if (!this.canvasContext)
526
233
  return;
527
- const fontSize = annotation.fontSize || 16;
234
+ const fontSize = annotation.fontSize || 24;
528
235
  const textWidth = this.getTextWidth(annotation.text, fontSize);
529
- const handleSize = 8;
530
- const handleX = annotation.x + textWidth;
531
- const handleY = annotation.y;
532
- // Draw resize handle (small square) - using widget primary color
533
- this.canvasContext.fillStyle = '#0070F4'; // var(--feedback-primary-color)
534
- this.canvasContext.strokeStyle = '#ffffff';
236
+ // Save current context
237
+ const originalStrokeStyle = this.canvasContext.strokeStyle;
238
+ const originalLineWidth = this.canvasContext.lineWidth;
239
+ // Draw selection outline around text
240
+ this.canvasContext.strokeStyle = '#0070F4';
535
241
  this.canvasContext.lineWidth = 2;
536
- this.canvasContext.fillRect(handleX - handleSize / 2, handleY - handleSize / 2, handleSize, handleSize);
537
- this.canvasContext.strokeRect(handleX - handleSize / 2, handleY - handleSize / 2, handleSize, handleSize);
242
+ this.canvasContext.setLineDash([3, 3]);
243
+ this.canvasContext.strokeRect(annotation.x - 4, annotation.y - fontSize - 4, textWidth + 8, fontSize + 8);
244
+ // Restore context
245
+ this.canvasContext.setLineDash([]);
246
+ this.canvasContext.strokeStyle = originalStrokeStyle;
247
+ this.canvasContext.lineWidth = originalLineWidth;
538
248
  };
539
249
  this.drawArrow = (fromX, fromY, toX, toY) => {
540
250
  const headlen = 15; // Arrow head length
@@ -602,16 +312,23 @@ const FeedbackModal = class {
602
312
  this.showColorPicker = false;
603
313
  this.editingColorIndex = -1;
604
314
  };
605
- // Check if point is in resize handle for any annotation type
315
+ // Get text width for resize handle positioning
316
+ this.getTextWidth = (text, fontSize) => {
317
+ // Better text width calculation
318
+ if (!this.canvasContext) {
319
+ return text.length * fontSize * 0.6; // Fallback
320
+ }
321
+ // Use actual canvas measurement for accuracy
322
+ const originalFont = this.canvasContext.font;
323
+ this.canvasContext.font = `${fontSize}px Arial`;
324
+ const width = this.canvasContext.measureText(text).width;
325
+ this.canvasContext.font = originalFont;
326
+ return width;
327
+ };
328
+ // Check if point is in resize handle for shapes (not text)
606
329
  this.isPointInResizeHandle = (x, y, annotation) => {
607
330
  const handleSize = 8;
608
331
  switch (annotation.type) {
609
- case 'text':
610
- const textWidth = this.getTextWidth(annotation.text, annotation.fontSize || 16);
611
- const handleX = annotation.x + textWidth;
612
- const handleY = annotation.y;
613
- return x >= handleX - handleSize / 2 && x <= handleX + handleSize / 2 &&
614
- y >= handleY - handleSize / 2 && y <= handleY + handleSize / 2;
615
332
  case 'rectangle':
616
333
  const right = annotation.startX + annotation.width;
617
334
  const bottom = annotation.startY + annotation.height;
@@ -636,84 +353,170 @@ const FeedbackModal = class {
636
353
  return false;
637
354
  }
638
355
  };
639
- // Get text width for resize handle positioning
640
- this.getTextWidth = (text, fontSize) => {
641
- // Approximate text width calculation
642
- return text.length * fontSize * 0.6;
643
- };
644
- // Start text resize
645
- this.startTextResize = (annotation, startPos) => {
646
- this.isResizing = true;
647
- this.resizingAnnotation = annotation;
648
- this.resizeStartSize = annotation.fontSize || 16;
649
- this.dragStartPos = startPos;
356
+ // Draw resize handles for rectangle annotation (only bottom-right corner)
357
+ this.drawRectangleResizeHandles = (annotation) => {
358
+ if (!this.canvasContext || annotation.type !== 'rectangle')
359
+ return;
360
+ const handleSize = 8;
361
+ const right = annotation.startX + annotation.width;
362
+ const bottom = annotation.startY + annotation.height;
363
+ // Only draw bottom-right corner handle
364
+ const handle = { x: right, y: bottom };
365
+ // Draw the handle
366
+ this.canvasContext.fillStyle = '#0070F4'; // Primary color
367
+ this.canvasContext.strokeStyle = '#ffffff';
368
+ this.canvasContext.lineWidth = 2;
369
+ this.canvasContext.fillRect(handle.x - handleSize / 2, handle.y - handleSize / 2, handleSize, handleSize);
370
+ this.canvasContext.strokeRect(handle.x - handleSize / 2, handle.y - handleSize / 2, handleSize, handleSize);
650
371
  };
651
- // Handle text resize
652
- this.handleTextResize = (currentPos) => {
653
- if (!this.resizingAnnotation || !this.dragStartPos)
372
+ // Draw resize handles for line/arrow annotation
373
+ this.drawLineResizeHandles = (annotation) => {
374
+ if (!this.canvasContext || (annotation.type !== 'line' && annotation.type !== 'arrow'))
654
375
  return;
655
- const deltaX = currentPos.x - this.dragStartPos.x;
656
- const deltaY = currentPos.y - this.dragStartPos.y;
657
- const avgDelta = (deltaX + deltaY) / 2;
658
- // Calculate new font size (minimum 8px, maximum 72px)
659
- const newSize = Math.max(8, Math.min(72, this.resizeStartSize + avgDelta * 0.5));
660
- // Update annotation font size
661
- const index = this.annotations.findIndex(a => a === this.resizingAnnotation);
662
- if (index !== -1) {
663
- this.annotations[index] = Object.assign(Object.assign({}, this.resizingAnnotation), { fontSize: Math.round(newSize) });
664
- this.resizingAnnotation = this.annotations[index];
665
- }
666
- this.redrawAnnotations();
376
+ const handleSize = 8;
377
+ // Define handle positions (2 endpoints)
378
+ const handles = [
379
+ { x: annotation.startX, y: annotation.startY },
380
+ { x: annotation.endX, y: annotation.endY } // End point
381
+ ];
382
+ // Draw each handle
383
+ this.canvasContext.fillStyle = '#0070F4'; // Primary color
384
+ this.canvasContext.strokeStyle = '#ffffff';
385
+ this.canvasContext.lineWidth = 2;
386
+ handles.forEach(handle => {
387
+ this.canvasContext.fillRect(handle.x - handleSize / 2, handle.y - handleSize / 2, handleSize, handleSize);
388
+ this.canvasContext.strokeRect(handle.x - handleSize / 2, handle.y - handleSize / 2, handleSize, handleSize);
389
+ });
667
390
  };
668
- // Start resize for any annotation type
391
+ // Start resize for shapes
669
392
  this.startResize = (annotation, handle, startPos) => {
670
393
  this.isResizing = true;
671
394
  this.resizingAnnotation = annotation;
672
395
  this.resizeHandle = handle;
673
396
  this.dragStartPos = startPos;
674
397
  // Store original values for different annotation types
675
- if (annotation.type === 'text') {
676
- this.resizeStartSize = annotation.fontSize || 16;
677
- }
678
- else if (annotation.type === 'rectangle') {
398
+ if (annotation.type === 'rectangle') {
679
399
  this.resizeStartDimensions = { width: annotation.width, height: annotation.height };
680
400
  }
681
401
  };
682
- // Enhanced mouse down handler with resize detection for all annotation types
683
- this.handleCanvasMouseDown = (event) => {
684
- if (!this.canvasRef)
402
+ // Handle resize for different annotation types
403
+ this.handleResize = (currentPos) => {
404
+ if (!this.resizingAnnotation || !this.dragStartPos)
685
405
  return;
686
- // Disable drawing on mobile devices
687
- if (window.innerWidth <= 768)
406
+ const annotation = this.resizingAnnotation;
407
+ const index = this.annotations.findIndex(a => a === annotation);
408
+ if (index === -1)
688
409
  return;
689
- // Close color picker if open
690
- if (this.showColorPicker) {
691
- this.closeColorPicker();
692
- }
693
- const coords = this.getCanvasCoordinates(event);
694
- // Check if clicking on existing annotation first
695
- const found = this.findAnnotationAt(coords.x, coords.y);
696
- if (found) {
697
- // Check if clicking on resize handle for any annotation type
698
- const handle = this.isPointInResizeHandle(coords.x, coords.y, found.annotation);
699
- if (handle) {
700
- this.startResize(found.annotation, handle, coords);
701
- this.canvasRef.style.cursor = 'nw-resize';
702
- return;
703
- }
704
- // Start dragging existing annotation
705
- if (!this.isDrawing) {
706
- this.isDragging = true;
707
- this.draggedAnnotation = found.annotation;
410
+ let updatedAnnotation = Object.assign({}, annotation);
411
+ switch (annotation.type) {
412
+ case 'rectangle':
413
+ // Rectangle resize logic - only bottom-right corner
414
+ const rectDeltaX = currentPos.x - this.dragStartPos.x;
415
+ const rectDeltaY = currentPos.y - this.dragStartPos.y;
416
+ // Update width and height based on original dimensions plus delta
417
+ updatedAnnotation.width = Math.max(10, this.resizeStartDimensions.width + rectDeltaX);
418
+ updatedAnnotation.height = Math.max(10, this.resizeStartDimensions.height + rectDeltaY);
419
+ break;
420
+ case 'line':
421
+ case 'arrow':
422
+ // Line/arrow resize logic - move endpoints
423
+ if (this.resizeHandle === 'start') {
424
+ updatedAnnotation.startX = currentPos.x;
425
+ updatedAnnotation.startY = currentPos.y;
426
+ }
427
+ else if (this.resizeHandle === 'end') {
428
+ updatedAnnotation.endX = currentPos.x;
429
+ updatedAnnotation.endY = currentPos.y;
430
+ }
431
+ break;
432
+ }
433
+ // Update annotation in array
434
+ this.annotations[index] = updatedAnnotation;
435
+ this.resizingAnnotation = updatedAnnotation;
436
+ this.redrawAnnotations();
437
+ };
438
+ // Text editing methods
439
+ this.startTextEditing = (annotation) => {
440
+ const newText = prompt(this.editTextPromptText, annotation.text);
441
+ if (newText !== null && newText.trim()) {
442
+ const index = this.annotations.findIndex(a => a === annotation);
443
+ if (index !== -1) {
444
+ this.annotations[index] = Object.assign(Object.assign({}, annotation), { text: newText.trim() });
445
+ this.selectedAnnotation = this.annotations[index];
446
+ this.redrawAnnotations();
447
+ }
448
+ }
449
+ };
450
+ // Update selected annotation font size
451
+ this.updateSelectedTextSize = (newSize) => {
452
+ if (this.selectedAnnotation && this.selectedAnnotation.type === 'text') {
453
+ const index = this.annotations.findIndex(a => a === this.selectedAnnotation);
454
+ if (index !== -1) {
455
+ this.annotations[index] = Object.assign(Object.assign({}, this.selectedAnnotation), { fontSize: Math.max(8, Math.min(72, newSize)) });
456
+ this.selectedAnnotation = this.annotations[index];
457
+ this.redrawAnnotations();
458
+ }
459
+ }
460
+ };
461
+ // Update selected annotation border width
462
+ this.updateSelectedBorderWidth = (newWidth) => {
463
+ if (this.selectedAnnotation && ['rectangle', 'line', 'arrow'].includes(this.selectedAnnotation.type)) {
464
+ const index = this.annotations.findIndex(a => a === this.selectedAnnotation);
465
+ if (index !== -1) {
466
+ this.annotations[index] = Object.assign(Object.assign({}, this.selectedAnnotation), { lineWidth: Math.max(1, Math.min(20, newWidth)) });
467
+ this.selectedAnnotation = this.annotations[index];
468
+ this.redrawAnnotations();
469
+ }
470
+ }
471
+ };
472
+ // Enhanced mouse down handler with resize support
473
+ this.handleCanvasMouseDown = (event) => {
474
+ if (!this.canvasRef)
475
+ return;
476
+ // Disable drawing on mobile devices
477
+ if (window.innerWidth <= 768)
478
+ return;
479
+ // Close color picker if open
480
+ if (this.showColorPicker) {
481
+ this.closeColorPicker();
482
+ }
483
+ const coords = this.getCanvasCoordinates(event);
484
+ // Check if clicking on existing annotation first
485
+ const found = this.findAnnotationAt(coords.x, coords.y);
486
+ if (found) {
487
+ // Select the annotation
488
+ this.selectedAnnotation = found.annotation;
489
+ // Check if clicking on resize handle for shapes (not text)
490
+ if (found.annotation.type !== 'text') {
491
+ const handle = this.isPointInResizeHandle(coords.x, coords.y, found.annotation);
492
+ if (handle) {
493
+ this.startResize(found.annotation, handle, coords);
494
+ this.canvasRef.style.cursor = 'nw-resize';
495
+ return;
496
+ }
497
+ }
498
+ // Check for double-click to edit text
499
+ if (found.annotation.type === 'text' && event.detail === 2) {
500
+ this.startTextEditing(found.annotation);
501
+ return;
502
+ }
503
+ // Start dragging existing annotation
504
+ if (!this.isDrawing) {
505
+ this.isDragging = true;
506
+ this.draggedAnnotation = found.annotation;
708
507
  this.dragStartPos = coords;
709
508
  this.canvasRef.style.cursor = 'grabbing';
710
509
  return;
711
510
  }
712
511
  }
512
+ else {
513
+ // Clear selection if clicking on empty space
514
+ this.selectedAnnotation = null;
515
+ }
713
516
  // Original drawing logic
714
517
  this.isDrawing = true;
715
518
  if (this.canvasDrawingTool === 'text') {
716
- const text = prompt('Enter text:');
519
+ const text = prompt(this.editTextPromptText);
717
520
  if (text) {
718
521
  const annotation = {
719
522
  type: 'text',
@@ -721,7 +524,7 @@ const FeedbackModal = class {
721
524
  y: coords.y,
722
525
  text,
723
526
  color: this.canvasDrawingColor,
724
- fontSize: 16
527
+ fontSize: this.canvasTextSize
725
528
  };
726
529
  this.annotations = [...this.annotations, annotation];
727
530
  this.redrawAnnotations();
@@ -745,7 +548,7 @@ const FeedbackModal = class {
745
548
  if (window.innerWidth <= 768)
746
549
  return;
747
550
  const coords = this.getCanvasCoordinates(event);
748
- // Handle resizing for any annotation type
551
+ // Handle resizing for shapes
749
552
  if (this.isResizing && this.resizingAnnotation) {
750
553
  this.handleResize(coords);
751
554
  return;
@@ -800,13 +603,15 @@ const FeedbackModal = class {
800
603
  // Handle hover states and cursor changes
801
604
  const found = this.findAnnotationAt(coords.x, coords.y);
802
605
  if (found) {
803
- // Check if hovering over resize handle for any annotation type
804
- const handle = this.isPointInResizeHandle(coords.x, coords.y, found.annotation);
805
- if (handle) {
806
- this.canvasRef.style.cursor = 'nw-resize';
807
- this.hoveredAnnotation = found.annotation;
808
- this.redrawAnnotations();
809
- return;
606
+ // Check if hovering over resize handle for shapes (not text)
607
+ if (found.annotation.type !== 'text') {
608
+ const handle = this.isPointInResizeHandle(coords.x, coords.y, found.annotation);
609
+ if (handle) {
610
+ this.canvasRef.style.cursor = 'nw-resize';
611
+ this.hoveredAnnotation = found.annotation;
612
+ this.redrawAnnotations();
613
+ return;
614
+ }
810
615
  }
811
616
  // Regular hover over annotation
812
617
  this.canvasRef.style.cursor = 'grab';
@@ -838,154 +643,577 @@ const FeedbackModal = class {
838
643
  if (this.canvasRef) {
839
644
  this.canvasRef.style.cursor = 'crosshair';
840
645
  }
841
- return;
646
+ return;
647
+ }
648
+ // Handle end of dragging
649
+ if (this.isDragging) {
650
+ this.isDragging = false;
651
+ this.draggedAnnotation = null;
652
+ this.dragStartPos = null;
653
+ if (this.canvasRef) {
654
+ this.canvasRef.style.cursor = 'crosshair';
655
+ }
656
+ return;
657
+ }
658
+ // Handle end of drawing
659
+ if (!this.isDrawing || !this.currentAnnotation)
660
+ return;
661
+ this.isDrawing = false;
662
+ this.annotations = [...this.annotations, this.currentAnnotation];
663
+ this.currentAnnotation = null;
664
+ this.redrawAnnotations();
665
+ };
666
+ // Convert screen coordinates to canvas coordinates
667
+ this.getCanvasCoordinates = (event) => {
668
+ if (!this.canvasRef)
669
+ return { x: 0, y: 0 };
670
+ const rect = this.canvasRef.getBoundingClientRect();
671
+ // Calculate the scale factor between display size and actual canvas size
672
+ const scaleX = this.canvasRef.width / rect.width;
673
+ const scaleY = this.canvasRef.height / rect.height;
674
+ const x = (event.clientX - rect.left) * scaleX;
675
+ const y = (event.clientY - rect.top) * scaleY;
676
+ return { x, y };
677
+ };
678
+ // Find annotation under mouse cursor
679
+ this.findAnnotationAt = (x, y) => {
680
+ // Check in reverse order (top to bottom)
681
+ for (let i = this.annotations.length - 1; i >= 0; i--) {
682
+ const annotation = this.annotations[i];
683
+ if (this.isPointInAnnotation(x, y, annotation)) {
684
+ return { annotation, index: i };
685
+ }
686
+ }
687
+ return null;
688
+ };
689
+ // Check if point is within annotation bounds
690
+ this.isPointInAnnotation = (x, y, annotation) => {
691
+ const tolerance = 10; // Click tolerance
692
+ switch (annotation.type) {
693
+ case 'rectangle':
694
+ const left = Math.min(annotation.startX, annotation.startX + annotation.width);
695
+ const right = Math.max(annotation.startX, annotation.startX + annotation.width);
696
+ const top = Math.min(annotation.startY, annotation.startY + annotation.height);
697
+ const bottom = Math.max(annotation.startY, annotation.startY + annotation.height);
698
+ return x >= left - tolerance && x <= right + tolerance &&
699
+ y >= top - tolerance && y <= bottom + tolerance;
700
+ case 'line':
701
+ case 'arrow':
702
+ // Distance from point to line
703
+ const A = annotation.endY - annotation.startY;
704
+ const B = annotation.startX - annotation.endX;
705
+ const C = annotation.endX * annotation.startY - annotation.startX * annotation.endY;
706
+ const distance = Math.abs(A * x + B * y + C) / Math.sqrt(A * A + B * B);
707
+ return distance <= tolerance;
708
+ case 'text':
709
+ // Use actual text dimensions for better dragging
710
+ const fontSize = annotation.fontSize || 24;
711
+ const textWidth = this.getTextWidth(annotation.text, fontSize);
712
+ const textHeight = fontSize;
713
+ // Text bounding box (y coordinate is baseline, so subtract font size for top)
714
+ const textLeft = annotation.x - tolerance;
715
+ const textRight = annotation.x + textWidth + tolerance;
716
+ const textTop = annotation.y - textHeight - tolerance;
717
+ const textBottom = annotation.y + tolerance;
718
+ return x >= textLeft && x <= textRight &&
719
+ y >= textTop && y <= textBottom;
720
+ default:
721
+ return false;
722
+ }
723
+ };
724
+ this.canvasEditorTitle = 'Edit screenshot';
725
+ this.canvasEditorCancelText = 'Cancel';
726
+ this.canvasEditorSaveText = 'Save';
727
+ this.screenshotTakingText = 'Taking screenshot...';
728
+ this.screenshotAttachedText = 'Screenshot attached';
729
+ this.screenshotButtonText = 'Add a screenshot';
730
+ this.autoStartScreenshot = false;
731
+ this.existingScreenshot = '';
732
+ this.editTextButtonText = 'Edit Text';
733
+ this.sizeLabelText = 'Size:';
734
+ this.borderLabelText = 'Border:';
735
+ this.editTextPromptText = 'Edit text:';
736
+ this.screenshotErrorGeneral = 'Failed to capture screenshot.';
737
+ this.screenshotErrorPermission = 'Permission denied. Please allow screen sharing to take screenshots.';
738
+ this.screenshotErrorNotSupported = 'Screen capture is not supported in this browser.';
739
+ this.screenshotErrorNotFound = 'No screen sources available for capture.';
740
+ this.screenshotErrorCancelled = 'Screenshot capture was cancelled.';
741
+ this.screenshotErrorBrowserNotSupported = 'Your browser does not support screen capture. Please use a browser like Chrome, Firefox, or Safari on desktop.';
742
+ this.screenshotErrorUnexpected = 'An unexpected error occurred. Please try again.';
743
+ this.takingScreenshot = false;
744
+ this.showCanvasEditor = false;
745
+ this.canvasDrawingTool = 'rectangle';
746
+ this.canvasDrawingColor = '#ff0000';
747
+ this.canvasLineWidth = 3;
748
+ this.canvasTextSize = 24;
749
+ this.isDrawing = false;
750
+ this.annotations = [];
751
+ this.currentAnnotation = null;
752
+ this.isDragging = false;
753
+ this.draggedAnnotation = null;
754
+ this.dragStartPos = null;
755
+ this.showColorPicker = false;
756
+ this.editingColorIndex = -1;
757
+ this.selectedAnnotation = null;
758
+ this.isResizing = false;
759
+ this.resizingAnnotation = null;
760
+ this.resizeStartSize = 24;
761
+ this.resizeStartDimensions = null;
762
+ this.hoveredAnnotation = null;
763
+ this.resizeHandle = false;
764
+ this.defaultColors = ['#ff0000', '#00ff00', '#0000ff', '#000000'];
765
+ }
766
+ componentDidLoad() {
767
+ if (this.autoStartScreenshot) {
768
+ // Show the editor UI and start screenshot capture
769
+ this.showCanvasEditor = true;
770
+ setTimeout(() => {
771
+ this.openScreenShot();
772
+ }, 100); // Small delay to ensure component is fully rendered
773
+ }
774
+ else if (this.existingScreenshot) {
775
+ // Show editor with existing screenshot data
776
+ this.originalImageData = this.existingScreenshot;
777
+ this.showCanvasEditor = true;
778
+ setTimeout(() => {
779
+ this.initializeCanvas();
780
+ }, 100);
781
+ }
782
+ }
783
+ async captureViewportScreenshot() {
784
+ try {
785
+ // Check if Screen Capture API is supported
786
+ if (!navigator.mediaDevices || !navigator.mediaDevices.getDisplayMedia) {
787
+ throw new Error('Screen Capture API is not supported in this browser');
788
+ }
789
+ // Request screen capture with preference for current tab
790
+ const stream = await navigator.mediaDevices.getDisplayMedia({
791
+ video: {
792
+ mediaSource: 'screen',
793
+ width: { ideal: window.innerWidth },
794
+ height: { ideal: window.innerHeight }
795
+ },
796
+ audio: false,
797
+ preferCurrentTab: true
798
+ });
799
+ // Create video element to capture frame
800
+ const video = document.createElement('video');
801
+ video.srcObject = stream;
802
+ video.autoplay = true;
803
+ video.muted = true;
804
+ return new Promise((resolve, reject) => {
805
+ video.onloadedmetadata = () => {
806
+ video.play();
807
+ // Wait a moment for video to stabilize
808
+ setTimeout(() => {
809
+ try {
810
+ // Create canvas to capture frame
811
+ const canvas = document.createElement('canvas');
812
+ canvas.width = video.videoWidth;
813
+ canvas.height = video.videoHeight;
814
+ const ctx = canvas.getContext('2d');
815
+ ctx.drawImage(video, 0, 0);
816
+ // Stop the stream
817
+ stream.getTracks().forEach(track => track.stop());
818
+ // Convert to data URL
819
+ const dataUrl = canvas.toDataURL('image/png');
820
+ console.log('Screenshot captured successfully using Screen Capture API');
821
+ resolve(dataUrl);
822
+ }
823
+ catch (error) {
824
+ stream.getTracks().forEach(track => track.stop());
825
+ reject(error);
826
+ }
827
+ }, 100);
828
+ };
829
+ video.onerror = () => {
830
+ stream.getTracks().forEach(track => track.stop());
831
+ reject(new Error('Failed to load video for screenshot capture'));
832
+ };
833
+ });
834
+ }
835
+ catch (error) {
836
+ console.error('Screen capture failed:', error);
837
+ throw error;
838
+ }
839
+ }
840
+ render() {
841
+ var _a, _b, _c, _d, _e, _f;
842
+ return (index.h("div", { class: "canvas-editor-wrapper" }, this.showCanvasEditor && (index.h("div", { class: "canvas-editor-overlay" }, index.h("div", { class: "canvas-editor-modal" }, index.h("div", { class: "canvas-editor-header" }, index.h("div", { class: "canvas-editor-title" }, index.h("h3", null, this.canvasEditorTitle)), index.h("div", { class: "canvas-editor-toolbar" }, index.h("div", { class: "toolbar-section" }, index.h("div", { class: "tool-group" }, index.h("button", { class: `tool-btn ${this.canvasDrawingTool === 'rectangle' ? 'active' : ''}`, onClick: () => this.canvasDrawingTool = 'rectangle', title: "Rectangle" }, index.h("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, index.h("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2", ry: "2" }))), index.h("button", { class: `tool-btn ${this.canvasDrawingTool === 'line' ? 'active' : ''}`, onClick: () => this.canvasDrawingTool = 'line', title: "Line" }, index.h("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, index.h("line", { x1: "5", y1: "12", x2: "19", y2: "12" }))), index.h("button", { class: `tool-btn ${this.canvasDrawingTool === 'arrow' ? 'active' : ''}`, onClick: () => this.canvasDrawingTool = 'arrow', title: "Arrow" }, index.h("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, index.h("line", { x1: "7", y1: "17", x2: "17", y2: "7" }), index.h("polyline", { points: "7,7 17,7 17,17" }))), index.h("button", { class: `tool-btn ${this.canvasDrawingTool === 'text' ? 'active' : ''}`, onClick: () => this.canvasDrawingTool = 'text', title: "Text" }, index.h("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, index.h("polyline", { points: "4,7 4,4 20,4 20,7" }), index.h("line", { x1: "9", y1: "20", x2: "15", y2: "20" }), index.h("line", { x1: "12", y1: "4", x2: "12", y2: "20" }))), index.h("div", { class: "toolbar-divider" }), index.h("button", { class: "tool-btn undo-btn", onClick: this.undoLastAnnotation, disabled: this.annotations.length === 0, title: "Undo" }, index.h("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, index.h("polyline", { points: "1,4 1,10 7,10" }), index.h("path", { d: "M3.51,15a9,9,0,0,0,14.85-3.36,9,9,0,0,0-9.19-10.15L1.83,10" }))))), index.h("div", { class: "toolbar-section" }, index.h("div", { class: "color-palette" }, this.defaultColors.map((color, index$1) => (index.h("div", { class: "color-slot-wrapper" }, index.h("button", { class: `color-btn ${this.canvasDrawingColor === color ? 'active' : ''} ${this.editingColorIndex === index$1 ? 'editing' : ''}`, style: { backgroundColor: color }, onClick: () => this.handleColorSlotClick(index$1), title: `Color ${index$1 + 1} - Click to customize` }, this.editingColorIndex === index$1 && (index.h("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "white", "stroke-width": "2" }, index.h("path", { d: "M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z" })))), this.editingColorIndex === index$1 && this.showColorPicker && (index.h("div", { class: "color-picker-dropdown" }, index.h("input", { type: "color", value: color, onInput: (e) => this.handleColorPickerInput(e), onClick: (e) => this.handleColorPickerClick(e) })))))))), (this.selectedAnnotation || this.canvasDrawingTool) && (index.h("div", { class: "toolbar-section selected-annotation-controls" }, (((_a = this.selectedAnnotation) === null || _a === void 0 ? void 0 : _a.type) === 'text' || (!this.selectedAnnotation && this.canvasDrawingTool === 'text')) && (index.h("div", { class: "text-controls" }, index.h("div", { class: "font-size-control" }, index.h("label", null, this.sizeLabelText), index.h("input", { type: "range", min: "8", max: "72", value: ((_b = this.selectedAnnotation) === null || _b === void 0 ? void 0 : _b.fontSize) || this.canvasTextSize, onInput: (e) => {
843
+ const newSize = parseInt(e.target.value);
844
+ if (this.selectedAnnotation) {
845
+ this.updateSelectedTextSize(newSize);
846
+ }
847
+ else {
848
+ this.canvasTextSize = newSize;
849
+ }
850
+ }, class: "size-slider" }), index.h("span", { class: "size-value" }, ((_c = this.selectedAnnotation) === null || _c === void 0 ? void 0 : _c.fontSize) || this.canvasTextSize, "px")), this.selectedAnnotation && (index.h("button", { class: "action-btn small", onClick: () => this.startTextEditing(this.selectedAnnotation) }, this.editTextButtonText)))), ((['rectangle', 'line', 'arrow'].includes((_d = this.selectedAnnotation) === null || _d === void 0 ? void 0 : _d.type)) ||
851
+ (!this.selectedAnnotation && ['rectangle', 'line', 'arrow'].includes(this.canvasDrawingTool))) && (index.h("div", { class: "shape-controls" }, index.h("div", { class: "border-width-control" }, index.h("label", null, this.borderLabelText), index.h("input", { type: "range", min: "1", max: "20", value: ((_e = this.selectedAnnotation) === null || _e === void 0 ? void 0 : _e.lineWidth) || this.canvasLineWidth, onInput: (e) => {
852
+ const newWidth = parseInt(e.target.value);
853
+ if (this.selectedAnnotation) {
854
+ this.updateSelectedBorderWidth(newWidth);
855
+ }
856
+ else {
857
+ this.canvasLineWidth = newWidth;
858
+ }
859
+ }, class: "size-slider" }), index.h("span", { class: "size-value" }, ((_f = this.selectedAnnotation) === null || _f === void 0 ? void 0 : _f.lineWidth) || this.canvasLineWidth, "px")))))), index.h("div", { class: "toolbar-section" }, index.h("button", { class: "action-btn secondary", onClick: this.closeCanvasEditor }, this.canvasEditorCancelText), index.h("button", { class: "action-btn primary", onClick: this.saveAnnotations }, this.canvasEditorSaveText))), index.h("div", { class: "canvas-editor-content" }, index.h("canvas", { ref: (el) => this.canvasRef = el, class: "annotation-canvas", onMouseDown: this.handleCanvasMouseDown, onMouseMove: this.handleCanvasMouseMove, onMouseUp: this.handleCanvasMouseUp, onMouseLeave: this.handleCanvasMouseUp }))))))));
860
+ }
861
+ };
862
+ CanvasEditor.style = canvasEditorCss;
863
+
864
+ const feedbackButtonCss = ".feedback-button-content{cursor:pointer;max-width:fit-content;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}}";
865
+
866
+ const FeedbackButton = class {
867
+ constructor(hostRef) {
868
+ index.registerInstance(this, hostRef);
869
+ this.feedbackSent = index.createEvent(this, "feedbackSent", 7);
870
+ this.feedbackError = index.createEvent(this, "feedbackError", 7);
871
+ this.buttonPosition = 'default';
872
+ this.buttonStyle = 'default';
873
+ this.hideIcon = false;
874
+ this.hideMobile = false;
875
+ this.sessionId = '';
876
+ this.metadata = '';
877
+ this.submit = false;
878
+ this.customFont = false;
879
+ this.emailAddress = '';
880
+ this.isEmailRequired = false;
881
+ this.fetchData = true;
882
+ this.hideEmail = false;
883
+ this.hidePrivacyPolicy = true;
884
+ this.hideRating = false;
885
+ this.hideScreenshotButton = false;
886
+ this.modalPosition = 'center';
887
+ this.project = '';
888
+ this.rating = undefined;
889
+ this.ratingMode = 'thumbs';
890
+ this.canvasEditorTitle = 'Edit screenshot';
891
+ this.canvasEditorCancelText = 'Cancel';
892
+ this.canvasEditorSaveText = 'Save';
893
+ this.editTextButtonText = 'Edit Text';
894
+ this.sizeLabelText = 'Size:';
895
+ this.borderLabelText = 'Border:';
896
+ this.editTextPromptText = 'Edit text:';
897
+ this.screenshotErrorGeneral = 'Failed to capture screenshot.';
898
+ this.screenshotErrorPermission = 'Permission denied. Please allow screen sharing to take screenshots.';
899
+ this.screenshotErrorNotSupported = 'Screen capture is not supported in this browser.';
900
+ this.screenshotErrorNotFound = 'No screen sources available for capture.';
901
+ this.screenshotErrorCancelled = 'Screenshot capture was cancelled.';
902
+ this.screenshotErrorBrowserNotSupported = 'Your browser does not support screen capture. Please use a browser like Chrome, Firefox, or Safari.';
903
+ this.screenshotErrorUnexpected = 'An unexpected error occurred. Please try again.';
904
+ this.emailPlaceholder = 'Email address (optional)';
905
+ this.errorMessage = 'Please try again later.';
906
+ this.errorMessage403 = 'The request URL does not match the one defined in PushFeedback for this project.';
907
+ this.errorMessage404 = 'We could not find the provided project id in PushFeedback.';
908
+ this.footerText = '';
909
+ this.messagePlaceholder = 'Comments';
910
+ this.modalTitle = 'Share your feedback';
911
+ this.modalTitleError = 'Oops!';
912
+ this.modalTitleSuccess = 'Thanks for your feedback!';
913
+ this.privacyPolicyText = "I have read and expressly consent to the terms of the <a href='https://pushfeedback.com/privacy'>Privacy Policy</a>.";
914
+ this.ratingPlaceholder = 'Was this page helpful?';
915
+ this.ratingStarsPlaceholder = 'How would you rate this page?';
916
+ this.screenshotAttachedText = 'Screenshot attached';
917
+ this.screenshotButtonText = 'Add a screenshot';
918
+ this.screenshotTakingText = 'Taking screenshot...';
919
+ this.screenshotTopbarText = 'Select an element on this page';
920
+ this.sendButtonText = 'Send';
921
+ this.successMessage = '';
922
+ }
923
+ componentWillLoad() {
924
+ if (!this.sessionId) {
925
+ let storedSessionId = localStorage.getItem('pushfeedback_sessionid');
926
+ if (!storedSessionId) {
927
+ storedSessionId = this.generateRandomSessionId();
928
+ localStorage.setItem('pushfeedback_sessionid', storedSessionId);
929
+ this.sessionId = storedSessionId;
930
+ }
931
+ }
932
+ else {
933
+ localStorage.setItem('pushfeedback_sessionid', this.sessionId);
934
+ }
935
+ }
936
+ componentDidLoad() {
937
+ if (this.buttonPosition === 'center-right') {
938
+ const buttonContent = this.el.shadowRoot.querySelector('.feedback-button-content');
939
+ let adjustement = 0;
940
+ if (this.isSafariBrowser()) {
941
+ adjustement = 5;
942
+ }
943
+ buttonContent.style.right = `${((buttonContent.offsetWidth + adjustement) / 2) * -1}px`;
944
+ }
945
+ if (!this.customFont) {
946
+ this.loadInterFont();
947
+ }
948
+ }
949
+ connectedCallback() {
950
+ this.feedbackModal = document.createElement('feedback-modal');
951
+ const props = [
952
+ 'customFont',
953
+ 'emailAddress',
954
+ 'fetchData',
955
+ 'hideEmail',
956
+ 'hidePrivacyPolicy',
957
+ 'hideRating',
958
+ 'hideScreenshotButton',
959
+ 'isEmailRequired',
960
+ 'modalPosition',
961
+ 'project',
962
+ 'rating',
963
+ 'ratingMode',
964
+ 'canvasEditorTitle',
965
+ 'canvasEditorCancelText',
966
+ 'canvasEditorSaveText',
967
+ 'editTextButtonText',
968
+ 'sizeLabelText',
969
+ 'borderLabelText',
970
+ 'editTextPromptText',
971
+ 'screenshotErrorGeneral',
972
+ 'screenshotErrorPermission',
973
+ 'screenshotErrorNotSupported',
974
+ 'screenshotErrorNotFound',
975
+ 'screenshotErrorCancelled',
976
+ 'screenshotErrorBrowserNotSupported',
977
+ 'screenshotErrorUnexpected',
978
+ 'emailPlaceholder',
979
+ 'errorMessage',
980
+ 'errorMessage403',
981
+ 'errorMessage404',
982
+ 'footerText',
983
+ 'messagePlaceholder',
984
+ 'metadata',
985
+ 'modalTitle',
986
+ 'modalTitleError',
987
+ 'modalTitleSuccess',
988
+ 'privacyPolicyText',
989
+ 'ratingPlaceholder',
990
+ 'ratingStarsPlaceholder',
991
+ 'screenshotAttachedText',
992
+ 'screenshotButtonText',
993
+ 'screenshotTakingText',
994
+ 'screenshotTopbarText',
995
+ 'sendButtonText',
996
+ 'successMessage',
997
+ ];
998
+ props.forEach((prop) => {
999
+ this.feedbackModal[prop] = this[prop];
1000
+ });
1001
+ document.body.appendChild(this.feedbackModal);
1002
+ }
1003
+ disconnectedCallback() {
1004
+ document.body.removeChild(this.feedbackModal);
1005
+ }
1006
+ generateRandomSessionId(length = 16) {
1007
+ return Math.random().toString(36).substr(2, length);
1008
+ }
1009
+ isSafariBrowser() {
1010
+ const isSafari = /safari/i.test(navigator.userAgent) && !/chrome/i.test(navigator.userAgent);
1011
+ return isSafari;
1012
+ }
1013
+ loadInterFont() {
1014
+ const link = document.createElement('link');
1015
+ link.href = 'https://fonts.googleapis.com/css2?family=Inter:wght@400;500&display=swap';
1016
+ link.rel = 'stylesheet';
1017
+ document.head.appendChild(link);
1018
+ }
1019
+ showModal() {
1020
+ if (this.submit) {
1021
+ this.submitRatingFeedback();
1022
+ }
1023
+ else {
1024
+ this.feedbackModal.openModal();
1025
+ }
1026
+ }
1027
+ async submitRatingFeedback() {
1028
+ try {
1029
+ const body = {
1030
+ url: window.location.href,
1031
+ project: this.project,
1032
+ rating: this.rating || -1,
1033
+ ratingMode: this.ratingMode,
1034
+ message: '',
1035
+ metadata: this.metadata,
1036
+ session: localStorage.getItem('pushfeedback_sessionid') || '',
1037
+ };
1038
+ const res = await fetch('https://app.pushfeedback.com/api/feedback/', {
1039
+ method: 'POST',
1040
+ body: JSON.stringify(body),
1041
+ headers: {
1042
+ 'Content-Type': 'application/json',
1043
+ },
1044
+ });
1045
+ if (res.status === 201) {
1046
+ const feedback_with_id = Object.assign(Object.assign({}, body), { id: await res.json() });
1047
+ this.feedbackSent.emit({ feedback: feedback_with_id });
1048
+ }
1049
+ else {
1050
+ const errorText = await res.text();
1051
+ const response = {
1052
+ status: res.status,
1053
+ message: errorText,
1054
+ };
1055
+ this.feedbackError.emit({ error: response });
1056
+ }
1057
+ }
1058
+ catch (error) {
1059
+ const response = {
1060
+ status: 500,
1061
+ message: error,
1062
+ };
1063
+ this.feedbackError.emit({ error: response });
1064
+ }
1065
+ }
1066
+ render() {
1067
+ return (index.h(index.Host, null, index.h("a", { class: `feedback-button-content feedback-button-content--${this.buttonStyle} feedback-button-content--${this.buttonPosition} ${this.customFont ? 'feedback-button-content--custom-font' : ''} ${this.hideMobile ? 'feedback-button-content--hide-mobile' : ''}`, onClick: () => this.showModal() }, !this.hideIcon && this.buttonStyle != 'default' && (index.h("span", { class: "feedback-button-content-icon" }, index.h("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "#fff", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", class: "icon-edit" }, index.h("path", { d: "M12 20h9" }), index.h("path", { d: "M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z" })))), index.h("slot", null))));
1068
+ }
1069
+ get el() { return index.getElement(this); }
1070
+ };
1071
+ FeedbackButton.style = feedbackButtonCss;
1072
+
1073
+ 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.2s ease-out}.feedback-overlay--visible{opacity:1}.feedback-modal{display:inline-block;position:relative}.feedback-modal-content{background-color:var(--feedback-modal-content-bg-color);border-color:1px solid var(--feedback-modal-header-text-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-content-text-color);display:flex;flex-direction:column;left:50%;max-width:90%;padding:20px;position:fixed;top:50%;transform:translate(-50%, -50%) scale(0.95);opacity:0;width:100%;z-index:var(--feedback-modal-content-z-index);transition:transform 0.2s ease-out, opacity 0.2s ease-out}.feedback-modal-content--open{transform:translate(-50%, -50%) scale(1);opacity:1}.feedback-modal-header{align-items:center;color:var(--feedback-modal-header-text-color);display:flex;font-size:var(--feedback-header-font-size);font-weight:var(--feedback-modal-header-font-weight);justify-content:space-between;margin-bottom:20px}.feedback-modal-rating-buttons{width:100%;margin-bottom:20px}.feedback-modal-rating-button{padding:0;background-color:transparent;border:transparent;margin-right:5px;cursor:pointer}.feedback-modal-rating-buttons--thumbs .feedback-modal-rating-button{border:1px 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:10px;justify-content:center;padding:5px 10px}.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:1px 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--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:1px 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:10px;resize:vertical;width:100%}.feedback-modal-email input{background-color:var(--feedback-modal-input-bg-color);border:1px 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:40px;padding:10px;width:100%;margin-bottom:20px}.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:1px solid var(--feedback-modal-input-border-color-focused);outline:none}.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:1px 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:40px;padding:5px 10px}.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:1px solid var(--feedback-modal-button-border-color-active);color:var(--feedback-modal-button-submit-text-color)}.feedback-modal-button:hover,.feedback-modal-button--active{background-color:var(--feedback-modal-button-bg-color-active);border:1px solid var(--feedback-modal-button-border-color-active);color:var(--feedback-modal-button-text-color-active)}.feedback-modal-button--submit:hover{background-color:var(--feedback-modal-button-submit-bg-color-hover);border:1px solid var(--feedback-modal-button-submit-border-color-hover);color:var(--feedback-modal-button-submit-text-color-hover)}.feedback-modal-input-heading{display:block;font-size:14px;font-weight:300;padding-bottom:10px}.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-modal-close{background-color:var(--feedback-modal-close-bg-color);border:0;border-radius:50%;cursor:pointer;height:22px;margin-left:auto;padding:0;width:22px}.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-top:0}.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:30px;height:30px;overflow:hidden;border-radius:4px;margin-right:10px;box-shadow:0 2px 4px rgba(0, 0, 0, 0.1);cursor:pointer;transition:transform 0.2s ease}.screenshot-preview:hover{transform:scale(1.1)}.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:1px solid #fcc;border-radius:8px;padding:12px 16px;display:flex;align-items:center;gap:12px;box-shadow:0 4px 12px rgba(0, 0, 0, 0.15);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:4px;border-radius:4px;color:#c53030;flex-shrink:0;transition:background-color 0.2s ease}.error-close-btn:hover{background:rgba(197, 48, 48, 0.1)}";
1074
+
1075
+ const FeedbackModal = class {
1076
+ constructor(hostRef) {
1077
+ index.registerInstance(this, hostRef);
1078
+ this.feedbackSent = index.createEvent(this, "feedbackSent", 7);
1079
+ this.feedbackError = index.createEvent(this, "feedbackError", 7);
1080
+ this.onScrollDebounced = () => {
1081
+ clearTimeout(this.scrollTimeout);
1082
+ this.scrollTimeout = setTimeout(() => {
1083
+ document.documentElement.classList.remove('feedback-modal-screenshot-closing');
1084
+ document.documentElement.style.top = '';
1085
+ window.removeEventListener('scroll', this.onScrollDebounced);
1086
+ }, 200);
1087
+ };
1088
+ this.handleSubmit = async (event) => {
1089
+ event.preventDefault();
1090
+ if (this.isEmailRequired && !this.formEmail) {
1091
+ return;
1092
+ }
1093
+ this.resetOverflow();
1094
+ this.showScreenshotMode = false;
1095
+ this.showScreenshotTopBar = false;
1096
+ this.showModal = false;
1097
+ this.sending = true;
1098
+ try {
1099
+ const body = {
1100
+ url: window.location.href,
1101
+ message: this.formMessage,
1102
+ email: this.formEmail,
1103
+ project: this.project,
1104
+ screenshot: this.encodedScreenshot,
1105
+ rating: this.selectedRating,
1106
+ ratingMode: this.ratingMode,
1107
+ metadata: this.metadata,
1108
+ verification: this.formVerification,
1109
+ session: localStorage.getItem('pushfeedback_sessionid') || '',
1110
+ };
1111
+ const res = await fetch('https://app.pushfeedback.com/api/feedback/', {
1112
+ method: 'POST',
1113
+ body: JSON.stringify(body),
1114
+ headers: {
1115
+ 'Content-Type': 'application/json',
1116
+ },
1117
+ });
1118
+ if (res.status === 201) {
1119
+ const feedback_with_id = Object.assign(Object.assign({}, body), { id: await res.json() });
1120
+ this.feedbackSent.emit({ feedback: feedback_with_id });
1121
+ this.formSuccess = true;
1122
+ this.formError = false;
1123
+ }
1124
+ else {
1125
+ const errorText = await res.text();
1126
+ const response = {
1127
+ status: res.status,
1128
+ message: errorText,
1129
+ };
1130
+ this.feedbackError.emit({ error: response });
1131
+ this.formSuccess = false;
1132
+ this.formError = true;
1133
+ this.formErrorStatus = res.status;
1134
+ }
842
1135
  }
843
- // Handle end of dragging
844
- if (this.isDragging) {
845
- this.isDragging = false;
846
- this.draggedAnnotation = null;
847
- this.dragStartPos = null;
848
- if (this.canvasRef) {
849
- this.canvasRef.style.cursor = 'crosshair';
850
- }
851
- return;
1136
+ catch (error) {
1137
+ const response = {
1138
+ status: 500,
1139
+ message: error,
1140
+ };
1141
+ this.feedbackError.emit({ error: response });
1142
+ this.formSuccess = false;
1143
+ this.formError = true;
1144
+ this.formErrorStatus = 500;
1145
+ }
1146
+ finally {
1147
+ this.sending = false;
1148
+ this.showModal = true;
852
1149
  }
853
- // Handle end of drawing
854
- if (!this.isDrawing || !this.currentAnnotation)
855
- return;
856
- this.isDrawing = false;
857
- this.annotations = [...this.annotations, this.currentAnnotation];
858
- this.currentAnnotation = null;
859
- this.redrawAnnotations();
860
1150
  };
861
- // Draw resize handles for rectangle annotation (only bottom-right corner)
862
- this.drawRectangleResizeHandles = (annotation) => {
863
- if (!this.canvasContext || annotation.type !== 'rectangle')
864
- return;
865
- const handleSize = 8;
866
- const right = annotation.startX + annotation.width;
867
- const bottom = annotation.startY + annotation.height;
868
- // Only draw bottom-right corner handle
869
- const handle = { x: right, y: bottom };
870
- // Draw the handle
871
- this.canvasContext.fillStyle = '#0070F4'; // Primary color
872
- this.canvasContext.strokeStyle = '#ffffff';
873
- this.canvasContext.lineWidth = 2;
874
- this.canvasContext.fillRect(handle.x - handleSize / 2, handle.y - handleSize / 2, handleSize, handleSize);
875
- this.canvasContext.strokeRect(handle.x - handleSize / 2, handle.y - handleSize / 2, handleSize, handleSize);
1151
+ this.close = () => {
1152
+ this.isAnimating = false;
1153
+ setTimeout(() => {
1154
+ this.sending = false;
1155
+ this.showModal = false;
1156
+ this.showScreenshotMode = false;
1157
+ this.showScreenshotTopBar = false;
1158
+ this.hasSelectedElement = false;
1159
+ this.encodedScreenshot = null;
1160
+ // Remove highlight from ALL selected elements
1161
+ document.querySelectorAll('.feedback-modal-element-selected').forEach(el => {
1162
+ el.classList.remove('feedback-modal-element-selected');
1163
+ });
1164
+ // Reset form states
1165
+ this.formSuccess = false;
1166
+ this.formError = false;
1167
+ this.formErrorStatus = 500;
1168
+ this.formMessage = '';
1169
+ this.formEmail = '';
1170
+ this.resetOverflow();
1171
+ }, 200);
876
1172
  };
877
- // Draw resize handles for line/arrow annotation
878
- this.drawLineResizeHandles = (annotation) => {
879
- if (!this.canvasContext || (annotation.type !== 'line' && annotation.type !== 'arrow'))
880
- return;
881
- const handleSize = 8;
882
- // Define handle positions (2 endpoints)
883
- const handles = [
884
- { x: annotation.startX, y: annotation.startY },
885
- { x: annotation.endX, y: annotation.endY } // End point
886
- ];
887
- // Draw each handle
888
- this.canvasContext.fillStyle = '#0070F4'; // Primary color
889
- this.canvasContext.strokeStyle = '#ffffff';
890
- this.canvasContext.lineWidth = 2;
891
- handles.forEach(handle => {
892
- this.canvasContext.fillRect(handle.x - handleSize / 2, handle.y - handleSize / 2, handleSize, handleSize);
893
- this.canvasContext.strokeRect(handle.x - handleSize / 2, handle.y - handleSize / 2, handleSize, handleSize);
894
- });
1173
+ // Handle screenshot events from canvas editor
1174
+ this.handleScreenshotReady = (event) => {
1175
+ this.encodedScreenshot = event.detail.screenshot;
1176
+ this.showModal = true;
1177
+ this.takingScreenshot = false;
1178
+ this.showCanvasEditor = false;
1179
+ this.autoStartCapture = false;
895
1180
  };
896
- // Convert screen coordinates to canvas coordinates
897
- this.getCanvasCoordinates = (event) => {
898
- if (!this.canvasRef)
899
- return { x: 0, y: 0 };
900
- const rect = this.canvasRef.getBoundingClientRect();
901
- // Calculate the scale factor between display size and actual canvas size
902
- const scaleX = this.canvasRef.width / rect.width;
903
- const scaleY = this.canvasRef.height / rect.height;
904
- const x = (event.clientX - rect.left) * scaleX;
905
- const y = (event.clientY - rect.top) * scaleY;
906
- return { x, y };
1181
+ this.handleScreenshotCancelled = () => {
1182
+ this.showModal = true;
1183
+ this.takingScreenshot = false;
1184
+ this.showCanvasEditor = false;
1185
+ this.autoStartCapture = false;
907
1186
  };
908
- // Find annotation under mouse cursor
909
- this.findAnnotationAt = (x, y) => {
910
- // Check in reverse order (top to bottom)
911
- for (let i = this.annotations.length - 1; i >= 0; i--) {
912
- const annotation = this.annotations[i];
913
- if (this.isPointInAnnotation(x, y, annotation)) {
914
- return { annotation, index: i };
915
- }
916
- }
917
- return null;
1187
+ this.handleScreenshotError = (event) => {
1188
+ console.error('Screenshot error:', event.detail.error);
1189
+ // Store error message to display in feedback modal
1190
+ this.screenshotError = event.detail.error;
1191
+ this.showScreenshotError = true;
1192
+ // Close canvas editor and return to feedback modal
1193
+ this.showModal = true;
1194
+ this.takingScreenshot = false;
1195
+ this.showCanvasEditor = false;
1196
+ this.autoStartCapture = false;
1197
+ // Auto-hide error after 8 seconds
1198
+ setTimeout(() => {
1199
+ this.showScreenshotError = false;
1200
+ }, 8000);
918
1201
  };
919
- // Check if point is within annotation bounds
920
- this.isPointInAnnotation = (x, y, annotation) => {
921
- const tolerance = 10; // Click tolerance
922
- switch (annotation.type) {
923
- case 'rectangle':
924
- const left = Math.min(annotation.startX, annotation.startX + annotation.width);
925
- const right = Math.max(annotation.startX, annotation.startX + annotation.width);
926
- const top = Math.min(annotation.startY, annotation.startY + annotation.height);
927
- const bottom = Math.max(annotation.startY, annotation.startY + annotation.height);
928
- return x >= left - tolerance && x <= right + tolerance &&
929
- y >= top - tolerance && y <= bottom + tolerance;
930
- case 'line':
931
- case 'arrow':
932
- // Distance from point to line
933
- const A = annotation.endY - annotation.startY;
934
- const B = annotation.startX - annotation.endX;
935
- const C = annotation.endX * annotation.startY - annotation.startX * annotation.endY;
936
- const distance = Math.abs(A * x + B * y + C) / Math.sqrt(A * A + B * B);
937
- return distance <= tolerance;
938
- case 'text':
939
- // Simple bounding box for text
940
- return x >= annotation.x - tolerance && x <= annotation.x + 100 &&
941
- y >= annotation.y - 20 && y <= annotation.y + tolerance;
942
- default:
943
- return false;
944
- }
1202
+ // Trigger screenshot capture
1203
+ this.openScreenShot = () => {
1204
+ this.showModal = false;
1205
+ this.takingScreenshot = true;
1206
+ this.autoStartCapture = true; // Auto-start new screenshot
1207
+ this.showCanvasEditor = true;
945
1208
  };
946
- // Handle resize for different annotation types
947
- this.handleResize = (currentPos) => {
948
- if (!this.resizingAnnotation || !this.dragStartPos)
949
- return;
950
- const annotation = this.resizingAnnotation;
951
- const index = this.annotations.findIndex(a => a === annotation);
952
- if (index === -1)
953
- return;
954
- let updatedAnnotation = Object.assign({}, annotation);
955
- switch (annotation.type) {
956
- case 'text':
957
- // Text resize logic (existing)
958
- const deltaX = currentPos.x - this.dragStartPos.x;
959
- const deltaY = currentPos.y - this.dragStartPos.y;
960
- const avgDelta = (deltaX + deltaY) / 2;
961
- const newSize = Math.max(8, Math.min(72, this.resizeStartSize + avgDelta * 0.5));
962
- updatedAnnotation.fontSize = Math.round(newSize);
963
- break;
964
- case 'rectangle':
965
- // Rectangle resize logic - only bottom-right corner
966
- const rectDeltaX = currentPos.x - this.dragStartPos.x;
967
- const rectDeltaY = currentPos.y - this.dragStartPos.y;
968
- // Update width and height based on original dimensions plus delta
969
- updatedAnnotation.width = Math.max(10, this.resizeStartDimensions.width + rectDeltaX); // Minimum width of 10px
970
- updatedAnnotation.height = Math.max(10, this.resizeStartDimensions.height + rectDeltaY); // Minimum height of 10px
971
- break;
972
- case 'line':
973
- case 'arrow':
974
- // Line/arrow resize logic - move endpoints
975
- if (this.resizeHandle === 'start') {
976
- updatedAnnotation.startX = currentPos.x;
977
- updatedAnnotation.startY = currentPos.y;
978
- }
979
- else if (this.resizeHandle === 'end') {
980
- updatedAnnotation.endX = currentPos.x;
981
- updatedAnnotation.endY = currentPos.y;
982
- }
983
- break;
1209
+ // Open canvas editor for existing screenshot
1210
+ this.openCanvasEditor = (event) => {
1211
+ if (event) {
1212
+ event.stopPropagation();
984
1213
  }
985
- // Update annotation in array
986
- this.annotations[index] = updatedAnnotation;
987
- this.resizingAnnotation = updatedAnnotation;
988
- this.redrawAnnotations();
1214
+ this.showModal = false;
1215
+ this.autoStartCapture = false; // Don't auto-start, just edit existing
1216
+ this.showCanvasEditor = true;
989
1217
  };
990
1218
  this.sending = false;
991
1219
  this.formMessage = '';
@@ -1001,28 +1229,10 @@ const FeedbackModal = class {
1001
1229
  this.overlayVisible = false;
1002
1230
  this.isAnimating = false;
1003
1231
  this.takingScreenshot = false;
1004
- this.showPreviewModal = false;
1005
- this.screenshotError = '';
1006
1232
  this.showScreenshotError = false;
1233
+ this.screenshotError = '';
1007
1234
  this.showCanvasEditor = false;
1008
- this.canvasDrawingTool = 'rectangle';
1009
- this.canvasDrawingColor = '#ff0000';
1010
- this.canvasLineWidth = 3;
1011
- this.isDrawing = false;
1012
- this.annotations = [];
1013
- this.currentAnnotation = null;
1014
- this.isDragging = false;
1015
- this.draggedAnnotation = null;
1016
- this.dragStartPos = null;
1017
- this.showColorPicker = false;
1018
- this.editingColorIndex = -1;
1019
- this.isResizing = false;
1020
- this.resizingAnnotation = null;
1021
- this.resizeStartSize = 16;
1022
- this.resizeStartDimensions = null;
1023
- this.hoveredAnnotation = null;
1024
- this.resizeHandle = false;
1025
- this.defaultColors = ['#ff0000', '#00ff00', '#0000ff', '#000000'];
1235
+ this.autoStartCapture = false;
1026
1236
  this.customFont = false;
1027
1237
  this.emailAddress = '';
1028
1238
  this.hideEmail = false;
@@ -1061,6 +1271,17 @@ const FeedbackModal = class {
1061
1271
  this.canvasEditorTitle = 'Edit screenshot';
1062
1272
  this.canvasEditorCancelText = 'Cancel';
1063
1273
  this.canvasEditorSaveText = 'Save';
1274
+ this.editTextButtonText = 'Edit Text';
1275
+ this.sizeLabelText = 'Size:';
1276
+ this.borderLabelText = 'Border:';
1277
+ this.editTextPromptText = 'Edit text:';
1278
+ this.screenshotErrorGeneral = 'Failed to capture screenshot.';
1279
+ this.screenshotErrorPermission = 'Permission denied. Please allow screen sharing to take screenshots.';
1280
+ this.screenshotErrorNotSupported = 'Screen capture is not supported in this browser.';
1281
+ this.screenshotErrorNotFound = 'No screen sources available for capture.';
1282
+ this.screenshotErrorCancelled = 'Screenshot capture was cancelled.';
1283
+ this.screenshotErrorBrowserNotSupported = 'Your browser does not support screen capture. Please use a browser like Chrome, Firefox, or Safari.';
1284
+ this.screenshotErrorUnexpected = 'An unexpected error occurred. Please try again.';
1064
1285
  }
1065
1286
  componentWillLoad() {
1066
1287
  if (this.fetchData)
@@ -1095,63 +1316,6 @@ const FeedbackModal = class {
1095
1316
  handleEmailInput(event) {
1096
1317
  this.formEmail = event.target.value;
1097
1318
  }
1098
- async captureViewportScreenshot() {
1099
- try {
1100
- // Check if Screen Capture API is supported
1101
- if (!navigator.mediaDevices || !navigator.mediaDevices.getDisplayMedia) {
1102
- throw new Error('Screen Capture API is not supported in this browser');
1103
- }
1104
- // Request screen capture with preference for current tab
1105
- const stream = await navigator.mediaDevices.getDisplayMedia({
1106
- video: {
1107
- mediaSource: 'screen',
1108
- width: { ideal: window.innerWidth },
1109
- height: { ideal: window.innerHeight }
1110
- },
1111
- audio: false,
1112
- preferCurrentTab: true
1113
- });
1114
- // Create video element to capture frame
1115
- const video = document.createElement('video');
1116
- video.srcObject = stream;
1117
- video.autoplay = true;
1118
- video.muted = true;
1119
- return new Promise((resolve, reject) => {
1120
- video.onloadedmetadata = () => {
1121
- video.play();
1122
- // Wait a moment for video to stabilize
1123
- setTimeout(() => {
1124
- try {
1125
- // Create canvas to capture frame
1126
- const canvas = document.createElement('canvas');
1127
- canvas.width = video.videoWidth;
1128
- canvas.height = video.videoHeight;
1129
- const ctx = canvas.getContext('2d');
1130
- ctx.drawImage(video, 0, 0);
1131
- // Stop the stream
1132
- stream.getTracks().forEach(track => track.stop());
1133
- // Convert to data URL
1134
- const dataUrl = canvas.toDataURL('image/png');
1135
- console.log('Screenshot captured successfully using Screen Capture API');
1136
- resolve(dataUrl);
1137
- }
1138
- catch (error) {
1139
- stream.getTracks().forEach(track => track.stop());
1140
- reject(error);
1141
- }
1142
- }, 100);
1143
- };
1144
- video.onerror = (_) => {
1145
- stream.getTracks().forEach(track => track.stop());
1146
- reject(new Error('Failed to load video for screenshot capture'));
1147
- };
1148
- });
1149
- }
1150
- catch (error) {
1151
- console.error('Screen capture failed:', error);
1152
- throw error;
1153
- }
1154
- }
1155
1319
  handleCheckboxChange(event) {
1156
1320
  this.isPrivacyChecked = event.target.checked;
1157
1321
  }
@@ -1162,7 +1326,7 @@ const FeedbackModal = class {
1162
1326
  this.selectedRating = newRating;
1163
1327
  }
1164
1328
  render() {
1165
- return (index.h("div", { class: `feedback-modal-wrapper ${this.customFont ? 'feedback-modal-wrapper--custom-font' : ''}` }, this.showScreenshotError && (index.h("div", { class: "screenshot-error-notification" }, index.h("div", { class: "screenshot-error-content" }, index.h("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, index.h("circle", { cx: "12", cy: "12", r: "10" }), index.h("line", { x1: "15", y1: "9", x2: "9", y2: "15" }), index.h("line", { x1: "9", y1: "9", x2: "15", y2: "15" })), index.h("span", null, this.screenshotError), index.h("button", { class: "error-close-btn", onClick: () => this.showScreenshotError = false, title: "Close" }, index.h("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, index.h("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), index.h("line", { x1: "6", y1: "6", x2: "18", y2: "18" })))))), this.showModal && (index.h("div", { class: `feedback-overlay ${this.isAnimating ? 'feedback-overlay--visible' : ''}` })), this.showModal && (index.h("div", { class: `feedback-modal-content feedback-modal-content--${this.modalPosition} ${this.isAnimating ? 'feedback-modal-content--open' : ''}`, ref: (el) => (this.modalContent = el) }, index.h("div", { class: "feedback-modal-header" }, !this.formSuccess && !this.formError ? (index.h("span", null, this.modalTitle)) : this.formSuccess ? (index.h("span", null, this.modalTitleSuccess)) : (index.h("span", null, this.modalTitleError)), index.h("button", { class: "feedback-modal-close", onClick: this.close }, index.h("svg", { xmlns: "http://www.w3.org/2000/svg", width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", stroke: "#191919", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", class: "feather feather-x" }, index.h("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), index.h("line", { x1: "6", y1: "6", x2: "18", y2: "18" })))), index.h("div", { class: "feedback-modal-body" }, !this.formSuccess && !this.formError ? (index.h("form", { onSubmit: this.handleSubmit }, !this.hideRating && (index.h("div", { class: "feedback-modal-rating" }, this.ratingMode === 'thumbs' ? (index.h("div", { class: "feedback-modal-rating-content" }, index.h("span", { class: "feedback-modal-input-heading" }, this.ratingPlaceholder), index.h("div", { class: "feedback-modal-rating-buttons feedback-modal-rating-buttons--thumbs" }, index.h("button", { title: "Yes", class: `feedback-modal-rating-button ${this.selectedRating === 1
1329
+ return (index.h("div", { class: `feedback-modal-wrapper ${this.customFont ? 'feedback-modal-wrapper--custom-font' : ''}` }, this.showCanvasEditor && (index.h("canvas-editor", { ref: (el) => this.canvasEditorRef = el, "canvas-editor-title": this.canvasEditorTitle, "canvas-editor-cancel-text": this.canvasEditorCancelText, "canvas-editor-save-text": this.canvasEditorSaveText, "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.editTextButtonText, "size-label-text": this.sizeLabelText, "border-label-text": this.borderLabelText, "edit-text-prompt-text": this.editTextPromptText, "screenshot-error-general": this.screenshotErrorGeneral, "screenshot-error-permission": this.screenshotErrorPermission, "screenshot-error-not-supported": this.screenshotErrorNotSupported, "screenshot-error-not-found": this.screenshotErrorNotFound, "screenshot-error-cancelled": this.screenshotErrorCancelled, "screenshot-error-browser-not-supported": this.screenshotErrorBrowserNotSupported, "screenshot-error-unexpected": this.screenshotErrorUnexpected, onScreenshotReady: this.handleScreenshotReady, onScreenshotCancelled: this.handleScreenshotCancelled, onScreenshotFailed: this.handleScreenshotError })), this.showScreenshotError && (index.h("div", { class: "screenshot-error-notification" }, index.h("div", { class: "screenshot-error-content" }, index.h("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, index.h("circle", { cx: "12", cy: "12", r: "10" }), index.h("line", { x1: "15", y1: "9", x2: "9", y2: "15" }), index.h("line", { x1: "9", y1: "9", x2: "15", y2: "15" })), index.h("span", null, this.screenshotError), index.h("button", { class: "error-close-btn", onClick: () => this.showScreenshotError = false, title: "Close" }, index.h("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, index.h("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), index.h("line", { x1: "6", y1: "6", x2: "18", y2: "18" })))))), this.showModal && (index.h("div", { class: `feedback-overlay ${this.isAnimating ? 'feedback-overlay--visible' : ''}` })), this.showModal && (index.h("div", { class: `feedback-modal-content feedback-modal-content--${this.modalPosition} ${this.isAnimating ? 'feedback-modal-content--open' : ''}`, ref: (el) => (this.modalContent = el) }, index.h("div", { class: "feedback-modal-header" }, !this.formSuccess && !this.formError ? (index.h("span", null, this.modalTitle)) : this.formSuccess ? (index.h("span", null, this.modalTitleSuccess)) : (index.h("span", null, this.modalTitleError)), index.h("button", { class: "feedback-modal-close", onClick: this.close }, index.h("svg", { xmlns: "http://www.w3.org/2000/svg", width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", stroke: "#191919", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", class: "feather feather-x" }, index.h("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), index.h("line", { x1: "6", y1: "6", x2: "18", y2: "18" })))), index.h("div", { class: "feedback-modal-body" }, !this.formSuccess && !this.formError ? (index.h("form", { onSubmit: this.handleSubmit }, !this.hideRating && (index.h("div", { class: "feedback-modal-rating" }, this.ratingMode === 'thumbs' ? (index.h("div", { class: "feedback-modal-rating-content" }, index.h("span", { class: "feedback-modal-input-heading" }, this.ratingPlaceholder), index.h("div", { class: "feedback-modal-rating-buttons feedback-modal-rating-buttons--thumbs" }, index.h("button", { title: "Yes", class: `feedback-modal-rating-button ${this.selectedRating === 1
1166
1330
  ? 'feedback-modal-rating-button--selected'
1167
1331
  : ''}`, onClick: (event) => {
1168
1332
  event.preventDefault();
@@ -1178,7 +1342,7 @@ const FeedbackModal = class {
1178
1342
  event.preventDefault();
1179
1343
  this.handleRatingChange(rating);
1180
1344
  } }, index.h("svg", { xmlns: "http://www.w3.org/2000/svg", width: "28", height: "28", viewBox: "0 0 24 24", fill: "none", stroke: "#5F6368", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, index.h("polygon", { points: "12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" })))))))))), index.h("div", { class: "feedback-modal-text" }, index.h("textarea", { placeholder: this.messagePlaceholder, value: this.formMessage, onInput: (event) => this.handleMessageInput(event) })), !this.hideEmail && (index.h("div", { class: "feedback-modal-email" }, index.h("input", { placeholder: this.emailPlaceholder, type: "email", onInput: (event) => this.handleEmailInput(event), value: this.formEmail, required: this.isEmailRequired }))), index.h("div", { class: "feedback-verification" }, index.h("input", { type: "text", name: "verification", style: { display: 'none' }, onInput: (event) => this.handleVerification(event), value: this.formVerification })), !this.hidePrivacyPolicy && (index.h("div", { class: "feedback-modal-privacy" }, index.h("input", { type: "checkbox", id: "privacyPolicy", onChange: (ev) => this.handleCheckboxChange(ev), required: true }), index.h("span", { innerHTML: this.privacyPolicyText }))), index.h("div", { class: `feedback-modal-buttons ${this.hideScreenshotButton ? 'single' : ''}` }, !this.hideScreenshotButton && (index.h("button", { type: "button", class: `feedback-modal-button feedback-modal-button--screenshot ${this.encodedScreenshot ? 'feedback-modal-button--active' : ''}`, onClick: this.openScreenShot, disabled: this.sending || this.takingScreenshot }, this.encodedScreenshot && (index.h("div", { class: "screenshot-preview", onClick: this.openCanvasEditor }, index.h("img", { src: this.encodedScreenshot, alt: "Screenshot Preview" }))), !this.encodedScreenshot && !this.takingScreenshot && (index.h("svg", { xmlns: "http://www.w3.org/2000/svg", height: "24", viewBox: "0 -960 960 960", width: "24" }, index.h("path", { d: "M680-80v-120H560v-80h120v-120h80v120h120v80H760v120h-80ZM200-200v-200h80v120h120v80H200Zm0-360v-200h200v80H280v120h-80Zm480 0v-120H560v-80h200v200h-80Z" }))), this.takingScreenshot && (index.h("div", { class: "screenshot-loading" }, index.h("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "#666", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", class: "feather-loader" }, index.h("line", { x1: "12", y1: "2", x2: "12", y2: "6" }), index.h("line", { x1: "12", y1: "18", x2: "12", y2: "22" }), index.h("line", { x1: "4.93", y1: "4.93", x2: "7.76", y2: "7.76" }), index.h("line", { x1: "16.24", y1: "16.24", x2: "19.07", y2: "19.07" }), index.h("line", { x1: "2", y1: "12", x2: "6", y2: "12" }), index.h("line", { x1: "18", y1: "12", x2: "22", y2: "12" }), index.h("line", { x1: "4.93", y1: "19.07", x2: "7.76", y2: "16.24" }), index.h("line", { x1: "16.24", y1: "7.76", x2: "19.07", y2: "4.93" })))), this.takingScreenshot ? this.screenshotTakingText :
1181
- this.encodedScreenshot ? this.screenshotAttachedText : this.screenshotButtonText)), index.h("button", { class: "feedback-modal-button feedback-modal-button--submit", type: "submit", disabled: this.sending }, this.sendButtonText)))) : this.formSuccess && !this.formError ? (index.h("div", { class: "feedback-modal-success" }, index.h("p", { class: "feedback-modal-message" }, this.successMessage))) : this.formError && this.formErrorStatus == 404 ? (index.h("p", { class: "feedback-modal-message" }, this.errorMessage404)) : this.formError && this.formErrorStatus == 403 ? (index.h("p", { class: "feedback-modal-message" }, this.errorMessage403)) : this.formError ? (index.h("p", { class: "feedback-modal-message" }, this.errorMessage)) : (index.h("span", null))), index.h("div", { class: "feedback-modal-footer" }, index.h("div", { class: "feedback-logo", style: { display: this.whitelabel ? 'none' : 'block' } }, "Powered by", ' ', index.h("a", { target: "_blank", href: "https://pushfeedback.com" }, "PushFeedback.com")), this.footerText && (index.h("div", { class: "feedback-footer-text" }, index.h("span", { innerHTML: this.footerText })))))), this.showCanvasEditor && (index.h("div", { class: "canvas-editor-overlay" }, index.h("div", { class: "canvas-editor-modal" }, index.h("div", { class: "canvas-editor-header" }, index.h("div", { class: "canvas-editor-title" }, index.h("h3", null, this.canvasEditorTitle)), index.h("div", { class: "canvas-editor-toolbar" }, index.h("div", { class: "toolbar-section" }, index.h("div", { class: "tool-group" }, index.h("button", { class: `tool-btn ${this.canvasDrawingTool === 'rectangle' ? 'active' : ''}`, onClick: () => this.canvasDrawingTool = 'rectangle', title: "Rectangle" }, index.h("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, index.h("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2", ry: "2" }))), index.h("button", { class: `tool-btn ${this.canvasDrawingTool === 'line' ? 'active' : ''}`, onClick: () => this.canvasDrawingTool = 'line', title: "Line" }, index.h("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, index.h("line", { x1: "5", y1: "12", x2: "19", y2: "12" }))), index.h("button", { class: `tool-btn ${this.canvasDrawingTool === 'arrow' ? 'active' : ''}`, onClick: () => this.canvasDrawingTool = 'arrow', title: "Arrow" }, index.h("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, index.h("line", { x1: "7", y1: "17", x2: "17", y2: "7" }), index.h("polyline", { points: "7,7 17,7 17,17" }))), index.h("button", { class: `tool-btn ${this.canvasDrawingTool === 'text' ? 'active' : ''}`, onClick: () => this.canvasDrawingTool = 'text', title: "Text" }, index.h("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, index.h("polyline", { points: "4,7 4,4 20,4 20,7" }), index.h("line", { x1: "9", y1: "20", x2: "15", y2: "20" }), index.h("line", { x1: "12", y1: "4", x2: "12", y2: "20" }))), index.h("div", { class: "toolbar-divider" }), index.h("button", { class: "tool-btn undo-btn", onClick: this.undoLastAnnotation, disabled: this.annotations.length === 0, title: "Undo" }, index.h("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round" }, index.h("polyline", { points: "1,4 1,10 7,10" }), index.h("path", { d: "M3.51,15a9,9,0,0,0,14.85-3.36,9,9,0,0,0-9.19-10.15L1.83,10" }))))), index.h("div", { class: "toolbar-section" }, index.h("div", { class: "color-palette" }, this.defaultColors.map((color, index$1) => (index.h("div", { class: "color-slot-wrapper" }, index.h("button", { class: `color-btn ${this.canvasDrawingColor === color ? 'active' : ''} ${this.editingColorIndex === index$1 ? 'editing' : ''}`, style: { backgroundColor: color }, onClick: () => this.handleColorSlotClick(index$1), title: `Color ${index$1 + 1} - Click to customize` }, this.editingColorIndex === index$1 && (index.h("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "white", "stroke-width": "2" }, index.h("path", { d: "M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z" })))), this.editingColorIndex === index$1 && this.showColorPicker && (index.h("div", { class: "color-picker-dropdown" }, index.h("input", { type: "color", value: color, onInput: (e) => this.handleColorPickerInput(e), onClick: (e) => this.handleColorPickerClick(e) })))))))), index.h("div", { class: "toolbar-section" }, index.h("div", { class: "size-control" }, index.h("input", { type: "range", min: "1", max: "10", value: this.canvasLineWidth, onInput: (e) => this.canvasLineWidth = parseInt(e.target.value), class: "size-slider" }), index.h("span", { class: "size-value" }, this.canvasLineWidth, "px"))), index.h("div", { class: "toolbar-section" }, index.h("button", { class: "action-btn secondary", onClick: this.closeCanvasEditor }, this.canvasEditorCancelText), index.h("button", { class: "action-btn primary", onClick: this.saveAnnotations }, this.canvasEditorSaveText))), index.h("div", { class: "canvas-editor-content" }, index.h("canvas", { ref: (el) => this.canvasRef = el, class: "annotation-canvas", onMouseDown: this.handleCanvasMouseDown, onMouseMove: this.handleCanvasMouseMove, onMouseUp: this.handleCanvasMouseUp, onMouseLeave: this.handleCanvasMouseUp }))))))));
1345
+ this.encodedScreenshot ? this.screenshotAttachedText : this.screenshotButtonText)), index.h("button", { class: "feedback-modal-button feedback-modal-button--submit", type: "submit", disabled: this.sending }, this.sendButtonText)))) : this.formSuccess && !this.formError ? (index.h("div", { class: "feedback-modal-success" }, index.h("p", { class: "feedback-modal-message" }, this.successMessage))) : this.formError && this.formErrorStatus == 404 ? (index.h("p", { class: "feedback-modal-message" }, this.errorMessage404)) : this.formError && this.formErrorStatus == 403 ? (index.h("p", { class: "feedback-modal-message" }, this.errorMessage403)) : this.formError ? (index.h("p", { class: "feedback-modal-message" }, this.errorMessage)) : (index.h("span", null))), index.h("div", { class: "feedback-modal-footer" }, index.h("div", { class: "feedback-logo", style: { display: this.whitelabel ? 'none' : 'block' } }, "Powered by", ' ', index.h("a", { target: "_blank", href: "https://pushfeedback.com" }, "PushFeedback.com")), this.footerText && (index.h("div", { class: "feedback-footer-text" }, index.h("span", { innerHTML: this.footerText }))))))));
1182
1346
  }
1183
1347
  componentDidRender() {
1184
1348
  if (this.showModal) {
@@ -1198,5 +1362,6 @@ const FeedbackModal = class {
1198
1362
  };
1199
1363
  FeedbackModal.style = feedbackModalCss;
1200
1364
 
1365
+ exports.canvas_editor = CanvasEditor;
1201
1366
  exports.feedback_button = FeedbackButton;
1202
1367
  exports.feedback_modal = FeedbackModal;