react-grab 0.0.23 → 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() {
@@ -810,7 +896,105 @@ var copyContentFallback = (content) => {
810
896
  }
811
897
  };
812
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
+
813
995
  // src/core.tsx
996
+ var SUCCESS_LABEL_DURATION_MS = 1700;
997
+ var PROGRESS_INDICATOR_DELAY_MS = 150;
814
998
  var init = (rawOptions) => {
815
999
  const options = {
816
1000
  enabled: true,
@@ -821,209 +1005,112 @@ var init = (rawOptions) => {
821
1005
  return;
822
1006
  }
823
1007
  return createRoot((dispose) => {
1008
+ const OFFSCREEN_POSITION = -1e3;
824
1009
  const [isHoldingKeys, setIsHoldingKeys] = createSignal(false);
825
- const [mouseX, setMouseX] = createSignal(-1e3);
826
- const [mouseY, setMouseY] = createSignal(-1e3);
1010
+ const [mouseX, setMouseX] = createSignal(OFFSCREEN_POSITION);
1011
+ const [mouseY, setMouseY] = createSignal(OFFSCREEN_POSITION);
827
1012
  const [isDragging, setIsDragging] = createSignal(false);
828
- const [dragStartX, setDragStartX] = createSignal(-1e3);
829
- const [dragStartY, setDragStartY] = createSignal(-1e3);
1013
+ const [dragStartX, setDragStartX] = createSignal(OFFSCREEN_POSITION);
1014
+ const [dragStartY, setDragStartY] = createSignal(OFFSCREEN_POSITION);
830
1015
  const [isCopying, setIsCopying] = createSignal(false);
831
1016
  const [lastGrabbedElement, setLastGrabbedElement] = createSignal(null);
832
1017
  const [progressStartTime, setProgressStartTime] = createSignal(null);
833
1018
  const [progressTick, setProgressTick] = createSignal(0);
834
- const [grabbedOverlays, setGrabbedOverlays] = createSignal([]);
1019
+ const [grabbedBoxes, setGrabbedBoxes] = createSignal([]);
835
1020
  const [successLabels, setSuccessLabels] = createSignal([]);
836
1021
  const [isActivated, setIsActivated] = createSignal(false);
837
1022
  const [showProgressIndicator, setShowProgressIndicator] = createSignal(false);
1023
+ const [grabMouseX, setGrabMouseX] = createSignal(null);
1024
+ const [grabMouseY, setGrabMouseY] = createSignal(null);
838
1025
  let holdTimerId = null;
839
1026
  let progressAnimationId = null;
840
1027
  let progressDelayTimerId = null;
841
- const isOverlayActive = createMemo(() => isActivated() && !isCopying());
1028
+ const isRendererActive = createMemo(() => isActivated() && !isCopying());
1029
+ const hasValidMousePosition = createMemo(() => mouseX() > OFFSCREEN_POSITION && mouseY() > OFFSCREEN_POSITION);
842
1030
  const isTargetKeyCombination = (event) => (event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "c";
843
- const getElementAtPosition = (x, y) => {
844
- const elementsAtPoint = document.elementsFromPoint(x, y);
845
- for (const candidateElement of elementsAtPoint) {
846
- if (candidateElement.closest(`[${ATTRIBUTE_NAME}]`)) {
847
- continue;
848
- }
849
- const computedStyle = window.getComputedStyle(candidateElement);
850
- if (!isElementVisible(candidateElement, computedStyle)) {
851
- continue;
852
- }
853
- return candidateElement;
854
- }
855
- return null;
856
- };
857
- const getElementsInMarquee = (marqueeRect) => {
858
- const elements = [];
859
- const allElements = Array.from(document.querySelectorAll("*"));
860
- const marqueeLeft = marqueeRect.x;
861
- const marqueeTop = marqueeRect.y;
862
- const marqueeRight = marqueeRect.x + marqueeRect.width;
863
- const marqueeBottom = marqueeRect.y + marqueeRect.height;
864
- for (const candidateElement of allElements) {
865
- if (candidateElement.closest(`[${ATTRIBUTE_NAME}]`)) {
866
- continue;
867
- }
868
- const computedStyle = window.getComputedStyle(candidateElement);
869
- if (!isElementVisible(candidateElement, computedStyle)) {
870
- continue;
871
- }
872
- const rect = candidateElement.getBoundingClientRect();
873
- const elementLeft = rect.left;
874
- const elementTop = rect.top;
875
- const elementRight = rect.left + rect.width;
876
- const elementBottom = rect.top + rect.height;
877
- const intersectionLeft = Math.max(marqueeLeft, elementLeft);
878
- const intersectionTop = Math.max(marqueeTop, elementTop);
879
- const intersectionRight = Math.min(marqueeRight, elementRight);
880
- const intersectionBottom = Math.min(marqueeBottom, elementBottom);
881
- const intersectionWidth = Math.max(0, intersectionRight - intersectionLeft);
882
- const intersectionHeight = Math.max(0, intersectionBottom - intersectionTop);
883
- const intersectionArea = intersectionWidth * intersectionHeight;
884
- const elementArea = Math.max(0, rect.width * rect.height);
885
- const COVERAGE_THRESHOLD = 0.75;
886
- const hasMajorityCoverage = elementArea > 0 && intersectionArea / elementArea >= COVERAGE_THRESHOLD;
887
- if (hasMajorityCoverage) {
888
- elements.push(candidateElement);
889
- }
890
- }
891
- return elements;
892
- };
893
- const getElementsInMarqueeLoose = (marqueeRect) => {
894
- const elements = [];
895
- const allElements = Array.from(document.querySelectorAll("*"));
896
- const marqueeLeft = marqueeRect.x;
897
- const marqueeTop = marqueeRect.y;
898
- const marqueeRight = marqueeRect.x + marqueeRect.width;
899
- const marqueeBottom = marqueeRect.y + marqueeRect.height;
900
- for (const candidateElement of allElements) {
901
- if (candidateElement.closest(`[${ATTRIBUTE_NAME}]`)) {
902
- continue;
903
- }
904
- const tag = (candidateElement.tagName || "").toUpperCase();
905
- if (tag === "HTML" || tag === "BODY") continue;
906
- const computedStyle = window.getComputedStyle(candidateElement);
907
- if (!isElementVisible(candidateElement, computedStyle)) {
908
- continue;
909
- }
910
- const rect = candidateElement.getBoundingClientRect();
911
- const elementLeft = rect.left;
912
- const elementTop = rect.top;
913
- const elementRight = rect.left + rect.width;
914
- const elementBottom = rect.top + rect.height;
915
- const intersects = elementLeft < marqueeRight && elementRight > marqueeLeft && elementTop < marqueeBottom && elementBottom > marqueeTop;
916
- if (intersects) {
917
- elements.push(candidateElement);
918
- }
919
- }
920
- return elements;
921
- };
922
- const wrapInReferencedElement = (content) => `
923
-
924
- <referenced_element>
925
- ${content}
926
- </referenced_element>`;
927
- const wrapInReferencedElements = (content) => `
928
-
929
- <referenced_elements>
930
- ${content}
931
- </referenced_elements>`;
932
- const addGrabbedOverlay = (bounds) => {
933
- const id = `grabbed-${Date.now()}-${Math.random()}`;
934
- setGrabbedOverlays((prev) => [...prev, {
935
- id,
1031
+ const addGrabbedBox = (bounds) => {
1032
+ const boxId = `grabbed-${Date.now()}-${Math.random()}`;
1033
+ const newBox = {
1034
+ id: boxId,
936
1035
  bounds
937
- }]);
938
- setTimeout(() => {
939
- setGrabbedOverlays((prev) => prev.filter((overlay) => overlay.id !== id));
940
- }, 300);
1036
+ };
1037
+ const currentBoxes = grabbedBoxes();
1038
+ setGrabbedBoxes([...currentBoxes, newBox]);
941
1039
  };
942
- const addSuccessLabel = (text, x, y) => {
943
- const id = `success-${Date.now()}-${Math.random()}`;
944
- setSuccessLabels((prev) => [...prev, {
945
- id,
1040
+ const addSuccessLabel = (text, positionX, positionY) => {
1041
+ const labelId = `success-${Date.now()}-${Math.random()}`;
1042
+ setSuccessLabels((previousLabels) => [...previousLabels, {
1043
+ id: labelId,
946
1044
  text,
947
- x,
948
- y
1045
+ x: positionX,
1046
+ y: positionY
949
1047
  }]);
950
1048
  setTimeout(() => {
951
- setSuccessLabels((prev) => prev.filter((label) => label.id !== id));
952
- }, 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;
953
1072
  };
1073
+ const getElementTagName = (element) => (element.tagName || "").toLowerCase();
954
1074
  const handleCopy = async (targetElement2) => {
955
1075
  const elementBounds = targetElement2.getBoundingClientRect();
956
- const tagName = (targetElement2.tagName || "").toLowerCase();
957
- addGrabbedOverlay({
958
- borderRadius: window.getComputedStyle(targetElement2).borderRadius || "0px",
959
- height: elementBounds.height,
960
- transform: window.getComputedStyle(targetElement2).transform || "none",
961
- width: elementBounds.width,
962
- x: elementBounds.left,
963
- y: elementBounds.top
964
- });
1076
+ const tagName = getElementTagName(targetElement2);
1077
+ setGrabMouseX(mouseX());
1078
+ setGrabMouseY(mouseY());
1079
+ addGrabbedBox(createElementBounds(targetElement2));
965
1080
  try {
966
- const elementHtml = getHTMLSnippet(targetElement2);
967
- await copyContent(wrapInReferencedElement(elementHtml));
968
- const componentStackTrace = await getSourceTrace(targetElement2);
969
- if (componentStackTrace?.length) {
970
- const formattedStackTrace = componentStackTrace.map((source) => ` ${source.functionName} - ${source.fileName}:${source.lineNumber}:${source.columnNumber}`).join("\n");
971
- await copyContent(wrapInReferencedElement(`${elementHtml}
972
-
973
- Component owner stack:
974
- ${formattedStackTrace}`));
975
- }
1081
+ const content = await getElementContentWithTrace(targetElement2);
1082
+ await copyContent(content);
976
1083
  } catch {
977
1084
  }
978
1085
  addSuccessLabel(tagName ? `<${tagName}>` : "<element>", elementBounds.left, elementBounds.top);
979
1086
  };
980
1087
  const handleMultipleCopy = async (targetElements) => {
981
1088
  if (targetElements.length === 0) return;
982
- let minX = Infinity;
983
- let minY = Infinity;
1089
+ let minPositionX = Infinity;
1090
+ let minPositionY = Infinity;
1091
+ setGrabMouseX(mouseX());
1092
+ setGrabMouseY(mouseY());
984
1093
  for (const element of targetElements) {
985
1094
  const elementBounds = element.getBoundingClientRect();
986
- minX = Math.min(minX, elementBounds.left);
987
- minY = Math.min(minY, elementBounds.top);
988
- addGrabbedOverlay({
989
- borderRadius: window.getComputedStyle(element).borderRadius || "0px",
990
- height: elementBounds.height,
991
- transform: window.getComputedStyle(element).transform || "none",
992
- width: elementBounds.width,
993
- x: elementBounds.left,
994
- y: elementBounds.top
995
- });
1095
+ minPositionX = Math.min(minPositionX, elementBounds.left);
1096
+ minPositionY = Math.min(minPositionY, elementBounds.top);
1097
+ addGrabbedBox(createElementBounds(element));
996
1098
  }
997
1099
  try {
998
- const elementSnippets = [];
999
- for (const element of targetElements) {
1000
- const elementHtml = getHTMLSnippet(element);
1001
- const componentStackTrace = await getSourceTrace(element);
1002
- if (componentStackTrace?.length) {
1003
- const formattedStackTrace = componentStackTrace.map((source) => ` ${source.functionName} - ${source.fileName}:${source.lineNumber}:${source.columnNumber}`).join("\n");
1004
- elementSnippets.push(`${elementHtml}
1005
-
1006
- Component owner stack:
1007
- ${formattedStackTrace}`);
1008
- } else {
1009
- elementSnippets.push(elementHtml);
1010
- }
1011
- }
1100
+ const elementSnippets = await Promise.all(targetElements.map((element) => getElementContentWithTrace(element)));
1012
1101
  const combinedContent = elementSnippets.join("\n\n---\n\n");
1013
- await copyContent(wrapInReferencedElements(combinedContent));
1102
+ await copyContent(combinedContent);
1014
1103
  } catch {
1015
1104
  }
1016
- addSuccessLabel(`${targetElements.length} elements`, minX, minY);
1105
+ addSuccessLabel(`${targetElements.length} elements`, minPositionX, minPositionY);
1017
1106
  };
1018
1107
  const targetElement = createMemo(() => {
1019
- if (!isOverlayActive() || isDragging()) return null;
1108
+ if (!isRendererActive() || isDragging()) return null;
1020
1109
  return getElementAtPosition(mouseX(), mouseY());
1021
1110
  });
1022
1111
  const selectionBounds = createMemo(() => {
1023
1112
  const element = targetElement();
1024
1113
  if (!element) return void 0;
1025
- const last = lastGrabbedElement();
1026
- if (last && element === last) return void 0;
1027
1114
  const elementBounds = element.getBoundingClientRect();
1028
1115
  const computedStyle = window.getComputedStyle(element);
1029
1116
  return {
@@ -1035,59 +1122,80 @@ ${formattedStackTrace}`);
1035
1122
  y: elementBounds.top
1036
1123
  };
1037
1124
  });
1038
- const marqueeBounds = createMemo(() => {
1039
- if (!isDragging()) return void 0;
1040
- const marqueeX = Math.min(dragStartX(), mouseX());
1041
- const marqueeY = Math.min(dragStartY(), mouseY());
1042
- const marqueeWidth = Math.abs(mouseX() - dragStartX());
1043
- 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());
1044
1150
  return {
1045
1151
  borderRadius: "0px",
1046
- height: marqueeHeight,
1152
+ height: drag.height,
1047
1153
  transform: "none",
1048
- width: marqueeWidth,
1049
- x: marqueeX,
1050
- y: marqueeY
1154
+ width: drag.width,
1155
+ x: drag.x,
1156
+ y: drag.y
1051
1157
  };
1052
1158
  });
1053
1159
  const labelText = createMemo(() => {
1054
1160
  const element = targetElement();
1055
1161
  if (!element) return "";
1056
- const tagName = (element.tagName || "").toLowerCase();
1162
+ const tagName = getElementTagName(element);
1057
1163
  return tagName ? `<${tagName}>` : "<element>";
1058
1164
  });
1059
1165
  const labelPosition = createMemo(() => {
1060
- const element = targetElement() ?? lastGrabbedElement();
1061
- if (element) {
1062
- const rect = element.getBoundingClientRect();
1063
- return {
1064
- x: rect.left,
1065
- y: rect.top
1066
- };
1067
- }
1068
1166
  return {
1069
1167
  x: mouseX(),
1070
1168
  y: mouseY()
1071
1169
  };
1072
1170
  });
1073
1171
  const isSameAsLast = createMemo(() => {
1074
- const current = targetElement();
1075
- const last = lastGrabbedElement();
1076
- return !!current && current === last;
1172
+ const currentElement = targetElement();
1173
+ const lastElement = lastGrabbedElement();
1174
+ return !!currentElement && currentElement === lastElement;
1077
1175
  });
1078
- createEffect(() => {
1079
- const current = targetElement();
1080
- const last = lastGrabbedElement();
1081
- if (last && current && last !== current) {
1176
+ createEffect(on(() => [targetElement(), lastGrabbedElement()], ([currentElement, lastElement]) => {
1177
+ if (lastElement && currentElement && lastElement !== currentElement) {
1082
1178
  setLastGrabbedElement(null);
1083
1179
  }
1084
- });
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
+ }));
1085
1193
  const progress = createMemo(() => {
1086
1194
  const startTime = progressStartTime();
1087
1195
  progressTick();
1088
1196
  if (startTime === null) return 0;
1089
- const elapsed = Date.now() - startTime;
1090
- return Math.min(elapsed / options.keyHoldDuration, 1);
1197
+ const elapsedTime = Date.now() - startTime;
1198
+ return Math.min(elapsedTime / options.keyHoldDuration, 1);
1091
1199
  });
1092
1200
  const startProgressAnimation = () => {
1093
1201
  setProgressStartTime(Date.now());
@@ -1095,10 +1203,10 @@ ${formattedStackTrace}`);
1095
1203
  progressDelayTimerId = window.setTimeout(() => {
1096
1204
  setShowProgressIndicator(true);
1097
1205
  progressDelayTimerId = null;
1098
- }, 150);
1206
+ }, PROGRESS_INDICATOR_DELAY_MS);
1099
1207
  const animateProgress = () => {
1100
1208
  if (progressStartTime() === null) return;
1101
- setProgressTick((t) => t + 1);
1209
+ setProgressTick((tick) => tick + 1);
1102
1210
  const currentProgress = progress();
1103
1211
  if (currentProgress < 1) {
1104
1212
  progressAnimationId = requestAnimationFrame(animateProgress);
@@ -1118,16 +1226,22 @@ ${formattedStackTrace}`);
1118
1226
  setProgressStartTime(null);
1119
1227
  setShowProgressIndicator(false);
1120
1228
  };
1121
- const activateOverlay = () => {
1229
+ const activateRenderer = () => {
1122
1230
  stopProgressAnimation();
1123
1231
  setIsActivated(true);
1232
+ document.body.style.cursor = "crosshair";
1124
1233
  };
1125
1234
  const abortController = new AbortController();
1126
- const signal = abortController.signal;
1235
+ const eventListenerSignal = abortController.signal;
1127
1236
  window.addEventListener("keydown", (event) => {
1128
1237
  if (event.key === "Escape" && isHoldingKeys()) {
1129
1238
  setIsHoldingKeys(false);
1130
1239
  setIsActivated(false);
1240
+ document.body.style.cursor = "";
1241
+ if (isDragging()) {
1242
+ setIsDragging(false);
1243
+ document.body.style.userSelect = "";
1244
+ }
1131
1245
  if (holdTimerId) window.clearTimeout(holdTimerId);
1132
1246
  stopProgressAnimation();
1133
1247
  return;
@@ -1137,67 +1251,56 @@ ${formattedStackTrace}`);
1137
1251
  setIsHoldingKeys(true);
1138
1252
  startProgressAnimation();
1139
1253
  holdTimerId = window.setTimeout(() => {
1140
- activateOverlay();
1254
+ activateRenderer();
1141
1255
  options.onActivate?.();
1142
1256
  }, options.keyHoldDuration);
1143
1257
  }
1144
1258
  }, {
1145
- signal
1259
+ signal: eventListenerSignal
1146
1260
  });
1147
1261
  window.addEventListener("keyup", (event) => {
1148
1262
  if (isHoldingKeys() && (!isTargetKeyCombination(event) || event.key.toLowerCase() === "c")) {
1149
1263
  setIsHoldingKeys(false);
1150
1264
  setIsActivated(false);
1265
+ document.body.style.cursor = "";
1151
1266
  if (holdTimerId) window.clearTimeout(holdTimerId);
1152
1267
  stopProgressAnimation();
1153
1268
  }
1154
1269
  }, {
1155
- signal
1270
+ signal: eventListenerSignal
1156
1271
  });
1157
1272
  window.addEventListener("mousemove", (event) => {
1158
1273
  setMouseX(event.clientX);
1159
1274
  setMouseY(event.clientY);
1160
1275
  }, {
1161
- signal
1276
+ signal: eventListenerSignal
1162
1277
  });
1163
1278
  window.addEventListener("mousedown", (event) => {
1164
- if (!isOverlayActive() || isCopying()) return;
1279
+ if (!isRendererActive() || isCopying()) return;
1280
+ event.preventDefault();
1165
1281
  setIsDragging(true);
1166
1282
  setDragStartX(event.clientX);
1167
1283
  setDragStartY(event.clientY);
1284
+ document.body.style.userSelect = "none";
1168
1285
  }, {
1169
- signal
1286
+ signal: eventListenerSignal
1170
1287
  });
1171
1288
  window.addEventListener("mouseup", (event) => {
1172
1289
  if (!isDragging()) return;
1173
- const dragDistanceX = Math.abs(event.clientX - dragStartX());
1174
- const dragDistanceY = Math.abs(event.clientY - dragStartY());
1175
- const DRAG_THRESHOLD = 5;
1176
- 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;
1177
1292
  setIsDragging(false);
1178
- if (wasDrag) {
1179
- const marqueeX = Math.min(dragStartX(), event.clientX);
1180
- const marqueeY = Math.min(dragStartY(), event.clientY);
1181
- const marqueeWidth = Math.abs(event.clientX - dragStartX());
1182
- const marqueeHeight = Math.abs(event.clientY - dragStartY());
1183
- const elements = getElementsInMarquee({
1184
- x: marqueeX,
1185
- y: marqueeY,
1186
- width: marqueeWidth,
1187
- height: marqueeHeight
1188
- });
1293
+ document.body.style.userSelect = "";
1294
+ if (wasDragGesture) {
1295
+ const dragRect = getDragRect(event.clientX, event.clientY);
1296
+ const elements = getElementsInDrag(dragRect, isValidGrabbableElement);
1189
1297
  if (elements.length > 0) {
1190
1298
  setIsCopying(true);
1191
1299
  void handleMultipleCopy(elements).finally(() => {
1192
1300
  setIsCopying(false);
1193
1301
  });
1194
1302
  } else {
1195
- const fallbackElements = getElementsInMarqueeLoose({
1196
- x: marqueeX,
1197
- y: marqueeY,
1198
- width: marqueeWidth,
1199
- height: marqueeHeight
1200
- });
1303
+ const fallbackElements = getElementsInDragLoose(dragRect, isValidGrabbableElement);
1201
1304
  if (fallbackElements.length > 0) {
1202
1305
  setIsCopying(true);
1203
1306
  void handleMultipleCopy(fallbackElements).finally(() => {
@@ -1215,51 +1318,84 @@ ${formattedStackTrace}`);
1215
1318
  });
1216
1319
  }
1217
1320
  }, {
1218
- signal
1219
- });
1220
- window.addEventListener("scroll", () => {
1221
- }, {
1222
- signal,
1223
- capture: true
1224
- });
1225
- window.addEventListener("resize", () => {
1226
- }, {
1227
- signal
1321
+ signal: eventListenerSignal
1228
1322
  });
1229
1323
  document.addEventListener("visibilitychange", () => {
1230
1324
  if (document.hidden) {
1231
- setGrabbedOverlays([]);
1325
+ setGrabbedBoxes([]);
1232
1326
  }
1233
1327
  }, {
1234
- signal
1328
+ signal: eventListenerSignal
1235
1329
  });
1236
1330
  onCleanup(() => {
1237
1331
  abortController.abort();
1238
1332
  if (holdTimerId) window.clearTimeout(holdTimerId);
1239
1333
  stopProgressAnimation();
1334
+ document.body.style.userSelect = "";
1335
+ document.body.style.cursor = "";
1240
1336
  });
1241
- const overlayRoot = mountRoot();
1242
- const overlayProps = createMemo(() => ({
1243
- selectionVisible: isOverlayActive() && !isDragging() && !!selectionBounds(),
1244
- selectionBounds: selectionBounds(),
1245
- marqueeVisible: isOverlayActive() && isDragging(),
1246
- marqueeBounds: marqueeBounds(),
1247
- grabbedOverlays: grabbedOverlays(),
1248
- successLabels: successLabels(),
1249
- labelVariant: isCopying() ? "processing" : "hover",
1250
- labelText: labelText(),
1251
- labelX: labelPosition().x,
1252
- labelY: labelPosition().y,
1253
- labelVisible: isOverlayActive() && !isDragging() && !!targetElement() && !isSameAsLast() || isCopying(),
1254
- progressVisible: isHoldingKeys() && showProgressIndicator(),
1255
- progress: progress(),
1256
- mouseX: mouseX(),
1257
- mouseY: mouseY()
1258
- }));
1259
- 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);
1260
1394
  return dispose;
1261
1395
  });
1262
1396
  };
1263
1397
 
1264
1398
  // src/index.ts
1265
1399
  init();
1400
+
1401
+ export { init };