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