react-grab 0.0.24 → 0.0.25

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
@@ -91,93 +91,89 @@ var mountRoot = () => {
91
91
  return root;
92
92
  };
93
93
 
94
- // src/utils/is-element-visible.ts
95
- var isElementVisible = (element, computedStyle = window.getComputedStyle(element)) => {
96
- return computedStyle.display !== "none" && computedStyle.visibility !== "hidden" && computedStyle.opacity !== "0";
97
- };
98
- var _tmpl$ = /* @__PURE__ */ web.template(`<div>`);
99
- var _tmpl$2 = /* @__PURE__ */ web.template(`<span style="display:inline-block;width:8px;height:8px;border:1.5px solid rgb(210, 57, 192);border-top-color:transparent;border-radius:50%;margin-right:4px;vertical-align:middle">`);
100
- var _tmpl$3 = /* @__PURE__ */ web.template(`<span style=display:inline-block;margin-right:4px;font-weight:600>\u2713`);
101
- var _tmpl$4 = /* @__PURE__ */ web.template(`<span style="font-family:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;font-variant-numeric:tabular-nums">`);
102
- var _tmpl$5 = /* @__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>`);
103
- 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)">`);
94
+ // src/constants.ts
104
95
  var VIEWPORT_MARGIN_PX = 8;
105
- var LABEL_OFFSET_PX = 6;
106
96
  var INDICATOR_CLAMP_PADDING_PX = 4;
107
- var INDICATOR_SUCCESS_VISIBLE_MS = 1500;
97
+ var CURSOR_OFFSET_PX = 14;
108
98
  var SELECTION_LERP_FACTOR = 0.95;
109
- var MARQUEE_LERP_FACTOR = 0.9;
99
+
100
+ // src/utils/lerp.ts
110
101
  var lerp = (start, end, factor) => {
111
102
  return start + (end - start) * factor;
112
103
  };
113
- var Overlay = (props) => {
104
+
105
+ // src/components/selection-box.tsx
106
+ var _tmpl$ = /* @__PURE__ */ web.template(`<div>`);
107
+ var SelectionBox = (props) => {
114
108
  const [currentX, setCurrentX] = solidJs.createSignal(props.bounds.x);
115
109
  const [currentY, setCurrentY] = solidJs.createSignal(props.bounds.y);
116
110
  const [currentWidth, setCurrentWidth] = solidJs.createSignal(props.bounds.width);
117
111
  const [currentHeight, setCurrentHeight] = solidJs.createSignal(props.bounds.height);
118
112
  const [opacity, setOpacity] = solidJs.createSignal(1);
119
- let hasBeenShown = false;
113
+ let hasBeenRenderedOnce = false;
120
114
  let animationFrameId = null;
121
115
  let targetBounds = props.bounds;
122
- solidJs.createEffect(() => {
123
- targetBounds = props.bounds;
124
- const factor = props.lerpFactor ?? SELECTION_LERP_FACTOR;
125
- if (!hasBeenShown) {
126
- setCurrentX(targetBounds.x);
127
- setCurrentY(targetBounds.y);
128
- setCurrentWidth(targetBounds.width);
129
- setCurrentHeight(targetBounds.height);
130
- hasBeenShown = true;
131
- return;
132
- }
133
- const EPSILON = 0.5;
116
+ let isAnimating = false;
117
+ const lerpFactor = () => {
118
+ if (props.lerpFactor !== void 0) return props.lerpFactor;
119
+ if (props.variant === "drag") return 0.9;
120
+ return SELECTION_LERP_FACTOR;
121
+ };
122
+ const startAnimation = () => {
123
+ if (isAnimating) return;
124
+ isAnimating = true;
134
125
  const animate = () => {
135
- const newX = lerp(currentX(), targetBounds.x, factor);
136
- const newY = lerp(currentY(), targetBounds.y, factor);
137
- const newWidth = lerp(currentWidth(), targetBounds.width, factor);
138
- const newHeight = lerp(currentHeight(), targetBounds.height, factor);
139
- setCurrentX(newX);
140
- setCurrentY(newY);
141
- setCurrentWidth(newWidth);
142
- setCurrentHeight(newHeight);
143
- const hasConverged = Math.abs(newX - targetBounds.x) < EPSILON && Math.abs(newY - targetBounds.y) < EPSILON && Math.abs(newWidth - targetBounds.width) < EPSILON && Math.abs(newHeight - targetBounds.height) < EPSILON;
144
- if (!hasConverged) {
126
+ const interpolatedX = lerp(currentX(), targetBounds.x, lerpFactor());
127
+ const interpolatedY = lerp(currentY(), targetBounds.y, lerpFactor());
128
+ const interpolatedWidth = lerp(currentWidth(), targetBounds.width, lerpFactor());
129
+ const interpolatedHeight = lerp(currentHeight(), targetBounds.height, lerpFactor());
130
+ setCurrentX(interpolatedX);
131
+ setCurrentY(interpolatedY);
132
+ setCurrentWidth(interpolatedWidth);
133
+ setCurrentHeight(interpolatedHeight);
134
+ const hasConvergedToTarget = Math.abs(interpolatedX - targetBounds.x) < 0.5 && Math.abs(interpolatedY - targetBounds.y) < 0.5 && Math.abs(interpolatedWidth - targetBounds.width) < 0.5 && Math.abs(interpolatedHeight - targetBounds.height) < 0.5;
135
+ if (!hasConvergedToTarget) {
145
136
  animationFrameId = requestAnimationFrame(animate);
146
137
  } else {
147
138
  animationFrameId = null;
139
+ isAnimating = false;
148
140
  }
149
141
  };
142
+ animationFrameId = requestAnimationFrame(animate);
143
+ };
144
+ solidJs.createEffect(solidJs.on(() => props.bounds, (newBounds) => {
145
+ targetBounds = newBounds;
146
+ if (!hasBeenRenderedOnce) {
147
+ setCurrentX(targetBounds.x);
148
+ setCurrentY(targetBounds.y);
149
+ setCurrentWidth(targetBounds.width);
150
+ setCurrentHeight(targetBounds.height);
151
+ hasBeenRenderedOnce = true;
152
+ return;
153
+ }
154
+ startAnimation();
155
+ }));
156
+ solidJs.onCleanup(() => {
150
157
  if (animationFrameId !== null) {
151
158
  cancelAnimationFrame(animationFrameId);
159
+ animationFrameId = null;
152
160
  }
153
- animationFrameId = requestAnimationFrame(animate);
154
- solidJs.onCleanup(() => {
155
- if (animationFrameId !== null) {
156
- cancelAnimationFrame(animationFrameId);
157
- animationFrameId = null;
158
- }
159
- });
160
- });
161
- solidJs.createEffect(() => {
162
- if (props.variant === "grabbed") {
163
- requestAnimationFrame(() => {
164
- setOpacity(0);
165
- });
166
- }
161
+ isAnimating = false;
167
162
  });
168
163
  const baseStyle = {
169
164
  position: "fixed",
170
165
  "box-sizing": "border-box",
171
- "pointer-events": props.variant === "marquee" ? "none" : "auto",
166
+ "pointer-events": props.variant === "drag" ? "none" : "auto",
172
167
  "z-index": "2147483646"
173
168
  };
174
169
  const variantStyle = () => {
175
- if (props.variant === "marquee") {
170
+ if (props.variant === "drag") {
176
171
  return {
177
172
  border: "1px dashed rgb(210, 57, 192)",
178
- "background-color": "rgba(210, 57, 192, 0.1)",
173
+ "background-color": "rgba(210, 57, 192, 0.15)",
179
174
  "will-change": "transform, width, height",
180
- contain: "layout paint size"
175
+ contain: "layout paint size",
176
+ cursor: "crosshair"
181
177
  };
182
178
  }
183
179
  return {
@@ -207,11 +203,12 @@ var Overlay = (props) => {
207
203
  }
208
204
  });
209
205
  };
206
+ var _tmpl$2 = /* @__PURE__ */ web.template(`<span style="display:inline-block;width:8px;height:8px;border:1.5px solid rgb(210, 57, 192);border-top-color:transparent;border-radius:50%;margin-right:4px;vertical-align:middle">`);
210
207
  var Spinner = (props) => {
211
- let ref;
212
- solidJs.createEffect(() => {
213
- if (ref) {
214
- ref.animate([{
208
+ let spinnerRef;
209
+ solidJs.onMount(() => {
210
+ if (spinnerRef) {
211
+ spinnerRef.animate([{
215
212
  transform: "rotate(0deg)"
216
213
  }, {
217
214
  transform: "rotate(360deg)"
@@ -223,126 +220,145 @@ var Spinner = (props) => {
223
220
  }
224
221
  });
225
222
  return (() => {
226
- var _el$2 = _tmpl$2();
227
- var _ref$ = ref;
228
- typeof _ref$ === "function" ? web.use(_ref$, _el$2) : ref = _el$2;
229
- web.effect((_$p) => web.style(_el$2, {
223
+ var _el$ = _tmpl$2();
224
+ var _ref$ = spinnerRef;
225
+ typeof _ref$ === "function" ? web.use(_ref$, _el$) : spinnerRef = _el$;
226
+ web.effect((_$p) => web.style(_el$, {
230
227
  ...props.style
231
228
  }, _$p));
232
- return _el$2;
229
+ return _el$;
233
230
  })();
234
231
  };
232
+
233
+ // src/utils/get-clamped-element-position.ts
234
+ var getClampedElementPosition = (positionLeft, positionTop, elementWidth, elementHeight) => {
235
+ const viewportWidth = window.innerWidth;
236
+ const viewportHeight = window.innerHeight;
237
+ const minLeft = VIEWPORT_MARGIN_PX;
238
+ const minTop = VIEWPORT_MARGIN_PX;
239
+ const maxLeft = viewportWidth - elementWidth - VIEWPORT_MARGIN_PX;
240
+ const maxTop = viewportHeight - elementHeight - VIEWPORT_MARGIN_PX;
241
+ const clampedLeft = Math.max(minLeft, Math.min(positionLeft, maxLeft));
242
+ const clampedTop = Math.max(minTop, Math.min(positionTop, maxTop));
243
+ return { left: clampedLeft, top: clampedTop };
244
+ };
245
+
246
+ // src/components/label.tsx
247
+ 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">`);
235
250
  var Label = (props) => {
236
251
  const [opacity, setOpacity] = solidJs.createSignal(0);
237
- let ref;
238
- solidJs.createEffect(() => {
239
- if (props.visible !== false) {
252
+ let labelRef;
253
+ solidJs.createEffect(solidJs.on(() => props.visible, (visible) => {
254
+ if (visible !== false) {
240
255
  requestAnimationFrame(() => {
241
256
  setOpacity(1);
242
257
  });
243
258
  } else {
244
259
  setOpacity(0);
260
+ return;
245
261
  }
246
- });
247
- solidJs.createEffect(() => {
248
262
  if (props.variant === "success") {
249
263
  const fadeOutTimer = setTimeout(() => {
250
264
  setOpacity(0);
251
- }, INDICATOR_SUCCESS_VISIBLE_MS);
265
+ }, 1500);
252
266
  solidJs.onCleanup(() => clearTimeout(fadeOutTimer));
253
267
  }
254
- });
255
- const indicatorRect = () => ref?.getBoundingClientRect();
268
+ }));
269
+ const labelBoundingRect = () => labelRef?.getBoundingClientRect();
256
270
  const computedPosition = () => {
257
- const rect = indicatorRect();
258
- if (!rect) return {
271
+ const boundingRect = labelBoundingRect();
272
+ if (!boundingRect) return {
259
273
  left: props.x,
260
274
  top: props.y
261
275
  };
262
- const viewportWidthPx = window.innerWidth;
263
- const viewportHeightPx = window.innerHeight;
264
- let indicatorLeftPx = Math.round(props.x);
265
- let indicatorTopPx = Math.round(props.y) - rect.height - LABEL_OFFSET_PX;
266
- const minLeft = VIEWPORT_MARGIN_PX;
267
- const minTop = VIEWPORT_MARGIN_PX;
268
- const maxLeft = viewportWidthPx - rect.width - VIEWPORT_MARGIN_PX;
269
- const maxTop = viewportHeightPx - rect.height - VIEWPORT_MARGIN_PX;
270
- const willClampLeft = indicatorLeftPx < minLeft;
271
- const willClampTop = indicatorTopPx < minTop;
272
- const isClamped = willClampLeft || willClampTop;
273
- indicatorLeftPx = Math.max(minLeft, Math.min(indicatorLeftPx, maxLeft));
274
- indicatorTopPx = Math.max(minTop, Math.min(indicatorTopPx, maxTop));
275
- if (isClamped) {
276
- indicatorLeftPx += INDICATOR_CLAMP_PADDING_PX;
277
- indicatorTopPx += INDICATOR_CLAMP_PADDING_PX;
276
+ if (props.variant === "success") {
277
+ const indicatorLeft = Math.round(props.x);
278
+ const indicatorTop = Math.round(props.y) - boundingRect.height - 6;
279
+ const willClampLeft = indicatorLeft < VIEWPORT_MARGIN_PX;
280
+ const willClampTop = indicatorTop < VIEWPORT_MARGIN_PX;
281
+ const isClamped = willClampLeft || willClampTop;
282
+ const clamped = getClampedElementPosition(indicatorLeft, indicatorTop, boundingRect.width, boundingRect.height);
283
+ if (isClamped) {
284
+ clamped.left += INDICATOR_CLAMP_PADDING_PX;
285
+ clamped.top += INDICATOR_CLAMP_PADDING_PX;
286
+ }
287
+ return clamped;
278
288
  }
279
- return {
280
- left: indicatorLeftPx,
281
- top: indicatorTopPx
282
- };
289
+ const CROSSHAIR_OFFSET = 12;
290
+ const viewportWidth = window.innerWidth;
291
+ const viewportHeight = window.innerHeight;
292
+ const quadrants = [{
293
+ left: Math.round(props.x) + CROSSHAIR_OFFSET,
294
+ top: Math.round(props.y) + CROSSHAIR_OFFSET
295
+ }, {
296
+ left: Math.round(props.x) - boundingRect.width - CROSSHAIR_OFFSET,
297
+ top: Math.round(props.y) + CROSSHAIR_OFFSET
298
+ }, {
299
+ left: Math.round(props.x) + CROSSHAIR_OFFSET,
300
+ top: Math.round(props.y) - boundingRect.height - CROSSHAIR_OFFSET
301
+ }, {
302
+ left: Math.round(props.x) - boundingRect.width - CROSSHAIR_OFFSET,
303
+ top: Math.round(props.y) - boundingRect.height - CROSSHAIR_OFFSET
304
+ }];
305
+ for (const position of quadrants) {
306
+ const fitsHorizontally = position.left >= VIEWPORT_MARGIN_PX && position.left + boundingRect.width <= viewportWidth - VIEWPORT_MARGIN_PX;
307
+ const fitsVertically = position.top >= VIEWPORT_MARGIN_PX && position.top + boundingRect.height <= viewportHeight - VIEWPORT_MARGIN_PX;
308
+ if (fitsHorizontally && fitsVertically) {
309
+ return position;
310
+ }
311
+ }
312
+ const fallback = getClampedElementPosition(quadrants[0].left, quadrants[0].top, boundingRect.width, boundingRect.height);
313
+ fallback.left += INDICATOR_CLAMP_PADDING_PX;
314
+ fallback.top += INDICATOR_CLAMP_PADDING_PX;
315
+ return fallback;
283
316
  };
284
317
  return web.createComponent(solidJs.Show, {
285
318
  get when() {
286
319
  return props.visible !== false;
287
320
  },
288
321
  get children() {
289
- var _el$3 = _tmpl$5(), _el$5 = _el$3.firstChild;
290
- var _ref$2 = ref;
291
- typeof _ref$2 === "function" ? web.use(_ref$2, _el$3) : ref = _el$3;
292
- web.insert(_el$3, web.createComponent(solidJs.Show, {
322
+ var _el$ = _tmpl$32(), _el$4 = _el$.firstChild;
323
+ var _ref$ = labelRef;
324
+ typeof _ref$ === "function" ? web.use(_ref$, _el$) : labelRef = _el$;
325
+ web.insert(_el$, web.createComponent(solidJs.Show, {
293
326
  get when() {
294
327
  return props.variant === "processing";
295
328
  },
296
329
  get children() {
297
330
  return web.createComponent(Spinner, {});
298
331
  }
299
- }), _el$5);
300
- web.insert(_el$3, web.createComponent(solidJs.Show, {
332
+ }), _el$4);
333
+ web.insert(_el$, web.createComponent(solidJs.Show, {
301
334
  get when() {
302
335
  return props.variant === "success";
303
336
  },
304
337
  get children() {
305
338
  return _tmpl$3();
306
339
  }
307
- }), _el$5);
308
- web.insert(_el$5, web.createComponent(solidJs.Show, {
340
+ }), _el$4);
341
+ web.insert(_el$, web.createComponent(solidJs.Show, {
309
342
  get when() {
310
343
  return props.variant === "success";
311
344
  },
312
- children: "Grabbed "
313
- }), null);
314
- web.insert(_el$5, web.createComponent(solidJs.Show, {
315
- get when() {
316
- return props.variant === "processing";
317
- },
318
- children: "Grabbing\u2026"
319
- }), null);
320
- web.insert(_el$5, web.createComponent(solidJs.Show, {
321
- get when() {
322
- return props.variant === "hover";
323
- },
324
345
  get children() {
325
- var _el$6 = _tmpl$4();
326
- web.insert(_el$6, () => props.text);
327
- return _el$6;
346
+ return _tmpl$22();
328
347
  }
329
- }), null);
330
- web.insert(_el$5, web.createComponent(solidJs.Show, {
348
+ }), _el$4);
349
+ web.insert(_el$, web.createComponent(solidJs.Show, {
331
350
  get when() {
332
- return props.variant !== "hover";
351
+ return props.variant === "processing";
333
352
  },
334
- get children() {
335
- var _el$7 = _tmpl$4();
336
- web.insert(_el$7, () => props.text);
337
- return _el$7;
338
- }
339
- }), null);
353
+ children: "Grabbing\u2026"
354
+ }), _el$4);
355
+ web.insert(_el$4, () => props.text);
340
356
  web.effect((_p$) => {
341
357
  var _v$ = `${computedPosition().top}px`, _v$2 = `${computedPosition().left}px`, _v$3 = props.zIndex?.toString() ?? "2147483647", _v$4 = opacity();
342
- _v$ !== _p$.e && web.setStyleProperty(_el$3, "top", _p$.e = _v$);
343
- _v$2 !== _p$.t && web.setStyleProperty(_el$3, "left", _p$.t = _v$2);
344
- _v$3 !== _p$.a && web.setStyleProperty(_el$3, "z-index", _p$.a = _v$3);
345
- _v$4 !== _p$.o && web.setStyleProperty(_el$3, "opacity", _p$.o = _v$4);
358
+ _v$ !== _p$.e && web.setStyleProperty(_el$, "top", _p$.e = _v$);
359
+ _v$2 !== _p$.t && web.setStyleProperty(_el$, "left", _p$.t = _v$2);
360
+ _v$3 !== _p$.a && web.setStyleProperty(_el$, "z-index", _p$.a = _v$3);
361
+ _v$4 !== _p$.o && web.setStyleProperty(_el$, "opacity", _p$.o = _v$4);
346
362
  return _p$;
347
363
  }, {
348
364
  e: void 0,
@@ -350,58 +366,52 @@ var Label = (props) => {
350
366
  a: void 0,
351
367
  o: void 0
352
368
  });
353
- return _el$3;
369
+ return _el$;
354
370
  }
355
371
  });
356
372
  };
357
- var ProgressIndicator = (props) => {
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)">`);
374
+ var useFadeInOut = (visible) => {
358
375
  const [opacity, setOpacity] = solidJs.createSignal(0);
359
- let ref;
360
- solidJs.createEffect(() => {
361
- if (props.visible !== false) {
376
+ solidJs.createEffect(solidJs.on(() => visible, (isVisible) => {
377
+ if (isVisible !== false) {
362
378
  requestAnimationFrame(() => {
363
379
  setOpacity(1);
364
380
  });
365
381
  } else {
366
382
  setOpacity(0);
367
383
  }
368
- });
384
+ }));
385
+ return opacity;
386
+ };
387
+ var ProgressIndicator = (props) => {
388
+ const opacity = useFadeInOut(props.visible);
389
+ let progressIndicatorRef;
369
390
  const computedPosition = () => {
370
- const rect = ref?.getBoundingClientRect();
371
- if (!rect) return {
391
+ const boundingRect = progressIndicatorRef?.getBoundingClientRect();
392
+ if (!boundingRect) return {
372
393
  left: props.mouseX,
373
394
  top: props.mouseY
374
395
  };
375
- const viewportWidth = window.innerWidth;
376
396
  const viewportHeight = window.innerHeight;
377
- const CURSOR_OFFSET = 14;
378
- const VIEWPORT_MARGIN = 8;
379
- let indicatorLeft = props.mouseX - rect.width / 2;
380
- let indicatorTop = props.mouseY + CURSOR_OFFSET;
381
- if (indicatorTop + rect.height + VIEWPORT_MARGIN > viewportHeight) {
382
- indicatorTop = props.mouseY - rect.height - CURSOR_OFFSET;
383
- }
384
- indicatorTop = Math.max(VIEWPORT_MARGIN, Math.min(indicatorTop, viewportHeight - rect.height - VIEWPORT_MARGIN));
385
- indicatorLeft = Math.max(VIEWPORT_MARGIN, Math.min(indicatorLeft, viewportWidth - rect.width - VIEWPORT_MARGIN));
386
- return {
387
- left: indicatorLeft,
388
- top: indicatorTop
389
- };
397
+ const indicatorLeft = props.mouseX - boundingRect.width / 2;
398
+ const indicatorTop = props.mouseY + CURSOR_OFFSET_PX + boundingRect.height + VIEWPORT_MARGIN_PX > viewportHeight ? props.mouseY - boundingRect.height - CURSOR_OFFSET_PX : props.mouseY + CURSOR_OFFSET_PX;
399
+ return getClampedElementPosition(indicatorLeft, indicatorTop, boundingRect.width, boundingRect.height);
390
400
  };
391
401
  return web.createComponent(solidJs.Show, {
392
402
  get when() {
393
403
  return props.visible !== false;
394
404
  },
395
405
  get children() {
396
- var _el$8 = _tmpl$6(), _el$9 = _el$8.firstChild, _el$0 = _el$9.firstChild;
397
- var _ref$3 = ref;
398
- typeof _ref$3 === "function" ? web.use(_ref$3, _el$8) : ref = _el$8;
406
+ var _el$ = _tmpl$4(), _el$2 = _el$.firstChild, _el$3 = _el$2.firstChild;
407
+ var _ref$ = progressIndicatorRef;
408
+ typeof _ref$ === "function" ? web.use(_ref$, _el$) : progressIndicatorRef = _el$;
399
409
  web.effect((_p$) => {
400
- var _v$5 = `${computedPosition().top}px`, _v$6 = `${computedPosition().left}px`, _v$7 = opacity(), _v$8 = `${Math.min(100, Math.max(0, props.progress * 100))}%`;
401
- _v$5 !== _p$.e && web.setStyleProperty(_el$8, "top", _p$.e = _v$5);
402
- _v$6 !== _p$.t && web.setStyleProperty(_el$8, "left", _p$.t = _v$6);
403
- _v$7 !== _p$.a && web.setStyleProperty(_el$8, "opacity", _p$.a = _v$7);
404
- _v$8 !== _p$.o && web.setStyleProperty(_el$0, "width", _p$.o = _v$8);
410
+ var _v$ = `${computedPosition().top}px`, _v$2 = `${computedPosition().left}px`, _v$3 = opacity(), _v$4 = `${Math.min(100, Math.max(0, props.progress * 100))}%`;
411
+ _v$ !== _p$.e && web.setStyleProperty(_el$, "top", _p$.e = _v$);
412
+ _v$2 !== _p$.t && web.setStyleProperty(_el$, "left", _p$.t = _v$2);
413
+ _v$3 !== _p$.a && web.setStyleProperty(_el$, "opacity", _p$.a = _v$3);
414
+ _v$4 !== _p$.o && web.setStyleProperty(_el$3, "width", _p$.o = _v$4);
405
415
  return _p$;
406
416
  }, {
407
417
  e: void 0,
@@ -409,53 +419,131 @@ var ProgressIndicator = (props) => {
409
419
  a: void 0,
410
420
  o: void 0
411
421
  });
412
- return _el$8;
422
+ return _el$;
423
+ }
424
+ });
425
+ };
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">`);
427
+ 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;
432
+ let targetX = props.mouseX;
433
+ let targetY = props.mouseY;
434
+ let isAnimating = false;
435
+ 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
+ };
451
+ animationFrameId = requestAnimationFrame(animate);
452
+ };
453
+ solidJs.createEffect(solidJs.on(() => [props.mouseX, props.mouseY], ([newMouseX, newMouseY]) => {
454
+ targetX = newMouseX;
455
+ targetY = newMouseY;
456
+ if (!hasBeenRenderedOnce) {
457
+ setCurrentX(targetX);
458
+ setCurrentY(targetY);
459
+ hasBeenRenderedOnce = true;
460
+ return;
461
+ }
462
+ startAnimation();
463
+ }));
464
+ solidJs.onCleanup(() => {
465
+ if (animationFrameId !== null) {
466
+ cancelAnimationFrame(animationFrameId);
467
+ animationFrameId = null;
468
+ }
469
+ isAnimating = false;
470
+ });
471
+ return web.createComponent(solidJs.Show, {
472
+ get when() {
473
+ return props.visible !== false;
474
+ },
475
+ 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
+ });
486
+ return _el$;
413
487
  }
414
488
  });
415
489
  };
416
- var ReactGrabOverlay = (props) => {
490
+
491
+ // src/components/renderer.tsx
492
+ var ReactGrabRenderer = (props) => {
417
493
  return [web.createComponent(solidJs.Show, {
418
494
  get when() {
419
495
  return web.memo(() => !!props.selectionVisible)() && props.selectionBounds;
420
496
  },
421
497
  get children() {
422
- return web.createComponent(Overlay, {
498
+ return web.createComponent(SelectionBox, {
423
499
  variant: "selection",
424
500
  get bounds() {
425
501
  return props.selectionBounds;
426
502
  },
427
503
  get visible() {
428
504
  return props.selectionVisible;
505
+ }
506
+ });
507
+ }
508
+ }), web.createComponent(solidJs.Show, {
509
+ get when() {
510
+ return web.memo(() => !!(props.crosshairVisible === true && props.mouseX !== void 0))() && props.mouseY !== void 0;
511
+ },
512
+ get children() {
513
+ return web.createComponent(Crosshair, {
514
+ get mouseX() {
515
+ return props.mouseX;
429
516
  },
430
- lerpFactor: SELECTION_LERP_FACTOR
517
+ get mouseY() {
518
+ return props.mouseY;
519
+ },
520
+ visible: true
431
521
  });
432
522
  }
433
523
  }), web.createComponent(solidJs.Show, {
434
524
  get when() {
435
- return web.memo(() => !!props.marqueeVisible)() && props.marqueeBounds;
525
+ return web.memo(() => !!props.dragVisible)() && props.dragBounds;
436
526
  },
437
527
  get children() {
438
- return web.createComponent(Overlay, {
439
- variant: "marquee",
528
+ return web.createComponent(SelectionBox, {
529
+ variant: "drag",
440
530
  get bounds() {
441
- return props.marqueeBounds;
531
+ return props.dragBounds;
442
532
  },
443
533
  get visible() {
444
- return props.marqueeVisible;
445
- },
446
- lerpFactor: MARQUEE_LERP_FACTOR
534
+ return props.dragVisible;
535
+ }
447
536
  });
448
537
  }
449
538
  }), web.createComponent(solidJs.For, {
450
539
  get each() {
451
- return props.grabbedOverlays ?? [];
540
+ return props.grabbedBoxes ?? [];
452
541
  },
453
- children: (overlay) => web.createComponent(Overlay, {
542
+ children: (box) => web.createComponent(SelectionBox, {
454
543
  variant: "grabbed",
455
544
  get bounds() {
456
- return overlay.bounds;
457
- },
458
- visible: true
545
+ return box.bounds;
546
+ }
459
547
  })
460
548
  }), web.createComponent(solidJs.For, {
461
549
  get each() {
@@ -471,9 +559,7 @@ var ReactGrabOverlay = (props) => {
471
559
  },
472
560
  get y() {
473
561
  return label.y;
474
- },
475
- visible: true,
476
- zIndex: 2147483648
562
+ }
477
563
  })
478
564
  }), web.createComponent(solidJs.Show, {
479
565
  get when() {
@@ -528,15 +614,6 @@ bippy.instrument({
528
614
  bippy._fiberRoots.add(fiberRoot);
529
615
  }
530
616
  });
531
- var isValidSource = (source) => {
532
- const fileName = source.fileName;
533
- if (fileName.includes("node_modules")) return false;
534
- if (fileName.includes("/dist/")) return false;
535
- if (fileName.includes("/.next/")) return false;
536
- if (fileName.includes("/build/")) return false;
537
- if (fileName.includes("webpack-internal:")) return false;
538
- return true;
539
- };
540
617
  var getSourceTrace = async (element) => {
541
618
  const fiber = bippy.getFiberFromHostInstance(element);
542
619
  if (!fiber) return null;
@@ -546,7 +623,7 @@ var getSourceTrace = async (element) => {
546
623
  Number.MAX_SAFE_INTEGER
547
624
  );
548
625
  if (!sources) return null;
549
- return sources.filter(isValidSource);
626
+ return sources;
550
627
  };
551
628
  var getHTMLSnippet = (element) => {
552
629
  const semanticTags = /* @__PURE__ */ new Set([
@@ -821,7 +898,105 @@ var copyContentFallback = (content) => {
821
898
  }
822
899
  };
823
900
 
901
+ // src/utils/is-element-visible.ts
902
+ var isElementVisible = (element, computedStyle = window.getComputedStyle(element)) => {
903
+ return computedStyle.display !== "none" && computedStyle.visibility !== "hidden" && computedStyle.opacity !== "0";
904
+ };
905
+
906
+ // src/utils/is-valid-grabbable-element.ts
907
+ var isValidGrabbableElement = (element) => {
908
+ if (element.closest(`[${ATTRIBUTE_NAME}]`)) {
909
+ return false;
910
+ }
911
+ const computedStyle = window.getComputedStyle(element);
912
+ if (!isElementVisible(element, computedStyle)) {
913
+ return false;
914
+ }
915
+ if (computedStyle.pointerEvents === "none") {
916
+ return false;
917
+ }
918
+ return true;
919
+ };
920
+
921
+ // src/utils/get-element-at-position.ts
922
+ var getElementAtPosition = (clientX, clientY) => {
923
+ const elementsAtPoint = document.elementsFromPoint(clientX, clientY);
924
+ for (const candidateElement of elementsAtPoint) {
925
+ if (isValidGrabbableElement(candidateElement)) {
926
+ return candidateElement;
927
+ }
928
+ }
929
+ return null;
930
+ };
931
+
932
+ // src/utils/get-elements-in-drag.ts
933
+ var DRAG_COVERAGE_THRESHOLD = 0.75;
934
+ var filterElementsInDrag = (dragRect, isValidGrabbableElement2, shouldCheckCoverage) => {
935
+ const elements = [];
936
+ const allElements = Array.from(document.querySelectorAll("*"));
937
+ const dragLeft = dragRect.x;
938
+ const dragTop = dragRect.y;
939
+ const dragRight = dragRect.x + dragRect.width;
940
+ const dragBottom = dragRect.y + dragRect.height;
941
+ for (const candidateElement of allElements) {
942
+ if (!shouldCheckCoverage) {
943
+ const tagName = (candidateElement.tagName || "").toUpperCase();
944
+ if (tagName === "HTML" || tagName === "BODY") continue;
945
+ }
946
+ if (!isValidGrabbableElement2(candidateElement)) {
947
+ continue;
948
+ }
949
+ const elementRect = candidateElement.getBoundingClientRect();
950
+ const elementLeft = elementRect.left;
951
+ const elementTop = elementRect.top;
952
+ const elementRight = elementRect.left + elementRect.width;
953
+ const elementBottom = elementRect.top + elementRect.height;
954
+ if (shouldCheckCoverage) {
955
+ const intersectionLeft = Math.max(dragLeft, elementLeft);
956
+ const intersectionTop = Math.max(dragTop, elementTop);
957
+ const intersectionRight = Math.min(dragRight, elementRight);
958
+ const intersectionBottom = Math.min(dragBottom, elementBottom);
959
+ const intersectionWidth = Math.max(0, intersectionRight - intersectionLeft);
960
+ const intersectionHeight = Math.max(0, intersectionBottom - intersectionTop);
961
+ const intersectionArea = intersectionWidth * intersectionHeight;
962
+ const elementArea = Math.max(0, elementRect.width * elementRect.height);
963
+ const hasMajorityCoverage = elementArea > 0 && intersectionArea / elementArea >= DRAG_COVERAGE_THRESHOLD;
964
+ if (hasMajorityCoverage) {
965
+ elements.push(candidateElement);
966
+ }
967
+ } else {
968
+ const hasIntersection = elementLeft < dragRight && elementRight > dragLeft && elementTop < dragBottom && elementBottom > dragTop;
969
+ if (hasIntersection) {
970
+ elements.push(candidateElement);
971
+ }
972
+ }
973
+ }
974
+ return elements;
975
+ };
976
+ var getElementsInDrag = (dragRect, isValidGrabbableElement2) => {
977
+ return filterElementsInDrag(dragRect, isValidGrabbableElement2, true);
978
+ };
979
+ var getElementsInDragLoose = (dragRect, isValidGrabbableElement2) => {
980
+ return filterElementsInDrag(dragRect, isValidGrabbableElement2, false);
981
+ };
982
+
983
+ // src/utils/create-element-bounds.ts
984
+ var createElementBounds = (element) => {
985
+ const boundingRect = element.getBoundingClientRect();
986
+ const computedStyle = window.getComputedStyle(element);
987
+ return {
988
+ borderRadius: computedStyle.borderRadius || "0px",
989
+ height: boundingRect.height,
990
+ transform: computedStyle.transform || "none",
991
+ width: boundingRect.width,
992
+ x: boundingRect.left,
993
+ y: boundingRect.top
994
+ };
995
+ };
996
+
824
997
  // src/core.tsx
998
+ var SUCCESS_LABEL_DURATION_MS = 1700;
999
+ var PROGRESS_INDICATOR_DELAY_MS = 150;
825
1000
  var init = (rawOptions) => {
826
1001
  const options = {
827
1002
  enabled: true,
@@ -832,215 +1007,111 @@ var init = (rawOptions) => {
832
1007
  return;
833
1008
  }
834
1009
  return solidJs.createRoot((dispose) => {
1010
+ const OFFSCREEN_POSITION = -1e3;
835
1011
  const [isHoldingKeys, setIsHoldingKeys] = solidJs.createSignal(false);
836
- const [mouseX, setMouseX] = solidJs.createSignal(-1e3);
837
- const [mouseY, setMouseY] = solidJs.createSignal(-1e3);
1012
+ const [mouseX, setMouseX] = solidJs.createSignal(OFFSCREEN_POSITION);
1013
+ const [mouseY, setMouseY] = solidJs.createSignal(OFFSCREEN_POSITION);
838
1014
  const [isDragging, setIsDragging] = solidJs.createSignal(false);
839
- const [dragStartX, setDragStartX] = solidJs.createSignal(-1e3);
840
- const [dragStartY, setDragStartY] = solidJs.createSignal(-1e3);
1015
+ const [dragStartX, setDragStartX] = solidJs.createSignal(OFFSCREEN_POSITION);
1016
+ const [dragStartY, setDragStartY] = solidJs.createSignal(OFFSCREEN_POSITION);
841
1017
  const [isCopying, setIsCopying] = solidJs.createSignal(false);
842
1018
  const [lastGrabbedElement, setLastGrabbedElement] = solidJs.createSignal(null);
843
1019
  const [progressStartTime, setProgressStartTime] = solidJs.createSignal(null);
844
1020
  const [progressTick, setProgressTick] = solidJs.createSignal(0);
845
- const [grabbedOverlays, setGrabbedOverlays] = solidJs.createSignal([]);
1021
+ const [grabbedBoxes, setGrabbedBoxes] = solidJs.createSignal([]);
846
1022
  const [successLabels, setSuccessLabels] = solidJs.createSignal([]);
847
1023
  const [isActivated, setIsActivated] = solidJs.createSignal(false);
848
1024
  const [showProgressIndicator, setShowProgressIndicator] = solidJs.createSignal(false);
1025
+ const [grabMouseX, setGrabMouseX] = solidJs.createSignal(null);
1026
+ const [grabMouseY, setGrabMouseY] = solidJs.createSignal(null);
849
1027
  let holdTimerId = null;
850
1028
  let progressAnimationId = null;
851
1029
  let progressDelayTimerId = null;
852
- const isOverlayActive = solidJs.createMemo(() => isActivated() && !isCopying());
1030
+ const isRendererActive = solidJs.createMemo(() => isActivated() && !isCopying());
1031
+ const hasValidMousePosition = solidJs.createMemo(() => mouseX() > OFFSCREEN_POSITION && mouseY() > OFFSCREEN_POSITION);
853
1032
  const isTargetKeyCombination = (event) => (event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "c";
854
- const getElementAtPosition = (x, y) => {
855
- const elementsAtPoint = document.elementsFromPoint(x, y);
856
- for (const candidateElement of elementsAtPoint) {
857
- if (candidateElement.closest(`[${ATTRIBUTE_NAME}]`)) {
858
- continue;
859
- }
860
- const computedStyle = window.getComputedStyle(candidateElement);
861
- if (!isElementVisible(candidateElement, computedStyle)) {
862
- continue;
863
- }
864
- if (computedStyle.pointerEvents === "none") {
865
- continue;
866
- }
867
- return candidateElement;
868
- }
869
- return null;
870
- };
871
- const getElementsInMarquee = (marqueeRect) => {
872
- const elements = [];
873
- const allElements = Array.from(document.querySelectorAll("*"));
874
- const marqueeLeft = marqueeRect.x;
875
- const marqueeTop = marqueeRect.y;
876
- const marqueeRight = marqueeRect.x + marqueeRect.width;
877
- const marqueeBottom = marqueeRect.y + marqueeRect.height;
878
- for (const candidateElement of allElements) {
879
- if (candidateElement.closest(`[${ATTRIBUTE_NAME}]`)) {
880
- continue;
881
- }
882
- const computedStyle = window.getComputedStyle(candidateElement);
883
- if (!isElementVisible(candidateElement, computedStyle)) {
884
- continue;
885
- }
886
- if (computedStyle.pointerEvents === "none") {
887
- continue;
888
- }
889
- const rect = candidateElement.getBoundingClientRect();
890
- const elementLeft = rect.left;
891
- const elementTop = rect.top;
892
- const elementRight = rect.left + rect.width;
893
- const elementBottom = rect.top + rect.height;
894
- const intersectionLeft = Math.max(marqueeLeft, elementLeft);
895
- const intersectionTop = Math.max(marqueeTop, elementTop);
896
- const intersectionRight = Math.min(marqueeRight, elementRight);
897
- const intersectionBottom = Math.min(marqueeBottom, elementBottom);
898
- const intersectionWidth = Math.max(0, intersectionRight - intersectionLeft);
899
- const intersectionHeight = Math.max(0, intersectionBottom - intersectionTop);
900
- const intersectionArea = intersectionWidth * intersectionHeight;
901
- const elementArea = Math.max(0, rect.width * rect.height);
902
- const COVERAGE_THRESHOLD = 0.75;
903
- const hasMajorityCoverage = elementArea > 0 && intersectionArea / elementArea >= COVERAGE_THRESHOLD;
904
- if (hasMajorityCoverage) {
905
- elements.push(candidateElement);
906
- }
907
- }
908
- return elements;
909
- };
910
- const getElementsInMarqueeLoose = (marqueeRect) => {
911
- const elements = [];
912
- const allElements = Array.from(document.querySelectorAll("*"));
913
- const marqueeLeft = marqueeRect.x;
914
- const marqueeTop = marqueeRect.y;
915
- const marqueeRight = marqueeRect.x + marqueeRect.width;
916
- const marqueeBottom = marqueeRect.y + marqueeRect.height;
917
- for (const candidateElement of allElements) {
918
- if (candidateElement.closest(`[${ATTRIBUTE_NAME}]`)) {
919
- continue;
920
- }
921
- const tag = (candidateElement.tagName || "").toUpperCase();
922
- if (tag === "HTML" || tag === "BODY") continue;
923
- const computedStyle = window.getComputedStyle(candidateElement);
924
- if (!isElementVisible(candidateElement, computedStyle)) {
925
- continue;
926
- }
927
- if (computedStyle.pointerEvents === "none") {
928
- continue;
929
- }
930
- const rect = candidateElement.getBoundingClientRect();
931
- const elementLeft = rect.left;
932
- const elementTop = rect.top;
933
- const elementRight = rect.left + rect.width;
934
- const elementBottom = rect.top + rect.height;
935
- const intersects = elementLeft < marqueeRight && elementRight > marqueeLeft && elementTop < marqueeBottom && elementBottom > marqueeTop;
936
- if (intersects) {
937
- elements.push(candidateElement);
938
- }
939
- }
940
- return elements;
941
- };
942
- const wrapInReferencedElement = (content) => `
943
-
944
- <referenced_element>
945
- ${content}
946
- </referenced_element>`;
947
- const wrapInReferencedElements = (content) => `
948
-
949
- <referenced_elements>
950
- ${content}
951
- </referenced_elements>`;
952
- const addGrabbedOverlay = (bounds) => {
953
- const id = `grabbed-${Date.now()}-${Math.random()}`;
954
- setGrabbedOverlays((prev) => [...prev, {
955
- id,
1033
+ const addGrabbedBox = (bounds) => {
1034
+ const boxId = `grabbed-${Date.now()}-${Math.random()}`;
1035
+ const newBox = {
1036
+ id: boxId,
956
1037
  bounds
957
- }]);
958
- setTimeout(() => {
959
- setGrabbedOverlays((prev) => prev.filter((overlay) => overlay.id !== id));
960
- }, 300);
1038
+ };
1039
+ const currentBoxes = grabbedBoxes();
1040
+ setGrabbedBoxes([...currentBoxes, newBox]);
961
1041
  };
962
- const addSuccessLabel = (text, x, y) => {
963
- const id = `success-${Date.now()}-${Math.random()}`;
964
- setSuccessLabels((prev) => [...prev, {
965
- id,
1042
+ const addSuccessLabel = (text, positionX, positionY) => {
1043
+ const labelId = `success-${Date.now()}-${Math.random()}`;
1044
+ setSuccessLabels((previousLabels) => [...previousLabels, {
1045
+ id: labelId,
966
1046
  text,
967
- x,
968
- y
1047
+ x: positionX,
1048
+ y: positionY
969
1049
  }]);
970
1050
  setTimeout(() => {
971
- setSuccessLabels((prev) => prev.filter((label) => label.id !== id));
972
- }, 1700);
1051
+ setSuccessLabels((previousLabels) => previousLabels.filter((label) => label.id !== labelId));
1052
+ }, SUCCESS_LABEL_DURATION_MS);
1053
+ };
1054
+ const formatStackTrace = (stackTrace) => {
1055
+ return stackTrace.map((source) => {
1056
+ const functionName = source.functionName ?? "anonymous";
1057
+ const fileName = source.fileName ?? "unknown";
1058
+ const lineNumber = source.lineNumber ?? 0;
1059
+ const columnNumber = source.columnNumber ?? 0;
1060
+ return ` ${functionName} - ${fileName}:${lineNumber}:${columnNumber}`;
1061
+ }).join("\n");
1062
+ };
1063
+ const getElementContentWithTrace = async (element) => {
1064
+ const elementHtml = getHTMLSnippet(element);
1065
+ const componentStackTrace = await getSourceTrace(element);
1066
+ if (componentStackTrace?.length) {
1067
+ const formattedStackTrace = formatStackTrace(componentStackTrace);
1068
+ return `${elementHtml}
1069
+
1070
+ Component owner stack:
1071
+ ${formattedStackTrace}`;
1072
+ }
1073
+ return elementHtml;
973
1074
  };
1075
+ const getElementTagName = (element) => (element.tagName || "").toLowerCase();
974
1076
  const handleCopy = async (targetElement2) => {
975
1077
  const elementBounds = targetElement2.getBoundingClientRect();
976
- const tagName = (targetElement2.tagName || "").toLowerCase();
977
- addGrabbedOverlay({
978
- borderRadius: window.getComputedStyle(targetElement2).borderRadius || "0px",
979
- height: elementBounds.height,
980
- transform: window.getComputedStyle(targetElement2).transform || "none",
981
- width: elementBounds.width,
982
- x: elementBounds.left,
983
- y: elementBounds.top
984
- });
1078
+ const tagName = getElementTagName(targetElement2);
1079
+ setGrabMouseX(mouseX());
1080
+ setGrabMouseY(mouseY());
1081
+ addGrabbedBox(createElementBounds(targetElement2));
985
1082
  try {
986
- const elementHtml = getHTMLSnippet(targetElement2);
987
- await copyContent(wrapInReferencedElement(elementHtml));
988
- const componentStackTrace = await getSourceTrace(targetElement2);
989
- if (componentStackTrace?.length) {
990
- const formattedStackTrace = componentStackTrace.map((source) => ` ${source.functionName} - ${source.fileName}:${source.lineNumber}:${source.columnNumber}`).join("\n");
991
- await copyContent(wrapInReferencedElement(`${elementHtml}
992
-
993
- Component owner stack:
994
- ${formattedStackTrace}`));
995
- }
1083
+ const content = await getElementContentWithTrace(targetElement2);
1084
+ await copyContent(content);
996
1085
  } catch {
997
1086
  }
998
1087
  addSuccessLabel(tagName ? `<${tagName}>` : "<element>", elementBounds.left, elementBounds.top);
999
1088
  };
1000
1089
  const handleMultipleCopy = async (targetElements) => {
1001
1090
  if (targetElements.length === 0) return;
1002
- let minX = Infinity;
1003
- let minY = Infinity;
1091
+ let minPositionX = Infinity;
1092
+ let minPositionY = Infinity;
1093
+ setGrabMouseX(mouseX());
1094
+ setGrabMouseY(mouseY());
1004
1095
  for (const element of targetElements) {
1005
1096
  const elementBounds = element.getBoundingClientRect();
1006
- minX = Math.min(minX, elementBounds.left);
1007
- minY = Math.min(minY, elementBounds.top);
1008
- addGrabbedOverlay({
1009
- borderRadius: window.getComputedStyle(element).borderRadius || "0px",
1010
- height: elementBounds.height,
1011
- transform: window.getComputedStyle(element).transform || "none",
1012
- width: elementBounds.width,
1013
- x: elementBounds.left,
1014
- y: elementBounds.top
1015
- });
1097
+ minPositionX = Math.min(minPositionX, elementBounds.left);
1098
+ minPositionY = Math.min(minPositionY, elementBounds.top);
1099
+ addGrabbedBox(createElementBounds(element));
1016
1100
  }
1017
1101
  try {
1018
- const elementSnippets = [];
1019
- for (const element of targetElements) {
1020
- const elementHtml = getHTMLSnippet(element);
1021
- const componentStackTrace = await getSourceTrace(element);
1022
- if (componentStackTrace?.length) {
1023
- const formattedStackTrace = componentStackTrace.map((source) => ` ${source.functionName} - ${source.fileName}:${source.lineNumber}:${source.columnNumber}`).join("\n");
1024
- elementSnippets.push(`${elementHtml}
1025
-
1026
- Component owner stack:
1027
- ${formattedStackTrace}`);
1028
- } else {
1029
- elementSnippets.push(elementHtml);
1030
- }
1031
- }
1102
+ const elementSnippets = await Promise.all(targetElements.map((element) => getElementContentWithTrace(element)));
1032
1103
  const combinedContent = elementSnippets.join("\n\n---\n\n");
1033
- await copyContent(wrapInReferencedElements(combinedContent));
1104
+ await copyContent(combinedContent);
1034
1105
  } catch {
1035
1106
  }
1036
- addSuccessLabel(`${targetElements.length} elements`, minX, minY);
1107
+ addSuccessLabel(`${targetElements.length} elements`, minPositionX, minPositionY);
1037
1108
  };
1038
1109
  const targetElement = solidJs.createMemo(() => {
1039
- if (!isOverlayActive() || isDragging()) return null;
1110
+ if (!isRendererActive() || isDragging()) return null;
1040
1111
  return getElementAtPosition(mouseX(), mouseY());
1041
1112
  });
1042
1113
  const selectionBounds = solidJs.createMemo(() => {
1043
- const element = targetElement() ?? lastGrabbedElement();
1114
+ const element = targetElement();
1044
1115
  if (!element) return void 0;
1045
1116
  const elementBounds = element.getBoundingClientRect();
1046
1117
  const computedStyle = window.getComputedStyle(element);
@@ -1053,54 +1124,80 @@ ${formattedStackTrace}`);
1053
1124
  y: elementBounds.top
1054
1125
  };
1055
1126
  });
1056
- const marqueeBounds = solidJs.createMemo(() => {
1057
- if (!isDragging()) return void 0;
1058
- const marqueeX = Math.min(dragStartX(), mouseX());
1059
- const marqueeY = Math.min(dragStartY(), mouseY());
1060
- const marqueeWidth = Math.abs(mouseX() - dragStartX());
1061
- const marqueeHeight = Math.abs(mouseY() - dragStartY());
1127
+ const DRAG_THRESHOLD_PX = 5;
1128
+ const getDragDistance = (endX, endY) => ({
1129
+ x: Math.abs(endX - dragStartX()),
1130
+ y: Math.abs(endY - dragStartY())
1131
+ });
1132
+ const isDraggingBeyondThreshold = solidJs.createMemo(() => {
1133
+ if (!isDragging()) return false;
1134
+ const dragDistance = getDragDistance(mouseX(), mouseY());
1135
+ return dragDistance.x > DRAG_THRESHOLD_PX || dragDistance.y > DRAG_THRESHOLD_PX;
1136
+ });
1137
+ const getDragRect = (endX, endY) => {
1138
+ const dragX = Math.min(dragStartX(), endX);
1139
+ const dragY = Math.min(dragStartY(), endY);
1140
+ const dragWidth = Math.abs(endX - dragStartX());
1141
+ const dragHeight = Math.abs(endY - dragStartY());
1142
+ return {
1143
+ x: dragX,
1144
+ y: dragY,
1145
+ width: dragWidth,
1146
+ height: dragHeight
1147
+ };
1148
+ };
1149
+ const dragBounds = solidJs.createMemo(() => {
1150
+ if (!isDraggingBeyondThreshold()) return void 0;
1151
+ const drag = getDragRect(mouseX(), mouseY());
1062
1152
  return {
1063
1153
  borderRadius: "0px",
1064
- height: marqueeHeight,
1154
+ height: drag.height,
1065
1155
  transform: "none",
1066
- width: marqueeWidth,
1067
- x: marqueeX,
1068
- y: marqueeY
1156
+ width: drag.width,
1157
+ x: drag.x,
1158
+ y: drag.y
1069
1159
  };
1070
1160
  });
1071
1161
  const labelText = solidJs.createMemo(() => {
1072
1162
  const element = targetElement();
1073
1163
  if (!element) return "";
1074
- const tagName = (element.tagName || "").toLowerCase();
1164
+ const tagName = getElementTagName(element);
1075
1165
  return tagName ? `<${tagName}>` : "<element>";
1076
1166
  });
1077
1167
  const labelPosition = solidJs.createMemo(() => {
1078
- const element = targetElement() ?? lastGrabbedElement();
1079
- if (element) {
1080
- const rect = element.getBoundingClientRect();
1081
- return {
1082
- x: rect.left,
1083
- y: rect.top
1084
- };
1085
- }
1086
1168
  return {
1087
1169
  x: mouseX(),
1088
1170
  y: mouseY()
1089
1171
  };
1090
1172
  });
1091
- solidJs.createEffect(() => {
1092
- const current = targetElement();
1093
- const last = lastGrabbedElement();
1094
- if (last && current && last !== current) {
1173
+ const isSameAsLast = solidJs.createMemo(() => {
1174
+ const currentElement = targetElement();
1175
+ const lastElement = lastGrabbedElement();
1176
+ return !!currentElement && currentElement === lastElement;
1177
+ });
1178
+ solidJs.createEffect(solidJs.on(() => [targetElement(), lastGrabbedElement()], ([currentElement, lastElement]) => {
1179
+ if (lastElement && currentElement && lastElement !== currentElement) {
1095
1180
  setLastGrabbedElement(null);
1096
1181
  }
1097
- });
1182
+ }));
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
+ }));
1098
1195
  const progress = solidJs.createMemo(() => {
1099
1196
  const startTime = progressStartTime();
1100
1197
  progressTick();
1101
1198
  if (startTime === null) return 0;
1102
- const elapsed = Date.now() - startTime;
1103
- return Math.min(elapsed / options.keyHoldDuration, 1);
1199
+ const elapsedTime = Date.now() - startTime;
1200
+ return Math.min(elapsedTime / options.keyHoldDuration, 1);
1104
1201
  });
1105
1202
  const startProgressAnimation = () => {
1106
1203
  setProgressStartTime(Date.now());
@@ -1108,10 +1205,10 @@ ${formattedStackTrace}`);
1108
1205
  progressDelayTimerId = window.setTimeout(() => {
1109
1206
  setShowProgressIndicator(true);
1110
1207
  progressDelayTimerId = null;
1111
- }, 150);
1208
+ }, PROGRESS_INDICATOR_DELAY_MS);
1112
1209
  const animateProgress = () => {
1113
1210
  if (progressStartTime() === null) return;
1114
- setProgressTick((t) => t + 1);
1211
+ setProgressTick((tick) => tick + 1);
1115
1212
  const currentProgress = progress();
1116
1213
  if (currentProgress < 1) {
1117
1214
  progressAnimationId = requestAnimationFrame(animateProgress);
@@ -1131,16 +1228,22 @@ ${formattedStackTrace}`);
1131
1228
  setProgressStartTime(null);
1132
1229
  setShowProgressIndicator(false);
1133
1230
  };
1134
- const activateOverlay = () => {
1231
+ const activateRenderer = () => {
1135
1232
  stopProgressAnimation();
1136
1233
  setIsActivated(true);
1234
+ document.body.style.cursor = "crosshair";
1137
1235
  };
1138
1236
  const abortController = new AbortController();
1139
- const signal = abortController.signal;
1237
+ const eventListenerSignal = abortController.signal;
1140
1238
  window.addEventListener("keydown", (event) => {
1141
1239
  if (event.key === "Escape" && isHoldingKeys()) {
1142
1240
  setIsHoldingKeys(false);
1143
1241
  setIsActivated(false);
1242
+ document.body.style.cursor = "";
1243
+ if (isDragging()) {
1244
+ setIsDragging(false);
1245
+ document.body.style.userSelect = "";
1246
+ }
1144
1247
  if (holdTimerId) window.clearTimeout(holdTimerId);
1145
1248
  stopProgressAnimation();
1146
1249
  return;
@@ -1150,67 +1253,56 @@ ${formattedStackTrace}`);
1150
1253
  setIsHoldingKeys(true);
1151
1254
  startProgressAnimation();
1152
1255
  holdTimerId = window.setTimeout(() => {
1153
- activateOverlay();
1256
+ activateRenderer();
1154
1257
  options.onActivate?.();
1155
1258
  }, options.keyHoldDuration);
1156
1259
  }
1157
1260
  }, {
1158
- signal
1261
+ signal: eventListenerSignal
1159
1262
  });
1160
1263
  window.addEventListener("keyup", (event) => {
1161
1264
  if (isHoldingKeys() && (!isTargetKeyCombination(event) || event.key.toLowerCase() === "c")) {
1162
1265
  setIsHoldingKeys(false);
1163
1266
  setIsActivated(false);
1267
+ document.body.style.cursor = "";
1164
1268
  if (holdTimerId) window.clearTimeout(holdTimerId);
1165
1269
  stopProgressAnimation();
1166
1270
  }
1167
1271
  }, {
1168
- signal
1272
+ signal: eventListenerSignal
1169
1273
  });
1170
1274
  window.addEventListener("mousemove", (event) => {
1171
1275
  setMouseX(event.clientX);
1172
1276
  setMouseY(event.clientY);
1173
1277
  }, {
1174
- signal
1278
+ signal: eventListenerSignal
1175
1279
  });
1176
1280
  window.addEventListener("mousedown", (event) => {
1177
- if (!isOverlayActive() || isCopying()) return;
1281
+ if (!isRendererActive() || isCopying()) return;
1282
+ event.preventDefault();
1178
1283
  setIsDragging(true);
1179
1284
  setDragStartX(event.clientX);
1180
1285
  setDragStartY(event.clientY);
1286
+ document.body.style.userSelect = "none";
1181
1287
  }, {
1182
- signal
1288
+ signal: eventListenerSignal
1183
1289
  });
1184
1290
  window.addEventListener("mouseup", (event) => {
1185
1291
  if (!isDragging()) return;
1186
- const dragDistanceX = Math.abs(event.clientX - dragStartX());
1187
- const dragDistanceY = Math.abs(event.clientY - dragStartY());
1188
- const DRAG_THRESHOLD = 5;
1189
- const wasDrag = dragDistanceX > DRAG_THRESHOLD || dragDistanceY > DRAG_THRESHOLD;
1292
+ const dragDistance = getDragDistance(event.clientX, event.clientY);
1293
+ const wasDragGesture = dragDistance.x > DRAG_THRESHOLD_PX || dragDistance.y > DRAG_THRESHOLD_PX;
1190
1294
  setIsDragging(false);
1191
- if (wasDrag) {
1192
- const marqueeX = Math.min(dragStartX(), event.clientX);
1193
- const marqueeY = Math.min(dragStartY(), event.clientY);
1194
- const marqueeWidth = Math.abs(event.clientX - dragStartX());
1195
- const marqueeHeight = Math.abs(event.clientY - dragStartY());
1196
- const elements = getElementsInMarquee({
1197
- x: marqueeX,
1198
- y: marqueeY,
1199
- width: marqueeWidth,
1200
- height: marqueeHeight
1201
- });
1295
+ document.body.style.userSelect = "";
1296
+ if (wasDragGesture) {
1297
+ const dragRect = getDragRect(event.clientX, event.clientY);
1298
+ const elements = getElementsInDrag(dragRect, isValidGrabbableElement);
1202
1299
  if (elements.length > 0) {
1203
1300
  setIsCopying(true);
1204
1301
  void handleMultipleCopy(elements).finally(() => {
1205
1302
  setIsCopying(false);
1206
1303
  });
1207
1304
  } else {
1208
- const fallbackElements = getElementsInMarqueeLoose({
1209
- x: marqueeX,
1210
- y: marqueeY,
1211
- width: marqueeWidth,
1212
- height: marqueeHeight
1213
- });
1305
+ const fallbackElements = getElementsInDragLoose(dragRect, isValidGrabbableElement);
1214
1306
  if (fallbackElements.length > 0) {
1215
1307
  setIsCopying(true);
1216
1308
  void handleMultipleCopy(fallbackElements).finally(() => {
@@ -1228,51 +1320,84 @@ ${formattedStackTrace}`);
1228
1320
  });
1229
1321
  }
1230
1322
  }, {
1231
- signal
1232
- });
1233
- window.addEventListener("scroll", () => {
1234
- }, {
1235
- signal,
1236
- capture: true
1237
- });
1238
- window.addEventListener("resize", () => {
1239
- }, {
1240
- signal
1323
+ signal: eventListenerSignal
1241
1324
  });
1242
1325
  document.addEventListener("visibilitychange", () => {
1243
1326
  if (document.hidden) {
1244
- setGrabbedOverlays([]);
1327
+ setGrabbedBoxes([]);
1245
1328
  }
1246
1329
  }, {
1247
- signal
1330
+ signal: eventListenerSignal
1248
1331
  });
1249
1332
  solidJs.onCleanup(() => {
1250
1333
  abortController.abort();
1251
1334
  if (holdTimerId) window.clearTimeout(holdTimerId);
1252
1335
  stopProgressAnimation();
1336
+ document.body.style.userSelect = "";
1337
+ document.body.style.cursor = "";
1253
1338
  });
1254
- const overlayRoot = mountRoot();
1255
- const overlayProps = solidJs.createMemo(() => ({
1256
- selectionVisible: isOverlayActive() && !isDragging() && !!selectionBounds(),
1257
- selectionBounds: selectionBounds(),
1258
- marqueeVisible: isOverlayActive() && isDragging(),
1259
- marqueeBounds: marqueeBounds(),
1260
- grabbedOverlays: grabbedOverlays(),
1261
- successLabels: successLabels(),
1262
- labelVariant: isCopying() ? "processing" : "hover",
1263
- labelText: labelText(),
1264
- labelX: labelPosition().x,
1265
- labelY: labelPosition().y,
1266
- labelVisible: isOverlayActive() && !isDragging() && !!targetElement() || isCopying(),
1267
- progressVisible: isHoldingKeys() && showProgressIndicator(),
1268
- progress: progress(),
1269
- mouseX: mouseX(),
1270
- mouseY: mouseY()
1271
- }));
1272
- web.render(() => web.createComponent(ReactGrabOverlay, web.mergeProps(overlayProps)), overlayRoot);
1339
+ const rendererRoot = mountRoot();
1340
+ const selectionVisible = solidJs.createMemo(() => false);
1341
+ const dragVisible = solidJs.createMemo(() => isRendererActive() && isDraggingBeyondThreshold());
1342
+ const labelVariant = solidJs.createMemo(() => isCopying() ? "processing" : "hover");
1343
+ const labelVisible = solidJs.createMemo(() => isRendererActive() && !isDragging() && !!targetElement() && !isSameAsLast() || isCopying());
1344
+ const progressVisible = solidJs.createMemo(() => isHoldingKeys() && showProgressIndicator() && hasValidMousePosition());
1345
+ const crosshairVisible = solidJs.createMemo(() => isRendererActive() && !isDragging());
1346
+ web.render(() => web.createComponent(ReactGrabRenderer, {
1347
+ get selectionVisible() {
1348
+ return selectionVisible();
1349
+ },
1350
+ get selectionBounds() {
1351
+ return selectionBounds();
1352
+ },
1353
+ get dragVisible() {
1354
+ return dragVisible();
1355
+ },
1356
+ get dragBounds() {
1357
+ return dragBounds();
1358
+ },
1359
+ get grabbedBoxes() {
1360
+ return grabbedBoxes();
1361
+ },
1362
+ get successLabels() {
1363
+ return successLabels();
1364
+ },
1365
+ get labelVariant() {
1366
+ return labelVariant();
1367
+ },
1368
+ get labelText() {
1369
+ return labelText();
1370
+ },
1371
+ get labelX() {
1372
+ return labelPosition().x;
1373
+ },
1374
+ get labelY() {
1375
+ return labelPosition().y;
1376
+ },
1377
+ get labelVisible() {
1378
+ return labelVisible();
1379
+ },
1380
+ get progressVisible() {
1381
+ return progressVisible();
1382
+ },
1383
+ get progress() {
1384
+ return progress();
1385
+ },
1386
+ get mouseX() {
1387
+ return mouseX();
1388
+ },
1389
+ get mouseY() {
1390
+ return mouseY();
1391
+ },
1392
+ get crosshairVisible() {
1393
+ return crosshairVisible();
1394
+ }
1395
+ }), rendererRoot);
1273
1396
  return dispose;
1274
1397
  });
1275
1398
  };
1276
1399
 
1277
1400
  // src/index.ts
1278
1401
  init();
1402
+
1403
+ exports.init = init;