react-grab 0.0.39 → 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,8 +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');
6
+ var source = require('bippy/source');
8
7
 
9
8
  /**
10
9
  * @license MIT
@@ -96,8 +95,12 @@ var mountRoot = () => {
96
95
  var VIEWPORT_MARGIN_PX = 8;
97
96
  var INDICATOR_CLAMP_PADDING_PX = 4;
98
97
  var CURSOR_OFFSET_PX = 14;
98
+ var OFFSCREEN_POSITION = -1e3;
99
99
  var SELECTION_LERP_FACTOR = 0.95;
100
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;
101
104
 
102
105
  // src/utils/lerp.ts
103
106
  var lerp = (start, end, factor) => {
@@ -178,7 +181,7 @@ var SelectionBox = (props) => {
178
181
  position: "fixed",
179
182
  "box-sizing": "border-box",
180
183
  "pointer-events": props.variant === "drag" ? "none" : "auto",
181
- "z-index": "2147483646"
184
+ "z-index": props.variant === "grabbed" ? "2147483645" : "2147483646"
182
185
  };
183
186
  const variantStyle = () => {
184
187
  if (props.variant === "drag") {
@@ -190,10 +193,16 @@ var SelectionBox = (props) => {
190
193
  cursor: "crosshair"
191
194
  };
192
195
  }
196
+ if (props.variant === "selection") {
197
+ return {
198
+ border: "1px dashed rgba(210, 57, 192, 0.5)",
199
+ "background-color": "rgba(210, 57, 192, 0.08)"
200
+ };
201
+ }
193
202
  return {
194
203
  border: "1px solid rgb(210, 57, 192)",
195
- "background-color": "rgba(210, 57, 192, 0.2)",
196
- transition: props.variant === "grabbed" ? "opacity 0.3s ease-out" : void 0
204
+ "background-color": "rgba(210, 57, 192, 0.08)",
205
+ transition: "opacity 0.3s ease-out"
197
206
  };
198
207
  };
199
208
  return web.createComponent(solidJs.Show, {
@@ -256,30 +265,22 @@ var getClampedElementPosition = (positionLeft, positionTop, elementWidth, elemen
256
265
  const clampedTop = Math.max(minTop, Math.min(positionTop, maxTop));
257
266
  return { left: clampedLeft, top: clampedTop };
258
267
  };
259
-
260
- // src/components/label.tsx
261
- var _tmpl$3 = /* @__PURE__ */ web.template(`<span style=display:inline-block;margin-right:4px;font-weight:600>\u2713`);
262
- var _tmpl$22 = /* @__PURE__ */ web.template(`<div style=margin-right:4px>Copied`);
263
- 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">`);
264
- var _tmpl$4 = /* @__PURE__ */ web.template(`<span style=font-variant-numeric:tabular-nums;font-size:10px;margin-left:4px;vertical-align:middle>`);
265
- var _tmpl$5 = /* @__PURE__ */ web.template(`<div style=margin-left:4px>to clipboard`);
266
- 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">`);
267
- var Label = (props) => {
268
- const [opacity, setOpacity] = solidJs.createSignal(0);
269
- const [positionTick, setPositionTick] = solidJs.createSignal(0);
270
- let labelRef;
271
- let currentX = props.x;
272
- let currentY = props.y;
273
- let targetX = props.x;
274
- 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();
275
275
  let animationFrameId = null;
276
276
  let hasBeenRenderedOnce = false;
277
277
  const animate = () => {
278
- currentX = lerp(currentX, targetX, 0.3);
279
- currentY = lerp(currentY, targetY, 0.3);
280
- setPositionTick((tick) => tick + 1);
281
- const hasConvergedToTarget = Math.abs(currentX - targetX) < 0.5 && Math.abs(currentY - targetY) < 0.5;
282
- 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) {
283
284
  animationFrameId = requestAnimationFrame(animate);
284
285
  } else {
285
286
  animationFrameId = null;
@@ -289,36 +290,16 @@ var Label = (props) => {
289
290
  if (animationFrameId !== null) return;
290
291
  animationFrameId = requestAnimationFrame(animate);
291
292
  };
292
- const updateTarget = () => {
293
- targetX = props.x;
294
- targetY = props.y;
293
+ solidJs.createEffect(() => {
294
+ targetX = options.x();
295
+ targetY = options.y();
295
296
  if (!hasBeenRenderedOnce) {
296
- currentX = targetX;
297
- currentY = targetY;
297
+ setX(targetX);
298
+ setY(targetY);
298
299
  hasBeenRenderedOnce = true;
299
- setPositionTick((tick) => tick + 1);
300
300
  return;
301
301
  }
302
302
  startAnimation();
303
- };
304
- solidJs.createEffect(solidJs.on(() => props.visible, (visible) => {
305
- if (visible !== false) {
306
- requestAnimationFrame(() => {
307
- setOpacity(1);
308
- });
309
- } else {
310
- setOpacity(0);
311
- return;
312
- }
313
- if (props.variant === "success") {
314
- const fadeOutTimer = setTimeout(() => {
315
- setOpacity(0);
316
- }, SUCCESS_LABEL_DURATION_MS);
317
- solidJs.onCleanup(() => clearTimeout(fadeOutTimer));
318
- }
319
- }));
320
- solidJs.createEffect(() => {
321
- updateTarget();
322
303
  });
323
304
  solidJs.onCleanup(() => {
324
305
  if (animationFrameId !== null) {
@@ -326,34 +307,89 @@ var Label = (props) => {
326
307
  animationFrameId = null;
327
308
  }
328
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
+ });
329
378
  const labelBoundingRect = () => labelRef?.getBoundingClientRect();
330
379
  const computedPosition = () => {
331
- positionTick();
332
380
  const boundingRect = labelBoundingRect();
333
381
  if (!boundingRect) return {
334
- left: currentX,
335
- top: currentY
382
+ left: position.x(),
383
+ top: position.y()
336
384
  };
337
385
  const viewportWidth = window.innerWidth;
338
386
  const viewportHeight = window.innerHeight;
339
- const quadrants = [{
340
- left: Math.round(currentX) + CURSOR_OFFSET_PX,
341
- top: Math.round(currentY) + CURSOR_OFFSET_PX
342
- }, {
343
- left: Math.round(currentX) - boundingRect.width - CURSOR_OFFSET_PX,
344
- top: Math.round(currentY) + CURSOR_OFFSET_PX
345
- }, {
346
- left: Math.round(currentX) + CURSOR_OFFSET_PX,
347
- top: Math.round(currentY) - boundingRect.height - CURSOR_OFFSET_PX
348
- }, {
349
- left: Math.round(currentX) - boundingRect.width - CURSOR_OFFSET_PX,
350
- top: Math.round(currentY) - boundingRect.height - CURSOR_OFFSET_PX
351
- }];
352
- for (const position of quadrants) {
353
- const fitsHorizontally = position.left >= VIEWPORT_MARGIN_PX && position.left + boundingRect.width <= viewportWidth - VIEWPORT_MARGIN_PX;
354
- 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;
355
391
  if (fitsHorizontally && fitsVertically) {
356
- return position;
392
+ return position2;
357
393
  }
358
394
  }
359
395
  const fallback = getClampedElementPosition(quadrants[0].left, quadrants[0].top, boundingRect.width, boundingRect.height);
@@ -460,21 +496,10 @@ var Label = (props) => {
460
496
  });
461
497
  };
462
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)">`);
463
- var useFadeInOut = (visible) => {
464
- const [opacity, setOpacity] = solidJs.createSignal(0);
465
- solidJs.createEffect(solidJs.on(() => visible, (isVisible) => {
466
- if (isVisible !== false) {
467
- requestAnimationFrame(() => {
468
- setOpacity(1);
469
- });
470
- } else {
471
- setOpacity(0);
472
- }
473
- }));
474
- return opacity;
475
- };
476
499
  var ProgressIndicator = (props) => {
477
- const opacity = useFadeInOut(props.visible);
500
+ const opacity = useFadeInOut({
501
+ visible: props.visible
502
+ });
478
503
  let progressIndicatorRef;
479
504
  const computedPosition = () => {
480
505
  const boundingRect = progressIndicatorRef?.getBoundingClientRect();
@@ -519,12 +544,11 @@ var Crosshair = (props) => {
519
544
  let width = 0;
520
545
  let height = 0;
521
546
  let dpr = 1;
522
- let currentX = props.mouseX;
523
- let currentY = props.mouseY;
524
- let targetX = props.mouseX;
525
- let targetY = props.mouseY;
526
- let animationFrameId = null;
527
- let hasBeenRenderedOnce = false;
547
+ const position = useAnimatedPosition({
548
+ x: () => props.mouseX,
549
+ y: () => props.mouseY,
550
+ lerpFactor: 0.3
551
+ });
528
552
  const setupCanvas = () => {
529
553
  if (!canvasRef) return;
530
554
  dpr = Math.max(window.devicePixelRatio || 1, 2);
@@ -545,39 +569,12 @@ var Crosshair = (props) => {
545
569
  context.strokeStyle = "rgba(210, 57, 192)";
546
570
  context.lineWidth = 1;
547
571
  context.beginPath();
548
- context.moveTo(currentX, 0);
549
- context.lineTo(currentX, height);
550
- context.moveTo(0, currentY);
551
- 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());
552
576
  context.stroke();
553
577
  };
554
- const animate = () => {
555
- currentX = lerp(currentX, targetX, 0.3);
556
- currentY = lerp(currentY, targetY, 0.3);
557
- render2();
558
- const hasConvergedToTarget = Math.abs(currentX - targetX) < 0.5 && Math.abs(currentY - targetY) < 0.5;
559
- if (!hasConvergedToTarget) {
560
- animationFrameId = requestAnimationFrame(animate);
561
- } else {
562
- animationFrameId = null;
563
- }
564
- };
565
- const startAnimation = () => {
566
- if (animationFrameId !== null) return;
567
- animationFrameId = requestAnimationFrame(animate);
568
- };
569
- const updateTarget = () => {
570
- targetX = props.mouseX;
571
- targetY = props.mouseY;
572
- if (!hasBeenRenderedOnce) {
573
- currentX = targetX;
574
- currentY = targetY;
575
- hasBeenRenderedOnce = true;
576
- render2();
577
- return;
578
- }
579
- startAnimation();
580
- };
581
578
  solidJs.createEffect(() => {
582
579
  setupCanvas();
583
580
  render2();
@@ -588,14 +585,12 @@ var Crosshair = (props) => {
588
585
  window.addEventListener("resize", handleResize);
589
586
  solidJs.onCleanup(() => {
590
587
  window.removeEventListener("resize", handleResize);
591
- if (animationFrameId !== null) {
592
- cancelAnimationFrame(animationFrameId);
593
- animationFrameId = null;
594
- }
595
588
  });
596
589
  });
597
590
  solidJs.createEffect(() => {
598
- updateTarget();
591
+ position.x();
592
+ position.y();
593
+ render2();
599
594
  });
600
595
  return web.createComponent(solidJs.Show, {
601
596
  get when() {
@@ -739,27 +734,62 @@ var ReactGrabRenderer = (props) => {
739
734
  var isCapitalized = (value) => value.length > 0 && /^[A-Z]/.test(value);
740
735
 
741
736
  // src/instrumentation.ts
742
- bippy.instrument({
743
- onCommitFiberRoot(_, fiberRoot) {
744
- bippy._fiberRoots.add(fiberRoot);
745
- }
746
- });
747
- var generateCSSSelector = (element) => {
748
- 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;
774
+ };
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;
749
781
  };
750
- var truncateString = (string, maxLength) => string.length > maxLength ? `${string.substring(0, maxLength)}...` : string;
751
- var isInternalComponent = (name) => !isCapitalized(name) || name.startsWith("_") || name.includes("Provider") && name.includes("Context");
752
- var getNearestComponentDisplayName = (element) => {
782
+ var getNearestComponentName = (element) => {
753
783
  const fiber = bippy.getFiberFromHostInstance(element);
754
784
  if (!fiber) return null;
755
- let componentName = null;
785
+ let foundComponentName = null;
756
786
  bippy.traverseFiber(
757
787
  fiber,
758
788
  (currentFiber) => {
759
789
  if (bippy.isCompositeFiber(currentFiber)) {
760
790
  const displayName = bippy.getDisplayName(currentFiber);
761
- if (displayName && !isInternalComponent(displayName)) {
762
- componentName = displayName;
791
+ if (displayName && checkIsSourceComponentName(displayName)) {
792
+ foundComponentName = displayName;
763
793
  return true;
764
794
  }
765
795
  }
@@ -767,213 +797,95 @@ var getNearestComponentDisplayName = (element) => {
767
797
  },
768
798
  true
769
799
  );
770
- return componentName;
800
+ return foundComponentName;
771
801
  };
772
- var formatComponentSourceLocation = async (el) => {
773
- const source$1 = await source.getSourceFromHostInstance(el);
774
- if (!source$1) return null;
775
- const fileName = source.normalizeFileName(source$1.fileName);
776
- if (source.isSourceFile(fileName)) {
777
- return `${fileName}:${source$1.lineNumber}:${source$1.columnNumber}`;
778
- }
779
- if (fileName && (fileName.includes(".tsx") || fileName.includes(".ts") || fileName.includes(".jsx") || fileName.includes(".js"))) {
780
- const cleanedFileName = fileName.replace(/^webpack:\/\/_N_E\//, "").replace(/^webpack:\/\/\//, "").replace(/^webpack:\/\//, "").replace(/^\.\//, "");
781
- if (cleanedFileName && !cleanedFileName.startsWith("node_modules") && !cleanedFileName.includes(".next") && !cleanedFileName.startsWith("webpack")) {
782
- return `${cleanedFileName}:${source$1.lineNumber}:${source$1.columnNumber}`;
783
- }
784
- }
785
- return null;
786
- };
787
- var getHTMLSnippet = async (element) => {
788
- const semanticTags = /* @__PURE__ */ new Set([
789
- "article",
790
- "aside",
791
- "footer",
792
- "form",
793
- "header",
794
- "main",
795
- "nav",
796
- "section"
797
- ]);
798
- const hasDistinguishingFeatures = (el) => {
799
- const tagName = el.tagName.toLowerCase();
800
- if (semanticTags.has(tagName)) return true;
801
- if (el.id) return true;
802
- if (el.className && typeof el.className === "string") {
803
- const classes = el.className.trim();
804
- if (classes && classes.length > 0) return true;
805
- }
806
- return Array.from(el.attributes).some(
807
- (attr) => attr.name.startsWith("data-")
808
- );
809
- };
810
- const collectDistinguishingAncestors = (el, maxDepth = 10) => {
811
- const ancestors2 = [];
812
- let current = el.parentElement;
813
- let depth = 0;
814
- while (current && depth < maxDepth && current.tagName !== "BODY") {
815
- if (hasDistinguishingFeatures(current)) {
816
- ancestors2.push(current);
817
- if (ancestors2.length >= 3) break;
818
- }
819
- current = current.parentElement;
820
- depth++;
821
- }
822
- return ancestors2.reverse();
823
- };
824
- const formatElementOpeningTag = (el, compact = false) => {
825
- const tagName = el.tagName.toLowerCase();
826
- const attrs = [];
827
- if (el.id) {
828
- attrs.push(`id="${el.id}"`);
829
- }
830
- if (el.className && typeof el.className === "string") {
831
- const classes = el.className.trim().split(/\s+/);
832
- if (classes.length > 0 && classes[0]) {
833
- const displayClasses = compact ? classes.slice(0, 3) : classes;
834
- const classStr = truncateString(displayClasses.join(" "), 30);
835
- attrs.push(`class="${classStr}"`);
836
- }
837
- }
838
- const dataAttrs = Array.from(el.attributes).filter(
839
- (attr) => attr.name.startsWith("data-")
840
- );
841
- const displayDataAttrs = compact ? dataAttrs.slice(0, 1) : dataAttrs;
842
- for (const attr of displayDataAttrs) {
843
- attrs.push(`${attr.name}="${truncateString(attr.value, 20)}"`);
844
- }
845
- const ariaLabel = el.getAttribute("aria-label");
846
- if (ariaLabel && !compact) {
847
- attrs.push(`aria-label="${truncateString(ariaLabel, 20)}"`);
848
- }
849
- return attrs.length > 0 ? `<${tagName} ${attrs.join(" ")}>` : `<${tagName}>`;
850
- };
851
- const formatElementClosingTag = (el) => `</${el.tagName.toLowerCase()}>`;
852
- const extractTruncatedTextContent = (el) => {
853
- const text = (el.textContent || "").trim().replace(/\s+/g, " ");
854
- return truncateString(text, 60);
855
- };
856
- const extractSiblingIdentifier = (el) => {
857
- if (el.id) return `#${el.id}`;
858
- if (el.className && typeof el.className === "string") {
859
- const classes = el.className.trim().split(/\s+/);
860
- if (classes.length > 0 && classes[0]) {
861
- 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
+ });
862
816
  }
863
- }
864
- return null;
865
- };
866
- const lines = [];
867
- const selector = generateCSSSelector(element);
868
- lines.push(`- selector: ${selector}`);
869
- const rect = element.getBoundingClientRect();
870
- lines.push(`- width: ${Math.round(rect.width)}`);
871
- lines.push(`- height: ${Math.round(rect.height)}`);
872
- lines.push("HTML snippet:");
873
- lines.push("```html");
874
- const ancestors = collectDistinguishingAncestors(element);
875
- const ancestorComponents = ancestors.map(
876
- (ancestor) => getNearestComponentDisplayName(ancestor)
817
+ },
818
+ true
877
819
  );
878
- const elementComponent = getNearestComponentDisplayName(element);
879
- const ancestorSources = await Promise.all(
880
- 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
+ }))
881
825
  );
882
- const elementSource = await formatComponentSourceLocation(element);
883
- for (let i = 0; i < ancestors.length; i++) {
884
- const indent2 = " ".repeat(i);
885
- const componentName = ancestorComponents[i];
886
- const source = ancestorSources[i];
887
- if (componentName && source && (i === 0 || ancestorComponents[i - 1] !== componentName)) {
888
- lines.push(`${indent2}<${componentName} source="${source}">`);
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)`;
889
834
  }
890
- lines.push(`${indent2}${formatElementOpeningTag(ancestors[i], true)}`);
891
- }
892
- const parent = element.parentElement;
893
- let targetIndex = -1;
894
- if (parent) {
895
- const siblings = Array.from(parent.children);
896
- targetIndex = siblings.indexOf(element);
897
- if (targetIndex > 0) {
898
- const indent2 = " ".repeat(ancestors.length);
899
- if (targetIndex <= 2) {
900
- for (let i = 0; i < targetIndex; i++) {
901
- const sibling = siblings[i];
902
- const siblingId = extractSiblingIdentifier(sibling);
903
- if (siblingId) {
904
- lines.push(`${indent2} ${formatElementOpeningTag(sibling, true)}`);
905
- lines.push(`${indent2} </${sibling.tagName.toLowerCase()}>`);
906
- }
907
- }
908
- } else {
909
- lines.push(
910
- `${indent2} ... (${targetIndex} element${targetIndex === 1 ? "" : "s"})`
911
- );
912
- }
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}`;
913
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} />`;
914
847
  }
915
- const indent = " ".repeat(ancestors.length);
916
- const lastAncestorComponent = ancestors.length > 0 ? ancestorComponents[ancestorComponents.length - 1] : null;
917
- const showElementComponent = elementComponent && elementSource && elementComponent !== lastAncestorComponent;
918
- if (showElementComponent) {
919
- lines.push(`${indent} <${elementComponent} used-at="${elementSource}">`);
920
- }
921
- lines.push(`${indent} <!-- IMPORTANT: selected element -->`);
922
- const textContent = extractTruncatedTextContent(element);
923
- const childrenCount = element.children.length;
924
- const elementIndent = `${indent}${showElementComponent ? " " : " "}`;
925
- if (textContent && childrenCount === 0 && textContent.length < 40) {
926
- lines.push(
927
- `${elementIndent}${formatElementOpeningTag(element)}${textContent}${formatElementClosingTag(
928
- element
929
- )}`
930
- );
931
- } else {
932
- lines.push(`${elementIndent}${formatElementOpeningTag(element)}`);
933
- if (textContent) {
934
- lines.push(`${elementIndent} ${textContent}`);
935
- }
936
- if (childrenCount > 0) {
937
- lines.push(
938
- `${elementIndent} ... (${childrenCount} element${childrenCount === 1 ? "" : "s"})`
939
- );
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)}...`;
940
856
  }
941
- lines.push(`${elementIndent}${formatElementClosingTag(element)}`);
942
- }
943
- if (showElementComponent) {
944
- lines.push(`${indent} </${elementComponent}>`);
857
+ attrsText += ` ${name}="${value}"`;
945
858
  }
946
- if (parent && targetIndex >= 0) {
947
- const siblings = Array.from(parent.children);
948
- const siblingsAfter = siblings.length - targetIndex - 1;
949
- if (siblingsAfter > 0) {
950
- if (siblingsAfter <= 2) {
951
- for (let i = targetIndex + 1; i < siblings.length; i++) {
952
- const sibling = siblings[i];
953
- const siblingId = extractSiblingIdentifier(sibling);
954
- if (siblingId) {
955
- lines.push(`${indent} ${formatElementOpeningTag(sibling, true)}`);
956
- lines.push(`${indent} </${sibling.tagName.toLowerCase()}>`);
957
- }
958
- }
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++;
959
872
  } else {
960
- lines.push(
961
- `${indent} ... (${siblingsAfter} element${siblingsAfter === 1 ? "" : "s"})`
962
- );
873
+ bottomElements++;
963
874
  }
964
875
  }
965
876
  }
966
- for (let i = ancestors.length - 1; i >= 0; i--) {
967
- const indent2 = " ".repeat(i);
968
- lines.push(`${indent2}${formatElementClosingTag(ancestors[i])}`);
969
- const componentName = ancestorComponents[i];
970
- const source = ancestorSources[i];
971
- if (componentName && source && (i === ancestors.length - 1 || ancestorComponents[i + 1] !== componentName)) {
972
- lines.push(`${indent2}</${componentName}>`);
973
- }
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}>`;
974
887
  }
975
- lines.push("```");
976
- return lines.join("\n");
888
+ return `<${tagName}${attrsText} />`;
977
889
  };
978
890
 
979
891
  // src/utils/copy-content.ts
@@ -993,73 +905,13 @@ var waitForFocus = () => {
993
905
  var copyContent = async (content, onSuccess) => {
994
906
  await waitForFocus();
995
907
  try {
996
- if (Array.isArray(content)) {
997
- if (!navigator?.clipboard?.write) {
998
- for (const contentPart of content) {
999
- if (typeof contentPart === "string") {
1000
- const result = copyContentFallback(contentPart, onSuccess);
1001
- if (!result) return result;
1002
- }
1003
- }
1004
- onSuccess?.();
1005
- return true;
1006
- }
1007
- const mimeTypeMap = /* @__PURE__ */ new Map();
1008
- for (const contentPart of content) {
1009
- if (contentPart instanceof Blob) {
1010
- const mimeType = contentPart.type || "text/plain";
1011
- if (!mimeTypeMap.has(mimeType)) {
1012
- mimeTypeMap.set(mimeType, contentPart);
1013
- }
1014
- } else {
1015
- if (!mimeTypeMap.has("text/plain")) {
1016
- mimeTypeMap.set(
1017
- "text/plain",
1018
- new Blob([contentPart], { type: "text/plain" })
1019
- );
1020
- }
1021
- }
1022
- }
1023
- if (mimeTypeMap.size === 0) {
1024
- const plainTextFallback = content.find(
1025
- (contentPart) => typeof contentPart === "string"
1026
- );
1027
- if (typeof plainTextFallback === "string") {
1028
- return copyContentFallback(plainTextFallback, onSuccess);
1029
- }
1030
- return false;
1031
- }
1032
- try {
1033
- await navigator.clipboard.write([
1034
- new ClipboardItem(Object.fromEntries(mimeTypeMap))
1035
- ]);
1036
- onSuccess?.();
1037
- return true;
1038
- } catch {
1039
- const plainTextParts = content.filter(
1040
- (contentPart) => typeof contentPart === "string"
1041
- );
1042
- if (plainTextParts.length > 0) {
1043
- const combinedText = plainTextParts.join("\n\n");
1044
- return copyContentFallback(combinedText, onSuccess);
1045
- }
1046
- return false;
1047
- }
1048
- } else if (content instanceof Blob) {
1049
- await navigator.clipboard.write([
1050
- new ClipboardItem({ [content.type]: content })
1051
- ]);
908
+ try {
909
+ await navigator.clipboard.writeText(content);
1052
910
  onSuccess?.();
1053
911
  return true;
1054
- } else {
1055
- try {
1056
- await navigator.clipboard.writeText(String(content));
1057
- onSuccess?.();
1058
- return true;
1059
- } catch {
1060
- const result = copyContentFallback(content, onSuccess);
1061
- return result;
1062
- }
912
+ } catch {
913
+ const result = copyContentFallback(content, onSuccess);
914
+ return result;
1063
915
  }
1064
916
  } catch {
1065
917
  return false;
@@ -1148,13 +1000,27 @@ var getElementAtPosition = (clientX, clientY) => {
1148
1000
 
1149
1001
  // src/utils/get-elements-in-drag.ts
1150
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
+ };
1151
1015
  var filterElementsInDrag = (dragRect, isValidGrabbableElement2, shouldCheckCoverage) => {
1152
1016
  const elements = [];
1153
1017
  const allElements = Array.from(document.querySelectorAll("*"));
1154
- const dragLeft = dragRect.x;
1155
- const dragTop = dragRect.y;
1156
- const dragRight = dragRect.x + dragRect.width;
1157
- 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
+ };
1158
1024
  for (const candidateElement of allElements) {
1159
1025
  if (!shouldCheckCoverage) {
1160
1026
  const tagName = (candidateElement.tagName || "").toUpperCase();
@@ -1164,26 +1030,21 @@ var filterElementsInDrag = (dragRect, isValidGrabbableElement2, shouldCheckCover
1164
1030
  continue;
1165
1031
  }
1166
1032
  const elementRect = candidateElement.getBoundingClientRect();
1167
- const elementLeft = elementRect.left;
1168
- const elementTop = elementRect.top;
1169
- const elementRight = elementRect.left + elementRect.width;
1170
- 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
+ };
1171
1039
  if (shouldCheckCoverage) {
1172
- const intersectionLeft = Math.max(dragLeft, elementLeft);
1173
- const intersectionTop = Math.max(dragTop, elementTop);
1174
- const intersectionRight = Math.min(dragRight, elementRight);
1175
- const intersectionBottom = Math.min(dragBottom, elementBottom);
1176
- const intersectionWidth = Math.max(0, intersectionRight - intersectionLeft);
1177
- const intersectionHeight = Math.max(0, intersectionBottom - intersectionTop);
1178
- const intersectionArea = intersectionWidth * intersectionHeight;
1040
+ const intersectionArea = calculateIntersectionArea(dragBounds, elementBounds);
1179
1041
  const elementArea = Math.max(0, elementRect.width * elementRect.height);
1180
1042
  const hasMajorityCoverage = elementArea > 0 && intersectionArea / elementArea >= DRAG_COVERAGE_THRESHOLD;
1181
1043
  if (hasMajorityCoverage) {
1182
1044
  elements.push(candidateElement);
1183
1045
  }
1184
1046
  } else {
1185
- const hasIntersection = elementLeft < dragRight && elementRight > dragLeft && elementTop < dragBottom && elementBottom > dragTop;
1186
- if (hasIntersection) {
1047
+ if (hasIntersection(elementBounds, dragBounds)) {
1187
1048
  elements.push(candidateElement);
1188
1049
  }
1189
1050
  }
@@ -1222,14 +1083,7 @@ var createElementBounds = (element) => {
1222
1083
  };
1223
1084
  };
1224
1085
 
1225
- // src/utils/is-localhost.ts
1226
- var isLocalhost = () => {
1227
- const hostname = window.location.hostname;
1228
- return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]";
1229
- };
1230
-
1231
1086
  // src/core.tsx
1232
- var PROGRESS_INDICATOR_DELAY_MS = 150;
1233
1087
  var init = (rawOptions) => {
1234
1088
  const options = {
1235
1089
  enabled: true,
@@ -1252,7 +1106,6 @@ var init = (rawOptions) => {
1252
1106
  };
1253
1107
  }
1254
1108
  return solidJs.createRoot((dispose) => {
1255
- const OFFSCREEN_POSITION = -1e3;
1256
1109
  const [isHoldingKeys, setIsHoldingKeys] = solidJs.createSignal(false);
1257
1110
  const [mouseX, setMouseX] = solidJs.createSignal(OFFSCREEN_POSITION);
1258
1111
  const [mouseY, setMouseY] = solidJs.createSignal(OFFSCREEN_POSITION);
@@ -1262,10 +1115,11 @@ var init = (rawOptions) => {
1262
1115
  const [isCopying, setIsCopying] = solidJs.createSignal(false);
1263
1116
  const [lastGrabbedElement, setLastGrabbedElement] = solidJs.createSignal(null);
1264
1117
  const [progressStartTime, setProgressStartTime] = solidJs.createSignal(null);
1265
- const [progressTick, setProgressTick] = solidJs.createSignal(0);
1118
+ const [progress, setProgress] = solidJs.createSignal(0);
1266
1119
  const [grabbedBoxes, setGrabbedBoxes] = solidJs.createSignal([]);
1267
1120
  const [successLabels, setSuccessLabels] = solidJs.createSignal([]);
1268
1121
  const [isActivated, setIsActivated] = solidJs.createSignal(false);
1122
+ const [isToggleMode, setIsToggleMode] = solidJs.createSignal(false);
1269
1123
  const [showProgressIndicator, setShowProgressIndicator] = solidJs.createSignal(false);
1270
1124
  const [didJustDrag, setDidJustDrag] = solidJs.createSignal(false);
1271
1125
  const [copyStartX, setCopyStartX] = solidJs.createSignal(OFFSCREEN_POSITION);
@@ -1308,55 +1162,10 @@ var init = (rawOptions) => {
1308
1162
  const wrapInSelectedElementTags = (context) => `<selected_element>
1309
1163
  ${context}
1310
1164
  </selected_element>`;
1311
- const extractRelevantComputedStyles = (element) => {
1312
- const computed = window.getComputedStyle(element);
1313
- const rect = element.getBoundingClientRect();
1314
- return {
1315
- width: `${Math.round(rect.width)}px`,
1316
- height: `${Math.round(rect.height)}px`,
1317
- paddingTop: computed.paddingTop,
1318
- paddingRight: computed.paddingRight,
1319
- paddingBottom: computed.paddingBottom,
1320
- paddingLeft: computed.paddingLeft,
1321
- background: computed.background,
1322
- opacity: computed.opacity
1323
- };
1324
- };
1325
- const createStructuredClipboardHtmlBlob = (elements) => {
1326
- const structuredData = {
1327
- elements: elements.map((element) => ({
1328
- tagName: element.tagName,
1329
- content: element.content,
1330
- computedStyles: element.computedStyles
1331
- }))
1332
- };
1333
- const jsonString = JSON.stringify(structuredData);
1334
- const base64Data = btoa(encodeURIComponent(jsonString).replace(/%([0-9A-F]{2})/g, (_match, p1) => String.fromCharCode(parseInt(p1, 16))));
1335
- const htmlContent = `<div data-react-grab="${base64Data}"></div>`;
1336
- return new Blob([htmlContent], {
1337
- type: "text/html"
1338
- });
1339
- };
1340
1165
  const extractElementTagName = (element) => (element.tagName || "").toLowerCase();
1341
- const extractNearestComponentName = (element) => {
1342
- const fiber = bippy.getFiberFromHostInstance(element);
1343
- if (!fiber) return null;
1344
- let componentName = null;
1345
- bippy.traverseFiber(fiber, (currentFiber) => {
1346
- if (bippy.isCompositeFiber(currentFiber)) {
1347
- const displayName = bippy.getDisplayName(currentFiber);
1348
- if (displayName && isCapitalized(displayName) && !displayName.startsWith("_")) {
1349
- componentName = displayName;
1350
- return true;
1351
- }
1352
- }
1353
- return false;
1354
- }, true);
1355
- return componentName;
1356
- };
1357
1166
  const extractElementLabelText = (element) => {
1358
1167
  const tagName = extractElementTagName(element);
1359
- const componentName = extractNearestComponentName(element);
1168
+ const componentName = getNearestComponentName(element);
1360
1169
  if (tagName && componentName) {
1361
1170
  return `<${tagName}> in ${componentName}`;
1362
1171
  }
@@ -1378,8 +1187,6 @@ ${context}
1378
1187
  } catch {
1379
1188
  }
1380
1189
  };
1381
- const isExtensionEnvironment = () => window.__REACT_GRAB_EXTENSION_ACTIVE__ === true || options.isExtension === true;
1382
- const isExtensionTextOnlyMode = () => isExtensionEnvironment() && !isLocalhost();
1383
1190
  const executeCopyOperation = async (positionX, positionY, operation) => {
1384
1191
  setCopyStartX(positionX);
1385
1192
  setCopyStartY(positionY);
@@ -1388,6 +1195,13 @@ ${context}
1388
1195
  await operation().finally(() => {
1389
1196
  setIsCopying(false);
1390
1197
  stopProgressAnimation();
1198
+ if (isToggleMode()) {
1199
+ if (!isHoldingKeys()) {
1200
+ deactivateRenderer();
1201
+ } else {
1202
+ setIsToggleMode(false);
1203
+ }
1204
+ }
1391
1205
  });
1392
1206
  };
1393
1207
  const hasInnerText = (element) => "innerText" in element;
@@ -1398,38 +1212,37 @@ ${context}
1398
1212
  return element.textContent ?? "";
1399
1213
  };
1400
1214
  const createCombinedTextContent = (elements) => elements.map((element) => extractElementTextContent(element).trim()).filter((textContent) => textContent.length > 0).join("\n\n");
1401
- const copySingleElementToClipboard = async (targetElement2) => {
1402
- showTemporaryGrabbedBox(createElementBounds(targetElement2));
1403
- await new Promise((resolve) => requestAnimationFrame(resolve));
1215
+ const tryCopyWithFallback = async (elements) => {
1404
1216
  let didCopy = false;
1405
1217
  try {
1406
- if (isExtensionTextOnlyMode()) {
1407
- const plainTextContent = createCombinedTextContent([targetElement2]);
1408
- if (plainTextContent.length > 0) {
1409
- didCopy = await copyContent(plainTextContent, options.playCopySound ? playCopySound : void 0);
1410
- }
1411
- } else {
1412
- const content = await getHTMLSnippet(targetElement2);
1413
- const plainTextContent = wrapInSelectedElementTags(content);
1414
- const htmlContent = createStructuredClipboardHtmlBlob([{
1415
- tagName: extractElementTagName(targetElement2),
1416
- content,
1417
- computedStyles: extractRelevantComputedStyles(targetElement2)
1418
- }]);
1419
- didCopy = await copyContent([plainTextContent, htmlContent], options.playCopySound ? playCopySound : void 0);
1420
- if (!didCopy) {
1421
- const plainTextContentOnly = createCombinedTextContent([targetElement2]);
1422
- if (plainTextContentOnly.length > 0) {
1423
- didCopy = await copyContent(plainTextContentOnly, options.playCopySound ? playCopySound : void 0);
1424
- }
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);
1425
1232
  }
1426
1233
  }
1427
1234
  } catch {
1428
- const plainTextContentOnly = createCombinedTextContent([targetElement2]);
1235
+ const plainTextContentOnly = createCombinedTextContent(elements);
1429
1236
  if (plainTextContentOnly.length > 0) {
1430
1237
  didCopy = await copyContent(plainTextContentOnly, options.playCopySound ? playCopySound : void 0);
1431
1238
  }
1432
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]);
1433
1246
  if (didCopy) {
1434
1247
  showTemporarySuccessLabel(extractElementLabelText(targetElement2));
1435
1248
  }
@@ -1441,40 +1254,7 @@ ${context}
1441
1254
  showTemporaryGrabbedBox(createElementBounds(element));
1442
1255
  }
1443
1256
  await new Promise((resolve) => requestAnimationFrame(resolve));
1444
- let didCopy = false;
1445
- try {
1446
- if (isExtensionTextOnlyMode()) {
1447
- const plainTextContent = createCombinedTextContent(targetElements);
1448
- if (plainTextContent.length > 0) {
1449
- didCopy = await copyContent(plainTextContent, options.playCopySound ? playCopySound : void 0);
1450
- }
1451
- } else {
1452
- const elementSnippetResults = await Promise.allSettled(targetElements.map((element) => getHTMLSnippet(element)));
1453
- const elementSnippets = elementSnippetResults.map((result) => result.status === "fulfilled" ? result.value : "").filter((snippet) => snippet.trim());
1454
- if (elementSnippets.length > 0) {
1455
- const plainTextContent = elementSnippets.map((snippet) => wrapInSelectedElementTags(snippet)).join("\n\n");
1456
- const structuredElements = elementSnippets.map((content, elementIndex) => ({
1457
- tagName: extractElementTagName(targetElements[elementIndex]),
1458
- content,
1459
- computedStyles: extractRelevantComputedStyles(targetElements[elementIndex])
1460
- }));
1461
- const htmlContent = createStructuredClipboardHtmlBlob(structuredElements);
1462
- didCopy = await copyContent([plainTextContent, htmlContent], options.playCopySound ? playCopySound : void 0);
1463
- if (!didCopy) {
1464
- const plainTextContentOnly = createCombinedTextContent(targetElements);
1465
- if (plainTextContentOnly.length > 0) {
1466
- didCopy = await copyContent(plainTextContentOnly, options.playCopySound ? playCopySound : void 0);
1467
- }
1468
- }
1469
- } else {
1470
- const plainTextContentOnly = createCombinedTextContent(targetElements);
1471
- if (plainTextContentOnly.length > 0) {
1472
- didCopy = await copyContent(plainTextContentOnly, options.playCopySound ? playCopySound : void 0);
1473
- }
1474
- }
1475
- }
1476
- } catch {
1477
- }
1257
+ const didCopy = await tryCopyWithFallback(targetElements);
1478
1258
  if (didCopy) {
1479
1259
  showTemporarySuccessLabel(`${targetElements.length} elements`);
1480
1260
  }
@@ -1498,7 +1278,6 @@ ${context}
1498
1278
  y: elementBounds.top
1499
1279
  };
1500
1280
  });
1501
- const DRAG_THRESHOLD_PX = 2;
1502
1281
  const calculateDragDistance = (endX, endY) => ({
1503
1282
  x: Math.abs(endX - dragStartX()),
1504
1283
  y: Math.abs(endY - dragStartY())
@@ -1556,30 +1335,23 @@ ${context}
1556
1335
  setLastGrabbedElement(null);
1557
1336
  }
1558
1337
  }));
1559
- const progress = solidJs.createMemo(() => {
1560
- const startTime = progressStartTime();
1561
- progressTick();
1562
- if (startTime === null) return 0;
1563
- const elapsedTime = Date.now() - startTime;
1564
- const normalizedTime = elapsedTime / options.keyHoldDuration;
1565
- const easedProgress = 1 - Math.exp(-normalizedTime);
1566
- const maxProgressBeforeCompletion = 0.95;
1567
- if (isCopying()) {
1568
- return Math.min(easedProgress, maxProgressBeforeCompletion);
1569
- }
1570
- return 1;
1571
- });
1572
1338
  const startProgressAnimation = () => {
1573
- setProgressStartTime(Date.now());
1339
+ const startTime = Date.now();
1340
+ setProgressStartTime(startTime);
1574
1341
  setShowProgressIndicator(false);
1575
1342
  progressDelayTimerId = window.setTimeout(() => {
1576
1343
  setShowProgressIndicator(true);
1577
1344
  progressDelayTimerId = null;
1578
1345
  }, PROGRESS_INDICATOR_DELAY_MS);
1579
1346
  const animateProgress = () => {
1580
- if (progressStartTime() === null) return;
1581
- setProgressTick((tick) => tick + 1);
1582
- 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);
1583
1355
  if (currentProgress < 1) {
1584
1356
  progressAnimationId = requestAnimationFrame(animateProgress);
1585
1357
  }
@@ -1596,6 +1368,7 @@ ${context}
1596
1368
  progressDelayTimerId = null;
1597
1369
  }
1598
1370
  setProgressStartTime(null);
1371
+ setProgress(1);
1599
1372
  setShowProgressIndicator(false);
1600
1373
  };
1601
1374
  const activateRenderer = () => {
@@ -1604,6 +1377,7 @@ ${context}
1604
1377
  document.body.style.cursor = "crosshair";
1605
1378
  };
1606
1379
  const deactivateRenderer = () => {
1380
+ setIsToggleMode(false);
1607
1381
  setIsHoldingKeys(false);
1608
1382
  setIsActivated(false);
1609
1383
  document.body.style.cursor = "";
@@ -1629,11 +1403,25 @@ ${context}
1629
1403
  deactivateRenderer();
1630
1404
  return;
1631
1405
  }
1406
+ if (event.key === "Enter" && isHoldingKeys()) {
1407
+ setIsToggleMode(true);
1408
+ if (keydownSpamTimerId !== null) {
1409
+ window.clearTimeout(keydownSpamTimerId);
1410
+ keydownSpamTimerId = null;
1411
+ }
1412
+ if (!isActivated()) {
1413
+ if (holdTimerId) window.clearTimeout(holdTimerId);
1414
+ activateRenderer();
1415
+ options.onActivate?.();
1416
+ }
1417
+ return;
1418
+ }
1632
1419
  if (!options.allowActivationInsideInput && isKeyboardEventTriggeredByInput(event)) {
1633
1420
  return;
1634
1421
  }
1635
1422
  if (!isTargetKeyCombination(event)) return;
1636
1423
  if (isActivated()) {
1424
+ if (isToggleMode()) return;
1637
1425
  if (keydownSpamTimerId !== null) {
1638
1426
  window.clearTimeout(keydownSpamTimerId);
1639
1427
  }
@@ -1654,13 +1442,15 @@ ${context}
1654
1442
  options.onActivate?.();
1655
1443
  }, options.keyHoldDuration);
1656
1444
  }, {
1657
- signal: eventListenerSignal
1445
+ signal: eventListenerSignal,
1446
+ capture: true
1658
1447
  });
1659
1448
  window.addEventListener("keyup", (event) => {
1660
1449
  if (!isHoldingKeys() && !isActivated()) return;
1661
1450
  const isReleasingModifier = !event.metaKey && !event.ctrlKey;
1662
1451
  const isReleasingC = event.key.toLowerCase() === "c";
1663
1452
  if (isReleasingC || isReleasingModifier) {
1453
+ if (isToggleMode()) return;
1664
1454
  deactivateRenderer();
1665
1455
  }
1666
1456
  }, {
@@ -1742,9 +1532,17 @@ ${context}
1742
1532
  if (isRendererActive() || isCopying() || didJustDrag()) {
1743
1533
  event.preventDefault();
1744
1534
  event.stopPropagation();
1745
- if (didJustDrag()) {
1535
+ const hadDrag = didJustDrag();
1536
+ if (hadDrag) {
1746
1537
  setDidJustDrag(false);
1747
1538
  }
1539
+ if (isToggleMode() && !isCopying()) {
1540
+ if (!isHoldingKeys()) {
1541
+ deactivateRenderer();
1542
+ } else {
1543
+ setIsToggleMode(false);
1544
+ }
1545
+ }
1748
1546
  }
1749
1547
  }, {
1750
1548
  signal: eventListenerSignal,
@@ -1767,10 +1565,14 @@ ${context}
1767
1565
  document.body.style.cursor = "";
1768
1566
  });
1769
1567
  const rendererRoot = mountRoot();
1770
- const selectionVisible = solidJs.createMemo(() => false);
1568
+ const selectionVisible = solidJs.createMemo(() => isRendererActive() && !isDragging() && Boolean(targetElement()));
1771
1569
  const dragVisible = solidJs.createMemo(() => isRendererActive() && isDraggingBeyondThreshold());
1772
1570
  const labelVariant = solidJs.createMemo(() => isCopying() ? "processing" : "hover");
1773
- const labelVisible = solidJs.createMemo(() => isRendererActive() && !isDragging() && mouseHasSettled() && (Boolean(targetElement()) && !isSameAsLast() || !targetElement()) || isCopying());
1571
+ const labelVisible = solidJs.createMemo(() => {
1572
+ if (isCopying()) return true;
1573
+ if (successLabels().length > 0) return false;
1574
+ return isRendererActive() && !isDragging() && mouseHasSettled() && (Boolean(targetElement()) && !isSameAsLast() || !targetElement());
1575
+ });
1774
1576
  const progressVisible = solidJs.createMemo(() => isCopying() && showProgressIndicator() && hasValidMousePosition());
1775
1577
  const crosshairVisible = solidJs.createMemo(() => isRendererActive() && !isDragging());
1776
1578
  web.render(() => web.createComponent(ReactGrabRenderer, {
@@ -1807,6 +1609,7 @@ ${context}
1807
1609
  get labelVisible() {
1808
1610
  return labelVisible();
1809
1611
  },
1612
+ labelZIndex: Z_INDEX_LABEL,
1810
1613
  get progressVisible() {
1811
1614
  return progressVisible();
1812
1615
  },
@@ -1852,10 +1655,7 @@ ${context}
1852
1655
  // src/index.ts
1853
1656
  var globalApi = null;
1854
1657
  var getGlobalApi = () => globalApi;
1855
- var EXTENSION_MARKER = "__REACT_GRAB_EXTENSION_ACTIVE__";
1856
- if (!window[EXTENSION_MARKER]) {
1857
- globalApi = init();
1858
- }
1658
+ globalApi = init();
1859
1659
 
1860
1660
  exports.getGlobalApi = getGlobalApi;
1861
1661
  exports.init = init;