react-grab 0.0.40 → 0.0.41

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
@@ -3,13 +3,7 @@
3
3
  var web = require('solid-js/web');
4
4
  var solidJs = require('solid-js');
5
5
  var bippy = require('bippy');
6
- var source = require('bippy/dist/source');
7
- var finder = require('@medv/finder');
8
- var TurndownService = require('turndown');
9
-
10
- function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
11
-
12
- var TurndownService__default = /*#__PURE__*/_interopDefault(TurndownService);
6
+ var source = require('bippy/source');
13
7
 
14
8
  /**
15
9
  * @license MIT
@@ -101,8 +95,12 @@ var mountRoot = () => {
101
95
  var VIEWPORT_MARGIN_PX = 8;
102
96
  var INDICATOR_CLAMP_PADDING_PX = 4;
103
97
  var CURSOR_OFFSET_PX = 14;
98
+ var OFFSCREEN_POSITION = -1e3;
104
99
  var SELECTION_LERP_FACTOR = 0.95;
105
100
  var SUCCESS_LABEL_DURATION_MS = 1700;
101
+ var PROGRESS_INDICATOR_DELAY_MS = 150;
102
+ var DRAG_THRESHOLD_PX = 2;
103
+ var Z_INDEX_LABEL = 2147483647;
106
104
 
107
105
  // src/utils/lerp.ts
108
106
  var lerp = (start, end, factor) => {
@@ -267,30 +265,22 @@ var getClampedElementPosition = (positionLeft, positionTop, elementWidth, elemen
267
265
  const clampedTop = Math.max(minTop, Math.min(positionTop, maxTop));
268
266
  return { left: clampedLeft, top: clampedTop };
269
267
  };
270
-
271
- // src/components/label.tsx
272
- var _tmpl$3 = /* @__PURE__ */ web.template(`<span style=display:inline-block;margin-right:4px;font-weight:600>\u2713`);
273
- var _tmpl$22 = /* @__PURE__ */ web.template(`<div style=margin-right:4px>Copied`);
274
- 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;vertical-align:middle">`);
275
- var _tmpl$4 = /* @__PURE__ */ web.template(`<span style=font-variant-numeric:tabular-nums;font-size:10px;margin-left:4px;vertical-align:middle>`);
276
- var _tmpl$5 = /* @__PURE__ */ web.template(`<div style=margin-left:4px>to clipboard`);
277
- var _tmpl$6 = /* @__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">`);
278
- var Label = (props) => {
279
- const [opacity, setOpacity] = solidJs.createSignal(0);
280
- const [positionTick, setPositionTick] = solidJs.createSignal(0);
281
- let labelRef;
282
- let currentX = props.x;
283
- let currentY = props.y;
284
- let targetX = props.x;
285
- let targetY = props.y;
268
+ var useAnimatedPosition = (options) => {
269
+ const lerpFactor = options.lerpFactor ?? 0.3;
270
+ const convergenceThreshold = options.convergenceThreshold ?? 0.5;
271
+ const [x, setX] = solidJs.createSignal(options.x());
272
+ const [y, setY] = solidJs.createSignal(options.y());
273
+ let targetX = options.x();
274
+ let targetY = options.y();
286
275
  let animationFrameId = null;
287
276
  let hasBeenRenderedOnce = false;
288
277
  const animate = () => {
289
- currentX = lerp(currentX, targetX, 0.3);
290
- currentY = lerp(currentY, targetY, 0.3);
291
- setPositionTick((tick) => tick + 1);
292
- const hasConvergedToTarget = Math.abs(currentX - targetX) < 0.5 && Math.abs(currentY - targetY) < 0.5;
293
- if (!hasConvergedToTarget) {
278
+ const currentX = lerp(x(), targetX, lerpFactor);
279
+ const currentY = lerp(y(), targetY, lerpFactor);
280
+ setX(currentX);
281
+ setY(currentY);
282
+ const hasConverged = Math.abs(currentX - targetX) < convergenceThreshold && Math.abs(currentY - targetY) < convergenceThreshold;
283
+ if (!hasConverged) {
294
284
  animationFrameId = requestAnimationFrame(animate);
295
285
  } else {
296
286
  animationFrameId = null;
@@ -300,36 +290,16 @@ var Label = (props) => {
300
290
  if (animationFrameId !== null) return;
301
291
  animationFrameId = requestAnimationFrame(animate);
302
292
  };
303
- const updateTarget = () => {
304
- targetX = props.x;
305
- targetY = props.y;
293
+ solidJs.createEffect(() => {
294
+ targetX = options.x();
295
+ targetY = options.y();
306
296
  if (!hasBeenRenderedOnce) {
307
- currentX = targetX;
308
- currentY = targetY;
297
+ setX(targetX);
298
+ setY(targetY);
309
299
  hasBeenRenderedOnce = true;
310
- setPositionTick((tick) => tick + 1);
311
300
  return;
312
301
  }
313
302
  startAnimation();
314
- };
315
- solidJs.createEffect(solidJs.on(() => props.visible, (visible) => {
316
- if (visible !== false) {
317
- requestAnimationFrame(() => {
318
- setOpacity(1);
319
- });
320
- } else {
321
- setOpacity(0);
322
- return;
323
- }
324
- if (props.variant === "success") {
325
- const fadeOutTimer = setTimeout(() => {
326
- setOpacity(0);
327
- }, SUCCESS_LABEL_DURATION_MS);
328
- solidJs.onCleanup(() => clearTimeout(fadeOutTimer));
329
- }
330
- }));
331
- solidJs.createEffect(() => {
332
- updateTarget();
333
303
  });
334
304
  solidJs.onCleanup(() => {
335
305
  if (animationFrameId !== null) {
@@ -337,34 +307,89 @@ var Label = (props) => {
337
307
  animationFrameId = null;
338
308
  }
339
309
  });
310
+ return { x, y };
311
+ };
312
+ var useFadeInOut = (options) => {
313
+ const [opacity, setOpacity] = solidJs.createSignal(0);
314
+ solidJs.createEffect(
315
+ solidJs.on(
316
+ () => options.visible,
317
+ (isVisible) => {
318
+ if (isVisible !== false) {
319
+ requestAnimationFrame(() => {
320
+ setOpacity(1);
321
+ });
322
+ } else {
323
+ setOpacity(0);
324
+ return;
325
+ }
326
+ if (options.autoFadeOutAfter !== void 0) {
327
+ const fadeOutTimer = setTimeout(() => {
328
+ setOpacity(0);
329
+ }, options.autoFadeOutAfter);
330
+ solidJs.onCleanup(() => clearTimeout(fadeOutTimer));
331
+ }
332
+ }
333
+ )
334
+ );
335
+ return opacity;
336
+ };
337
+
338
+ // src/utils/get-cursor-quadrants.ts
339
+ var getCursorQuadrants = (cursorX, cursorY, elementWidth, elementHeight, offset) => {
340
+ return [
341
+ {
342
+ left: Math.round(cursorX) + offset,
343
+ top: Math.round(cursorY) + offset
344
+ },
345
+ {
346
+ left: Math.round(cursorX) - elementWidth - offset,
347
+ top: Math.round(cursorY) + offset
348
+ },
349
+ {
350
+ left: Math.round(cursorX) + offset,
351
+ top: Math.round(cursorY) - elementHeight - offset
352
+ },
353
+ {
354
+ left: Math.round(cursorX) - elementWidth - offset,
355
+ top: Math.round(cursorY) - elementHeight - offset
356
+ }
357
+ ];
358
+ };
359
+
360
+ // src/components/label.tsx
361
+ var _tmpl$3 = /* @__PURE__ */ web.template(`<span style=display:inline-block;margin-right:4px;font-weight:600>\u2713`);
362
+ var _tmpl$22 = /* @__PURE__ */ web.template(`<div style=margin-right:4px>Copied`);
363
+ 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;vertical-align:middle">`);
364
+ var _tmpl$4 = /* @__PURE__ */ web.template(`<span style=font-variant-numeric:tabular-nums;font-size:10px;margin-left:4px;vertical-align:middle>`);
365
+ var _tmpl$5 = /* @__PURE__ */ web.template(`<div style=margin-left:4px>to clipboard`);
366
+ var _tmpl$6 = /* @__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">`);
367
+ var Label = (props) => {
368
+ let labelRef;
369
+ const position = useAnimatedPosition({
370
+ x: () => props.x,
371
+ y: () => props.y,
372
+ lerpFactor: 0.3
373
+ });
374
+ const opacity = useFadeInOut({
375
+ visible: props.visible,
376
+ autoFadeOutAfter: props.variant === "success" ? SUCCESS_LABEL_DURATION_MS : void 0
377
+ });
340
378
  const labelBoundingRect = () => labelRef?.getBoundingClientRect();
341
379
  const computedPosition = () => {
342
- positionTick();
343
380
  const boundingRect = labelBoundingRect();
344
381
  if (!boundingRect) return {
345
- left: currentX,
346
- top: currentY
382
+ left: position.x(),
383
+ top: position.y()
347
384
  };
348
385
  const viewportWidth = window.innerWidth;
349
386
  const viewportHeight = window.innerHeight;
350
- const quadrants = [{
351
- left: Math.round(currentX) + CURSOR_OFFSET_PX,
352
- top: Math.round(currentY) + CURSOR_OFFSET_PX
353
- }, {
354
- left: Math.round(currentX) - boundingRect.width - CURSOR_OFFSET_PX,
355
- top: Math.round(currentY) + CURSOR_OFFSET_PX
356
- }, {
357
- left: Math.round(currentX) + CURSOR_OFFSET_PX,
358
- top: Math.round(currentY) - boundingRect.height - CURSOR_OFFSET_PX
359
- }, {
360
- left: Math.round(currentX) - boundingRect.width - CURSOR_OFFSET_PX,
361
- top: Math.round(currentY) - boundingRect.height - CURSOR_OFFSET_PX
362
- }];
363
- for (const position of quadrants) {
364
- const fitsHorizontally = position.left >= VIEWPORT_MARGIN_PX && position.left + boundingRect.width <= viewportWidth - VIEWPORT_MARGIN_PX;
365
- const fitsVertically = position.top >= VIEWPORT_MARGIN_PX && position.top + boundingRect.height <= viewportHeight - VIEWPORT_MARGIN_PX;
387
+ const quadrants = getCursorQuadrants(position.x(), position.y(), boundingRect.width, boundingRect.height, CURSOR_OFFSET_PX);
388
+ for (const position2 of quadrants) {
389
+ const fitsHorizontally = position2.left >= VIEWPORT_MARGIN_PX && position2.left + boundingRect.width <= viewportWidth - VIEWPORT_MARGIN_PX;
390
+ const fitsVertically = position2.top >= VIEWPORT_MARGIN_PX && position2.top + boundingRect.height <= viewportHeight - VIEWPORT_MARGIN_PX;
366
391
  if (fitsHorizontally && fitsVertically) {
367
- return position;
392
+ return position2;
368
393
  }
369
394
  }
370
395
  const fallback = getClampedElementPosition(quadrants[0].left, quadrants[0].top, boundingRect.width, boundingRect.height);
@@ -471,21 +496,10 @@ var Label = (props) => {
471
496
  });
472
497
  };
473
498
  var _tmpl$7 = /* @__PURE__ */ web.template(`<div style="position:fixed;z-index:2147483647;pointer-events:none;transition:opacity 0.1s ease-in-out"><div style="width:32px;height:2px;background-color:rgba(178, 28, 142, 0.2);border-radius:1px;overflow:hidden;position:relative"><div style="height:100%;background-color:#b21c8e;border-radius:1px;transition:width 0.05s cubic-bezier(0.165, 0.84, 0.44, 1)">`);
474
- var useFadeInOut = (visible) => {
475
- const [opacity, setOpacity] = solidJs.createSignal(0);
476
- solidJs.createEffect(solidJs.on(() => visible, (isVisible) => {
477
- if (isVisible !== false) {
478
- requestAnimationFrame(() => {
479
- setOpacity(1);
480
- });
481
- } else {
482
- setOpacity(0);
483
- }
484
- }));
485
- return opacity;
486
- };
487
499
  var ProgressIndicator = (props) => {
488
- const opacity = useFadeInOut(props.visible);
500
+ const opacity = useFadeInOut({
501
+ visible: props.visible
502
+ });
489
503
  let progressIndicatorRef;
490
504
  const computedPosition = () => {
491
505
  const boundingRect = progressIndicatorRef?.getBoundingClientRect();
@@ -530,12 +544,11 @@ var Crosshair = (props) => {
530
544
  let width = 0;
531
545
  let height = 0;
532
546
  let dpr = 1;
533
- let currentX = props.mouseX;
534
- let currentY = props.mouseY;
535
- let targetX = props.mouseX;
536
- let targetY = props.mouseY;
537
- let animationFrameId = null;
538
- let hasBeenRenderedOnce = false;
547
+ const position = useAnimatedPosition({
548
+ x: () => props.mouseX,
549
+ y: () => props.mouseY,
550
+ lerpFactor: 0.3
551
+ });
539
552
  const setupCanvas = () => {
540
553
  if (!canvasRef) return;
541
554
  dpr = Math.max(window.devicePixelRatio || 1, 2);
@@ -556,39 +569,12 @@ var Crosshair = (props) => {
556
569
  context.strokeStyle = "rgba(210, 57, 192)";
557
570
  context.lineWidth = 1;
558
571
  context.beginPath();
559
- context.moveTo(currentX, 0);
560
- context.lineTo(currentX, height);
561
- context.moveTo(0, currentY);
562
- context.lineTo(width, currentY);
572
+ context.moveTo(position.x(), 0);
573
+ context.lineTo(position.x(), height);
574
+ context.moveTo(0, position.y());
575
+ context.lineTo(width, position.y());
563
576
  context.stroke();
564
577
  };
565
- const animate = () => {
566
- currentX = lerp(currentX, targetX, 0.3);
567
- currentY = lerp(currentY, targetY, 0.3);
568
- render2();
569
- const hasConvergedToTarget = Math.abs(currentX - targetX) < 0.5 && Math.abs(currentY - targetY) < 0.5;
570
- if (!hasConvergedToTarget) {
571
- animationFrameId = requestAnimationFrame(animate);
572
- } else {
573
- animationFrameId = null;
574
- }
575
- };
576
- const startAnimation = () => {
577
- if (animationFrameId !== null) return;
578
- animationFrameId = requestAnimationFrame(animate);
579
- };
580
- const updateTarget = () => {
581
- targetX = props.mouseX;
582
- targetY = props.mouseY;
583
- if (!hasBeenRenderedOnce) {
584
- currentX = targetX;
585
- currentY = targetY;
586
- hasBeenRenderedOnce = true;
587
- render2();
588
- return;
589
- }
590
- startAnimation();
591
- };
592
578
  solidJs.createEffect(() => {
593
579
  setupCanvas();
594
580
  render2();
@@ -599,14 +585,12 @@ var Crosshair = (props) => {
599
585
  window.addEventListener("resize", handleResize);
600
586
  solidJs.onCleanup(() => {
601
587
  window.removeEventListener("resize", handleResize);
602
- if (animationFrameId !== null) {
603
- cancelAnimationFrame(animationFrameId);
604
- animationFrameId = null;
605
- }
606
588
  });
607
589
  });
608
590
  solidJs.createEffect(() => {
609
- updateTarget();
591
+ position.x();
592
+ position.y();
593
+ render2();
610
594
  });
611
595
  return web.createComponent(solidJs.Show, {
612
596
  get when() {
@@ -750,27 +734,62 @@ var ReactGrabRenderer = (props) => {
750
734
  var isCapitalized = (value) => value.length > 0 && /^[A-Z]/.test(value);
751
735
 
752
736
  // src/instrumentation.ts
753
- bippy.instrument({
754
- onCommitFiberRoot(_, fiberRoot) {
755
- bippy._fiberRoots.add(fiberRoot);
756
- }
757
- });
758
- var generateCSSSelector = (element) => {
759
- return finder.finder(element);
737
+ var NEXT_INTERNAL_COMPONENT_NAMES = [
738
+ "InnerLayoutRouter",
739
+ "RedirectErrorBoundary",
740
+ "RedirectBoundary",
741
+ "HTTPAccessFallbackErrorBoundary",
742
+ "HTTPAccessFallbackBoundary",
743
+ "LoadingBoundary",
744
+ "ErrorBoundary",
745
+ "InnerScrollAndFocusHandler",
746
+ "ScrollAndFocusHandler",
747
+ "RenderFromTemplateContext",
748
+ "OuterLayoutRouter",
749
+ "body",
750
+ "html",
751
+ "RedirectErrorBoundary",
752
+ "RedirectBoundary",
753
+ "HTTPAccessFallbackErrorBoundary",
754
+ "HTTPAccessFallbackBoundary",
755
+ "DevRootHTTPAccessFallbackBoundary",
756
+ "AppDevOverlayErrorBoundary",
757
+ "AppDevOverlay",
758
+ "HotReload",
759
+ "Router",
760
+ "ErrorBoundaryHandler",
761
+ "ErrorBoundary",
762
+ "AppRouter",
763
+ "ServerRoot",
764
+ "SegmentStateProvider",
765
+ "RootErrorBoundary"
766
+ ];
767
+ var checkIsNextProject = () => {
768
+ return Boolean(document.getElementById("__NEXT_DATA__"));
769
+ };
770
+ var checkIsInternalComponentName = (name) => {
771
+ if (name.startsWith("_")) return true;
772
+ if (NEXT_INTERNAL_COMPONENT_NAMES.includes(name)) return true;
773
+ return false;
760
774
  };
761
- var truncateString = (string, maxLength) => string.length > maxLength ? `${string.substring(0, maxLength)}...` : string;
762
- var isInternalComponent = (name) => !isCapitalized(name) || name.startsWith("_") || name.startsWith("Primitive.") || name.includes("Provider") && name.includes("Context");
763
- var getNearestComponentDisplayName = (element) => {
775
+ var checkIsSourceComponentName = (name) => {
776
+ if (checkIsInternalComponentName(name)) return false;
777
+ if (!isCapitalized(name)) return false;
778
+ if (name.startsWith("Primitive.")) return false;
779
+ if (name.includes("Provider") && name.includes("Context")) return false;
780
+ return true;
781
+ };
782
+ var getNearestComponentName = (element) => {
764
783
  const fiber = bippy.getFiberFromHostInstance(element);
765
784
  if (!fiber) return null;
766
- let componentName = null;
785
+ let foundComponentName = null;
767
786
  bippy.traverseFiber(
768
787
  fiber,
769
788
  (currentFiber) => {
770
789
  if (bippy.isCompositeFiber(currentFiber)) {
771
790
  const displayName = bippy.getDisplayName(currentFiber);
772
- if (displayName && !isInternalComponent(displayName)) {
773
- componentName = displayName;
791
+ if (displayName && checkIsSourceComponentName(displayName)) {
792
+ foundComponentName = displayName;
774
793
  return true;
775
794
  }
776
795
  }
@@ -778,231 +797,95 @@ var getNearestComponentDisplayName = (element) => {
778
797
  },
779
798
  true
780
799
  );
781
- return componentName;
782
- };
783
- var formatComponentSourceLocation = async (el) => {
784
- const source$1 = await source.getSourceFromHostInstance(el);
785
- if (!source$1) return null;
786
- const fileName = source.normalizeFileName(source$1.fileName);
787
- if (source.isSourceFile(fileName)) {
788
- return `${fileName}:${source$1.lineNumber}:${source$1.columnNumber}`;
789
- }
790
- if (fileName && (fileName.includes(".tsx") || fileName.includes(".ts") || fileName.includes(".jsx") || fileName.includes(".js"))) {
791
- const cleanedFileName = fileName.replace(/^webpack:\/\/_N_E\//, "").replace(/^webpack:\/\/\//, "").replace(/^webpack:\/\//, "").replace(/^\.\//, "");
792
- if (cleanedFileName && !cleanedFileName.startsWith("node_modules") && !cleanedFileName.includes(".next") && !cleanedFileName.startsWith("webpack")) {
793
- return `${cleanedFileName}:${source$1.lineNumber}:${source$1.columnNumber}`;
794
- }
795
- }
796
- return null;
800
+ return foundComponentName;
797
801
  };
798
- var getHTMLSnippet = async (element) => {
799
- const semanticTags = /* @__PURE__ */ new Set([
800
- "article",
801
- "aside",
802
- "footer",
803
- "form",
804
- "header",
805
- "main",
806
- "nav",
807
- "section"
808
- ]);
809
- const hasDistinguishingFeatures = (el) => {
810
- const tagName = el.tagName.toLowerCase();
811
- if (semanticTags.has(tagName)) return true;
812
- if (el.id) return true;
813
- if (el.className && typeof el.className === "string") {
814
- const classes = el.className.trim();
815
- if (classes && classes.length > 0) return true;
816
- }
817
- return Array.from(el.attributes).some(
818
- (attr) => attr.name.startsWith("data-")
819
- );
820
- };
821
- const collectDistinguishingAncestors = (el, maxDepth = 10) => {
822
- const ancestors2 = [];
823
- let current = el.parentElement;
824
- let depth = 0;
825
- while (current && depth < maxDepth && current.tagName !== "BODY") {
826
- if (hasDistinguishingFeatures(current)) {
827
- ancestors2.push(current);
828
- if (ancestors2.length >= 3) break;
829
- }
830
- current = current.parentElement;
831
- depth++;
832
- }
833
- return ancestors2.reverse();
834
- };
835
- const formatElementOpeningTag = (el, compact = false) => {
836
- const tagName = el.tagName.toLowerCase();
837
- const attrs = [];
838
- if (el.id) {
839
- attrs.push(`id="${el.id}"`);
840
- }
841
- if (el.className && typeof el.className === "string") {
842
- const classes = el.className.trim().split(/\s+/);
843
- if (classes.length > 0 && classes[0]) {
844
- const displayClasses = compact ? classes.slice(0, 3) : classes;
845
- const classStr = truncateString(displayClasses.join(" "), 30);
846
- attrs.push(`class="${classStr}"`);
847
- }
848
- }
849
- const dataAttrs = Array.from(el.attributes).filter(
850
- (attr) => attr.name.startsWith("data-")
851
- );
852
- const displayDataAttrs = compact ? dataAttrs.slice(0, 1) : dataAttrs;
853
- for (const attr of displayDataAttrs) {
854
- attrs.push(`${attr.name}="${truncateString(attr.value, 20)}"`);
855
- }
856
- const ariaLabel = el.getAttribute("aria-label");
857
- if (ariaLabel && !compact) {
858
- attrs.push(`aria-label="${truncateString(ariaLabel, 20)}"`);
859
- }
860
- return attrs.length > 0 ? `<${tagName} ${attrs.join(" ")}>` : `<${tagName}>`;
861
- };
862
- const formatElementClosingTag = (el) => `</${el.tagName.toLowerCase()}>`;
863
- const extractTruncatedTextContent = (el) => {
864
- const text = (el.textContent || "").trim().replace(/\s+/g, " ");
865
- return truncateString(text, 60);
866
- };
867
- const extractSiblingIdentifier = (el) => {
868
- if (el.id) return `#${el.id}`;
869
- if (el.className && typeof el.className === "string") {
870
- const classes = el.className.trim().split(/\s+/);
871
- if (classes.length > 0 && classes[0]) {
872
- return `.${classes[0]}`;
802
+ var getStack = async (element) => {
803
+ const maybeFiber = bippy.getFiberFromHostInstance(element);
804
+ if (!maybeFiber || !bippy.isFiber(maybeFiber)) return [];
805
+ const fiber = bippy.getLatestFiber(maybeFiber);
806
+ const unresolvedStack = [];
807
+ bippy.traverseFiber(
808
+ fiber,
809
+ (currentFiber) => {
810
+ const displayName = bippy.isHostFiber(currentFiber) ? typeof currentFiber.type === "string" ? currentFiber.type : null : bippy.getDisplayName(currentFiber);
811
+ if (displayName && !checkIsInternalComponentName(displayName)) {
812
+ unresolvedStack.push({
813
+ name: displayName,
814
+ sourcePromise: source.getSource(currentFiber)
815
+ });
873
816
  }
874
- }
875
- return null;
876
- };
877
- const lines = [];
878
- const selector = generateCSSSelector(element);
879
- lines.push(`- selector: ${selector}`);
880
- const rect = element.getBoundingClientRect();
881
- lines.push(`- width: ${Math.round(rect.width)}`);
882
- lines.push(`- height: ${Math.round(rect.height)}`);
883
- lines.push("HTML snippet:");
884
- lines.push("```html");
885
- const ancestors = collectDistinguishingAncestors(element);
886
- const ancestorComponents = ancestors.map(
887
- (ancestor) => getNearestComponentDisplayName(ancestor)
817
+ },
818
+ true
888
819
  );
889
- const elementComponent = getNearestComponentDisplayName(element);
890
- const ancestorSources = await Promise.all(
891
- ancestors.map((ancestor) => formatComponentSourceLocation(ancestor))
820
+ const resolvedStack = await Promise.all(
821
+ unresolvedStack.map(async (frame) => ({
822
+ name: frame.name,
823
+ source: await frame.sourcePromise
824
+ }))
892
825
  );
893
- const elementSource = await formatComponentSourceLocation(element);
894
- const ancestorElementIndents = [];
895
- const ancestorComponentIndents = [];
896
- let currentIndentLevel = 0;
897
- const getIndent = (level) => " ".repeat(level);
898
- for (let i = 0; i < ancestors.length; i++) {
899
- const componentName = ancestorComponents[i];
900
- const source = ancestorSources[i];
901
- const isNewComponent = componentName && source && (i === 0 || ancestorComponents[i - 1] !== componentName);
902
- if (isNewComponent) {
903
- ancestorComponentIndents[i] = currentIndentLevel;
904
- lines.push(
905
- `${getIndent(currentIndentLevel)}<${componentName} used-at="${source}">`
906
- );
907
- currentIndentLevel++;
826
+ return resolvedStack.filter((frame) => frame.source !== null);
827
+ };
828
+ var formatStack = (stack) => {
829
+ const isNextProject = checkIsNextProject();
830
+ return stack.map(({ name, source: source$1 }) => {
831
+ if (!source$1) return ` at ${name}`;
832
+ if (source$1.fileName.startsWith("about://React/Server")) {
833
+ return ` at ${name} (Server)`;
908
834
  }
909
- ancestorElementIndents[i] = currentIndentLevel;
910
- lines.push(
911
- `${getIndent(currentIndentLevel)}${formatElementOpeningTag(ancestors[i], true)}`
912
- );
913
- currentIndentLevel++;
914
- }
915
- const parent = element.parentElement;
916
- let targetIndex = -1;
917
- if (parent) {
918
- const siblings = Array.from(parent.children);
919
- targetIndex = siblings.indexOf(element);
920
- if (targetIndex > 0) {
921
- const indent = getIndent(currentIndentLevel);
922
- if (targetIndex <= 2) {
923
- for (let i = 0; i < targetIndex; i++) {
924
- const sibling = siblings[i];
925
- const siblingId = extractSiblingIdentifier(sibling);
926
- if (siblingId) {
927
- lines.push(`${indent}${formatElementOpeningTag(sibling, true)}`);
928
- lines.push(`${indent}</${sibling.tagName.toLowerCase()}>`);
929
- }
930
- }
931
- } else {
932
- lines.push(
933
- `${indent}... (${targetIndex} element${targetIndex === 1 ? "" : "s"})`
934
- );
935
- }
835
+ if (!source.isSourceFile(source$1.fileName)) return ` at ${name}`;
836
+ const framePart = ` at ${name} in ${source.normalizeFileName(source$1.fileName)}`;
837
+ if (isNextProject) {
838
+ return `${framePart}:${source$1.lineNumber}:${source$1.columnNumber}`;
936
839
  }
840
+ return framePart;
841
+ }).join("\n");
842
+ };
843
+ var getHTMLPreview = (element) => {
844
+ const tagName = element.tagName.toLowerCase();
845
+ if (!(element instanceof HTMLElement)) {
846
+ return `<${tagName} />`;
937
847
  }
938
- const lastAncestorComponent = ancestors.length > 0 ? ancestorComponents[ancestorComponents.length - 1] : null;
939
- const showElementComponent = elementComponent && elementSource && elementComponent !== lastAncestorComponent;
940
- let elementIndentLevel = currentIndentLevel;
941
- const elementComponentIndentLevel = currentIndentLevel;
942
- if (showElementComponent) {
943
- lines.push(
944
- `${getIndent(elementIndentLevel)}<${elementComponent} used-at="${elementSource}">`
945
- );
946
- elementIndentLevel++;
947
- }
948
- const elementIndent = getIndent(elementIndentLevel);
949
- lines.push(`${elementIndent}<!-- IMPORTANT: selected element -->`);
950
- const textContent = extractTruncatedTextContent(element);
951
- const childrenCount = element.children.length;
952
- if (textContent && childrenCount === 0 && textContent.length < 40) {
953
- lines.push(
954
- `${elementIndent}${formatElementOpeningTag(element)}${textContent}${formatElementClosingTag(
955
- element
956
- )}`
957
- );
958
- } else {
959
- lines.push(`${elementIndent}${formatElementOpeningTag(element)}`);
960
- if (textContent) {
961
- lines.push(`${elementIndent} ${textContent}`);
962
- }
963
- if (childrenCount > 0) {
964
- lines.push(
965
- `${elementIndent} ... (${childrenCount} element${childrenCount === 1 ? "" : "s"})`
966
- );
848
+ const text = element.innerText?.trim() ?? element.textContent?.trim() ?? "";
849
+ let attrsText = "";
850
+ const attributes = Array.from(element.attributes);
851
+ for (const attribute of attributes) {
852
+ const name = attribute.name;
853
+ let value = attribute.value;
854
+ if (value.length > 20) {
855
+ value = `${value.slice(0, 20)}...`;
967
856
  }
968
- lines.push(`${elementIndent}${formatElementClosingTag(element)}`);
857
+ attrsText += ` ${name}="${value}"`;
969
858
  }
970
- if (showElementComponent) {
971
- lines.push(
972
- `${getIndent(elementComponentIndentLevel)}</${elementComponent}>`
973
- );
974
- }
975
- if (parent && targetIndex >= 0) {
976
- const siblings = Array.from(parent.children);
977
- const siblingsAfter = siblings.length - targetIndex - 1;
978
- if (siblingsAfter > 0) {
979
- const indent = getIndent(currentIndentLevel);
980
- if (siblingsAfter <= 2) {
981
- for (let i = targetIndex + 1; i < siblings.length; i++) {
982
- const sibling = siblings[i];
983
- const siblingId = extractSiblingIdentifier(sibling);
984
- if (siblingId) {
985
- lines.push(`${indent}${formatElementOpeningTag(sibling, true)}`);
986
- lines.push(`${indent}</${sibling.tagName.toLowerCase()}>`);
987
- }
988
- }
859
+ let topElements = 0;
860
+ let bottomElements = 0;
861
+ let foundFirstText = false;
862
+ const childNodes = Array.from(element.childNodes);
863
+ for (const node of childNodes) {
864
+ if (node.nodeType === Node.COMMENT_NODE) continue;
865
+ if (node.nodeType === Node.TEXT_NODE) {
866
+ if (node.textContent && node.textContent.trim().length > 0) {
867
+ foundFirstText = true;
868
+ }
869
+ } else if (node.nodeType === Node.ELEMENT_NODE) {
870
+ if (!foundFirstText) {
871
+ topElements++;
989
872
  } else {
990
- lines.push(
991
- `${indent}... (${siblingsAfter} element${siblingsAfter === 1 ? "" : "s"})`
992
- );
873
+ bottomElements++;
993
874
  }
994
875
  }
995
876
  }
996
- for (let i = ancestors.length - 1; i >= 0; i--) {
997
- const elementIndent2 = getIndent(ancestorElementIndents[i]);
998
- lines.push(`${elementIndent2}${formatElementClosingTag(ancestors[i])}`);
999
- if (ancestorComponentIndents[i] !== void 0) {
1000
- const compIndent = getIndent(ancestorComponentIndents[i]);
1001
- lines.push(`${compIndent}</${ancestorComponents[i]}>`);
1002
- }
877
+ let content = "";
878
+ if (topElements > 0) content += `
879
+ (${topElements} elements)`;
880
+ if (text.length > 0) content += `
881
+ ${text}`;
882
+ if (bottomElements > 0) content += `
883
+ (${bottomElements} elements)`;
884
+ if (content.length > 0) {
885
+ return `<${tagName}${attrsText}>${content}
886
+ </${tagName}>`;
1003
887
  }
1004
- lines.push("```");
1005
- return lines.join("\n");
888
+ return `<${tagName}${attrsText} />`;
1006
889
  };
1007
890
 
1008
891
  // src/utils/copy-content.ts
@@ -1022,73 +905,13 @@ var waitForFocus = () => {
1022
905
  var copyContent = async (content, onSuccess) => {
1023
906
  await waitForFocus();
1024
907
  try {
1025
- if (Array.isArray(content)) {
1026
- if (!navigator?.clipboard?.write) {
1027
- for (const contentPart of content) {
1028
- if (typeof contentPart === "string") {
1029
- const result = copyContentFallback(contentPart, onSuccess);
1030
- if (!result) return result;
1031
- }
1032
- }
1033
- onSuccess?.();
1034
- return true;
1035
- }
1036
- const mimeTypeMap = /* @__PURE__ */ new Map();
1037
- for (const contentPart of content) {
1038
- if (contentPart instanceof Blob) {
1039
- const mimeType = contentPart.type || "text/plain";
1040
- if (!mimeTypeMap.has(mimeType)) {
1041
- mimeTypeMap.set(mimeType, contentPart);
1042
- }
1043
- } else {
1044
- if (!mimeTypeMap.has("text/plain")) {
1045
- mimeTypeMap.set(
1046
- "text/plain",
1047
- new Blob([contentPart], { type: "text/plain" })
1048
- );
1049
- }
1050
- }
1051
- }
1052
- if (mimeTypeMap.size === 0) {
1053
- const plainTextFallback = content.find(
1054
- (contentPart) => typeof contentPart === "string"
1055
- );
1056
- if (typeof plainTextFallback === "string") {
1057
- return copyContentFallback(plainTextFallback, onSuccess);
1058
- }
1059
- return false;
1060
- }
1061
- try {
1062
- await navigator.clipboard.write([
1063
- new ClipboardItem(Object.fromEntries(mimeTypeMap))
1064
- ]);
1065
- onSuccess?.();
1066
- return true;
1067
- } catch {
1068
- const plainTextParts = content.filter(
1069
- (contentPart) => typeof contentPart === "string"
1070
- );
1071
- if (plainTextParts.length > 0) {
1072
- const combinedText = plainTextParts.join("\n\n");
1073
- return copyContentFallback(combinedText, onSuccess);
1074
- }
1075
- return false;
1076
- }
1077
- } else if (content instanceof Blob) {
1078
- await navigator.clipboard.write([
1079
- new ClipboardItem({ [content.type]: content })
1080
- ]);
908
+ try {
909
+ await navigator.clipboard.writeText(content);
1081
910
  onSuccess?.();
1082
911
  return true;
1083
- } else {
1084
- try {
1085
- await navigator.clipboard.writeText(String(content));
1086
- onSuccess?.();
1087
- return true;
1088
- } catch {
1089
- const result = copyContentFallback(content, onSuccess);
1090
- return result;
1091
- }
912
+ } catch {
913
+ const result = copyContentFallback(content, onSuccess);
914
+ return result;
1092
915
  }
1093
916
  } catch {
1094
917
  return false;
@@ -1177,13 +1000,27 @@ var getElementAtPosition = (clientX, clientY) => {
1177
1000
 
1178
1001
  // src/utils/get-elements-in-drag.ts
1179
1002
  var DRAG_COVERAGE_THRESHOLD = 0.75;
1003
+ var calculateIntersectionArea = (rect1, rect2) => {
1004
+ const intersectionLeft = Math.max(rect1.left, rect2.left);
1005
+ const intersectionTop = Math.max(rect1.top, rect2.top);
1006
+ const intersectionRight = Math.min(rect1.right, rect2.right);
1007
+ const intersectionBottom = Math.min(rect1.bottom, rect2.bottom);
1008
+ const intersectionWidth = Math.max(0, intersectionRight - intersectionLeft);
1009
+ const intersectionHeight = Math.max(0, intersectionBottom - intersectionTop);
1010
+ return intersectionWidth * intersectionHeight;
1011
+ };
1012
+ var hasIntersection = (rect1, rect2) => {
1013
+ return rect1.left < rect2.right && rect1.right > rect2.left && rect1.top < rect2.bottom && rect1.bottom > rect2.top;
1014
+ };
1180
1015
  var filterElementsInDrag = (dragRect, isValidGrabbableElement2, shouldCheckCoverage) => {
1181
1016
  const elements = [];
1182
1017
  const allElements = Array.from(document.querySelectorAll("*"));
1183
- const dragLeft = dragRect.x;
1184
- const dragTop = dragRect.y;
1185
- const dragRight = dragRect.x + dragRect.width;
1186
- const dragBottom = dragRect.y + dragRect.height;
1018
+ const dragBounds = {
1019
+ left: dragRect.x,
1020
+ top: dragRect.y,
1021
+ right: dragRect.x + dragRect.width,
1022
+ bottom: dragRect.y + dragRect.height
1023
+ };
1187
1024
  for (const candidateElement of allElements) {
1188
1025
  if (!shouldCheckCoverage) {
1189
1026
  const tagName = (candidateElement.tagName || "").toUpperCase();
@@ -1193,26 +1030,21 @@ var filterElementsInDrag = (dragRect, isValidGrabbableElement2, shouldCheckCover
1193
1030
  continue;
1194
1031
  }
1195
1032
  const elementRect = candidateElement.getBoundingClientRect();
1196
- const elementLeft = elementRect.left;
1197
- const elementTop = elementRect.top;
1198
- const elementRight = elementRect.left + elementRect.width;
1199
- const elementBottom = elementRect.top + elementRect.height;
1033
+ const elementBounds = {
1034
+ left: elementRect.left,
1035
+ top: elementRect.top,
1036
+ right: elementRect.left + elementRect.width,
1037
+ bottom: elementRect.top + elementRect.height
1038
+ };
1200
1039
  if (shouldCheckCoverage) {
1201
- const intersectionLeft = Math.max(dragLeft, elementLeft);
1202
- const intersectionTop = Math.max(dragTop, elementTop);
1203
- const intersectionRight = Math.min(dragRight, elementRight);
1204
- const intersectionBottom = Math.min(dragBottom, elementBottom);
1205
- const intersectionWidth = Math.max(0, intersectionRight - intersectionLeft);
1206
- const intersectionHeight = Math.max(0, intersectionBottom - intersectionTop);
1207
- const intersectionArea = intersectionWidth * intersectionHeight;
1040
+ const intersectionArea = calculateIntersectionArea(dragBounds, elementBounds);
1208
1041
  const elementArea = Math.max(0, elementRect.width * elementRect.height);
1209
1042
  const hasMajorityCoverage = elementArea > 0 && intersectionArea / elementArea >= DRAG_COVERAGE_THRESHOLD;
1210
1043
  if (hasMajorityCoverage) {
1211
1044
  elements.push(candidateElement);
1212
1045
  }
1213
1046
  } else {
1214
- const hasIntersection = elementLeft < dragRight && elementRight > dragLeft && elementTop < dragBottom && elementBottom > dragTop;
1215
- if (hasIntersection) {
1047
+ if (hasIntersection(elementBounds, dragBounds)) {
1216
1048
  elements.push(candidateElement);
1217
1049
  }
1218
1050
  }
@@ -1251,70 +1083,7 @@ var createElementBounds = (element) => {
1251
1083
  };
1252
1084
  };
1253
1085
 
1254
- // src/utils/is-localhost.ts
1255
- var isLocalhost = () => {
1256
- const hostname = window.location.hostname;
1257
- return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]";
1258
- };
1259
- var turndownService = null;
1260
- var getTurndownService = () => {
1261
- if (!turndownService) {
1262
- turndownService = new TurndownService__default.default({
1263
- headingStyle: "atx",
1264
- codeBlockStyle: "fenced",
1265
- emDelimiter: "_",
1266
- bulletListMarker: "-",
1267
- linkStyle: "inlined",
1268
- linkReferenceStyle: "full"
1269
- });
1270
- turndownService.addRule("strikethrough", {
1271
- filter: ["del", "s"],
1272
- replacement: (content) => `~~${content}~~`
1273
- });
1274
- turndownService.addRule("removeHidden", {
1275
- filter: (node) => {
1276
- if (node instanceof HTMLElement) {
1277
- const style = window.getComputedStyle(node);
1278
- return style.display === "none" || style.visibility === "hidden" || style.opacity === "0";
1279
- }
1280
- return false;
1281
- },
1282
- replacement: () => ""
1283
- });
1284
- turndownService.addRule("preserveButtons", {
1285
- filter: ["button"],
1286
- replacement: (content) => content ? `[${content}]` : ""
1287
- });
1288
- turndownService.addRule("preserveInputs", {
1289
- filter: (node) => {
1290
- if (node.nodeName === "INPUT" && node instanceof HTMLInputElement) {
1291
- return true;
1292
- }
1293
- return false;
1294
- },
1295
- replacement: (_, node) => {
1296
- if (node instanceof HTMLInputElement) {
1297
- const value = node.value || node.placeholder;
1298
- return value ? `[${value}]` : "";
1299
- }
1300
- return "";
1301
- }
1302
- });
1303
- }
1304
- return turndownService;
1305
- };
1306
- var htmlToMarkdown = (html) => {
1307
- const service = getTurndownService();
1308
- return service.turndown(html).trim();
1309
- };
1310
- var elementToMarkdown = (element) => {
1311
- const clonedElement = element.cloneNode(true);
1312
- const service = getTurndownService();
1313
- return service.turndown(clonedElement).trim();
1314
- };
1315
-
1316
1086
  // src/core.tsx
1317
- var PROGRESS_INDICATOR_DELAY_MS = 150;
1318
1087
  var init = (rawOptions) => {
1319
1088
  const options = {
1320
1089
  enabled: true,
@@ -1337,7 +1106,6 @@ var init = (rawOptions) => {
1337
1106
  };
1338
1107
  }
1339
1108
  return solidJs.createRoot((dispose) => {
1340
- const OFFSCREEN_POSITION = -1e3;
1341
1109
  const [isHoldingKeys, setIsHoldingKeys] = solidJs.createSignal(false);
1342
1110
  const [mouseX, setMouseX] = solidJs.createSignal(OFFSCREEN_POSITION);
1343
1111
  const [mouseY, setMouseY] = solidJs.createSignal(OFFSCREEN_POSITION);
@@ -1347,7 +1115,7 @@ var init = (rawOptions) => {
1347
1115
  const [isCopying, setIsCopying] = solidJs.createSignal(false);
1348
1116
  const [lastGrabbedElement, setLastGrabbedElement] = solidJs.createSignal(null);
1349
1117
  const [progressStartTime, setProgressStartTime] = solidJs.createSignal(null);
1350
- const [progressTick, setProgressTick] = solidJs.createSignal(0);
1118
+ const [progress, setProgress] = solidJs.createSignal(0);
1351
1119
  const [grabbedBoxes, setGrabbedBoxes] = solidJs.createSignal([]);
1352
1120
  const [successLabels, setSuccessLabels] = solidJs.createSignal([]);
1353
1121
  const [isActivated, setIsActivated] = solidJs.createSignal(false);
@@ -1394,55 +1162,10 @@ var init = (rawOptions) => {
1394
1162
  const wrapInSelectedElementTags = (context) => `<selected_element>
1395
1163
  ${context}
1396
1164
  </selected_element>`;
1397
- const extractRelevantComputedStyles = (element) => {
1398
- const computed = window.getComputedStyle(element);
1399
- const rect = element.getBoundingClientRect();
1400
- return {
1401
- width: `${Math.round(rect.width)}px`,
1402
- height: `${Math.round(rect.height)}px`,
1403
- paddingTop: computed.paddingTop,
1404
- paddingRight: computed.paddingRight,
1405
- paddingBottom: computed.paddingBottom,
1406
- paddingLeft: computed.paddingLeft,
1407
- background: computed.background,
1408
- opacity: computed.opacity
1409
- };
1410
- };
1411
- const createStructuredClipboardHtmlBlob = (elements) => {
1412
- const structuredData = {
1413
- elements: elements.map((element) => ({
1414
- tagName: element.tagName,
1415
- content: element.content,
1416
- computedStyles: element.computedStyles
1417
- }))
1418
- };
1419
- const jsonString = JSON.stringify(structuredData);
1420
- const base64Data = btoa(encodeURIComponent(jsonString).replace(/%([0-9A-F]{2})/g, (_match, p1) => String.fromCharCode(parseInt(p1, 16))));
1421
- const htmlContent = `<div data-react-grab="${base64Data}"></div>`;
1422
- return new Blob([htmlContent], {
1423
- type: "text/html"
1424
- });
1425
- };
1426
1165
  const extractElementTagName = (element) => (element.tagName || "").toLowerCase();
1427
- const extractNearestComponentName = (element) => {
1428
- const fiber = bippy.getFiberFromHostInstance(element);
1429
- if (!fiber) return null;
1430
- let componentName = null;
1431
- bippy.traverseFiber(fiber, (currentFiber) => {
1432
- if (bippy.isCompositeFiber(currentFiber)) {
1433
- const displayName = bippy.getDisplayName(currentFiber);
1434
- if (displayName && isCapitalized(displayName) && !displayName.startsWith("_") && !displayName.startsWith("Primitive.")) {
1435
- componentName = displayName;
1436
- return true;
1437
- }
1438
- }
1439
- return false;
1440
- }, true);
1441
- return componentName;
1442
- };
1443
1166
  const extractElementLabelText = (element) => {
1444
1167
  const tagName = extractElementTagName(element);
1445
- const componentName = extractNearestComponentName(element);
1168
+ const componentName = getNearestComponentName(element);
1446
1169
  if (tagName && componentName) {
1447
1170
  return `<${tagName}> in ${componentName}`;
1448
1171
  }
@@ -1464,8 +1187,6 @@ ${context}
1464
1187
  } catch {
1465
1188
  }
1466
1189
  };
1467
- const isExtensionEnvironment = () => window.__REACT_GRAB_EXTENSION_ACTIVE__ === true || options.isExtension === true;
1468
- const isExtensionTextOnlyMode = () => isExtensionEnvironment() && !isLocalhost();
1469
1190
  const executeCopyOperation = async (positionX, positionY, operation) => {
1470
1191
  setCopyStartX(positionX);
1471
1192
  setCopyStartY(positionY);
@@ -1491,39 +1212,37 @@ ${context}
1491
1212
  return element.textContent ?? "";
1492
1213
  };
1493
1214
  const createCombinedTextContent = (elements) => elements.map((element) => extractElementTextContent(element).trim()).filter((textContent) => textContent.length > 0).join("\n\n");
1494
- const createCombinedMarkdownContent = (elements) => elements.map((element) => elementToMarkdown(element).trim()).filter((markdownContent) => markdownContent.length > 0).join("\n\n");
1495
- const copySingleElementToClipboard = async (targetElement2) => {
1496
- showTemporaryGrabbedBox(createElementBounds(targetElement2));
1497
- await new Promise((resolve) => requestAnimationFrame(resolve));
1215
+ const tryCopyWithFallback = async (elements) => {
1498
1216
  let didCopy = false;
1499
1217
  try {
1500
- if (isExtensionTextOnlyMode()) {
1501
- const markdownContent = createCombinedMarkdownContent([targetElement2]);
1502
- if (markdownContent.length > 0) {
1503
- didCopy = await copyContent(markdownContent, options.playCopySound ? playCopySound : void 0);
1504
- }
1505
- } else {
1506
- const content = await getHTMLSnippet(targetElement2);
1507
- const plainTextContent = wrapInSelectedElementTags(content);
1508
- const htmlContent = createStructuredClipboardHtmlBlob([{
1509
- tagName: extractElementTagName(targetElement2),
1510
- content,
1511
- computedStyles: extractRelevantComputedStyles(targetElement2)
1512
- }]);
1513
- didCopy = await copyContent([plainTextContent, htmlContent], options.playCopySound ? playCopySound : void 0);
1514
- if (!didCopy) {
1515
- const plainTextContentOnly = createCombinedTextContent([targetElement2]);
1516
- if (plainTextContentOnly.length > 0) {
1517
- didCopy = await copyContent(plainTextContentOnly, options.playCopySound ? playCopySound : void 0);
1518
- }
1218
+ const elementSnippetResults = await Promise.allSettled(elements.map(async (element) => `## HTML Frame:
1219
+ ${getHTMLPreview(element)}
1220
+
1221
+ ## Code Location:
1222
+ ${formatStack(await getStack(element))}`));
1223
+ const elementSnippets = elementSnippetResults.map((result) => result.status === "fulfilled" ? result.value : "").filter((snippet) => snippet.trim());
1224
+ if (elementSnippets.length > 0) {
1225
+ const plainTextContent = elementSnippets.map((snippet) => wrapInSelectedElementTags(snippet)).join("\n\n");
1226
+ didCopy = await copyContent(plainTextContent, options.playCopySound ? playCopySound : void 0);
1227
+ }
1228
+ if (!didCopy) {
1229
+ const plainTextContentOnly = createCombinedTextContent(elements);
1230
+ if (plainTextContentOnly.length > 0) {
1231
+ didCopy = await copyContent(plainTextContentOnly, options.playCopySound ? playCopySound : void 0);
1519
1232
  }
1520
1233
  }
1521
1234
  } catch {
1522
- const plainTextContentOnly = createCombinedTextContent([targetElement2]);
1235
+ const plainTextContentOnly = createCombinedTextContent(elements);
1523
1236
  if (plainTextContentOnly.length > 0) {
1524
1237
  didCopy = await copyContent(plainTextContentOnly, options.playCopySound ? playCopySound : void 0);
1525
1238
  }
1526
1239
  }
1240
+ return didCopy;
1241
+ };
1242
+ const copySingleElementToClipboard = async (targetElement2) => {
1243
+ showTemporaryGrabbedBox(createElementBounds(targetElement2));
1244
+ await new Promise((resolve) => requestAnimationFrame(resolve));
1245
+ const didCopy = await tryCopyWithFallback([targetElement2]);
1527
1246
  if (didCopy) {
1528
1247
  showTemporarySuccessLabel(extractElementLabelText(targetElement2));
1529
1248
  }
@@ -1535,40 +1254,7 @@ ${context}
1535
1254
  showTemporaryGrabbedBox(createElementBounds(element));
1536
1255
  }
1537
1256
  await new Promise((resolve) => requestAnimationFrame(resolve));
1538
- let didCopy = false;
1539
- try {
1540
- if (isExtensionTextOnlyMode()) {
1541
- const markdownContent = createCombinedMarkdownContent(targetElements);
1542
- if (markdownContent.length > 0) {
1543
- didCopy = await copyContent(markdownContent, options.playCopySound ? playCopySound : void 0);
1544
- }
1545
- } else {
1546
- const elementSnippetResults = await Promise.allSettled(targetElements.map((element) => getHTMLSnippet(element)));
1547
- const elementSnippets = elementSnippetResults.map((result) => result.status === "fulfilled" ? result.value : "").filter((snippet) => snippet.trim());
1548
- if (elementSnippets.length > 0) {
1549
- const plainTextContent = elementSnippets.map((snippet) => wrapInSelectedElementTags(snippet)).join("\n\n");
1550
- const structuredElements = elementSnippets.map((content, elementIndex) => ({
1551
- tagName: extractElementTagName(targetElements[elementIndex]),
1552
- content,
1553
- computedStyles: extractRelevantComputedStyles(targetElements[elementIndex])
1554
- }));
1555
- const htmlContent = createStructuredClipboardHtmlBlob(structuredElements);
1556
- didCopy = await copyContent([plainTextContent, htmlContent], options.playCopySound ? playCopySound : void 0);
1557
- if (!didCopy) {
1558
- const plainTextContentOnly = createCombinedTextContent(targetElements);
1559
- if (plainTextContentOnly.length > 0) {
1560
- didCopy = await copyContent(plainTextContentOnly, options.playCopySound ? playCopySound : void 0);
1561
- }
1562
- }
1563
- } else {
1564
- const plainTextContentOnly = createCombinedTextContent(targetElements);
1565
- if (plainTextContentOnly.length > 0) {
1566
- didCopy = await copyContent(plainTextContentOnly, options.playCopySound ? playCopySound : void 0);
1567
- }
1568
- }
1569
- }
1570
- } catch {
1571
- }
1257
+ const didCopy = await tryCopyWithFallback(targetElements);
1572
1258
  if (didCopy) {
1573
1259
  showTemporarySuccessLabel(`${targetElements.length} elements`);
1574
1260
  }
@@ -1592,7 +1278,6 @@ ${context}
1592
1278
  y: elementBounds.top
1593
1279
  };
1594
1280
  });
1595
- const DRAG_THRESHOLD_PX = 2;
1596
1281
  const calculateDragDistance = (endX, endY) => ({
1597
1282
  x: Math.abs(endX - dragStartX()),
1598
1283
  y: Math.abs(endY - dragStartY())
@@ -1650,30 +1335,23 @@ ${context}
1650
1335
  setLastGrabbedElement(null);
1651
1336
  }
1652
1337
  }));
1653
- const progress = solidJs.createMemo(() => {
1654
- const startTime = progressStartTime();
1655
- progressTick();
1656
- if (startTime === null) return 0;
1657
- const elapsedTime = Date.now() - startTime;
1658
- const normalizedTime = elapsedTime / options.keyHoldDuration;
1659
- const easedProgress = 1 - Math.exp(-normalizedTime);
1660
- const maxProgressBeforeCompletion = 0.95;
1661
- if (isCopying()) {
1662
- return Math.min(easedProgress, maxProgressBeforeCompletion);
1663
- }
1664
- return 1;
1665
- });
1666
1338
  const startProgressAnimation = () => {
1667
- setProgressStartTime(Date.now());
1339
+ const startTime = Date.now();
1340
+ setProgressStartTime(startTime);
1668
1341
  setShowProgressIndicator(false);
1669
1342
  progressDelayTimerId = window.setTimeout(() => {
1670
1343
  setShowProgressIndicator(true);
1671
1344
  progressDelayTimerId = null;
1672
1345
  }, PROGRESS_INDICATOR_DELAY_MS);
1673
1346
  const animateProgress = () => {
1674
- if (progressStartTime() === null) return;
1675
- setProgressTick((tick) => tick + 1);
1676
- const currentProgress = progress();
1347
+ const currentStartTime = progressStartTime();
1348
+ if (currentStartTime === null) return;
1349
+ const elapsedTime = Date.now() - currentStartTime;
1350
+ const normalizedTime = elapsedTime / options.keyHoldDuration;
1351
+ const easedProgress = 1 - Math.exp(-normalizedTime);
1352
+ const maxProgressBeforeCompletion = 0.95;
1353
+ const currentProgress = isCopying() ? Math.min(easedProgress, maxProgressBeforeCompletion) : 1;
1354
+ setProgress(currentProgress);
1677
1355
  if (currentProgress < 1) {
1678
1356
  progressAnimationId = requestAnimationFrame(animateProgress);
1679
1357
  }
@@ -1690,6 +1368,7 @@ ${context}
1690
1368
  progressDelayTimerId = null;
1691
1369
  }
1692
1370
  setProgressStartTime(null);
1371
+ setProgress(1);
1693
1372
  setShowProgressIndicator(false);
1694
1373
  };
1695
1374
  const activateRenderer = () => {
@@ -1930,7 +1609,7 @@ ${context}
1930
1609
  get labelVisible() {
1931
1610
  return labelVisible();
1932
1611
  },
1933
- labelZIndex: 2147483646,
1612
+ labelZIndex: Z_INDEX_LABEL,
1934
1613
  get progressVisible() {
1935
1614
  return progressVisible();
1936
1615
  },
@@ -1976,13 +1655,8 @@ ${context}
1976
1655
  // src/index.ts
1977
1656
  var globalApi = null;
1978
1657
  var getGlobalApi = () => globalApi;
1979
- var EXTENSION_MARKER = "__REACT_GRAB_EXTENSION_ACTIVE__";
1980
- if (!window[EXTENSION_MARKER]) {
1981
- globalApi = init();
1982
- }
1658
+ globalApi = init();
1983
1659
 
1984
- exports.elementToMarkdown = elementToMarkdown;
1985
1660
  exports.getGlobalApi = getGlobalApi;
1986
- exports.htmlToMarkdown = htmlToMarkdown;
1987
1661
  exports.init = init;
1988
1662
  exports.playCopySound = playCopySound;