react-grab 0.0.25 → 0.0.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
- import { render, createComponent, memo, template, effect, style, setStyleProperty, insert, use } from 'solid-js/web';
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
3
  import { instrument, _fiberRoots, getFiberFromHostInstance } from 'bippy';
4
- import { getOwnerStack, getSourcesFromStack } from 'bippy/dist/source';
4
+ import { getOwnerStack, getSourcesFromStack, isSourceFile, normalizeFileName } from 'bippy/dist/source';
5
5
 
6
6
  /**
7
7
  * @license MIT
@@ -110,11 +110,12 @@ var SelectionBox = (props) => {
110
110
  const [opacity, setOpacity] = createSignal(1);
111
111
  let hasBeenRenderedOnce = false;
112
112
  let animationFrameId = null;
113
+ let fadeTimerId = null;
113
114
  let targetBounds = props.bounds;
114
115
  let isAnimating = false;
115
116
  const lerpFactor = () => {
116
117
  if (props.lerpFactor !== void 0) return props.lerpFactor;
117
- if (props.variant === "drag") return 0.9;
118
+ if (props.variant === "drag") return 0.7;
118
119
  return SELECTION_LERP_FACTOR;
119
120
  };
120
121
  const startAnimation = () => {
@@ -151,11 +152,22 @@ var SelectionBox = (props) => {
151
152
  }
152
153
  startAnimation();
153
154
  }));
155
+ createEffect(() => {
156
+ if (props.variant === "grabbed" && props.createdAt) {
157
+ fadeTimerId = window.setTimeout(() => {
158
+ setOpacity(0);
159
+ }, 1500);
160
+ }
161
+ });
154
162
  onCleanup(() => {
155
163
  if (animationFrameId !== null) {
156
164
  cancelAnimationFrame(animationFrameId);
157
165
  animationFrameId = null;
158
166
  }
167
+ if (fadeTimerId !== null) {
168
+ window.clearTimeout(fadeTimerId);
169
+ fadeTimerId = null;
170
+ }
159
171
  isAnimating = false;
160
172
  });
161
173
  const baseStyle = {
@@ -167,8 +179,8 @@ var SelectionBox = (props) => {
167
179
  const variantStyle = () => {
168
180
  if (props.variant === "drag") {
169
181
  return {
170
- border: "1px dashed rgb(210, 57, 192)",
171
- "background-color": "rgba(210, 57, 192, 0.15)",
182
+ border: "1px dashed rgba(210, 57, 192, 0.4)",
183
+ "background-color": "rgba(210, 57, 192, 0.05)",
172
184
  "will-change": "transform, width, height",
173
185
  contain: "layout paint size",
174
186
  cursor: "crosshair"
@@ -243,11 +255,47 @@ var getClampedElementPosition = (positionLeft, positionTop, elementWidth, elemen
243
255
 
244
256
  // src/components/label.tsx
245
257
  var _tmpl$3 = /* @__PURE__ */ template(`<span style=display:inline-block;margin-right:4px;font-weight:600>\u2713`);
246
- var _tmpl$22 = /* @__PURE__ */ template(`<div style=margin-right:4px>Grabbed`);
247
- var _tmpl$32 = /* @__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"><span style="font-family:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;font-variant-numeric:tabular-nums">`);
258
+ var _tmpl$22 = /* @__PURE__ */ template(`<div style=margin-right:4px>Copied`);
259
+ var _tmpl$32 = /* @__PURE__ */ template(`<div style=margin-left:4px>to clipboard`);
260
+ var _tmpl$4 = /* @__PURE__ */ template(`<div style="position:fixed;padding:2px 6px;background-color:#fde7f7;color:#b21c8e;border:1px solid #f7c5ec;border-radius:4px;font-size:11px;font-weight:500;font-family:-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;pointer-events:none;transition:opacity 0.2s ease-in-out;display:flex;align-items:center;max-width:calc(100vw - (16px + env(safe-area-inset-left) + env(safe-area-inset-right)));overflow:hidden;text-overflow:ellipsis;white-space:nowrap">`);
261
+ var _tmpl$5 = /* @__PURE__ */ template(`<span style="font-family:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;font-variant-numeric:tabular-nums">`);
248
262
  var Label = (props) => {
249
263
  const [opacity, setOpacity] = createSignal(0);
264
+ const [positionTick, setPositionTick] = createSignal(0);
250
265
  let labelRef;
266
+ let currentX = props.x;
267
+ let currentY = props.y;
268
+ let targetX = props.x;
269
+ let targetY = props.y;
270
+ let animationFrameId = null;
271
+ let hasBeenRenderedOnce = false;
272
+ const animate = () => {
273
+ currentX = lerp(currentX, targetX, 0.3);
274
+ currentY = lerp(currentY, targetY, 0.3);
275
+ setPositionTick((tick) => tick + 1);
276
+ const hasConvergedToTarget = Math.abs(currentX - targetX) < 0.5 && Math.abs(currentY - targetY) < 0.5;
277
+ if (!hasConvergedToTarget) {
278
+ animationFrameId = requestAnimationFrame(animate);
279
+ } else {
280
+ animationFrameId = null;
281
+ }
282
+ };
283
+ const startAnimation = () => {
284
+ if (animationFrameId !== null) return;
285
+ animationFrameId = requestAnimationFrame(animate);
286
+ };
287
+ const updateTarget = () => {
288
+ targetX = props.x;
289
+ targetY = props.y;
290
+ if (!hasBeenRenderedOnce) {
291
+ currentX = targetX;
292
+ currentY = targetY;
293
+ hasBeenRenderedOnce = true;
294
+ setPositionTick((tick) => tick + 1);
295
+ return;
296
+ }
297
+ startAnimation();
298
+ };
251
299
  createEffect(on(() => props.visible, (visible) => {
252
300
  if (visible !== false) {
253
301
  requestAnimationFrame(() => {
@@ -264,16 +312,26 @@ var Label = (props) => {
264
312
  onCleanup(() => clearTimeout(fadeOutTimer));
265
313
  }
266
314
  }));
315
+ createEffect(() => {
316
+ updateTarget();
317
+ });
318
+ onCleanup(() => {
319
+ if (animationFrameId !== null) {
320
+ cancelAnimationFrame(animationFrameId);
321
+ animationFrameId = null;
322
+ }
323
+ });
267
324
  const labelBoundingRect = () => labelRef?.getBoundingClientRect();
268
325
  const computedPosition = () => {
326
+ positionTick();
269
327
  const boundingRect = labelBoundingRect();
270
328
  if (!boundingRect) return {
271
- left: props.x,
272
- top: props.y
329
+ left: currentX,
330
+ top: currentY
273
331
  };
274
332
  if (props.variant === "success") {
275
- const indicatorLeft = Math.round(props.x);
276
- const indicatorTop = Math.round(props.y) - boundingRect.height - 6;
333
+ const indicatorLeft = Math.round(currentX);
334
+ const indicatorTop = Math.round(currentY) - boundingRect.height - 6;
277
335
  const willClampLeft = indicatorLeft < VIEWPORT_MARGIN_PX;
278
336
  const willClampTop = indicatorTop < VIEWPORT_MARGIN_PX;
279
337
  const isClamped = willClampLeft || willClampTop;
@@ -288,17 +346,17 @@ var Label = (props) => {
288
346
  const viewportWidth = window.innerWidth;
289
347
  const viewportHeight = window.innerHeight;
290
348
  const quadrants = [{
291
- left: Math.round(props.x) + CROSSHAIR_OFFSET,
292
- top: Math.round(props.y) + CROSSHAIR_OFFSET
349
+ left: Math.round(currentX) + CROSSHAIR_OFFSET,
350
+ top: Math.round(currentY) + CROSSHAIR_OFFSET
293
351
  }, {
294
- left: Math.round(props.x) - boundingRect.width - CROSSHAIR_OFFSET,
295
- top: Math.round(props.y) + CROSSHAIR_OFFSET
352
+ left: Math.round(currentX) - boundingRect.width - CROSSHAIR_OFFSET,
353
+ top: Math.round(currentY) + CROSSHAIR_OFFSET
296
354
  }, {
297
- left: Math.round(props.x) + CROSSHAIR_OFFSET,
298
- top: Math.round(props.y) - boundingRect.height - CROSSHAIR_OFFSET
355
+ left: Math.round(currentX) + CROSSHAIR_OFFSET,
356
+ top: Math.round(currentY) - boundingRect.height - CROSSHAIR_OFFSET
299
357
  }, {
300
- left: Math.round(props.x) - boundingRect.width - CROSSHAIR_OFFSET,
301
- top: Math.round(props.y) - boundingRect.height - CROSSHAIR_OFFSET
358
+ left: Math.round(currentX) - boundingRect.width - CROSSHAIR_OFFSET,
359
+ top: Math.round(currentY) - boundingRect.height - CROSSHAIR_OFFSET
302
360
  }];
303
361
  for (const position of quadrants) {
304
362
  const fitsHorizontally = position.left >= VIEWPORT_MARGIN_PX && position.left + boundingRect.width <= viewportWidth - VIEWPORT_MARGIN_PX;
@@ -317,7 +375,7 @@ var Label = (props) => {
317
375
  return props.visible !== false;
318
376
  },
319
377
  get children() {
320
- var _el$ = _tmpl$32(), _el$4 = _el$.firstChild;
378
+ var _el$ = _tmpl$4();
321
379
  var _ref$ = labelRef;
322
380
  typeof _ref$ === "function" ? use(_ref$, _el$) : labelRef = _el$;
323
381
  insert(_el$, createComponent(Show, {
@@ -327,7 +385,7 @@ var Label = (props) => {
327
385
  get children() {
328
386
  return createComponent(Spinner, {});
329
387
  }
330
- }), _el$4);
388
+ }), null);
331
389
  insert(_el$, createComponent(Show, {
332
390
  get when() {
333
391
  return props.variant === "success";
@@ -335,7 +393,7 @@ var Label = (props) => {
335
393
  get children() {
336
394
  return _tmpl$3();
337
395
  }
338
- }), _el$4);
396
+ }), null);
339
397
  insert(_el$, createComponent(Show, {
340
398
  get when() {
341
399
  return props.variant === "success";
@@ -343,14 +401,36 @@ var Label = (props) => {
343
401
  get children() {
344
402
  return _tmpl$22();
345
403
  }
346
- }), _el$4);
404
+ }), null);
347
405
  insert(_el$, createComponent(Show, {
348
406
  get when() {
349
407
  return props.variant === "processing";
350
408
  },
351
409
  children: "Grabbing\u2026"
352
- }), _el$4);
353
- insert(_el$4, () => props.text);
410
+ }), null);
411
+ insert(_el$, createComponent(Show, {
412
+ get when() {
413
+ return props.text.startsWith("(");
414
+ },
415
+ get fallback() {
416
+ return (() => {
417
+ var _el$5 = _tmpl$5();
418
+ insert(_el$5, () => props.text);
419
+ return _el$5;
420
+ })();
421
+ },
422
+ get children() {
423
+ return props.text;
424
+ }
425
+ }), null);
426
+ insert(_el$, createComponent(Show, {
427
+ get when() {
428
+ return props.variant === "success";
429
+ },
430
+ get children() {
431
+ return _tmpl$32();
432
+ }
433
+ }), null);
354
434
  effect((_p$) => {
355
435
  var _v$ = `${computedPosition().top}px`, _v$2 = `${computedPosition().left}px`, _v$3 = props.zIndex?.toString() ?? "2147483647", _v$4 = opacity();
356
436
  _v$ !== _p$.e && setStyleProperty(_el$, "top", _p$.e = _v$);
@@ -368,7 +448,7 @@ var Label = (props) => {
368
448
  }
369
449
  });
370
450
  };
371
- var _tmpl$4 = /* @__PURE__ */ template(`<div style="position:fixed;z-index:2147483647;pointer-events:none;transition:opacity 0.1s ease-in-out"><div style="width:32px;height:2px;background-color:rgba(178, 28, 142, 0.2);border-radius:1px;overflow:hidden;position:relative"><div style="height:100%;background-color:#b21c8e;border-radius:1px;transition:width 0.05s cubic-bezier(0.165, 0.84, 0.44, 1)">`);
451
+ var _tmpl$6 = /* @__PURE__ */ template(`<div style="position:fixed;z-index:2147483647;pointer-events:none;transition:opacity 0.1s ease-in-out"><div style="width:32px;height:2px;background-color:rgba(178, 28, 142, 0.2);border-radius:1px;overflow:hidden;position:relative"><div style="height:100%;background-color:#b21c8e;border-radius:1px;transition:width 0.05s cubic-bezier(0.165, 0.84, 0.44, 1)">`);
372
452
  var useFadeInOut = (visible) => {
373
453
  const [opacity, setOpacity] = createSignal(0);
374
454
  createEffect(on(() => visible, (isVisible) => {
@@ -401,7 +481,7 @@ var ProgressIndicator = (props) => {
401
481
  return props.visible !== false;
402
482
  },
403
483
  get children() {
404
- var _el$ = _tmpl$4(), _el$2 = _el$.firstChild, _el$3 = _el$2.firstChild;
484
+ var _el$ = _tmpl$6(), _el$2 = _el$.firstChild, _el$3 = _el$2.firstChild;
405
485
  var _ref$ = progressIndicatorRef;
406
486
  typeof _ref$ === "function" ? use(_ref$, _el$) : progressIndicatorRef = _el$;
407
487
  effect((_p$) => {
@@ -421,66 +501,99 @@ var ProgressIndicator = (props) => {
421
501
  }
422
502
  });
423
503
  };
424
- var _tmpl$5 = /* @__PURE__ */ template(`<div style=position:fixed;top:0;left:0;width:100vw;height:100vh;pointer-events:none;z-index:2147483645><div style="position:absolute;top:0;width:1px;height:100%;background-color:rgba(210, 57, 192, 0.5);will-change:transform"></div><div style="position:absolute;left:0;width:100%;height:1px;background-color:rgba(210, 57, 192, 0.5);will-change:transform">`);
504
+ var _tmpl$7 = /* @__PURE__ */ template(`<canvas style=position:fixed;top:0;left:0;pointer-events:none;z-index:2147483645>`);
425
505
  var Crosshair = (props) => {
426
- const [currentX, setCurrentX] = createSignal(props.mouseX);
427
- const [currentY, setCurrentY] = createSignal(props.mouseY);
428
- let hasBeenRenderedOnce = false;
429
- let animationFrameId = null;
506
+ let canvasRef;
507
+ let context = null;
508
+ let width = 0;
509
+ let height = 0;
510
+ let dpr = 1;
511
+ let currentX = props.mouseX;
512
+ let currentY = props.mouseY;
430
513
  let targetX = props.mouseX;
431
514
  let targetY = props.mouseY;
432
- let isAnimating = false;
515
+ let animationFrameId = null;
516
+ let hasBeenRenderedOnce = false;
517
+ const setupCanvas = () => {
518
+ if (!canvasRef) return;
519
+ dpr = Math.max(window.devicePixelRatio || 1, 2);
520
+ width = window.innerWidth;
521
+ height = window.innerHeight;
522
+ canvasRef.width = width * dpr;
523
+ canvasRef.height = height * dpr;
524
+ canvasRef.style.width = `${width}px`;
525
+ canvasRef.style.height = `${height}px`;
526
+ context = canvasRef.getContext("2d");
527
+ if (context) {
528
+ context.scale(dpr, dpr);
529
+ }
530
+ };
531
+ const render2 = () => {
532
+ if (!context) return;
533
+ context.clearRect(0, 0, width, height);
534
+ context.strokeStyle = "rgba(210, 57, 192)";
535
+ context.lineWidth = 1;
536
+ context.beginPath();
537
+ context.moveTo(currentX, 0);
538
+ context.lineTo(currentX, height);
539
+ context.moveTo(0, currentY);
540
+ context.lineTo(width, currentY);
541
+ context.stroke();
542
+ };
543
+ const animate = () => {
544
+ currentX = lerp(currentX, targetX, 0.3);
545
+ currentY = lerp(currentY, targetY, 0.3);
546
+ render2();
547
+ const hasConvergedToTarget = Math.abs(currentX - targetX) < 0.5 && Math.abs(currentY - targetY) < 0.5;
548
+ if (!hasConvergedToTarget) {
549
+ animationFrameId = requestAnimationFrame(animate);
550
+ } else {
551
+ animationFrameId = null;
552
+ }
553
+ };
433
554
  const startAnimation = () => {
434
- if (isAnimating) return;
435
- isAnimating = true;
436
- const animate = () => {
437
- const interpolatedX = lerp(currentX(), targetX, 0.3);
438
- const interpolatedY = lerp(currentY(), targetY, 0.3);
439
- setCurrentX(interpolatedX);
440
- setCurrentY(interpolatedY);
441
- const hasConvergedToTarget = Math.abs(interpolatedX - targetX) < 0.5 && Math.abs(interpolatedY - targetY) < 0.5;
442
- if (!hasConvergedToTarget) {
443
- animationFrameId = requestAnimationFrame(animate);
444
- } else {
445
- animationFrameId = null;
446
- isAnimating = false;
447
- }
448
- };
555
+ if (animationFrameId !== null) return;
449
556
  animationFrameId = requestAnimationFrame(animate);
450
557
  };
451
- createEffect(on(() => [props.mouseX, props.mouseY], ([newMouseX, newMouseY]) => {
452
- targetX = newMouseX;
453
- targetY = newMouseY;
558
+ const updateTarget = () => {
559
+ targetX = props.mouseX;
560
+ targetY = props.mouseY;
454
561
  if (!hasBeenRenderedOnce) {
455
- setCurrentX(targetX);
456
- setCurrentY(targetY);
562
+ currentX = targetX;
563
+ currentY = targetY;
457
564
  hasBeenRenderedOnce = true;
565
+ render2();
458
566
  return;
459
567
  }
460
568
  startAnimation();
461
- }));
462
- onCleanup(() => {
463
- if (animationFrameId !== null) {
464
- cancelAnimationFrame(animationFrameId);
465
- animationFrameId = null;
466
- }
467
- isAnimating = false;
569
+ };
570
+ createEffect(() => {
571
+ setupCanvas();
572
+ render2();
573
+ const handleResize = () => {
574
+ setupCanvas();
575
+ render2();
576
+ };
577
+ window.addEventListener("resize", handleResize);
578
+ onCleanup(() => {
579
+ window.removeEventListener("resize", handleResize);
580
+ if (animationFrameId !== null) {
581
+ cancelAnimationFrame(animationFrameId);
582
+ animationFrameId = null;
583
+ }
584
+ });
585
+ });
586
+ createEffect(() => {
587
+ updateTarget();
468
588
  });
469
589
  return createComponent(Show, {
470
590
  get when() {
471
591
  return props.visible !== false;
472
592
  },
473
593
  get children() {
474
- var _el$ = _tmpl$5(), _el$2 = _el$.firstChild, _el$3 = _el$2.nextSibling;
475
- effect((_p$) => {
476
- var _v$ = `${currentX()}px`, _v$2 = `${currentY()}px`;
477
- _v$ !== _p$.e && setStyleProperty(_el$2, "left", _p$.e = _v$);
478
- _v$2 !== _p$.t && setStyleProperty(_el$3, "top", _p$.t = _v$2);
479
- return _p$;
480
- }, {
481
- e: void 0,
482
- t: void 0
483
- });
594
+ var _el$ = _tmpl$7();
595
+ var _ref$ = canvasRef;
596
+ typeof _ref$ === "function" ? use(_ref$, _el$) : canvasRef = _el$;
484
597
  return _el$;
485
598
  }
486
599
  });
@@ -541,6 +654,9 @@ var ReactGrabRenderer = (props) => {
541
654
  variant: "grabbed",
542
655
  get bounds() {
543
656
  return box.bounds;
657
+ },
658
+ get createdAt() {
659
+ return box.createdAt;
544
660
  }
545
661
  })
546
662
  }), createComponent(For, {
@@ -621,7 +737,14 @@ var getSourceTrace = async (element) => {
621
737
  Number.MAX_SAFE_INTEGER
622
738
  );
623
739
  if (!sources) return null;
624
- return sources;
740
+ return sources.filter((source) => {
741
+ return isSourceFile(source.fileName);
742
+ }).map((source) => {
743
+ return {
744
+ ...source,
745
+ fileName: normalizeFileName(source.fileName)
746
+ };
747
+ });
625
748
  };
626
749
  var getHTMLSnippet = (element) => {
627
750
  const semanticTags = /* @__PURE__ */ new Set([
@@ -971,11 +1094,65 @@ var filterElementsInDrag = (dragRect, isValidGrabbableElement2, shouldCheckCover
971
1094
  }
972
1095
  return elements;
973
1096
  };
1097
+ var removeNestedElements = (elements) => {
1098
+ return elements.filter((element) => {
1099
+ return !elements.some(
1100
+ (otherElement) => otherElement !== element && otherElement.contains(element)
1101
+ );
1102
+ });
1103
+ };
1104
+ var findBestParentElement = (elements, dragRect, isValidGrabbableElement2) => {
1105
+ if (elements.length <= 1) return null;
1106
+ const dragLeft = dragRect.x;
1107
+ const dragTop = dragRect.y;
1108
+ const dragRight = dragRect.x + dragRect.width;
1109
+ const dragBottom = dragRect.y + dragRect.height;
1110
+ let currentParent = elements[0];
1111
+ while (currentParent) {
1112
+ const parent = currentParent.parentElement;
1113
+ if (!parent) break;
1114
+ const parentRect = parent.getBoundingClientRect();
1115
+ const intersectionLeft = Math.max(dragLeft, parentRect.left);
1116
+ const intersectionTop = Math.max(dragTop, parentRect.top);
1117
+ const intersectionRight = Math.min(dragRight, parentRect.left + parentRect.width);
1118
+ const intersectionBottom = Math.min(dragBottom, parentRect.top + parentRect.height);
1119
+ const intersectionWidth = Math.max(0, intersectionRight - intersectionLeft);
1120
+ const intersectionHeight = Math.max(0, intersectionBottom - intersectionTop);
1121
+ const intersectionArea = intersectionWidth * intersectionHeight;
1122
+ const parentArea = Math.max(0, parentRect.width * parentRect.height);
1123
+ const hasMajorityCoverage = parentArea > 0 && intersectionArea / parentArea >= DRAG_COVERAGE_THRESHOLD;
1124
+ if (!hasMajorityCoverage) break;
1125
+ if (!isValidGrabbableElement2(parent)) {
1126
+ currentParent = parent;
1127
+ continue;
1128
+ }
1129
+ const allChildrenInParent = elements.every(
1130
+ (element) => parent.contains(element)
1131
+ );
1132
+ if (allChildrenInParent) {
1133
+ return parent;
1134
+ }
1135
+ currentParent = parent;
1136
+ }
1137
+ return null;
1138
+ };
974
1139
  var getElementsInDrag = (dragRect, isValidGrabbableElement2) => {
975
- return filterElementsInDrag(dragRect, isValidGrabbableElement2, true);
1140
+ const elements = filterElementsInDrag(dragRect, isValidGrabbableElement2, true);
1141
+ const uniqueElements = removeNestedElements(elements);
1142
+ const bestParent = findBestParentElement(uniqueElements, dragRect, isValidGrabbableElement2);
1143
+ if (bestParent) {
1144
+ return [bestParent];
1145
+ }
1146
+ return uniqueElements;
976
1147
  };
977
1148
  var getElementsInDragLoose = (dragRect, isValidGrabbableElement2) => {
978
- return filterElementsInDrag(dragRect, isValidGrabbableElement2, false);
1149
+ const elements = filterElementsInDrag(dragRect, isValidGrabbableElement2, false);
1150
+ const uniqueElements = removeNestedElements(elements);
1151
+ const bestParent = findBestParentElement(uniqueElements, dragRect, isValidGrabbableElement2);
1152
+ if (bestParent) {
1153
+ return [bestParent];
1154
+ }
1155
+ return uniqueElements;
979
1156
  };
980
1157
 
981
1158
  // src/utils/create-element-bounds.ts
@@ -1020,22 +1197,26 @@ var init = (rawOptions) => {
1020
1197
  const [successLabels, setSuccessLabels] = createSignal([]);
1021
1198
  const [isActivated, setIsActivated] = createSignal(false);
1022
1199
  const [showProgressIndicator, setShowProgressIndicator] = createSignal(false);
1023
- const [grabMouseX, setGrabMouseX] = createSignal(null);
1024
- const [grabMouseY, setGrabMouseY] = createSignal(null);
1025
1200
  let holdTimerId = null;
1026
1201
  let progressAnimationId = null;
1027
1202
  let progressDelayTimerId = null;
1203
+ let keydownSpamTimerId = null;
1028
1204
  const isRendererActive = createMemo(() => isActivated() && !isCopying());
1029
1205
  const hasValidMousePosition = createMemo(() => mouseX() > OFFSCREEN_POSITION && mouseY() > OFFSCREEN_POSITION);
1030
1206
  const isTargetKeyCombination = (event) => (event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "c";
1031
1207
  const addGrabbedBox = (bounds) => {
1032
1208
  const boxId = `grabbed-${Date.now()}-${Math.random()}`;
1209
+ const createdAt = Date.now();
1033
1210
  const newBox = {
1034
1211
  id: boxId,
1035
- bounds
1212
+ bounds,
1213
+ createdAt
1036
1214
  };
1037
1215
  const currentBoxes = grabbedBoxes();
1038
1216
  setGrabbedBoxes([...currentBoxes, newBox]);
1217
+ setTimeout(() => {
1218
+ setGrabbedBoxes((previousBoxes) => previousBoxes.filter((box) => box.id !== boxId));
1219
+ }, SUCCESS_LABEL_DURATION_MS);
1039
1220
  };
1040
1221
  const addSuccessLabel = (text, positionX, positionY) => {
1041
1222
  const labelId = `success-${Date.now()}-${Math.random()}`;
@@ -1058,6 +1239,9 @@ var init = (rawOptions) => {
1058
1239
  return ` ${functionName} - ${fileName}:${lineNumber}:${columnNumber}`;
1059
1240
  }).join("\n");
1060
1241
  };
1242
+ const wrapContextInXmlTags = (context) => {
1243
+ return `<selected_element>${context}</selected_element>`;
1244
+ };
1061
1245
  const getElementContentWithTrace = async (element) => {
1062
1246
  const elementHtml = getHTMLSnippet(element);
1063
1247
  const componentStackTrace = await getSourceTrace(element);
@@ -1074,12 +1258,10 @@ ${formattedStackTrace}`;
1074
1258
  const handleCopy = async (targetElement2) => {
1075
1259
  const elementBounds = targetElement2.getBoundingClientRect();
1076
1260
  const tagName = getElementTagName(targetElement2);
1077
- setGrabMouseX(mouseX());
1078
- setGrabMouseY(mouseY());
1079
1261
  addGrabbedBox(createElementBounds(targetElement2));
1080
1262
  try {
1081
1263
  const content = await getElementContentWithTrace(targetElement2);
1082
- await copyContent(content);
1264
+ await copyContent(wrapContextInXmlTags(content));
1083
1265
  } catch {
1084
1266
  }
1085
1267
  addSuccessLabel(tagName ? `<${tagName}>` : "<element>", elementBounds.left, elementBounds.top);
@@ -1088,8 +1270,6 @@ ${formattedStackTrace}`;
1088
1270
  if (targetElements.length === 0) return;
1089
1271
  let minPositionX = Infinity;
1090
1272
  let minPositionY = Infinity;
1091
- setGrabMouseX(mouseX());
1092
- setGrabMouseY(mouseY());
1093
1273
  for (const element of targetElements) {
1094
1274
  const elementBounds = element.getBoundingClientRect();
1095
1275
  minPositionX = Math.min(minPositionX, elementBounds.left);
@@ -1099,7 +1279,7 @@ ${formattedStackTrace}`;
1099
1279
  try {
1100
1280
  const elementSnippets = await Promise.all(targetElements.map((element) => getElementContentWithTrace(element)));
1101
1281
  const combinedContent = elementSnippets.join("\n\n---\n\n");
1102
- await copyContent(combinedContent);
1282
+ await copyContent(wrapContextInXmlTags(combinedContent));
1103
1283
  } catch {
1104
1284
  }
1105
1285
  addSuccessLabel(`${targetElements.length} elements`, minPositionX, minPositionY);
@@ -1122,7 +1302,7 @@ ${formattedStackTrace}`;
1122
1302
  y: elementBounds.top
1123
1303
  };
1124
1304
  });
1125
- const DRAG_THRESHOLD_PX = 5;
1305
+ const DRAG_THRESHOLD_PX = 2;
1126
1306
  const getDragDistance = (endX, endY) => ({
1127
1307
  x: Math.abs(endX - dragStartX()),
1128
1308
  y: Math.abs(endY - dragStartY())
@@ -1158,7 +1338,7 @@ ${formattedStackTrace}`;
1158
1338
  });
1159
1339
  const labelText = createMemo(() => {
1160
1340
  const element = targetElement();
1161
- if (!element) return "";
1341
+ if (!element) return "(click or drag to select element(s))";
1162
1342
  const tagName = getElementTagName(element);
1163
1343
  return tagName ? `<${tagName}>` : "<element>";
1164
1344
  });
@@ -1178,18 +1358,6 @@ ${formattedStackTrace}`;
1178
1358
  setLastGrabbedElement(null);
1179
1359
  }
1180
1360
  }));
1181
- createEffect(on(() => [mouseX(), mouseY(), grabMouseX(), grabMouseY()], ([currentMouseX, currentMouseY, initialGrabMouseX, initialGrabMouseY]) => {
1182
- if (initialGrabMouseX === null || initialGrabMouseY === null) return;
1183
- if (grabbedBoxes().length === 0) return;
1184
- const MOUSE_MOVE_THRESHOLD_PX = 5;
1185
- const distanceX = Math.abs(currentMouseX - initialGrabMouseX);
1186
- const distanceY = Math.abs(currentMouseY - initialGrabMouseY);
1187
- if (distanceX > MOUSE_MOVE_THRESHOLD_PX || distanceY > MOUSE_MOVE_THRESHOLD_PX) {
1188
- setGrabbedBoxes([]);
1189
- setGrabMouseX(null);
1190
- setGrabMouseY(null);
1191
- }
1192
- }));
1193
1361
  const progress = createMemo(() => {
1194
1362
  const startTime = progressStartTime();
1195
1363
  progressTick();
@@ -1231,43 +1399,55 @@ ${formattedStackTrace}`;
1231
1399
  setIsActivated(true);
1232
1400
  document.body.style.cursor = "crosshair";
1233
1401
  };
1402
+ const deactivateRenderer = () => {
1403
+ setIsHoldingKeys(false);
1404
+ setIsActivated(false);
1405
+ document.body.style.cursor = "";
1406
+ if (isDragging()) {
1407
+ setIsDragging(false);
1408
+ document.body.style.userSelect = "";
1409
+ }
1410
+ if (holdTimerId) window.clearTimeout(holdTimerId);
1411
+ if (keydownSpamTimerId) window.clearTimeout(keydownSpamTimerId);
1412
+ stopProgressAnimation();
1413
+ };
1234
1414
  const abortController = new AbortController();
1235
1415
  const eventListenerSignal = abortController.signal;
1236
1416
  window.addEventListener("keydown", (event) => {
1237
1417
  if (event.key === "Escape" && isHoldingKeys()) {
1238
- setIsHoldingKeys(false);
1239
- setIsActivated(false);
1240
- document.body.style.cursor = "";
1241
- if (isDragging()) {
1242
- setIsDragging(false);
1243
- document.body.style.userSelect = "";
1244
- }
1245
- if (holdTimerId) window.clearTimeout(holdTimerId);
1246
- stopProgressAnimation();
1418
+ deactivateRenderer();
1247
1419
  return;
1248
1420
  }
1249
1421
  if (isKeyboardEventTriggeredByInput(event)) return;
1250
- if (isTargetKeyCombination(event) && !isHoldingKeys()) {
1251
- setIsHoldingKeys(true);
1252
- startProgressAnimation();
1253
- holdTimerId = window.setTimeout(() => {
1254
- activateRenderer();
1255
- options.onActivate?.();
1256
- }, options.keyHoldDuration);
1422
+ if (isTargetKeyCombination(event)) {
1423
+ if (!isHoldingKeys()) {
1424
+ setIsHoldingKeys(true);
1425
+ startProgressAnimation();
1426
+ holdTimerId = window.setTimeout(() => {
1427
+ activateRenderer();
1428
+ options.onActivate?.();
1429
+ }, options.keyHoldDuration);
1430
+ }
1431
+ if (isActivated()) {
1432
+ if (keydownSpamTimerId) window.clearTimeout(keydownSpamTimerId);
1433
+ keydownSpamTimerId = window.setTimeout(() => {
1434
+ deactivateRenderer();
1435
+ }, 200);
1436
+ }
1257
1437
  }
1258
1438
  }, {
1259
1439
  signal: eventListenerSignal
1260
1440
  });
1261
1441
  window.addEventListener("keyup", (event) => {
1262
- if (isHoldingKeys() && (!isTargetKeyCombination(event) || event.key.toLowerCase() === "c")) {
1263
- setIsHoldingKeys(false);
1264
- setIsActivated(false);
1265
- document.body.style.cursor = "";
1266
- if (holdTimerId) window.clearTimeout(holdTimerId);
1267
- stopProgressAnimation();
1442
+ if (!isHoldingKeys() && !isActivated()) return;
1443
+ const isReleasingC = event.key.toLowerCase() === "c";
1444
+ const isReleasingModifier = !event.metaKey && !event.ctrlKey;
1445
+ if (isReleasingC || isReleasingModifier) {
1446
+ deactivateRenderer();
1268
1447
  }
1269
1448
  }, {
1270
- signal: eventListenerSignal
1449
+ signal: eventListenerSignal,
1450
+ capture: true
1271
1451
  });
1272
1452
  window.addEventListener("mousemove", (event) => {
1273
1453
  setMouseX(event.clientX);
@@ -1330,6 +1510,7 @@ ${formattedStackTrace}`;
1330
1510
  onCleanup(() => {
1331
1511
  abortController.abort();
1332
1512
  if (holdTimerId) window.clearTimeout(holdTimerId);
1513
+ if (keydownSpamTimerId) window.clearTimeout(keydownSpamTimerId);
1333
1514
  stopProgressAnimation();
1334
1515
  document.body.style.userSelect = "";
1335
1516
  document.body.style.cursor = "";
@@ -1338,7 +1519,7 @@ ${formattedStackTrace}`;
1338
1519
  const selectionVisible = createMemo(() => false);
1339
1520
  const dragVisible = createMemo(() => isRendererActive() && isDraggingBeyondThreshold());
1340
1521
  const labelVariant = createMemo(() => isCopying() ? "processing" : "hover");
1341
- const labelVisible = createMemo(() => isRendererActive() && !isDragging() && !!targetElement() && !isSameAsLast() || isCopying());
1522
+ const labelVisible = createMemo(() => isRendererActive() && !isDragging() && (!!targetElement() && !isSameAsLast() || !targetElement()) || isCopying());
1342
1523
  const progressVisible = createMemo(() => isHoldingKeys() && showProgressIndicator() && hasValidMousePosition());
1343
1524
  const crosshairVisible = createMemo(() => isRendererActive() && !isDragging());
1344
1525
  render(() => createComponent(ReactGrabRenderer, {