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