reflet-sdk 0.6.2 → 0.6.4

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.
@@ -172,6 +172,14 @@ function buildSnapdomOptions(options) {
172
172
  reconcile: true
173
173
  };
174
174
  }
175
+ /** snapdom's `clip: "viewport"` box, same fallbacks: the layout viewport minus classic (Windows, Linux) scrollbars. */
176
+ function capturedViewportSize() {
177
+ const root = document.documentElement;
178
+ return {
179
+ height: root.clientHeight || window.innerHeight,
180
+ width: root.clientWidth || window.innerWidth
181
+ };
182
+ }
175
183
  function fitWithin(size, maxWidth) {
176
184
  if (size.width <= maxWidth) return {
177
185
  height: size.height,
@@ -809,7 +817,7 @@ function collectPageContext(options) {
809
817
  }
810
818
  //#endregion
811
819
  //#region src/feedback/types.ts
812
- const SDK_VERSION = "0.6.2";
820
+ const SDK_VERSION = "0.6.4";
813
821
  const DEFAULT_WIDGET_LABELS = {
814
822
  annotateHint: "Draw on the screenshot to point at the problem.",
815
823
  attachElement: "Attach this element",
@@ -835,6 +843,7 @@ const DEFAULT_WIDGET_LABELS = {
835
843
  minimize: "Minimize feedback",
836
844
  moreOptions: "More options",
837
845
  moveFeedback: "Move feedback",
846
+ openScreenshot: "Open screenshot",
838
847
  pickAnother: "Pick another",
839
848
  pickElement: "Point at an element",
840
849
  pickElementHint: "Pick the element you are talking about",
@@ -1038,6 +1047,46 @@ function BlurIcon() {
1038
1047
  }), /* @__PURE__ */ jsx("path", { d: "M8 8h8v8H8z" })] });
1039
1048
  }
1040
1049
  //#endregion
1050
+ //#region src/feedback/ui/keyboard.ts
1051
+ const APPLE_PLATFORM = /Mac|iPhone|iPad/;
1052
+ const LATIN_LETTER_OR_DIGIT = /^[a-z0-9]$/i;
1053
+ const LETTER = /^[a-z]$/;
1054
+ const DIGIT = /^\d$/;
1055
+ const IME_PROCESSING_KEY_CODE = 229;
1056
+ function isApplePlatform(platform = typeof navigator === "undefined" ? "" : navigator.platform) {
1057
+ return APPLE_PLATFORM.test(platform);
1058
+ }
1059
+ function physicalCodeFor(key) {
1060
+ if (LETTER.test(key)) return `Key${key.toUpperCase()}`;
1061
+ return DIGIT.test(key) ? `Digit${key}` : null;
1062
+ }
1063
+ /**
1064
+ * ⌥F types "ƒ" on macOS and Ctrl+C types "с" on a Cyrillic layout: when the
1065
+ * layout produced no Latin character, the physical key is what was meant.
1066
+ */
1067
+ function pressesKey(event, key) {
1068
+ if (event.key.toLowerCase() === key) return true;
1069
+ return !LATIN_LETTER_OR_DIGIT.test(event.key) && event.code !== "" && event.code === physicalCodeFor(key);
1070
+ }
1071
+ function matchesHotkey(event, hotkey, platform) {
1072
+ const parts = hotkey.toLowerCase().split("+").map((part) => part.trim());
1073
+ const key = parts.at(-1);
1074
+ if (!(key && pressesKey(event, key))) return false;
1075
+ const isApple = isApplePlatform(platform);
1076
+ const wantsMod = parts.includes("mod");
1077
+ const expected = {
1078
+ alt: parts.includes("alt") || parts.includes("option"),
1079
+ ctrl: parts.includes("ctrl") || wantsMod && !isApple,
1080
+ meta: parts.includes("meta") || parts.includes("cmd") || wantsMod && isApple,
1081
+ shift: parts.includes("shift")
1082
+ };
1083
+ return event.altKey === expected.alt && event.ctrlKey === expected.ctrl && event.metaKey === expected.meta && event.shiftKey === expected.shift;
1084
+ }
1085
+ /** Enter that sends, not the Enter that confirms an IME composition (Japanese, Chinese, Korean input). */
1086
+ function isSubmitEnter(event) {
1087
+ return event.key === "Enter" && !event.shiftKey && !event.isComposing && event.keyCode !== IME_PROCESSING_KEY_CODE;
1088
+ }
1089
+ //#endregion
1041
1090
  //#region src/feedback/ui/widget-events.ts
1042
1091
  const REPLAYED_EVENTS = [
1043
1092
  "keydown",
@@ -1510,104 +1559,6 @@ function useFloatingPosition(corner = "bottom-right", isPanelOpen = false, { anc
1510
1559
  };
1511
1560
  }
1512
1561
  //#endregion
1513
- //#region src/feedback/core/submission/upload-screenshots.ts
1514
- async function uploadImage(transport, image) {
1515
- const { uploadUrl } = await transport.getScreenshotUploadUrl();
1516
- return transport.uploadImage(uploadUrl, image);
1517
- }
1518
- async function prepareScreenshot(transport, screenshot, index) {
1519
- const { image, annotations, context } = screenshot;
1520
- const annotated = annotations.length > 0 ? await renderAnnotatedImage(image, annotations) : null;
1521
- if (annotations.length > 0 && !annotated) throw new Error("Could not render your annotations. Please try again.");
1522
- try {
1523
- const storageId = await uploadImage(transport, image);
1524
- return {
1525
- annotatedStorageId: annotated ? await uploadImage(transport, annotated) : void 0,
1526
- annotations: annotations.length > 0 ? toWireAnnotations(annotations) : void 0,
1527
- captureSource: "widget",
1528
- filename: `screenshot-${index + 1}.png`,
1529
- height: image.height,
1530
- mimeType: image.mimeType,
1531
- pageUrl: context.url,
1532
- size: image.blob.size,
1533
- storageId,
1534
- width: image.width
1535
- };
1536
- } finally {
1537
- releaseCapture(annotated);
1538
- }
1539
- }
1540
- async function uploadScreenshots(transport, submission) {
1541
- return (await Promise.allSettled(submission.screenshots.map((screenshot, index) => prepareScreenshot(transport, screenshot, index)))).map((result) => {
1542
- if (result.status === "rejected") throw result.reason;
1543
- return result.value;
1544
- });
1545
- }
1546
- //#endregion
1547
- //#region src/feedback/core/submit.ts
1548
- const MAX_TITLE_LENGTH = 100;
1549
- const NEWLINE = /\r?\n/;
1550
- function deriveTitle(message) {
1551
- const firstLine = message.split(NEWLINE).map((line) => line.trim()).find(Boolean) ?? "";
1552
- if (firstLine.length <= MAX_TITLE_LENGTH) return firstLine;
1553
- const clipped = firstLine.slice(0, 99);
1554
- const lastSpace = clipped.lastIndexOf(" ");
1555
- return `${(lastSpace > 0 ? clipped.slice(0, lastSpace) : clipped).trimEnd()}…`;
1556
- }
1557
- function buildDescription(params) {
1558
- const { email, isAnonymous, message } = params;
1559
- const contact = isAnonymous && email ? `\n\n---\nContact: ${email}` : "";
1560
- return `${message.trim()}${contact}`;
1561
- }
1562
- async function attachPreparedScreenshots(transport, result) {
1563
- const results = await Promise.allSettled(result.pendingScreenshots.map((screenshot) => transport.saveScreenshot({
1564
- ...screenshot,
1565
- feedbackId: result.feedbackId
1566
- })));
1567
- const pendingScreenshots = result.pendingScreenshots.filter((_, index) => results[index]?.status === "rejected");
1568
- return {
1569
- feedbackId: result.feedbackId,
1570
- pendingScreenshots
1571
- };
1572
- }
1573
- async function createReportWithScreenshots(transport, submission, pendingScreenshots) {
1574
- const { feedbackId } = await transport.create({
1575
- context: submission.context,
1576
- description: buildDescription(submission),
1577
- title: deriveTitle(submission.message)
1578
- });
1579
- return attachPreparedScreenshots(transport, {
1580
- feedbackId,
1581
- pendingScreenshots
1582
- });
1583
- }
1584
- async function submitWidgetFeedback(transport, submission) {
1585
- if (!deriveTitle(submission.message)) throw new Error("Please describe your feedback before sending it.");
1586
- return createReportWithScreenshots(transport, submission, await uploadScreenshots(transport, submission));
1587
- }
1588
- function isStorageResponse(value) {
1589
- return typeof value === "object" && value !== null && typeof Reflect.get(value, "storageId") === "string";
1590
- }
1591
- async function uploadImageBlob(uploadUrl, image) {
1592
- const response = await fetch(uploadUrl, {
1593
- body: image.blob,
1594
- headers: { "Content-Type": image.mimeType },
1595
- method: "POST"
1596
- });
1597
- if (!response.ok) throw new Error(`Screenshot upload failed (${response.status})`);
1598
- const payload = await response.json();
1599
- if (!isStorageResponse(payload)) throw new Error("Screenshot upload returned no storage id");
1600
- return payload.storageId;
1601
- }
1602
- function createFeedbackTransport(client) {
1603
- return {
1604
- create: (params) => client.create(params),
1605
- getScreenshotUploadUrl: () => client.getScreenshotUploadUrl(),
1606
- saveScreenshot: (params) => client.saveScreenshot(params),
1607
- uploadImage: uploadImageBlob
1608
- };
1609
- }
1610
- //#endregion
1611
1562
  //#region src/feedback/ui/floating/element-walk.ts
1612
1563
  const ARROW_SIBLING = {
1613
1564
  ArrowLeft: "previousElementSibling",
@@ -1665,7 +1616,6 @@ function stepAim(from, key, backwards) {
1665
1616
  //#region src/feedback/ui/picker.tsx
1666
1617
  const LABEL_HEIGHT = 24;
1667
1618
  const LABEL_GAP = 6;
1668
- const NOTE_WIDTH = 300;
1669
1619
  const NOTE_GAP = 10;
1670
1620
  const EDGE_GAP = 12;
1671
1621
  function elementUnder(x, y) {
@@ -1682,35 +1632,68 @@ function describeTarget(element) {
1682
1632
  region: describeRegion(element)
1683
1633
  };
1684
1634
  }
1685
- /**
1686
- * Pins the note card to the element: below it, flipped above when the visible
1687
- * viewport — what is left of the screen once a mobile keyboard is up — has no
1688
- * room there. Measures against the part of the element actually on screen, so
1689
- * picking a full-height section still leaves the card in view.
1690
- */
1691
- function noteStyle(rect, view) {
1692
- const left = Math.min(Math.max(view.left + EDGE_GAP, rect.left), Math.max(view.left + EDGE_GAP, view.left + view.width - NOTE_WIDTH - EDGE_GAP));
1693
- const visibleTop = Math.max(rect.top, view.top + EDGE_GAP);
1694
- const visibleBottom = Math.min(rect.bottom, view.top + view.height - EDGE_GAP);
1695
- if (view.top + view.height - visibleBottom > visibleTop - view.top) return {
1635
+ function noteCardPosition(rect, view, card) {
1636
+ const viewLeft = view.left + EDGE_GAP;
1637
+ const viewRight = view.left + view.width - EDGE_GAP;
1638
+ const viewTop = view.top + EDGE_GAP;
1639
+ const viewBottom = view.top + view.height - EDGE_GAP;
1640
+ const fitLeft = (left) => Math.max(viewLeft, Math.min(left, viewRight - card.width));
1641
+ const fitTop = (top) => Math.max(viewTop, Math.min(top, viewBottom - card.height));
1642
+ const below = rect.bottom + NOTE_GAP;
1643
+ if (below >= viewTop && below + card.height <= viewBottom) return {
1644
+ left: fitLeft(rect.left),
1645
+ top: below
1646
+ };
1647
+ const above = rect.top - NOTE_GAP - card.height;
1648
+ if (above >= viewTop && rect.top <= viewBottom) return {
1649
+ left: fitLeft(rect.left),
1650
+ top: above
1651
+ };
1652
+ const besideTop = fitTop(rect.top);
1653
+ const right = rect.right + NOTE_GAP;
1654
+ if (right + card.width <= viewRight) return {
1655
+ left: right,
1656
+ top: besideTop
1657
+ };
1658
+ const left = rect.left - NOTE_GAP - card.width;
1659
+ if (left >= viewLeft) return {
1696
1660
  left,
1697
- top: visibleBottom + NOTE_GAP
1661
+ top: besideTop
1698
1662
  };
1699
1663
  return {
1700
- bottom: window.innerHeight - visibleTop + NOTE_GAP,
1701
- left
1664
+ left: fitLeft(rect.left),
1665
+ top: fitTop(Math.min(rect.bottom, viewBottom) - card.height)
1702
1666
  };
1703
1667
  }
1704
- function ElementPicker({ inspect, labels, onCancel, onPick }) {
1668
+ function labelTop(rect, view) {
1669
+ const above = rect.top - LABEL_HEIGHT - LABEL_GAP;
1670
+ if (above >= view.top) return above;
1671
+ const below = rect.bottom + LABEL_GAP;
1672
+ if (below + LABEL_HEIGHT <= view.top + view.height) return below;
1673
+ return Math.max(rect.top, view.top) + LABEL_GAP;
1674
+ }
1675
+ function ElementPicker({ copy, inspect, labels, onCancel, onPick }) {
1705
1676
  const [target, setTarget] = useState(null);
1706
1677
  const [pinned, setPinned] = useState(null);
1707
1678
  const [note, setNote] = useState("");
1679
+ const [noteSize, setNoteSize] = useState({
1680
+ height: 0,
1681
+ width: 0
1682
+ });
1708
1683
  const pickerRef = useRef(null);
1684
+ const noteCardRef = useRef(null);
1709
1685
  const noteRef = useRef(null);
1710
1686
  const pinnedRef = useRef(false);
1711
1687
  const [view, setView] = useState(visibleViewport);
1712
1688
  const aimRef = useRef(null);
1713
1689
  useEffect(() => onViewportChange(() => setView(visibleViewport())), []);
1690
+ useLayoutEffect(() => {
1691
+ const card = noteCardRef.current;
1692
+ if (pinned && card) setNoteSize({
1693
+ height: card.offsetHeight,
1694
+ width: card.offsetWidth
1695
+ });
1696
+ }, [pinned]);
1714
1697
  useEffect(() => {
1715
1698
  pinnedRef.current = pinned !== null;
1716
1699
  if (pinned) noteRef.current?.focus({ preventScroll: true });
@@ -1748,7 +1731,8 @@ function ElementPicker({ inspect, labels, onCancel, onPick }) {
1748
1731
  const onPointerUp = (event) => {
1749
1732
  if (startsInWidget(event)) return;
1750
1733
  swallow(event);
1751
- if (pinnedRef.current) return;
1734
+ const isPrimaryPress = event.button === 0;
1735
+ if (pinnedRef.current || !isPrimaryPress) return;
1752
1736
  const element = elementUnder(event.clientX, event.clientY);
1753
1737
  if (element) pin(element, event.shiftKey);
1754
1738
  };
@@ -1770,13 +1754,21 @@ function ElementPicker({ inspect, labels, onCancel, onPick }) {
1770
1754
  }
1771
1755
  onCancel();
1772
1756
  };
1757
+ const copyAimed = (event) => {
1758
+ const aimed = aimRef.current;
1759
+ if (!(copy && aimed && matchesHotkey(event, "mod+c"))) return false;
1760
+ swallow(event);
1761
+ copy.onCopy(aimed, "");
1762
+ return true;
1763
+ };
1773
1764
  const onKeyDown = (event) => {
1765
+ if (event.isComposing) return;
1774
1766
  if (event.key === "Escape") {
1775
1767
  swallow(event);
1776
1768
  onEscape();
1777
1769
  return;
1778
1770
  }
1779
- if (pinnedRef.current || startsInWidget(event)) return;
1771
+ if (pinnedRef.current || startsInWidget(event) || copyAimed(event)) return;
1780
1772
  if (event.key === "Enter") {
1781
1773
  swallow(event);
1782
1774
  if (aimRef.current) pin(aimRef.current, event.shiftKey);
@@ -1802,6 +1794,7 @@ function ElementPicker({ inspect, labels, onCancel, onPick }) {
1802
1794
  window.addEventListener("mousedown", swallow, true);
1803
1795
  window.addEventListener("mouseup", swallow, true);
1804
1796
  window.addEventListener("click", swallow, true);
1797
+ window.addEventListener("contextmenu", swallow, true);
1805
1798
  const stopListeningToKeys = listenToKeydown(pickerRef.current, onKeyDown, true);
1806
1799
  window.addEventListener("scroll", onScroll, {
1807
1800
  capture: true,
@@ -1816,14 +1809,19 @@ function ElementPicker({ inspect, labels, onCancel, onPick }) {
1816
1809
  window.removeEventListener("mousedown", swallow, true);
1817
1810
  window.removeEventListener("mouseup", swallow, true);
1818
1811
  window.removeEventListener("click", swallow, true);
1812
+ window.removeEventListener("contextmenu", swallow, true);
1819
1813
  stopListeningToKeys();
1820
1814
  window.removeEventListener("scroll", onScroll, true);
1821
1815
  document.body.style.cursor = previousCursor;
1822
1816
  };
1823
- }, [inspect, onCancel]);
1817
+ }, [
1818
+ copy,
1819
+ inspect,
1820
+ onCancel
1821
+ ]);
1824
1822
  const rect = (pinned ?? target)?.rect;
1825
- const labelAbove = rect ? rect.top > 30 : true;
1826
1823
  return /* @__PURE__ */ jsxs("div", {
1824
+ className: "picker",
1827
1825
  ref: pickerRef,
1828
1826
  children: [
1829
1827
  rect && /* @__PURE__ */ jsx("div", {
@@ -1837,7 +1835,7 @@ function ElementPicker({ inspect, labels, onCancel, onPick }) {
1837
1835
  }),
1838
1836
  rect && target && !pinned && /* @__PURE__ */ jsxs("div", {
1839
1837
  className: "picker-label",
1840
- style: { translate: `${Math.max(4, rect.left)}px ${labelAbove ? rect.top - LABEL_HEIGHT - LABEL_GAP : rect.bottom + LABEL_GAP}px` },
1838
+ style: { translate: `${Math.max(4, rect.left)}px ${labelTop(rect, view)}px` },
1841
1839
  children: [/* @__PURE__ */ jsx("strong", { children: target.componentName ? `<${target.componentName}>` : target.label }), target.region && /* @__PURE__ */ jsx("span", { children: target.region })]
1842
1840
  }),
1843
1841
  pinned ? /* @__PURE__ */ jsxs("form", {
@@ -1846,7 +1844,8 @@ function ElementPicker({ inspect, labels, onCancel, onPick }) {
1846
1844
  event.preventDefault();
1847
1845
  onPick(pinned.element, note.trim());
1848
1846
  },
1849
- style: noteStyle(pinned.rect, view),
1847
+ ref: noteCardRef,
1848
+ style: noteCardPosition(pinned.rect, view, noteSize),
1850
1849
  children: [
1851
1850
  /* @__PURE__ */ jsxs("p", {
1852
1851
  className: "picker-note-target",
@@ -1857,7 +1856,7 @@ function ElementPicker({ inspect, labels, onCancel, onPick }) {
1857
1856
  maxLength: MAX_SELECTION_COMMENT_LENGTH,
1858
1857
  onChange: (event) => setNote(event.target.value),
1859
1858
  onKeyDown: (event) => {
1860
- if (event.key === "Enter" && !event.shiftKey) {
1859
+ if (isSubmitEnter(event.nativeEvent)) {
1861
1860
  event.preventDefault();
1862
1861
  onPick(pinned.element, note.trim());
1863
1862
  }
@@ -1885,6 +1884,12 @@ function ElementPicker({ inspect, labels, onCancel, onPick }) {
1885
1884
  type: "button",
1886
1885
  children: inspect.label
1887
1886
  }),
1887
+ copy && /* @__PURE__ */ jsx("button", {
1888
+ className: "picker-cancel",
1889
+ onClick: () => copy.onCopy(pinned.element, note.trim()),
1890
+ type: "button",
1891
+ children: copy.label
1892
+ }),
1888
1893
  /* @__PURE__ */ jsx("button", {
1889
1894
  "aria-label": labels.attachElement,
1890
1895
  className: "submit",
@@ -1905,6 +1910,7 @@ function ElementPicker({ inspect, labels, onCancel, onPick }) {
1905
1910
  /* @__PURE__ */ jsx("kbd", { children: "Tab" }),
1906
1911
  /* @__PURE__ */ jsx("kbd", { children: "Enter" }),
1907
1912
  inspect && /* @__PURE__ */ jsx("kbd", { children: "⇧ Click" }),
1913
+ copy && /* @__PURE__ */ jsx("kbd", { children: isApplePlatform() ? "⌘C" : "Ctrl C" }),
1908
1914
  /* @__PURE__ */ jsx("kbd", { children: "Esc" })
1909
1915
  ]
1910
1916
  }), /* @__PURE__ */ jsx("button", {
@@ -1918,6 +1924,104 @@ function ElementPicker({ inspect, labels, onCancel, onPick }) {
1918
1924
  });
1919
1925
  }
1920
1926
  //#endregion
1921
- export { TargetIcon as A, buildElementSelection as B, GripIcon as C, SpotlightIcon as D, PencilIcon as E, normalizeRect as F, WIDGET_MARKER as G, getFiberFromNode as H, DEFAULT_WIDGET_LABELS as I, releaseCapture as J, captureViewport as K, SDK_VERSION as L, TrashIcon as M, UndoIcon as N, SquareIcon as O, isDegenerate as P, collectPageContext as R, CloseIcon as S, MoreIcon as T, resolveComponentStack as U, describeElement as V, shortenSourcePath as W, BlurIcon as _, deriveTitle as a, CheckIcon as b, useFloatingPosition as c, SelectionOutline as d, drawAnnotations as f, ArrowIcon as g, stopClicksAtHost as h, createFeedbackTransport as i, TextIcon as j, Svg as k, onViewportChange as l, listenToKeydown as m, isWidgetOwned as n, submitWidgetFeedback as o, listenOnPageAndWidget as p, capturedFromCanvas as q, attachPreparedScreenshots as r, uploadImageBlob as s, ElementPicker as t, visibleViewport as u, CameraIcon as v, HighlightIcon as w, ChevronDownIcon as x, ChatIcon as y, MAX_SELECTION_COMMENT_LENGTH as z };
1927
+ //#region src/feedback/core/submission/upload-screenshots.ts
1928
+ async function uploadImage(transport, image) {
1929
+ const { uploadUrl } = await transport.getScreenshotUploadUrl();
1930
+ return transport.uploadImage(uploadUrl, image);
1931
+ }
1932
+ async function prepareScreenshot(transport, screenshot, index) {
1933
+ const { image, annotations, context } = screenshot;
1934
+ const annotated = annotations.length > 0 ? await renderAnnotatedImage(image, annotations) : null;
1935
+ if (annotations.length > 0 && !annotated) throw new Error("Could not render your annotations. Please try again.");
1936
+ try {
1937
+ const storageId = await uploadImage(transport, image);
1938
+ return {
1939
+ annotatedStorageId: annotated ? await uploadImage(transport, annotated) : void 0,
1940
+ annotations: annotations.length > 0 ? toWireAnnotations(annotations) : void 0,
1941
+ captureSource: "widget",
1942
+ filename: `screenshot-${index + 1}.png`,
1943
+ height: image.height,
1944
+ mimeType: image.mimeType,
1945
+ pageUrl: context.url,
1946
+ size: image.blob.size,
1947
+ storageId,
1948
+ width: image.width
1949
+ };
1950
+ } finally {
1951
+ releaseCapture(annotated);
1952
+ }
1953
+ }
1954
+ async function uploadScreenshots(transport, submission) {
1955
+ return (await Promise.allSettled(submission.screenshots.map((screenshot, index) => prepareScreenshot(transport, screenshot, index)))).map((result) => {
1956
+ if (result.status === "rejected") throw result.reason;
1957
+ return result.value;
1958
+ });
1959
+ }
1960
+ //#endregion
1961
+ //#region src/feedback/core/submit.ts
1962
+ const MAX_TITLE_LENGTH = 100;
1963
+ const NEWLINE = /\r?\n/;
1964
+ function deriveTitle(message) {
1965
+ const firstLine = message.split(NEWLINE).map((line) => line.trim()).find(Boolean) ?? "";
1966
+ if (firstLine.length <= MAX_TITLE_LENGTH) return firstLine;
1967
+ const clipped = firstLine.slice(0, 99);
1968
+ const lastSpace = clipped.lastIndexOf(" ");
1969
+ return `${(lastSpace > 0 ? clipped.slice(0, lastSpace) : clipped).trimEnd()}…`;
1970
+ }
1971
+ function buildDescription(params) {
1972
+ const { email, isAnonymous, message } = params;
1973
+ const contact = isAnonymous && email ? `\n\n---\nContact: ${email}` : "";
1974
+ return `${message.trim()}${contact}`;
1975
+ }
1976
+ async function attachPreparedScreenshots(transport, result) {
1977
+ const results = await Promise.allSettled(result.pendingScreenshots.map((screenshot) => transport.saveScreenshot({
1978
+ ...screenshot,
1979
+ feedbackId: result.feedbackId
1980
+ })));
1981
+ const pendingScreenshots = result.pendingScreenshots.filter((_, index) => results[index]?.status === "rejected");
1982
+ return {
1983
+ feedbackId: result.feedbackId,
1984
+ pendingScreenshots
1985
+ };
1986
+ }
1987
+ async function createReportWithScreenshots(transport, submission, pendingScreenshots) {
1988
+ const { feedbackId } = await transport.create({
1989
+ context: submission.context,
1990
+ description: buildDescription(submission),
1991
+ title: deriveTitle(submission.message)
1992
+ });
1993
+ return attachPreparedScreenshots(transport, {
1994
+ feedbackId,
1995
+ pendingScreenshots
1996
+ });
1997
+ }
1998
+ async function submitWidgetFeedback(transport, submission) {
1999
+ if (!deriveTitle(submission.message)) throw new Error("Please describe your feedback before sending it.");
2000
+ return createReportWithScreenshots(transport, submission, await uploadScreenshots(transport, submission));
2001
+ }
2002
+ function isStorageResponse(value) {
2003
+ return typeof value === "object" && value !== null && typeof Reflect.get(value, "storageId") === "string";
2004
+ }
2005
+ async function uploadImageBlob(uploadUrl, image) {
2006
+ const response = await fetch(uploadUrl, {
2007
+ body: image.blob,
2008
+ headers: { "Content-Type": image.mimeType },
2009
+ method: "POST"
2010
+ });
2011
+ if (!response.ok) throw new Error(`Screenshot upload failed (${response.status})`);
2012
+ const payload = await response.json();
2013
+ if (!isStorageResponse(payload)) throw new Error("Screenshot upload returned no storage id");
2014
+ return payload.storageId;
2015
+ }
2016
+ function createFeedbackTransport(client) {
2017
+ return {
2018
+ create: (params) => client.create(params),
2019
+ getScreenshotUploadUrl: () => client.getScreenshotUploadUrl(),
2020
+ saveScreenshot: (params) => client.saveScreenshot(params),
2021
+ uploadImage: uploadImageBlob
2022
+ };
2023
+ }
2024
+ //#endregion
2025
+ export { SquareIcon as A, collectPageContext as B, ChevronDownIcon as C, MoreIcon as D, HighlightIcon as E, UndoIcon as F, resolveComponentStack as G, buildElementSelection as H, isDegenerate as I, captureViewport as J, shortenSourcePath as K, normalizeRect as L, TargetIcon as M, TextIcon as N, PencilIcon as O, TrashIcon as P, DEFAULT_WIDGET_LABELS as R, CheckIcon as S, GripIcon as T, describeElement as U, MAX_SELECTION_COMMENT_LENGTH as V, getFiberFromNode as W, capturedViewportSize as X, capturedFromCanvas as Y, releaseCapture as Z, matchesHotkey as _, uploadImageBlob as a, CameraIcon as b, useFloatingPosition as c, SelectionOutline as d, drawAnnotations as f, isSubmitEnter as g, stopClicksAtHost as h, submitWidgetFeedback as i, Svg as j, SpotlightIcon as k, onViewportChange as l, listenToKeydown as m, createFeedbackTransport as n, ElementPicker as o, listenOnPageAndWidget as p, WIDGET_MARKER as q, deriveTitle as r, isWidgetOwned as s, attachPreparedScreenshots as t, visibleViewport as u, ArrowIcon as v, CloseIcon as w, ChatIcon as x, BlurIcon as y, SDK_VERSION as z };
1922
2026
 
1923
- //# sourceMappingURL=picker-DCoAFX5y.js.map
2027
+ //# sourceMappingURL=submit-Cd-g1Dtg.js.map