reflet-sdk 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/dist/client-BYuSKJw5.js.map +1 -1
- package/dist/{client-Bi-GlSii.d.ts → client-CSyPxFPK.d.ts} +2 -2
- package/dist/{client-Bi-GlSii.d.ts.map → client-CSyPxFPK.d.ts.map} +1 -1
- package/dist/feedback.d.ts +1 -1
- package/dist/feedback.d.ts.map +1 -1
- package/dist/feedback.js +538 -267
- package/dist/feedback.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/react.d.ts +2 -2
- package/dist/{types-xyOadyfX.d.ts → types-UKPPJGrA.d.ts} +6 -2
- package/dist/types-UKPPJGrA.d.ts.map +1 -0
- package/package.json +2 -2
- package/dist/types-xyOadyfX.d.ts.map +0 -1
package/dist/feedback.js
CHANGED
|
@@ -94,6 +94,9 @@ const FORWARD_REF_TYPE = Symbol.for("react.forward_ref");
|
|
|
94
94
|
const MAX_STACK_DEPTH = 6;
|
|
95
95
|
const MAX_FIBER_WALK = 60;
|
|
96
96
|
const MAX_OWNER_WALK = 8;
|
|
97
|
+
const MIN_NAME_LENGTH = 4;
|
|
98
|
+
const CAPITALIZED = /^[A-Z]/;
|
|
99
|
+
const WORD_INSIDE = /[a-z]{2}/;
|
|
97
100
|
const ORIGIN_PREFIX = /^[a-z][\w+.-]*:\/\/[^/]*/i;
|
|
98
101
|
const WEBPACK_PREFIX = /^webpack-internal:\/{2,}(\.\/)?/;
|
|
99
102
|
const STACK_LOCATION = /([^\s()]+):(\d+):(\d+)\)?\s*$/;
|
|
@@ -122,6 +125,14 @@ function readName(value) {
|
|
|
122
125
|
const name = Reflect.get(value, "name");
|
|
123
126
|
return typeof name === "string" && name ? name : null;
|
|
124
127
|
}
|
|
128
|
+
/**
|
|
129
|
+
* Minified bundles hand out names like `Ur` or `Sn`. Reporting those as the
|
|
130
|
+
* owning component actively misleads an agent, so only real words get through.
|
|
131
|
+
*/
|
|
132
|
+
function isMeaningfulName(name) {
|
|
133
|
+
if (name.includes("/") || name.includes("_")) return true;
|
|
134
|
+
return name.length >= MIN_NAME_LENGTH && CAPITALIZED.test(name) && WORD_INSIDE.test(name);
|
|
135
|
+
}
|
|
125
136
|
function componentName(type, depth = 0) {
|
|
126
137
|
if (typeof type === "string" || type === null || depth > 3) return null;
|
|
127
138
|
if (isRecord(type)) {
|
|
@@ -130,8 +141,7 @@ function componentName(type, depth = 0) {
|
|
|
130
141
|
if (marker === FORWARD_REF_TYPE) return componentName(Reflect.get(type, "render"), depth + 1);
|
|
131
142
|
}
|
|
132
143
|
const name = readName(type);
|
|
133
|
-
|
|
134
|
-
return name[0] === name[0]?.toUpperCase() || name.includes("/") ? name : null;
|
|
144
|
+
return name && isMeaningfulName(name) ? name : null;
|
|
135
145
|
}
|
|
136
146
|
/** Component names owning the fiber, innermost first. */
|
|
137
147
|
function resolveComponentStack(fiber) {
|
|
@@ -205,14 +215,131 @@ function inspectReactElement(element) {
|
|
|
205
215
|
};
|
|
206
216
|
}
|
|
207
217
|
//#endregion
|
|
218
|
+
//#region src/feedback/core/redact.ts
|
|
219
|
+
/**
|
|
220
|
+
* The picked element's markup leaves the reporter's browser and ends up in an
|
|
221
|
+
* agent prompt, so it is scrubbed here: typed-in values, credentials and
|
|
222
|
+
* anything marked `data-reflet-redact` never make the trip.
|
|
223
|
+
*/
|
|
224
|
+
const EMAIL = /[\w.%+-]+@[\w.-]+\.[a-z]{2,}/gi;
|
|
225
|
+
const LONG_TOKEN = /\b[\w-]{28,}\b/g;
|
|
226
|
+
const OPAQUE_URI = /^(?:data|blob|javascript):/i;
|
|
227
|
+
const WHITESPACE_RUN = /\s+/g;
|
|
228
|
+
const URL_QUERY = /[?#]/;
|
|
229
|
+
const REDACTED = "[redacted]";
|
|
230
|
+
const ELLIPSIS = "…";
|
|
231
|
+
const MAX_ATTRIBUTE_LENGTH = 120;
|
|
232
|
+
const MAX_TEXT_LENGTH = 200;
|
|
233
|
+
const MAX_CHILD_DEPTH = 3;
|
|
234
|
+
const MAX_CLONED_NODES = 400;
|
|
235
|
+
const DROPPED_TAGS = /* @__PURE__ */ new Set([
|
|
236
|
+
"script",
|
|
237
|
+
"style",
|
|
238
|
+
"noscript",
|
|
239
|
+
"template"
|
|
240
|
+
]);
|
|
241
|
+
const VALUE_HOLDERS = /* @__PURE__ */ new Set([
|
|
242
|
+
"input",
|
|
243
|
+
"textarea",
|
|
244
|
+
"select",
|
|
245
|
+
"option"
|
|
246
|
+
]);
|
|
247
|
+
const URL_ATTRIBUTES = /* @__PURE__ */ new Set([
|
|
248
|
+
"src",
|
|
249
|
+
"srcset",
|
|
250
|
+
"href",
|
|
251
|
+
"poster",
|
|
252
|
+
"action",
|
|
253
|
+
"formaction"
|
|
254
|
+
]);
|
|
255
|
+
function truncate$1(value, max) {
|
|
256
|
+
return value.length <= max ? value : `${value.slice(0, max - 1)}${ELLIPSIS}`;
|
|
257
|
+
}
|
|
258
|
+
/** Emails and token-shaped strings, wherever they appear in reported text. */
|
|
259
|
+
function maskSecrets(value) {
|
|
260
|
+
return value.replace(EMAIL, REDACTED).replace(LONG_TOKEN, REDACTED);
|
|
261
|
+
}
|
|
262
|
+
function sanitizeUrl(value) {
|
|
263
|
+
if (OPAQUE_URI.test(value)) return `${value.slice(0, value.indexOf(":"))}:${ELLIPSIS}`;
|
|
264
|
+
return value.split(URL_QUERY)[0] ?? value;
|
|
265
|
+
}
|
|
266
|
+
function sanitizeAttributes(element) {
|
|
267
|
+
const holdsTypedValue = VALUE_HOLDERS.has(element.localName);
|
|
268
|
+
for (const { name, value } of Array.from(element.attributes)) {
|
|
269
|
+
if (holdsTypedValue && name === "value") {
|
|
270
|
+
element.removeAttribute(name);
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
const cleaned = URL_ATTRIBUTES.has(name) ? sanitizeUrl(value) : maskSecrets(value);
|
|
274
|
+
element.setAttribute(name, truncate$1(cleaned, MAX_ATTRIBUTE_LENGTH));
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
function collapse(element) {
|
|
278
|
+
element.replaceChildren(element.ownerDocument.createTextNode(ELLIPSIS));
|
|
279
|
+
}
|
|
280
|
+
function sanitizeTree(element, depth) {
|
|
281
|
+
sanitizeAttributes(element);
|
|
282
|
+
if (element.hasAttribute("data-reflet-redact")) {
|
|
283
|
+
element.replaceChildren(element.ownerDocument.createTextNode(REDACTED));
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
if (element.localName === "svg") {
|
|
287
|
+
element.replaceChildren();
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
for (const child of Array.from(element.childNodes)) {
|
|
291
|
+
if (child.nodeType === Node.COMMENT_NODE) {
|
|
292
|
+
child.remove();
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
if (child.nodeType === Node.TEXT_NODE) {
|
|
296
|
+
child.textContent = truncate$1(maskSecrets(child.textContent ?? ""), MAX_TEXT_LENGTH);
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
if (!(child instanceof Element)) continue;
|
|
300
|
+
if (DROPPED_TAGS.has(child.localName)) {
|
|
301
|
+
child.remove();
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
if (depth >= MAX_CHILD_DEPTH) {
|
|
305
|
+
sanitizeAttributes(child);
|
|
306
|
+
collapse(child);
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
sanitizeTree(child, depth + 1);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
/** Redacted `outerHTML`, shallow-cloned when the subtree is too large to be useful. */
|
|
313
|
+
function sanitizeMarkup(element, maxLength) {
|
|
314
|
+
const isSmallEnough = element.querySelectorAll("*").length <= MAX_CLONED_NODES;
|
|
315
|
+
const clone = element.cloneNode(isSmallEnough);
|
|
316
|
+
if (!(clone instanceof Element)) return "";
|
|
317
|
+
sanitizeTree(clone, 0);
|
|
318
|
+
if (!isSmallEnough) collapse(clone);
|
|
319
|
+
return truncate$1(clone.outerHTML.replace(WHITESPACE_RUN, " "), maxLength);
|
|
320
|
+
}
|
|
321
|
+
//#endregion
|
|
208
322
|
//#region src/feedback/core/element-selector.ts
|
|
209
323
|
const SAFE_ID = /^[A-Za-z][\w-]*$/;
|
|
210
324
|
const WHITESPACE = /\s+/g;
|
|
211
325
|
const MAX_LABEL_LENGTH = 80;
|
|
212
326
|
const MAX_HTML_LENGTH = 600;
|
|
327
|
+
const MAX_REGION_LENGTH = 160;
|
|
328
|
+
const MAX_HANDLE_VALUE_LENGTH = 80;
|
|
213
329
|
const MAX_DEPTH = 12;
|
|
330
|
+
const MIN_PATH_SEGMENTS = 2;
|
|
214
331
|
const MAX_SELECTOR_LENGTH = 600;
|
|
215
332
|
const MAX_SOURCE_LOCATION_LENGTH = 400;
|
|
333
|
+
const TEST_ATTRIBUTES = [
|
|
334
|
+
"data-testid",
|
|
335
|
+
"data-test-id",
|
|
336
|
+
"data-test",
|
|
337
|
+
"data-cy",
|
|
338
|
+
"data-qa"
|
|
339
|
+
];
|
|
340
|
+
const NAMING_ATTRIBUTES = ["name", "aria-label"];
|
|
341
|
+
const LANDMARK_SELECTOR = "dialog, [role=\"dialog\"], [role=\"alertdialog\"], main, nav, aside, header, footer, form, section, article";
|
|
342
|
+
const HEADING_SELECTOR = "h1, h2, h3, h4, h5, h6, [role=\"heading\"]";
|
|
216
343
|
function isUnique(selector) {
|
|
217
344
|
try {
|
|
218
345
|
return document.querySelectorAll(selector).length === 1;
|
|
@@ -223,13 +350,23 @@ function isUnique(selector) {
|
|
|
223
350
|
function escapeAttributeValue(value) {
|
|
224
351
|
return value.replace(/["\\]/g, "\\$&");
|
|
225
352
|
}
|
|
353
|
+
function attributeHandle(element, attribute, qualifier) {
|
|
354
|
+
const value = element.getAttribute(attribute);
|
|
355
|
+
if (!value || value.length > MAX_HANDLE_VALUE_LENGTH) return null;
|
|
356
|
+
const selector = `${qualifier}[${attribute}="${escapeAttributeValue(value)}"]`;
|
|
357
|
+
return isUnique(selector) ? selector : null;
|
|
358
|
+
}
|
|
359
|
+
/** Something an engineer would have written on purpose, unlike a positional path. */
|
|
226
360
|
function uniqueHandle(element) {
|
|
227
361
|
const id = element.getAttribute("id");
|
|
228
362
|
if (id && SAFE_ID.test(id) && isUnique(`#${id}`)) return `#${id}`;
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
363
|
+
for (const attribute of TEST_ATTRIBUTES) {
|
|
364
|
+
const handle = attributeHandle(element, attribute, "");
|
|
365
|
+
if (handle) return handle;
|
|
366
|
+
}
|
|
367
|
+
for (const attribute of NAMING_ATTRIBUTES) {
|
|
368
|
+
const handle = attributeHandle(element, attribute, element.localName);
|
|
369
|
+
if (handle) return handle;
|
|
233
370
|
}
|
|
234
371
|
return null;
|
|
235
372
|
}
|
|
@@ -243,6 +380,9 @@ function positionalSegment(element) {
|
|
|
243
380
|
}
|
|
244
381
|
/**
|
|
245
382
|
* Shortest selector that still resolves back to `element` from the document.
|
|
383
|
+
* Grows the path one ancestor at a time and stops as soon as it is unique, so
|
|
384
|
+
* a stable handle anywhere up the tree replaces the whole positional chain.
|
|
385
|
+
* A bare tag is never enough: positional paths keep a parent for context.
|
|
246
386
|
*/
|
|
247
387
|
function buildSelector(element) {
|
|
248
388
|
if (element === document.documentElement) return "html";
|
|
@@ -251,43 +391,58 @@ function buildSelector(element) {
|
|
|
251
391
|
if (handle) return handle;
|
|
252
392
|
const segments = [];
|
|
253
393
|
let current = element;
|
|
254
|
-
let depth = 0;
|
|
255
|
-
while (current && depth < MAX_DEPTH) {
|
|
256
|
-
if (current === document.body) {
|
|
257
|
-
segments.unshift("body");
|
|
258
|
-
break;
|
|
259
|
-
}
|
|
260
|
-
if (current === document.documentElement) {
|
|
261
|
-
segments.unshift("html");
|
|
262
|
-
break;
|
|
263
|
-
}
|
|
394
|
+
for (let depth = 0; current && depth < MAX_DEPTH; depth++) {
|
|
264
395
|
const ancestorHandle = current === element ? null : uniqueHandle(current);
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
segments.unshift(positionalSegment(current));
|
|
396
|
+
segments.unshift(ancestorHandle ?? positionalSegment(current));
|
|
397
|
+
const candidate = segments.join(" > ");
|
|
398
|
+
const isAnchored = segments.length >= MIN_PATH_SEGMENTS;
|
|
399
|
+
if (ancestorHandle || current === document.body || isAnchored && isUnique(candidate)) return candidate;
|
|
270
400
|
current = current.parentElement;
|
|
271
|
-
depth++;
|
|
272
401
|
}
|
|
273
402
|
return segments.join(" > ");
|
|
274
403
|
}
|
|
275
404
|
function truncate(value, max) {
|
|
276
405
|
return value.length <= max ? value : `${value.slice(0, max - 1)}…`;
|
|
277
406
|
}
|
|
407
|
+
function collapseText(value) {
|
|
408
|
+
return value.replace(WHITESPACE, " ").trim();
|
|
409
|
+
}
|
|
278
410
|
function accessibleLabel(element) {
|
|
279
411
|
const explicit = element.getAttribute("aria-label") ?? element.getAttribute("title") ?? element.getAttribute("alt") ?? element.getAttribute("placeholder");
|
|
280
412
|
if (explicit?.trim()) return explicit.trim();
|
|
281
|
-
return (element.textContent ?? "")
|
|
413
|
+
return collapseText(element.textContent ?? "");
|
|
282
414
|
}
|
|
283
415
|
/** Short human label such as `button "Sign in"` or `div.card`. */
|
|
284
416
|
function describeElement(element) {
|
|
285
417
|
const tag = element.localName;
|
|
286
|
-
const label = accessibleLabel(element);
|
|
418
|
+
const label = maskSecrets(accessibleLabel(element));
|
|
287
419
|
if (label) return truncate(`${tag} "${label}"`, MAX_LABEL_LENGTH);
|
|
288
420
|
const firstClass = element.classList.item(0);
|
|
289
421
|
return truncate(firstClass ? `${tag}.${firstClass}` : tag, MAX_LABEL_LENGTH);
|
|
290
422
|
}
|
|
423
|
+
function describeLandmark(element) {
|
|
424
|
+
const landmark = element.parentElement?.closest(LANDMARK_SELECTOR);
|
|
425
|
+
if (!landmark) return null;
|
|
426
|
+
const name = landmark.getAttribute("aria-label") ?? landmark.getAttribute("title");
|
|
427
|
+
return name?.trim() ? `${landmark.localName} "${collapseText(name)}"` : landmark.localName;
|
|
428
|
+
}
|
|
429
|
+
function headingAbove(element) {
|
|
430
|
+
let closest = null;
|
|
431
|
+
for (const heading of document.querySelectorAll(HEADING_SELECTOR)) if ((heading.compareDocumentPosition(element) & Node.DOCUMENT_POSITION_FOLLOWING) !== 0) closest = heading;
|
|
432
|
+
return collapseText(closest?.textContent ?? "") || null;
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Where the element sits from a reader's point of view — `dialog "Members" ›
|
|
436
|
+
* Danger zone` says more to an agent than any DOM path can.
|
|
437
|
+
*/
|
|
438
|
+
function describeRegion(element) {
|
|
439
|
+
const landmark = describeLandmark(element);
|
|
440
|
+
const heading = headingAbove(element);
|
|
441
|
+
const parts = [landmark];
|
|
442
|
+
if (heading && !landmark?.includes(`"${heading}"`)) parts.push(heading);
|
|
443
|
+
const region = parts.filter((part) => part !== null).join(" › ");
|
|
444
|
+
return region ? truncate(maskSecrets(region), MAX_REGION_LENGTH) : void 0;
|
|
445
|
+
}
|
|
291
446
|
function getElementRect(element) {
|
|
292
447
|
const rect = element.getBoundingClientRect();
|
|
293
448
|
return {
|
|
@@ -302,9 +457,10 @@ function buildElementSelection(element) {
|
|
|
302
457
|
const { componentStack, sourceLocation } = inspectReactElement(element);
|
|
303
458
|
return {
|
|
304
459
|
componentStack,
|
|
305
|
-
html:
|
|
460
|
+
html: sanitizeMarkup(element, MAX_HTML_LENGTH),
|
|
306
461
|
label: describeElement(element),
|
|
307
462
|
rect: getElementRect(element),
|
|
463
|
+
region: describeRegion(element),
|
|
308
464
|
selector: truncate(buildSelector(element), MAX_SELECTOR_LENGTH),
|
|
309
465
|
sourceLocation: sourceLocation ? truncate(sourceLocation, MAX_SOURCE_LOCATION_LENGTH) : sourceLocation
|
|
310
466
|
};
|
|
@@ -396,8 +552,8 @@ function collectPageContext(options) {
|
|
|
396
552
|
}
|
|
397
553
|
//#endregion
|
|
398
554
|
//#region src/feedback/types.ts
|
|
399
|
-
/** Reported with every submission.
|
|
400
|
-
const SDK_VERSION = "0.
|
|
555
|
+
/** Reported with every submission. */
|
|
556
|
+
const SDK_VERSION = "0.3.0";
|
|
401
557
|
const DEFAULT_WIDGET_LABELS = {
|
|
402
558
|
annotateHint: "Draw on the screenshot to point at the problem.",
|
|
403
559
|
attachScreenshot: "Attach a screenshot",
|
|
@@ -657,9 +813,10 @@ async function renderAnnotatedImage(source, annotations) {
|
|
|
657
813
|
}
|
|
658
814
|
//#endregion
|
|
659
815
|
//#region src/feedback/ui/icons.tsx
|
|
660
|
-
function Svg({ children, size = 16 }) {
|
|
816
|
+
function Svg({ children, className, size = 16 }) {
|
|
661
817
|
return /* @__PURE__ */ jsx("svg", {
|
|
662
818
|
"aria-hidden": "true",
|
|
819
|
+
className,
|
|
663
820
|
fill: "none",
|
|
664
821
|
height: size,
|
|
665
822
|
stroke: "currentColor",
|
|
@@ -671,14 +828,18 @@ function Svg({ children, size = 16 }) {
|
|
|
671
828
|
children
|
|
672
829
|
});
|
|
673
830
|
}
|
|
674
|
-
function ChatIcon() {
|
|
831
|
+
function ChatIcon({ className }) {
|
|
675
832
|
return /* @__PURE__ */ jsx(Svg, {
|
|
676
|
-
|
|
833
|
+
className,
|
|
834
|
+
size: 15,
|
|
677
835
|
children: /* @__PURE__ */ jsx("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" })
|
|
678
836
|
});
|
|
679
837
|
}
|
|
680
|
-
function CloseIcon() {
|
|
681
|
-
return /* @__PURE__ */ jsx(Svg, {
|
|
838
|
+
function CloseIcon({ className }) {
|
|
839
|
+
return /* @__PURE__ */ jsx(Svg, {
|
|
840
|
+
className,
|
|
841
|
+
children: /* @__PURE__ */ jsx("path", { d: "M18 6 6 18M6 6l12 12" })
|
|
842
|
+
});
|
|
682
843
|
}
|
|
683
844
|
function BugIcon() {
|
|
684
845
|
return /* @__PURE__ */ jsx(Svg, {
|
|
@@ -941,15 +1102,21 @@ function Annotator({ annotations, capture, labels, onChange, onDone }) {
|
|
|
941
1102
|
}
|
|
942
1103
|
//#endregion
|
|
943
1104
|
//#region src/feedback/ui/launcher.tsx
|
|
944
|
-
|
|
1105
|
+
/**
|
|
1106
|
+
* Never changes size between states — the panel opening must not move the
|
|
1107
|
+
* button out from under the pointer.
|
|
1108
|
+
*/
|
|
1109
|
+
function Launcher({ isOpen, label, onClick }) {
|
|
945
1110
|
return /* @__PURE__ */ jsxs("button", {
|
|
946
1111
|
"aria-expanded": isOpen,
|
|
947
|
-
"aria-label": label,
|
|
948
1112
|
className: "launcher",
|
|
949
|
-
"data-
|
|
1113
|
+
"data-open": isOpen,
|
|
950
1114
|
onClick,
|
|
951
1115
|
type: "button",
|
|
952
|
-
children: [
|
|
1116
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
1117
|
+
className: "launcher-icon",
|
|
1118
|
+
children: [/* @__PURE__ */ jsx(ChatIcon, { className: "on-open" }), /* @__PURE__ */ jsx(CloseIcon, { className: "on-close" })]
|
|
1119
|
+
}), label]
|
|
953
1120
|
});
|
|
954
1121
|
}
|
|
955
1122
|
//#endregion
|
|
@@ -970,7 +1137,7 @@ const CATEGORY_META = {
|
|
|
970
1137
|
};
|
|
971
1138
|
function Attachment({ labels, state }) {
|
|
972
1139
|
if (state.isCapturing) return /* @__PURE__ */ jsxs("div", {
|
|
973
|
-
className: "
|
|
1140
|
+
className: "card",
|
|
974
1141
|
children: [/* @__PURE__ */ jsx("div", { className: "thumb" }), /* @__PURE__ */ jsxs("div", {
|
|
975
1142
|
className: "meta",
|
|
976
1143
|
children: [/* @__PURE__ */ jsx("strong", { children: labels.retakeHint }), /* @__PURE__ */ jsx("span", { children: "…" })]
|
|
@@ -984,7 +1151,7 @@ function Attachment({ labels, state }) {
|
|
|
984
1151
|
});
|
|
985
1152
|
const annotationCount = state.annotations.length;
|
|
986
1153
|
return /* @__PURE__ */ jsxs("div", {
|
|
987
|
-
className: "
|
|
1154
|
+
className: "card",
|
|
988
1155
|
children: [
|
|
989
1156
|
/* @__PURE__ */ jsx("div", {
|
|
990
1157
|
className: "thumb",
|
|
@@ -997,12 +1164,18 @@ function Attachment({ labels, state }) {
|
|
|
997
1164
|
}),
|
|
998
1165
|
/* @__PURE__ */ jsxs("div", {
|
|
999
1166
|
className: "meta",
|
|
1000
|
-
children: [/* @__PURE__ */ jsx("strong", {
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1167
|
+
children: [/* @__PURE__ */ jsx("strong", {
|
|
1168
|
+
className: "truncate",
|
|
1169
|
+
children: labels.retakeHint
|
|
1170
|
+
}), /* @__PURE__ */ jsxs("span", {
|
|
1171
|
+
className: "truncate",
|
|
1172
|
+
children: [
|
|
1173
|
+
state.capture.width,
|
|
1174
|
+
"×",
|
|
1175
|
+
state.capture.height,
|
|
1176
|
+
annotationCount > 0 ? ` · ${annotationCount} marks` : ""
|
|
1177
|
+
]
|
|
1178
|
+
})]
|
|
1006
1179
|
}),
|
|
1007
1180
|
/* @__PURE__ */ jsxs("div", {
|
|
1008
1181
|
className: "actions",
|
|
@@ -1036,8 +1209,9 @@ function Attachment({ labels, state }) {
|
|
|
1036
1209
|
]
|
|
1037
1210
|
});
|
|
1038
1211
|
}
|
|
1039
|
-
|
|
1040
|
-
|
|
1212
|
+
/** Shows the reporter exactly what will be sent about the element they picked. */
|
|
1213
|
+
function Selection({ labels, state }) {
|
|
1214
|
+
const { elementCapture, selection } = state;
|
|
1041
1215
|
if (!selection) return /* @__PURE__ */ jsxs("button", {
|
|
1042
1216
|
className: "ghost-btn",
|
|
1043
1217
|
onClick: () => state.setStep("picking"),
|
|
@@ -1045,24 +1219,45 @@ function SelectionChip({ labels, state }) {
|
|
|
1045
1219
|
children: [/* @__PURE__ */ jsx(TargetIcon, {}), labels.pickElement]
|
|
1046
1220
|
});
|
|
1047
1221
|
const component = selection.componentStack[0];
|
|
1048
|
-
return /* @__PURE__ */ jsxs("
|
|
1049
|
-
className: "
|
|
1222
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1223
|
+
className: "card",
|
|
1050
1224
|
children: [
|
|
1051
|
-
/* @__PURE__ */ jsx(
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1225
|
+
/* @__PURE__ */ jsx("div", {
|
|
1226
|
+
className: "thumb contain",
|
|
1227
|
+
children: elementCapture && /* @__PURE__ */ jsx("img", {
|
|
1228
|
+
alt: "",
|
|
1229
|
+
height: elementCapture.height,
|
|
1230
|
+
src: elementCapture.objectUrl,
|
|
1231
|
+
width: elementCapture.width
|
|
1232
|
+
})
|
|
1233
|
+
}),
|
|
1234
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1235
|
+
className: "meta",
|
|
1236
|
+
children: [/* @__PURE__ */ jsx("strong", {
|
|
1237
|
+
className: "truncate",
|
|
1238
|
+
children: component ? `<${component}>` : selection.label
|
|
1239
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
1240
|
+
className: "truncate",
|
|
1241
|
+
children: selection.region ?? selection.sourceLocation ?? selection.selector
|
|
1242
|
+
})]
|
|
1059
1243
|
}),
|
|
1060
|
-
/* @__PURE__ */
|
|
1061
|
-
"
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1244
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1245
|
+
className: "actions",
|
|
1246
|
+
children: [/* @__PURE__ */ jsx("button", {
|
|
1247
|
+
"aria-label": labels.pickElement,
|
|
1248
|
+
className: "icon-btn",
|
|
1249
|
+
onClick: () => state.setStep("picking"),
|
|
1250
|
+
title: labels.pickElement,
|
|
1251
|
+
type: "button",
|
|
1252
|
+
children: /* @__PURE__ */ jsx(TargetIcon, {})
|
|
1253
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
1254
|
+
"aria-label": labels.cancel,
|
|
1255
|
+
className: "icon-btn",
|
|
1256
|
+
onClick: state.clearSelection,
|
|
1257
|
+
title: labels.cancel,
|
|
1258
|
+
type: "button",
|
|
1259
|
+
children: /* @__PURE__ */ jsx(CloseIcon, {})
|
|
1260
|
+
})]
|
|
1066
1261
|
})
|
|
1067
1262
|
]
|
|
1068
1263
|
});
|
|
@@ -1129,13 +1324,15 @@ function FeedbackPanel({ categories, labels, state }) {
|
|
|
1129
1324
|
ref: messageRef,
|
|
1130
1325
|
value: state.message
|
|
1131
1326
|
}),
|
|
1132
|
-
/* @__PURE__ */
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1327
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1328
|
+
className: "attachments",
|
|
1329
|
+
children: [/* @__PURE__ */ jsx(Attachment, {
|
|
1330
|
+
labels,
|
|
1331
|
+
state
|
|
1332
|
+
}), /* @__PURE__ */ jsx(Selection, {
|
|
1333
|
+
labels,
|
|
1334
|
+
state
|
|
1335
|
+
})]
|
|
1139
1336
|
}),
|
|
1140
1337
|
state.isAnonymous && /* @__PURE__ */ jsx("input", {
|
|
1141
1338
|
"aria-label": labels.emailLabel,
|
|
@@ -1192,7 +1389,7 @@ function FeedbackPanel({ categories, labels, state }) {
|
|
|
1192
1389
|
}
|
|
1193
1390
|
//#endregion
|
|
1194
1391
|
//#region src/feedback/ui/picker.tsx
|
|
1195
|
-
const LABEL_HEIGHT =
|
|
1392
|
+
const LABEL_HEIGHT = 24;
|
|
1196
1393
|
const LABEL_GAP = 6;
|
|
1197
1394
|
function isWidgetOwned(element) {
|
|
1198
1395
|
return element.closest(`[${WIDGET_MARKER}]`) !== null;
|
|
@@ -1205,14 +1402,15 @@ function elementUnder(x, y) {
|
|
|
1205
1402
|
function describeTarget(element) {
|
|
1206
1403
|
return {
|
|
1207
1404
|
componentName: resolveComponentStack(getFiberFromNode(element))[0],
|
|
1405
|
+
element,
|
|
1208
1406
|
label: describeElement(element),
|
|
1209
1407
|
rect: element.getBoundingClientRect(),
|
|
1210
|
-
|
|
1408
|
+
region: describeRegion(element)
|
|
1211
1409
|
};
|
|
1212
1410
|
}
|
|
1213
1411
|
/**
|
|
1214
1412
|
* Points at any element in the page the way a coding agent needs it: the React
|
|
1215
|
-
* component that owns it plus
|
|
1413
|
+
* component that owns it plus the page region it lives in.
|
|
1216
1414
|
*/
|
|
1217
1415
|
function ElementPicker({ hint, onCancel, onPick }) {
|
|
1218
1416
|
const [target, setTarget] = useState(null);
|
|
@@ -1223,7 +1421,14 @@ function ElementPicker({ hint, onCancel, onPick }) {
|
|
|
1223
1421
|
};
|
|
1224
1422
|
const onMove = (event) => {
|
|
1225
1423
|
const element = elementUnder(event.clientX, event.clientY);
|
|
1226
|
-
|
|
1424
|
+
if (!element) {
|
|
1425
|
+
setTarget(null);
|
|
1426
|
+
return;
|
|
1427
|
+
}
|
|
1428
|
+
setTarget((current) => current?.element === element ? {
|
|
1429
|
+
...current,
|
|
1430
|
+
rect: element.getBoundingClientRect()
|
|
1431
|
+
} : describeTarget(element));
|
|
1227
1432
|
};
|
|
1228
1433
|
const onClick = (event) => {
|
|
1229
1434
|
swallow(event);
|
|
@@ -1255,7 +1460,7 @@ function ElementPicker({ hint, onCancel, onPick }) {
|
|
|
1255
1460
|
};
|
|
1256
1461
|
}, [onCancel, onPick]);
|
|
1257
1462
|
const rect = target?.rect;
|
|
1258
|
-
const labelAbove = rect ? rect.top >
|
|
1463
|
+
const labelAbove = rect ? rect.top > 30 : true;
|
|
1259
1464
|
return /* @__PURE__ */ jsxs("div", { children: [
|
|
1260
1465
|
rect && /* @__PURE__ */ jsx("div", {
|
|
1261
1466
|
className: "picker-box",
|
|
@@ -1272,7 +1477,7 @@ function ElementPicker({ hint, onCancel, onPick }) {
|
|
|
1272
1477
|
left: Math.max(4, rect.left),
|
|
1273
1478
|
top: labelAbove ? rect.top - LABEL_HEIGHT - LABEL_GAP : rect.bottom + LABEL_GAP
|
|
1274
1479
|
},
|
|
1275
|
-
children: [
|
|
1480
|
+
children: [/* @__PURE__ */ jsx("strong", { children: target.componentName ? `<${target.componentName}>` : target.label }), target.region && /* @__PURE__ */ jsx("span", { children: target.region })]
|
|
1276
1481
|
}),
|
|
1277
1482
|
/* @__PURE__ */ jsxs("div", {
|
|
1278
1483
|
className: "picker-hint",
|
|
@@ -1293,30 +1498,42 @@ function ElementPicker({ hint, onCancel, onPick }) {
|
|
|
1293
1498
|
const WIDGET_STYLES = `
|
|
1294
1499
|
:host {
|
|
1295
1500
|
--rf-bg: #ffffff;
|
|
1296
|
-
--rf-bg-subtle:
|
|
1297
|
-
--rf-
|
|
1298
|
-
--rf-
|
|
1299
|
-
--rf-text
|
|
1300
|
-
--rf-
|
|
1501
|
+
--rf-bg-subtle: rgb(55 53 47 / 4%);
|
|
1502
|
+
--rf-bg-hover: rgb(55 53 47 / 7%);
|
|
1503
|
+
--rf-border: rgb(55 53 47 / 9%);
|
|
1504
|
+
--rf-text: #37352f;
|
|
1505
|
+
--rf-text-muted: rgb(55 53 47 / 55%);
|
|
1506
|
+
--rf-primary: #37352f;
|
|
1301
1507
|
--rf-primary-text: #ffffff;
|
|
1302
|
-
--rf-
|
|
1303
|
-
--rf-
|
|
1304
|
-
--rf-
|
|
1305
|
-
--rf-
|
|
1306
|
-
--rf-
|
|
1307
|
-
--rf-
|
|
1508
|
+
--rf-accent: #2383e2;
|
|
1509
|
+
--rf-danger: #d44c47;
|
|
1510
|
+
--rf-success: #448361;
|
|
1511
|
+
--rf-radius: 12px;
|
|
1512
|
+
--rf-radius-sm: 8px;
|
|
1513
|
+
--rf-shadow-inset: inset 0 0 0 1px var(--rf-border);
|
|
1514
|
+
--rf-shadow-low: 0 0 0 1px rgb(15 15 15 / 4%), 0 1px 2px rgb(15 15 15 / 6%), 0 4px 10px -4px rgb(15 15 15 / 10%);
|
|
1515
|
+
--rf-shadow: 0 0 0 1px rgb(15 15 15 / 4%), 0 2px 5px -1px rgb(15 15 15 / 7%), 0 12px 32px -8px rgb(15 15 15 / 16%);
|
|
1516
|
+
--rf-ring: 0 0 0 3px color-mix(in srgb, var(--rf-accent) 20%, transparent);
|
|
1517
|
+
--rf-ease: cubic-bezier(0.16, 1, 0.3, 1);
|
|
1308
1518
|
color-scheme: light;
|
|
1309
1519
|
all: initial;
|
|
1310
1520
|
font-family: var(--rf-font, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif);
|
|
1311
1521
|
}
|
|
1312
1522
|
|
|
1313
1523
|
:host([data-theme="dark"]) {
|
|
1314
|
-
--rf-bg: #
|
|
1315
|
-
--rf-bg-subtle:
|
|
1316
|
-
--rf-
|
|
1317
|
-
--rf-
|
|
1318
|
-
--rf-text
|
|
1319
|
-
--rf-
|
|
1524
|
+
--rf-bg: #202020;
|
|
1525
|
+
--rf-bg-subtle: rgb(255 255 255 / 5%);
|
|
1526
|
+
--rf-bg-hover: rgb(255 255 255 / 9%);
|
|
1527
|
+
--rf-border: rgb(255 255 255 / 9%);
|
|
1528
|
+
--rf-text: #ebeae8;
|
|
1529
|
+
--rf-text-muted: rgb(255 255 255 / 46%);
|
|
1530
|
+
--rf-primary: #f1efec;
|
|
1531
|
+
--rf-primary-text: #1c1c1b;
|
|
1532
|
+
--rf-accent: #529cca;
|
|
1533
|
+
--rf-danger: #eb5757;
|
|
1534
|
+
--rf-success: #4dab74;
|
|
1535
|
+
--rf-shadow-low: 0 0 0 1px rgb(255 255 255 / 6%), 0 1px 2px rgb(0 0 0 / 30%), 0 4px 10px -4px rgb(0 0 0 / 40%);
|
|
1536
|
+
--rf-shadow: 0 0 0 1px rgb(255 255 255 / 7%), 0 2px 5px -1px rgb(0 0 0 / 35%), 0 14px 36px -10px rgb(0 0 0 / 55%);
|
|
1320
1537
|
color-scheme: dark;
|
|
1321
1538
|
}
|
|
1322
1539
|
|
|
@@ -1325,19 +1542,19 @@ const WIDGET_STYLES = `
|
|
|
1325
1542
|
.root {
|
|
1326
1543
|
position: fixed;
|
|
1327
1544
|
z-index: 2147483000;
|
|
1545
|
+
display: flex;
|
|
1546
|
+
flex-direction: column;
|
|
1547
|
+
gap: 10px;
|
|
1328
1548
|
font-family: inherit;
|
|
1329
|
-
font-size:
|
|
1549
|
+
font-size: 13.5px;
|
|
1330
1550
|
line-height: 1.45;
|
|
1331
1551
|
color: var(--rf-text);
|
|
1332
1552
|
}
|
|
1333
1553
|
|
|
1334
|
-
.root[data-position
|
|
1335
|
-
.root[data-position
|
|
1336
|
-
.root[data-position
|
|
1337
|
-
.root[data-position
|
|
1338
|
-
|
|
1339
|
-
.stack { display: flex; flex-direction: column; gap: 12px; }
|
|
1340
|
-
.root[data-position^="top"] .stack { flex-direction: column-reverse; }
|
|
1554
|
+
.root[data-position$="right"] { right: var(--rf-offset); align-items: flex-end; }
|
|
1555
|
+
.root[data-position$="left"] { left: var(--rf-offset); align-items: flex-start; }
|
|
1556
|
+
.root[data-position^="bottom"] { bottom: var(--rf-offset); }
|
|
1557
|
+
.root[data-position^="top"] { top: var(--rf-offset); flex-direction: column-reverse; }
|
|
1341
1558
|
|
|
1342
1559
|
button {
|
|
1343
1560
|
font: inherit;
|
|
@@ -1353,64 +1570,76 @@ button {
|
|
|
1353
1570
|
.launcher {
|
|
1354
1571
|
display: inline-flex;
|
|
1355
1572
|
align-items: center;
|
|
1356
|
-
gap:
|
|
1357
|
-
height:
|
|
1358
|
-
padding: 0
|
|
1359
|
-
border-radius:
|
|
1573
|
+
gap: 6px;
|
|
1574
|
+
height: 32px;
|
|
1575
|
+
padding: 0 11px;
|
|
1576
|
+
border-radius: var(--rf-radius-sm);
|
|
1360
1577
|
background: var(--rf-primary);
|
|
1361
1578
|
color: var(--rf-primary-text);
|
|
1362
|
-
font-
|
|
1363
|
-
|
|
1364
|
-
|
|
1579
|
+
font-size: 13px;
|
|
1580
|
+
font-weight: 500;
|
|
1581
|
+
letter-spacing: -0.005em;
|
|
1582
|
+
box-shadow: var(--rf-shadow-low);
|
|
1583
|
+
transition: transform 240ms var(--rf-ease), box-shadow 240ms var(--rf-ease), opacity 160ms var(--rf-ease);
|
|
1365
1584
|
}
|
|
1366
|
-
.launcher:hover { transform: translateY(-1px);
|
|
1367
|
-
.launcher:active { transform: translateY(0) scale(0.
|
|
1368
|
-
|
|
1585
|
+
.launcher:hover { transform: translateY(-1px); box-shadow: var(--rf-shadow); }
|
|
1586
|
+
.launcher:active { transform: translateY(0) scale(0.98); }
|
|
1587
|
+
|
|
1588
|
+
.launcher-icon { position: relative; width: 15px; height: 15px; flex: none; }
|
|
1589
|
+
.launcher-icon svg { position: absolute; inset: 0; width: 100%; height: 100%; transition: opacity 200ms var(--rf-ease), transform 320ms var(--rf-ease); }
|
|
1590
|
+
.launcher-icon .on-close { opacity: 0; transform: rotate(-90deg) scale(0.6); }
|
|
1591
|
+
.launcher[data-open="true"] .on-open { opacity: 0; transform: rotate(90deg) scale(0.6); }
|
|
1592
|
+
.launcher[data-open="true"] .on-close { opacity: 1; transform: none; }
|
|
1369
1593
|
|
|
1370
1594
|
.panel {
|
|
1371
|
-
width: min(
|
|
1372
|
-
max-height: min(
|
|
1595
|
+
width: min(352px, calc(100vw - 24px));
|
|
1596
|
+
max-height: min(600px, calc(100vh - 96px));
|
|
1373
1597
|
display: flex;
|
|
1374
1598
|
flex-direction: column;
|
|
1375
1599
|
overflow: hidden;
|
|
1376
1600
|
background: var(--rf-bg);
|
|
1377
|
-
border: 1px solid var(--rf-border);
|
|
1378
1601
|
border-radius: var(--rf-radius);
|
|
1379
1602
|
box-shadow: var(--rf-shadow);
|
|
1380
1603
|
animation: rf-in 220ms var(--rf-ease);
|
|
1381
1604
|
}
|
|
1605
|
+
.root[data-position^="bottom"] .panel { transform-origin: bottom center; }
|
|
1606
|
+
.root[data-position^="top"] .panel { transform-origin: top center; animation-name: rf-in-top; }
|
|
1382
1607
|
|
|
1383
1608
|
@keyframes rf-in {
|
|
1384
|
-
from { opacity: 0; transform: translateY(
|
|
1385
|
-
|
|
1609
|
+
from { opacity: 0; transform: translateY(6px) scale(0.985); }
|
|
1610
|
+
}
|
|
1611
|
+
@keyframes rf-in-top {
|
|
1612
|
+
from { opacity: 0; transform: translateY(-6px) scale(0.985); }
|
|
1386
1613
|
}
|
|
1614
|
+
@keyframes rf-fade { from { opacity: 0; } }
|
|
1387
1615
|
|
|
1388
1616
|
@media (prefers-reduced-motion: reduce) {
|
|
1389
|
-
|
|
1617
|
+
*, *::before, *::after { animation: none !important; transition: none !important; }
|
|
1390
1618
|
}
|
|
1391
1619
|
|
|
1392
1620
|
.head {
|
|
1393
1621
|
display: flex;
|
|
1394
1622
|
align-items: center;
|
|
1395
1623
|
gap: 8px;
|
|
1396
|
-
padding:
|
|
1624
|
+
padding: 12px 10px 4px 14px;
|
|
1397
1625
|
}
|
|
1398
|
-
.head h2 { margin: 0; font-size:
|
|
1626
|
+
.head h2 { margin: 0; font-size: 13.5px; font-weight: 600; letter-spacing: -0.01em; }
|
|
1399
1627
|
.head .spacer { flex: 1; }
|
|
1400
1628
|
|
|
1401
1629
|
.icon-btn {
|
|
1402
1630
|
display: inline-flex;
|
|
1403
1631
|
align-items: center;
|
|
1404
1632
|
justify-content: center;
|
|
1405
|
-
width:
|
|
1406
|
-
height:
|
|
1407
|
-
|
|
1633
|
+
width: 26px;
|
|
1634
|
+
height: 26px;
|
|
1635
|
+
flex: none;
|
|
1636
|
+
border-radius: 6px;
|
|
1408
1637
|
color: var(--rf-text-muted);
|
|
1409
1638
|
transition: background 120ms var(--rf-ease), color 120ms var(--rf-ease);
|
|
1410
1639
|
}
|
|
1411
|
-
.icon-btn:hover { background: var(--rf-bg-
|
|
1640
|
+
.icon-btn:hover { background: var(--rf-bg-hover); color: var(--rf-text); }
|
|
1412
1641
|
|
|
1413
|
-
.body { display: flex; flex-direction: column; gap:
|
|
1642
|
+
.body { display: flex; flex-direction: column; gap: 8px; padding: 6px 14px 14px; overflow-y: auto; }
|
|
1414
1643
|
|
|
1415
1644
|
.segmented {
|
|
1416
1645
|
border: 0;
|
|
@@ -1419,94 +1648,88 @@ button {
|
|
|
1419
1648
|
display: grid;
|
|
1420
1649
|
grid-auto-flow: column;
|
|
1421
1650
|
grid-auto-columns: 1fr;
|
|
1422
|
-
gap:
|
|
1423
|
-
padding:
|
|
1651
|
+
gap: 2px;
|
|
1652
|
+
padding: 2px;
|
|
1424
1653
|
background: var(--rf-bg-subtle);
|
|
1425
|
-
border-radius:
|
|
1654
|
+
border-radius: var(--rf-radius-sm);
|
|
1426
1655
|
}
|
|
1427
1656
|
.segmented button {
|
|
1428
1657
|
display: flex;
|
|
1429
1658
|
align-items: center;
|
|
1430
1659
|
justify-content: center;
|
|
1431
|
-
gap:
|
|
1432
|
-
height:
|
|
1433
|
-
border-radius:
|
|
1434
|
-
font-size:
|
|
1660
|
+
gap: 5px;
|
|
1661
|
+
height: 26px;
|
|
1662
|
+
border-radius: 6px;
|
|
1663
|
+
font-size: 12.5px;
|
|
1435
1664
|
font-weight: 500;
|
|
1436
1665
|
color: var(--rf-text-muted);
|
|
1437
|
-
transition: background
|
|
1666
|
+
transition: background 160ms var(--rf-ease), color 160ms var(--rf-ease);
|
|
1438
1667
|
}
|
|
1668
|
+
.segmented button:hover { color: var(--rf-text); }
|
|
1439
1669
|
.segmented button[aria-pressed="true"] {
|
|
1440
1670
|
background: var(--rf-bg);
|
|
1441
1671
|
color: var(--rf-text);
|
|
1442
|
-
box-shadow:
|
|
1672
|
+
box-shadow: var(--rf-shadow-low);
|
|
1443
1673
|
}
|
|
1444
1674
|
|
|
1445
1675
|
textarea, input[type="email"] {
|
|
1446
1676
|
width: 100%;
|
|
1447
|
-
padding: 10px
|
|
1677
|
+
padding: 8px 10px;
|
|
1448
1678
|
font: inherit;
|
|
1449
1679
|
color: var(--rf-text);
|
|
1450
1680
|
background: var(--rf-bg);
|
|
1451
1681
|
border: 1px solid var(--rf-border);
|
|
1452
|
-
border-radius:
|
|
1453
|
-
transition: border-color
|
|
1682
|
+
border-radius: var(--rf-radius-sm);
|
|
1683
|
+
transition: border-color 160ms var(--rf-ease), box-shadow 160ms var(--rf-ease);
|
|
1454
1684
|
}
|
|
1455
|
-
textarea { min-height:
|
|
1685
|
+
textarea { min-height: 84px; max-height: 220px; resize: none; }
|
|
1456
1686
|
textarea::placeholder, input::placeholder { color: var(--rf-text-muted); }
|
|
1457
|
-
textarea:focus, input:focus { outline: none; border-color: var(--rf-
|
|
1687
|
+
textarea:focus, input:focus { outline: none; border-color: color-mix(in srgb, var(--rf-accent) 55%, transparent); box-shadow: var(--rf-ring); }
|
|
1688
|
+
|
|
1689
|
+
.attachments { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
1458
1690
|
|
|
1459
|
-
.
|
|
1691
|
+
.card {
|
|
1692
|
+
flex: 1 0 100%;
|
|
1460
1693
|
display: flex;
|
|
1694
|
+
align-items: center;
|
|
1461
1695
|
gap: 10px;
|
|
1462
|
-
|
|
1463
|
-
|
|
1696
|
+
min-width: 0;
|
|
1697
|
+
padding: 6px;
|
|
1464
1698
|
border-radius: 10px;
|
|
1465
1699
|
background: var(--rf-bg-subtle);
|
|
1700
|
+
animation: rf-fade 200ms var(--rf-ease);
|
|
1466
1701
|
}
|
|
1467
1702
|
.thumb {
|
|
1468
1703
|
position: relative;
|
|
1469
|
-
flex: 0 0
|
|
1470
|
-
height:
|
|
1704
|
+
flex: 0 0 60px;
|
|
1705
|
+
height: 42px;
|
|
1471
1706
|
overflow: hidden;
|
|
1472
1707
|
border-radius: 6px;
|
|
1473
|
-
border: 1px solid var(--rf-border);
|
|
1474
1708
|
background: var(--rf-bg);
|
|
1709
|
+
box-shadow: var(--rf-shadow-inset);
|
|
1475
1710
|
}
|
|
1476
1711
|
.thumb img { width: 100%; height: 100%; object-fit: cover; object-position: top center; display: block; }
|
|
1477
|
-
.
|
|
1478
|
-
.
|
|
1479
|
-
.
|
|
1480
|
-
.
|
|
1481
|
-
|
|
1482
|
-
.
|
|
1483
|
-
|
|
1484
|
-
align-items: center;
|
|
1485
|
-
gap: 6px;
|
|
1486
|
-
max-width: 100%;
|
|
1487
|
-
padding: 5px 8px;
|
|
1488
|
-
font-size: 12px;
|
|
1489
|
-
border: 1px solid var(--rf-border);
|
|
1490
|
-
border-radius: 8px;
|
|
1491
|
-
background: var(--rf-bg-subtle);
|
|
1492
|
-
color: var(--rf-text-muted);
|
|
1493
|
-
}
|
|
1494
|
-
.chip code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11.5px; color: var(--rf-text); }
|
|
1495
|
-
.chip .truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1712
|
+
.thumb.contain img { object-fit: contain; object-position: center; }
|
|
1713
|
+
.card .meta { display: flex; flex-direction: column; justify-content: center; gap: 1px; min-width: 0; }
|
|
1714
|
+
.card .meta strong { font-size: 12.5px; font-weight: 550; }
|
|
1715
|
+
.card .meta span { font-size: 11.5px; color: var(--rf-text-muted); }
|
|
1716
|
+
.card .actions { display: flex; gap: 2px; margin-left: auto; align-items: center; }
|
|
1717
|
+
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1718
|
+
.card code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px; }
|
|
1496
1719
|
|
|
1497
|
-
.row { display: flex; gap: 8px; align-items: center; }
|
|
1498
1720
|
.ghost-btn {
|
|
1721
|
+
flex: 0 0 auto;
|
|
1499
1722
|
display: inline-flex;
|
|
1500
1723
|
align-items: center;
|
|
1501
1724
|
gap: 6px;
|
|
1502
|
-
height:
|
|
1725
|
+
height: 30px;
|
|
1503
1726
|
padding: 0 10px;
|
|
1504
|
-
font-size:
|
|
1727
|
+
font-size: 12.5px;
|
|
1505
1728
|
font-weight: 500;
|
|
1506
1729
|
color: var(--rf-text-muted);
|
|
1507
|
-
border:
|
|
1508
|
-
|
|
1509
|
-
transition: background
|
|
1730
|
+
border-radius: var(--rf-radius-sm);
|
|
1731
|
+
box-shadow: var(--rf-shadow-inset);
|
|
1732
|
+
transition: background 160ms var(--rf-ease), color 160ms var(--rf-ease);
|
|
1510
1733
|
}
|
|
1511
1734
|
.ghost-btn:hover { background: var(--rf-bg-subtle); color: var(--rf-text); }
|
|
1512
1735
|
|
|
@@ -1516,53 +1739,55 @@ textarea:focus, input:focus { outline: none; border-color: var(--rf-primary); bo
|
|
|
1516
1739
|
justify-content: center;
|
|
1517
1740
|
gap: 8px;
|
|
1518
1741
|
width: 100%;
|
|
1519
|
-
height:
|
|
1742
|
+
height: 32px;
|
|
1743
|
+
margin-top: 2px;
|
|
1744
|
+
font-size: 13px;
|
|
1520
1745
|
font-weight: 550;
|
|
1521
1746
|
color: var(--rf-primary-text);
|
|
1522
1747
|
background: var(--rf-primary);
|
|
1523
|
-
border-radius:
|
|
1524
|
-
transition:
|
|
1748
|
+
border-radius: var(--rf-radius-sm);
|
|
1749
|
+
transition: opacity 160ms var(--rf-ease), transform 160ms var(--rf-ease);
|
|
1525
1750
|
}
|
|
1526
|
-
.submit:hover:not(:disabled) {
|
|
1527
|
-
.submit:disabled {
|
|
1751
|
+
.submit:hover:not(:disabled) { opacity: 0.88; }
|
|
1752
|
+
.submit:active:not(:disabled) { transform: scale(0.99); }
|
|
1753
|
+
.submit:disabled { opacity: 0.35; cursor: not-allowed; }
|
|
1528
1754
|
|
|
1529
|
-
.error { margin: 0; font-size:
|
|
1530
|
-
.hint { margin: 0; font-size: 12px; color: var(--rf-text-muted); }
|
|
1755
|
+
.error { margin: 0; font-size: 12px; color: var(--rf-danger); }
|
|
1531
1756
|
|
|
1532
1757
|
.footer {
|
|
1533
|
-
min-height:
|
|
1758
|
+
min-height: 34px;
|
|
1534
1759
|
padding: 0 14px;
|
|
1535
1760
|
border-top: 1px solid var(--rf-border);
|
|
1536
1761
|
display: flex;
|
|
1537
1762
|
align-items: center;
|
|
1538
1763
|
justify-content: center;
|
|
1539
1764
|
gap: 12px;
|
|
1540
|
-
font-size:
|
|
1765
|
+
font-size: 11px;
|
|
1541
1766
|
color: var(--rf-text-muted);
|
|
1542
1767
|
}
|
|
1543
1768
|
.footer[data-dismissible="true"] { justify-content: space-between; }
|
|
1544
|
-
.footer a { color: inherit; text-decoration: none; font-weight: 550; }
|
|
1769
|
+
.footer a { color: inherit; text-decoration: none; font-weight: 550; transition: color 120ms var(--rf-ease); }
|
|
1545
1770
|
.footer a:hover { color: var(--rf-text); }
|
|
1546
1771
|
.dismiss-btn {
|
|
1547
1772
|
align-self: stretch;
|
|
1548
1773
|
color: inherit;
|
|
1549
|
-
font-size:
|
|
1550
|
-
|
|
1551
|
-
text-underline-offset: 3px;
|
|
1774
|
+
font-size: 11px;
|
|
1775
|
+
transition: color 120ms var(--rf-ease);
|
|
1552
1776
|
}
|
|
1553
1777
|
.dismiss-btn:hover { color: var(--rf-text); }
|
|
1554
1778
|
|
|
1555
|
-
.done { display: flex; flex-direction: column; align-items: center; gap:
|
|
1779
|
+
.done { display: flex; flex-direction: column; align-items: center; gap: 6px; padding: 30px 20px 34px; text-align: center; }
|
|
1556
1780
|
.done svg { color: var(--rf-success); }
|
|
1557
|
-
.done h2 { margin: 0; font-size:
|
|
1558
|
-
.done p { margin: 0; font-size:
|
|
1781
|
+
.done h2 { margin: 0; font-size: 14px; font-weight: 600; }
|
|
1782
|
+
.done p { margin: 0; font-size: 12.5px; color: var(--rf-text-muted); }
|
|
1559
1783
|
|
|
1560
1784
|
.spinner {
|
|
1561
|
-
width:
|
|
1562
|
-
height:
|
|
1785
|
+
width: 14px;
|
|
1786
|
+
height: 14px;
|
|
1563
1787
|
border: 2px solid currentColor;
|
|
1564
1788
|
border-right-color: transparent;
|
|
1565
1789
|
border-radius: 50%;
|
|
1790
|
+
opacity: 0.6;
|
|
1566
1791
|
animation: rf-spin 700ms linear infinite;
|
|
1567
1792
|
}
|
|
1568
1793
|
@keyframes rf-spin { to { transform: rotate(360deg); } }
|
|
@@ -1575,16 +1800,17 @@ textarea:focus, input:focus { outline: none; border-color: var(--rf-primary); bo
|
|
|
1575
1800
|
z-index: 2147483001;
|
|
1576
1801
|
display: flex;
|
|
1577
1802
|
flex-direction: column;
|
|
1578
|
-
background: rgb(
|
|
1579
|
-
backdrop-filter: blur(
|
|
1803
|
+
background: rgb(15 15 15 / 76%);
|
|
1804
|
+
backdrop-filter: blur(3px);
|
|
1805
|
+
animation: rf-fade 180ms var(--rf-ease);
|
|
1580
1806
|
}
|
|
1581
1807
|
|
|
1582
1808
|
.editor { flex: 1; display: flex; align-items: center; justify-content: center; padding: 24px 24px 0; min-height: 0; }
|
|
1583
1809
|
.editor canvas {
|
|
1584
1810
|
max-width: 100%;
|
|
1585
1811
|
max-height: 100%;
|
|
1586
|
-
border-radius:
|
|
1587
|
-
box-shadow: 0
|
|
1812
|
+
border-radius: 10px;
|
|
1813
|
+
box-shadow: 0 20px 60px -16px rgb(0 0 0 / 70%);
|
|
1588
1814
|
cursor: crosshair;
|
|
1589
1815
|
touch-action: none;
|
|
1590
1816
|
}
|
|
@@ -1600,97 +1826,106 @@ textarea:focus, input:focus { outline: none; border-color: var(--rf-primary); bo
|
|
|
1600
1826
|
.toolbar .group {
|
|
1601
1827
|
display: flex;
|
|
1602
1828
|
align-items: center;
|
|
1603
|
-
gap:
|
|
1604
|
-
padding:
|
|
1829
|
+
gap: 2px;
|
|
1830
|
+
padding: 4px;
|
|
1605
1831
|
background: var(--rf-bg);
|
|
1606
|
-
border:
|
|
1607
|
-
border-radius: 12px;
|
|
1832
|
+
border-radius: 10px;
|
|
1608
1833
|
box-shadow: var(--rf-shadow);
|
|
1609
1834
|
}
|
|
1610
1835
|
.tool {
|
|
1611
1836
|
display: inline-flex;
|
|
1612
1837
|
align-items: center;
|
|
1613
1838
|
justify-content: center;
|
|
1614
|
-
width:
|
|
1615
|
-
height:
|
|
1616
|
-
border-radius:
|
|
1839
|
+
width: 30px;
|
|
1840
|
+
height: 30px;
|
|
1841
|
+
border-radius: 7px;
|
|
1617
1842
|
color: var(--rf-text-muted);
|
|
1618
|
-
transition: background
|
|
1843
|
+
transition: background 160ms var(--rf-ease), color 160ms var(--rf-ease);
|
|
1619
1844
|
}
|
|
1620
|
-
.tool:hover { background: var(--rf-bg-
|
|
1621
|
-
.tool[aria-pressed="true"] { background: var(--rf-
|
|
1845
|
+
.tool:hover { background: var(--rf-bg-hover); color: var(--rf-text); }
|
|
1846
|
+
.tool[aria-pressed="true"] { background: var(--rf-bg-hover); color: var(--rf-text); }
|
|
1622
1847
|
.swatch {
|
|
1623
|
-
width:
|
|
1624
|
-
height:
|
|
1848
|
+
width: 20px;
|
|
1849
|
+
height: 20px;
|
|
1625
1850
|
border-radius: 50%;
|
|
1626
1851
|
border: 2px solid transparent;
|
|
1627
|
-
transition: transform
|
|
1852
|
+
transition: transform 200ms var(--rf-ease), border-color 160ms var(--rf-ease);
|
|
1628
1853
|
}
|
|
1629
|
-
.swatch[aria-pressed="true"] { border-color: var(--rf-text); transform: scale(1.
|
|
1854
|
+
.swatch[aria-pressed="true"] { border-color: var(--rf-text); transform: scale(1.15); }
|
|
1630
1855
|
.toolbar .done-btn {
|
|
1631
|
-
height:
|
|
1632
|
-
padding: 0
|
|
1856
|
+
height: 30px;
|
|
1857
|
+
padding: 0 13px;
|
|
1858
|
+
font-size: 13px;
|
|
1633
1859
|
font-weight: 550;
|
|
1634
|
-
border-radius:
|
|
1860
|
+
border-radius: 7px;
|
|
1635
1861
|
color: var(--rf-primary-text);
|
|
1636
1862
|
background: var(--rf-primary);
|
|
1863
|
+
transition: opacity 160ms var(--rf-ease);
|
|
1637
1864
|
}
|
|
1865
|
+
.toolbar .done-btn:hover { opacity: 0.88; }
|
|
1638
1866
|
|
|
1639
|
-
.picker { position: fixed; inset: 0; z-index: 2147483002; cursor: crosshair; }
|
|
1640
1867
|
.picker-box {
|
|
1641
1868
|
position: fixed;
|
|
1642
1869
|
pointer-events: none;
|
|
1643
|
-
border:
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
transition:
|
|
1870
|
+
border-radius: 5px;
|
|
1871
|
+
background: color-mix(in srgb, var(--rf-accent) 10%, transparent);
|
|
1872
|
+
box-shadow: 0 0 0 2px var(--rf-accent), 0 0 0 5px color-mix(in srgb, var(--rf-accent) 18%, transparent);
|
|
1873
|
+
transition: top 130ms var(--rf-ease), left 130ms var(--rf-ease), width 130ms var(--rf-ease), height 130ms var(--rf-ease);
|
|
1647
1874
|
}
|
|
1648
1875
|
.picker-label {
|
|
1649
1876
|
position: fixed;
|
|
1650
1877
|
pointer-events: none;
|
|
1651
|
-
display: flex;
|
|
1878
|
+
display: inline-flex;
|
|
1652
1879
|
align-items: center;
|
|
1653
1880
|
gap: 6px;
|
|
1654
1881
|
max-width: min(420px, 90vw);
|
|
1655
|
-
|
|
1656
|
-
|
|
1882
|
+
height: 24px;
|
|
1883
|
+
padding: 0 8px;
|
|
1884
|
+
font-size: 11.5px;
|
|
1657
1885
|
font-weight: 500;
|
|
1658
|
-
color:
|
|
1659
|
-
background: var(--rf-
|
|
1660
|
-
border-radius:
|
|
1886
|
+
color: #ffffff;
|
|
1887
|
+
background: var(--rf-accent);
|
|
1888
|
+
border-radius: 6px;
|
|
1889
|
+
box-shadow: 0 4px 12px -3px rgb(15 15 15 / 35%);
|
|
1661
1890
|
white-space: nowrap;
|
|
1662
1891
|
overflow: hidden;
|
|
1663
|
-
|
|
1892
|
+
transition: top 130ms var(--rf-ease), left 130ms var(--rf-ease);
|
|
1664
1893
|
}
|
|
1665
|
-
.picker-label
|
|
1894
|
+
.picker-label strong { flex: none; font-weight: 600; }
|
|
1895
|
+
.picker-label span { min-width: 0; overflow: hidden; text-overflow: ellipsis; opacity: 0.75; }
|
|
1666
1896
|
.picker-hint {
|
|
1667
1897
|
position: fixed;
|
|
1668
1898
|
left: 50%;
|
|
1669
1899
|
bottom: 24px;
|
|
1670
1900
|
transform: translateX(-50%);
|
|
1671
|
-
|
|
1672
|
-
|
|
1901
|
+
display: flex;
|
|
1902
|
+
align-items: center;
|
|
1903
|
+
gap: 6px;
|
|
1904
|
+
padding: 7px 12px;
|
|
1905
|
+
font-size: 12.5px;
|
|
1673
1906
|
color: var(--rf-text);
|
|
1674
1907
|
background: var(--rf-bg);
|
|
1675
|
-
border: 1px solid var(--rf-border);
|
|
1676
1908
|
border-radius: 10px;
|
|
1677
1909
|
box-shadow: var(--rf-shadow);
|
|
1910
|
+
animation: rf-fade 240ms var(--rf-ease);
|
|
1678
1911
|
}
|
|
1679
1912
|
.picker-hint kbd {
|
|
1680
1913
|
padding: 1px 5px;
|
|
1681
1914
|
font: inherit;
|
|
1682
|
-
font-size:
|
|
1683
|
-
|
|
1915
|
+
font-size: 11px;
|
|
1916
|
+
color: var(--rf-text-muted);
|
|
1684
1917
|
border-radius: 4px;
|
|
1685
1918
|
background: var(--rf-bg-subtle);
|
|
1919
|
+
box-shadow: var(--rf-shadow-inset);
|
|
1686
1920
|
}
|
|
1921
|
+
|
|
1687
1922
|
@media (pointer: coarse) {
|
|
1688
1923
|
.icon-btn, .tool, .swatch {
|
|
1689
1924
|
width: 44px;
|
|
1690
1925
|
height: 44px;
|
|
1691
1926
|
}
|
|
1692
1927
|
|
|
1693
|
-
.segmented button, .ghost-btn, .submit, .toolbar .done-btn {
|
|
1928
|
+
.launcher, .segmented button, .ghost-btn, .submit, .toolbar .done-btn {
|
|
1694
1929
|
min-height: 44px;
|
|
1695
1930
|
}
|
|
1696
1931
|
|
|
@@ -1705,7 +1940,6 @@ textarea:focus, input:focus { outline: none; border-color: var(--rf-primary); bo
|
|
|
1705
1940
|
justify-content: center;
|
|
1706
1941
|
}
|
|
1707
1942
|
}
|
|
1708
|
-
|
|
1709
1943
|
`;
|
|
1710
1944
|
//#endregion
|
|
1711
1945
|
//#region src/feedback/ui/shadow-portal.tsx
|
|
@@ -1849,20 +2083,20 @@ function buildDescription(params) {
|
|
|
1849
2083
|
const contact = isAnonymous && email ? `\n\n---\nContact: ${email}` : "";
|
|
1850
2084
|
return `[${label}] ${message.trim()}${contact}`;
|
|
1851
2085
|
}
|
|
2086
|
+
async function upload(transport, image) {
|
|
2087
|
+
const { uploadUrl } = await transport.getScreenshotUploadUrl();
|
|
2088
|
+
return await transport.uploadImage(uploadUrl, image);
|
|
2089
|
+
}
|
|
1852
2090
|
async function attachScreenshot(transport, feedbackId, submission) {
|
|
1853
2091
|
const { annotated, annotations, context, screenshot } = submission;
|
|
1854
2092
|
if (!screenshot) return false;
|
|
1855
|
-
const
|
|
1856
|
-
const
|
|
1857
|
-
let annotatedStorageId;
|
|
1858
|
-
if (annotated) {
|
|
1859
|
-
const target = await transport.getScreenshotUploadUrl();
|
|
1860
|
-
annotatedStorageId = await transport.uploadImage(target.uploadUrl, annotated);
|
|
1861
|
-
}
|
|
2093
|
+
const storageId = await upload(transport, screenshot);
|
|
2094
|
+
const annotatedStorageId = annotated ? await upload(transport, annotated) : void 0;
|
|
1862
2095
|
const wireAnnotations = toWireAnnotations(annotations);
|
|
1863
2096
|
await transport.saveScreenshot({
|
|
1864
2097
|
annotatedStorageId,
|
|
1865
2098
|
annotations: wireAnnotations.length > 0 ? wireAnnotations : void 0,
|
|
2099
|
+
captureSource: "widget",
|
|
1866
2100
|
feedbackId,
|
|
1867
2101
|
filename: "screenshot.png",
|
|
1868
2102
|
height: screenshot.height,
|
|
@@ -1874,6 +2108,21 @@ async function attachScreenshot(transport, feedbackId, submission) {
|
|
|
1874
2108
|
});
|
|
1875
2109
|
return true;
|
|
1876
2110
|
}
|
|
2111
|
+
async function attachElement(transport, feedbackId, submission) {
|
|
2112
|
+
const { context, element } = submission;
|
|
2113
|
+
if (!element) return;
|
|
2114
|
+
await transport.saveScreenshot({
|
|
2115
|
+
captureSource: "element",
|
|
2116
|
+
feedbackId,
|
|
2117
|
+
filename: "element.png",
|
|
2118
|
+
height: element.height,
|
|
2119
|
+
mimeType: element.mimeType,
|
|
2120
|
+
pageUrl: context.url,
|
|
2121
|
+
size: element.blob.size,
|
|
2122
|
+
storageId: await upload(transport, element),
|
|
2123
|
+
width: element.width
|
|
2124
|
+
});
|
|
2125
|
+
}
|
|
1877
2126
|
/**
|
|
1878
2127
|
* Creates the feedback first, then attaches the capture. A failed upload is
|
|
1879
2128
|
* reported back but never discards what the user wrote.
|
|
@@ -1886,12 +2135,8 @@ async function submitWidgetFeedback(transport, submission) {
|
|
|
1886
2135
|
description: buildDescription(submission),
|
|
1887
2136
|
title
|
|
1888
2137
|
});
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
screenshotSaved = await attachScreenshot(transport, feedbackId, submission);
|
|
1892
|
-
} catch {
|
|
1893
|
-
screenshotSaved = false;
|
|
1894
|
-
}
|
|
2138
|
+
const screenshotSaved = await attachScreenshot(transport, feedbackId, submission).catch(() => false);
|
|
2139
|
+
await attachElement(transport, feedbackId, submission).catch(() => void 0);
|
|
1895
2140
|
return {
|
|
1896
2141
|
feedbackId,
|
|
1897
2142
|
screenshotSaved
|
|
@@ -1921,6 +2166,7 @@ function createFeedbackTransport(client) {
|
|
|
1921
2166
|
//#endregion
|
|
1922
2167
|
//#region src/feedback/ui/use-widget-state.ts
|
|
1923
2168
|
const SUCCESS_CLOSE_DELAY = 2400;
|
|
2169
|
+
const ELEMENT_CAPTURE_MAX_WIDTH = 1200;
|
|
1924
2170
|
const IS_APPLE = /Mac|iPhone|iPad/;
|
|
1925
2171
|
const MILLISECONDS_PER_DAY = 864e5;
|
|
1926
2172
|
const DISMISSAL_STORAGE_PREFIX = "reflet-feedback-dismissed";
|
|
@@ -1975,6 +2221,22 @@ function highlightFor(selection, capture) {
|
|
|
1975
2221
|
tool: "rectangle"
|
|
1976
2222
|
};
|
|
1977
2223
|
}
|
|
2224
|
+
/**
|
|
2225
|
+
* A capture whose object URL is revoked the moment it is replaced or dropped —
|
|
2226
|
+
* holding one without this leaks the blob for the lifetime of the page.
|
|
2227
|
+
*/
|
|
2228
|
+
function useOwnedCapture() {
|
|
2229
|
+
const [image, setImage] = useState(null);
|
|
2230
|
+
const ref = useRef(null);
|
|
2231
|
+
useEffect(() => {
|
|
2232
|
+
ref.current = image;
|
|
2233
|
+
}, [image]);
|
|
2234
|
+
useEffect(() => () => releaseCapture(ref.current), []);
|
|
2235
|
+
return [image, useCallback((next) => {
|
|
2236
|
+
releaseCapture(ref.current);
|
|
2237
|
+
setImage(next);
|
|
2238
|
+
}, [])];
|
|
2239
|
+
}
|
|
1978
2240
|
function useWidgetState(props) {
|
|
1979
2241
|
const context = useContext(RefletContext);
|
|
1980
2242
|
const publicKey = props.publicKey ?? context?.publicKey;
|
|
@@ -1990,7 +2252,8 @@ function useWidgetState(props) {
|
|
|
1990
2252
|
const [message, setMessage] = useState("");
|
|
1991
2253
|
const [email, setEmail] = useState("");
|
|
1992
2254
|
const [honeypot, setHoneypot] = useState("");
|
|
1993
|
-
const [capture,
|
|
2255
|
+
const [capture, replaceCapture] = useOwnedCapture();
|
|
2256
|
+
const [elementCapture, replaceElementCapture] = useOwnedCapture();
|
|
1994
2257
|
const [annotations, setAnnotations] = useState([]);
|
|
1995
2258
|
const [selection, setSelection] = useState(null);
|
|
1996
2259
|
const [isCapturing, setIsCapturing] = useState(false);
|
|
@@ -1998,6 +2261,7 @@ function useWidgetState(props) {
|
|
|
1998
2261
|
const [error, setError] = useState(null);
|
|
1999
2262
|
const [dismissedUntil, setDismissedUntil] = useState(() => readDismissedUntil(dismissalKey));
|
|
2000
2263
|
const captureRef = useRef(null);
|
|
2264
|
+
const pickRef = useRef(0);
|
|
2001
2265
|
const consoleRef = useRef(null);
|
|
2002
2266
|
useEffect(() => {
|
|
2003
2267
|
captureRef.current = capture;
|
|
@@ -2014,13 +2278,6 @@ function useWidgetState(props) {
|
|
|
2014
2278
|
recorder.stop();
|
|
2015
2279
|
};
|
|
2016
2280
|
}, [props.captureConsole]);
|
|
2017
|
-
useEffect(() => () => {
|
|
2018
|
-
releaseCapture(captureRef.current);
|
|
2019
|
-
}, []);
|
|
2020
|
-
const replaceCapture = useCallback((next) => {
|
|
2021
|
-
releaseCapture(captureRef.current);
|
|
2022
|
-
setCapture(next);
|
|
2023
|
-
}, []);
|
|
2024
2281
|
const takeCapture = useCallback((element) => {
|
|
2025
2282
|
setIsCapturing(true);
|
|
2026
2283
|
captureViewport({ element }).catch(() => null).then((image) => {
|
|
@@ -2029,8 +2286,9 @@ function useWidgetState(props) {
|
|
|
2029
2286
|
});
|
|
2030
2287
|
}, [replaceCapture]);
|
|
2031
2288
|
const reset = useCallback(() => {
|
|
2032
|
-
|
|
2033
|
-
|
|
2289
|
+
pickRef.current++;
|
|
2290
|
+
replaceCapture(null);
|
|
2291
|
+
replaceElementCapture(null);
|
|
2034
2292
|
setAnnotations([]);
|
|
2035
2293
|
setSelection(null);
|
|
2036
2294
|
setMessage("");
|
|
@@ -2039,7 +2297,11 @@ function useWidgetState(props) {
|
|
|
2039
2297
|
setError(null);
|
|
2040
2298
|
setStep("compose");
|
|
2041
2299
|
setCategory(props.defaultCategory ?? "bug");
|
|
2042
|
-
}, [
|
|
2300
|
+
}, [
|
|
2301
|
+
props.defaultCategory,
|
|
2302
|
+
replaceCapture,
|
|
2303
|
+
replaceElementCapture
|
|
2304
|
+
]);
|
|
2043
2305
|
const close = useCallback(() => {
|
|
2044
2306
|
setIsOpen(false);
|
|
2045
2307
|
reset();
|
|
@@ -2075,11 +2337,21 @@ function useWidgetState(props) {
|
|
|
2075
2337
|
setStep("compose");
|
|
2076
2338
|
const current = captureRef.current;
|
|
2077
2339
|
if (current) setAnnotations((previous) => [...previous.filter((item) => !item.id.startsWith("selection-")), highlightFor(picked, current)]);
|
|
2078
|
-
|
|
2340
|
+
const pick = ++pickRef.current;
|
|
2341
|
+
captureViewport({
|
|
2342
|
+
element,
|
|
2343
|
+
maxWidth: ELEMENT_CAPTURE_MAX_WIDTH
|
|
2344
|
+
}).catch(() => null).then((image) => {
|
|
2345
|
+
if (pickRef.current === pick) replaceElementCapture(image);
|
|
2346
|
+
else releaseCapture(image);
|
|
2347
|
+
});
|
|
2348
|
+
}, [replaceElementCapture]);
|
|
2079
2349
|
const clearSelection = useCallback(() => {
|
|
2350
|
+
pickRef.current++;
|
|
2351
|
+
replaceElementCapture(null);
|
|
2080
2352
|
setSelection(null);
|
|
2081
2353
|
setAnnotations((previous) => previous.filter((item) => !item.id.startsWith("selection-")));
|
|
2082
|
-
}, []);
|
|
2354
|
+
}, [replaceElementCapture]);
|
|
2083
2355
|
const removeCapture = useCallback(() => {
|
|
2084
2356
|
replaceCapture(null);
|
|
2085
2357
|
setAnnotations([]);
|
|
@@ -2119,6 +2391,7 @@ function useWidgetState(props) {
|
|
|
2119
2391
|
metadata: props.metadata,
|
|
2120
2392
|
selection: selection ?? void 0
|
|
2121
2393
|
},
|
|
2394
|
+
element: elementCapture,
|
|
2122
2395
|
email,
|
|
2123
2396
|
isAnonymous,
|
|
2124
2397
|
message,
|
|
@@ -2139,6 +2412,7 @@ function useWidgetState(props) {
|
|
|
2139
2412
|
category,
|
|
2140
2413
|
client,
|
|
2141
2414
|
close,
|
|
2415
|
+
elementCapture,
|
|
2142
2416
|
email,
|
|
2143
2417
|
honeypot,
|
|
2144
2418
|
isAnonymous,
|
|
@@ -2156,6 +2430,7 @@ function useWidgetState(props) {
|
|
|
2156
2430
|
close,
|
|
2157
2431
|
dismiss,
|
|
2158
2432
|
dismissForDays,
|
|
2433
|
+
elementCapture,
|
|
2159
2434
|
email,
|
|
2160
2435
|
error,
|
|
2161
2436
|
honeypot,
|
|
@@ -2238,22 +2513,18 @@ function RefletFeedback(props) {
|
|
|
2238
2513
|
primaryColor: props.primaryColor,
|
|
2239
2514
|
theme,
|
|
2240
2515
|
children: [
|
|
2241
|
-
/* @__PURE__ */
|
|
2516
|
+
/* @__PURE__ */ jsxs("div", {
|
|
2242
2517
|
className: "root",
|
|
2243
2518
|
"data-position": position,
|
|
2244
|
-
children: /* @__PURE__ */
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
label: labels.trigger,
|
|
2254
|
-
onClick: isOpen ? close : open
|
|
2255
|
-
})]
|
|
2256
|
-
})
|
|
2519
|
+
children: [showPanel && /* @__PURE__ */ jsx(FeedbackPanel, {
|
|
2520
|
+
categories,
|
|
2521
|
+
labels,
|
|
2522
|
+
state
|
|
2523
|
+
}), /* @__PURE__ */ jsx(Launcher, {
|
|
2524
|
+
isOpen,
|
|
2525
|
+
label: labels.trigger,
|
|
2526
|
+
onClick: isOpen ? close : open
|
|
2527
|
+
})]
|
|
2257
2528
|
}),
|
|
2258
2529
|
isOpen && state.step === "annotate" && state.capture && /* @__PURE__ */ jsx(Annotator, {
|
|
2259
2530
|
annotations: state.annotations,
|