react-grab 0.0.20 → 0.0.23

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,7 @@
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
3
  import { instrument, _fiberRoots, getFiberFromHostInstance } from 'bippy';
2
- import { getFiberStackTrace, getOwnerStack, getFiberSource } from 'bippy/dist/source';
4
+ import { getOwnerStack, getSourcesFromStack } from 'bippy/dist/source';
3
5
 
4
6
  /**
5
7
  * @license MIT
@@ -10,18 +12,8 @@ import { getFiberStackTrace, getOwnerStack, getFiberSource } from 'bippy/dist/so
10
12
  * LICENSE file in the root directory of this source tree.
11
13
  */
12
14
 
13
- // src/adapters.ts
14
- var cursorAdapter = {
15
- name: "cursor",
16
- open: (promptText) => {
17
- if (!promptText) return;
18
- const url = new URL("cursor://anysphere.cursor-deeplink/prompt");
19
- url.searchParams.set("text", promptText);
20
- window.open(url.toString(), "_blank");
21
- }
22
- };
23
15
 
24
- // src/hotkeys.ts
16
+ // src/utils/is-keyboard-event-triggered-by-input.ts
25
17
  var FORM_TAGS_AND_ROLES = [
26
18
  "input",
27
19
  "textarea",
@@ -69,194 +61,481 @@ var isHotkeyEnabledOnTagName = (event, enabledOnTags = false) => {
69
61
  var isKeyboardEventTriggeredByInput = (event) => {
70
62
  return isHotkeyEnabledOnTagName(event, FORM_TAGS_AND_ROLES);
71
63
  };
72
- var trackHotkeys = () => {
73
- const handleKeyDown = (event) => {
74
- if (isKeyboardEventTriggeredByInput(event)) {
75
- return;
64
+
65
+ // src/utils/mount-root.ts
66
+ var ATTRIBUTE_NAME = "data-react-grab";
67
+ var mountRoot = () => {
68
+ const mountedHost = document.querySelector(`[${ATTRIBUTE_NAME}]`);
69
+ if (mountedHost) {
70
+ const mountedRoot = mountedHost.shadowRoot?.querySelector(
71
+ `[${ATTRIBUTE_NAME}]`
72
+ );
73
+ if (mountedRoot instanceof HTMLDivElement && mountedHost.shadowRoot) {
74
+ return mountedRoot;
76
75
  }
77
- if (event.code === void 0) {
76
+ }
77
+ const host = document.createElement("div");
78
+ host.setAttribute(ATTRIBUTE_NAME, "true");
79
+ host.style.zIndex = "2147483646";
80
+ host.style.position = "fixed";
81
+ host.style.top = "0";
82
+ host.style.left = "0";
83
+ const shadowRoot = host.attachShadow({ mode: "open" });
84
+ const root = document.createElement("div");
85
+ root.setAttribute(ATTRIBUTE_NAME, "true");
86
+ shadowRoot.appendChild(root);
87
+ const doc = document.body ?? document.documentElement;
88
+ doc.appendChild(host);
89
+ return root;
90
+ };
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)">`);
102
+ var VIEWPORT_MARGIN_PX = 8;
103
+ var LABEL_OFFSET_PX = 6;
104
+ var INDICATOR_CLAMP_PADDING_PX = 4;
105
+ var INDICATOR_SUCCESS_VISIBLE_MS = 1500;
106
+ var SELECTION_LERP_FACTOR = 0.95;
107
+ var MARQUEE_LERP_FACTOR = 0.9;
108
+ var lerp = (start, end, factor) => {
109
+ return start + (end - start) * factor;
110
+ };
111
+ var Overlay = (props) => {
112
+ const [currentX, setCurrentX] = createSignal(props.bounds.x);
113
+ const [currentY, setCurrentY] = createSignal(props.bounds.y);
114
+ const [currentWidth, setCurrentWidth] = createSignal(props.bounds.width);
115
+ const [currentHeight, setCurrentHeight] = createSignal(props.bounds.height);
116
+ const [opacity, setOpacity] = createSignal(1);
117
+ let hasBeenShown = false;
118
+ let animationFrameId = null;
119
+ 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;
78
129
  return;
79
130
  }
80
- libStore.setState((state) => {
81
- const newTimestamps = new Map(state.keyPressTimestamps);
82
- if (!state.pressedKeys.has(event.key)) {
83
- newTimestamps.set(event.key, Date.now());
131
+ const EPSILON = 0.5;
132
+ 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) {
143
+ animationFrameId = requestAnimationFrame(animate);
144
+ } else {
145
+ animationFrameId = null;
146
+ }
147
+ };
148
+ if (animationFrameId !== null) {
149
+ cancelAnimationFrame(animationFrameId);
150
+ }
151
+ animationFrameId = requestAnimationFrame(animate);
152
+ onCleanup(() => {
153
+ if (animationFrameId !== null) {
154
+ cancelAnimationFrame(animationFrameId);
155
+ animationFrameId = null;
84
156
  }
85
- return {
86
- ...state,
87
- keyPressTimestamps: newTimestamps,
88
- pressedKeys: /* @__PURE__ */ new Set([event.key, ...state.pressedKeys])
89
- };
90
157
  });
91
- };
92
- const handleKeyUp = (event) => {
93
- if (event.code === void 0) {
94
- return;
158
+ });
159
+ createEffect(() => {
160
+ if (props.variant === "grabbed") {
161
+ requestAnimationFrame(() => {
162
+ setOpacity(0);
163
+ });
95
164
  }
96
- libStore.setState((state) => {
97
- const newTimestamps = new Map(state.keyPressTimestamps);
98
- newTimestamps.delete(event.key);
165
+ });
166
+ const baseStyle = {
167
+ position: "fixed",
168
+ "box-sizing": "border-box",
169
+ "pointer-events": props.variant === "marquee" ? "none" : "auto",
170
+ "z-index": "2147483646"
171
+ };
172
+ const variantStyle = () => {
173
+ if (props.variant === "marquee") {
99
174
  return {
100
- ...state,
101
- keyPressTimestamps: newTimestamps,
102
- pressedKeys: new Set(
103
- [...state.pressedKeys].filter((key) => key !== event.key)
104
- )
175
+ border: "1px dashed rgb(210, 57, 192)",
176
+ "background-color": "rgba(210, 57, 192, 0.1)",
177
+ "will-change": "transform, width, height",
178
+ contain: "layout paint size"
105
179
  };
106
- });
107
- };
108
- const handleBlur = () => {
109
- libStore.setState((state) => ({
110
- ...state,
111
- keyPressTimestamps: /* @__PURE__ */ new Map(),
112
- pressedKeys: /* @__PURE__ */ new Set()
113
- }));
114
- };
115
- const handleContextmenu = () => {
116
- libStore.setState((state) => ({
117
- ...state,
118
- keyPressTimestamps: /* @__PURE__ */ new Map(),
119
- pressedKeys: /* @__PURE__ */ new Set()
120
- }));
121
- };
122
- document.addEventListener("keydown", handleKeyDown);
123
- document.addEventListener("keyup", handleKeyUp);
124
- window.addEventListener("blur", handleBlur);
125
- window.addEventListener("contextmenu", handleContextmenu);
126
- return () => {
127
- document.removeEventListener("keydown", handleKeyDown);
128
- document.removeEventListener("keyup", handleKeyUp);
129
- window.removeEventListener("blur", handleBlur);
130
- window.removeEventListener("contextmenu", handleContextmenu);
180
+ }
181
+ return {
182
+ border: "1px solid rgb(210, 57, 192)",
183
+ "background-color": "rgba(210, 57, 192, 0.2)",
184
+ transition: props.variant === "grabbed" ? "opacity 0.3s ease-out" : void 0
185
+ };
131
186
  };
187
+ return createComponent(Show, {
188
+ get when() {
189
+ return props.visible !== false;
190
+ },
191
+ get children() {
192
+ var _el$ = _tmpl$();
193
+ effect((_$p) => style(_el$, {
194
+ ...baseStyle,
195
+ ...variantStyle(),
196
+ top: `${currentY()}px`,
197
+ left: `${currentX()}px`,
198
+ width: `${currentWidth()}px`,
199
+ height: `${currentHeight()}px`,
200
+ "border-radius": props.bounds.borderRadius,
201
+ transform: props.bounds.transform,
202
+ opacity: opacity()
203
+ }, _$p));
204
+ return _el$;
205
+ }
206
+ });
132
207
  };
133
- var isKeyPressed = (key) => {
134
- const { pressedKeys } = libStore.getState();
135
- if (key.length === 1) {
136
- return pressedKeys.has(key.toLowerCase()) || pressedKeys.has(key.toUpperCase());
137
- }
138
- return pressedKeys.has(key);
208
+ var Spinner = (props) => {
209
+ let ref;
210
+ createEffect(() => {
211
+ if (ref) {
212
+ ref.animate([{
213
+ transform: "rotate(0deg)"
214
+ }, {
215
+ transform: "rotate(360deg)"
216
+ }], {
217
+ duration: 600,
218
+ easing: "linear",
219
+ iterations: Infinity
220
+ });
221
+ }
222
+ });
223
+ 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, {
228
+ ...props.style
229
+ }, _$p));
230
+ return _el$2;
231
+ })();
139
232
  };
140
- var watchKeyHeldFor = (key, duration, onHeld) => {
141
- let timeoutId = null;
142
- let unsubscribe = null;
143
- const watchStartTime = Date.now();
144
- const cleanup = () => {
145
- if (timeoutId !== null) {
146
- clearTimeout(timeoutId);
147
- timeoutId = null;
233
+ var Label = (props) => {
234
+ const [opacity, setOpacity] = createSignal(0);
235
+ let ref;
236
+ createEffect(() => {
237
+ if (props.visible !== false) {
238
+ requestAnimationFrame(() => {
239
+ setOpacity(1);
240
+ });
241
+ } else {
242
+ setOpacity(0);
148
243
  }
149
- if (unsubscribe !== null) {
150
- unsubscribe();
151
- unsubscribe = null;
244
+ });
245
+ createEffect(() => {
246
+ if (props.variant === "success") {
247
+ const fadeOutTimer = setTimeout(() => {
248
+ setOpacity(0);
249
+ }, INDICATOR_SUCCESS_VISIBLE_MS);
250
+ onCleanup(() => clearTimeout(fadeOutTimer));
152
251
  }
153
- };
154
- const checkSingleKeyPressed = (keyToCheck, pressedKeys) => {
155
- if (keyToCheck.length === 1) {
156
- return pressedKeys.has(keyToCheck.toLowerCase()) || pressedKeys.has(keyToCheck.toUpperCase());
252
+ });
253
+ const indicatorRect = () => ref?.getBoundingClientRect();
254
+ const computedPosition = () => {
255
+ const rect = indicatorRect();
256
+ if (!rect) return {
257
+ left: props.x,
258
+ top: props.y
259
+ };
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;
157
276
  }
158
- return pressedKeys.has(keyToCheck);
277
+ return {
278
+ left: indicatorLeftPx,
279
+ top: indicatorTopPx
280
+ };
159
281
  };
160
- const checkAllKeysPressed = (pressedKeys) => {
161
- if (Array.isArray(key)) {
162
- return key.every(
163
- (keyFromCombo) => checkSingleKeyPressed(keyFromCombo, pressedKeys)
164
- );
282
+ return createComponent(Show, {
283
+ get when() {
284
+ return props.visible !== false;
285
+ },
286
+ 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, {
291
+ get when() {
292
+ return props.variant === "processing";
293
+ },
294
+ get children() {
295
+ return createComponent(Spinner, {});
296
+ }
297
+ }), _el$5);
298
+ insert(_el$3, createComponent(Show, {
299
+ get when() {
300
+ return props.variant === "success";
301
+ },
302
+ get children() {
303
+ return _tmpl$3();
304
+ }
305
+ }), _el$5);
306
+ insert(_el$5, createComponent(Show, {
307
+ get when() {
308
+ return props.variant === "success";
309
+ },
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
+ get children() {
323
+ var _el$6 = _tmpl$4();
324
+ insert(_el$6, () => props.text);
325
+ return _el$6;
326
+ }
327
+ }), null);
328
+ insert(_el$5, createComponent(Show, {
329
+ get when() {
330
+ return props.variant !== "hover";
331
+ },
332
+ get children() {
333
+ var _el$7 = _tmpl$4();
334
+ insert(_el$7, () => props.text);
335
+ return _el$7;
336
+ }
337
+ }), null);
338
+ effect((_p$) => {
339
+ 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);
344
+ return _p$;
345
+ }, {
346
+ e: void 0,
347
+ t: void 0,
348
+ a: void 0,
349
+ o: void 0
350
+ });
351
+ return _el$3;
352
+ }
353
+ });
354
+ };
355
+ var ProgressIndicator = (props) => {
356
+ const [opacity, setOpacity] = createSignal(0);
357
+ let ref;
358
+ createEffect(() => {
359
+ if (props.visible !== false) {
360
+ requestAnimationFrame(() => {
361
+ setOpacity(1);
362
+ });
363
+ } else {
364
+ setOpacity(0);
365
+ }
366
+ });
367
+ const computedPosition = () => {
368
+ const rect = ref?.getBoundingClientRect();
369
+ if (!rect) return {
370
+ left: props.mouseX,
371
+ top: props.mouseY
372
+ };
373
+ const viewportWidth = window.innerWidth;
374
+ 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;
165
381
  }
166
- return checkSingleKeyPressed(key, pressedKeys);
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
+ };
167
388
  };
168
- const scheduleCallback = () => {
169
- const state = libStore.getState();
170
- const { pressedKeys } = state;
171
- if (!checkAllKeysPressed(pressedKeys)) {
172
- if (timeoutId !== null) {
173
- clearTimeout(timeoutId);
174
- timeoutId = null;
175
- }
176
- return;
389
+ return createComponent(Show, {
390
+ get when() {
391
+ return props.visible !== false;
392
+ },
393
+ 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;
397
+ 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);
403
+ return _p$;
404
+ }, {
405
+ e: void 0,
406
+ t: void 0,
407
+ a: void 0,
408
+ o: void 0
409
+ });
410
+ return _el$8;
177
411
  }
178
- const elapsed = Date.now() - watchStartTime;
179
- const remaining = duration - elapsed;
180
- if (remaining <= 0) {
181
- onHeld();
182
- cleanup();
183
- return;
412
+ });
413
+ };
414
+ var ReactGrabOverlay = (props) => {
415
+ return [createComponent(Show, {
416
+ get when() {
417
+ return memo(() => !!props.selectionVisible)() && props.selectionBounds;
418
+ },
419
+ get children() {
420
+ return createComponent(Overlay, {
421
+ variant: "selection",
422
+ get bounds() {
423
+ return props.selectionBounds;
424
+ },
425
+ get visible() {
426
+ return props.selectionVisible;
427
+ },
428
+ lerpFactor: SELECTION_LERP_FACTOR
429
+ });
184
430
  }
185
- if (timeoutId !== null) {
186
- clearTimeout(timeoutId);
431
+ }), createComponent(Show, {
432
+ get when() {
433
+ return memo(() => !!props.marqueeVisible)() && props.marqueeBounds;
434
+ },
435
+ get children() {
436
+ return createComponent(Overlay, {
437
+ variant: "marquee",
438
+ get bounds() {
439
+ return props.marqueeBounds;
440
+ },
441
+ get visible() {
442
+ return props.marqueeVisible;
443
+ },
444
+ lerpFactor: MARQUEE_LERP_FACTOR
445
+ });
187
446
  }
188
- timeoutId = setTimeout(() => {
189
- onHeld();
190
- cleanup();
191
- }, remaining);
192
- };
193
- unsubscribe = libStore.subscribe(
194
- () => {
195
- scheduleCallback();
447
+ }), createComponent(For, {
448
+ get each() {
449
+ return props.grabbedOverlays ?? [];
196
450
  },
197
- (state) => state.pressedKeys
198
- );
199
- scheduleCallback();
200
- return cleanup;
451
+ children: (overlay) => createComponent(Overlay, {
452
+ variant: "grabbed",
453
+ get bounds() {
454
+ return overlay.bounds;
455
+ },
456
+ visible: true
457
+ })
458
+ }), createComponent(For, {
459
+ get each() {
460
+ return props.successLabels ?? [];
461
+ },
462
+ children: (label) => createComponent(Label, {
463
+ variant: "success",
464
+ get text() {
465
+ return label.text;
466
+ },
467
+ get x() {
468
+ return label.x;
469
+ },
470
+ get y() {
471
+ return label.y;
472
+ },
473
+ visible: true,
474
+ zIndex: 2147483648
475
+ })
476
+ }), createComponent(Show, {
477
+ get when() {
478
+ return memo(() => !!(props.labelVisible && props.labelVariant && props.labelText && props.labelX !== void 0))() && props.labelY !== void 0;
479
+ },
480
+ get children() {
481
+ return createComponent(Label, {
482
+ get variant() {
483
+ return props.labelVariant;
484
+ },
485
+ get text() {
486
+ return props.labelText;
487
+ },
488
+ get x() {
489
+ return props.labelX;
490
+ },
491
+ get y() {
492
+ return props.labelY;
493
+ },
494
+ get visible() {
495
+ return props.labelVisible;
496
+ },
497
+ get zIndex() {
498
+ return props.labelZIndex;
499
+ }
500
+ });
501
+ }
502
+ }), createComponent(Show, {
503
+ get when() {
504
+ return memo(() => !!(props.progressVisible && props.progress !== void 0 && props.mouseX !== void 0))() && props.mouseY !== void 0;
505
+ },
506
+ get children() {
507
+ return createComponent(ProgressIndicator, {
508
+ get progress() {
509
+ return props.progress;
510
+ },
511
+ get mouseX() {
512
+ return props.mouseX;
513
+ },
514
+ get mouseY() {
515
+ return props.mouseY;
516
+ },
517
+ get visible() {
518
+ return props.progressVisible;
519
+ }
520
+ });
521
+ }
522
+ })];
201
523
  };
202
- var fiberRoots = _fiberRoots;
203
524
  instrument({
204
525
  onCommitFiberRoot(_, fiberRoot) {
205
- fiberRoots.add(fiberRoot);
526
+ _fiberRoots.add(fiberRoot);
206
527
  }
207
528
  });
208
- var dedupeFileName = (fileName) => {
209
- if (!fileName) return fileName;
210
- const parts = fileName.split("/");
211
- for (let start = 0; start < parts.length / 2; start++) {
212
- for (let len = 1; len <= parts.length - start; len++) {
213
- const firstSeq = parts.slice(start, start + len);
214
- const secondStart = start + len;
215
- const secondSeq = parts.slice(secondStart, secondStart + len);
216
- if (firstSeq.length > 2 && firstSeq.length === secondSeq.length && firstSeq.every((part, i) => part === secondSeq[i])) {
217
- return parts.slice(secondStart).join("/");
218
- }
219
- }
220
- }
221
- return fileName;
222
- };
223
- var getStack = async (element) => {
529
+ var getSourceTrace = async (element) => {
224
530
  const fiber = getFiberFromHostInstance(element);
225
531
  if (!fiber) return null;
226
- const stackTrace = getFiberStackTrace(fiber);
227
- const rawOwnerStack = await getOwnerStack(stackTrace);
228
- const stack = rawOwnerStack.map((item) => ({
229
- componentName: item.name,
230
- fileName: dedupeFileName(item.source?.fileName)
231
- }));
232
- if (stack.length > 0 && fiber) {
233
- const fiberSource = await getFiberSource(fiber);
234
- if (fiberSource) {
235
- const fiberType = fiber.type;
236
- const displayName = fiberType?.displayName ?? fiberType?.name ?? stack[0].componentName;
237
- stack[0].displayName = displayName;
238
- const dedupedFileName = dedupeFileName(fiberSource.fileName);
239
- stack[0].source = `${dedupedFileName}:${fiberSource.lineNumber}:${fiberSource.columnNumber}`;
240
- }
241
- }
242
- return stack;
243
- };
244
- var filterStack = (stack) => {
245
- return stack.filter(
246
- (item) => item.fileName && !item.fileName.includes("node_modules") && item.componentName.length > 1 && !item.fileName.startsWith("_")
532
+ const ownerStack = getOwnerStack(fiber);
533
+ const sources = await getSourcesFromStack(
534
+ ownerStack,
535
+ Number.MAX_SAFE_INTEGER
247
536
  );
248
- };
249
- var serializeStack = (stack) => {
250
- return stack.map((item, index) => {
251
- const fileName = item.fileName;
252
- const componentName = item.displayName || item.componentName;
253
- let result = `${componentName}${fileName ? ` (${fileName})` : ""}`;
254
- if (index === 0 && item.source) {
255
- result += `
256
- ${item.source}`;
257
- }
258
- return result;
259
- }).join("\n");
537
+ if (!sources) return null;
538
+ return sources;
260
539
  };
261
540
  var getHTMLSnippet = (element) => {
262
541
  const semanticTags = /* @__PURE__ */ new Set([
@@ -456,951 +735,531 @@ var getHTMLSnippet = (element) => {
456
735
  return lines.join("\n");
457
736
  };
458
737
 
459
- // src/overlay.ts
460
- var VIEWPORT_MARGIN_PX = 8;
461
- var LABEL_OFFSET_PX = 6;
462
- var INDICATOR_CLAMP_PADDING_PX = 4;
463
- var INDICATOR_SUCCESS_VISIBLE_MS = 1500;
464
- var INDICATOR_FADE_MS = 200;
465
- var lerp = (start, end, factor) => {
466
- return start + (end - start) * factor;
467
- };
468
- var SELECTION_LERP_FACTOR = 0.95;
469
- var createSelectionElement = ({
470
- borderRadius,
471
- height,
472
- transform,
473
- width,
474
- x,
475
- y
476
- }) => {
477
- const overlay = document.createElement("div");
478
- overlay.style.position = "fixed";
479
- overlay.style.top = `${y}px`;
480
- overlay.style.left = `${x}px`;
481
- overlay.style.width = `${width}px`;
482
- overlay.style.height = `${height}px`;
483
- overlay.style.borderRadius = borderRadius;
484
- overlay.style.transform = transform;
485
- overlay.style.pointerEvents = "none";
486
- overlay.style.border = "1px solid rgb(210, 57, 192)";
487
- overlay.style.backgroundColor = "rgba(210, 57, 192, 0.2)";
488
- overlay.style.zIndex = "2147483646";
489
- overlay.style.boxSizing = "border-box";
490
- overlay.style.display = "none";
491
- return overlay;
492
- };
493
- var updateSelectionElement = (element, { borderRadius, height, transform, width, x, y }) => {
494
- const currentTop = parseFloat(element.style.top) || 0;
495
- const currentLeft = parseFloat(element.style.left) || 0;
496
- const currentWidth = parseFloat(element.style.width) || 0;
497
- const currentHeight = parseFloat(element.style.height) || 0;
498
- const topValue = `${lerp(currentTop, y, SELECTION_LERP_FACTOR)}px`;
499
- const leftValue = `${lerp(currentLeft, x, SELECTION_LERP_FACTOR)}px`;
500
- const widthValue = `${lerp(currentWidth, width, SELECTION_LERP_FACTOR)}px`;
501
- const heightValue = `${lerp(currentHeight, height, SELECTION_LERP_FACTOR)}px`;
502
- if (element.style.top !== topValue) {
503
- element.style.top = topValue;
504
- }
505
- if (element.style.left !== leftValue) {
506
- element.style.left = leftValue;
507
- }
508
- if (element.style.width !== widthValue) {
509
- element.style.width = widthValue;
510
- }
511
- if (element.style.height !== heightValue) {
512
- element.style.height = heightValue;
513
- }
514
- if (element.style.borderRadius !== borderRadius) {
515
- element.style.borderRadius = borderRadius;
516
- }
517
- if (element.style.transform !== transform) {
518
- element.style.transform = transform;
519
- }
520
- };
521
- var createSelectionOverlay = (root) => {
522
- const element = createSelectionElement({
523
- borderRadius: "0px",
524
- height: 0,
525
- transform: "none",
526
- width: 0,
527
- x: -1e3,
528
- y: -1e3
529
- });
530
- root.appendChild(element);
531
- let visible = false;
532
- element.addEventListener("mousedown", (event) => {
533
- event.preventDefault();
534
- event.stopPropagation();
535
- event.stopImmediatePropagation();
536
- const { overlayMode } = libStore.getState();
537
- if (overlayMode === "visible") {
538
- libStore.setState((state) => ({
539
- ...state,
540
- overlayMode: "copying"
541
- }));
542
- }
543
- }, true);
544
- return {
545
- element,
546
- hide: () => {
547
- visible = false;
548
- element.style.display = "none";
549
- },
550
- isVisible: () => visible,
551
- show: () => {
552
- visible = true;
553
- element.style.display = "block";
554
- },
555
- update: (selection) => {
556
- updateSelectionElement(element, selection);
557
- }
558
- };
559
- };
560
- var createGrabbedOverlay = (root, selection) => {
561
- const element = document.createElement("div");
562
- element.style.position = "fixed";
563
- element.style.top = `${selection.y}px`;
564
- element.style.left = `${selection.x}px`;
565
- element.style.width = `${selection.width}px`;
566
- element.style.height = `${selection.height}px`;
567
- element.style.borderRadius = selection.borderRadius;
568
- element.style.transform = selection.transform;
569
- element.style.pointerEvents = "none";
570
- element.style.border = "1px solid rgb(210, 57, 192)";
571
- element.style.backgroundColor = "rgba(210, 57, 192, 0.2)";
572
- element.style.zIndex = "2147483646";
573
- element.style.boxSizing = "border-box";
574
- element.style.transition = "opacity 0.3s ease-out";
575
- element.style.opacity = "1";
576
- root.appendChild(element);
577
- requestAnimationFrame(() => {
578
- element.style.opacity = "0";
579
- });
580
- setTimeout(() => {
581
- element.remove();
582
- }, 300);
583
- };
584
- var createSpinner = () => {
585
- const spinner = document.createElement("span");
586
- spinner.style.display = "inline-block";
587
- spinner.style.width = "8px";
588
- spinner.style.height = "8px";
589
- spinner.style.border = "1.5px solid rgb(210, 57, 192)";
590
- spinner.style.borderTopColor = "transparent";
591
- spinner.style.borderRadius = "50%";
592
- spinner.style.marginRight = "4px";
593
- spinner.style.verticalAlign = "middle";
594
- spinner.animate(
595
- [{ transform: "rotate(0deg)" }, { transform: "rotate(360deg)" }],
596
- {
597
- duration: 600,
598
- easing: "linear",
599
- iterations: Infinity
600
- }
601
- );
602
- return spinner;
603
- };
604
- var activeIndicator = null;
605
- var createIndicator = () => {
606
- const indicator = document.createElement("div");
607
- indicator.style.position = "fixed";
608
- indicator.style.top = "calc(8px + env(safe-area-inset-top))";
609
- indicator.style.padding = "2px 6px";
610
- indicator.style.backgroundColor = "#fde7f7";
611
- indicator.style.color = "#b21c8e";
612
- indicator.style.border = "1px solid #f7c5ec";
613
- indicator.style.borderRadius = "4px";
614
- indicator.style.fontSize = "11px";
615
- indicator.style.fontWeight = "500";
616
- indicator.style.fontFamily = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif";
617
- indicator.style.zIndex = "2147483647";
618
- indicator.style.pointerEvents = "none";
619
- indicator.style.opacity = "0";
620
- indicator.style.transition = "opacity 0.2s ease-in-out";
621
- indicator.style.display = "flex";
622
- indicator.style.alignItems = "center";
623
- indicator.style.maxWidth = "calc(100vw - (16px + env(safe-area-inset-left) + env(safe-area-inset-right)))";
624
- indicator.style.overflow = "hidden";
625
- indicator.style.textOverflow = "ellipsis";
626
- indicator.style.whiteSpace = "nowrap";
627
- return indicator;
628
- };
629
- var showLabel = (root, selectionLeftPx, selectionTopPx, tagName) => {
630
- let indicator = activeIndicator;
631
- let isNewIndicator = false;
632
- if (!indicator) {
633
- indicator = createIndicator();
634
- root.appendChild(indicator);
635
- activeIndicator = indicator;
636
- isNewIndicator = true;
637
- isProcessing = false;
638
- }
639
- if (!isProcessing) {
640
- const labelText = indicator.querySelector("span");
641
- if (labelText) {
642
- const tagNameMonospace = document.createElement("span");
643
- tagNameMonospace.textContent = tagName ? `<${tagName}>` : "<element>";
644
- tagNameMonospace.style.fontFamily = "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace";
645
- tagNameMonospace.style.fontVariantNumeric = "tabular-nums";
646
- labelText.replaceChildren(tagNameMonospace);
647
- } else {
648
- const newLabelText = document.createElement("span");
649
- const tagNameMonospace = document.createElement("span");
650
- tagNameMonospace.textContent = tagName ? `<${tagName}>` : "<element>";
651
- tagNameMonospace.style.fontFamily = "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace";
652
- tagNameMonospace.style.fontVariantNumeric = "tabular-nums";
653
- newLabelText.appendChild(tagNameMonospace);
654
- indicator.appendChild(newLabelText);
655
- }
656
- }
657
- const indicatorRect = indicator.getBoundingClientRect();
658
- const viewportWidthPx = window.innerWidth;
659
- const viewportHeightPx = window.innerHeight;
660
- let indicatorLeftPx = Math.round(selectionLeftPx);
661
- let indicatorTopPx = Math.round(selectionTopPx) - indicatorRect.height - LABEL_OFFSET_PX;
662
- const CLAMPED_PADDING = INDICATOR_CLAMP_PADDING_PX;
663
- const minLeft = VIEWPORT_MARGIN_PX;
664
- const minTop = VIEWPORT_MARGIN_PX;
665
- const maxLeft = viewportWidthPx - indicatorRect.width - VIEWPORT_MARGIN_PX;
666
- const maxTop = viewportHeightPx - indicatorRect.height - VIEWPORT_MARGIN_PX;
667
- const willClampLeft = indicatorLeftPx < minLeft;
668
- const willClampTop = indicatorTopPx < minTop;
669
- const isClamped = willClampLeft || willClampTop;
670
- indicatorLeftPx = Math.max(minLeft, Math.min(indicatorLeftPx, maxLeft));
671
- indicatorTopPx = Math.max(minTop, Math.min(indicatorTopPx, maxTop));
672
- if (isClamped) {
673
- indicatorLeftPx += CLAMPED_PADDING;
674
- indicatorTopPx += CLAMPED_PADDING;
675
- }
676
- indicator.style.left = `${indicatorLeftPx}px`;
677
- indicator.style.top = `${indicatorTopPx}px`;
678
- indicator.style.right = "auto";
679
- if (isNewIndicator) {
680
- requestAnimationFrame(() => {
681
- indicator.style.opacity = "1";
682
- });
683
- } else if (indicator.style.opacity !== "1") {
684
- indicator.style.opacity = "1";
685
- }
686
- };
687
- var isProcessing = false;
688
- var activeGrabbedIndicators = /* @__PURE__ */ new Set();
689
- var updateLabelToProcessing = (root, selectionLeftPx, selectionTopPx) => {
690
- const indicator = createIndicator();
691
- indicator.style.zIndex = "2147483648";
692
- root.appendChild(indicator);
693
- activeGrabbedIndicators.add(indicator);
694
- const positionIndicator = () => {
695
- if (selectionLeftPx === void 0 || selectionTopPx === void 0) return;
696
- const indicatorRect = indicator.getBoundingClientRect();
697
- const viewportWidthPx = window.innerWidth;
698
- const viewportHeightPx = window.innerHeight;
699
- let indicatorLeftPx = Math.round(selectionLeftPx);
700
- let indicatorTopPx = Math.round(selectionTopPx) - indicatorRect.height - LABEL_OFFSET_PX;
701
- const CLAMPED_PADDING = INDICATOR_CLAMP_PADDING_PX;
702
- const minLeft = VIEWPORT_MARGIN_PX;
703
- const minTop = VIEWPORT_MARGIN_PX;
704
- const maxLeft = viewportWidthPx - indicatorRect.width - VIEWPORT_MARGIN_PX;
705
- const maxTop = viewportHeightPx - indicatorRect.height - VIEWPORT_MARGIN_PX;
706
- const willClampLeft = indicatorLeftPx < minLeft;
707
- const willClampTop = indicatorTopPx < minTop;
708
- const isClamped = willClampLeft || willClampTop;
709
- indicatorLeftPx = Math.max(minLeft, Math.min(indicatorLeftPx, maxLeft));
710
- indicatorTopPx = Math.max(minTop, Math.min(indicatorTopPx, maxTop));
711
- if (isClamped) {
712
- indicatorLeftPx += CLAMPED_PADDING;
713
- indicatorTopPx += CLAMPED_PADDING;
714
- }
715
- indicator.style.left = `${indicatorLeftPx}px`;
716
- indicator.style.top = `${indicatorTopPx}px`;
717
- indicator.style.right = "auto";
718
- };
719
- const loadingSpinner = createSpinner();
720
- const labelText = document.createElement("span");
721
- labelText.textContent = "Grabbing\u2026";
722
- indicator.appendChild(loadingSpinner);
723
- indicator.appendChild(labelText);
724
- positionIndicator();
725
- requestAnimationFrame(() => {
726
- indicator.style.opacity = "1";
738
+ // src/utils/copy-content.ts
739
+ var waitForFocus = () => {
740
+ if (document.hasFocus()) return Promise.resolve();
741
+ return new Promise((resolve) => {
742
+ const onFocus = () => {
743
+ window.removeEventListener("focus", onFocus);
744
+ resolve();
745
+ };
746
+ window.addEventListener("focus", onFocus);
747
+ window.focus();
727
748
  });
728
- return (tagName) => {
729
- indicator.textContent = "";
730
- const checkmarkIcon = document.createElement("span");
731
- checkmarkIcon.textContent = "\u2713";
732
- checkmarkIcon.style.display = "inline-block";
733
- checkmarkIcon.style.marginRight = "4px";
734
- checkmarkIcon.style.fontWeight = "600";
735
- const newLabelText = document.createElement("span");
736
- const tagNameMonospace = document.createElement("span");
737
- tagNameMonospace.textContent = tagName ? `<${tagName}>` : "<element>";
738
- tagNameMonospace.style.fontFamily = "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace";
739
- tagNameMonospace.style.fontVariantNumeric = "tabular-nums";
740
- newLabelText.appendChild(document.createTextNode("Grabbed "));
741
- newLabelText.appendChild(tagNameMonospace);
742
- indicator.appendChild(checkmarkIcon);
743
- indicator.appendChild(newLabelText);
744
- requestAnimationFrame(() => {
745
- positionIndicator();
746
- });
747
- setTimeout(() => {
748
- indicator.style.opacity = "0";
749
- setTimeout(() => {
750
- indicator.remove();
751
- activeGrabbedIndicators.delete(indicator);
752
- }, INDICATOR_FADE_MS);
753
- }, INDICATOR_SUCCESS_VISIBLE_MS);
754
- };
755
- };
756
- var hideLabel = () => {
757
- if (activeIndicator) {
758
- activeIndicator.remove();
759
- activeIndicator = null;
760
- }
761
- isProcessing = false;
762
- };
763
- var cleanupGrabbedIndicators = () => {
764
- for (const indicator of activeGrabbedIndicators) {
765
- indicator.remove();
766
- }
767
- activeGrabbedIndicators.clear();
768
- };
769
- var activeProgressIndicator = null;
770
- var createProgressIndicatorElement = () => {
771
- const container = document.createElement("div");
772
- container.style.position = "fixed";
773
- container.style.zIndex = "2147483647";
774
- container.style.pointerEvents = "none";
775
- container.style.opacity = "0";
776
- container.style.transition = "opacity 0.1s ease-in-out";
777
- const progressBarContainer = document.createElement("div");
778
- progressBarContainer.style.width = "32px";
779
- progressBarContainer.style.height = "2px";
780
- progressBarContainer.style.backgroundColor = "rgba(178, 28, 142, 0.2)";
781
- progressBarContainer.style.borderRadius = "1px";
782
- progressBarContainer.style.overflow = "hidden";
783
- progressBarContainer.style.position = "relative";
784
- const progressBarFill = document.createElement("div");
785
- progressBarFill.style.width = "0%";
786
- progressBarFill.style.height = "100%";
787
- progressBarFill.style.backgroundColor = "#b21c8e";
788
- progressBarFill.style.borderRadius = "1px";
789
- progressBarFill.style.transition = "width 0.05s linear";
790
- progressBarFill.setAttribute("data-progress-fill", "true");
791
- progressBarContainer.appendChild(progressBarFill);
792
- container.appendChild(progressBarContainer);
793
- return container;
794
- };
795
- var showProgressIndicator = (root, progress, mouseX, mouseY) => {
796
- if (!activeProgressIndicator) {
797
- activeProgressIndicator = createProgressIndicatorElement();
798
- root.appendChild(activeProgressIndicator);
799
- requestAnimationFrame(() => {
800
- if (activeProgressIndicator) {
801
- activeProgressIndicator.style.opacity = "1";
802
- }
803
- });
804
- }
805
- const indicator = activeProgressIndicator;
806
- const indicatorRect = indicator.getBoundingClientRect();
807
- const viewportWidth = window.innerWidth;
808
- const viewportHeight = window.innerHeight;
809
- const CURSOR_OFFSET = 14;
810
- const VIEWPORT_MARGIN = 8;
811
- let indicatorLeft = mouseX - indicatorRect.width / 2;
812
- let indicatorTop = mouseY + CURSOR_OFFSET;
813
- if (indicatorTop + indicatorRect.height + VIEWPORT_MARGIN > viewportHeight) {
814
- indicatorTop = mouseY - indicatorRect.height - CURSOR_OFFSET;
815
- }
816
- indicatorTop = Math.max(
817
- VIEWPORT_MARGIN,
818
- Math.min(indicatorTop, viewportHeight - indicatorRect.height - VIEWPORT_MARGIN)
819
- );
820
- indicatorLeft = Math.max(
821
- VIEWPORT_MARGIN,
822
- Math.min(indicatorLeft, viewportWidth - indicatorRect.width - VIEWPORT_MARGIN)
823
- );
824
- indicator.style.top = `${indicatorTop}px`;
825
- indicator.style.left = `${indicatorLeft}px`;
826
- const progressFill = indicator.querySelector(
827
- "[data-progress-fill]"
828
- );
829
- if (progressFill) {
830
- const percentage = Math.min(100, Math.max(0, progress * 100));
831
- progressFill.style.width = `${percentage}%`;
832
- }
833
749
  };
834
- var hideProgressIndicator = () => {
835
- if (activeProgressIndicator) {
836
- activeProgressIndicator.style.opacity = "0";
837
- setTimeout(() => {
838
- if (activeProgressIndicator) {
839
- activeProgressIndicator.remove();
840
- activeProgressIndicator = null;
750
+ var copyContent = async (content) => {
751
+ await waitForFocus();
752
+ try {
753
+ if (Array.isArray(content)) {
754
+ if (!navigator?.clipboard?.write) {
755
+ for (const contentPart of content) {
756
+ if (typeof contentPart === "string") {
757
+ const result = copyContentFallback(contentPart);
758
+ if (!result) return result;
759
+ }
760
+ }
761
+ return true;
841
762
  }
842
- }, 100);
843
- }
844
- };
845
-
846
- // src/utils/copy-text.ts
847
- var IS_NAVIGATOR_CLIPBOARD_AVAILABLE = typeof window !== "undefined" && window.navigator.clipboard && window.isSecureContext;
848
- var copyTextToClipboard = async (text) => {
849
- if (IS_NAVIGATOR_CLIPBOARD_AVAILABLE) {
850
- try {
851
- await navigator.clipboard.writeText(text);
763
+ await navigator.clipboard.write([
764
+ new ClipboardItem(
765
+ Object.fromEntries(
766
+ content.map((contentPart) => {
767
+ if (contentPart instanceof Blob) {
768
+ return [contentPart.type ?? "text/plain", contentPart];
769
+ } else {
770
+ return [
771
+ "text/plain",
772
+ new Blob([contentPart], { type: "text/plain" })
773
+ ];
774
+ }
775
+ })
776
+ )
777
+ )
778
+ ]);
779
+ return true;
780
+ } else if (content instanceof Blob) {
781
+ await navigator.clipboard.write([
782
+ new ClipboardItem({ [content.type]: content })
783
+ ]);
852
784
  return true;
853
- } catch {
785
+ } else {
786
+ try {
787
+ await navigator.clipboard.writeText(String(content));
788
+ return true;
789
+ } catch {
790
+ return copyContentFallback(content);
791
+ }
854
792
  }
793
+ } catch {
794
+ return false;
855
795
  }
856
- const textareaElement = document.createElement("textarea");
857
- textareaElement.value = text;
858
- textareaElement.setAttribute("readonly", "");
859
- textareaElement.style.position = "fixed";
860
- textareaElement.style.top = "-9999px";
861
- textareaElement.style.opacity = "0";
862
- textareaElement.style.pointerEvents = "none";
796
+ };
797
+ var copyContentFallback = (content) => {
798
+ if (!document.execCommand) return false;
799
+ const el = document.createElement("textarea");
800
+ el.value = String(content);
801
+ el.style.clipPath = "inset(50%)";
802
+ el.ariaHidden = "true";
863
803
  const doc = document.body || document.documentElement;
864
- doc.appendChild(textareaElement);
865
- textareaElement.select();
866
- textareaElement.setSelectionRange(0, textareaElement.value.length);
867
- let didCopyToClipboard = false;
804
+ doc.append(el);
868
805
  try {
869
- didCopyToClipboard = document.execCommand("copy");
870
- } catch {
871
- didCopyToClipboard = false;
806
+ el.select();
807
+ return document.execCommand("copy");
872
808
  } finally {
873
- doc.removeChild(textareaElement);
809
+ el.remove();
874
810
  }
875
- return didCopyToClipboard;
876
- };
877
-
878
- // src/utils/is-element-visible.ts
879
- var isElementVisible = (element, computedStyle = window.getComputedStyle(element)) => {
880
- return computedStyle.display !== "none" && computedStyle.visibility !== "hidden" && computedStyle.opacity !== "0";
881
811
  };
882
812
 
883
- // src/utils/mount-root.ts
884
- var ATTRIBUTE_NAME = "data-react-grab";
885
- var mountRoot = () => {
886
- const mountedHost = document.querySelector(`[${ATTRIBUTE_NAME}]`);
887
- if (mountedHost) {
888
- const mountedRoot = mountedHost.shadowRoot?.querySelector(
889
- `[${ATTRIBUTE_NAME}]`
890
- );
891
- if (mountedRoot instanceof HTMLDivElement && mountedHost.shadowRoot) {
892
- return mountedRoot;
893
- }
813
+ // src/core.tsx
814
+ var init = (rawOptions) => {
815
+ const options = {
816
+ enabled: true,
817
+ keyHoldDuration: 300,
818
+ ...rawOptions
819
+ };
820
+ if (options.enabled === false) {
821
+ return;
894
822
  }
895
- const host = document.createElement("div");
896
- host.setAttribute(ATTRIBUTE_NAME, "true");
897
- host.style.zIndex = "2147483646";
898
- host.style.position = "fixed";
899
- host.style.top = "0";
900
- host.style.left = "0";
901
- const shadowRoot = host.attachShadow({ mode: "open" });
902
- const root = document.createElement("div");
903
- root.setAttribute(ATTRIBUTE_NAME, "true");
904
- shadowRoot.appendChild(root);
905
- const doc = document.body ?? document.documentElement;
906
- doc.appendChild(host);
907
- return root;
908
- };
909
-
910
- // src/utils/store.ts
911
- var createStore = (initializer) => {
912
- const subscriberMap = /* @__PURE__ */ new Map();
913
- let currentListenerIndex = 0;
914
- let currentState;
915
- const setState = (maybeStateOrReducer) => {
916
- const prevState = currentState;
917
- const resolvedState = typeof maybeStateOrReducer === "function" ? maybeStateOrReducer(prevState) : maybeStateOrReducer;
918
- const nextState = {
919
- ...prevState,
920
- ...resolvedState
823
+ return createRoot((dispose) => {
824
+ const [isHoldingKeys, setIsHoldingKeys] = createSignal(false);
825
+ const [mouseX, setMouseX] = createSignal(-1e3);
826
+ const [mouseY, setMouseY] = createSignal(-1e3);
827
+ const [isDragging, setIsDragging] = createSignal(false);
828
+ const [dragStartX, setDragStartX] = createSignal(-1e3);
829
+ const [dragStartY, setDragStartY] = createSignal(-1e3);
830
+ const [isCopying, setIsCopying] = createSignal(false);
831
+ const [lastGrabbedElement, setLastGrabbedElement] = createSignal(null);
832
+ const [progressStartTime, setProgressStartTime] = createSignal(null);
833
+ const [progressTick, setProgressTick] = createSignal(0);
834
+ const [grabbedOverlays, setGrabbedOverlays] = createSignal([]);
835
+ const [successLabels, setSuccessLabels] = createSignal([]);
836
+ const [isActivated, setIsActivated] = createSignal(false);
837
+ const [showProgressIndicator, setShowProgressIndicator] = createSignal(false);
838
+ let holdTimerId = null;
839
+ let progressAnimationId = null;
840
+ let progressDelayTimerId = null;
841
+ const isOverlayActive = createMemo(() => isActivated() && !isCopying());
842
+ 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;
921
856
  };
922
- currentState = nextState;
923
- for (const entry of subscriberMap.values()) {
924
- if (entry.type === "selected" /* Selected */) {
925
- const nextSelectedValue = entry.selector(nextState);
926
- const prevSelectedValue = entry.prevSelectedValue;
927
- if (!Object.is(prevSelectedValue, nextSelectedValue)) {
928
- entry.prevSelectedValue = nextSelectedValue;
929
- entry.listener(nextSelectedValue, prevSelectedValue);
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);
930
889
  }
931
- } else {
932
- entry.listener(nextState, prevState);
933
890
  }
934
- }
935
- return currentState;
936
- };
937
- const getState = () => {
938
- return currentState;
939
- };
940
- const initialState = initializer(setState, getState);
941
- currentState = initialState;
942
- const subscribeWithSelector = (listener, selector) => {
943
- const index = String(currentListenerIndex++);
944
- const wrappedListener = (value, prevValue) => listener(value, prevValue);
945
- const entry = {
946
- listener: wrappedListener,
947
- prevSelectedValue: selector(currentState),
948
- selector,
949
- type: "selected" /* Selected */
891
+ return elements;
950
892
  };
951
- subscriberMap.set(index, entry);
952
- return () => {
953
- subscriberMap.delete(index);
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;
954
921
  };
955
- };
956
- const subscribeToFullState = (listener) => {
957
- const index = String(currentListenerIndex++);
958
- const entry = {
959
- listener,
960
- type: "full" /* Full */
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,
936
+ bounds
937
+ }]);
938
+ setTimeout(() => {
939
+ setGrabbedOverlays((prev) => prev.filter((overlay) => overlay.id !== id));
940
+ }, 300);
961
941
  };
962
- subscriberMap.set(index, entry);
963
- return () => {
964
- subscriberMap.delete(index);
942
+ const addSuccessLabel = (text, x, y) => {
943
+ const id = `success-${Date.now()}-${Math.random()}`;
944
+ setSuccessLabels((prev) => [...prev, {
945
+ id,
946
+ text,
947
+ x,
948
+ y
949
+ }]);
950
+ setTimeout(() => {
951
+ setSuccessLabels((prev) => prev.filter((label) => label.id !== id));
952
+ }, 1700);
965
953
  };
966
- };
967
- function subscribe(subscriber, selector) {
968
- return selector ? subscribeWithSelector(subscriber, selector) : subscribeToFullState(subscriber);
969
- }
970
- const store = {
971
- getInitialState() {
972
- return initialState;
973
- },
974
- getState,
975
- setState,
976
- subscribe
977
- };
978
- return store;
979
- };
954
+ const handleCopy = async (targetElement2) => {
955
+ 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
+ });
965
+ 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}
980
972
 
981
- // src/index.ts
982
- var libStore = createStore(() => ({
983
- keyPressTimestamps: /* @__PURE__ */ new Map(),
984
- mouseX: -1e3,
985
- mouseY: -1e3,
986
- overlayMode: "hidden",
987
- pressedKeys: /* @__PURE__ */ new Set()
988
- }));
989
- var getDefaultHotkey = () => {
990
- if (typeof navigator === "undefined") {
991
- return ["Meta", "C"];
992
- }
993
- const isMac = navigator.platform.toLowerCase().includes("mac");
994
- return isMac ? ["Meta", "C"] : ["Control", "C"];
995
- };
996
- var init = (options = {}) => {
997
- if (options.enabled === false) {
998
- return;
999
- }
1000
- const resolvedOptions = {
1001
- adapter: void 0,
1002
- enabled: true,
1003
- hotkey: options.hotkey ?? getDefaultHotkey(),
1004
- keyHoldDuration: 500,
1005
- ...options
1006
- };
1007
- const root = mountRoot();
1008
- const selectionOverlay = createSelectionOverlay(root);
1009
- let hoveredElement = null;
1010
- let lastGrabbedElement = null;
1011
- let isCopying = false;
1012
- let progressAnimationFrame = null;
1013
- let progressStartTime = null;
1014
- const checkIsActivationHotkeyPressed = () => {
1015
- if (Array.isArray(resolvedOptions.hotkey)) {
1016
- for (const key of resolvedOptions.hotkey) {
1017
- if (!isKeyPressed(key)) {
1018
- return false;
973
+ Component owner stack:
974
+ ${formattedStackTrace}`));
1019
975
  }
976
+ } catch {
1020
977
  }
1021
- return true;
1022
- }
1023
- return isKeyPressed(resolvedOptions.hotkey);
1024
- };
1025
- const updateProgressIndicator = () => {
1026
- if (progressStartTime === null) return;
1027
- const elapsed = Date.now() - progressStartTime;
1028
- const progress = Math.min(1, elapsed / resolvedOptions.keyHoldDuration);
1029
- const { mouseX, mouseY } = libStore.getState();
1030
- showProgressIndicator(root, progress, mouseX, mouseY);
1031
- if (progress < 1) {
1032
- progressAnimationFrame = requestAnimationFrame(updateProgressIndicator);
1033
- }
1034
- };
1035
- const startProgressTracking = () => {
1036
- if (progressAnimationFrame !== null) return;
1037
- progressStartTime = Date.now();
1038
- const { mouseX, mouseY } = libStore.getState();
1039
- showProgressIndicator(root, 0, mouseX, mouseY);
1040
- progressAnimationFrame = requestAnimationFrame(updateProgressIndicator);
1041
- };
1042
- const stopProgressTracking = () => {
1043
- if (progressAnimationFrame !== null) {
1044
- cancelAnimationFrame(progressAnimationFrame);
1045
- progressAnimationFrame = null;
1046
- }
1047
- progressStartTime = null;
1048
- hideProgressIndicator();
1049
- };
1050
- let cleanupActivationHotkeyWatcher = null;
1051
- const handleKeyStateChange = (pressedKeys) => {
1052
- const { overlayMode } = libStore.getState();
1053
- if (pressedKeys.has("Escape") || pressedKeys.has("Esc")) {
1054
- libStore.setState((state) => {
1055
- const nextPressedKeys = new Set(state.pressedKeys);
1056
- nextPressedKeys.delete("Escape");
1057
- nextPressedKeys.delete("Esc");
1058
- const nextTimestamps = new Map(state.keyPressTimestamps);
1059
- nextTimestamps.delete("Escape");
1060
- nextTimestamps.delete("Esc");
1061
- const activationKeys = Array.isArray(resolvedOptions.hotkey) ? resolvedOptions.hotkey : [resolvedOptions.hotkey];
1062
- for (const activationKey of activationKeys) {
1063
- if (activationKey.length === 1) {
1064
- nextPressedKeys.delete(activationKey.toLowerCase());
1065
- nextPressedKeys.delete(activationKey.toUpperCase());
1066
- nextTimestamps.delete(activationKey.toLowerCase());
1067
- nextTimestamps.delete(activationKey.toUpperCase());
978
+ addSuccessLabel(tagName ? `<${tagName}>` : "<element>", elementBounds.left, elementBounds.top);
979
+ };
980
+ const handleMultipleCopy = async (targetElements) => {
981
+ if (targetElements.length === 0) return;
982
+ let minX = Infinity;
983
+ let minY = Infinity;
984
+ for (const element of targetElements) {
985
+ 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
+ });
996
+ }
997
+ 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}`);
1068
1008
  } else {
1069
- nextPressedKeys.delete(activationKey);
1070
- nextTimestamps.delete(activationKey);
1009
+ elementSnippets.push(elementHtml);
1071
1010
  }
1072
1011
  }
1012
+ const combinedContent = elementSnippets.join("\n\n---\n\n");
1013
+ await copyContent(wrapInReferencedElements(combinedContent));
1014
+ } catch {
1015
+ }
1016
+ addSuccessLabel(`${targetElements.length} elements`, minX, minY);
1017
+ };
1018
+ const targetElement = createMemo(() => {
1019
+ if (!isOverlayActive() || isDragging()) return null;
1020
+ return getElementAtPosition(mouseX(), mouseY());
1021
+ });
1022
+ const selectionBounds = createMemo(() => {
1023
+ const element = targetElement();
1024
+ if (!element) return void 0;
1025
+ const last = lastGrabbedElement();
1026
+ if (last && element === last) return void 0;
1027
+ const elementBounds = element.getBoundingClientRect();
1028
+ const computedStyle = window.getComputedStyle(element);
1029
+ return {
1030
+ borderRadius: computedStyle.borderRadius || "0px",
1031
+ height: elementBounds.height,
1032
+ transform: computedStyle.transform || "none",
1033
+ width: elementBounds.width,
1034
+ x: elementBounds.left,
1035
+ y: elementBounds.top
1036
+ };
1037
+ });
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());
1044
+ return {
1045
+ borderRadius: "0px",
1046
+ height: marqueeHeight,
1047
+ transform: "none",
1048
+ width: marqueeWidth,
1049
+ x: marqueeX,
1050
+ y: marqueeY
1051
+ };
1052
+ });
1053
+ const labelText = createMemo(() => {
1054
+ const element = targetElement();
1055
+ if (!element) return "";
1056
+ const tagName = (element.tagName || "").toLowerCase();
1057
+ return tagName ? `<${tagName}>` : "<element>";
1058
+ });
1059
+ const labelPosition = createMemo(() => {
1060
+ const element = targetElement() ?? lastGrabbedElement();
1061
+ if (element) {
1062
+ const rect = element.getBoundingClientRect();
1073
1063
  return {
1074
- ...state,
1075
- keyPressTimestamps: nextTimestamps,
1076
- overlayMode: "hidden",
1077
- pressedKeys: nextPressedKeys
1064
+ x: rect.left,
1065
+ y: rect.top
1078
1066
  };
1079
- });
1080
- if (cleanupActivationHotkeyWatcher) {
1081
- cleanupActivationHotkeyWatcher();
1082
- cleanupActivationHotkeyWatcher = null;
1083
- }
1084
- stopProgressTracking();
1085
- return;
1086
- }
1087
- const isActivationHotkeyPressed = checkIsActivationHotkeyPressed();
1088
- if (!isActivationHotkeyPressed) {
1089
- if (cleanupActivationHotkeyWatcher) {
1090
- cleanupActivationHotkeyWatcher();
1091
- cleanupActivationHotkeyWatcher = null;
1092
- }
1093
- if (overlayMode !== "hidden") {
1094
- libStore.setState((state) => ({
1095
- ...state,
1096
- overlayMode: "hidden"
1097
- }));
1098
1067
  }
1099
- stopProgressTracking();
1100
- return;
1101
- }
1102
- if (overlayMode === "hidden" && !cleanupActivationHotkeyWatcher) {
1103
- startProgressTracking();
1104
- cleanupActivationHotkeyWatcher = watchKeyHeldFor(
1105
- resolvedOptions.hotkey,
1106
- resolvedOptions.keyHoldDuration,
1107
- () => {
1108
- libStore.setState((state) => ({
1109
- ...state,
1110
- overlayMode: "visible"
1111
- }));
1112
- stopProgressTracking();
1113
- cleanupActivationHotkeyWatcher = null;
1114
- }
1115
- );
1116
- }
1117
- };
1118
- const cleanupKeyStateChangeSubscription = libStore.subscribe(
1119
- handleKeyStateChange,
1120
- (state) => state.pressedKeys
1121
- );
1122
- let mouseMoveScheduled = false;
1123
- let pendingMouseX = -1e3;
1124
- let pendingMouseY = -1e3;
1125
- const handleMouseMove = (event) => {
1126
- pendingMouseX = event.clientX;
1127
- pendingMouseY = event.clientY;
1128
- if (mouseMoveScheduled) return;
1129
- mouseMoveScheduled = true;
1130
- requestAnimationFrame(() => {
1131
- mouseMoveScheduled = false;
1132
- libStore.setState((state) => ({
1133
- ...state,
1134
- mouseX: pendingMouseX,
1135
- mouseY: pendingMouseY
1136
- }));
1068
+ return {
1069
+ x: mouseX(),
1070
+ y: mouseY()
1071
+ };
1137
1072
  });
1138
- };
1139
- const handleMouseDown = (event) => {
1140
- if (event.button !== 0) {
1141
- return;
1142
- }
1143
- const { overlayMode } = libStore.getState();
1144
- if (overlayMode === "hidden") {
1145
- return;
1146
- }
1147
- event.preventDefault();
1148
- event.stopPropagation();
1149
- event.stopImmediatePropagation();
1150
- libStore.setState((state) => ({
1151
- ...state,
1152
- overlayMode: "copying"
1153
- }));
1154
- };
1155
- const handleClick = (event) => {
1156
- const { overlayMode } = libStore.getState();
1157
- if (overlayMode === "hidden") {
1158
- return;
1159
- }
1160
- event.preventDefault();
1161
- event.stopPropagation();
1162
- event.stopImmediatePropagation();
1163
- };
1164
- const handleVisibilityChange = () => {
1165
- if (document.hidden) {
1166
- cleanupGrabbedIndicators();
1167
- hideLabel();
1168
- }
1169
- };
1170
- let scrollScheduled = false;
1171
- const handleScroll = () => {
1172
- if (scrollScheduled) return;
1173
- scrollScheduled = true;
1174
- requestAnimationFrame(() => {
1175
- scrollScheduled = false;
1176
- scheduleRender();
1073
+ const isSameAsLast = createMemo(() => {
1074
+ const current = targetElement();
1075
+ const last = lastGrabbedElement();
1076
+ return !!current && current === last;
1177
1077
  });
1178
- };
1179
- let resizeScheduled = false;
1180
- const handleResize = () => {
1181
- if (resizeScheduled) return;
1182
- resizeScheduled = true;
1183
- requestAnimationFrame(() => {
1184
- resizeScheduled = false;
1185
- scheduleRender();
1078
+ createEffect(() => {
1079
+ const current = targetElement();
1080
+ const last = lastGrabbedElement();
1081
+ if (last && current && last !== current) {
1082
+ setLastGrabbedElement(null);
1083
+ }
1186
1084
  });
1187
- };
1188
- window.addEventListener("mousemove", handleMouseMove);
1189
- window.addEventListener("mousedown", handleMouseDown, true);
1190
- window.addEventListener("click", handleClick, true);
1191
- window.addEventListener("scroll", handleScroll, true);
1192
- window.addEventListener("resize", handleResize);
1193
- document.addEventListener("visibilitychange", handleVisibilityChange);
1194
- const cleanupTrackHotkeys = trackHotkeys();
1195
- const getElementAtPosition = (x, y) => {
1196
- const elements = document.elementsFromPoint(x, y);
1197
- for (const element of elements) {
1198
- if (element.closest(`[${ATTRIBUTE_NAME}]`)) {
1199
- continue;
1085
+ const progress = createMemo(() => {
1086
+ const startTime = progressStartTime();
1087
+ progressTick();
1088
+ if (startTime === null) return 0;
1089
+ const elapsed = Date.now() - startTime;
1090
+ return Math.min(elapsed / options.keyHoldDuration, 1);
1091
+ });
1092
+ const startProgressAnimation = () => {
1093
+ setProgressStartTime(Date.now());
1094
+ setShowProgressIndicator(false);
1095
+ progressDelayTimerId = window.setTimeout(() => {
1096
+ setShowProgressIndicator(true);
1097
+ progressDelayTimerId = null;
1098
+ }, 150);
1099
+ const animateProgress = () => {
1100
+ if (progressStartTime() === null) return;
1101
+ setProgressTick((t) => t + 1);
1102
+ const currentProgress = progress();
1103
+ if (currentProgress < 1) {
1104
+ progressAnimationId = requestAnimationFrame(animateProgress);
1105
+ }
1106
+ };
1107
+ animateProgress();
1108
+ };
1109
+ const stopProgressAnimation = () => {
1110
+ if (progressAnimationId !== null) {
1111
+ cancelAnimationFrame(progressAnimationId);
1112
+ progressAnimationId = null;
1200
1113
  }
1201
- const computedStyle = window.getComputedStyle(element);
1202
- if (!isElementVisible(element, computedStyle)) {
1203
- continue;
1114
+ if (progressDelayTimerId !== null) {
1115
+ window.clearTimeout(progressDelayTimerId);
1116
+ progressDelayTimerId = null;
1204
1117
  }
1205
- return element;
1206
- }
1207
- return null;
1208
- };
1209
- const handleCopy = async (element) => {
1210
- const tagName = (element.tagName || "").toLowerCase();
1211
- const rect = element.getBoundingClientRect();
1212
- const cleanupIndicator = updateLabelToProcessing(root, rect.left, rect.top);
1213
- try {
1214
- const htmlSnippet = getHTMLSnippet(element);
1215
- await copyTextToClipboard(
1216
- `
1217
-
1218
- <referenced_element>
1219
- ${htmlSnippet}
1220
- </referenced_element>`
1221
- );
1222
- const stack = await getStack(element);
1223
- if (stack) {
1224
- const filteredStack = filterStack(stack);
1225
- if (filteredStack.length > 0) {
1226
- const serializedStack = serializeStack(filteredStack);
1227
- const fullText = `${htmlSnippet}
1228
-
1229
- Component owner stack:
1230
- ${serializedStack}`;
1231
- await copyTextToClipboard(
1232
- `
1233
-
1234
- <referenced_element>
1235
- ${fullText}
1236
- </referenced_element>`
1237
- ).catch(() => {
1238
- });
1239
- if (resolvedOptions.adapter) {
1240
- resolvedOptions.adapter.open(fullText);
1241
- }
1242
- } else if (resolvedOptions.adapter) {
1243
- resolvedOptions.adapter.open(htmlSnippet);
1244
- }
1245
- } else if (resolvedOptions.adapter) {
1246
- resolvedOptions.adapter.open(htmlSnippet);
1118
+ setProgressStartTime(null);
1119
+ setShowProgressIndicator(false);
1120
+ };
1121
+ const activateOverlay = () => {
1122
+ stopProgressAnimation();
1123
+ setIsActivated(true);
1124
+ };
1125
+ const abortController = new AbortController();
1126
+ const signal = abortController.signal;
1127
+ window.addEventListener("keydown", (event) => {
1128
+ if (event.key === "Escape" && isHoldingKeys()) {
1129
+ setIsHoldingKeys(false);
1130
+ setIsActivated(false);
1131
+ if (holdTimerId) window.clearTimeout(holdTimerId);
1132
+ stopProgressAnimation();
1133
+ return;
1247
1134
  }
1248
- cleanupIndicator(tagName);
1249
- } catch {
1250
- cleanupIndicator(tagName);
1251
- }
1252
- };
1253
- const handleRender = (state) => {
1254
- const { mouseX, mouseY, overlayMode } = state;
1255
- if (overlayMode === "hidden") {
1256
- if (selectionOverlay.isVisible()) {
1257
- selectionOverlay.hide();
1135
+ if (isKeyboardEventTriggeredByInput(event)) return;
1136
+ if (isTargetKeyCombination(event) && !isHoldingKeys()) {
1137
+ setIsHoldingKeys(true);
1138
+ startProgressAnimation();
1139
+ holdTimerId = window.setTimeout(() => {
1140
+ activateOverlay();
1141
+ options.onActivate?.();
1142
+ }, options.keyHoldDuration);
1258
1143
  }
1259
- if (!isCopying) {
1260
- hideLabel();
1144
+ }, {
1145
+ signal
1146
+ });
1147
+ window.addEventListener("keyup", (event) => {
1148
+ if (isHoldingKeys() && (!isTargetKeyCombination(event) || event.key.toLowerCase() === "c")) {
1149
+ setIsHoldingKeys(false);
1150
+ setIsActivated(false);
1151
+ if (holdTimerId) window.clearTimeout(holdTimerId);
1152
+ stopProgressAnimation();
1261
1153
  }
1262
- hoveredElement = null;
1263
- lastGrabbedElement = null;
1264
- return;
1265
- }
1266
- if (overlayMode === "copying" && hoveredElement) {
1267
- if (!isCopying) {
1268
- isCopying = true;
1269
- lastGrabbedElement = hoveredElement;
1270
- const computedStyle2 = window.getComputedStyle(hoveredElement);
1271
- const rect2 = hoveredElement.getBoundingClientRect();
1272
- createGrabbedOverlay(root, {
1273
- borderRadius: computedStyle2.borderRadius || "0px",
1274
- height: rect2.height,
1275
- transform: computedStyle2.transform || "none",
1276
- width: rect2.width,
1277
- x: rect2.left,
1278
- y: rect2.top
1154
+ }, {
1155
+ signal
1156
+ });
1157
+ window.addEventListener("mousemove", (event) => {
1158
+ setMouseX(event.clientX);
1159
+ setMouseY(event.clientY);
1160
+ }, {
1161
+ signal
1162
+ });
1163
+ window.addEventListener("mousedown", (event) => {
1164
+ if (!isOverlayActive() || isCopying()) return;
1165
+ setIsDragging(true);
1166
+ setDragStartX(event.clientX);
1167
+ setDragStartY(event.clientY);
1168
+ }, {
1169
+ signal
1170
+ });
1171
+ window.addEventListener("mouseup", (event) => {
1172
+ 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;
1177
+ 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
1279
1188
  });
1280
- void handleCopy(hoveredElement).finally(() => {
1281
- isCopying = false;
1189
+ if (elements.length > 0) {
1190
+ setIsCopying(true);
1191
+ void handleMultipleCopy(elements).finally(() => {
1192
+ setIsCopying(false);
1193
+ });
1194
+ } else {
1195
+ const fallbackElements = getElementsInMarqueeLoose({
1196
+ x: marqueeX,
1197
+ y: marqueeY,
1198
+ width: marqueeWidth,
1199
+ height: marqueeHeight
1200
+ });
1201
+ if (fallbackElements.length > 0) {
1202
+ setIsCopying(true);
1203
+ void handleMultipleCopy(fallbackElements).finally(() => {
1204
+ setIsCopying(false);
1205
+ });
1206
+ }
1207
+ }
1208
+ } else {
1209
+ const element = getElementAtPosition(event.clientX, event.clientY);
1210
+ if (!element) return;
1211
+ setIsCopying(true);
1212
+ setLastGrabbedElement(element);
1213
+ void handleCopy(element).finally(() => {
1214
+ setIsCopying(false);
1282
1215
  });
1283
- const isStillPressed = checkIsActivationHotkeyPressed();
1284
- libStore.setState((state2) => ({
1285
- ...state2,
1286
- overlayMode: isStillPressed ? "visible" : "hidden"
1287
- }));
1288
- }
1289
- return;
1290
- }
1291
- const element = getElementAtPosition(mouseX, mouseY);
1292
- if (!element) {
1293
- if (selectionOverlay.isVisible()) {
1294
- selectionOverlay.hide();
1295
- }
1296
- if (!isCopying) {
1297
- hideLabel();
1298
- }
1299
- hoveredElement = null;
1300
- return;
1301
- }
1302
- if (lastGrabbedElement && element !== lastGrabbedElement) {
1303
- lastGrabbedElement = null;
1304
- }
1305
- if (element === lastGrabbedElement) {
1306
- if (selectionOverlay.isVisible()) {
1307
- selectionOverlay.hide();
1308
1216
  }
1309
- if (!isCopying) {
1310
- hideLabel();
1311
- }
1312
- hoveredElement = element;
1313
- return;
1314
- }
1315
- const tagName = (element.tagName || "").toLowerCase();
1316
- hoveredElement = element;
1317
- const rect = element.getBoundingClientRect();
1318
- const computedStyle = window.getComputedStyle(element);
1319
- const borderRadius = computedStyle.borderRadius || "0px";
1320
- const transform = computedStyle.transform || "none";
1321
- selectionOverlay.update({
1322
- borderRadius,
1323
- height: rect.height,
1324
- transform,
1325
- width: rect.width,
1326
- x: rect.left,
1327
- y: rect.top
1217
+ }, {
1218
+ signal
1328
1219
  });
1329
- if (!selectionOverlay.isVisible()) {
1330
- selectionOverlay.show();
1331
- }
1332
- showLabel(root, rect.left, rect.top, tagName);
1333
- const isDisabled = (
1334
- // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access -- we do any because typing this will be a massive fucking headache
1335
- Boolean(element.disabled) || computedStyle.pointerEvents === "none"
1336
- );
1337
- if (isDisabled) {
1338
- const overlayElement = selectionOverlay.element;
1339
- if (overlayElement) {
1340
- overlayElement.style.pointerEvents = "auto";
1220
+ window.addEventListener("scroll", () => {
1221
+ }, {
1222
+ signal,
1223
+ capture: true
1224
+ });
1225
+ window.addEventListener("resize", () => {
1226
+ }, {
1227
+ signal
1228
+ });
1229
+ document.addEventListener("visibilitychange", () => {
1230
+ if (document.hidden) {
1231
+ setGrabbedOverlays([]);
1341
1232
  }
1342
- }
1343
- };
1344
- let renderScheduled = false;
1345
- const scheduleRender = () => {
1346
- if (renderScheduled) return;
1347
- renderScheduled = true;
1348
- requestAnimationFrame(() => {
1349
- renderScheduled = false;
1350
- handleRender(libStore.getState());
1233
+ }, {
1234
+ signal
1351
1235
  });
1352
- };
1353
- const cleanupRenderSubscription = libStore.subscribe(() => {
1354
- scheduleRender();
1236
+ onCleanup(() => {
1237
+ abortController.abort();
1238
+ if (holdTimerId) window.clearTimeout(holdTimerId);
1239
+ stopProgressAnimation();
1240
+ });
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);
1260
+ return dispose;
1355
1261
  });
1356
- const continuousRender = () => {
1357
- scheduleRender();
1358
- requestAnimationFrame(continuousRender);
1359
- };
1360
- continuousRender();
1361
- return () => {
1362
- window.removeEventListener("mousemove", handleMouseMove);
1363
- window.removeEventListener("mousedown", handleMouseDown, true);
1364
- window.removeEventListener("click", handleClick, true);
1365
- window.removeEventListener("scroll", handleScroll, true);
1366
- window.removeEventListener("resize", handleResize);
1367
- document.removeEventListener("visibilitychange", handleVisibilityChange);
1368
- cleanupTrackHotkeys();
1369
- cleanupRenderSubscription();
1370
- cleanupKeyStateChangeSubscription();
1371
- if (cleanupActivationHotkeyWatcher) {
1372
- cleanupActivationHotkeyWatcher();
1373
- }
1374
- stopProgressTracking();
1375
- cleanupGrabbedIndicators();
1376
- hideLabel();
1377
- };
1378
1262
  };
1379
- if (typeof window !== "undefined" && typeof document !== "undefined") {
1380
- const currentScript = document.currentScript;
1381
- const options = {};
1382
- if (currentScript?.dataset) {
1383
- const { adapter, enabled, hotkey, keyHoldDuration } = currentScript.dataset;
1384
- if (adapter !== void 0) {
1385
- if (adapter === "cursor") {
1386
- options.adapter = cursorAdapter;
1387
- }
1388
- }
1389
- if (enabled !== void 0) {
1390
- options.enabled = enabled === "true";
1391
- }
1392
- if (hotkey !== void 0) {
1393
- const keys = hotkey.split(",").map((key) => key.trim());
1394
- options.hotkey = keys.length === 1 ? keys[0] : keys;
1395
- }
1396
- if (keyHoldDuration !== void 0) {
1397
- const duration = Number(keyHoldDuration);
1398
- if (!Number.isNaN(duration)) {
1399
- options.keyHoldDuration = duration;
1400
- }
1401
- }
1402
- }
1403
- init(options);
1404
- }
1405
1263
 
1406
- export { cursorAdapter, init, libStore };
1264
+ // src/index.ts
1265
+ init();