react-grab 0.0.25 → 0.0.26

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, {
@@ -973,11 +1089,65 @@ var filterElementsInDrag = (dragRect, isValidGrabbableElement2, shouldCheckCover
973
1089
  }
974
1090
  return elements;
975
1091
  };
1092
+ var removeNestedElements = (elements) => {
1093
+ return elements.filter((element) => {
1094
+ return !elements.some(
1095
+ (otherElement) => otherElement !== element && otherElement.contains(element)
1096
+ );
1097
+ });
1098
+ };
1099
+ var findBestParentElement = (elements, dragRect, isValidGrabbableElement2) => {
1100
+ if (elements.length <= 1) return null;
1101
+ const dragLeft = dragRect.x;
1102
+ const dragTop = dragRect.y;
1103
+ const dragRight = dragRect.x + dragRect.width;
1104
+ const dragBottom = dragRect.y + dragRect.height;
1105
+ let currentParent = elements[0];
1106
+ while (currentParent) {
1107
+ const parent = currentParent.parentElement;
1108
+ if (!parent) break;
1109
+ const parentRect = parent.getBoundingClientRect();
1110
+ const intersectionLeft = Math.max(dragLeft, parentRect.left);
1111
+ const intersectionTop = Math.max(dragTop, parentRect.top);
1112
+ const intersectionRight = Math.min(dragRight, parentRect.left + parentRect.width);
1113
+ const intersectionBottom = Math.min(dragBottom, parentRect.top + parentRect.height);
1114
+ const intersectionWidth = Math.max(0, intersectionRight - intersectionLeft);
1115
+ const intersectionHeight = Math.max(0, intersectionBottom - intersectionTop);
1116
+ const intersectionArea = intersectionWidth * intersectionHeight;
1117
+ const parentArea = Math.max(0, parentRect.width * parentRect.height);
1118
+ const hasMajorityCoverage = parentArea > 0 && intersectionArea / parentArea >= DRAG_COVERAGE_THRESHOLD;
1119
+ if (!hasMajorityCoverage) break;
1120
+ if (!isValidGrabbableElement2(parent)) {
1121
+ currentParent = parent;
1122
+ continue;
1123
+ }
1124
+ const allChildrenInParent = elements.every(
1125
+ (element) => parent.contains(element)
1126
+ );
1127
+ if (allChildrenInParent) {
1128
+ return parent;
1129
+ }
1130
+ currentParent = parent;
1131
+ }
1132
+ return null;
1133
+ };
976
1134
  var getElementsInDrag = (dragRect, isValidGrabbableElement2) => {
977
- return filterElementsInDrag(dragRect, isValidGrabbableElement2, true);
1135
+ const elements = filterElementsInDrag(dragRect, isValidGrabbableElement2, true);
1136
+ const uniqueElements = removeNestedElements(elements);
1137
+ const bestParent = findBestParentElement(uniqueElements, dragRect, isValidGrabbableElement2);
1138
+ if (bestParent) {
1139
+ return [bestParent];
1140
+ }
1141
+ return uniqueElements;
978
1142
  };
979
1143
  var getElementsInDragLoose = (dragRect, isValidGrabbableElement2) => {
980
- return filterElementsInDrag(dragRect, isValidGrabbableElement2, false);
1144
+ const elements = filterElementsInDrag(dragRect, isValidGrabbableElement2, false);
1145
+ const uniqueElements = removeNestedElements(elements);
1146
+ const bestParent = findBestParentElement(uniqueElements, dragRect, isValidGrabbableElement2);
1147
+ if (bestParent) {
1148
+ return [bestParent];
1149
+ }
1150
+ return uniqueElements;
981
1151
  };
982
1152
 
983
1153
  // src/utils/create-element-bounds.ts
@@ -1022,22 +1192,26 @@ var init = (rawOptions) => {
1022
1192
  const [successLabels, setSuccessLabels] = solidJs.createSignal([]);
1023
1193
  const [isActivated, setIsActivated] = solidJs.createSignal(false);
1024
1194
  const [showProgressIndicator, setShowProgressIndicator] = solidJs.createSignal(false);
1025
- const [grabMouseX, setGrabMouseX] = solidJs.createSignal(null);
1026
- const [grabMouseY, setGrabMouseY] = solidJs.createSignal(null);
1027
1195
  let holdTimerId = null;
1028
1196
  let progressAnimationId = null;
1029
1197
  let progressDelayTimerId = null;
1198
+ let keydownSpamTimerId = null;
1030
1199
  const isRendererActive = solidJs.createMemo(() => isActivated() && !isCopying());
1031
1200
  const hasValidMousePosition = solidJs.createMemo(() => mouseX() > OFFSCREEN_POSITION && mouseY() > OFFSCREEN_POSITION);
1032
1201
  const isTargetKeyCombination = (event) => (event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "c";
1033
1202
  const addGrabbedBox = (bounds) => {
1034
1203
  const boxId = `grabbed-${Date.now()}-${Math.random()}`;
1204
+ const createdAt = Date.now();
1035
1205
  const newBox = {
1036
1206
  id: boxId,
1037
- bounds
1207
+ bounds,
1208
+ createdAt
1038
1209
  };
1039
1210
  const currentBoxes = grabbedBoxes();
1040
1211
  setGrabbedBoxes([...currentBoxes, newBox]);
1212
+ setTimeout(() => {
1213
+ setGrabbedBoxes((previousBoxes) => previousBoxes.filter((box) => box.id !== boxId));
1214
+ }, SUCCESS_LABEL_DURATION_MS);
1041
1215
  };
1042
1216
  const addSuccessLabel = (text, positionX, positionY) => {
1043
1217
  const labelId = `success-${Date.now()}-${Math.random()}`;
@@ -1060,6 +1234,9 @@ var init = (rawOptions) => {
1060
1234
  return ` ${functionName} - ${fileName}:${lineNumber}:${columnNumber}`;
1061
1235
  }).join("\n");
1062
1236
  };
1237
+ const wrapContextInXmlTags = (context) => {
1238
+ return `<selected_element>${context}</selected_element>`;
1239
+ };
1063
1240
  const getElementContentWithTrace = async (element) => {
1064
1241
  const elementHtml = getHTMLSnippet(element);
1065
1242
  const componentStackTrace = await getSourceTrace(element);
@@ -1076,12 +1253,10 @@ ${formattedStackTrace}`;
1076
1253
  const handleCopy = async (targetElement2) => {
1077
1254
  const elementBounds = targetElement2.getBoundingClientRect();
1078
1255
  const tagName = getElementTagName(targetElement2);
1079
- setGrabMouseX(mouseX());
1080
- setGrabMouseY(mouseY());
1081
1256
  addGrabbedBox(createElementBounds(targetElement2));
1082
1257
  try {
1083
1258
  const content = await getElementContentWithTrace(targetElement2);
1084
- await copyContent(content);
1259
+ await copyContent(wrapContextInXmlTags(content));
1085
1260
  } catch {
1086
1261
  }
1087
1262
  addSuccessLabel(tagName ? `<${tagName}>` : "<element>", elementBounds.left, elementBounds.top);
@@ -1090,8 +1265,6 @@ ${formattedStackTrace}`;
1090
1265
  if (targetElements.length === 0) return;
1091
1266
  let minPositionX = Infinity;
1092
1267
  let minPositionY = Infinity;
1093
- setGrabMouseX(mouseX());
1094
- setGrabMouseY(mouseY());
1095
1268
  for (const element of targetElements) {
1096
1269
  const elementBounds = element.getBoundingClientRect();
1097
1270
  minPositionX = Math.min(minPositionX, elementBounds.left);
@@ -1101,7 +1274,7 @@ ${formattedStackTrace}`;
1101
1274
  try {
1102
1275
  const elementSnippets = await Promise.all(targetElements.map((element) => getElementContentWithTrace(element)));
1103
1276
  const combinedContent = elementSnippets.join("\n\n---\n\n");
1104
- await copyContent(combinedContent);
1277
+ await copyContent(wrapContextInXmlTags(combinedContent));
1105
1278
  } catch {
1106
1279
  }
1107
1280
  addSuccessLabel(`${targetElements.length} elements`, minPositionX, minPositionY);
@@ -1124,7 +1297,7 @@ ${formattedStackTrace}`;
1124
1297
  y: elementBounds.top
1125
1298
  };
1126
1299
  });
1127
- const DRAG_THRESHOLD_PX = 5;
1300
+ const DRAG_THRESHOLD_PX = 2;
1128
1301
  const getDragDistance = (endX, endY) => ({
1129
1302
  x: Math.abs(endX - dragStartX()),
1130
1303
  y: Math.abs(endY - dragStartY())
@@ -1160,7 +1333,7 @@ ${formattedStackTrace}`;
1160
1333
  });
1161
1334
  const labelText = solidJs.createMemo(() => {
1162
1335
  const element = targetElement();
1163
- if (!element) return "";
1336
+ if (!element) return "(click or drag to select element(s))";
1164
1337
  const tagName = getElementTagName(element);
1165
1338
  return tagName ? `<${tagName}>` : "<element>";
1166
1339
  });
@@ -1180,18 +1353,6 @@ ${formattedStackTrace}`;
1180
1353
  setLastGrabbedElement(null);
1181
1354
  }
1182
1355
  }));
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
1356
  const progress = solidJs.createMemo(() => {
1196
1357
  const startTime = progressStartTime();
1197
1358
  progressTick();
@@ -1233,43 +1394,55 @@ ${formattedStackTrace}`;
1233
1394
  setIsActivated(true);
1234
1395
  document.body.style.cursor = "crosshair";
1235
1396
  };
1397
+ const deactivateRenderer = () => {
1398
+ setIsHoldingKeys(false);
1399
+ setIsActivated(false);
1400
+ document.body.style.cursor = "";
1401
+ if (isDragging()) {
1402
+ setIsDragging(false);
1403
+ document.body.style.userSelect = "";
1404
+ }
1405
+ if (holdTimerId) window.clearTimeout(holdTimerId);
1406
+ if (keydownSpamTimerId) window.clearTimeout(keydownSpamTimerId);
1407
+ stopProgressAnimation();
1408
+ };
1236
1409
  const abortController = new AbortController();
1237
1410
  const eventListenerSignal = abortController.signal;
1238
1411
  window.addEventListener("keydown", (event) => {
1239
1412
  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();
1413
+ deactivateRenderer();
1249
1414
  return;
1250
1415
  }
1251
1416
  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);
1417
+ if (isTargetKeyCombination(event)) {
1418
+ if (!isHoldingKeys()) {
1419
+ setIsHoldingKeys(true);
1420
+ startProgressAnimation();
1421
+ holdTimerId = window.setTimeout(() => {
1422
+ activateRenderer();
1423
+ options.onActivate?.();
1424
+ }, options.keyHoldDuration);
1425
+ }
1426
+ if (isActivated()) {
1427
+ if (keydownSpamTimerId) window.clearTimeout(keydownSpamTimerId);
1428
+ keydownSpamTimerId = window.setTimeout(() => {
1429
+ deactivateRenderer();
1430
+ }, 200);
1431
+ }
1259
1432
  }
1260
1433
  }, {
1261
1434
  signal: eventListenerSignal
1262
1435
  });
1263
1436
  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();
1437
+ if (!isHoldingKeys() && !isActivated()) return;
1438
+ const isReleasingC = event.key.toLowerCase() === "c";
1439
+ const isReleasingModifier = !event.metaKey && !event.ctrlKey;
1440
+ if (isReleasingC || isReleasingModifier) {
1441
+ deactivateRenderer();
1270
1442
  }
1271
1443
  }, {
1272
- signal: eventListenerSignal
1444
+ signal: eventListenerSignal,
1445
+ capture: true
1273
1446
  });
1274
1447
  window.addEventListener("mousemove", (event) => {
1275
1448
  setMouseX(event.clientX);
@@ -1332,6 +1505,7 @@ ${formattedStackTrace}`;
1332
1505
  solidJs.onCleanup(() => {
1333
1506
  abortController.abort();
1334
1507
  if (holdTimerId) window.clearTimeout(holdTimerId);
1508
+ if (keydownSpamTimerId) window.clearTimeout(keydownSpamTimerId);
1335
1509
  stopProgressAnimation();
1336
1510
  document.body.style.userSelect = "";
1337
1511
  document.body.style.cursor = "";
@@ -1340,7 +1514,7 @@ ${formattedStackTrace}`;
1340
1514
  const selectionVisible = solidJs.createMemo(() => false);
1341
1515
  const dragVisible = solidJs.createMemo(() => isRendererActive() && isDraggingBeyondThreshold());
1342
1516
  const labelVariant = solidJs.createMemo(() => isCopying() ? "processing" : "hover");
1343
- const labelVisible = solidJs.createMemo(() => isRendererActive() && !isDragging() && !!targetElement() && !isSameAsLast() || isCopying());
1517
+ const labelVisible = solidJs.createMemo(() => isRendererActive() && !isDragging() && (!!targetElement() && !isSameAsLast() || !targetElement()) || isCopying());
1344
1518
  const progressVisible = solidJs.createMemo(() => isHoldingKeys() && showProgressIndicator() && hasValidMousePosition());
1345
1519
  const crosshairVisible = solidJs.createMemo(() => isRendererActive() && !isDragging());
1346
1520
  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;
package/dist/index.d.ts 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;