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