@bitvea/feedback-toolbar 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. package/README.md +262 -0
  2. package/dist/FeedbackPanel.d.ts +117 -0
  3. package/dist/FeedbackPanel.d.ts.map +1 -0
  4. package/dist/FeedbackPanel.js +622 -0
  5. package/dist/FeedbackPanel.js.map +1 -0
  6. package/dist/FeedbackToolbar.d.ts +54 -0
  7. package/dist/FeedbackToolbar.d.ts.map +1 -0
  8. package/dist/FeedbackToolbar.js +1149 -0
  9. package/dist/FeedbackToolbar.js.map +1 -0
  10. package/dist/InboxSheet.d.ts +80 -0
  11. package/dist/InboxSheet.d.ts.map +1 -0
  12. package/dist/InboxSheet.js +580 -0
  13. package/dist/InboxSheet.js.map +1 -0
  14. package/dist/PinLayer.d.ts +59 -0
  15. package/dist/PinLayer.d.ts.map +1 -0
  16. package/dist/PinLayer.js +1702 -0
  17. package/dist/PinLayer.js.map +1 -0
  18. package/dist/RichText.d.ts +6 -0
  19. package/dist/RichText.d.ts.map +1 -0
  20. package/dist/RichText.js +135 -0
  21. package/dist/RichText.js.map +1 -0
  22. package/dist/browser/entry.d.ts +3 -0
  23. package/dist/browser/entry.d.ts.map +1 -0
  24. package/dist/browser/entry.js +13 -0
  25. package/dist/browser/entry.js.map +1 -0
  26. package/dist/browser/mount.d.ts +37 -0
  27. package/dist/browser/mount.d.ts.map +1 -0
  28. package/dist/browser/mount.js +150 -0
  29. package/dist/browser/mount.js.map +1 -0
  30. package/dist/browser/toolbar.js +38 -0
  31. package/dist/browser/toolbar.js.map +7 -0
  32. package/dist/capture.d.ts +58 -0
  33. package/dist/capture.d.ts.map +1 -0
  34. package/dist/capture.js +98 -0
  35. package/dist/capture.js.map +1 -0
  36. package/dist/cli/bin.d.ts +3 -0
  37. package/dist/cli/bin.d.ts.map +1 -0
  38. package/dist/cli/bin.js +196 -0
  39. package/dist/cli/bin.js.map +1 -0
  40. package/dist/cli/fs.d.ts +16 -0
  41. package/dist/cli/fs.d.ts.map +1 -0
  42. package/dist/cli/fs.js +33 -0
  43. package/dist/cli/fs.js.map +1 -0
  44. package/dist/cli/init.d.ts +75 -0
  45. package/dist/cli/init.d.ts.map +1 -0
  46. package/dist/cli/init.js +266 -0
  47. package/dist/cli/init.js.map +1 -0
  48. package/dist/cli/register.d.ts +55 -0
  49. package/dist/cli/register.d.ts.map +1 -0
  50. package/dist/cli/register.js +84 -0
  51. package/dist/cli/register.js.map +1 -0
  52. package/dist/cli/registration.d.ts +37 -0
  53. package/dist/cli/registration.d.ts.map +1 -0
  54. package/dist/cli/registration.js +69 -0
  55. package/dist/cli/registration.js.map +1 -0
  56. package/dist/cli/strategies.d.ts +98 -0
  57. package/dist/cli/strategies.d.ts.map +1 -0
  58. package/dist/cli/strategies.js +198 -0
  59. package/dist/cli/strategies.js.map +1 -0
  60. package/dist/client.d.ts +59 -0
  61. package/dist/client.d.ts.map +1 -0
  62. package/dist/client.js +113 -0
  63. package/dist/client.js.map +1 -0
  64. package/dist/config.d.ts +92 -0
  65. package/dist/config.d.ts.map +1 -0
  66. package/dist/config.js +65 -0
  67. package/dist/config.js.map +1 -0
  68. package/dist/deepLink.d.ts +86 -0
  69. package/dist/deepLink.d.ts.map +1 -0
  70. package/dist/deepLink.js +185 -0
  71. package/dist/deepLink.js.map +1 -0
  72. package/dist/fontFace.d.ts +70 -0
  73. package/dist/fontFace.d.ts.map +1 -0
  74. package/dist/fontFace.js +88 -0
  75. package/dist/fontFace.js.map +1 -0
  76. package/dist/gating.d.ts +32 -0
  77. package/dist/gating.d.ts.map +1 -0
  78. package/dist/gating.js +49 -0
  79. package/dist/gating.js.map +1 -0
  80. package/dist/icons.d.ts +68 -0
  81. package/dist/icons.d.ts.map +1 -0
  82. package/dist/icons.js +97 -0
  83. package/dist/icons.js.map +1 -0
  84. package/dist/index.d.ts +14 -0
  85. package/dist/index.d.ts.map +1 -0
  86. package/dist/index.js +68 -0
  87. package/dist/index.js.map +1 -0
  88. package/dist/markdown.d.ts +59 -0
  89. package/dist/markdown.d.ts.map +1 -0
  90. package/dist/markdown.js +130 -0
  91. package/dist/markdown.js.map +1 -0
  92. package/dist/motion.d.ts +178 -0
  93. package/dist/motion.d.ts.map +1 -0
  94. package/dist/motion.js +302 -0
  95. package/dist/motion.js.map +1 -0
  96. package/dist/preferences.d.ts +71 -0
  97. package/dist/preferences.d.ts.map +1 -0
  98. package/dist/preferences.js +195 -0
  99. package/dist/preferences.js.map +1 -0
  100. package/dist/rasterise.d.ts +45 -0
  101. package/dist/rasterise.d.ts.map +1 -0
  102. package/dist/rasterise.js +490 -0
  103. package/dist/rasterise.js.map +1 -0
  104. package/dist/selector.d.ts +19 -0
  105. package/dist/selector.d.ts.map +1 -0
  106. package/dist/selector.js +142 -0
  107. package/dist/selector.js.map +1 -0
  108. package/dist/session.d.ts +19 -0
  109. package/dist/session.d.ts.map +1 -0
  110. package/dist/session.js +36 -0
  111. package/dist/session.js.map +1 -0
  112. package/dist/shortcut.d.ts +88 -0
  113. package/dist/shortcut.d.ts.map +1 -0
  114. package/dist/shortcut.js +149 -0
  115. package/dist/shortcut.js.map +1 -0
  116. package/dist/tokens.d.ts +709 -0
  117. package/dist/tokens.d.ts.map +1 -0
  118. package/dist/tokens.js +749 -0
  119. package/dist/tokens.js.map +1 -0
  120. package/dist/vendor/types/account.d.ts +26 -0
  121. package/dist/vendor/types/account.d.ts.map +1 -0
  122. package/dist/vendor/types/account.js +23 -0
  123. package/dist/vendor/types/account.js.map +1 -0
  124. package/dist/vendor/types/apiKey.d.ts +38 -0
  125. package/dist/vendor/types/apiKey.d.ts.map +1 -0
  126. package/dist/vendor/types/apiKey.js +36 -0
  127. package/dist/vendor/types/apiKey.js.map +1 -0
  128. package/dist/vendor/types/auth.d.ts +10 -0
  129. package/dist/vendor/types/auth.d.ts.map +1 -0
  130. package/dist/vendor/types/auth.js +15 -0
  131. package/dist/vendor/types/auth.js.map +1 -0
  132. package/dist/vendor/types/entity.d.ts +74 -0
  133. package/dist/vendor/types/entity.d.ts.map +1 -0
  134. package/dist/vendor/types/entity.js +111 -0
  135. package/dist/vendor/types/entity.js.map +1 -0
  136. package/dist/vendor/types/feedback.d.ts +285 -0
  137. package/dist/vendor/types/feedback.d.ts.map +1 -0
  138. package/dist/vendor/types/feedback.js +286 -0
  139. package/dist/vendor/types/feedback.js.map +1 -0
  140. package/dist/vendor/types/index.d.ts +14 -0
  141. package/dist/vendor/types/index.d.ts.map +1 -0
  142. package/dist/vendor/types/index.js +16 -0
  143. package/dist/vendor/types/index.js.map +1 -0
  144. package/dist/vendor/types/invite.d.ts +42 -0
  145. package/dist/vendor/types/invite.d.ts.map +1 -0
  146. package/dist/vendor/types/invite.js +30 -0
  147. package/dist/vendor/types/invite.js.map +1 -0
  148. package/dist/vendor/types/mcpToken.d.ts +38 -0
  149. package/dist/vendor/types/mcpToken.d.ts.map +1 -0
  150. package/dist/vendor/types/mcpToken.js +39 -0
  151. package/dist/vendor/types/mcpToken.js.map +1 -0
  152. package/dist/vendor/types/organization.d.ts +13 -0
  153. package/dist/vendor/types/organization.d.ts.map +1 -0
  154. package/dist/vendor/types/organization.js +12 -0
  155. package/dist/vendor/types/organization.js.map +1 -0
  156. package/dist/vendor/types/project.d.ts +73 -0
  157. package/dist/vendor/types/project.d.ts.map +1 -0
  158. package/dist/vendor/types/project.js +117 -0
  159. package/dist/vendor/types/project.js.map +1 -0
  160. package/dist/vendor/types/role.d.ts +12 -0
  161. package/dist/vendor/types/role.d.ts.map +1 -0
  162. package/dist/vendor/types/role.js +20 -0
  163. package/dist/vendor/types/role.js.map +1 -0
  164. package/dist/vendor/types/roleDefinition.d.ts +61 -0
  165. package/dist/vendor/types/roleDefinition.d.ts.map +1 -0
  166. package/dist/vendor/types/roleDefinition.js +45 -0
  167. package/dist/vendor/types/roleDefinition.js.map +1 -0
  168. package/dist/vendor/types/team.d.ts +25 -0
  169. package/dist/vendor/types/team.d.ts.map +1 -0
  170. package/dist/vendor/types/team.js +17 -0
  171. package/dist/vendor/types/team.js.map +1 -0
  172. package/dist/vendor/types/util.d.ts +39 -0
  173. package/dist/vendor/types/util.d.ts.map +1 -0
  174. package/dist/vendor/types/util.js +31 -0
  175. package/dist/vendor/types/util.js.map +1 -0
  176. package/package.json +59 -0
@@ -0,0 +1,490 @@
1
+ // Turning the current viewport into pixels, with no permission prompt and no
2
+ // dependency.
3
+ //
4
+ // The technique is the platform's own: clone the visible DOM, write the styles
5
+ // that matter onto the clones as inline style, wrap the result in an SVG
6
+ // `<foreignObject>`, and let the browser decode that SVG as an image and draw
7
+ // it into a canvas. Everything here is synchronous DOM work plus one image
8
+ // decode, so it costs the reviewer nothing and asks them for nothing.
9
+ //
10
+ // Known and accepted limits, all of them consequences of how an SVG image is
11
+ // rendered - it is a sealed document that may not load external resources:
12
+ //
13
+ // - Web fonts do not load, so text falls back down its font stack. Metrics
14
+ // shift; the layout does not.
15
+ // - Cross-origin images and `background-image` URLs do not load. Same-origin
16
+ // and CORS-enabled `<img>` content is inlined below, so the common case
17
+ // survives; the rest render as empty boxes rather than broken icons.
18
+ // - Only the properties in `COPIED_PROPERTIES` are carried over. Copying all
19
+ // ~340 computed properties per element is correct but produces megabytes of
20
+ // CSS for an ordinary page, and the data URL that has to carry it grows
21
+ // faster still. The allowlist is the visual set.
22
+ //
23
+ // The spec puts cross-browser screenshot fidelity under exotic CSS out of scope
24
+ // for v1, and these are exactly that class of gap. A screenshot that is
25
+ // recognisably the page, taken silently, beats a pixel-exact one that costs a
26
+ // modal dialog per submission.
27
+ /** Elements that never contribute pixels, or that must not be in the shot. */
28
+ const SKIPPED_TAGS = new Set([
29
+ "SCRIPT",
30
+ "STYLE",
31
+ "LINK",
32
+ "META",
33
+ "TITLE",
34
+ "NOSCRIPT",
35
+ "TEMPLATE",
36
+ "BASE",
37
+ ]);
38
+ /**
39
+ * The toolbar's own root carries this attribute. A screenshot of the page the
40
+ * reviewer is reporting on should not have the reporting UI sitting in it.
41
+ */
42
+ const TOOLBAR_ROOT_ATTRIBUTE = "data-bitvea-feedback-toolbar";
43
+ /**
44
+ * Past this many elements the clone-and-restyle walk stops being something a
45
+ * reviewer would sit through. Bail rather than freeze the page they are trying
46
+ * to comment on; a null screenshot is a supported outcome, a hung tab is not.
47
+ */
48
+ const MAX_ELEMENTS = 12_000;
49
+ /**
50
+ * Attributes worth carrying onto a clone. Everything else either only mattered
51
+ * through a stylesheet the SVG document cannot see, or is a framework
52
+ * attribute whose name is not valid XML.
53
+ */
54
+ const KEPT_ATTRIBUTES = new Set([
55
+ "colspan",
56
+ "rowspan",
57
+ "span",
58
+ "dir",
59
+ "alt",
60
+ "start",
61
+ "reversed",
62
+ "type",
63
+ "value",
64
+ "checked",
65
+ "selected",
66
+ "open",
67
+ "lang",
68
+ ]);
69
+ /** How long to wait for the browser to decode the SVG before giving up. */
70
+ const DECODE_TIMEOUT_MS = 5_000;
71
+ /**
72
+ * The properties written onto each clone. Longhands only: `getComputedStyle`
73
+ * resolves to longhands, and asking it for a shorthand is not portable.
74
+ */
75
+ const COPIED_PROPERTIES = [
76
+ // Box
77
+ "display",
78
+ "position",
79
+ "top",
80
+ "right",
81
+ "bottom",
82
+ "left",
83
+ "float",
84
+ "clear",
85
+ "z-index",
86
+ "box-sizing",
87
+ "width",
88
+ "height",
89
+ "min-width",
90
+ "min-height",
91
+ "max-width",
92
+ "max-height",
93
+ "aspect-ratio",
94
+ "margin-top",
95
+ "margin-right",
96
+ "margin-bottom",
97
+ "margin-left",
98
+ "padding-top",
99
+ "padding-right",
100
+ "padding-bottom",
101
+ "padding-left",
102
+ "overflow-x",
103
+ "overflow-y",
104
+ "visibility",
105
+ "opacity",
106
+ // Border and outline
107
+ "border-top-width",
108
+ "border-right-width",
109
+ "border-bottom-width",
110
+ "border-left-width",
111
+ "border-top-style",
112
+ "border-right-style",
113
+ "border-bottom-style",
114
+ "border-left-style",
115
+ "border-top-color",
116
+ "border-right-color",
117
+ "border-bottom-color",
118
+ "border-left-color",
119
+ "border-top-left-radius",
120
+ "border-top-right-radius",
121
+ "border-bottom-left-radius",
122
+ "border-bottom-right-radius",
123
+ "outline-width",
124
+ "outline-style",
125
+ "outline-color",
126
+ "outline-offset",
127
+ // Paint
128
+ "background-color",
129
+ "background-image",
130
+ "background-size",
131
+ "background-position",
132
+ "background-repeat",
133
+ "background-clip",
134
+ "background-origin",
135
+ "box-shadow",
136
+ "filter",
137
+ "mix-blend-mode",
138
+ "clip-path",
139
+ "transform",
140
+ "transform-origin",
141
+ // Type
142
+ "color",
143
+ "font-family",
144
+ "font-size",
145
+ "font-style",
146
+ "font-weight",
147
+ "font-variant",
148
+ "font-stretch",
149
+ "letter-spacing",
150
+ "line-height",
151
+ "text-align",
152
+ "text-decoration-line",
153
+ "text-decoration-color",
154
+ "text-decoration-style",
155
+ "text-indent",
156
+ "text-transform",
157
+ "text-shadow",
158
+ "white-space",
159
+ "word-break",
160
+ "overflow-wrap",
161
+ "vertical-align",
162
+ "text-overflow",
163
+ "direction",
164
+ // Flex and grid
165
+ "flex-direction",
166
+ "flex-wrap",
167
+ "flex-grow",
168
+ "flex-shrink",
169
+ "flex-basis",
170
+ "justify-content",
171
+ "justify-items",
172
+ "justify-self",
173
+ "align-content",
174
+ "align-items",
175
+ "align-self",
176
+ "order",
177
+ "gap",
178
+ "row-gap",
179
+ "column-gap",
180
+ "grid-template-columns",
181
+ "grid-template-rows",
182
+ "grid-auto-flow",
183
+ "grid-auto-columns",
184
+ "grid-auto-rows",
185
+ "grid-column-start",
186
+ "grid-column-end",
187
+ "grid-row-start",
188
+ "grid-row-end",
189
+ // Tables, lists, media
190
+ "table-layout",
191
+ "border-collapse",
192
+ "border-spacing",
193
+ "caption-side",
194
+ "list-style-type",
195
+ "list-style-position",
196
+ "object-fit",
197
+ "object-position",
198
+ "fill",
199
+ "stroke",
200
+ "stroke-width",
201
+ ];
202
+ /**
203
+ * Rasterise the visible viewport to a PNG data URL, or null if the browser
204
+ * would not produce one.
205
+ *
206
+ * @param maxDataUrlLength Longest data URL the API will accept. The image is
207
+ * re-encoded at half scale, twice, before giving up, so a very large or very
208
+ * dense viewport degrades in quality rather than disappearing.
209
+ */
210
+ export async function rasteriseViewport(maxDataUrlLength) {
211
+ return rasteriseRegion(maxDataUrlLength, null);
212
+ }
213
+ /**
214
+ * Rasterise ONE element to a PNG data URL, or null if the browser would not
215
+ * produce one.
216
+ *
217
+ * A reviewer pointing at a broken button wants a picture of the button, not of
218
+ * the page it is on: the whole-viewport shot puts the thing they are reporting
219
+ * somewhere in a 1512x916 image and leaves the reader to find it. This is the
220
+ * camera in the composer.
221
+ *
222
+ * An element with no box - `display: none`, or detached - yields null rather
223
+ * than a 1x1 image, because a blank attachment is worse than none.
224
+ */
225
+ export async function rasteriseElement(element, maxDataUrlLength) {
226
+ const rect = element.getBoundingClientRect();
227
+ if (rect.width < 1 || rect.height < 1)
228
+ return null;
229
+ return rasteriseRegion(maxDataUrlLength, rect);
230
+ }
231
+ /**
232
+ * The shared half. `region` is null for the viewport, or a rectangle in
233
+ * viewport coordinates for one element.
234
+ *
235
+ * @param maxDataUrlLength Longest data URL the API will accept. The image is
236
+ * re-encoded at half scale, twice, before giving up, so a very large or very
237
+ * dense capture degrades in quality rather than disappearing.
238
+ */
239
+ async function rasteriseRegion(maxDataUrlLength, region) {
240
+ if (typeof document === "undefined" || !document.body)
241
+ return null;
242
+ const width = Math.max(1, Math.floor(region ? region.width : window.innerWidth));
243
+ const height = Math.max(1, Math.floor(region ? region.height : window.innerHeight));
244
+ const origin = region ? { x: region.x, y: region.y } : { x: 0, y: 0 };
245
+ // Ask for the drawing surface before doing any work: where there is no 2D
246
+ // context there is no screenshot, and finding that out first keeps the
247
+ // failure cheap.
248
+ const canvas = document.createElement("canvas");
249
+ const ctx = canvas.getContext("2d");
250
+ if (!ctx)
251
+ return null;
252
+ const svg = buildViewportSvg(width, height, origin);
253
+ if (svg === null)
254
+ return null;
255
+ const image = await decodeSvg(svg);
256
+ if (!image)
257
+ return null;
258
+ const background = resolvePageBackground();
259
+ let scale = Math.min(window.devicePixelRatio || 1, 2);
260
+ for (let attempt = 0; attempt < 3; attempt++) {
261
+ canvas.width = Math.max(1, Math.round(width * scale));
262
+ canvas.height = Math.max(1, Math.round(height * scale));
263
+ ctx.fillStyle = background;
264
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
265
+ ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
266
+ const dataUrl = canvas.toDataURL("image/png");
267
+ if (dataUrl.length <= maxDataUrlLength)
268
+ return dataUrl;
269
+ scale /= 2;
270
+ }
271
+ return null;
272
+ }
273
+ /**
274
+ * Serialise the visible page as an SVG document string, or null if the page is
275
+ * too large to walk. Exported for the unit tests: it is pure DOM work with no
276
+ * canvas or image decoding in it, so it is the part that is provable in jsdom.
277
+ */
278
+ export function buildViewportSvg(width, height,
279
+ /**
280
+ * Where in the viewport the picture starts. `{x: 0, y: 0}` is the viewport
281
+ * shot; an element shot passes its own top-left, and the body clone shifts
282
+ * by the same amount so the element lands at the picture's origin.
283
+ *
284
+ * Cropping by MOVING THE WHOLE PAGE rather than by cloning only the element
285
+ * is the decision here. An element does not carry its own layout: its width
286
+ * may come from a flex parent, its colour from a `.dark` ancestor, its font
287
+ * from `html`. Cloning it alone reproduces none of that, and the picture
288
+ * that comes back is not the thing the reviewer pointed at. The page is
289
+ * cloned exactly as it is and the viewport is narrowed instead.
290
+ */
291
+ origin = { x: 0, y: 0 }) {
292
+ if (document.body.getElementsByTagName("*").length > MAX_ELEMENTS) {
293
+ return null;
294
+ }
295
+ const clone = cloneStyled(document.body);
296
+ if (!clone)
297
+ return null;
298
+ // Placing the body clone at the viewport coordinates of the real body is
299
+ // what folds scroll position, body margins and body offset into one number
300
+ // each. Descendants then lay themselves out normally inside it, and
301
+ // `position: fixed` descendants resolve against the SVG viewport, which is
302
+ // the browser viewport - so a sticky header stays where it looks.
303
+ const rect = document.body.getBoundingClientRect();
304
+ clone.style.setProperty("position", "absolute");
305
+ clone.style.setProperty("left", `${rect.left - origin.x}px`);
306
+ clone.style.setProperty("top", `${rect.top - origin.y}px`);
307
+ clone.style.setProperty("width", `${rect.width}px`);
308
+ clone.style.setProperty("height", `${rect.height}px`);
309
+ clone.style.setProperty("margin", "0");
310
+ const container = document.createElement("div");
311
+ container.setAttribute("style", [
312
+ "position:relative",
313
+ `width:${width}px`,
314
+ `height:${height}px`,
315
+ "overflow:hidden",
316
+ `background:${resolvePageBackground()}`,
317
+ ].join(";"));
318
+ container.appendChild(clone);
319
+ const body = new XMLSerializer().serializeToString(container);
320
+ return (`<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" ` +
321
+ `viewBox="0 0 ${width} ${height}">` +
322
+ `<foreignObject x="0" y="0" width="100%" height="100%">${body}</foreignObject>` +
323
+ `</svg>`);
324
+ }
325
+ /* ── The walk ─────────────────────────────────────────────────────────── */
326
+ function cloneStyled(source) {
327
+ if (SKIPPED_TAGS.has(source.tagName))
328
+ return null;
329
+ if (source.hasAttribute(TOOLBAR_ROOT_ATTRIBUTE))
330
+ return null;
331
+ const computed = window.getComputedStyle(source);
332
+ // `display: none` contributes nothing, and skipping it prunes whole subtrees
333
+ // - closed menus and offscreen drawers are usually most of a page's DOM.
334
+ if (computed.getPropertyValue("display") === "none")
335
+ return null;
336
+ // Inline SVG - icons, mostly - is already an XML island whose attributes are
337
+ // its geometry. Take the whole subtree verbatim and only add the inherited
338
+ // paint, which is what `currentColor` needs to resolve to.
339
+ if (source instanceof SVGElement) {
340
+ const svgClone = source.cloneNode(true);
341
+ svgClone.setAttribute("style", inlineStyleFor(source, computed));
342
+ return svgClone;
343
+ }
344
+ // A canvas paints from its bitmap, not its children, so it becomes an image.
345
+ const clone = (source instanceof HTMLCanvasElement
346
+ ? document.createElement("img")
347
+ : source.cloneNode(false));
348
+ // Most attributes only mattered through stylesheets the SVG document cannot
349
+ // see, and framework attributes (`@click`, `[ngIf]`, `x-on:click`) are not
350
+ // valid XML names, so serialising them would break the whole document. Keep
351
+ // the few that still drive layout or content.
352
+ for (const name of [...clone.getAttributeNames()]) {
353
+ if (!KEPT_ATTRIBUTES.has(name))
354
+ clone.removeAttribute(name);
355
+ }
356
+ clone.setAttribute("style", inlineStyleFor(source, computed));
357
+ if (!inlineReplacement(source, clone)) {
358
+ for (const child of Array.from(source.childNodes)) {
359
+ if (child.nodeType === Node.TEXT_NODE) {
360
+ clone.appendChild(child.cloneNode(false));
361
+ }
362
+ else if (child.nodeType === Node.ELEMENT_NODE) {
363
+ const childClone = cloneStyled(child);
364
+ if (childClone)
365
+ clone.appendChild(childClone);
366
+ }
367
+ // Comments, processing instructions and the rest paint nothing.
368
+ }
369
+ }
370
+ return clone;
371
+ }
372
+ function inlineStyleFor(source, computed) {
373
+ const declarations = [];
374
+ for (const property of COPIED_PROPERTIES) {
375
+ const value = computed.getPropertyValue(property);
376
+ if (value === "")
377
+ continue;
378
+ declarations.push(`${property}:${value}`);
379
+ }
380
+ // An element scrolled internally shows its scrolled content, and nothing in
381
+ // the computed style says so.
382
+ if (source.scrollTop || source.scrollLeft) {
383
+ declarations.push(`transform:translate(${-source.scrollLeft}px, ${-source.scrollTop}px)`);
384
+ }
385
+ return declarations.join(";");
386
+ }
387
+ /**
388
+ * Elements whose pixels are not in their child nodes. Returns true when the
389
+ * clone has been dealt with and must not be walked into.
390
+ */
391
+ function inlineReplacement(source, clone) {
392
+ if (source instanceof HTMLImageElement) {
393
+ const inlined = toDataUrl(source);
394
+ // No `src` at all beats a broken-image glyph where the picture was.
395
+ if (inlined)
396
+ clone.setAttribute("src", inlined);
397
+ return true;
398
+ }
399
+ if (source instanceof HTMLCanvasElement) {
400
+ // `clone` is an `<img>` standing in for the canvas; see `cloneStyled`.
401
+ const inlined = safeCanvasDataUrl(source);
402
+ if (inlined)
403
+ clone.setAttribute("src", inlined);
404
+ return true;
405
+ }
406
+ if (source instanceof HTMLInputElement ||
407
+ source instanceof HTMLTextAreaElement) {
408
+ // What the reviewer typed is often the point of the report, and a value
409
+ // set by script lives in the property, not the attribute.
410
+ if (source instanceof HTMLTextAreaElement) {
411
+ clone.textContent = source.value;
412
+ }
413
+ else if (source.type !== "password") {
414
+ clone.setAttribute("value", source.value);
415
+ clone.setAttribute("type", source.type);
416
+ }
417
+ return true;
418
+ }
419
+ return false;
420
+ }
421
+ function toDataUrl(image) {
422
+ if (image.currentSrc.startsWith("data:"))
423
+ return image.currentSrc;
424
+ if (!image.complete || image.naturalWidth === 0)
425
+ return null;
426
+ try {
427
+ const canvas = document.createElement("canvas");
428
+ canvas.width = image.naturalWidth;
429
+ canvas.height = image.naturalHeight;
430
+ const ctx = canvas.getContext("2d");
431
+ if (!ctx)
432
+ return null;
433
+ ctx.drawImage(image, 0, 0);
434
+ // Throws a SecurityError on a cross-origin image without CORS. That is the
435
+ // expected outcome for a third-party image, not an error worth surfacing.
436
+ return canvas.toDataURL("image/png");
437
+ }
438
+ catch {
439
+ return null;
440
+ }
441
+ }
442
+ function safeCanvasDataUrl(canvas) {
443
+ try {
444
+ return canvas.toDataURL("image/png");
445
+ }
446
+ catch {
447
+ return null;
448
+ }
449
+ }
450
+ /* ── Decoding ─────────────────────────────────────────────────────────── */
451
+ function decodeSvg(svg) {
452
+ return new Promise((resolve) => {
453
+ const image = new Image();
454
+ let settled = false;
455
+ const settle = (value) => {
456
+ if (settled)
457
+ return;
458
+ settled = true;
459
+ window.clearTimeout(timer);
460
+ resolve(value);
461
+ };
462
+ // A malformed clone makes the decode fail rather than throw, and an
463
+ // environment that never loads anything (jsdom, a blocked document) makes
464
+ // it do neither - hence the timeout as well as the error handler.
465
+ const timer = window.setTimeout(() => settle(null), DECODE_TIMEOUT_MS);
466
+ image.onload = () => settle(image);
467
+ image.onerror = () => settle(null);
468
+ image.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
469
+ });
470
+ }
471
+ /**
472
+ * The colour behind the page. `body` is usually transparent and the paint is
473
+ * on `html`, or on neither, in which case the browser's canvas is white.
474
+ */
475
+ function resolvePageBackground() {
476
+ for (const element of [document.documentElement, document.body]) {
477
+ if (!element)
478
+ continue;
479
+ const colour = window
480
+ .getComputedStyle(element)
481
+ .getPropertyValue("background-color");
482
+ if (colour &&
483
+ colour !== "transparent" &&
484
+ !colour.includes("rgba(0, 0, 0, 0)")) {
485
+ return colour;
486
+ }
487
+ }
488
+ return "#ffffff";
489
+ }
490
+ //# sourceMappingURL=rasterise.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rasterise.js","sourceRoot":"","sources":["../src/rasterise.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Build a selector that resolves back to `el`. Returns null when no selector
3
+ * can be built, which the caller treats as "this element cannot be pinned"
4
+ * rather than storing an anchor that will not resolve.
5
+ */
6
+ export declare function buildSelector(el: Element): string | null;
7
+ /** Resolve a stored selector, or null when the element is no longer there. */
8
+ export declare function resolveSelector(selector: string): Element | null;
9
+ /** Where inside an element a click landed, as fractions of its box. */
10
+ export declare function offsetWithin(el: Element, clientX: number, clientY: number): {
11
+ offsetX: number;
12
+ offsetY: number;
13
+ };
14
+ /** Viewport position of a stored anchor, or null when it no longer resolves. */
15
+ export declare function locatePin(selector: string, offsetX: number, offsetY: number): {
16
+ x: number;
17
+ y: number;
18
+ } | null;
19
+ //# sourceMappingURL=selector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selector.d.ts","sourceRoot":"","sources":["../src/selector.ts"],"names":[],"mappings":"AAiDA;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,EAAE,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAsCxD;AAwBD,8EAA8E;AAC9E,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAMhE;AAED,uEAAuE;AACvE,wBAAgB,YAAY,CAC1B,EAAE,EAAE,OAAO,EACX,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,GACd;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAUtC;AAED,gFAAgF;AAChF,wBAAgB,SAAS,CACvB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,GACd;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CASjC"}
@@ -0,0 +1,142 @@
1
+ // Turning an element into something that still finds it after a reload.
2
+ //
3
+ // This is the part of pinning that decides whether a pin survives, so it is a
4
+ // pure DOM function with no React in it and its own tests. The strategy, in
5
+ // order of how much it can be trusted:
6
+ //
7
+ // 1. `id`, when there is one and it looks stable.
8
+ // 2. `data-testid`, which a team puts on things it intends to keep.
9
+ // 3. A path of tag + nth-of-type steps up to the nearest anchor above.
10
+ //
11
+ // Class names are deliberately not used. A build that hashes CSS module names
12
+ // changes every class on every deploy, and a pin anchored to `.css-1x2y3z`
13
+ // would break on the next commit, which is precisely the pin that matters.
14
+ /** Attributes considered stable enough to anchor to, most trusted first. */
15
+ const STABLE_ATTRIBUTES = ["data-testid", "data-test-id", "data-qa"];
16
+ /** An id a bundler generated is not an anchor, however unique it is. */
17
+ function looksGenerated(value) {
18
+ // Long hex-ish or random-looking runs: React's useId (":r1:"), CSS modules,
19
+ // and most component libraries' generated ids.
20
+ return (value.length > 40 ||
21
+ /^:r[0-9a-z]+:$/i.test(value) ||
22
+ /^[0-9a-f]{16,}$/i.test(value) ||
23
+ /^(radix|headlessui|mui|chakra)-/i.test(value));
24
+ }
25
+ function escapeIdent(value) {
26
+ // CSS.escape exists everywhere the toolbar runs, but jsdom in older setups
27
+ // and a stray non-browser import should not throw.
28
+ const cssEscape = globalThis
29
+ .CSS?.escape;
30
+ return cssEscape ? cssEscape(value) : value.replace(/[^\w-]/g, "\\$&");
31
+ }
32
+ /** The step that identifies one element among its siblings. */
33
+ function nthOfTypeStep(el) {
34
+ const tag = el.tagName.toLowerCase();
35
+ const parent = el.parentElement;
36
+ if (!parent)
37
+ return tag;
38
+ const sameTag = Array.from(parent.children).filter((c) => c.tagName === el.tagName);
39
+ if (sameTag.length === 1)
40
+ return tag;
41
+ return `${tag}:nth-of-type(${sameTag.indexOf(el) + 1})`;
42
+ }
43
+ /**
44
+ * Build a selector that resolves back to `el`. Returns null when no selector
45
+ * can be built, which the caller treats as "this element cannot be pinned"
46
+ * rather than storing an anchor that will not resolve.
47
+ */
48
+ export function buildSelector(el) {
49
+ if (el.id && !looksGenerated(el.id)) {
50
+ const candidate = `#${escapeIdent(el.id)}`;
51
+ if (resolvesUniquely(candidate, el))
52
+ return candidate;
53
+ }
54
+ for (const attr of STABLE_ATTRIBUTES) {
55
+ const value = el.getAttribute(attr);
56
+ if (value) {
57
+ const candidate = `[${attr}="${cssStringEscape(value)}"]`;
58
+ if (resolvesUniquely(candidate, el))
59
+ return candidate;
60
+ }
61
+ }
62
+ // Walk up, prepending steps, and stop as soon as the path is unique. Anchor
63
+ // on an id or test id above if one turns up, which keeps the selector short
64
+ // and unaffected by anything that changes outside that subtree.
65
+ const steps = [];
66
+ let current = el;
67
+ while (current && current !== document.documentElement) {
68
+ steps.unshift(nthOfTypeStep(current));
69
+ const parent = current.parentElement;
70
+ if (parent) {
71
+ const anchor = anchorFor(parent);
72
+ if (anchor) {
73
+ const candidate = `${anchor} > ${steps.join(" > ")}`;
74
+ if (resolvesUniquely(candidate, el))
75
+ return candidate;
76
+ }
77
+ }
78
+ const candidate = steps.join(" > ");
79
+ if (resolvesUniquely(candidate, el))
80
+ return candidate;
81
+ current = parent;
82
+ }
83
+ const full = steps.join(" > ");
84
+ return resolvesUniquely(full, el) ? full : null;
85
+ }
86
+ function anchorFor(el) {
87
+ if (el.id && !looksGenerated(el.id))
88
+ return `#${escapeIdent(el.id)}`;
89
+ for (const attr of STABLE_ATTRIBUTES) {
90
+ const value = el.getAttribute(attr);
91
+ if (value)
92
+ return `[${attr}="${cssStringEscape(value)}"]`;
93
+ }
94
+ return null;
95
+ }
96
+ function cssStringEscape(value) {
97
+ return value.replace(/["\\]/g, "\\$&");
98
+ }
99
+ function resolvesUniquely(selector, el) {
100
+ try {
101
+ const found = document.querySelectorAll(selector);
102
+ return found.length === 1 && found[0] === el;
103
+ }
104
+ catch {
105
+ return false;
106
+ }
107
+ }
108
+ /** Resolve a stored selector, or null when the element is no longer there. */
109
+ export function resolveSelector(selector) {
110
+ try {
111
+ return document.querySelector(selector);
112
+ }
113
+ catch {
114
+ return null;
115
+ }
116
+ }
117
+ /** Where inside an element a click landed, as fractions of its box. */
118
+ export function offsetWithin(el, clientX, clientY) {
119
+ const rect = el.getBoundingClientRect();
120
+ // Fractions rather than pixels so the dot stays where the reviewer put it
121
+ // when the element is a different size on a different viewport.
122
+ const offsetX = rect.width > 0 ? (clientX - rect.left) / rect.width : 0.5;
123
+ const offsetY = rect.height > 0 ? (clientY - rect.top) / rect.height : 0.5;
124
+ return {
125
+ offsetX: Math.min(1, Math.max(0, offsetX)),
126
+ offsetY: Math.min(1, Math.max(0, offsetY)),
127
+ };
128
+ }
129
+ /** Viewport position of a stored anchor, or null when it no longer resolves. */
130
+ export function locatePin(selector, offsetX, offsetY) {
131
+ const el = resolveSelector(selector);
132
+ if (!el)
133
+ return null;
134
+ const rect = el.getBoundingClientRect();
135
+ if (rect.width === 0 && rect.height === 0)
136
+ return null;
137
+ return {
138
+ x: rect.left + rect.width * offsetX,
139
+ y: rect.top + rect.height * offsetY,
140
+ };
141
+ }
142
+ //# sourceMappingURL=selector.js.map