react-grab 0.0.36 → 0.0.38

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
@@ -258,9 +258,10 @@ var getClampedElementPosition = (positionLeft, positionTop, elementWidth, elemen
258
258
  // src/components/label.tsx
259
259
  var _tmpl$3 = /* @__PURE__ */ template(`<span style=display:inline-block;margin-right:4px;font-weight:600>\u2713`);
260
260
  var _tmpl$22 = /* @__PURE__ */ template(`<div style=margin-right:4px>Copied`);
261
- var _tmpl$32 = /* @__PURE__ */ template(`<span style="font-family:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;font-variant-numeric:tabular-nums">`);
262
- var _tmpl$4 = /* @__PURE__ */ template(`<div style=margin-left:4px>to clipboard`);
263
- 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">`);
261
+ var _tmpl$32 = /* @__PURE__ */ template(`<span style="font-family:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;font-variant-numeric:tabular-nums;vertical-align:middle">`);
262
+ var _tmpl$4 = /* @__PURE__ */ template(`<span style=font-variant-numeric:tabular-nums;font-size:10px;margin-left:4px;vertical-align:middle>`);
263
+ var _tmpl$5 = /* @__PURE__ */ template(`<div style=margin-left:4px>to clipboard`);
264
+ var _tmpl$6 = /* @__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">`);
264
265
  var Label = (props) => {
265
266
  const [opacity, setOpacity] = createSignal(0);
266
267
  const [positionTick, setPositionTick] = createSignal(0);
@@ -358,12 +359,26 @@ var Label = (props) => {
358
359
  fallback.top += INDICATOR_CLAMP_PADDING_PX;
359
360
  return fallback;
360
361
  };
362
+ const labelSegments = () => {
363
+ const separator = " in ";
364
+ const separatorIndex = props.text.indexOf(separator);
365
+ if (separatorIndex === -1) {
366
+ return {
367
+ primary: props.text,
368
+ secondary: ""
369
+ };
370
+ }
371
+ return {
372
+ primary: props.text.slice(0, separatorIndex),
373
+ secondary: props.text.slice(separatorIndex)
374
+ };
375
+ };
361
376
  return createComponent(Show, {
362
377
  get when() {
363
378
  return props.visible !== false;
364
379
  },
365
380
  get children() {
366
- var _el$ = _tmpl$5();
381
+ var _el$ = _tmpl$6();
367
382
  var _ref$ = labelRef;
368
383
  typeof _ref$ === "function" ? use(_ref$, _el$) : labelRef = _el$;
369
384
  insert(_el$, createComponent(Show, {
@@ -401,9 +416,20 @@ var Label = (props) => {
401
416
  return props.variant !== "processing";
402
417
  },
403
418
  get children() {
404
- var _el$4 = _tmpl$32();
405
- insert(_el$4, () => props.text);
406
- return _el$4;
419
+ return [(() => {
420
+ var _el$4 = _tmpl$32();
421
+ insert(_el$4, () => labelSegments().primary);
422
+ return _el$4;
423
+ })(), createComponent(Show, {
424
+ get when() {
425
+ return memo(() => props.variant === "hover")() && labelSegments().secondary !== "";
426
+ },
427
+ get children() {
428
+ var _el$5 = _tmpl$4();
429
+ insert(_el$5, () => labelSegments().secondary);
430
+ return _el$5;
431
+ }
432
+ })];
407
433
  }
408
434
  }), null);
409
435
  insert(_el$, createComponent(Show, {
@@ -411,7 +437,7 @@ var Label = (props) => {
411
437
  return props.variant === "success";
412
438
  },
413
439
  get children() {
414
- return _tmpl$4();
440
+ return _tmpl$5();
415
441
  }
416
442
  }), null);
417
443
  effect((_p$) => {
@@ -431,7 +457,7 @@ var Label = (props) => {
431
457
  }
432
458
  });
433
459
  };
434
- 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)">`);
460
+ var _tmpl$7 = /* @__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)">`);
435
461
  var useFadeInOut = (visible) => {
436
462
  const [opacity, setOpacity] = createSignal(0);
437
463
  createEffect(on(() => visible, (isVisible) => {
@@ -464,7 +490,7 @@ var ProgressIndicator = (props) => {
464
490
  return props.visible !== false;
465
491
  },
466
492
  get children() {
467
- var _el$ = _tmpl$6(), _el$2 = _el$.firstChild, _el$3 = _el$2.firstChild;
493
+ var _el$ = _tmpl$7(), _el$2 = _el$.firstChild, _el$3 = _el$2.firstChild;
468
494
  var _ref$ = progressIndicatorRef;
469
495
  typeof _ref$ === "function" ? use(_ref$, _el$) : progressIndicatorRef = _el$;
470
496
  effect((_p$) => {
@@ -484,7 +510,7 @@ var ProgressIndicator = (props) => {
484
510
  }
485
511
  });
486
512
  };
487
- var _tmpl$7 = /* @__PURE__ */ template(`<canvas style=position:fixed;top:0;left:0;pointer-events:none;z-index:2147483645>`);
513
+ var _tmpl$8 = /* @__PURE__ */ template(`<canvas style=position:fixed;top:0;left:0;pointer-events:none;z-index:2147483645>`);
488
514
  var Crosshair = (props) => {
489
515
  let canvasRef;
490
516
  let context = null;
@@ -574,7 +600,7 @@ var Crosshair = (props) => {
574
600
  return props.visible !== false;
575
601
  },
576
602
  get children() {
577
- var _el$ = _tmpl$7();
603
+ var _el$ = _tmpl$8();
578
604
  var _ref$ = canvasRef;
579
605
  typeof _ref$ === "function" ? use(_ref$, _el$) : canvasRef = _el$;
580
606
  return _el$;
@@ -706,6 +732,11 @@ var ReactGrabRenderer = (props) => {
706
732
  }
707
733
  })];
708
734
  };
735
+
736
+ // src/utils/is-capitalized.ts
737
+ var isCapitalized = (value) => value.length > 0 && /^[A-Z]/.test(value);
738
+
739
+ // src/instrumentation.ts
709
740
  instrument({
710
741
  onCommitFiberRoot(_, fiberRoot) {
711
742
  _fiberRoots.add(fiberRoot);
@@ -714,40 +745,36 @@ instrument({
714
745
  var generateCSSSelector = (element) => {
715
746
  return finder(element);
716
747
  };
717
- var getHTMLSnippet = async (element) => {
718
- const truncateString = (string, maxLength) => string.length > maxLength ? `${string.substring(0, maxLength)}...` : string;
719
- const isInternalComponent = (name) => {
720
- if (name.startsWith("_")) return true;
721
- if (name.includes("Provider") && name.includes("Context")) return true;
722
- return false;
723
- };
724
- const extractReactComponentName = (el) => {
725
- const fiber = getFiberFromHostInstance(el);
726
- if (!fiber) return null;
727
- let componentName = null;
728
- traverseFiber(
729
- fiber,
730
- (currentFiber) => {
731
- if (isCompositeFiber(currentFiber)) {
732
- const displayName = getDisplayName(currentFiber);
733
- if (displayName && !isInternalComponent(displayName)) {
734
- componentName = displayName;
735
- return true;
736
- }
748
+ var truncateString = (string, maxLength) => string.length > maxLength ? `${string.substring(0, maxLength)}...` : string;
749
+ var isInternalComponent = (name) => !isCapitalized(name) || name.startsWith("_") || name.includes("Provider") && name.includes("Context");
750
+ var getNearestComponentDisplayName = (element) => {
751
+ const fiber = getFiberFromHostInstance(element);
752
+ if (!fiber) return null;
753
+ let componentName = null;
754
+ traverseFiber(
755
+ fiber,
756
+ (currentFiber) => {
757
+ if (isCompositeFiber(currentFiber)) {
758
+ const displayName = getDisplayName(currentFiber);
759
+ if (displayName && !isInternalComponent(displayName)) {
760
+ componentName = displayName;
761
+ return true;
737
762
  }
738
- return false;
739
- },
740
- true
741
- );
742
- return componentName;
743
- };
744
- const formatComponentSourceLocation = async (el) => {
745
- const source = await getSourceFromHostInstance(el);
746
- if (!source) return null;
747
- const fileName = normalizeFileName(source.fileName);
748
- if (!isSourceFile(fileName)) return null;
749
- return `${fileName}:${source.lineNumber}:${source.columnNumber}`;
750
- };
763
+ }
764
+ return false;
765
+ },
766
+ true
767
+ );
768
+ return componentName;
769
+ };
770
+ var formatComponentSourceLocation = async (el) => {
771
+ const source = await getSourceFromHostInstance(el);
772
+ if (!source) return null;
773
+ const fileName = normalizeFileName(source.fileName);
774
+ if (!isSourceFile(fileName)) return null;
775
+ return `${fileName}:${source.lineNumber}:${source.columnNumber}`;
776
+ };
777
+ var getHTMLSnippet = async (element) => {
751
778
  const semanticTags = /* @__PURE__ */ new Set([
752
779
  "article",
753
780
  "aside",
@@ -836,9 +863,9 @@ var getHTMLSnippet = async (element) => {
836
863
  lines.push("```html");
837
864
  const ancestors = collectDistinguishingAncestors(element);
838
865
  const ancestorComponents = ancestors.map(
839
- (ancestor) => extractReactComponentName(ancestor)
866
+ (ancestor) => getNearestComponentDisplayName(ancestor)
840
867
  );
841
- const elementComponent = extractReactComponentName(element);
868
+ const elementComponent = getNearestComponentDisplayName(element);
842
869
  const ancestorSources = await Promise.all(
843
870
  ancestors.map((ancestor) => formatComponentSourceLocation(ancestor))
844
871
  );
@@ -946,17 +973,18 @@ var waitForFocus = () => {
946
973
  window.focus();
947
974
  });
948
975
  };
949
- var copyContent = async (content) => {
976
+ var copyContent = async (content, onSuccess) => {
950
977
  await waitForFocus();
951
978
  try {
952
979
  if (Array.isArray(content)) {
953
980
  if (!navigator?.clipboard?.write) {
954
981
  for (const contentPart of content) {
955
982
  if (typeof contentPart === "string") {
956
- const result = copyContentFallback(contentPart);
983
+ const result = copyContentFallback(contentPart, onSuccess);
957
984
  if (!result) return result;
958
985
  }
959
986
  }
987
+ onSuccess?.();
960
988
  return true;
961
989
  }
962
990
  const mimeTypeMap = /* @__PURE__ */ new Map();
@@ -975,28 +1003,52 @@ var copyContent = async (content) => {
975
1003
  }
976
1004
  }
977
1005
  }
978
- await navigator.clipboard.write([
979
- new ClipboardItem(Object.fromEntries(mimeTypeMap))
980
- ]);
981
- return true;
1006
+ if (mimeTypeMap.size === 0) {
1007
+ const plainTextFallback = content.find(
1008
+ (contentPart) => typeof contentPart === "string"
1009
+ );
1010
+ if (typeof plainTextFallback === "string") {
1011
+ return copyContentFallback(plainTextFallback, onSuccess);
1012
+ }
1013
+ return false;
1014
+ }
1015
+ try {
1016
+ await navigator.clipboard.write([
1017
+ new ClipboardItem(Object.fromEntries(mimeTypeMap))
1018
+ ]);
1019
+ onSuccess?.();
1020
+ return true;
1021
+ } catch {
1022
+ const plainTextParts = content.filter(
1023
+ (contentPart) => typeof contentPart === "string"
1024
+ );
1025
+ if (plainTextParts.length > 0) {
1026
+ const combinedText = plainTextParts.join("\n\n");
1027
+ return copyContentFallback(combinedText, onSuccess);
1028
+ }
1029
+ return false;
1030
+ }
982
1031
  } else if (content instanceof Blob) {
983
1032
  await navigator.clipboard.write([
984
1033
  new ClipboardItem({ [content.type]: content })
985
1034
  ]);
1035
+ onSuccess?.();
986
1036
  return true;
987
1037
  } else {
988
1038
  try {
989
1039
  await navigator.clipboard.writeText(String(content));
1040
+ onSuccess?.();
990
1041
  return true;
991
1042
  } catch {
992
- return copyContentFallback(content);
1043
+ const result = copyContentFallback(content, onSuccess);
1044
+ return result;
993
1045
  }
994
1046
  }
995
1047
  } catch {
996
1048
  return false;
997
1049
  }
998
1050
  };
999
- var copyContentFallback = (content) => {
1051
+ var copyContentFallback = (content, onSuccess) => {
1000
1052
  if (!document.execCommand) return false;
1001
1053
  const el = document.createElement("textarea");
1002
1054
  el.value = String(content);
@@ -1006,12 +1058,46 @@ var copyContentFallback = (content) => {
1006
1058
  doc.append(el);
1007
1059
  try {
1008
1060
  el.select();
1009
- return document.execCommand("copy");
1061
+ const result = document.execCommand("copy");
1062
+ if (result) onSuccess?.();
1063
+ return result;
1010
1064
  } finally {
1011
1065
  el.remove();
1012
1066
  }
1013
1067
  };
1014
1068
 
1069
+ // src/utils/play-copy-sound.ts
1070
+ var playCopySound = () => {
1071
+ try {
1072
+ const audioContext = new (window.AudioContext || // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access -- window.webkitAudioContext is not typed
1073
+ window.webkitAudioContext)();
1074
+ const masterGain = audioContext.createGain();
1075
+ masterGain.connect(audioContext.destination);
1076
+ const notes = [
1077
+ { freq: 523.25, start: 0, duration: 0.1 },
1078
+ { freq: 659.25, start: 0.05, duration: 0.1 },
1079
+ { freq: 783.99, start: 0.1, duration: 0.15 }
1080
+ ];
1081
+ notes.forEach((note) => {
1082
+ const oscillator = audioContext.createOscillator();
1083
+ const gainNode = audioContext.createGain();
1084
+ oscillator.connect(gainNode);
1085
+ gainNode.connect(masterGain);
1086
+ oscillator.frequency.value = note.freq;
1087
+ oscillator.type = "triangle";
1088
+ const startTime = audioContext.currentTime + note.start;
1089
+ const peakTime = startTime + 0.01;
1090
+ const endTime = startTime + note.duration;
1091
+ gainNode.gain.setValueAtTime(0, startTime);
1092
+ gainNode.gain.linearRampToValueAtTime(0.15, peakTime);
1093
+ gainNode.gain.exponentialRampToValueAtTime(0.01, endTime);
1094
+ oscillator.start(startTime);
1095
+ oscillator.stop(endTime);
1096
+ });
1097
+ } catch {
1098
+ }
1099
+ };
1100
+
1015
1101
  // src/utils/is-element-visible.ts
1016
1102
  var isElementVisible = (element, computedStyle = window.getComputedStyle(element)) => {
1017
1103
  return computedStyle.display !== "none" && computedStyle.visibility !== "hidden" && computedStyle.opacity !== "0";
@@ -1119,6 +1205,12 @@ var createElementBounds = (element) => {
1119
1205
  };
1120
1206
  };
1121
1207
 
1208
+ // src/utils/is-localhost.ts
1209
+ var isLocalhost = () => {
1210
+ const hostname = window.location.hostname;
1211
+ return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]";
1212
+ };
1213
+
1122
1214
  // src/core.tsx
1123
1215
  var PROGRESS_INDICATOR_DELAY_MS = 150;
1124
1216
  var init = (rawOptions) => {
@@ -1126,6 +1218,7 @@ var init = (rawOptions) => {
1126
1218
  enabled: true,
1127
1219
  keyHoldDuration: 300,
1128
1220
  allowActivationInsideInput: true,
1221
+ playCopySound: false,
1129
1222
  ...rawOptions
1130
1223
  };
1131
1224
  if (options.enabled === false) {
@@ -1222,13 +1315,41 @@ ${context}
1222
1315
  computedStyles: element.computedStyles
1223
1316
  }))
1224
1317
  };
1225
- const base64Data = btoa(JSON.stringify(structuredData));
1318
+ const jsonString = JSON.stringify(structuredData);
1319
+ const base64Data = btoa(encodeURIComponent(jsonString).replace(/%([0-9A-F]{2})/g, (_match, p1) => String.fromCharCode(parseInt(p1, 16))));
1226
1320
  const htmlContent = `<div data-react-grab="${base64Data}"></div>`;
1227
1321
  return new Blob([htmlContent], {
1228
1322
  type: "text/html"
1229
1323
  });
1230
1324
  };
1231
1325
  const extractElementTagName = (element) => (element.tagName || "").toLowerCase();
1326
+ const extractNearestComponentName = (element) => {
1327
+ const fiber = getFiberFromHostInstance(element);
1328
+ if (!fiber) return null;
1329
+ let componentName = null;
1330
+ traverseFiber(fiber, (currentFiber) => {
1331
+ if (isCompositeFiber(currentFiber)) {
1332
+ const displayName = getDisplayName(currentFiber);
1333
+ if (displayName && isCapitalized(displayName) && !displayName.startsWith("_")) {
1334
+ componentName = displayName;
1335
+ return true;
1336
+ }
1337
+ }
1338
+ return false;
1339
+ }, true);
1340
+ return componentName;
1341
+ };
1342
+ const extractElementLabelText = (element) => {
1343
+ const tagName = extractElementTagName(element);
1344
+ const componentName = extractNearestComponentName(element);
1345
+ if (tagName && componentName) {
1346
+ return `<${tagName}> in ${componentName}`;
1347
+ }
1348
+ if (tagName) {
1349
+ return `<${tagName}>`;
1350
+ }
1351
+ return "<element>";
1352
+ };
1232
1353
  const notifyElementsSelected = (elements) => {
1233
1354
  try {
1234
1355
  const elementsPayload = elements.map((element) => ({
@@ -1242,6 +1363,8 @@ ${context}
1242
1363
  } catch {
1243
1364
  }
1244
1365
  };
1366
+ const isExtensionEnvironment = () => window.__REACT_GRAB_EXTENSION_ACTIVE__ === true || options.isExtension === true;
1367
+ const isExtensionTextOnlyMode = () => isExtensionEnvironment() && !isLocalhost();
1245
1368
  const executeCopyOperation = async (positionX, positionY, operation) => {
1246
1369
  setCopyStartX(positionX);
1247
1370
  setCopyStartY(positionY);
@@ -1252,22 +1375,49 @@ ${context}
1252
1375
  stopProgressAnimation();
1253
1376
  });
1254
1377
  };
1378
+ const hasInnerText = (element) => "innerText" in element;
1379
+ const extractElementTextContent = (element) => {
1380
+ if (hasInnerText(element)) {
1381
+ return element.innerText;
1382
+ }
1383
+ return element.textContent ?? "";
1384
+ };
1385
+ const createCombinedTextContent = (elements) => elements.map((element) => extractElementTextContent(element).trim()).filter((textContent) => textContent.length > 0).join("\n\n");
1255
1386
  const copySingleElementToClipboard = async (targetElement2) => {
1256
- const tagName = extractElementTagName(targetElement2);
1257
1387
  showTemporaryGrabbedBox(createElementBounds(targetElement2));
1258
1388
  await new Promise((resolve) => requestAnimationFrame(resolve));
1389
+ let didCopy = false;
1259
1390
  try {
1260
- const content = await getHTMLSnippet(targetElement2);
1261
- const plainTextContent = wrapInSelectedElementTags(content);
1262
- const htmlContent = createStructuredClipboardHtmlBlob([{
1263
- tagName,
1264
- content,
1265
- computedStyles: extractRelevantComputedStyles(targetElement2)
1266
- }]);
1267
- await copyContent([plainTextContent, htmlContent]);
1391
+ if (isExtensionTextOnlyMode()) {
1392
+ const plainTextContent = createCombinedTextContent([targetElement2]);
1393
+ if (plainTextContent.length > 0) {
1394
+ didCopy = await copyContent(plainTextContent, options.playCopySound ? playCopySound : void 0);
1395
+ }
1396
+ } else {
1397
+ const content = await getHTMLSnippet(targetElement2);
1398
+ const plainTextContent = wrapInSelectedElementTags(content);
1399
+ const htmlContent = createStructuredClipboardHtmlBlob([{
1400
+ tagName: extractElementTagName(targetElement2),
1401
+ content,
1402
+ computedStyles: extractRelevantComputedStyles(targetElement2)
1403
+ }]);
1404
+ didCopy = await copyContent([plainTextContent, htmlContent], options.playCopySound ? playCopySound : void 0);
1405
+ if (!didCopy) {
1406
+ const plainTextContentOnly = createCombinedTextContent([targetElement2]);
1407
+ if (plainTextContentOnly.length > 0) {
1408
+ didCopy = await copyContent(plainTextContentOnly, options.playCopySound ? playCopySound : void 0);
1409
+ }
1410
+ }
1411
+ }
1268
1412
  } catch {
1413
+ const plainTextContentOnly = createCombinedTextContent([targetElement2]);
1414
+ if (plainTextContentOnly.length > 0) {
1415
+ didCopy = await copyContent(plainTextContentOnly, options.playCopySound ? playCopySound : void 0);
1416
+ }
1417
+ }
1418
+ if (didCopy) {
1419
+ showTemporarySuccessLabel(extractElementLabelText(targetElement2), copyStartX(), copyStartY());
1269
1420
  }
1270
- showTemporarySuccessLabel(tagName ? `<${tagName}>` : "<element>", copyStartX(), copyStartY());
1271
1421
  notifyElementsSelected([targetElement2]);
1272
1422
  };
1273
1423
  const copyMultipleElementsToClipboard = async (targetElements) => {
@@ -1276,19 +1426,43 @@ ${context}
1276
1426
  showTemporaryGrabbedBox(createElementBounds(element));
1277
1427
  }
1278
1428
  await new Promise((resolve) => requestAnimationFrame(resolve));
1429
+ let didCopy = false;
1279
1430
  try {
1280
- const elementSnippets = await Promise.all(targetElements.map((element) => getHTMLSnippet(element)));
1281
- const plainTextContent = elementSnippets.filter((snippet) => snippet.trim()).map((snippet) => wrapInSelectedElementTags(snippet)).join("\n\n");
1282
- const structuredElements = elementSnippets.map((content, index) => ({
1283
- tagName: extractElementTagName(targetElements[index]),
1284
- content,
1285
- computedStyles: extractRelevantComputedStyles(targetElements[index])
1286
- }));
1287
- const htmlContent = createStructuredClipboardHtmlBlob(structuredElements);
1288
- await copyContent([plainTextContent, htmlContent]);
1431
+ if (isExtensionTextOnlyMode()) {
1432
+ const plainTextContent = createCombinedTextContent(targetElements);
1433
+ if (plainTextContent.length > 0) {
1434
+ didCopy = await copyContent(plainTextContent, options.playCopySound ? playCopySound : void 0);
1435
+ }
1436
+ } else {
1437
+ const elementSnippetResults = await Promise.allSettled(targetElements.map((element) => getHTMLSnippet(element)));
1438
+ const elementSnippets = elementSnippetResults.map((result) => result.status === "fulfilled" ? result.value : "").filter((snippet) => snippet.trim());
1439
+ if (elementSnippets.length > 0) {
1440
+ const plainTextContent = elementSnippets.map((snippet) => wrapInSelectedElementTags(snippet)).join("\n\n");
1441
+ const structuredElements = elementSnippets.map((content, elementIndex) => ({
1442
+ tagName: extractElementTagName(targetElements[elementIndex]),
1443
+ content,
1444
+ computedStyles: extractRelevantComputedStyles(targetElements[elementIndex])
1445
+ }));
1446
+ const htmlContent = createStructuredClipboardHtmlBlob(structuredElements);
1447
+ didCopy = await copyContent([plainTextContent, htmlContent], options.playCopySound ? playCopySound : void 0);
1448
+ if (!didCopy) {
1449
+ const plainTextContentOnly = createCombinedTextContent(targetElements);
1450
+ if (plainTextContentOnly.length > 0) {
1451
+ didCopy = await copyContent(plainTextContentOnly, options.playCopySound ? playCopySound : void 0);
1452
+ }
1453
+ }
1454
+ } else {
1455
+ const plainTextContentOnly = createCombinedTextContent(targetElements);
1456
+ if (plainTextContentOnly.length > 0) {
1457
+ didCopy = await copyContent(plainTextContentOnly, options.playCopySound ? playCopySound : void 0);
1458
+ }
1459
+ }
1460
+ }
1289
1461
  } catch {
1290
1462
  }
1291
- showTemporarySuccessLabel(`${targetElements.length} elements`, copyStartX(), copyStartY());
1463
+ if (didCopy) {
1464
+ showTemporarySuccessLabel(`${targetElements.length} elements`, copyStartX(), copyStartY());
1465
+ }
1292
1466
  notifyElementsSelected(targetElements);
1293
1467
  };
1294
1468
  const targetElement = createMemo(() => {
@@ -1345,7 +1519,7 @@ ${context}
1345
1519
  });
1346
1520
  const labelText = createMemo(() => {
1347
1521
  const element = targetElement();
1348
- return element ? `<${extractElementTagName(element)}>` : "<element>";
1522
+ return element ? extractElementLabelText(element) : "<element>";
1349
1523
  });
1350
1524
  const labelPosition = createMemo(() => isCopying() ? {
1351
1525
  x: copyStartX(),
@@ -1372,7 +1546,13 @@ ${context}
1372
1546
  progressTick();
1373
1547
  if (startTime === null) return 0;
1374
1548
  const elapsedTime = Date.now() - startTime;
1375
- return Math.min(elapsedTime / options.keyHoldDuration, 1);
1549
+ const normalizedTime = elapsedTime / options.keyHoldDuration;
1550
+ const easedProgress = 1 - Math.exp(-normalizedTime);
1551
+ const maxProgressBeforeCompletion = 0.95;
1552
+ if (isCopying()) {
1553
+ return Math.min(easedProgress, maxProgressBeforeCompletion);
1554
+ }
1555
+ return 1;
1376
1556
  });
1377
1557
  const startProgressAnimation = () => {
1378
1558
  setProgressStartTime(Date.now());
@@ -1657,6 +1837,9 @@ ${context}
1657
1837
  // src/index.ts
1658
1838
  var globalApi = null;
1659
1839
  var getGlobalApi = () => globalApi;
1660
- globalApi = init();
1840
+ var EXTENSION_MARKER = "__REACT_GRAB_EXTENSION_ACTIVE__";
1841
+ if (!window[EXTENSION_MARKER]) {
1842
+ globalApi = init();
1843
+ }
1661
1844
 
1662
- export { getGlobalApi, init };
1845
+ export { getGlobalApi, init, playCopySound };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-grab",
3
- "version": "0.0.36",
3
+ "version": "0.0.38",
4
4
  "description": "",
5
5
  "keywords": [
6
6
  "react",
@@ -69,7 +69,8 @@
69
69
  "@medv/finder": "^4.0.2",
70
70
  "bippy": "^0.5.14",
71
71
  "modern-screenshot": "^4.6.6",
72
- "solid-js": "^1.9.10"
72
+ "solid-js": "^1.9.10",
73
+ "solid-sonner": "^0.2.8"
73
74
  },
74
75
  "scripts": {
75
76
  "build": "NODE_ENV=production tsup",