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