react-grab 0.0.29 → 0.0.30

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/dist/index.cjs CHANGED
@@ -2,8 +2,10 @@
2
2
 
3
3
  var web = require('solid-js/web');
4
4
  var solidJs = require('solid-js');
5
+ var modernScreenshot = require('modern-screenshot');
5
6
  var bippy = require('bippy');
6
7
  var source = require('bippy/dist/source');
8
+ var finder = require('@medv/finder');
7
9
 
8
10
  /**
9
11
  * @license MIT
@@ -96,6 +98,7 @@ var VIEWPORT_MARGIN_PX = 8;
96
98
  var INDICATOR_CLAMP_PADDING_PX = 4;
97
99
  var CURSOR_OFFSET_PX = 14;
98
100
  var SELECTION_LERP_FACTOR = 0.95;
101
+ var SUCCESS_LABEL_DURATION_MS = 1700;
99
102
 
100
103
  // src/utils/lerp.ts
101
104
  var lerp = (start, end, factor) => {
@@ -258,9 +261,9 @@ var getClampedElementPosition = (positionLeft, positionTop, elementWidth, elemen
258
261
  // src/components/label.tsx
259
262
  var _tmpl$3 = /* @__PURE__ */ web.template(`<span style=display:inline-block;margin-right:4px;font-weight:600>\u2713`);
260
263
  var _tmpl$22 = /* @__PURE__ */ web.template(`<div style=margin-right:4px>Copied`);
261
- var _tmpl$32 = /* @__PURE__ */ web.template(`<div style=margin-left:4px>to clipboard`);
262
- var _tmpl$4 = /* @__PURE__ */ web.template(`<div style="position:fixed;padding:2px 6px;background-color:#fde7f7;color:#b21c8e;border:1px solid #f7c5ec;border-radius:4px;font-size:11px;font-weight:500;font-family:-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;pointer-events:none;transition:opacity 0.2s ease-in-out;display:flex;align-items:center;max-width:calc(100vw - (16px + env(safe-area-inset-left) + env(safe-area-inset-right)));overflow:hidden;text-overflow:ellipsis;white-space:nowrap">`);
263
- var _tmpl$5 = /* @__PURE__ */ web.template(`<span style="font-family:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;font-variant-numeric:tabular-nums">`);
264
+ var _tmpl$32 = /* @__PURE__ */ web.template(`<span style="font-family:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;font-variant-numeric:tabular-nums">`);
265
+ var _tmpl$4 = /* @__PURE__ */ web.template(`<div style=margin-left:4px>to clipboard`);
266
+ var _tmpl$5 = /* @__PURE__ */ web.template(`<div style="position:fixed;padding:2px 6px;background-color:#fde7f7;color:#b21c8e;border:1px solid #f7c5ec;border-radius:4px;font-size:11px;font-weight:500;font-family:-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;pointer-events:none;transition:opacity 0.2s ease-in-out;display:flex;align-items:center;max-width:calc(100vw - (16px + env(safe-area-inset-left) + env(safe-area-inset-right)));overflow:hidden;text-overflow:ellipsis;white-space:nowrap">`);
264
267
  var Label = (props) => {
265
268
  const [opacity, setOpacity] = solidJs.createSignal(0);
266
269
  const [positionTick, setPositionTick] = solidJs.createSignal(0);
@@ -310,7 +313,7 @@ var Label = (props) => {
310
313
  if (props.variant === "success") {
311
314
  const fadeOutTimer = setTimeout(() => {
312
315
  setOpacity(0);
313
- }, 1500);
316
+ }, SUCCESS_LABEL_DURATION_MS);
314
317
  solidJs.onCleanup(() => clearTimeout(fadeOutTimer));
315
318
  }
316
319
  }));
@@ -331,34 +334,20 @@ var Label = (props) => {
331
334
  left: currentX,
332
335
  top: currentY
333
336
  };
334
- if (props.variant === "success") {
335
- const indicatorLeft = Math.round(currentX);
336
- const indicatorTop = Math.round(currentY) - boundingRect.height - 6;
337
- const willClampLeft = indicatorLeft < VIEWPORT_MARGIN_PX;
338
- const willClampTop = indicatorTop < VIEWPORT_MARGIN_PX;
339
- const isClamped = willClampLeft || willClampTop;
340
- const clamped = getClampedElementPosition(indicatorLeft, indicatorTop, boundingRect.width, boundingRect.height);
341
- if (isClamped) {
342
- clamped.left += INDICATOR_CLAMP_PADDING_PX;
343
- clamped.top += INDICATOR_CLAMP_PADDING_PX;
344
- }
345
- return clamped;
346
- }
347
- const CROSSHAIR_OFFSET = 12;
348
337
  const viewportWidth = window.innerWidth;
349
338
  const viewportHeight = window.innerHeight;
350
339
  const quadrants = [{
351
- left: Math.round(currentX) + CROSSHAIR_OFFSET,
352
- top: Math.round(currentY) + CROSSHAIR_OFFSET
340
+ left: Math.round(currentX) + CURSOR_OFFSET_PX,
341
+ top: Math.round(currentY) + CURSOR_OFFSET_PX
353
342
  }, {
354
- left: Math.round(currentX) - boundingRect.width - CROSSHAIR_OFFSET,
355
- top: Math.round(currentY) + CROSSHAIR_OFFSET
343
+ left: Math.round(currentX) - boundingRect.width - CURSOR_OFFSET_PX,
344
+ top: Math.round(currentY) + CURSOR_OFFSET_PX
356
345
  }, {
357
- left: Math.round(currentX) + CROSSHAIR_OFFSET,
358
- top: Math.round(currentY) - boundingRect.height - CROSSHAIR_OFFSET
346
+ left: Math.round(currentX) + CURSOR_OFFSET_PX,
347
+ top: Math.round(currentY) - boundingRect.height - CURSOR_OFFSET_PX
359
348
  }, {
360
- left: Math.round(currentX) - boundingRect.width - CROSSHAIR_OFFSET,
361
- top: Math.round(currentY) - boundingRect.height - CROSSHAIR_OFFSET
349
+ left: Math.round(currentX) - boundingRect.width - CURSOR_OFFSET_PX,
350
+ top: Math.round(currentY) - boundingRect.height - CURSOR_OFFSET_PX
362
351
  }];
363
352
  for (const position of quadrants) {
364
353
  const fitsHorizontally = position.left >= VIEWPORT_MARGIN_PX && position.left + boundingRect.width <= viewportWidth - VIEWPORT_MARGIN_PX;
@@ -377,7 +366,7 @@ var Label = (props) => {
377
366
  return props.visible !== false;
378
367
  },
379
368
  get children() {
380
- var _el$ = _tmpl$4();
369
+ var _el$ = _tmpl$5();
381
370
  var _ref$ = labelRef;
382
371
  typeof _ref$ === "function" ? web.use(_ref$, _el$) : labelRef = _el$;
383
372
  web.insert(_el$, web.createComponent(solidJs.Show, {
@@ -412,17 +401,12 @@ var Label = (props) => {
412
401
  }), null);
413
402
  web.insert(_el$, web.createComponent(solidJs.Show, {
414
403
  get when() {
415
- return props.text.startsWith("(");
416
- },
417
- get fallback() {
418
- return (() => {
419
- var _el$5 = _tmpl$5();
420
- web.insert(_el$5, () => props.text);
421
- return _el$5;
422
- })();
404
+ return props.variant !== "processing";
423
405
  },
424
406
  get children() {
425
- return props.text;
407
+ var _el$4 = _tmpl$32();
408
+ web.insert(_el$4, () => props.text);
409
+ return _el$4;
426
410
  }
427
411
  }), null);
428
412
  web.insert(_el$, web.createComponent(solidJs.Show, {
@@ -430,7 +414,7 @@ var Label = (props) => {
430
414
  return props.variant === "success";
431
415
  },
432
416
  get children() {
433
- return _tmpl$32();
417
+ return _tmpl$4();
434
418
  }
435
419
  }), null);
436
420
  web.effect((_p$) => {
@@ -530,53 +514,16 @@ var Crosshair = (props) => {
530
514
  context.scale(dpr, dpr);
531
515
  }
532
516
  };
533
- const getSmallestElementBounds = (x, y) => {
534
- const element = document.elementFromPoint(x, y);
535
- if (!element || element === canvasRef || element === document.body || element === document.documentElement) {
536
- return null;
537
- }
538
- const rect = element.getBoundingClientRect();
539
- if (rect.width === 0 || rect.height === 0) {
540
- return null;
541
- }
542
- return {
543
- top: rect.top,
544
- bottom: rect.bottom,
545
- left: rect.left,
546
- right: rect.right
547
- };
548
- };
549
- const scanBoundary = (startX, startY, deltaX, deltaY, maxDistance) => {
550
- const baseBounds = getSmallestElementBounds(startX, startY);
551
- if (!baseBounds) return maxDistance;
552
- const stepSize = 5;
553
- for (let distance = stepSize; distance < maxDistance; distance += stepSize) {
554
- const checkX = startX + deltaX * distance;
555
- const checkY = startY + deltaY * distance;
556
- if (checkX < 0 || checkX >= width || checkY < 0 || checkY >= height) {
557
- return distance;
558
- }
559
- const currentBounds = getSmallestElementBounds(checkX, checkY);
560
- if (!currentBounds || currentBounds.top !== baseBounds.top || currentBounds.bottom !== baseBounds.bottom || currentBounds.left !== baseBounds.left || currentBounds.right !== baseBounds.right) {
561
- return distance;
562
- }
563
- }
564
- return maxDistance;
565
- };
566
517
  const render2 = () => {
567
518
  if (!context) return;
568
519
  context.clearRect(0, 0, width, height);
569
520
  context.strokeStyle = "rgba(210, 57, 192)";
570
521
  context.lineWidth = 1;
571
522
  context.beginPath();
572
- const topBoundary = scanBoundary(currentX, currentY, 0, -1, currentY);
573
- const bottomBoundary = scanBoundary(currentX, currentY, 0, 1, height - currentY);
574
- const leftBoundary = scanBoundary(currentX, currentY, -1, 0, currentX);
575
- const rightBoundary = scanBoundary(currentX, currentY, 1, 0, width - currentX);
576
- context.moveTo(currentX, currentY - topBoundary);
577
- context.lineTo(currentX, currentY + bottomBoundary);
578
- context.moveTo(currentX - leftBoundary, currentY);
579
- context.lineTo(currentX + rightBoundary, currentY);
523
+ context.moveTo(currentX, 0);
524
+ context.lineTo(currentX, height);
525
+ context.moveTo(0, currentY);
526
+ context.lineTo(width, currentY);
580
527
  context.stroke();
581
528
  };
582
529
  const animate = () => {
@@ -767,26 +714,43 @@ bippy.instrument({
767
714
  bippy._fiberRoots.add(fiberRoot);
768
715
  }
769
716
  });
770
- var getSourceTrace = async (element) => {
771
- const fiber = bippy.getFiberFromHostInstance(element);
772
- if (!fiber) return null;
773
- const ownerStack = source.getOwnerStack(fiber);
774
- const sources = await source.getSourcesFromStack(
775
- ownerStack,
776
- Number.MAX_SAFE_INTEGER
777
- );
778
- if (!sources) return null;
779
- console.log(sources);
780
- return sources.map((source$1) => {
781
- return {
782
- ...source$1,
783
- fileName: source.normalizeFileName(source$1.fileName)
784
- };
785
- }).filter((source$1) => {
786
- return source.isSourceFile(source$1.fileName);
787
- });
717
+ var generateCSSSelector = (element) => {
718
+ return finder.finder(element);
788
719
  };
789
- var getHTMLSnippet = (element) => {
720
+ var getHTMLSnippet = async (element) => {
721
+ const truncateString = (string, maxLength) => string.length > maxLength ? `${string.substring(0, maxLength)}...` : string;
722
+ const isInternalComponent = (name) => {
723
+ if (name.startsWith("_")) return true;
724
+ if (name.includes("Provider") && name.includes("Context")) return true;
725
+ return false;
726
+ };
727
+ const extractReactComponentName = (el) => {
728
+ const fiber = bippy.getFiberFromHostInstance(el);
729
+ if (!fiber) return null;
730
+ let componentName = null;
731
+ bippy.traverseFiber(
732
+ fiber,
733
+ (currentFiber) => {
734
+ if (bippy.isCompositeFiber(currentFiber)) {
735
+ const displayName = bippy.getDisplayName(currentFiber);
736
+ if (displayName && !isInternalComponent(displayName)) {
737
+ componentName = displayName;
738
+ return true;
739
+ }
740
+ }
741
+ return false;
742
+ },
743
+ true
744
+ );
745
+ return componentName;
746
+ };
747
+ const formatComponentSourceLocation = async (el) => {
748
+ const source$1 = await source.getSourceFromHostInstance(el);
749
+ if (!source$1) return null;
750
+ const fileName = source.normalizeFileName(source$1.fileName);
751
+ if (!source.isSourceFile(fileName)) return null;
752
+ return `${fileName}:${source$1.lineNumber}:${source$1.columnNumber}`;
753
+ };
790
754
  const semanticTags = /* @__PURE__ */ new Set([
791
755
  "article",
792
756
  "aside",
@@ -809,7 +773,7 @@ var getHTMLSnippet = (element) => {
809
773
  (attr) => attr.name.startsWith("data-")
810
774
  );
811
775
  };
812
- const getAncestorChain = (el, maxDepth = 10) => {
776
+ const collectDistinguishingAncestors = (el, maxDepth = 10) => {
813
777
  const ancestors2 = [];
814
778
  let current = el.parentElement;
815
779
  let depth = 0;
@@ -823,35 +787,7 @@ var getHTMLSnippet = (element) => {
823
787
  }
824
788
  return ancestors2.reverse();
825
789
  };
826
- const getCSSPath = (el) => {
827
- const parts = [];
828
- let current = el;
829
- let depth = 0;
830
- const maxDepth = 5;
831
- while (current && depth < maxDepth && current.tagName !== "BODY") {
832
- let selector = current.tagName.toLowerCase();
833
- if (current.id) {
834
- selector += `#${current.id}`;
835
- parts.unshift(selector);
836
- break;
837
- } else if (current.className && typeof current.className === "string" && current.className.trim()) {
838
- const classes = current.className.trim().split(/\s+/).slice(0, 2);
839
- selector += `.${classes.join(".")}`;
840
- }
841
- if (!current.id && (!current.className || !current.className.trim()) && current.parentElement) {
842
- const siblings = Array.from(current.parentElement.children);
843
- const index = siblings.indexOf(current);
844
- if (index >= 0 && siblings.length > 1) {
845
- selector += `:nth-child(${index + 1})`;
846
- }
847
- }
848
- parts.unshift(selector);
849
- current = current.parentElement;
850
- depth++;
851
- }
852
- return parts.join(" > ");
853
- };
854
- const getElementTag = (el, compact = false) => {
790
+ const formatElementOpeningTag = (el, compact = false) => {
855
791
  const tagName = el.tagName.toLowerCase();
856
792
  const attrs = [];
857
793
  if (el.id) {
@@ -861,10 +797,7 @@ var getHTMLSnippet = (element) => {
861
797
  const classes = el.className.trim().split(/\s+/);
862
798
  if (classes.length > 0 && classes[0]) {
863
799
  const displayClasses = compact ? classes.slice(0, 3) : classes;
864
- let classStr = displayClasses.join(" ");
865
- if (classStr.length > 30) {
866
- classStr = classStr.substring(0, 30) + "...";
867
- }
800
+ const classStr = truncateString(displayClasses.join(" "), 30);
868
801
  attrs.push(`class="${classStr}"`);
869
802
  }
870
803
  }
@@ -873,35 +806,20 @@ var getHTMLSnippet = (element) => {
873
806
  );
874
807
  const displayDataAttrs = compact ? dataAttrs.slice(0, 1) : dataAttrs;
875
808
  for (const attr of displayDataAttrs) {
876
- let value = attr.value;
877
- if (value.length > 20) {
878
- value = value.substring(0, 20) + "...";
879
- }
880
- attrs.push(`${attr.name}="${value}"`);
809
+ attrs.push(`${attr.name}="${truncateString(attr.value, 20)}"`);
881
810
  }
882
811
  const ariaLabel = el.getAttribute("aria-label");
883
812
  if (ariaLabel && !compact) {
884
- let value = ariaLabel;
885
- if (value.length > 20) {
886
- value = value.substring(0, 20) + "...";
887
- }
888
- attrs.push(`aria-label="${value}"`);
813
+ attrs.push(`aria-label="${truncateString(ariaLabel, 20)}"`);
889
814
  }
890
815
  return attrs.length > 0 ? `<${tagName} ${attrs.join(" ")}>` : `<${tagName}>`;
891
816
  };
892
- const getClosingTag = (el) => {
893
- return `</${el.tagName.toLowerCase()}>`;
894
- };
895
- const getTextContent = (el) => {
896
- let text = el.textContent || "";
897
- text = text.trim().replace(/\s+/g, " ");
898
- const maxLength = 60;
899
- if (text.length > maxLength) {
900
- text = text.substring(0, maxLength) + "...";
901
- }
902
- return text;
817
+ const formatElementClosingTag = (el) => `</${el.tagName.toLowerCase()}>`;
818
+ const extractTruncatedTextContent = (el) => {
819
+ const text = (el.textContent || "").trim().replace(/\s+/g, " ");
820
+ return truncateString(text, 60);
903
821
  };
904
- const getSiblingIdentifier = (el) => {
822
+ const extractSiblingIdentifier = (el) => {
905
823
  if (el.id) return `#${el.id}`;
906
824
  if (el.className && typeof el.className === "string") {
907
825
  const classes = el.className.trim().split(/\s+/);
@@ -912,12 +830,31 @@ var getHTMLSnippet = (element) => {
912
830
  return null;
913
831
  };
914
832
  const lines = [];
915
- lines.push(`Path: ${getCSSPath(element)}`);
916
- lines.push("");
917
- const ancestors = getAncestorChain(element);
833
+ const selector = generateCSSSelector(element);
834
+ lines.push(`Locate this element in the codebase:`);
835
+ lines.push(`- selector: ${selector}`);
836
+ const rect = element.getBoundingClientRect();
837
+ lines.push(`- width: ${Math.round(rect.width)}`);
838
+ lines.push(`- height: ${Math.round(rect.height)}`);
839
+ lines.push("HTML snippet:");
840
+ lines.push("```html");
841
+ const ancestors = collectDistinguishingAncestors(element);
842
+ const ancestorComponents = ancestors.map(
843
+ (ancestor) => extractReactComponentName(ancestor)
844
+ );
845
+ const elementComponent = extractReactComponentName(element);
846
+ const ancestorSources = await Promise.all(
847
+ ancestors.map((ancestor) => formatComponentSourceLocation(ancestor))
848
+ );
849
+ const elementSource = await formatComponentSourceLocation(element);
918
850
  for (let i = 0; i < ancestors.length; i++) {
919
851
  const indent2 = " ".repeat(i);
920
- lines.push(indent2 + getElementTag(ancestors[i], true));
852
+ const componentName = ancestorComponents[i];
853
+ const source = ancestorSources[i];
854
+ if (componentName && source && (i === 0 || ancestorComponents[i - 1] !== componentName)) {
855
+ lines.push(`${indent2}<${componentName} source="${source}">`);
856
+ }
857
+ lines.push(`${indent2}${formatElementOpeningTag(ancestors[i], true)}`);
921
858
  }
922
859
  const parent = element.parentElement;
923
860
  let targetIndex = -1;
@@ -926,10 +863,10 @@ var getHTMLSnippet = (element) => {
926
863
  targetIndex = siblings.indexOf(element);
927
864
  if (targetIndex > 0) {
928
865
  const prevSibling = siblings[targetIndex - 1];
929
- const prevId = getSiblingIdentifier(prevSibling);
866
+ const prevId = extractSiblingIdentifier(prevSibling);
930
867
  if (prevId && targetIndex <= 2) {
931
868
  const indent2 = " ".repeat(ancestors.length);
932
- lines.push(`${indent2} ${getElementTag(prevSibling, true)}`);
869
+ lines.push(`${indent2} ${formatElementOpeningTag(prevSibling, true)}`);
933
870
  lines.push(`${indent2} </${prevSibling.tagName.toLowerCase()}>`);
934
871
  } else if (targetIndex > 0) {
935
872
  const indent2 = " ".repeat(ancestors.length);
@@ -940,35 +877,44 @@ var getHTMLSnippet = (element) => {
940
877
  }
941
878
  }
942
879
  const indent = " ".repeat(ancestors.length);
943
- lines.push(indent + " <!-- SELECTED -->");
944
- const textContent = getTextContent(element);
880
+ const lastAncestorComponent = ancestors.length > 0 ? ancestorComponents[ancestorComponents.length - 1] : null;
881
+ const showElementComponent = elementComponent && elementSource && elementComponent !== lastAncestorComponent;
882
+ if (showElementComponent) {
883
+ lines.push(`${indent} <${elementComponent} used-at="${elementSource}">`);
884
+ }
885
+ lines.push(`${indent} <!-- IMPORTANT: selected element -->`);
886
+ const textContent = extractTruncatedTextContent(element);
945
887
  const childrenCount = element.children.length;
888
+ const elementIndent = `${indent}${showElementComponent ? " " : " "}`;
946
889
  if (textContent && childrenCount === 0 && textContent.length < 40) {
947
890
  lines.push(
948
- `${indent} ${getElementTag(element)}${textContent}${getClosingTag(
891
+ `${elementIndent}${formatElementOpeningTag(element)}${textContent}${formatElementClosingTag(
949
892
  element
950
893
  )}`
951
894
  );
952
895
  } else {
953
- lines.push(indent + " " + getElementTag(element));
896
+ lines.push(`${elementIndent}${formatElementOpeningTag(element)}`);
954
897
  if (textContent) {
955
- lines.push(`${indent} ${textContent}`);
898
+ lines.push(`${elementIndent} ${textContent}`);
956
899
  }
957
900
  if (childrenCount > 0) {
958
901
  lines.push(
959
- `${indent} ... (${childrenCount} element${childrenCount === 1 ? "" : "s"})`
902
+ `${elementIndent} ... (${childrenCount} element${childrenCount === 1 ? "" : "s"})`
960
903
  );
961
904
  }
962
- lines.push(indent + " " + getClosingTag(element));
905
+ lines.push(`${elementIndent}${formatElementClosingTag(element)}`);
906
+ }
907
+ if (showElementComponent) {
908
+ lines.push(`${indent} </${elementComponent}>`);
963
909
  }
964
910
  if (parent && targetIndex >= 0) {
965
911
  const siblings = Array.from(parent.children);
966
912
  const siblingsAfter = siblings.length - targetIndex - 1;
967
913
  if (siblingsAfter > 0) {
968
914
  const nextSibling = siblings[targetIndex + 1];
969
- const nextId = getSiblingIdentifier(nextSibling);
915
+ const nextId = extractSiblingIdentifier(nextSibling);
970
916
  if (nextId && siblingsAfter <= 2) {
971
- lines.push(`${indent} ${getElementTag(nextSibling, true)}`);
917
+ lines.push(`${indent} ${formatElementOpeningTag(nextSibling, true)}`);
972
918
  lines.push(`${indent} </${nextSibling.tagName.toLowerCase()}>`);
973
919
  } else {
974
920
  lines.push(
@@ -979,8 +925,14 @@ var getHTMLSnippet = (element) => {
979
925
  }
980
926
  for (let i = ancestors.length - 1; i >= 0; i--) {
981
927
  const indent2 = " ".repeat(i);
982
- lines.push(indent2 + getClosingTag(ancestors[i]));
928
+ lines.push(`${indent2}${formatElementClosingTag(ancestors[i])}`);
929
+ const componentName = ancestorComponents[i];
930
+ const source = ancestorSources[i];
931
+ if (componentName && source && (i === ancestors.length - 1 || ancestorComponents[i + 1] !== componentName)) {
932
+ lines.push(`${indent2}</${componentName}>`);
933
+ }
983
934
  }
935
+ lines.push("```");
984
936
  return lines.join("\n");
985
937
  };
986
938
 
@@ -1210,8 +1162,8 @@ var createElementBounds = (element) => {
1210
1162
  };
1211
1163
 
1212
1164
  // src/core.tsx
1213
- var SUCCESS_LABEL_DURATION_MS = 1700;
1214
1165
  var PROGRESS_INDICATOR_DELAY_MS = 150;
1166
+ var QUICK_REPRESS_THRESHOLD_MS = 150;
1215
1167
  var init = (rawOptions) => {
1216
1168
  const options = {
1217
1169
  enabled: true,
@@ -1237,14 +1189,19 @@ var init = (rawOptions) => {
1237
1189
  const [successLabels, setSuccessLabels] = solidJs.createSignal([]);
1238
1190
  const [isActivated, setIsActivated] = solidJs.createSignal(false);
1239
1191
  const [showProgressIndicator, setShowProgressIndicator] = solidJs.createSignal(false);
1192
+ const [didJustDrag, setDidJustDrag] = solidJs.createSignal(false);
1193
+ const [isModifierHeld, setIsModifierHeld] = solidJs.createSignal(false);
1194
+ const [copyStartX, setCopyStartX] = solidJs.createSignal(OFFSCREEN_POSITION);
1195
+ const [copyStartY, setCopyStartY] = solidJs.createSignal(OFFSCREEN_POSITION);
1240
1196
  let holdTimerId = null;
1241
1197
  let progressAnimationId = null;
1242
1198
  let progressDelayTimerId = null;
1243
1199
  let keydownSpamTimerId = null;
1200
+ let lastDeactivationTime = null;
1244
1201
  const isRendererActive = solidJs.createMemo(() => isActivated() && !isCopying());
1245
1202
  const hasValidMousePosition = solidJs.createMemo(() => mouseX() > OFFSCREEN_POSITION && mouseY() > OFFSCREEN_POSITION);
1246
1203
  const isTargetKeyCombination = (event) => (event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "c";
1247
- const addGrabbedBox = (bounds) => {
1204
+ const showTemporaryGrabbedBox = (bounds) => {
1248
1205
  const boxId = `grabbed-${Date.now()}-${Math.random()}`;
1249
1206
  const createdAt = Date.now();
1250
1207
  const newBox = {
@@ -1258,7 +1215,7 @@ var init = (rawOptions) => {
1258
1215
  setGrabbedBoxes((previousBoxes) => previousBoxes.filter((box) => box.id !== boxId));
1259
1216
  }, SUCCESS_LABEL_DURATION_MS);
1260
1217
  };
1261
- const addSuccessLabel = (text, positionX, positionY) => {
1218
+ const showTemporarySuccessLabel = (text, positionX, positionY) => {
1262
1219
  const labelId = `success-${Date.now()}-${Math.random()}`;
1263
1220
  setSuccessLabels((previousLabels) => [...previousLabels, {
1264
1221
  id: labelId,
@@ -1270,24 +1227,15 @@ var init = (rawOptions) => {
1270
1227
  setSuccessLabels((previousLabels) => previousLabels.filter((label) => label.id !== labelId));
1271
1228
  }, SUCCESS_LABEL_DURATION_MS);
1272
1229
  };
1273
- const formatStackTrace = (stackTrace) => {
1274
- return stackTrace.map((source) => {
1275
- const functionName = source.functionName ?? "anonymous";
1276
- const fileName = source.fileName ?? "unknown";
1277
- const lineNumber = source.lineNumber ?? 0;
1278
- const columnNumber = source.columnNumber ?? 0;
1279
- return ` at ${functionName} (${fileName}:${lineNumber}:${columnNumber})`;
1280
- }).join("\n");
1281
- };
1282
- const wrapContextInXmlTags = (context) => {
1283
- return `<selected_element>${context}</selected_element>`;
1284
- };
1285
- const getComputedStyles = (element) => {
1230
+ const wrapInSelectedElementTags = (context) => `<selected_element>
1231
+ ${context}
1232
+ </selected_element>`;
1233
+ const extractRelevantComputedStyles = (element) => {
1286
1234
  const computed = window.getComputedStyle(element);
1287
1235
  const rect = element.getBoundingClientRect();
1288
1236
  return {
1289
- width: `${rect.width}px`,
1290
- height: `${rect.height}px`,
1237
+ width: `${Math.round(rect.width)}px`,
1238
+ height: `${Math.round(rect.height)}px`,
1291
1239
  paddingTop: computed.paddingTop,
1292
1240
  paddingRight: computed.paddingRight,
1293
1241
  paddingBottom: computed.paddingBottom,
@@ -1296,7 +1244,7 @@ var init = (rawOptions) => {
1296
1244
  opacity: computed.opacity
1297
1245
  };
1298
1246
  };
1299
- const createStructuredClipboardHtml = (elements) => {
1247
+ const createStructuredClipboardHtmlBlob = (elements) => {
1300
1248
  const structuredData = {
1301
1249
  elements: elements.map((element) => ({
1302
1250
  tagName: element.tagName,
@@ -1310,60 +1258,74 @@ var init = (rawOptions) => {
1310
1258
  type: "text/html"
1311
1259
  });
1312
1260
  };
1313
- const getElementContentWithTrace = async (element) => {
1314
- const elementHtml = getHTMLSnippet(element);
1315
- const componentStackTrace = await getSourceTrace(element);
1316
- if (componentStackTrace?.length) {
1317
- const formattedStackTrace = formatStackTrace(componentStackTrace);
1318
- return `${elementHtml}
1319
-
1320
- Component owner stack:
1321
- ${formattedStackTrace}`;
1322
- }
1323
- return elementHtml;
1261
+ const extractElementTagName = (element) => (element.tagName || "").toLowerCase();
1262
+ const executeCopyOperation = async (positionX, positionY, operation) => {
1263
+ setCopyStartX(positionX);
1264
+ setCopyStartY(positionY);
1265
+ setIsCopying(true);
1266
+ await operation().finally(() => {
1267
+ setIsCopying(false);
1268
+ });
1324
1269
  };
1325
- const getElementTagName = (element) => (element.tagName || "").toLowerCase();
1326
- const handleCopy = async (targetElement2) => {
1327
- const elementBounds = targetElement2.getBoundingClientRect();
1328
- const tagName = getElementTagName(targetElement2);
1329
- addGrabbedBox(createElementBounds(targetElement2));
1270
+ const copySingleElementToClipboard = async (targetElement2) => {
1271
+ const tagName = extractElementTagName(targetElement2);
1272
+ showTemporaryGrabbedBox(createElementBounds(targetElement2));
1330
1273
  try {
1331
- const content = await getElementContentWithTrace(targetElement2);
1332
- const plainTextContent = wrapContextInXmlTags(content);
1333
- const htmlContent = createStructuredClipboardHtml([{
1274
+ const content = await getHTMLSnippet(targetElement2);
1275
+ const plainTextContent = wrapInSelectedElementTags(content);
1276
+ const htmlContent = createStructuredClipboardHtmlBlob([{
1334
1277
  tagName,
1335
- content: await getElementContentWithTrace(targetElement2),
1336
- computedStyles: getComputedStyles(targetElement2)
1278
+ content,
1279
+ computedStyles: extractRelevantComputedStyles(targetElement2)
1337
1280
  }]);
1338
- await copyContent([plainTextContent, htmlContent]);
1281
+ const clipboardData = [plainTextContent, htmlContent];
1282
+ try {
1283
+ const screenshotDataUrl = await modernScreenshot.domToPng(targetElement2);
1284
+ const response = await fetch(screenshotDataUrl);
1285
+ const pngBlob = await response.blob();
1286
+ const imagePngBlob = new Blob([pngBlob], {
1287
+ type: "image/png"
1288
+ });
1289
+ clipboardData.push(imagePngBlob);
1290
+ } catch {
1291
+ }
1292
+ await copyContent(clipboardData);
1339
1293
  } catch {
1340
1294
  }
1341
- addSuccessLabel(tagName ? `<${tagName}>` : "<element>", elementBounds.left, elementBounds.top);
1295
+ showTemporarySuccessLabel(tagName ? `<${tagName}>` : "<element>", copyStartX(), copyStartY());
1342
1296
  };
1343
- const handleMultipleCopy = async (targetElements) => {
1297
+ const copyMultipleElementsToClipboard = async (targetElements) => {
1344
1298
  if (targetElements.length === 0) return;
1345
- let minPositionX = Infinity;
1346
- let minPositionY = Infinity;
1347
1299
  for (const element of targetElements) {
1348
- const elementBounds = element.getBoundingClientRect();
1349
- minPositionX = Math.min(minPositionX, elementBounds.left);
1350
- minPositionY = Math.min(minPositionY, elementBounds.top);
1351
- addGrabbedBox(createElementBounds(element));
1300
+ showTemporaryGrabbedBox(createElementBounds(element));
1352
1301
  }
1353
1302
  try {
1354
- const elementSnippets = await Promise.all(targetElements.map((element) => getElementContentWithTrace(element)));
1303
+ const elementSnippets = await Promise.all(targetElements.map((element) => getHTMLSnippet(element)));
1355
1304
  const combinedContent = elementSnippets.join("\n\n---\n\n");
1356
- const plainTextContent = wrapContextInXmlTags(combinedContent);
1357
- const structuredElements = await Promise.all(targetElements.map(async (element) => ({
1358
- tagName: getElementTagName(element),
1359
- content: await getElementContentWithTrace(element),
1360
- computedStyles: getComputedStyles(element)
1361
- })));
1362
- const htmlContent = createStructuredClipboardHtml(structuredElements);
1363
- await copyContent([plainTextContent, htmlContent]);
1305
+ const plainTextContent = wrapInSelectedElementTags(combinedContent);
1306
+ const structuredElements = elementSnippets.map((content, index) => ({
1307
+ tagName: extractElementTagName(targetElements[index]),
1308
+ content,
1309
+ computedStyles: extractRelevantComputedStyles(targetElements[index])
1310
+ }));
1311
+ const htmlContent = createStructuredClipboardHtmlBlob(structuredElements);
1312
+ const clipboardData = [plainTextContent, htmlContent];
1313
+ if (targetElements.length > 0) {
1314
+ try {
1315
+ const screenshotDataUrl = await modernScreenshot.domToPng(targetElements[0]);
1316
+ const response = await fetch(screenshotDataUrl);
1317
+ const pngBlob = await response.blob();
1318
+ const imagePngBlob = new Blob([pngBlob], {
1319
+ type: "image/png"
1320
+ });
1321
+ clipboardData.push(imagePngBlob);
1322
+ } catch {
1323
+ }
1324
+ }
1325
+ await copyContent(clipboardData);
1364
1326
  } catch {
1365
1327
  }
1366
- addSuccessLabel(`${targetElements.length} elements`, minPositionX, minPositionY);
1328
+ showTemporarySuccessLabel(`${targetElements.length} elements`, copyStartX(), copyStartY());
1367
1329
  };
1368
1330
  const targetElement = solidJs.createMemo(() => {
1369
1331
  if (!isRendererActive() || isDragging()) return null;
@@ -1384,16 +1346,16 @@ ${formattedStackTrace}`;
1384
1346
  };
1385
1347
  });
1386
1348
  const DRAG_THRESHOLD_PX = 2;
1387
- const getDragDistance = (endX, endY) => ({
1349
+ const calculateDragDistance = (endX, endY) => ({
1388
1350
  x: Math.abs(endX - dragStartX()),
1389
1351
  y: Math.abs(endY - dragStartY())
1390
1352
  });
1391
1353
  const isDraggingBeyondThreshold = solidJs.createMemo(() => {
1392
1354
  if (!isDragging()) return false;
1393
- const dragDistance = getDragDistance(mouseX(), mouseY());
1355
+ const dragDistance = calculateDragDistance(mouseX(), mouseY());
1394
1356
  return dragDistance.x > DRAG_THRESHOLD_PX || dragDistance.y > DRAG_THRESHOLD_PX;
1395
1357
  });
1396
- const getDragRect = (endX, endY) => {
1358
+ const calculateDragRectangle = (endX, endY) => {
1397
1359
  const dragX = Math.min(dragStartX(), endX);
1398
1360
  const dragY = Math.min(dragStartY(), endY);
1399
1361
  const dragWidth = Math.abs(endX - dragStartX());
@@ -1407,7 +1369,7 @@ ${formattedStackTrace}`;
1407
1369
  };
1408
1370
  const dragBounds = solidJs.createMemo(() => {
1409
1371
  if (!isDraggingBeyondThreshold()) return void 0;
1410
- const drag = getDragRect(mouseX(), mouseY());
1372
+ const drag = calculateDragRectangle(mouseX(), mouseY());
1411
1373
  return {
1412
1374
  borderRadius: "0px",
1413
1375
  height: drag.height,
@@ -1419,19 +1381,16 @@ ${formattedStackTrace}`;
1419
1381
  });
1420
1382
  const labelText = solidJs.createMemo(() => {
1421
1383
  const element = targetElement();
1422
- return element ? `<${getElementTagName(element)}>` : "<element>";
1384
+ return element ? `<${extractElementTagName(element)}>` : "<element>";
1423
1385
  });
1424
- const labelPosition = solidJs.createMemo(() => {
1425
- return {
1426
- x: mouseX(),
1427
- y: mouseY()
1428
- };
1429
- });
1430
- const isSameAsLast = solidJs.createMemo(() => {
1431
- const currentElement = targetElement();
1432
- const lastElement = lastGrabbedElement();
1433
- return Boolean(currentElement) && currentElement === lastElement;
1386
+ const labelPosition = solidJs.createMemo(() => isCopying() ? {
1387
+ x: copyStartX(),
1388
+ y: copyStartY()
1389
+ } : {
1390
+ x: mouseX(),
1391
+ y: mouseY()
1434
1392
  });
1393
+ const isSameAsLast = solidJs.createMemo(() => Boolean(targetElement() && targetElement() === lastGrabbedElement()));
1435
1394
  solidJs.createEffect(solidJs.on(() => [targetElement(), lastGrabbedElement()], ([currentElement, lastElement]) => {
1436
1395
  if (lastElement && currentElement && lastElement !== currentElement) {
1437
1396
  setLastGrabbedElement(null);
@@ -1478,9 +1437,12 @@ ${formattedStackTrace}`;
1478
1437
  setIsActivated(true);
1479
1438
  document.body.style.cursor = "crosshair";
1480
1439
  };
1481
- const deactivateRenderer = () => {
1440
+ const deactivateRenderer = (shouldResetModifier = true) => {
1482
1441
  setIsHoldingKeys(false);
1483
1442
  setIsActivated(false);
1443
+ if (shouldResetModifier) {
1444
+ setIsModifierHeld(false);
1445
+ }
1484
1446
  document.body.style.cursor = "";
1485
1447
  if (isDragging()) {
1486
1448
  setIsDragging(false);
@@ -1489,23 +1451,38 @@ ${formattedStackTrace}`;
1489
1451
  if (holdTimerId) window.clearTimeout(holdTimerId);
1490
1452
  if (keydownSpamTimerId) window.clearTimeout(keydownSpamTimerId);
1491
1453
  stopProgressAnimation();
1454
+ lastDeactivationTime = Date.now();
1492
1455
  };
1493
1456
  const abortController = new AbortController();
1494
1457
  const eventListenerSignal = abortController.signal;
1495
1458
  window.addEventListener("keydown", (event) => {
1459
+ if (event.metaKey || event.ctrlKey) {
1460
+ setIsModifierHeld(true);
1461
+ }
1496
1462
  if (event.key === "Escape" && isHoldingKeys()) {
1497
1463
  deactivateRenderer();
1498
1464
  return;
1499
1465
  }
1500
1466
  if (isKeyboardEventTriggeredByInput(event)) return;
1501
1467
  if (isTargetKeyCombination(event)) {
1468
+ const wasRecentlyDeactivated = lastDeactivationTime !== null && Date.now() - lastDeactivationTime < QUICK_REPRESS_THRESHOLD_MS;
1502
1469
  if (!isHoldingKeys()) {
1503
1470
  setIsHoldingKeys(true);
1504
- startProgressAnimation();
1505
- holdTimerId = window.setTimeout(() => {
1471
+ if (wasRecentlyDeactivated && isModifierHeld()) {
1506
1472
  activateRenderer();
1507
1473
  options.onActivate?.();
1508
- }, options.keyHoldDuration);
1474
+ const element = getElementAtPosition(mouseX(), mouseY());
1475
+ if (element) {
1476
+ setLastGrabbedElement(element);
1477
+ void executeCopyOperation(mouseX(), mouseY(), () => copySingleElementToClipboard(element));
1478
+ }
1479
+ } else {
1480
+ startProgressAnimation();
1481
+ holdTimerId = window.setTimeout(() => {
1482
+ activateRenderer();
1483
+ options.onActivate?.();
1484
+ }, options.keyHoldDuration);
1485
+ }
1509
1486
  }
1510
1487
  if (isActivated()) {
1511
1488
  if (keydownSpamTimerId) window.clearTimeout(keydownSpamTimerId);
@@ -1518,11 +1495,16 @@ ${formattedStackTrace}`;
1518
1495
  signal: eventListenerSignal
1519
1496
  });
1520
1497
  window.addEventListener("keyup", (event) => {
1521
- if (!isHoldingKeys() && !isActivated()) return;
1522
- const isReleasingC = event.key.toLowerCase() === "c";
1523
1498
  const isReleasingModifier = !event.metaKey && !event.ctrlKey;
1524
- if (isReleasingC || isReleasingModifier) {
1525
- deactivateRenderer();
1499
+ const isReleasingC = event.key.toLowerCase() === "c";
1500
+ if (isReleasingModifier) {
1501
+ setIsModifierHeld(false);
1502
+ }
1503
+ if (!isHoldingKeys() && !isActivated()) return;
1504
+ if (isReleasingC) {
1505
+ deactivateRenderer(false);
1506
+ } else if (isReleasingModifier) {
1507
+ deactivateRenderer(true);
1526
1508
  }
1527
1509
  }, {
1528
1510
  signal: eventListenerSignal,
@@ -1546,39 +1528,41 @@ ${formattedStackTrace}`;
1546
1528
  });
1547
1529
  window.addEventListener("mouseup", (event) => {
1548
1530
  if (!isDragging()) return;
1549
- const dragDistance = getDragDistance(event.clientX, event.clientY);
1531
+ const dragDistance = calculateDragDistance(event.clientX, event.clientY);
1550
1532
  const wasDragGesture = dragDistance.x > DRAG_THRESHOLD_PX || dragDistance.y > DRAG_THRESHOLD_PX;
1551
1533
  setIsDragging(false);
1552
1534
  document.body.style.userSelect = "";
1553
1535
  if (wasDragGesture) {
1554
- const dragRect = getDragRect(event.clientX, event.clientY);
1536
+ setDidJustDrag(true);
1537
+ const dragRect = calculateDragRectangle(event.clientX, event.clientY);
1555
1538
  const elements = getElementsInDrag(dragRect, isValidGrabbableElement);
1556
1539
  if (elements.length > 0) {
1557
- setIsCopying(true);
1558
- void handleMultipleCopy(elements).finally(() => {
1559
- setIsCopying(false);
1560
- });
1540
+ void executeCopyOperation(event.clientX, event.clientY, () => copyMultipleElementsToClipboard(elements));
1561
1541
  } else {
1562
1542
  const fallbackElements = getElementsInDragLoose(dragRect, isValidGrabbableElement);
1563
1543
  if (fallbackElements.length > 0) {
1564
- setIsCopying(true);
1565
- void handleMultipleCopy(fallbackElements).finally(() => {
1566
- setIsCopying(false);
1567
- });
1544
+ void executeCopyOperation(event.clientX, event.clientY, () => copyMultipleElementsToClipboard(fallbackElements));
1568
1545
  }
1569
1546
  }
1570
1547
  } else {
1571
1548
  const element = getElementAtPosition(event.clientX, event.clientY);
1572
1549
  if (!element) return;
1573
- setIsCopying(true);
1574
1550
  setLastGrabbedElement(element);
1575
- void handleCopy(element).finally(() => {
1576
- setIsCopying(false);
1577
- });
1551
+ void executeCopyOperation(event.clientX, event.clientY, () => copySingleElementToClipboard(element));
1578
1552
  }
1579
1553
  }, {
1580
1554
  signal: eventListenerSignal
1581
1555
  });
1556
+ window.addEventListener("click", (event) => {
1557
+ if (didJustDrag()) {
1558
+ event.preventDefault();
1559
+ event.stopPropagation();
1560
+ setDidJustDrag(false);
1561
+ }
1562
+ }, {
1563
+ signal: eventListenerSignal,
1564
+ capture: true
1565
+ });
1582
1566
  document.addEventListener("visibilitychange", () => {
1583
1567
  if (document.hidden) {
1584
1568
  setGrabbedBoxes([]);