reflet-sdk 0.6.3 → 0.6.5

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.3";
820
+ const SDK_VERSION = "0.6.5";
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",
@@ -1721,7 +1672,6 @@ function labelTop(rect, view) {
1721
1672
  if (below + LABEL_HEIGHT <= view.top + view.height) return below;
1722
1673
  return Math.max(rect.top, view.top) + LABEL_GAP;
1723
1674
  }
1724
- const APPLE_PLATFORM = /Mac|iPhone|iPad/;
1725
1675
  function ElementPicker({ copy, inspect, labels, onCancel, onPick }) {
1726
1676
  const [target, setTarget] = useState(null);
1727
1677
  const [pinned, setPinned] = useState(null);
@@ -1781,7 +1731,8 @@ function ElementPicker({ copy, inspect, labels, onCancel, onPick }) {
1781
1731
  const onPointerUp = (event) => {
1782
1732
  if (startsInWidget(event)) return;
1783
1733
  swallow(event);
1784
- if (pinnedRef.current) return;
1734
+ const isPrimaryPress = event.button === 0;
1735
+ if (pinnedRef.current || !isPrimaryPress) return;
1785
1736
  const element = elementUnder(event.clientX, event.clientY);
1786
1737
  if (element) pin(element, event.shiftKey);
1787
1738
  };
@@ -1805,12 +1756,13 @@ function ElementPicker({ copy, inspect, labels, onCancel, onPick }) {
1805
1756
  };
1806
1757
  const copyAimed = (event) => {
1807
1758
  const aimed = aimRef.current;
1808
- if (!((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "c" && copy && aimed)) return false;
1759
+ if (!(copy && aimed && matchesHotkey(event, "mod+c"))) return false;
1809
1760
  swallow(event);
1810
1761
  copy.onCopy(aimed, "");
1811
1762
  return true;
1812
1763
  };
1813
1764
  const onKeyDown = (event) => {
1765
+ if (event.isComposing) return;
1814
1766
  if (event.key === "Escape") {
1815
1767
  swallow(event);
1816
1768
  onEscape();
@@ -1842,6 +1794,7 @@ function ElementPicker({ copy, inspect, labels, onCancel, onPick }) {
1842
1794
  window.addEventListener("mousedown", swallow, true);
1843
1795
  window.addEventListener("mouseup", swallow, true);
1844
1796
  window.addEventListener("click", swallow, true);
1797
+ window.addEventListener("contextmenu", swallow, true);
1845
1798
  const stopListeningToKeys = listenToKeydown(pickerRef.current, onKeyDown, true);
1846
1799
  window.addEventListener("scroll", onScroll, {
1847
1800
  capture: true,
@@ -1856,6 +1809,7 @@ function ElementPicker({ copy, inspect, labels, onCancel, onPick }) {
1856
1809
  window.removeEventListener("mousedown", swallow, true);
1857
1810
  window.removeEventListener("mouseup", swallow, true);
1858
1811
  window.removeEventListener("click", swallow, true);
1812
+ window.removeEventListener("contextmenu", swallow, true);
1859
1813
  stopListeningToKeys();
1860
1814
  window.removeEventListener("scroll", onScroll, true);
1861
1815
  document.body.style.cursor = previousCursor;
@@ -1902,10 +1856,11 @@ function ElementPicker({ copy, inspect, labels, onCancel, onPick }) {
1902
1856
  maxLength: MAX_SELECTION_COMMENT_LENGTH,
1903
1857
  onChange: (event) => setNote(event.target.value),
1904
1858
  onKeyDown: (event) => {
1905
- if (event.key === "Enter" && !event.shiftKey) {
1906
- event.preventDefault();
1907
- onPick(pinned.element, note.trim());
1908
- }
1859
+ if (!isSubmitEnter(event.nativeEvent)) return;
1860
+ event.preventDefault();
1861
+ const trimmedNote = note.trim();
1862
+ if (copy && !trimmedNote) copy.onCopy(pinned.element, "");
1863
+ else onPick(pinned.element, trimmedNote);
1909
1864
  },
1910
1865
  placeholder: labels.elementNotePlaceholder,
1911
1866
  ref: noteRef,
@@ -1956,7 +1911,7 @@ function ElementPicker({ copy, inspect, labels, onCancel, onPick }) {
1956
1911
  /* @__PURE__ */ jsx("kbd", { children: "Tab" }),
1957
1912
  /* @__PURE__ */ jsx("kbd", { children: "Enter" }),
1958
1913
  inspect && /* @__PURE__ */ jsx("kbd", { children: "⇧ Click" }),
1959
- copy && /* @__PURE__ */ jsx("kbd", { children: APPLE_PLATFORM.test(navigator.userAgent) ? "⌘C" : "Ctrl C" }),
1914
+ copy && /* @__PURE__ */ jsx("kbd", { children: isApplePlatform() ? "⌘C" : "Ctrl C" }),
1960
1915
  /* @__PURE__ */ jsx("kbd", { children: "Esc" })
1961
1916
  ]
1962
1917
  }), /* @__PURE__ */ jsx("button", {
@@ -1970,6 +1925,104 @@ function ElementPicker({ copy, inspect, labels, onCancel, onPick }) {
1970
1925
  });
1971
1926
  }
1972
1927
  //#endregion
1973
- 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 };
1928
+ //#region src/feedback/core/submission/upload-screenshots.ts
1929
+ async function uploadImage(transport, image) {
1930
+ const { uploadUrl } = await transport.getScreenshotUploadUrl();
1931
+ return transport.uploadImage(uploadUrl, image);
1932
+ }
1933
+ async function prepareScreenshot(transport, screenshot, index) {
1934
+ const { image, annotations, context } = screenshot;
1935
+ const annotated = annotations.length > 0 ? await renderAnnotatedImage(image, annotations) : null;
1936
+ if (annotations.length > 0 && !annotated) throw new Error("Could not render your annotations. Please try again.");
1937
+ try {
1938
+ const storageId = await uploadImage(transport, image);
1939
+ return {
1940
+ annotatedStorageId: annotated ? await uploadImage(transport, annotated) : void 0,
1941
+ annotations: annotations.length > 0 ? toWireAnnotations(annotations) : void 0,
1942
+ captureSource: "widget",
1943
+ filename: `screenshot-${index + 1}.png`,
1944
+ height: image.height,
1945
+ mimeType: image.mimeType,
1946
+ pageUrl: context.url,
1947
+ size: image.blob.size,
1948
+ storageId,
1949
+ width: image.width
1950
+ };
1951
+ } finally {
1952
+ releaseCapture(annotated);
1953
+ }
1954
+ }
1955
+ async function uploadScreenshots(transport, submission) {
1956
+ return (await Promise.allSettled(submission.screenshots.map((screenshot, index) => prepareScreenshot(transport, screenshot, index)))).map((result) => {
1957
+ if (result.status === "rejected") throw result.reason;
1958
+ return result.value;
1959
+ });
1960
+ }
1961
+ //#endregion
1962
+ //#region src/feedback/core/submit.ts
1963
+ const MAX_TITLE_LENGTH = 100;
1964
+ const NEWLINE = /\r?\n/;
1965
+ function deriveTitle(message) {
1966
+ const firstLine = message.split(NEWLINE).map((line) => line.trim()).find(Boolean) ?? "";
1967
+ if (firstLine.length <= MAX_TITLE_LENGTH) return firstLine;
1968
+ const clipped = firstLine.slice(0, 99);
1969
+ const lastSpace = clipped.lastIndexOf(" ");
1970
+ return `${(lastSpace > 0 ? clipped.slice(0, lastSpace) : clipped).trimEnd()}…`;
1971
+ }
1972
+ function buildDescription(params) {
1973
+ const { email, isAnonymous, message } = params;
1974
+ const contact = isAnonymous && email ? `\n\n---\nContact: ${email}` : "";
1975
+ return `${message.trim()}${contact}`;
1976
+ }
1977
+ async function attachPreparedScreenshots(transport, result) {
1978
+ const results = await Promise.allSettled(result.pendingScreenshots.map((screenshot) => transport.saveScreenshot({
1979
+ ...screenshot,
1980
+ feedbackId: result.feedbackId
1981
+ })));
1982
+ const pendingScreenshots = result.pendingScreenshots.filter((_, index) => results[index]?.status === "rejected");
1983
+ return {
1984
+ feedbackId: result.feedbackId,
1985
+ pendingScreenshots
1986
+ };
1987
+ }
1988
+ async function createReportWithScreenshots(transport, submission, pendingScreenshots) {
1989
+ const { feedbackId } = await transport.create({
1990
+ context: submission.context,
1991
+ description: buildDescription(submission),
1992
+ title: deriveTitle(submission.message)
1993
+ });
1994
+ return attachPreparedScreenshots(transport, {
1995
+ feedbackId,
1996
+ pendingScreenshots
1997
+ });
1998
+ }
1999
+ async function submitWidgetFeedback(transport, submission) {
2000
+ if (!deriveTitle(submission.message)) throw new Error("Please describe your feedback before sending it.");
2001
+ return createReportWithScreenshots(transport, submission, await uploadScreenshots(transport, submission));
2002
+ }
2003
+ function isStorageResponse(value) {
2004
+ return typeof value === "object" && value !== null && typeof Reflect.get(value, "storageId") === "string";
2005
+ }
2006
+ async function uploadImageBlob(uploadUrl, image) {
2007
+ const response = await fetch(uploadUrl, {
2008
+ body: image.blob,
2009
+ headers: { "Content-Type": image.mimeType },
2010
+ method: "POST"
2011
+ });
2012
+ if (!response.ok) throw new Error(`Screenshot upload failed (${response.status})`);
2013
+ const payload = await response.json();
2014
+ if (!isStorageResponse(payload)) throw new Error("Screenshot upload returned no storage id");
2015
+ return payload.storageId;
2016
+ }
2017
+ function createFeedbackTransport(client) {
2018
+ return {
2019
+ create: (params) => client.create(params),
2020
+ getScreenshotUploadUrl: () => client.getScreenshotUploadUrl(),
2021
+ saveScreenshot: (params) => client.saveScreenshot(params),
2022
+ uploadImage: uploadImageBlob
2023
+ };
2024
+ }
2025
+ //#endregion
2026
+ 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 };
1974
2027
 
1975
- //# sourceMappingURL=picker-Rh-hsxyX.js.map
2028
+ //# sourceMappingURL=submit-MvVjNdYc.js.map