react-grab 0.0.12 → 0.0.14

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.d.ts CHANGED
@@ -14,7 +14,7 @@ interface Options {
14
14
  /**
15
15
  * hotkey to trigger the overlay
16
16
  *
17
- * default: "Meta"
17
+ * default: ["Meta", "C"]
18
18
  */
19
19
  hotkey?: Hotkey | Hotkey[];
20
20
  /**
@@ -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) => {
@@ -147,7 +147,9 @@ var ReactGrab = (function (exports) {
147
147
  };
148
148
  const checkAllKeysPressed = (pressedKeys) => {
149
149
  if (Array.isArray(key)) {
150
- return key.every((keyFromCombo) => checkSingleKeyPressed(keyFromCombo, pressedKeys));
150
+ return key.every(
151
+ (keyFromCombo) => checkSingleKeyPressed(keyFromCombo, pressedKeys)
152
+ );
151
153
  }
152
154
  return checkSingleKeyPressed(key, pressedKeys);
153
155
  };
@@ -162,10 +164,10 @@ var ReactGrab = (function (exports) {
162
164
  let earliest;
163
165
  for (const keyFromCombo of keysToInspect) {
164
166
  const timestamp = getKeyFromTimestamps(keyFromCombo, timestamps);
165
- if (timestamp === undefined) {
166
- return undefined;
167
+ if (timestamp === void 0) {
168
+ return void 0;
167
169
  }
168
- if (earliest === undefined || timestamp < earliest) {
170
+ if (earliest === void 0 || timestamp < earliest) {
169
171
  earliest = timestamp;
170
172
  }
171
173
  }
@@ -182,7 +184,7 @@ var ReactGrab = (function (exports) {
182
184
  return;
183
185
  }
184
186
  const earliestPressTime = getEarliestPressTime(keyPressTimestamps);
185
- if (earliestPressTime === undefined) {
187
+ if (earliestPressTime === void 0) {
186
188
  if (timeoutId !== null) {
187
189
  clearTimeout(timeoutId);
188
190
  timeoutId = null;
@@ -214,237 +216,7 @@ var ReactGrab = (function (exports) {
214
216
  return cleanup;
215
217
  };
216
218
 
217
- // src/overlay.ts
218
- var VIEWPORT_MARGIN_PX = 8;
219
- var LABEL_OFFSET_PX = 6;
220
- var lerp = (start, end, factor) => {
221
- return start + (end - start) * factor;
222
- };
223
- var SELECTION_LERP_FACTOR = 0.95;
224
- var createSelectionElement = ({
225
- borderRadius,
226
- height,
227
- transform,
228
- width,
229
- x,
230
- y
231
- }) => {
232
- const overlay = document.createElement("div");
233
- overlay.style.position = "fixed";
234
- overlay.style.top = `${y}px`;
235
- overlay.style.left = `${x}px`;
236
- overlay.style.width = `${width}px`;
237
- overlay.style.height = `${height}px`;
238
- overlay.style.borderRadius = borderRadius;
239
- overlay.style.transform = transform;
240
- overlay.style.pointerEvents = "none";
241
- overlay.style.border = "1px solid rgb(210, 57, 192)";
242
- overlay.style.backgroundColor = "rgba(210, 57, 192, 0.2)";
243
- overlay.style.zIndex = "2147483646";
244
- overlay.style.boxSizing = "border-box";
245
- overlay.style.display = "none";
246
- return overlay;
247
- };
248
- var updateSelectionElement = (element, { borderRadius, height, transform, width, x, y }) => {
249
- const currentTop = parseFloat(element.style.top) || 0;
250
- const currentLeft = parseFloat(element.style.left) || 0;
251
- const currentWidth = parseFloat(element.style.width) || 0;
252
- const currentHeight = parseFloat(element.style.height) || 0;
253
- const topValue = `${lerp(currentTop, y, SELECTION_LERP_FACTOR)}px`;
254
- const leftValue = `${lerp(currentLeft, x, SELECTION_LERP_FACTOR)}px`;
255
- const widthValue = `${lerp(currentWidth, width, SELECTION_LERP_FACTOR)}px`;
256
- const heightValue = `${lerp(currentHeight, height, SELECTION_LERP_FACTOR)}px`;
257
- if (element.style.top !== topValue) {
258
- element.style.top = topValue;
259
- }
260
- if (element.style.left !== leftValue) {
261
- element.style.left = leftValue;
262
- }
263
- if (element.style.width !== widthValue) {
264
- element.style.width = widthValue;
265
- }
266
- if (element.style.height !== heightValue) {
267
- element.style.height = heightValue;
268
- }
269
- if (element.style.borderRadius !== borderRadius) {
270
- element.style.borderRadius = borderRadius;
271
- }
272
- if (element.style.transform !== transform) {
273
- element.style.transform = transform;
274
- }
275
- };
276
- var createSelectionOverlay = (root) => {
277
- const element = createSelectionElement({
278
- borderRadius: "0px",
279
- height: 0,
280
- transform: "none",
281
- width: 0,
282
- x: -1e3,
283
- y: -1e3
284
- });
285
- root.appendChild(element);
286
- let visible = false;
287
- return {
288
- hide: () => {
289
- visible = false;
290
- element.style.display = "none";
291
- element.style.pointerEvents = "none";
292
- },
293
- isVisible: () => visible,
294
- show: () => {
295
- visible = true;
296
- element.style.display = "block";
297
- element.style.pointerEvents = "auto";
298
- },
299
- update: (selection) => {
300
- updateSelectionElement(element, selection);
301
- }
302
- };
303
- };
304
- var createSpinner = () => {
305
- const spinner = document.createElement("span");
306
- spinner.style.display = "inline-block";
307
- spinner.style.width = "8px";
308
- spinner.style.height = "8px";
309
- spinner.style.border = "1.5px solid rgb(210, 57, 192)";
310
- spinner.style.borderTopColor = "transparent";
311
- spinner.style.borderRadius = "50%";
312
- spinner.style.marginRight = "4px";
313
- spinner.style.verticalAlign = "middle";
314
- spinner.animate(
315
- [{ transform: "rotate(0deg)" }, { transform: "rotate(360deg)" }],
316
- {
317
- duration: 600,
318
- easing: "linear",
319
- iterations: Infinity
320
- }
321
- );
322
- return spinner;
323
- };
324
- var activeIndicator = null;
325
- var createIndicator = () => {
326
- const indicator = document.createElement("div");
327
- indicator.style.position = "fixed";
328
- indicator.style.top = "calc(8px + env(safe-area-inset-top))";
329
- indicator.style.padding = "2px 6px";
330
- indicator.style.backgroundColor = "#fde7f7";
331
- indicator.style.color = "#b21c8e";
332
- indicator.style.border = "1px solid #f7c5ec";
333
- indicator.style.borderRadius = "4px";
334
- indicator.style.fontSize = "11px";
335
- indicator.style.fontWeight = "500";
336
- indicator.style.fontFamily = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif";
337
- indicator.style.zIndex = "2147483647";
338
- indicator.style.pointerEvents = "none";
339
- indicator.style.opacity = "0";
340
- indicator.style.transition = "opacity 0.2s ease-in-out";
341
- indicator.style.display = "flex";
342
- indicator.style.alignItems = "center";
343
- indicator.style.maxWidth = "calc(100vw - (16px + env(safe-area-inset-left) + env(safe-area-inset-right)))";
344
- indicator.style.overflow = "hidden";
345
- indicator.style.textOverflow = "ellipsis";
346
- indicator.style.whiteSpace = "nowrap";
347
- return indicator;
348
- };
349
- var showCopyIndicator = (selectionLeftPx, selectionTopPx) => {
350
- if (activeIndicator) {
351
- activeIndicator.remove();
352
- activeIndicator = null;
353
- }
354
- const indicator = createIndicator();
355
- const loadingSpinner = createSpinner();
356
- const labelText = document.createElement("span");
357
- labelText.textContent = "Grabbing\u2026";
358
- indicator.appendChild(loadingSpinner);
359
- indicator.appendChild(labelText);
360
- document.body.appendChild(indicator);
361
- activeIndicator = indicator;
362
- const indicatorRect = indicator.getBoundingClientRect();
363
- const viewportWidthPx = window.innerWidth;
364
- const viewportHeightPx = window.innerHeight;
365
- let indicatorLeftPx = Math.round(selectionLeftPx);
366
- let indicatorTopPx = Math.round(selectionTopPx) - indicatorRect.height - LABEL_OFFSET_PX;
367
- indicatorLeftPx = Math.max(
368
- VIEWPORT_MARGIN_PX,
369
- Math.min(
370
- indicatorLeftPx,
371
- viewportWidthPx - indicatorRect.width - VIEWPORT_MARGIN_PX
372
- )
373
- );
374
- indicatorTopPx = Math.max(
375
- VIEWPORT_MARGIN_PX,
376
- Math.min(
377
- indicatorTopPx,
378
- viewportHeightPx - indicatorRect.height - VIEWPORT_MARGIN_PX
379
- )
380
- );
381
- indicator.style.left = `${indicatorLeftPx}px`;
382
- indicator.style.top = `${indicatorTopPx}px`;
383
- indicator.style.right = "auto";
384
- requestAnimationFrame(() => {
385
- indicator.style.opacity = "1";
386
- });
387
- return (tagName) => {
388
- loadingSpinner.remove();
389
- const checkmarkIcon = document.createElement("span");
390
- checkmarkIcon.textContent = "\u2713";
391
- checkmarkIcon.style.display = "inline-block";
392
- checkmarkIcon.style.marginRight = "4px";
393
- checkmarkIcon.style.fontWeight = "600";
394
- indicator.insertBefore(checkmarkIcon, labelText);
395
- const tagNameMonospace = document.createElement("span");
396
- tagNameMonospace.textContent = tagName ? `<${tagName}>` : "<element>";
397
- tagNameMonospace.style.fontFamily = "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace";
398
- tagNameMonospace.style.fontVariantNumeric = "tabular-nums";
399
- labelText.replaceChildren(
400
- document.createTextNode("Grabbed "),
401
- tagNameMonospace
402
- );
403
- setTimeout(() => {
404
- indicator.style.opacity = "0";
405
- setTimeout(() => {
406
- indicator.remove();
407
- if (activeIndicator === indicator) {
408
- activeIndicator = null;
409
- }
410
- }, 200);
411
- }, 1500);
412
- };
413
- };
414
-
415
- // src/utils/copy-text.ts
416
- var IS_NAVIGATOR_CLIPBOARD_AVAILABLE = typeof window !== "undefined" && window.navigator.clipboard && window.isSecureContext;
417
- var copyTextToClipboard = async (text) => {
418
- if (IS_NAVIGATOR_CLIPBOARD_AVAILABLE) {
419
- try {
420
- await navigator.clipboard.writeText(text);
421
- return true;
422
- } catch {
423
- }
424
- }
425
- const textareaElement = document.createElement("textarea");
426
- textareaElement.value = text;
427
- textareaElement.setAttribute("readonly", "");
428
- textareaElement.style.position = "fixed";
429
- textareaElement.style.top = "-9999px";
430
- textareaElement.style.opacity = "0";
431
- textareaElement.style.pointerEvents = "none";
432
- const doc = document.body || document.documentElement;
433
- doc.appendChild(textareaElement);
434
- textareaElement.select();
435
- textareaElement.setSelectionRange(0, textareaElement.value.length);
436
- let didCopyToClipboard = false;
437
- try {
438
- didCopyToClipboard = document.execCommand("copy");
439
- } catch {
440
- didCopyToClipboard = false;
441
- } finally {
442
- doc.removeChild(textareaElement);
443
- }
444
- return didCopyToClipboard;
445
- };
446
-
447
- // node_modules/.pnpm/bippy@0.3.31_@types+react@19.2.2_react@19.2.0/node_modules/bippy/dist/src-R2iEnVC1.js
219
+ // ../../node_modules/.pnpm/bippy@0.3.31_@types+react@19.2.2_react@19.2.0/node_modules/bippy/dist/src-R2iEnVC1.js
448
220
  var version = "0.3.31";
449
221
  var BIPPY_INSTRUMENTATION_STRING = `bippy-${version}`;
450
222
  var objectDefineProperty = Object.defineProperty;
@@ -464,7 +236,7 @@ var ReactGrab = (function (exports) {
464
236
  return "getFiberRoots" in rdtHook;
465
237
  };
466
238
  var isReactRefreshOverride = false;
467
- var injectFnStr = undefined;
239
+ var injectFnStr = void 0;
468
240
  var isReactRefresh = (rdtHook = getRDTHook()) => {
469
241
  if (isReactRefreshOverride) return true;
470
242
  if (typeof rdtHook.inject === "function") injectFnStr = rdtHook.inject.toString();
@@ -542,6 +314,7 @@ var ReactGrab = (function (exports) {
542
314
  return rdtHook;
543
315
  };
544
316
  var patchRDTHook = (onActive) => {
317
+ if (onActive) onActiveListeners.add(onActive);
545
318
  try {
546
319
  const rdtHook = globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__;
547
320
  if (!rdtHook) return;
@@ -622,6 +395,28 @@ var ReactGrab = (function (exports) {
622
395
  if (!unwrappedType) return null;
623
396
  return unwrappedType.displayName || unwrappedType.name || null;
624
397
  };
398
+ var instrument = (options) => {
399
+ return getRDTHook(() => {
400
+ const rdtHook = getRDTHook();
401
+ options.onActive?.();
402
+ rdtHook._instrumentationSource = options.name ?? BIPPY_INSTRUMENTATION_STRING;
403
+ const prevOnCommitFiberRoot = rdtHook.onCommitFiberRoot;
404
+ if (options.onCommitFiberRoot) rdtHook.onCommitFiberRoot = (rendererID, root, priority) => {
405
+ if (prevOnCommitFiberRoot) prevOnCommitFiberRoot(rendererID, root, priority);
406
+ options.onCommitFiberRoot?.(rendererID, root, priority);
407
+ };
408
+ const prevOnCommitFiberUnmount = rdtHook.onCommitFiberUnmount;
409
+ if (options.onCommitFiberUnmount) rdtHook.onCommitFiberUnmount = (rendererID, root) => {
410
+ if (prevOnCommitFiberUnmount) prevOnCommitFiberUnmount(rendererID, root);
411
+ options.onCommitFiberUnmount?.(rendererID, root);
412
+ };
413
+ const prevOnPostCommitFiberRoot = rdtHook.onPostCommitFiberRoot;
414
+ if (options.onPostCommitFiberRoot) rdtHook.onPostCommitFiberRoot = (rendererID, root) => {
415
+ if (prevOnPostCommitFiberRoot) prevOnPostCommitFiberRoot(rendererID, root);
416
+ options.onPostCommitFiberRoot?.(rendererID, root);
417
+ };
418
+ });
419
+ };
625
420
  var getFiberFromHostInstance = (hostInstance) => {
626
421
  const rdtHook = getRDTHook();
627
422
  for (const renderer of rdtHook.renderers.values()) try {
@@ -635,9 +430,10 @@ var ReactGrab = (function (exports) {
635
430
  }
636
431
  return null;
637
432
  };
433
+ var _fiberRoots = /* @__PURE__ */ new Set();
638
434
  safelyInstallRDTHook();
639
435
 
640
- // node_modules/.pnpm/bippy@0.3.31_@types+react@19.2.2_react@19.2.0/node_modules/bippy/dist/source-DWOhEbf2.js
436
+ // ../../node_modules/.pnpm/bippy@0.3.31_@types+react@19.2.2_react@19.2.0/node_modules/bippy/dist/source-DWOhEbf2.js
641
437
  var __create = Object.create;
642
438
  var __defProp = Object.defineProperty;
643
439
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -670,15 +466,15 @@ var ReactGrab = (function (exports) {
670
466
  function extractLocation(urlLike) {
671
467
  if (!urlLike.includes(":")) return [
672
468
  urlLike,
673
- undefined,
674
- undefined
469
+ void 0,
470
+ void 0
675
471
  ];
676
472
  const regExp = /(.+?)(?::(\d+))?(?::(\d+))?$/;
677
473
  const parts = regExp.exec(urlLike.replace(/[()]/g, ""));
678
474
  return [
679
475
  parts[1],
680
- parts[2] || undefined,
681
- parts[3] || undefined
476
+ parts[2] || void 0,
477
+ parts[3] || void 0
682
478
  ];
683
479
  }
684
480
  function applySlice(lines, options) {
@@ -694,13 +490,13 @@ var ReactGrab = (function (exports) {
694
490
  const location = sanitizedLine.match(/ (\(.+\)$)/);
695
491
  sanitizedLine = location ? sanitizedLine.replace(location[0], "") : sanitizedLine;
696
492
  const locationParts = extractLocation(location ? location[1] : sanitizedLine);
697
- const functionName = location && sanitizedLine || undefined;
698
- const fileName = ["eval", "<anonymous>"].includes(locationParts[0]) ? undefined : locationParts[0];
493
+ const functionName = location && sanitizedLine || void 0;
494
+ const fileName = ["eval", "<anonymous>"].includes(locationParts[0]) ? void 0 : locationParts[0];
699
495
  return {
700
496
  function: functionName,
701
497
  file: fileName,
702
- line: locationParts[1] ? +locationParts[1] : undefined,
703
- col: locationParts[2] ? +locationParts[2] : undefined,
498
+ line: locationParts[1] ? +locationParts[1] : void 0,
499
+ col: locationParts[2] ? +locationParts[2] : void 0,
704
500
  raw: line
705
501
  };
706
502
  });
@@ -715,13 +511,13 @@ var ReactGrab = (function (exports) {
715
511
  else {
716
512
  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][^@]*)?)?[^@]*)@/;
717
513
  const matches = line.match(functionNameRegex);
718
- const functionName = matches && matches[1] ? matches[1] : undefined;
514
+ const functionName = matches && matches[1] ? matches[1] : void 0;
719
515
  const locationParts = extractLocation(line.replace(functionNameRegex, ""));
720
516
  return {
721
517
  function: functionName,
722
518
  file: locationParts[0],
723
- line: locationParts[1] ? +locationParts[1] : undefined,
724
- col: locationParts[2] ? +locationParts[2] : undefined,
519
+ line: locationParts[1] ? +locationParts[1] : void 0,
520
+ col: locationParts[2] ? +locationParts[2] : void 0,
725
521
  raw: line
726
522
  };
727
523
  }
@@ -1423,7 +1219,7 @@ var ReactGrab = (function (exports) {
1423
1219
  let sortCache = /* @__PURE__ */ new WeakMap();
1424
1220
  exports.quickSort = function(ary, comparator, start = 0) {
1425
1221
  let doQuickSort = sortCache.get(comparator);
1426
- if (doQuickSort === undefined) {
1222
+ if (doQuickSort === void 0) {
1427
1223
  doQuickSort = cloneSort(comparator);
1428
1224
  sortCache.set(comparator, doQuickSort);
1429
1225
  }
@@ -1520,7 +1316,7 @@ var ReactGrab = (function (exports) {
1520
1316
  var index = this._findMapping(needle, this._originalMappings, "originalLine", "originalColumn", util$1.compareByOriginalPositions, binarySearch.LEAST_UPPER_BOUND);
1521
1317
  if (index >= 0) {
1522
1318
  var mapping = this._originalMappings[index];
1523
- if (aArgs.column === undefined) {
1319
+ if (aArgs.column === void 0) {
1524
1320
  var originalLine = mapping.originalLine;
1525
1321
  while (mapping && mapping.originalLine === originalLine) {
1526
1322
  mappings.push({
@@ -1958,7 +1754,7 @@ var ReactGrab = (function (exports) {
1958
1754
  var newLine = getNextLine() || "";
1959
1755
  return lineContents + newLine;
1960
1756
  function getNextLine() {
1961
- return remainingLinesIndex < remainingLines.length ? remainingLines[remainingLinesIndex++] : undefined;
1757
+ return remainingLinesIndex < remainingLines.length ? remainingLines[remainingLinesIndex++] : void 0;
1962
1758
  }
1963
1759
  };
1964
1760
  var lastGeneratedLine = 1, lastGeneratedColumn = 0;
@@ -2002,7 +1798,7 @@ var ReactGrab = (function (exports) {
2002
1798
  });
2003
1799
  return node;
2004
1800
  function addMappingWithCode(mapping, code) {
2005
- if (mapping === null || mapping.source === undefined) node.add(code);
1801
+ if (mapping === null || mapping.source === void 0) node.add(code);
2006
1802
  else {
2007
1803
  var source = aRelativePath ? util.join(aRelativePath, mapping.source) : mapping.source;
2008
1804
  node.add(new SourceNode(mapping.originalLine, mapping.originalColumn, source, code, mapping.name));
@@ -2215,7 +2011,7 @@ var ReactGrab = (function (exports) {
2215
2011
  var describeNativeComponentFrame = (fn, construct) => {
2216
2012
  if (!fn || reentry) return "";
2217
2013
  const previousPrepareStackTrace = Error.prepareStackTrace;
2218
- Error.prepareStackTrace = undefined;
2014
+ Error.prepareStackTrace = void 0;
2219
2015
  reentry = true;
2220
2016
  const previousDispatcher = getCurrentDispatcher();
2221
2017
  setCurrentDispatcher(null);
@@ -2397,7 +2193,7 @@ ${error.stack}`;
2397
2193
  match = componentPattern.exec(stackTrace);
2398
2194
  matches.push({
2399
2195
  name,
2400
- source: undefined
2196
+ source: void 0
2401
2197
  });
2402
2198
  continue;
2403
2199
  }
@@ -2410,21 +2206,25 @@ ${error.stack}`;
2410
2206
  }
2411
2207
  matches.push({
2412
2208
  name,
2413
- source: source || undefined
2209
+ source: source || void 0
2414
2210
  });
2415
2211
  match = componentPattern.exec(stackTrace);
2416
2212
  }
2417
2213
  return matches;
2418
2214
  };
2419
2215
 
2420
- // src/utils/data.ts
2216
+ // src/instrumentation.ts
2217
+ var fiberRoots = _fiberRoots;
2218
+ instrument({
2219
+ onCommitFiberRoot(_, fiberRoot) {
2220
+ fiberRoots.add(fiberRoot);
2221
+ }
2222
+ });
2421
2223
  var getStack = async (element) => {
2422
2224
  const fiber = getFiberFromHostInstance(element);
2423
2225
  if (!fiber) return null;
2424
2226
  const stackTrace = getFiberStackTrace(fiber);
2425
- console.log(stackTrace);
2426
2227
  const rawOwnerStack = await getOwnerStack(stackTrace);
2427
- console.log(rawOwnerStack);
2428
2228
  const stack = rawOwnerStack.map((item) => ({
2429
2229
  componentName: item.name,
2430
2230
  fileName: item.source?.fileName
@@ -2612,7 +2412,9 @@ ${error.stack}`;
2612
2412
  lines.push(`${indent2} </${prevSibling.tagName.toLowerCase()}>`);
2613
2413
  } else if (targetIndex > 0) {
2614
2414
  const indent2 = " ".repeat(ancestors.length);
2615
- lines.push(`${indent2} ... (${targetIndex} element${targetIndex === 1 ? "" : "s"})`);
2415
+ lines.push(
2416
+ `${indent2} ... (${targetIndex} element${targetIndex === 1 ? "" : "s"})`
2417
+ );
2616
2418
  }
2617
2419
  }
2618
2420
  }
@@ -2622,7 +2424,9 @@ ${error.stack}`;
2622
2424
  const childrenCount = element.children.length;
2623
2425
  if (textContent && childrenCount === 0 && textContent.length < 40) {
2624
2426
  lines.push(
2625
- `${indent} ${getElementTag(element)}${textContent}${getClosingTag(element)}`
2427
+ `${indent} ${getElementTag(element)}${textContent}${getClosingTag(
2428
+ element
2429
+ )}`
2626
2430
  );
2627
2431
  } else {
2628
2432
  lines.push(indent + " " + getElementTag(element));
@@ -2659,6 +2463,284 @@ ${error.stack}`;
2659
2463
  return lines.join("\n");
2660
2464
  };
2661
2465
 
2466
+ // src/overlay.ts
2467
+ var VIEWPORT_MARGIN_PX = 8;
2468
+ var LABEL_OFFSET_PX = 6;
2469
+ var INDICATOR_CLAMP_PADDING_PX = 4;
2470
+ var INDICATOR_SUCCESS_VISIBLE_MS = 1500;
2471
+ var INDICATOR_FADE_MS = 200;
2472
+ var INDICATOR_TOTAL_HIDE_DELAY_MS = INDICATOR_SUCCESS_VISIBLE_MS + INDICATOR_FADE_MS;
2473
+ var lerp = (start, end, factor) => {
2474
+ return start + (end - start) * factor;
2475
+ };
2476
+ var SELECTION_LERP_FACTOR = 0.95;
2477
+ var createSelectionElement = ({
2478
+ borderRadius,
2479
+ height,
2480
+ transform,
2481
+ width,
2482
+ x,
2483
+ y
2484
+ }) => {
2485
+ const overlay = document.createElement("div");
2486
+ overlay.style.position = "fixed";
2487
+ overlay.style.top = `${y}px`;
2488
+ overlay.style.left = `${x}px`;
2489
+ overlay.style.width = `${width}px`;
2490
+ overlay.style.height = `${height}px`;
2491
+ overlay.style.borderRadius = borderRadius;
2492
+ overlay.style.transform = transform;
2493
+ overlay.style.pointerEvents = "none";
2494
+ overlay.style.border = "1px solid rgb(210, 57, 192)";
2495
+ overlay.style.backgroundColor = "rgba(210, 57, 192, 0.2)";
2496
+ overlay.style.zIndex = "2147483646";
2497
+ overlay.style.boxSizing = "border-box";
2498
+ overlay.style.display = "none";
2499
+ return overlay;
2500
+ };
2501
+ var updateSelectionElement = (element, { borderRadius, height, transform, width, x, y }) => {
2502
+ const currentTop = parseFloat(element.style.top) || 0;
2503
+ const currentLeft = parseFloat(element.style.left) || 0;
2504
+ const currentWidth = parseFloat(element.style.width) || 0;
2505
+ const currentHeight = parseFloat(element.style.height) || 0;
2506
+ const topValue = `${lerp(currentTop, y, SELECTION_LERP_FACTOR)}px`;
2507
+ const leftValue = `${lerp(currentLeft, x, SELECTION_LERP_FACTOR)}px`;
2508
+ const widthValue = `${lerp(currentWidth, width, SELECTION_LERP_FACTOR)}px`;
2509
+ const heightValue = `${lerp(currentHeight, height, SELECTION_LERP_FACTOR)}px`;
2510
+ if (element.style.top !== topValue) {
2511
+ element.style.top = topValue;
2512
+ }
2513
+ if (element.style.left !== leftValue) {
2514
+ element.style.left = leftValue;
2515
+ }
2516
+ if (element.style.width !== widthValue) {
2517
+ element.style.width = widthValue;
2518
+ }
2519
+ if (element.style.height !== heightValue) {
2520
+ element.style.height = heightValue;
2521
+ }
2522
+ if (element.style.borderRadius !== borderRadius) {
2523
+ element.style.borderRadius = borderRadius;
2524
+ }
2525
+ if (element.style.transform !== transform) {
2526
+ element.style.transform = transform;
2527
+ }
2528
+ };
2529
+ var createSelectionOverlay = (root) => {
2530
+ const element = createSelectionElement({
2531
+ borderRadius: "0px",
2532
+ height: 0,
2533
+ transform: "none",
2534
+ width: 0,
2535
+ x: -1e3,
2536
+ y: -1e3
2537
+ });
2538
+ root.appendChild(element);
2539
+ let visible = false;
2540
+ return {
2541
+ hide: () => {
2542
+ visible = false;
2543
+ element.style.display = "none";
2544
+ element.style.pointerEvents = "none";
2545
+ },
2546
+ isVisible: () => visible,
2547
+ show: () => {
2548
+ visible = true;
2549
+ element.style.display = "block";
2550
+ element.style.pointerEvents = "auto";
2551
+ },
2552
+ update: (selection) => {
2553
+ updateSelectionElement(element, selection);
2554
+ }
2555
+ };
2556
+ };
2557
+ var createSpinner = () => {
2558
+ const spinner = document.createElement("span");
2559
+ spinner.style.display = "inline-block";
2560
+ spinner.style.width = "8px";
2561
+ spinner.style.height = "8px";
2562
+ spinner.style.border = "1.5px solid rgb(210, 57, 192)";
2563
+ spinner.style.borderTopColor = "transparent";
2564
+ spinner.style.borderRadius = "50%";
2565
+ spinner.style.marginRight = "4px";
2566
+ spinner.style.verticalAlign = "middle";
2567
+ spinner.animate(
2568
+ [{ transform: "rotate(0deg)" }, { transform: "rotate(360deg)" }],
2569
+ {
2570
+ duration: 600,
2571
+ easing: "linear",
2572
+ iterations: Infinity
2573
+ }
2574
+ );
2575
+ return spinner;
2576
+ };
2577
+ var activeIndicator = null;
2578
+ var createIndicator = () => {
2579
+ const indicator = document.createElement("div");
2580
+ indicator.style.position = "fixed";
2581
+ indicator.style.top = "calc(8px + env(safe-area-inset-top))";
2582
+ indicator.style.padding = "2px 6px";
2583
+ indicator.style.backgroundColor = "#fde7f7";
2584
+ indicator.style.color = "#b21c8e";
2585
+ indicator.style.border = "1px solid #f7c5ec";
2586
+ indicator.style.borderRadius = "4px";
2587
+ indicator.style.fontSize = "11px";
2588
+ indicator.style.fontWeight = "500";
2589
+ indicator.style.fontFamily = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif";
2590
+ indicator.style.zIndex = "2147483647";
2591
+ indicator.style.pointerEvents = "none";
2592
+ indicator.style.opacity = "0";
2593
+ indicator.style.transition = "opacity 0.2s ease-in-out";
2594
+ indicator.style.display = "flex";
2595
+ indicator.style.alignItems = "center";
2596
+ indicator.style.maxWidth = "calc(100vw - (16px + env(safe-area-inset-left) + env(safe-area-inset-right)))";
2597
+ indicator.style.overflow = "hidden";
2598
+ indicator.style.textOverflow = "ellipsis";
2599
+ indicator.style.whiteSpace = "nowrap";
2600
+ return indicator;
2601
+ };
2602
+ var showLabel = (selectionLeftPx, selectionTopPx, tagName) => {
2603
+ let indicator = activeIndicator;
2604
+ let isNewIndicator = false;
2605
+ if (!indicator) {
2606
+ indicator = createIndicator();
2607
+ document.body.appendChild(indicator);
2608
+ activeIndicator = indicator;
2609
+ isNewIndicator = true;
2610
+ isProcessing = false;
2611
+ }
2612
+ if (!isProcessing) {
2613
+ const labelText = indicator.querySelector("span");
2614
+ if (labelText) {
2615
+ const tagNameMonospace = document.createElement("span");
2616
+ tagNameMonospace.textContent = tagName ? `<${tagName}>` : "<element>";
2617
+ tagNameMonospace.style.fontFamily = "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace";
2618
+ tagNameMonospace.style.fontVariantNumeric = "tabular-nums";
2619
+ labelText.replaceChildren(tagNameMonospace);
2620
+ } else {
2621
+ const newLabelText = document.createElement("span");
2622
+ const tagNameMonospace = document.createElement("span");
2623
+ tagNameMonospace.textContent = tagName ? `<${tagName}>` : "<element>";
2624
+ tagNameMonospace.style.fontFamily = "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace";
2625
+ tagNameMonospace.style.fontVariantNumeric = "tabular-nums";
2626
+ newLabelText.appendChild(tagNameMonospace);
2627
+ indicator.appendChild(newLabelText);
2628
+ }
2629
+ }
2630
+ const indicatorRect = indicator.getBoundingClientRect();
2631
+ const viewportWidthPx = window.innerWidth;
2632
+ const viewportHeightPx = window.innerHeight;
2633
+ let indicatorLeftPx = Math.round(selectionLeftPx);
2634
+ let indicatorTopPx = Math.round(selectionTopPx) - indicatorRect.height - LABEL_OFFSET_PX;
2635
+ const CLAMPED_PADDING = INDICATOR_CLAMP_PADDING_PX;
2636
+ const minLeft = VIEWPORT_MARGIN_PX;
2637
+ const minTop = VIEWPORT_MARGIN_PX;
2638
+ const maxLeft = viewportWidthPx - indicatorRect.width - VIEWPORT_MARGIN_PX;
2639
+ const maxTop = viewportHeightPx - indicatorRect.height - VIEWPORT_MARGIN_PX;
2640
+ const willClampLeft = indicatorLeftPx < minLeft;
2641
+ const willClampTop = indicatorTopPx < minTop;
2642
+ const isClamped = willClampLeft || willClampTop;
2643
+ indicatorLeftPx = Math.max(minLeft, Math.min(indicatorLeftPx, maxLeft));
2644
+ indicatorTopPx = Math.max(minTop, Math.min(indicatorTopPx, maxTop));
2645
+ if (isClamped) {
2646
+ indicatorLeftPx += CLAMPED_PADDING;
2647
+ indicatorTopPx += CLAMPED_PADDING;
2648
+ }
2649
+ indicator.style.left = `${indicatorLeftPx}px`;
2650
+ indicator.style.top = `${indicatorTopPx}px`;
2651
+ indicator.style.right = "auto";
2652
+ if (isNewIndicator) {
2653
+ requestAnimationFrame(() => {
2654
+ indicator.style.opacity = "1";
2655
+ });
2656
+ } else if (indicator.style.opacity !== "1") {
2657
+ indicator.style.opacity = "1";
2658
+ }
2659
+ };
2660
+ var isProcessing = false;
2661
+ var updateLabelToProcessing = () => {
2662
+ if (!activeIndicator || isProcessing) return () => {
2663
+ };
2664
+ isProcessing = true;
2665
+ const indicator = activeIndicator;
2666
+ indicator.innerHTML = "";
2667
+ const loadingSpinner = createSpinner();
2668
+ const labelText = document.createElement("span");
2669
+ labelText.textContent = "Grabbing\u2026";
2670
+ indicator.appendChild(loadingSpinner);
2671
+ indicator.appendChild(labelText);
2672
+ return (tagName) => {
2673
+ if (!activeIndicator) {
2674
+ isProcessing = false;
2675
+ return;
2676
+ }
2677
+ indicator.textContent = "";
2678
+ const checkmarkIcon = document.createElement("span");
2679
+ checkmarkIcon.textContent = "\u2713";
2680
+ checkmarkIcon.style.display = "inline-block";
2681
+ checkmarkIcon.style.marginRight = "4px";
2682
+ checkmarkIcon.style.fontWeight = "600";
2683
+ const newLabelText = document.createElement("span");
2684
+ const tagNameMonospace = document.createElement("span");
2685
+ tagNameMonospace.textContent = tagName ? `<${tagName}>` : "<element>";
2686
+ tagNameMonospace.style.fontFamily = "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace";
2687
+ tagNameMonospace.style.fontVariantNumeric = "tabular-nums";
2688
+ newLabelText.appendChild(document.createTextNode("Grabbed "));
2689
+ newLabelText.appendChild(tagNameMonospace);
2690
+ indicator.appendChild(checkmarkIcon);
2691
+ indicator.appendChild(newLabelText);
2692
+ setTimeout(() => {
2693
+ indicator.style.opacity = "0";
2694
+ setTimeout(() => {
2695
+ indicator.remove();
2696
+ if (activeIndicator === indicator) {
2697
+ activeIndicator = null;
2698
+ }
2699
+ isProcessing = false;
2700
+ }, INDICATOR_FADE_MS);
2701
+ }, INDICATOR_SUCCESS_VISIBLE_MS);
2702
+ };
2703
+ };
2704
+ var hideLabel = () => {
2705
+ if (activeIndicator) {
2706
+ activeIndicator.remove();
2707
+ activeIndicator = null;
2708
+ }
2709
+ isProcessing = false;
2710
+ };
2711
+
2712
+ // src/utils/copy-text.ts
2713
+ var IS_NAVIGATOR_CLIPBOARD_AVAILABLE = typeof window !== "undefined" && window.navigator.clipboard && window.isSecureContext;
2714
+ var copyTextToClipboard = async (text) => {
2715
+ if (IS_NAVIGATOR_CLIPBOARD_AVAILABLE) {
2716
+ try {
2717
+ await navigator.clipboard.writeText(text);
2718
+ return true;
2719
+ } catch {
2720
+ }
2721
+ }
2722
+ const textareaElement = document.createElement("textarea");
2723
+ textareaElement.value = text;
2724
+ textareaElement.setAttribute("readonly", "");
2725
+ textareaElement.style.position = "fixed";
2726
+ textareaElement.style.top = "-9999px";
2727
+ textareaElement.style.opacity = "0";
2728
+ textareaElement.style.pointerEvents = "none";
2729
+ const doc = document.body || document.documentElement;
2730
+ doc.appendChild(textareaElement);
2731
+ textareaElement.select();
2732
+ textareaElement.setSelectionRange(0, textareaElement.value.length);
2733
+ let didCopyToClipboard = false;
2734
+ try {
2735
+ didCopyToClipboard = document.execCommand("copy");
2736
+ } catch {
2737
+ didCopyToClipboard = false;
2738
+ } finally {
2739
+ doc.removeChild(textareaElement);
2740
+ }
2741
+ return didCopyToClipboard;
2742
+ };
2743
+
2662
2744
  // src/utils/is-element-visible.ts
2663
2745
  var isElementVisible = (element, computedStyle = window.getComputedStyle(element)) => {
2664
2746
  return computedStyle.display !== "none" && computedStyle.visibility !== "hidden" && computedStyle.opacity !== "0";
@@ -2810,8 +2892,7 @@ ${error.stack}`;
2810
2892
  return;
2811
2893
  }
2812
2894
  const resolvedOptions = {
2813
- enabled: true,
2814
- hotkey: "Meta",
2895
+ hotkey: ["Meta", "C"],
2815
2896
  keyHoldDuration: 500,
2816
2897
  ...options
2817
2898
  };
@@ -2830,9 +2911,6 @@ ${error.stack}`;
2830
2911
  }
2831
2912
  return isKeyPressed(resolvedOptions.hotkey);
2832
2913
  };
2833
- const isCopyHotkeyPressed = () => {
2834
- return isKeyPressed("Meta") && isKeyPressed("C");
2835
- };
2836
2914
  let cleanupActivationHotkeyWatcher = null;
2837
2915
  const handleKeyStateChange = (pressedKeys) => {
2838
2916
  const { overlayMode } = libStore.getState();
@@ -2869,13 +2947,6 @@ ${error.stack}`;
2869
2947
  }
2870
2948
  return;
2871
2949
  }
2872
- if (isCopyHotkeyPressed() && overlayMode === "visible") {
2873
- libStore.setState((state) => ({
2874
- ...state,
2875
- overlayMode: "copying"
2876
- }));
2877
- return;
2878
- }
2879
2950
  const isActivationHotkeyPressed = checkIsActivationHotkeyPressed();
2880
2951
  if (!isActivationHotkeyPressed) {
2881
2952
  if (cleanupActivationHotkeyWatcher) {
@@ -2949,8 +3020,7 @@ ${error.stack}`;
2949
3020
  return null;
2950
3021
  };
2951
3022
  const handleCopy = async (element) => {
2952
- const rect = element.getBoundingClientRect();
2953
- const cleanupCopyIndicator = showCopyIndicator(rect.left, rect.top);
3023
+ const cleanupIndicator = updateLabelToProcessing();
2954
3024
  try {
2955
3025
  const stack = await getStack(element);
2956
3026
  const htmlSnippet = getHTMLSnippet(element);
@@ -2958,16 +3028,22 @@ ${error.stack}`;
2958
3028
  if (stack) {
2959
3029
  const filteredStack = filterStack(stack);
2960
3030
  const serializedStack = serializeStack(filteredStack);
2961
- text = `${serializedStack}
3031
+ text = `${htmlSnippet}
2962
3032
 
2963
- ${htmlSnippet}`;
3033
+ Component owner stack:
3034
+ ${serializedStack}`;
2964
3035
  }
2965
- await copyTextToClipboard(`
2966
- ${text}`);
3036
+ await copyTextToClipboard(
3037
+ `
3038
+
3039
+ <referenced_element>
3040
+ ${text}
3041
+ </referenced_element>`
3042
+ );
2967
3043
  const tagName = (element.tagName || "").toLowerCase();
2968
- cleanupCopyIndicator(tagName);
3044
+ cleanupIndicator(tagName);
2969
3045
  } catch {
2970
- cleanupCopyIndicator();
3046
+ cleanupIndicator();
2971
3047
  }
2972
3048
  };
2973
3049
  const handleRender = throttle((state) => {
@@ -2975,6 +3051,9 @@ ${text}`);
2975
3051
  if (overlayMode === "hidden") {
2976
3052
  if (selectionOverlay.isVisible()) {
2977
3053
  selectionOverlay.hide();
3054
+ if (!isCopying) {
3055
+ hideLabel();
3056
+ }
2978
3057
  hoveredElement = null;
2979
3058
  }
2980
3059
  return;
@@ -3000,7 +3079,10 @@ ${text}`);
3000
3079
  ...state2,
3001
3080
  overlayMode: "hidden"
3002
3081
  }));
3003
- isCopying = false;
3082
+ selectionOverlay.hide();
3083
+ window.setTimeout(() => {
3084
+ isCopying = false;
3085
+ }, INDICATOR_TOTAL_HIDE_DELAY_MS);
3004
3086
  });
3005
3087
  }
3006
3088
  return;
@@ -3009,10 +3091,14 @@ ${text}`);
3009
3091
  if (!element) {
3010
3092
  if (selectionOverlay.isVisible()) {
3011
3093
  selectionOverlay.hide();
3094
+ if (!isCopying) {
3095
+ hideLabel();
3096
+ }
3012
3097
  }
3013
3098
  hoveredElement = null;
3014
3099
  return;
3015
3100
  }
3101
+ const tagName = (element.tagName || "").toLowerCase();
3016
3102
  hoveredElement = element;
3017
3103
  const rect = element.getBoundingClientRect();
3018
3104
  const computedStyle = window.getComputedStyle(element);
@@ -3029,6 +3115,7 @@ ${text}`);
3029
3115
  if (!selectionOverlay.isVisible()) {
3030
3116
  selectionOverlay.show();
3031
3117
  }
3118
+ showLabel(rect.left, rect.top, tagName);
3032
3119
  }, 10);
3033
3120
  const cleanupRenderSubscription = libStore.subscribe((state) => {
3034
3121
  scheduleRunWhenIdle(() => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-grab",
3
- "version": "0.0.12",
3
+ "version": "0.0.14",
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