react-grab 0.0.13 → 0.0.15

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.
Files changed (3) hide show
  1. package/dist/index.global.js +307 -170
  2. package/package.json +11 -12
  3. package/README.md +0 -46
@@ -63,7 +63,7 @@ var ReactGrab = (function (exports) {
63
63
  if (isKeyboardEventTriggeredByInput(event)) {
64
64
  return;
65
65
  }
66
- if (event.code === undefined) {
66
+ if (event.code === void 0) {
67
67
  return;
68
68
  }
69
69
  libStore.setState((state) => {
@@ -79,7 +79,7 @@ var ReactGrab = (function (exports) {
79
79
  });
80
80
  };
81
81
  const handleKeyUp = (event) => {
82
- if (event.code === undefined) {
82
+ if (event.code === void 0) {
83
83
  return;
84
84
  }
85
85
  libStore.setState((state) => {
@@ -129,6 +129,7 @@ var ReactGrab = (function (exports) {
129
129
  var watchKeyHeldFor = (key, duration, onHeld) => {
130
130
  let timeoutId = null;
131
131
  let unsubscribe = null;
132
+ const watchStartTime = Date.now();
132
133
  const cleanup = () => {
133
134
  if (timeoutId !== null) {
134
135
  clearTimeout(timeoutId);
@@ -153,29 +154,9 @@ var ReactGrab = (function (exports) {
153
154
  }
154
155
  return checkSingleKeyPressed(key, pressedKeys);
155
156
  };
156
- const getKeyFromTimestamps = (keyToFind, timestamps) => {
157
- if (keyToFind.length === 1) {
158
- return timestamps.get(keyToFind.toLowerCase()) || timestamps.get(keyToFind.toUpperCase());
159
- }
160
- return timestamps.get(keyToFind);
161
- };
162
- const getEarliestPressTime = (timestamps) => {
163
- const keysToInspect = Array.isArray(key) ? key : [key];
164
- let earliest;
165
- for (const keyFromCombo of keysToInspect) {
166
- const timestamp = getKeyFromTimestamps(keyFromCombo, timestamps);
167
- if (timestamp === undefined) {
168
- return undefined;
169
- }
170
- if (earliest === undefined || timestamp < earliest) {
171
- earliest = timestamp;
172
- }
173
- }
174
- return earliest;
175
- };
176
157
  const scheduleCallback = () => {
177
158
  const state = libStore.getState();
178
- const { keyPressTimestamps, pressedKeys } = state;
159
+ const { pressedKeys } = state;
179
160
  if (!checkAllKeysPressed(pressedKeys)) {
180
161
  if (timeoutId !== null) {
181
162
  clearTimeout(timeoutId);
@@ -183,15 +164,7 @@ var ReactGrab = (function (exports) {
183
164
  }
184
165
  return;
185
166
  }
186
- const earliestPressTime = getEarliestPressTime(keyPressTimestamps);
187
- if (earliestPressTime === undefined) {
188
- if (timeoutId !== null) {
189
- clearTimeout(timeoutId);
190
- timeoutId = null;
191
- }
192
- return;
193
- }
194
- const elapsed = Date.now() - earliestPressTime;
167
+ const elapsed = Date.now() - watchStartTime;
195
168
  const remaining = duration - elapsed;
196
169
  if (remaining <= 0) {
197
170
  onHeld();
@@ -216,7 +189,7 @@ var ReactGrab = (function (exports) {
216
189
  return cleanup;
217
190
  };
218
191
 
219
- // node_modules/.pnpm/bippy@0.3.31_@types+react@19.2.2_react@19.2.0/node_modules/bippy/dist/src-R2iEnVC1.js
192
+ // ../../node_modules/.pnpm/bippy@0.3.31_@types+react@19.2.2_react@19.2.0/node_modules/bippy/dist/src-R2iEnVC1.js
220
193
  var version = "0.3.31";
221
194
  var BIPPY_INSTRUMENTATION_STRING = `bippy-${version}`;
222
195
  var objectDefineProperty = Object.defineProperty;
@@ -236,7 +209,7 @@ var ReactGrab = (function (exports) {
236
209
  return "getFiberRoots" in rdtHook;
237
210
  };
238
211
  var isReactRefreshOverride = false;
239
- var injectFnStr = undefined;
212
+ var injectFnStr = void 0;
240
213
  var isReactRefresh = (rdtHook = getRDTHook()) => {
241
214
  if (isReactRefreshOverride) return true;
242
215
  if (typeof rdtHook.inject === "function") injectFnStr = rdtHook.inject.toString();
@@ -433,7 +406,7 @@ var ReactGrab = (function (exports) {
433
406
  var _fiberRoots = /* @__PURE__ */ new Set();
434
407
  safelyInstallRDTHook();
435
408
 
436
- // node_modules/.pnpm/bippy@0.3.31_@types+react@19.2.2_react@19.2.0/node_modules/bippy/dist/source-DWOhEbf2.js
409
+ // ../../node_modules/.pnpm/bippy@0.3.31_@types+react@19.2.2_react@19.2.0/node_modules/bippy/dist/source-DWOhEbf2.js
437
410
  var __create = Object.create;
438
411
  var __defProp = Object.defineProperty;
439
412
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -466,15 +439,15 @@ var ReactGrab = (function (exports) {
466
439
  function extractLocation(urlLike) {
467
440
  if (!urlLike.includes(":")) return [
468
441
  urlLike,
469
- undefined,
470
- undefined
442
+ void 0,
443
+ void 0
471
444
  ];
472
445
  const regExp = /(.+?)(?::(\d+))?(?::(\d+))?$/;
473
446
  const parts = regExp.exec(urlLike.replace(/[()]/g, ""));
474
447
  return [
475
448
  parts[1],
476
- parts[2] || undefined,
477
- parts[3] || undefined
449
+ parts[2] || void 0,
450
+ parts[3] || void 0
478
451
  ];
479
452
  }
480
453
  function applySlice(lines, options) {
@@ -490,13 +463,13 @@ var ReactGrab = (function (exports) {
490
463
  const location = sanitizedLine.match(/ (\(.+\)$)/);
491
464
  sanitizedLine = location ? sanitizedLine.replace(location[0], "") : sanitizedLine;
492
465
  const locationParts = extractLocation(location ? location[1] : sanitizedLine);
493
- const functionName = location && sanitizedLine || undefined;
494
- const fileName = ["eval", "<anonymous>"].includes(locationParts[0]) ? undefined : locationParts[0];
466
+ const functionName = location && sanitizedLine || void 0;
467
+ const fileName = ["eval", "<anonymous>"].includes(locationParts[0]) ? void 0 : locationParts[0];
495
468
  return {
496
469
  function: functionName,
497
470
  file: fileName,
498
- line: locationParts[1] ? +locationParts[1] : undefined,
499
- col: locationParts[2] ? +locationParts[2] : undefined,
471
+ line: locationParts[1] ? +locationParts[1] : void 0,
472
+ col: locationParts[2] ? +locationParts[2] : void 0,
500
473
  raw: line
501
474
  };
502
475
  });
@@ -511,13 +484,13 @@ var ReactGrab = (function (exports) {
511
484
  else {
512
485
  const functionNameRegex = /(([^\n\r"\u2028\u2029]*".[^\n\r"\u2028\u2029]*"[^\n\r@\u2028\u2029]*(?:@[^\n\r"\u2028\u2029]*"[^\n\r@\u2028\u2029]*)*(?:[\n\r\u2028\u2029][^@]*)?)?[^@]*)@/;
513
486
  const matches = line.match(functionNameRegex);
514
- const functionName = matches && matches[1] ? matches[1] : undefined;
487
+ const functionName = matches && matches[1] ? matches[1] : void 0;
515
488
  const locationParts = extractLocation(line.replace(functionNameRegex, ""));
516
489
  return {
517
490
  function: functionName,
518
491
  file: locationParts[0],
519
- line: locationParts[1] ? +locationParts[1] : undefined,
520
- col: locationParts[2] ? +locationParts[2] : undefined,
492
+ line: locationParts[1] ? +locationParts[1] : void 0,
493
+ col: locationParts[2] ? +locationParts[2] : void 0,
521
494
  raw: line
522
495
  };
523
496
  }
@@ -1219,7 +1192,7 @@ var ReactGrab = (function (exports) {
1219
1192
  let sortCache = /* @__PURE__ */ new WeakMap();
1220
1193
  exports.quickSort = function(ary, comparator, start = 0) {
1221
1194
  let doQuickSort = sortCache.get(comparator);
1222
- if (doQuickSort === undefined) {
1195
+ if (doQuickSort === void 0) {
1223
1196
  doQuickSort = cloneSort(comparator);
1224
1197
  sortCache.set(comparator, doQuickSort);
1225
1198
  }
@@ -1316,7 +1289,7 @@ var ReactGrab = (function (exports) {
1316
1289
  var index = this._findMapping(needle, this._originalMappings, "originalLine", "originalColumn", util$1.compareByOriginalPositions, binarySearch.LEAST_UPPER_BOUND);
1317
1290
  if (index >= 0) {
1318
1291
  var mapping = this._originalMappings[index];
1319
- if (aArgs.column === undefined) {
1292
+ if (aArgs.column === void 0) {
1320
1293
  var originalLine = mapping.originalLine;
1321
1294
  while (mapping && mapping.originalLine === originalLine) {
1322
1295
  mappings.push({
@@ -1754,7 +1727,7 @@ var ReactGrab = (function (exports) {
1754
1727
  var newLine = getNextLine() || "";
1755
1728
  return lineContents + newLine;
1756
1729
  function getNextLine() {
1757
- return remainingLinesIndex < remainingLines.length ? remainingLines[remainingLinesIndex++] : undefined;
1730
+ return remainingLinesIndex < remainingLines.length ? remainingLines[remainingLinesIndex++] : void 0;
1758
1731
  }
1759
1732
  };
1760
1733
  var lastGeneratedLine = 1, lastGeneratedColumn = 0;
@@ -1798,7 +1771,7 @@ var ReactGrab = (function (exports) {
1798
1771
  });
1799
1772
  return node;
1800
1773
  function addMappingWithCode(mapping, code) {
1801
- if (mapping === null || mapping.source === undefined) node.add(code);
1774
+ if (mapping === null || mapping.source === void 0) node.add(code);
1802
1775
  else {
1803
1776
  var source = aRelativePath ? util.join(aRelativePath, mapping.source) : mapping.source;
1804
1777
  node.add(new SourceNode(mapping.originalLine, mapping.originalColumn, source, code, mapping.name));
@@ -2011,7 +1984,7 @@ var ReactGrab = (function (exports) {
2011
1984
  var describeNativeComponentFrame = (fn, construct) => {
2012
1985
  if (!fn || reentry) return "";
2013
1986
  const previousPrepareStackTrace = Error.prepareStackTrace;
2014
- Error.prepareStackTrace = undefined;
1987
+ Error.prepareStackTrace = void 0;
2015
1988
  reentry = true;
2016
1989
  const previousDispatcher = getCurrentDispatcher();
2017
1990
  setCurrentDispatcher(null);
@@ -2193,7 +2166,7 @@ ${error.stack}`;
2193
2166
  match = componentPattern.exec(stackTrace);
2194
2167
  matches.push({
2195
2168
  name,
2196
- source: undefined
2169
+ source: void 0
2197
2170
  });
2198
2171
  continue;
2199
2172
  }
@@ -2206,7 +2179,7 @@ ${error.stack}`;
2206
2179
  }
2207
2180
  matches.push({
2208
2181
  name,
2209
- source: source || undefined
2182
+ source: source || void 0
2210
2183
  });
2211
2184
  match = componentPattern.exec(stackTrace);
2212
2185
  }
@@ -2469,7 +2442,6 @@ ${error.stack}`;
2469
2442
  var INDICATOR_CLAMP_PADDING_PX = 4;
2470
2443
  var INDICATOR_SUCCESS_VISIBLE_MS = 1500;
2471
2444
  var INDICATOR_FADE_MS = 200;
2472
- var INDICATOR_TOTAL_HIDE_DELAY_MS = INDICATOR_SUCCESS_VISIBLE_MS + INDICATOR_FADE_MS;
2473
2445
  var lerp = (start, end, factor) => {
2474
2446
  return start + (end - start) * factor;
2475
2447
  };
@@ -2554,6 +2526,30 @@ ${error.stack}`;
2554
2526
  }
2555
2527
  };
2556
2528
  };
2529
+ var createGrabbedOverlay = (root, selection) => {
2530
+ const element = document.createElement("div");
2531
+ element.style.position = "fixed";
2532
+ element.style.top = `${selection.y}px`;
2533
+ element.style.left = `${selection.x}px`;
2534
+ element.style.width = `${selection.width}px`;
2535
+ element.style.height = `${selection.height}px`;
2536
+ element.style.borderRadius = selection.borderRadius;
2537
+ element.style.transform = selection.transform;
2538
+ element.style.pointerEvents = "none";
2539
+ element.style.border = "1px solid rgb(210, 57, 192)";
2540
+ element.style.backgroundColor = "rgba(210, 57, 192, 0.2)";
2541
+ element.style.zIndex = "2147483646";
2542
+ element.style.boxSizing = "border-box";
2543
+ element.style.transition = "opacity 0.3s ease-out";
2544
+ element.style.opacity = "1";
2545
+ root.appendChild(element);
2546
+ requestAnimationFrame(() => {
2547
+ element.style.opacity = "0";
2548
+ });
2549
+ setTimeout(() => {
2550
+ element.remove();
2551
+ }, 300);
2552
+ };
2557
2553
  var createSpinner = () => {
2558
2554
  const spinner = document.createElement("span");
2559
2555
  spinner.style.display = "inline-block";
@@ -2599,12 +2595,12 @@ ${error.stack}`;
2599
2595
  indicator.style.whiteSpace = "nowrap";
2600
2596
  return indicator;
2601
2597
  };
2602
- var showLabel = (selectionLeftPx, selectionTopPx, tagName) => {
2598
+ var showLabel = (root, selectionLeftPx, selectionTopPx, tagName) => {
2603
2599
  let indicator = activeIndicator;
2604
2600
  let isNewIndicator = false;
2605
2601
  if (!indicator) {
2606
2602
  indicator = createIndicator();
2607
- document.body.appendChild(indicator);
2603
+ root.appendChild(indicator);
2608
2604
  activeIndicator = indicator;
2609
2605
  isNewIndicator = true;
2610
2606
  isProcessing = false;
@@ -2658,22 +2654,47 @@ ${error.stack}`;
2658
2654
  }
2659
2655
  };
2660
2656
  var isProcessing = false;
2661
- var updateLabelToProcessing = () => {
2662
- if (!activeIndicator || isProcessing) return () => {
2657
+ var activeGrabbedIndicators = /* @__PURE__ */ new Set();
2658
+ var updateLabelToProcessing = (root, selectionLeftPx, selectionTopPx) => {
2659
+ const indicator = createIndicator();
2660
+ indicator.style.zIndex = "2147483648";
2661
+ root.appendChild(indicator);
2662
+ activeGrabbedIndicators.add(indicator);
2663
+ const positionIndicator = () => {
2664
+ if (selectionLeftPx === void 0 || selectionTopPx === void 0) return;
2665
+ const indicatorRect = indicator.getBoundingClientRect();
2666
+ const viewportWidthPx = window.innerWidth;
2667
+ const viewportHeightPx = window.innerHeight;
2668
+ let indicatorLeftPx = Math.round(selectionLeftPx);
2669
+ let indicatorTopPx = Math.round(selectionTopPx) - indicatorRect.height - LABEL_OFFSET_PX;
2670
+ const CLAMPED_PADDING = INDICATOR_CLAMP_PADDING_PX;
2671
+ const minLeft = VIEWPORT_MARGIN_PX;
2672
+ const minTop = VIEWPORT_MARGIN_PX;
2673
+ const maxLeft = viewportWidthPx - indicatorRect.width - VIEWPORT_MARGIN_PX;
2674
+ const maxTop = viewportHeightPx - indicatorRect.height - VIEWPORT_MARGIN_PX;
2675
+ const willClampLeft = indicatorLeftPx < minLeft;
2676
+ const willClampTop = indicatorTopPx < minTop;
2677
+ const isClamped = willClampLeft || willClampTop;
2678
+ indicatorLeftPx = Math.max(minLeft, Math.min(indicatorLeftPx, maxLeft));
2679
+ indicatorTopPx = Math.max(minTop, Math.min(indicatorTopPx, maxTop));
2680
+ if (isClamped) {
2681
+ indicatorLeftPx += CLAMPED_PADDING;
2682
+ indicatorTopPx += CLAMPED_PADDING;
2683
+ }
2684
+ indicator.style.left = `${indicatorLeftPx}px`;
2685
+ indicator.style.top = `${indicatorTopPx}px`;
2686
+ indicator.style.right = "auto";
2663
2687
  };
2664
- isProcessing = true;
2665
- const indicator = activeIndicator;
2666
- indicator.innerHTML = "";
2667
2688
  const loadingSpinner = createSpinner();
2668
2689
  const labelText = document.createElement("span");
2669
2690
  labelText.textContent = "Grabbing\u2026";
2670
2691
  indicator.appendChild(loadingSpinner);
2671
2692
  indicator.appendChild(labelText);
2693
+ positionIndicator();
2694
+ requestAnimationFrame(() => {
2695
+ indicator.style.opacity = "1";
2696
+ });
2672
2697
  return (tagName) => {
2673
- if (!activeIndicator) {
2674
- isProcessing = false;
2675
- return;
2676
- }
2677
2698
  indicator.textContent = "";
2678
2699
  const checkmarkIcon = document.createElement("span");
2679
2700
  checkmarkIcon.textContent = "\u2713";
@@ -2689,14 +2710,14 @@ ${error.stack}`;
2689
2710
  newLabelText.appendChild(tagNameMonospace);
2690
2711
  indicator.appendChild(checkmarkIcon);
2691
2712
  indicator.appendChild(newLabelText);
2713
+ requestAnimationFrame(() => {
2714
+ positionIndicator();
2715
+ });
2692
2716
  setTimeout(() => {
2693
2717
  indicator.style.opacity = "0";
2694
2718
  setTimeout(() => {
2695
2719
  indicator.remove();
2696
- if (activeIndicator === indicator) {
2697
- activeIndicator = null;
2698
- }
2699
- isProcessing = false;
2720
+ activeGrabbedIndicators.delete(indicator);
2700
2721
  }, INDICATOR_FADE_MS);
2701
2722
  }, INDICATOR_SUCCESS_VISIBLE_MS);
2702
2723
  };
@@ -2708,6 +2729,88 @@ ${error.stack}`;
2708
2729
  }
2709
2730
  isProcessing = false;
2710
2731
  };
2732
+ var cleanupGrabbedIndicators = () => {
2733
+ for (const indicator of activeGrabbedIndicators) {
2734
+ indicator.remove();
2735
+ }
2736
+ activeGrabbedIndicators.clear();
2737
+ };
2738
+ var activeProgressIndicator = null;
2739
+ var createProgressIndicatorElement = () => {
2740
+ const container = document.createElement("div");
2741
+ container.style.position = "fixed";
2742
+ container.style.zIndex = "2147483647";
2743
+ container.style.pointerEvents = "none";
2744
+ container.style.opacity = "0";
2745
+ container.style.transition = "opacity 0.1s ease-in-out";
2746
+ const progressBarContainer = document.createElement("div");
2747
+ progressBarContainer.style.width = "32px";
2748
+ progressBarContainer.style.height = "2px";
2749
+ progressBarContainer.style.backgroundColor = "rgba(178, 28, 142, 0.2)";
2750
+ progressBarContainer.style.borderRadius = "1px";
2751
+ progressBarContainer.style.overflow = "hidden";
2752
+ progressBarContainer.style.position = "relative";
2753
+ const progressBarFill = document.createElement("div");
2754
+ progressBarFill.style.width = "0%";
2755
+ progressBarFill.style.height = "100%";
2756
+ progressBarFill.style.backgroundColor = "#b21c8e";
2757
+ progressBarFill.style.borderRadius = "1px";
2758
+ progressBarFill.style.transition = "width 0.05s linear";
2759
+ progressBarFill.setAttribute("data-progress-fill", "true");
2760
+ progressBarContainer.appendChild(progressBarFill);
2761
+ container.appendChild(progressBarContainer);
2762
+ return container;
2763
+ };
2764
+ var showProgressIndicator = (root, progress, mouseX, mouseY) => {
2765
+ if (!activeProgressIndicator) {
2766
+ activeProgressIndicator = createProgressIndicatorElement();
2767
+ root.appendChild(activeProgressIndicator);
2768
+ requestAnimationFrame(() => {
2769
+ if (activeProgressIndicator) {
2770
+ activeProgressIndicator.style.opacity = "1";
2771
+ }
2772
+ });
2773
+ }
2774
+ const indicator = activeProgressIndicator;
2775
+ const indicatorRect = indicator.getBoundingClientRect();
2776
+ const viewportWidth = window.innerWidth;
2777
+ const viewportHeight = window.innerHeight;
2778
+ const CURSOR_OFFSET = 14;
2779
+ const VIEWPORT_MARGIN = 8;
2780
+ let indicatorLeft = mouseX - indicatorRect.width / 2;
2781
+ let indicatorTop = mouseY + CURSOR_OFFSET;
2782
+ if (indicatorTop + indicatorRect.height + VIEWPORT_MARGIN > viewportHeight) {
2783
+ indicatorTop = mouseY - indicatorRect.height - CURSOR_OFFSET;
2784
+ }
2785
+ indicatorTop = Math.max(
2786
+ VIEWPORT_MARGIN,
2787
+ Math.min(indicatorTop, viewportHeight - indicatorRect.height - VIEWPORT_MARGIN)
2788
+ );
2789
+ indicatorLeft = Math.max(
2790
+ VIEWPORT_MARGIN,
2791
+ Math.min(indicatorLeft, viewportWidth - indicatorRect.width - VIEWPORT_MARGIN)
2792
+ );
2793
+ indicator.style.top = `${indicatorTop}px`;
2794
+ indicator.style.left = `${indicatorLeft}px`;
2795
+ const progressFill = indicator.querySelector(
2796
+ "[data-progress-fill]"
2797
+ );
2798
+ if (progressFill) {
2799
+ const percentage = Math.min(100, Math.max(0, progress * 100));
2800
+ progressFill.style.width = `${percentage}%`;
2801
+ }
2802
+ };
2803
+ var hideProgressIndicator = () => {
2804
+ if (activeProgressIndicator) {
2805
+ activeProgressIndicator.style.opacity = "0";
2806
+ setTimeout(() => {
2807
+ if (activeProgressIndicator) {
2808
+ activeProgressIndicator.remove();
2809
+ activeProgressIndicator = null;
2810
+ }
2811
+ }, 100);
2812
+ }
2813
+ };
2711
2814
 
2712
2815
  // src/utils/copy-text.ts
2713
2816
  var IS_NAVIGATOR_CLIPBOARD_AVAILABLE = typeof window !== "undefined" && window.navigator.clipboard && window.isSecureContext;
@@ -2773,19 +2876,6 @@ ${error.stack}`;
2773
2876
  return root;
2774
2877
  };
2775
2878
 
2776
- // src/utils/schedule-run-when-idle.ts
2777
- var scheduleRunWhenIdle = (callback) => {
2778
- if ("scheduler" in globalThis) {
2779
- return globalThis.scheduler.postTask(callback, {
2780
- priority: "background"
2781
- });
2782
- }
2783
- if ("requestIdleCallback" in window) {
2784
- return requestIdleCallback(callback);
2785
- }
2786
- return setTimeout(callback, 0);
2787
- };
2788
-
2789
2879
  // src/utils/store.ts
2790
2880
  var createStore = (initializer) => {
2791
2881
  const subscriberMap = /* @__PURE__ */ new Map();
@@ -2857,29 +2947,7 @@ ${error.stack}`;
2857
2947
  return store;
2858
2948
  };
2859
2949
 
2860
- // src/utils/throttle.ts
2861
- var throttle = (fn, delay) => {
2862
- let timeout = null;
2863
- const throttled = (...args) => {
2864
- if (timeout) {
2865
- return;
2866
- }
2867
- timeout = window.setTimeout(() => {
2868
- fn(...args);
2869
- timeout = null;
2870
- }, delay);
2871
- };
2872
- throttled.cancel = () => {
2873
- if (timeout) {
2874
- window.clearTimeout(timeout);
2875
- timeout = null;
2876
- }
2877
- };
2878
- return throttled;
2879
- };
2880
-
2881
2950
  // src/index.ts
2882
- var THROTTLE_DELAY = 16;
2883
2951
  var libStore = createStore(() => ({
2884
2952
  keyPressTimestamps: /* @__PURE__ */ new Map(),
2885
2953
  mouseX: -1e3,
@@ -2892,7 +2960,6 @@ ${error.stack}`;
2892
2960
  return;
2893
2961
  }
2894
2962
  const resolvedOptions = {
2895
- enabled: true,
2896
2963
  hotkey: ["Meta", "C"],
2897
2964
  keyHoldDuration: 500,
2898
2965
  ...options
@@ -2900,7 +2967,10 @@ ${error.stack}`;
2900
2967
  const root = mountRoot();
2901
2968
  const selectionOverlay = createSelectionOverlay(root);
2902
2969
  let hoveredElement = null;
2970
+ let lastGrabbedElement = null;
2903
2971
  let isCopying = false;
2972
+ let progressAnimationFrame = null;
2973
+ let progressStartTime = null;
2904
2974
  const checkIsActivationHotkeyPressed = () => {
2905
2975
  if (Array.isArray(resolvedOptions.hotkey)) {
2906
2976
  for (const key of resolvedOptions.hotkey) {
@@ -2912,6 +2982,31 @@ ${error.stack}`;
2912
2982
  }
2913
2983
  return isKeyPressed(resolvedOptions.hotkey);
2914
2984
  };
2985
+ const updateProgressIndicator = () => {
2986
+ if (progressStartTime === null) return;
2987
+ const elapsed = Date.now() - progressStartTime;
2988
+ const progress = Math.min(1, elapsed / resolvedOptions.keyHoldDuration);
2989
+ const { mouseX, mouseY } = libStore.getState();
2990
+ showProgressIndicator(root, progress, mouseX, mouseY);
2991
+ if (progress < 1) {
2992
+ progressAnimationFrame = requestAnimationFrame(updateProgressIndicator);
2993
+ }
2994
+ };
2995
+ const startProgressTracking = () => {
2996
+ if (progressAnimationFrame !== null) return;
2997
+ progressStartTime = Date.now();
2998
+ const { mouseX, mouseY } = libStore.getState();
2999
+ showProgressIndicator(root, 0, mouseX, mouseY);
3000
+ progressAnimationFrame = requestAnimationFrame(updateProgressIndicator);
3001
+ };
3002
+ const stopProgressTracking = () => {
3003
+ if (progressAnimationFrame !== null) {
3004
+ cancelAnimationFrame(progressAnimationFrame);
3005
+ progressAnimationFrame = null;
3006
+ }
3007
+ progressStartTime = null;
3008
+ hideProgressIndicator();
3009
+ };
2915
3010
  let cleanupActivationHotkeyWatcher = null;
2916
3011
  const handleKeyStateChange = (pressedKeys) => {
2917
3012
  const { overlayMode } = libStore.getState();
@@ -2946,6 +3041,7 @@ ${error.stack}`;
2946
3041
  cleanupActivationHotkeyWatcher();
2947
3042
  cleanupActivationHotkeyWatcher = null;
2948
3043
  }
3044
+ stopProgressTracking();
2949
3045
  return;
2950
3046
  }
2951
3047
  const isActivationHotkeyPressed = checkIsActivationHotkeyPressed();
@@ -2960,9 +3056,11 @@ ${error.stack}`;
2960
3056
  overlayMode: "hidden"
2961
3057
  }));
2962
3058
  }
3059
+ stopProgressTracking();
2963
3060
  return;
2964
3061
  }
2965
3062
  if (overlayMode === "hidden" && !cleanupActivationHotkeyWatcher) {
3063
+ startProgressTracking();
2966
3064
  cleanupActivationHotkeyWatcher = watchKeyHeldFor(
2967
3065
  resolvedOptions.hotkey,
2968
3066
  resolvedOptions.keyHoldDuration,
@@ -2971,6 +3069,7 @@ ${error.stack}`;
2971
3069
  ...state,
2972
3070
  overlayMode: "visible"
2973
3071
  }));
3072
+ stopProgressTracking();
2974
3073
  cleanupActivationHotkeyWatcher = null;
2975
3074
  }
2976
3075
  );
@@ -2980,13 +3079,23 @@ ${error.stack}`;
2980
3079
  handleKeyStateChange,
2981
3080
  (state) => state.pressedKeys
2982
3081
  );
2983
- const handleMouseMove = throttle((event) => {
2984
- libStore.setState((state) => ({
2985
- ...state,
2986
- mouseX: event.clientX,
2987
- mouseY: event.clientY
2988
- }));
2989
- }, THROTTLE_DELAY);
3082
+ let mouseMoveScheduled = false;
3083
+ let pendingMouseX = -1e3;
3084
+ let pendingMouseY = -1e3;
3085
+ const handleMouseMove = (event) => {
3086
+ pendingMouseX = event.clientX;
3087
+ pendingMouseY = event.clientY;
3088
+ if (mouseMoveScheduled) return;
3089
+ mouseMoveScheduled = true;
3090
+ requestAnimationFrame(() => {
3091
+ mouseMoveScheduled = false;
3092
+ libStore.setState((state) => ({
3093
+ ...state,
3094
+ mouseX: pendingMouseX,
3095
+ mouseY: pendingMouseY
3096
+ }));
3097
+ });
3098
+ };
2990
3099
  const handleMouseDown = (event) => {
2991
3100
  if (event.button !== 0) {
2992
3101
  return;
@@ -3003,8 +3112,15 @@ ${error.stack}`;
3003
3112
  overlayMode: "copying"
3004
3113
  }));
3005
3114
  };
3115
+ const handleVisibilityChange = () => {
3116
+ if (document.hidden) {
3117
+ cleanupGrabbedIndicators();
3118
+ hideLabel();
3119
+ }
3120
+ };
3006
3121
  window.addEventListener("mousemove", handleMouseMove);
3007
3122
  window.addEventListener("mousedown", handleMouseDown);
3123
+ document.addEventListener("visibilitychange", handleVisibilityChange);
3008
3124
  const cleanupTrackHotkeys = trackHotkeys();
3009
3125
  const getElementAtPosition = (x, y) => {
3010
3126
  const elements = document.elementsFromPoint(x, y);
@@ -3021,70 +3137,75 @@ ${error.stack}`;
3021
3137
  return null;
3022
3138
  };
3023
3139
  const handleCopy = async (element) => {
3024
- const cleanupIndicator = updateLabelToProcessing();
3140
+ const tagName = (element.tagName || "").toLowerCase();
3141
+ const rect = element.getBoundingClientRect();
3142
+ const cleanupIndicator = updateLabelToProcessing(root, rect.left, rect.top);
3025
3143
  try {
3026
- const stack = await getStack(element);
3027
3144
  const htmlSnippet = getHTMLSnippet(element);
3028
- let text = htmlSnippet;
3145
+ await copyTextToClipboard(
3146
+ `
3147
+
3148
+ <referenced_element>
3149
+ ${htmlSnippet}
3150
+ </referenced_element>`
3151
+ );
3152
+ cleanupIndicator(tagName);
3153
+ const stack = await getStack(element);
3029
3154
  if (stack) {
3030
3155
  const filteredStack = filterStack(stack);
3031
3156
  const serializedStack = serializeStack(filteredStack);
3032
- text = `${htmlSnippet}
3157
+ const fullText = `${htmlSnippet}
3033
3158
 
3034
3159
  Component owner stack:
3035
3160
  ${serializedStack}`;
3036
- }
3037
- await copyTextToClipboard(
3038
- `
3161
+ await copyTextToClipboard(
3162
+ `
3039
3163
 
3040
3164
  <referenced_element>
3041
- ${text}
3165
+ ${fullText}
3042
3166
  </referenced_element>`
3043
- );
3044
- const tagName = (element.tagName || "").toLowerCase();
3045
- cleanupIndicator(tagName);
3167
+ ).catch(() => {
3168
+ });
3169
+ }
3046
3170
  } catch {
3047
- cleanupIndicator();
3171
+ cleanupIndicator(tagName);
3048
3172
  }
3049
3173
  };
3050
- const handleRender = throttle((state) => {
3174
+ const handleRender = (state) => {
3051
3175
  const { mouseX, mouseY, overlayMode } = state;
3052
3176
  if (overlayMode === "hidden") {
3053
3177
  if (selectionOverlay.isVisible()) {
3054
3178
  selectionOverlay.hide();
3055
- if (!isCopying) {
3056
- hideLabel();
3057
- }
3058
- hoveredElement = null;
3059
3179
  }
3180
+ if (!isCopying) {
3181
+ hideLabel();
3182
+ }
3183
+ hoveredElement = null;
3184
+ lastGrabbedElement = null;
3060
3185
  return;
3061
3186
  }
3062
3187
  if (overlayMode === "copying" && hoveredElement) {
3063
- const computedStyle2 = window.getComputedStyle(hoveredElement);
3064
- const rect2 = hoveredElement.getBoundingClientRect();
3065
- selectionOverlay.update({
3066
- borderRadius: computedStyle2.borderRadius || "0px",
3067
- height: rect2.height,
3068
- transform: computedStyle2.transform || "none",
3069
- width: rect2.width,
3070
- x: rect2.left,
3071
- y: rect2.top
3072
- });
3073
- if (!selectionOverlay.isVisible()) {
3074
- selectionOverlay.show();
3075
- }
3076
3188
  if (!isCopying) {
3077
3189
  isCopying = true;
3190
+ lastGrabbedElement = hoveredElement;
3191
+ const computedStyle2 = window.getComputedStyle(hoveredElement);
3192
+ const rect2 = hoveredElement.getBoundingClientRect();
3193
+ createGrabbedOverlay(root, {
3194
+ borderRadius: computedStyle2.borderRadius || "0px",
3195
+ height: rect2.height,
3196
+ transform: computedStyle2.transform || "none",
3197
+ width: rect2.width,
3198
+ x: rect2.left,
3199
+ y: rect2.top
3200
+ });
3078
3201
  void handleCopy(hoveredElement).finally(() => {
3079
- libStore.setState((state2) => ({
3080
- ...state2,
3081
- overlayMode: "hidden"
3082
- }));
3083
- selectionOverlay.hide();
3084
- window.setTimeout(() => {
3085
- isCopying = false;
3086
- }, INDICATOR_TOTAL_HIDE_DELAY_MS);
3202
+ isCopying = false;
3087
3203
  });
3204
+ const isStillPressed = checkIsActivationHotkeyPressed();
3205
+ libStore.setState((state2) => ({
3206
+ ...state2,
3207
+ overlayMode: isStillPressed ? "visible" : "hidden"
3208
+ }));
3088
3209
  }
3089
3210
  return;
3090
3211
  }
@@ -3092,13 +3213,26 @@ ${text}
3092
3213
  if (!element) {
3093
3214
  if (selectionOverlay.isVisible()) {
3094
3215
  selectionOverlay.hide();
3095
- if (!isCopying) {
3096
- hideLabel();
3097
- }
3216
+ }
3217
+ if (!isCopying) {
3218
+ hideLabel();
3098
3219
  }
3099
3220
  hoveredElement = null;
3100
3221
  return;
3101
3222
  }
3223
+ if (lastGrabbedElement && element !== lastGrabbedElement) {
3224
+ lastGrabbedElement = null;
3225
+ }
3226
+ if (element === lastGrabbedElement) {
3227
+ if (selectionOverlay.isVisible()) {
3228
+ selectionOverlay.hide();
3229
+ }
3230
+ if (!isCopying) {
3231
+ hideLabel();
3232
+ }
3233
+ hoveredElement = element;
3234
+ return;
3235
+ }
3102
3236
  const tagName = (element.tagName || "").toLowerCase();
3103
3237
  hoveredElement = element;
3104
3238
  const rect = element.getBoundingClientRect();
@@ -3116,35 +3250,38 @@ ${text}
3116
3250
  if (!selectionOverlay.isVisible()) {
3117
3251
  selectionOverlay.show();
3118
3252
  }
3119
- showLabel(rect.left, rect.top, tagName);
3120
- }, 10);
3121
- const cleanupRenderSubscription = libStore.subscribe((state) => {
3122
- scheduleRunWhenIdle(() => {
3123
- handleRender(state);
3253
+ showLabel(root, rect.left, rect.top, tagName);
3254
+ };
3255
+ let renderScheduled = false;
3256
+ const scheduleRender = () => {
3257
+ if (renderScheduled) return;
3258
+ renderScheduled = true;
3259
+ requestAnimationFrame(() => {
3260
+ renderScheduled = false;
3261
+ handleRender(libStore.getState());
3124
3262
  });
3263
+ };
3264
+ const cleanupRenderSubscription = libStore.subscribe(() => {
3265
+ scheduleRender();
3125
3266
  });
3126
- let timeout = null;
3127
- const render = () => {
3128
- timeout = window.setTimeout(() => {
3129
- scheduleRunWhenIdle(() => {
3130
- handleRender(libStore.getState());
3131
- render();
3132
- });
3133
- }, 100);
3267
+ const continuousRender = () => {
3268
+ scheduleRender();
3269
+ requestAnimationFrame(continuousRender);
3134
3270
  };
3135
- render();
3271
+ continuousRender();
3136
3272
  return () => {
3137
3273
  window.removeEventListener("mousemove", handleMouseMove);
3138
3274
  window.removeEventListener("mousedown", handleMouseDown);
3275
+ document.removeEventListener("visibilitychange", handleVisibilityChange);
3139
3276
  cleanupTrackHotkeys();
3140
3277
  cleanupRenderSubscription();
3141
3278
  cleanupKeyStateChangeSubscription();
3142
- if (timeout) {
3143
- window.clearTimeout(timeout);
3144
- }
3145
3279
  if (cleanupActivationHotkeyWatcher) {
3146
3280
  cleanupActivationHotkeyWatcher();
3147
3281
  }
3282
+ stopProgressTracking();
3283
+ cleanupGrabbedIndicators();
3284
+ hideLabel();
3148
3285
  };
3149
3286
  };
3150
3287
  if (typeof window !== "undefined" && typeof document !== "undefined") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-grab",
3
- "version": "0.0.13",
3
+ "version": "0.0.15",
4
4
  "description": "",
5
5
  "keywords": [],
6
6
  "homepage": "https://github.com/aidenybai/react-grab#readme",
@@ -45,16 +45,6 @@
45
45
  "README.md",
46
46
  "LICENSE"
47
47
  ],
48
- "scripts": {
49
- "build": "tsup",
50
- "dev": "tsup --watch --ignore-watch dist",
51
- "lint": "eslint src/**/*.ts",
52
- "lint:fix": "eslint src/**/*.ts --fix",
53
- "format": "prettier --write .",
54
- "check": "eslint src/**/*.ts && prettier --check .",
55
- "publint": "publint",
56
- "prepublishOnly": "pnpm build"
57
- },
58
48
  "devDependencies": {
59
49
  "eslint": "^9.37.0",
60
50
  "eslint-plugin-perfectionist": "^4.15.1",
@@ -68,5 +58,14 @@
68
58
  },
69
59
  "dependencies": {
70
60
  "bippy": "^0.3.31"
61
+ },
62
+ "scripts": {
63
+ "build": "tsup",
64
+ "dev": "tsup --watch --ignore-watch dist",
65
+ "lint": "eslint src/**/*.ts",
66
+ "lint:fix": "eslint src/**/*.ts --fix",
67
+ "format": "prettier --write .",
68
+ "check": "eslint src/**/*.ts && prettier --check .",
69
+ "publint": "publint"
71
70
  }
72
- }
71
+ }
package/README.md DELETED
@@ -1,46 +0,0 @@
1
- # react-grab
2
-
3
- Inspect React components and copy their source file paths to clipboard.
4
-
5
- ## Install
6
-
7
- ```bash
8
- npm install react-grab
9
- # or
10
- pnpm add react-grab
11
- # or
12
- yarn add react-grab
13
- ```
14
-
15
- ## Usage
16
-
17
- ### Next.js (App Router)
18
-
19
- Add to your `app/layout.tsx`:
20
-
21
- ```jsx
22
- import Script from "next/script";
23
-
24
- export default function RootLayout({ children }) {
25
- return (
26
- <html>
27
- <head>
28
- {process.env.NODE_ENV === "development" && (
29
- <Script
30
- src="//unpkg.com/react-grab@0.0.7/dist/index.global.js"
31
- crossOrigin="anonymous"
32
- strategy="beforeInteractive"
33
- />
34
- )}
35
- </head>
36
- <body>{children}</body>
37
- </html>
38
- );
39
- }
40
- ```
41
-
42
- ### How it works
43
-
44
- 1. Hold **Cmd** (Mac) for ~1 second to activate
45
- 2. Hover over any element on the page
46
- 3. Click to copy component stack trace and HTML to clipboard