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