react-grab 0.0.19 → 0.0.21

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