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