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