react-grab 0.0.29 → 0.0.31

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