react-grab 0.0.11 → 0.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,3 +1,37 @@
1
- declare const init: () => () => void;
1
+ interface StoreApi<T> {
2
+ getInitialState(): T;
3
+ getState(): T;
4
+ setState(partialState: ((prevState: T) => T) | Partial<T>): T;
5
+ subscribe(listener: Listener<T>): () => void;
6
+ subscribe<U>(listener: Listener<U>, selector: (state: T) => unknown): () => void;
7
+ }
8
+ type Listener<T> = (state: T, prevState: T | undefined) => (() => unknown) | void;
2
9
 
3
- export { init };
10
+ type Hotkey = KeyboardEvent["key"];
11
+
12
+ interface Options {
13
+ enabled?: boolean;
14
+ /**
15
+ * hotkey to trigger the overlay
16
+ *
17
+ * default: ["Meta", "C"]
18
+ */
19
+ hotkey?: Hotkey | Hotkey[];
20
+ /**
21
+ * time required (ms) to hold the key to trigger the overlay
22
+ *
23
+ * default: 1000
24
+ */
25
+ keyHoldDuration?: number;
26
+ }
27
+ interface LibStore {
28
+ keyPressTimestamps: Map<Hotkey, number>;
29
+ mouseX: number;
30
+ mouseY: number;
31
+ overlayMode: "copying" | "hidden" | "visible";
32
+ pressedKeys: Set<Hotkey>;
33
+ }
34
+ declare const libStore: StoreApi<LibStore>;
35
+ declare const init: (options?: Options) => (() => void) | undefined;
36
+
37
+ export { type Options, init, libStore };
@@ -10,202 +10,210 @@ var ReactGrab = (function (exports) {
10
10
  * LICENSE file in the root directory of this source tree.
11
11
  */
12
12
 
13
- // src/overlay.ts
14
- var lerp = (start, end, factor) => {
15
- return start + (end - start) * factor;
13
+ // src/hotkeys.ts
14
+ var FORM_TAGS_AND_ROLES = [
15
+ "input",
16
+ "textarea",
17
+ "select",
18
+ "searchbox",
19
+ "slider",
20
+ "spinbutton",
21
+ "menuitem",
22
+ "menuitemcheckbox",
23
+ "menuitemradio",
24
+ "option",
25
+ "radio",
26
+ "textbox"
27
+ ];
28
+ var isCustomElement = (element) => {
29
+ return Boolean(element.tagName) && !element.tagName.startsWith("-") && element.tagName.includes("-");
16
30
  };
17
- var SELECTION_LERP_FACTOR = 0.99;
18
- var createSelectionElement = ({
19
- borderRadius,
20
- height,
21
- transform,
22
- width,
23
- x,
24
- y
25
- }) => {
26
- const overlay = document.createElement("div");
27
- overlay.style.position = "fixed";
28
- overlay.style.top = `${y}px`;
29
- overlay.style.left = `${x}px`;
30
- overlay.style.width = `${width}px`;
31
- overlay.style.height = `${height}px`;
32
- overlay.style.borderRadius = borderRadius;
33
- overlay.style.transform = transform;
34
- overlay.style.pointerEvents = "none";
35
- overlay.style.border = "2px solid #007AFF";
36
- overlay.style.backgroundColor = "rgba(0, 122, 255, 0.1)";
37
- overlay.style.zIndex = "2147483646";
38
- overlay.style.boxSizing = "border-box";
39
- overlay.style.display = "none";
40
- overlay.animate(
41
- [
42
- { backgroundColor: "rgba(0, 122, 255, 0.1)" },
43
- { backgroundColor: "rgba(0, 122, 255, 0.15)" },
44
- { backgroundColor: "rgba(0, 122, 255, 0.1)" }
45
- ],
46
- {
47
- duration: 2e3,
48
- easing: "ease-in-out",
49
- iterations: Infinity
50
- }
51
- );
52
- return overlay;
31
+ var isReadonlyArray = (value) => {
32
+ return Array.isArray(value);
53
33
  };
54
- var updateSelectionElement = (element, { borderRadius, height, transform, width, x, y }) => {
55
- const currentTop = parseFloat(element.style.top) || 0;
56
- const currentLeft = parseFloat(element.style.left) || 0;
57
- const currentWidth = parseFloat(element.style.width) || 0;
58
- const currentHeight = parseFloat(element.style.height) || 0;
59
- const topValue = `${lerp(currentTop, y, SELECTION_LERP_FACTOR)}px`;
60
- const leftValue = `${lerp(currentLeft, x, SELECTION_LERP_FACTOR)}px`;
61
- const widthValue = `${lerp(currentWidth, width, SELECTION_LERP_FACTOR)}px`;
62
- const heightValue = `${lerp(currentHeight, height, SELECTION_LERP_FACTOR)}px`;
63
- if (element.style.top !== topValue) {
64
- element.style.top = topValue;
65
- }
66
- if (element.style.left !== leftValue) {
67
- element.style.left = leftValue;
68
- }
69
- if (element.style.width !== widthValue) {
70
- element.style.width = widthValue;
71
- }
72
- if (element.style.height !== heightValue) {
73
- element.style.height = heightValue;
74
- }
75
- if (element.style.borderRadius !== borderRadius) {
76
- element.style.borderRadius = borderRadius;
77
- }
78
- if (element.style.transform !== transform) {
79
- element.style.transform = transform;
34
+ var isHotkeyEnabledOnTagName = (event, enabledOnTags = false) => {
35
+ const { composed, target } = event;
36
+ let targetTagName;
37
+ let targetRole;
38
+ if (target instanceof HTMLElement && isCustomElement(target) && composed) {
39
+ const composedPath = event.composedPath();
40
+ const targetElement = composedPath[0];
41
+ if (targetElement instanceof HTMLElement) {
42
+ targetTagName = targetElement.tagName;
43
+ targetRole = targetElement.role;
44
+ }
45
+ } else if (target instanceof HTMLElement) {
46
+ targetTagName = target.tagName;
47
+ targetRole = target.role;
48
+ }
49
+ if (isReadonlyArray(enabledOnTags)) {
50
+ return Boolean(
51
+ targetTagName && enabledOnTags && enabledOnTags.some(
52
+ (tag) => typeof targetTagName === "string" && tag.toLowerCase() === targetTagName.toLowerCase() || tag === targetRole
53
+ )
54
+ );
80
55
  }
56
+ return Boolean(targetTagName && enabledOnTags && enabledOnTags);
81
57
  };
82
- var SelectionOverlay = class {
83
- element;
84
- visible = false;
85
- constructor(root) {
86
- this.element = createSelectionElement({
87
- borderRadius: "0px",
88
- height: 0,
89
- transform: "none",
90
- width: 0,
91
- x: -1e3,
92
- y: -1e3
93
- });
94
- root.appendChild(this.element);
95
- }
96
- hide() {
97
- this.visible = false;
98
- this.element.style.display = "none";
99
- this.element.style.pointerEvents = "none";
100
- }
101
- isVisible() {
102
- return this.visible;
103
- }
104
- show() {
105
- this.visible = true;
106
- this.element.style.display = "block";
107
- this.element.style.pointerEvents = "auto";
108
- }
109
- update(selection) {
110
- updateSelectionElement(this.element, selection);
111
- }
58
+ var isKeyboardEventTriggeredByInput = (event) => {
59
+ return isHotkeyEnabledOnTagName(event, FORM_TAGS_AND_ROLES);
112
60
  };
113
- var createSpinner = () => {
114
- const spinner = document.createElement("span");
115
- spinner.style.display = "inline-block";
116
- spinner.style.width = "8px";
117
- spinner.style.height = "8px";
118
- spinner.style.border = "1.5px solid #1e4ed8";
119
- spinner.style.borderTopColor = "transparent";
120
- spinner.style.borderRadius = "50%";
121
- spinner.style.marginRight = "4px";
122
- spinner.style.verticalAlign = "middle";
123
- spinner.animate(
124
- [
125
- { transform: "rotate(0deg)" },
126
- { transform: "rotate(360deg)" }
127
- ],
128
- {
129
- duration: 600,
130
- easing: "linear",
131
- iterations: Infinity
61
+ var trackHotkeys = () => {
62
+ const handleKeyDown = (event) => {
63
+ if (isKeyboardEventTriggeredByInput(event)) {
64
+ return;
132
65
  }
133
- );
134
- return spinner;
135
- };
136
- var createIndicator = (x, y) => {
137
- const indicator = document.createElement("div");
138
- indicator.style.position = "fixed";
139
- indicator.style.left = `${x}px`;
140
- indicator.style.top = `${y - 4}px`;
141
- indicator.style.transform = "translateY(-100%)";
142
- indicator.style.padding = "0px 4px";
143
- indicator.style.backgroundColor = "#dbeafe";
144
- indicator.style.color = "#1e4ed8";
145
- indicator.style.border = "1px solid #c7dbfb";
146
- indicator.style.borderRadius = "4px";
147
- indicator.style.fontSize = "11px";
148
- indicator.style.fontWeight = "500";
149
- indicator.style.fontFamily = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif";
150
- indicator.style.zIndex = "2147483647";
151
- indicator.style.pointerEvents = "none";
152
- indicator.style.opacity = "0";
153
- indicator.style.transition = "opacity 0.2s ease-in-out";
154
- indicator.style.display = "flex";
155
- indicator.style.alignItems = "center";
156
- return indicator;
157
- };
158
- var showCopyIndicator = (x, y) => {
159
- const indicator = createIndicator(x, y);
160
- const spinner = createSpinner();
161
- const text = document.createElement("span");
162
- text.textContent = "Copying\u2026";
163
- indicator.appendChild(spinner);
164
- indicator.appendChild(text);
165
- document.body.appendChild(indicator);
166
- requestAnimationFrame(() => {
167
- indicator.style.opacity = "1";
168
- });
66
+ if (event.code === undefined) {
67
+ return;
68
+ }
69
+ libStore.setState((state) => {
70
+ const newTimestamps = new Map(state.keyPressTimestamps);
71
+ if (!state.pressedKeys.has(event.key)) {
72
+ newTimestamps.set(event.key, Date.now());
73
+ }
74
+ return {
75
+ ...state,
76
+ keyPressTimestamps: newTimestamps,
77
+ pressedKeys: /* @__PURE__ */ new Set([event.key, ...state.pressedKeys])
78
+ };
79
+ });
80
+ };
81
+ const handleKeyUp = (event) => {
82
+ if (event.code === undefined) {
83
+ return;
84
+ }
85
+ libStore.setState((state) => {
86
+ const newTimestamps = new Map(state.keyPressTimestamps);
87
+ newTimestamps.delete(event.key);
88
+ return {
89
+ ...state,
90
+ keyPressTimestamps: newTimestamps,
91
+ pressedKeys: new Set(
92
+ [...state.pressedKeys].filter((key) => key !== event.key)
93
+ )
94
+ };
95
+ });
96
+ };
97
+ const handleBlur = () => {
98
+ libStore.setState((state) => ({
99
+ ...state,
100
+ keyPressTimestamps: /* @__PURE__ */ new Map(),
101
+ pressedKeys: /* @__PURE__ */ new Set()
102
+ }));
103
+ };
104
+ const handleContextmenu = () => {
105
+ libStore.setState((state) => ({
106
+ ...state,
107
+ keyPressTimestamps: /* @__PURE__ */ new Map(),
108
+ pressedKeys: /* @__PURE__ */ new Set()
109
+ }));
110
+ };
111
+ document.addEventListener("keydown", handleKeyDown);
112
+ document.addEventListener("keyup", handleKeyUp);
113
+ window.addEventListener("blur", handleBlur);
114
+ window.addEventListener("contextmenu", handleContextmenu);
169
115
  return () => {
170
- spinner.remove();
171
- text.textContent = "Copied!";
172
- setTimeout(() => {
173
- indicator.style.opacity = "0";
174
- setTimeout(() => {
175
- document.body.removeChild(indicator);
176
- }, 200);
177
- }, 1500);
116
+ document.removeEventListener("keydown", handleKeyDown);
117
+ document.removeEventListener("keyup", handleKeyUp);
118
+ window.removeEventListener("blur", handleBlur);
119
+ window.removeEventListener("contextmenu", handleContextmenu);
178
120
  };
179
121
  };
180
-
181
- // src/utils/copy-text.ts
182
- var copyText = async (text) => {
183
- if (navigator.clipboard && window.isSecureContext) {
184
- try {
185
- await navigator.clipboard.writeText(text);
186
- return true;
187
- } catch {
188
- }
189
- }
190
- const textareaEl = document.createElement("textarea");
191
- textareaEl.value = text;
192
- textareaEl.setAttribute("readonly", "");
193
- textareaEl.style.position = "fixed";
194
- textareaEl.style.top = "-9999px";
195
- textareaEl.style.opacity = "0";
196
- textareaEl.style.pointerEvents = "none";
197
- document.body.appendChild(textareaEl);
198
- textareaEl.select();
199
- textareaEl.setSelectionRange(0, textareaEl.value.length);
200
- let didCopy = false;
201
- try {
202
- didCopy = document.execCommand("copy");
203
- } catch {
204
- didCopy = false;
205
- } finally {
206
- document.body.removeChild(textareaEl);
122
+ var isKeyPressed = (key) => {
123
+ const { pressedKeys } = libStore.getState();
124
+ if (key.length === 1) {
125
+ return pressedKeys.has(key.toLowerCase()) || pressedKeys.has(key.toUpperCase());
207
126
  }
208
- return didCopy;
127
+ return pressedKeys.has(key);
128
+ };
129
+ var watchKeyHeldFor = (key, duration, onHeld) => {
130
+ let timeoutId = null;
131
+ let unsubscribe = null;
132
+ const cleanup = () => {
133
+ if (timeoutId !== null) {
134
+ clearTimeout(timeoutId);
135
+ timeoutId = null;
136
+ }
137
+ if (unsubscribe !== null) {
138
+ unsubscribe();
139
+ unsubscribe = null;
140
+ }
141
+ };
142
+ const checkSingleKeyPressed = (keyToCheck, pressedKeys) => {
143
+ if (keyToCheck.length === 1) {
144
+ return pressedKeys.has(keyToCheck.toLowerCase()) || pressedKeys.has(keyToCheck.toUpperCase());
145
+ }
146
+ return pressedKeys.has(keyToCheck);
147
+ };
148
+ const checkAllKeysPressed = (pressedKeys) => {
149
+ if (Array.isArray(key)) {
150
+ return key.every(
151
+ (keyFromCombo) => checkSingleKeyPressed(keyFromCombo, pressedKeys)
152
+ );
153
+ }
154
+ return checkSingleKeyPressed(key, pressedKeys);
155
+ };
156
+ const getKeyFromTimestamps = (keyToFind, timestamps) => {
157
+ if (keyToFind.length === 1) {
158
+ return timestamps.get(keyToFind.toLowerCase()) || timestamps.get(keyToFind.toUpperCase());
159
+ }
160
+ return timestamps.get(keyToFind);
161
+ };
162
+ const getEarliestPressTime = (timestamps) => {
163
+ const keysToInspect = Array.isArray(key) ? key : [key];
164
+ let earliest;
165
+ for (const keyFromCombo of keysToInspect) {
166
+ const timestamp = getKeyFromTimestamps(keyFromCombo, timestamps);
167
+ if (timestamp === undefined) {
168
+ return undefined;
169
+ }
170
+ if (earliest === undefined || timestamp < earliest) {
171
+ earliest = timestamp;
172
+ }
173
+ }
174
+ return earliest;
175
+ };
176
+ const scheduleCallback = () => {
177
+ const state = libStore.getState();
178
+ const { keyPressTimestamps, pressedKeys } = state;
179
+ if (!checkAllKeysPressed(pressedKeys)) {
180
+ if (timeoutId !== null) {
181
+ clearTimeout(timeoutId);
182
+ timeoutId = null;
183
+ }
184
+ return;
185
+ }
186
+ const earliestPressTime = getEarliestPressTime(keyPressTimestamps);
187
+ if (earliestPressTime === undefined) {
188
+ if (timeoutId !== null) {
189
+ clearTimeout(timeoutId);
190
+ timeoutId = null;
191
+ }
192
+ return;
193
+ }
194
+ const elapsed = Date.now() - earliestPressTime;
195
+ const remaining = duration - elapsed;
196
+ if (remaining <= 0) {
197
+ onHeld();
198
+ cleanup();
199
+ return;
200
+ }
201
+ if (timeoutId !== null) {
202
+ clearTimeout(timeoutId);
203
+ }
204
+ timeoutId = setTimeout(() => {
205
+ onHeld();
206
+ cleanup();
207
+ }, remaining);
208
+ };
209
+ unsubscribe = libStore.subscribe(
210
+ () => {
211
+ scheduleCallback();
212
+ },
213
+ (state) => state.pressedKeys
214
+ );
215
+ scheduleCallback();
216
+ return cleanup;
209
217
  };
210
218
 
211
219
  // node_modules/.pnpm/bippy@0.3.31_@types+react@19.2.2_react@19.2.0/node_modules/bippy/dist/src-R2iEnVC1.js
@@ -306,6 +314,7 @@ var ReactGrab = (function (exports) {
306
314
  return rdtHook;
307
315
  };
308
316
  var patchRDTHook = (onActive) => {
317
+ if (onActive) onActiveListeners.add(onActive);
309
318
  try {
310
319
  const rdtHook = globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__;
311
320
  if (!rdtHook) return;
@@ -386,6 +395,28 @@ var ReactGrab = (function (exports) {
386
395
  if (!unwrappedType) return null;
387
396
  return unwrappedType.displayName || unwrappedType.name || null;
388
397
  };
398
+ var instrument = (options) => {
399
+ return getRDTHook(() => {
400
+ const rdtHook = getRDTHook();
401
+ options.onActive?.();
402
+ rdtHook._instrumentationSource = options.name ?? BIPPY_INSTRUMENTATION_STRING;
403
+ const prevOnCommitFiberRoot = rdtHook.onCommitFiberRoot;
404
+ if (options.onCommitFiberRoot) rdtHook.onCommitFiberRoot = (rendererID, root, priority) => {
405
+ if (prevOnCommitFiberRoot) prevOnCommitFiberRoot(rendererID, root, priority);
406
+ options.onCommitFiberRoot?.(rendererID, root, priority);
407
+ };
408
+ const prevOnCommitFiberUnmount = rdtHook.onCommitFiberUnmount;
409
+ if (options.onCommitFiberUnmount) rdtHook.onCommitFiberUnmount = (rendererID, root) => {
410
+ if (prevOnCommitFiberUnmount) prevOnCommitFiberUnmount(rendererID, root);
411
+ options.onCommitFiberUnmount?.(rendererID, root);
412
+ };
413
+ const prevOnPostCommitFiberRoot = rdtHook.onPostCommitFiberRoot;
414
+ if (options.onPostCommitFiberRoot) rdtHook.onPostCommitFiberRoot = (rendererID, root) => {
415
+ if (prevOnPostCommitFiberRoot) prevOnPostCommitFiberRoot(rendererID, root);
416
+ options.onPostCommitFiberRoot?.(rendererID, root);
417
+ };
418
+ });
419
+ };
389
420
  var getFiberFromHostInstance = (hostInstance) => {
390
421
  const rdtHook = getRDTHook();
391
422
  for (const renderer of rdtHook.renderers.values()) try {
@@ -399,6 +430,7 @@ var ReactGrab = (function (exports) {
399
430
  }
400
431
  return null;
401
432
  };
433
+ var _fiberRoots = /* @__PURE__ */ new Set();
402
434
  safelyInstallRDTHook();
403
435
 
404
436
  // node_modules/.pnpm/bippy@0.3.31_@types+react@19.2.2_react@19.2.0/node_modules/bippy/dist/source-DWOhEbf2.js
@@ -2181,14 +2213,18 @@ ${error.stack}`;
2181
2213
  return matches;
2182
2214
  };
2183
2215
 
2184
- // src/utils/data.ts
2216
+ // src/instrumentation.ts
2217
+ var fiberRoots = _fiberRoots;
2218
+ instrument({
2219
+ onCommitFiberRoot(_, fiberRoot) {
2220
+ fiberRoots.add(fiberRoot);
2221
+ }
2222
+ });
2185
2223
  var getStack = async (element) => {
2186
2224
  const fiber = getFiberFromHostInstance(element);
2187
2225
  if (!fiber) return null;
2188
2226
  const stackTrace = getFiberStackTrace(fiber);
2189
- console.log(stackTrace);
2190
2227
  const rawOwnerStack = await getOwnerStack(stackTrace);
2191
- console.log(rawOwnerStack);
2192
2228
  const stack = rawOwnerStack.map((item) => ({
2193
2229
  componentName: item.name,
2194
2230
  fileName: item.source?.fileName
@@ -2376,7 +2412,9 @@ ${error.stack}`;
2376
2412
  lines.push(`${indent2} </${prevSibling.tagName.toLowerCase()}>`);
2377
2413
  } else if (targetIndex > 0) {
2378
2414
  const indent2 = " ".repeat(ancestors.length);
2379
- lines.push(`${indent2} ... (${targetIndex} element${targetIndex === 1 ? "" : "s"})`);
2415
+ lines.push(
2416
+ `${indent2} ... (${targetIndex} element${targetIndex === 1 ? "" : "s"})`
2417
+ );
2380
2418
  }
2381
2419
  }
2382
2420
  }
@@ -2386,7 +2424,9 @@ ${error.stack}`;
2386
2424
  const childrenCount = element.children.length;
2387
2425
  if (textContent && childrenCount === 0 && textContent.length < 40) {
2388
2426
  lines.push(
2389
- `${indent} ${getElementTag(element)}${textContent}${getClosingTag(element)}`
2427
+ `${indent} ${getElementTag(element)}${textContent}${getClosingTag(
2428
+ element
2429
+ )}`
2390
2430
  );
2391
2431
  } else {
2392
2432
  lines.push(indent + " " + getElementTag(element));
@@ -2423,24 +2463,299 @@ ${error.stack}`;
2423
2463
  return lines.join("\n");
2424
2464
  };
2425
2465
 
2426
- // src/utils/is-element-visible.ts
2427
- var isElementVisible = (element, computedStyle = null) => {
2428
- if (!computedStyle) {
2429
- computedStyle = window.getComputedStyle(element);
2466
+ // src/overlay.ts
2467
+ var VIEWPORT_MARGIN_PX = 8;
2468
+ var LABEL_OFFSET_PX = 6;
2469
+ var INDICATOR_CLAMP_PADDING_PX = 4;
2470
+ var INDICATOR_SUCCESS_VISIBLE_MS = 1500;
2471
+ var INDICATOR_FADE_MS = 200;
2472
+ var INDICATOR_TOTAL_HIDE_DELAY_MS = INDICATOR_SUCCESS_VISIBLE_MS + INDICATOR_FADE_MS;
2473
+ var lerp = (start, end, factor) => {
2474
+ return start + (end - start) * factor;
2475
+ };
2476
+ var SELECTION_LERP_FACTOR = 0.95;
2477
+ var createSelectionElement = ({
2478
+ borderRadius,
2479
+ height,
2480
+ transform,
2481
+ width,
2482
+ x,
2483
+ y
2484
+ }) => {
2485
+ const overlay = document.createElement("div");
2486
+ overlay.style.position = "fixed";
2487
+ overlay.style.top = `${y}px`;
2488
+ overlay.style.left = `${x}px`;
2489
+ overlay.style.width = `${width}px`;
2490
+ overlay.style.height = `${height}px`;
2491
+ overlay.style.borderRadius = borderRadius;
2492
+ overlay.style.transform = transform;
2493
+ overlay.style.pointerEvents = "none";
2494
+ overlay.style.border = "1px solid rgb(210, 57, 192)";
2495
+ overlay.style.backgroundColor = "rgba(210, 57, 192, 0.2)";
2496
+ overlay.style.zIndex = "2147483646";
2497
+ overlay.style.boxSizing = "border-box";
2498
+ overlay.style.display = "none";
2499
+ return overlay;
2500
+ };
2501
+ var updateSelectionElement = (element, { borderRadius, height, transform, width, x, y }) => {
2502
+ const currentTop = parseFloat(element.style.top) || 0;
2503
+ const currentLeft = parseFloat(element.style.left) || 0;
2504
+ const currentWidth = parseFloat(element.style.width) || 0;
2505
+ const currentHeight = parseFloat(element.style.height) || 0;
2506
+ const topValue = `${lerp(currentTop, y, SELECTION_LERP_FACTOR)}px`;
2507
+ const leftValue = `${lerp(currentLeft, x, SELECTION_LERP_FACTOR)}px`;
2508
+ const widthValue = `${lerp(currentWidth, width, SELECTION_LERP_FACTOR)}px`;
2509
+ const heightValue = `${lerp(currentHeight, height, SELECTION_LERP_FACTOR)}px`;
2510
+ if (element.style.top !== topValue) {
2511
+ element.style.top = topValue;
2512
+ }
2513
+ if (element.style.left !== leftValue) {
2514
+ element.style.left = leftValue;
2515
+ }
2516
+ if (element.style.width !== widthValue) {
2517
+ element.style.width = widthValue;
2518
+ }
2519
+ if (element.style.height !== heightValue) {
2520
+ element.style.height = heightValue;
2521
+ }
2522
+ if (element.style.borderRadius !== borderRadius) {
2523
+ element.style.borderRadius = borderRadius;
2430
2524
  }
2431
- return computedStyle.display !== "none" && computedStyle.visibility !== "hidden";
2525
+ if (element.style.transform !== transform) {
2526
+ element.style.transform = transform;
2527
+ }
2528
+ };
2529
+ var createSelectionOverlay = (root) => {
2530
+ const element = createSelectionElement({
2531
+ borderRadius: "0px",
2532
+ height: 0,
2533
+ transform: "none",
2534
+ width: 0,
2535
+ x: -1e3,
2536
+ y: -1e3
2537
+ });
2538
+ root.appendChild(element);
2539
+ let visible = false;
2540
+ return {
2541
+ hide: () => {
2542
+ visible = false;
2543
+ element.style.display = "none";
2544
+ element.style.pointerEvents = "none";
2545
+ },
2546
+ isVisible: () => visible,
2547
+ show: () => {
2548
+ visible = true;
2549
+ element.style.display = "block";
2550
+ element.style.pointerEvents = "auto";
2551
+ },
2552
+ update: (selection) => {
2553
+ updateSelectionElement(element, selection);
2554
+ }
2555
+ };
2556
+ };
2557
+ var createSpinner = () => {
2558
+ const spinner = document.createElement("span");
2559
+ spinner.style.display = "inline-block";
2560
+ spinner.style.width = "8px";
2561
+ spinner.style.height = "8px";
2562
+ spinner.style.border = "1.5px solid rgb(210, 57, 192)";
2563
+ spinner.style.borderTopColor = "transparent";
2564
+ spinner.style.borderRadius = "50%";
2565
+ spinner.style.marginRight = "4px";
2566
+ spinner.style.verticalAlign = "middle";
2567
+ spinner.animate(
2568
+ [{ transform: "rotate(0deg)" }, { transform: "rotate(360deg)" }],
2569
+ {
2570
+ duration: 600,
2571
+ easing: "linear",
2572
+ iterations: Infinity
2573
+ }
2574
+ );
2575
+ return spinner;
2576
+ };
2577
+ var activeIndicator = null;
2578
+ var createIndicator = () => {
2579
+ const indicator = document.createElement("div");
2580
+ indicator.style.position = "fixed";
2581
+ indicator.style.top = "calc(8px + env(safe-area-inset-top))";
2582
+ indicator.style.padding = "2px 6px";
2583
+ indicator.style.backgroundColor = "#fde7f7";
2584
+ indicator.style.color = "#b21c8e";
2585
+ indicator.style.border = "1px solid #f7c5ec";
2586
+ indicator.style.borderRadius = "4px";
2587
+ indicator.style.fontSize = "11px";
2588
+ indicator.style.fontWeight = "500";
2589
+ indicator.style.fontFamily = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif";
2590
+ indicator.style.zIndex = "2147483647";
2591
+ indicator.style.pointerEvents = "none";
2592
+ indicator.style.opacity = "0";
2593
+ indicator.style.transition = "opacity 0.2s ease-in-out";
2594
+ indicator.style.display = "flex";
2595
+ indicator.style.alignItems = "center";
2596
+ indicator.style.maxWidth = "calc(100vw - (16px + env(safe-area-inset-left) + env(safe-area-inset-right)))";
2597
+ indicator.style.overflow = "hidden";
2598
+ indicator.style.textOverflow = "ellipsis";
2599
+ indicator.style.whiteSpace = "nowrap";
2600
+ return indicator;
2601
+ };
2602
+ var showLabel = (selectionLeftPx, selectionTopPx, tagName) => {
2603
+ let indicator = activeIndicator;
2604
+ let isNewIndicator = false;
2605
+ if (!indicator) {
2606
+ indicator = createIndicator();
2607
+ document.body.appendChild(indicator);
2608
+ activeIndicator = indicator;
2609
+ isNewIndicator = true;
2610
+ isProcessing = false;
2611
+ }
2612
+ if (!isProcessing) {
2613
+ const labelText = indicator.querySelector("span");
2614
+ if (labelText) {
2615
+ const tagNameMonospace = document.createElement("span");
2616
+ tagNameMonospace.textContent = tagName ? `<${tagName}>` : "<element>";
2617
+ tagNameMonospace.style.fontFamily = "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace";
2618
+ tagNameMonospace.style.fontVariantNumeric = "tabular-nums";
2619
+ labelText.replaceChildren(tagNameMonospace);
2620
+ } else {
2621
+ const newLabelText = document.createElement("span");
2622
+ const tagNameMonospace = document.createElement("span");
2623
+ tagNameMonospace.textContent = tagName ? `<${tagName}>` : "<element>";
2624
+ tagNameMonospace.style.fontFamily = "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace";
2625
+ tagNameMonospace.style.fontVariantNumeric = "tabular-nums";
2626
+ newLabelText.appendChild(tagNameMonospace);
2627
+ indicator.appendChild(newLabelText);
2628
+ }
2629
+ }
2630
+ const indicatorRect = indicator.getBoundingClientRect();
2631
+ const viewportWidthPx = window.innerWidth;
2632
+ const viewportHeightPx = window.innerHeight;
2633
+ let indicatorLeftPx = Math.round(selectionLeftPx);
2634
+ let indicatorTopPx = Math.round(selectionTopPx) - indicatorRect.height - LABEL_OFFSET_PX;
2635
+ const CLAMPED_PADDING = INDICATOR_CLAMP_PADDING_PX;
2636
+ const minLeft = VIEWPORT_MARGIN_PX;
2637
+ const minTop = VIEWPORT_MARGIN_PX;
2638
+ const maxLeft = viewportWidthPx - indicatorRect.width - VIEWPORT_MARGIN_PX;
2639
+ const maxTop = viewportHeightPx - indicatorRect.height - VIEWPORT_MARGIN_PX;
2640
+ const willClampLeft = indicatorLeftPx < minLeft;
2641
+ const willClampTop = indicatorTopPx < minTop;
2642
+ const isClamped = willClampLeft || willClampTop;
2643
+ indicatorLeftPx = Math.max(minLeft, Math.min(indicatorLeftPx, maxLeft));
2644
+ indicatorTopPx = Math.max(minTop, Math.min(indicatorTopPx, maxTop));
2645
+ if (isClamped) {
2646
+ indicatorLeftPx += CLAMPED_PADDING;
2647
+ indicatorTopPx += CLAMPED_PADDING;
2648
+ }
2649
+ indicator.style.left = `${indicatorLeftPx}px`;
2650
+ indicator.style.top = `${indicatorTopPx}px`;
2651
+ indicator.style.right = "auto";
2652
+ if (isNewIndicator) {
2653
+ requestAnimationFrame(() => {
2654
+ indicator.style.opacity = "1";
2655
+ });
2656
+ } else if (indicator.style.opacity !== "1") {
2657
+ indicator.style.opacity = "1";
2658
+ }
2659
+ };
2660
+ var isProcessing = false;
2661
+ var updateLabelToProcessing = () => {
2662
+ if (!activeIndicator || isProcessing) return () => {
2663
+ };
2664
+ isProcessing = true;
2665
+ const indicator = activeIndicator;
2666
+ indicator.innerHTML = "";
2667
+ const loadingSpinner = createSpinner();
2668
+ const labelText = document.createElement("span");
2669
+ labelText.textContent = "Grabbing\u2026";
2670
+ indicator.appendChild(loadingSpinner);
2671
+ indicator.appendChild(labelText);
2672
+ return (tagName) => {
2673
+ if (!activeIndicator) {
2674
+ isProcessing = false;
2675
+ return;
2676
+ }
2677
+ indicator.textContent = "";
2678
+ const checkmarkIcon = document.createElement("span");
2679
+ checkmarkIcon.textContent = "\u2713";
2680
+ checkmarkIcon.style.display = "inline-block";
2681
+ checkmarkIcon.style.marginRight = "4px";
2682
+ checkmarkIcon.style.fontWeight = "600";
2683
+ const newLabelText = document.createElement("span");
2684
+ const tagNameMonospace = document.createElement("span");
2685
+ tagNameMonospace.textContent = tagName ? `<${tagName}>` : "<element>";
2686
+ tagNameMonospace.style.fontFamily = "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace";
2687
+ tagNameMonospace.style.fontVariantNumeric = "tabular-nums";
2688
+ newLabelText.appendChild(document.createTextNode("Grabbed "));
2689
+ newLabelText.appendChild(tagNameMonospace);
2690
+ indicator.appendChild(checkmarkIcon);
2691
+ indicator.appendChild(newLabelText);
2692
+ setTimeout(() => {
2693
+ indicator.style.opacity = "0";
2694
+ setTimeout(() => {
2695
+ indicator.remove();
2696
+ if (activeIndicator === indicator) {
2697
+ activeIndicator = null;
2698
+ }
2699
+ isProcessing = false;
2700
+ }, INDICATOR_FADE_MS);
2701
+ }, INDICATOR_SUCCESS_VISIBLE_MS);
2702
+ };
2703
+ };
2704
+ var hideLabel = () => {
2705
+ if (activeIndicator) {
2706
+ activeIndicator.remove();
2707
+ activeIndicator = null;
2708
+ }
2709
+ isProcessing = false;
2710
+ };
2711
+
2712
+ // src/utils/copy-text.ts
2713
+ var IS_NAVIGATOR_CLIPBOARD_AVAILABLE = typeof window !== "undefined" && window.navigator.clipboard && window.isSecureContext;
2714
+ var copyTextToClipboard = async (text) => {
2715
+ if (IS_NAVIGATOR_CLIPBOARD_AVAILABLE) {
2716
+ try {
2717
+ await navigator.clipboard.writeText(text);
2718
+ return true;
2719
+ } catch {
2720
+ }
2721
+ }
2722
+ const textareaElement = document.createElement("textarea");
2723
+ textareaElement.value = text;
2724
+ textareaElement.setAttribute("readonly", "");
2725
+ textareaElement.style.position = "fixed";
2726
+ textareaElement.style.top = "-9999px";
2727
+ textareaElement.style.opacity = "0";
2728
+ textareaElement.style.pointerEvents = "none";
2729
+ const doc = document.body || document.documentElement;
2730
+ doc.appendChild(textareaElement);
2731
+ textareaElement.select();
2732
+ textareaElement.setSelectionRange(0, textareaElement.value.length);
2733
+ let didCopyToClipboard = false;
2734
+ try {
2735
+ didCopyToClipboard = document.execCommand("copy");
2736
+ } catch {
2737
+ didCopyToClipboard = false;
2738
+ } finally {
2739
+ doc.removeChild(textareaElement);
2740
+ }
2741
+ return didCopyToClipboard;
2742
+ };
2743
+
2744
+ // src/utils/is-element-visible.ts
2745
+ var isElementVisible = (element, computedStyle = window.getComputedStyle(element)) => {
2746
+ return computedStyle.display !== "none" && computedStyle.visibility !== "hidden" && computedStyle.opacity !== "0";
2432
2747
  };
2433
2748
 
2434
2749
  // src/utils/mount-root.ts
2435
2750
  var ATTRIBUTE_NAME = "data-react-grab";
2436
2751
  var mountRoot = () => {
2437
- const existingHost = document.querySelector(`[${ATTRIBUTE_NAME}]`);
2438
- if (existingHost) {
2439
- const existingRoot = existingHost.shadowRoot?.querySelector(
2752
+ const mountedHost = document.querySelector(`[${ATTRIBUTE_NAME}]`);
2753
+ if (mountedHost) {
2754
+ const mountedRoot = mountedHost.shadowRoot?.querySelector(
2440
2755
  `[${ATTRIBUTE_NAME}]`
2441
2756
  );
2442
- if (existingRoot instanceof HTMLDivElement && existingHost.shadowRoot) {
2443
- return existingRoot;
2757
+ if (mountedRoot instanceof HTMLDivElement && mountedHost.shadowRoot) {
2758
+ return mountedRoot;
2444
2759
  }
2445
2760
  }
2446
2761
  const host = document.createElement("div");
@@ -2451,7 +2766,6 @@ ${error.stack}`;
2451
2766
  host.style.left = "0";
2452
2767
  const shadowRoot = host.attachShadow({ mode: "open" });
2453
2768
  const root = document.createElement("div");
2454
- root.style.transition = "opacity 0.1s ease-out";
2455
2769
  root.setAttribute(ATTRIBUTE_NAME, "true");
2456
2770
  shadowRoot.appendChild(root);
2457
2771
  const doc = document.body ?? document.documentElement;
@@ -2472,6 +2786,77 @@ ${error.stack}`;
2472
2786
  return setTimeout(callback, 0);
2473
2787
  };
2474
2788
 
2789
+ // src/utils/store.ts
2790
+ var createStore = (initializer) => {
2791
+ const subscriberMap = /* @__PURE__ */ new Map();
2792
+ let currentListenerIndex = 0;
2793
+ let currentState;
2794
+ const setState = (maybeStateOrReducer) => {
2795
+ const prevState = currentState;
2796
+ const resolvedState = typeof maybeStateOrReducer === "function" ? maybeStateOrReducer(prevState) : maybeStateOrReducer;
2797
+ const nextState = {
2798
+ ...prevState,
2799
+ ...resolvedState
2800
+ };
2801
+ currentState = nextState;
2802
+ for (const entry of subscriberMap.values()) {
2803
+ if (entry.type === "selected" /* Selected */) {
2804
+ const nextSelectedValue = entry.selector(nextState);
2805
+ const prevSelectedValue = entry.prevSelectedValue;
2806
+ if (!Object.is(prevSelectedValue, nextSelectedValue)) {
2807
+ entry.prevSelectedValue = nextSelectedValue;
2808
+ entry.listener(nextSelectedValue, prevSelectedValue);
2809
+ }
2810
+ } else {
2811
+ entry.listener(nextState, prevState);
2812
+ }
2813
+ }
2814
+ return currentState;
2815
+ };
2816
+ const getState = () => {
2817
+ return currentState;
2818
+ };
2819
+ const initialState = initializer(setState, getState);
2820
+ currentState = initialState;
2821
+ const subscribeWithSelector = (listener, selector) => {
2822
+ const index = String(currentListenerIndex++);
2823
+ const wrappedListener = (value, prevValue) => listener(value, prevValue);
2824
+ const entry = {
2825
+ listener: wrappedListener,
2826
+ prevSelectedValue: selector(currentState),
2827
+ selector,
2828
+ type: "selected" /* Selected */
2829
+ };
2830
+ subscriberMap.set(index, entry);
2831
+ return () => {
2832
+ subscriberMap.delete(index);
2833
+ };
2834
+ };
2835
+ const subscribeToFullState = (listener) => {
2836
+ const index = String(currentListenerIndex++);
2837
+ const entry = {
2838
+ listener,
2839
+ type: "full" /* Full */
2840
+ };
2841
+ subscriberMap.set(index, entry);
2842
+ return () => {
2843
+ subscriberMap.delete(index);
2844
+ };
2845
+ };
2846
+ function subscribe(subscriber, selector) {
2847
+ return selector ? subscribeWithSelector(subscriber, selector) : subscribeToFullState(subscriber);
2848
+ }
2849
+ const store = {
2850
+ getInitialState() {
2851
+ return initialState;
2852
+ },
2853
+ getState,
2854
+ setState,
2855
+ subscribe
2856
+ };
2857
+ return store;
2858
+ };
2859
+
2475
2860
  // src/utils/throttle.ts
2476
2861
  var throttle = (fn, delay) => {
2477
2862
  let timeout = null;
@@ -2494,167 +2879,288 @@ ${error.stack}`;
2494
2879
  };
2495
2880
 
2496
2881
  // src/index.ts
2497
- var TICK_INTERVAL = 50;
2498
2882
  var THROTTLE_DELAY = 16;
2499
- var init = () => {
2883
+ var libStore = createStore(() => ({
2884
+ keyPressTimestamps: /* @__PURE__ */ new Map(),
2885
+ mouseX: -1e3,
2886
+ mouseY: -1e3,
2887
+ overlayMode: "hidden",
2888
+ pressedKeys: /* @__PURE__ */ new Set()
2889
+ }));
2890
+ var init = (options = {}) => {
2891
+ if (options.enabled === false) {
2892
+ return;
2893
+ }
2894
+ const resolvedOptions = {
2895
+ enabled: true,
2896
+ hotkey: ["Meta", "C"],
2897
+ keyHoldDuration: 500,
2898
+ ...options
2899
+ };
2500
2900
  const root = mountRoot();
2501
- let mouseX = 0;
2502
- let mouseY = 0;
2503
- let shiftKeyTimeout = null;
2504
- let currentHoveredElement = null;
2505
- const isFocusedInInput = () => {
2506
- const active = document.activeElement;
2507
- return active?.tagName === "INPUT" || active?.tagName === "TEXTAREA";
2508
- };
2509
- const selectionOverlay = new SelectionOverlay(root);
2510
- const render = () => {
2511
- if (!selectionOverlay.isVisible()) return;
2512
- const element = document.elementsFromPoint(mouseX, mouseY).filter((element2) => !element2.hasAttribute(ATTRIBUTE_NAME))[0];
2513
- if (!element) {
2514
- currentHoveredElement = null;
2901
+ const selectionOverlay = createSelectionOverlay(root);
2902
+ let hoveredElement = null;
2903
+ let isCopying = false;
2904
+ const checkIsActivationHotkeyPressed = () => {
2905
+ if (Array.isArray(resolvedOptions.hotkey)) {
2906
+ for (const key of resolvedOptions.hotkey) {
2907
+ if (!isKeyPressed(key)) {
2908
+ return false;
2909
+ }
2910
+ }
2911
+ return true;
2912
+ }
2913
+ return isKeyPressed(resolvedOptions.hotkey);
2914
+ };
2915
+ let cleanupActivationHotkeyWatcher = null;
2916
+ const handleKeyStateChange = (pressedKeys) => {
2917
+ const { overlayMode } = libStore.getState();
2918
+ if (pressedKeys.has("Escape") || pressedKeys.has("Esc")) {
2919
+ libStore.setState((state) => {
2920
+ const nextPressedKeys = new Set(state.pressedKeys);
2921
+ nextPressedKeys.delete("Escape");
2922
+ nextPressedKeys.delete("Esc");
2923
+ const nextTimestamps = new Map(state.keyPressTimestamps);
2924
+ nextTimestamps.delete("Escape");
2925
+ nextTimestamps.delete("Esc");
2926
+ const activationKeys = Array.isArray(resolvedOptions.hotkey) ? resolvedOptions.hotkey : [resolvedOptions.hotkey];
2927
+ for (const activationKey of activationKeys) {
2928
+ if (activationKey.length === 1) {
2929
+ nextPressedKeys.delete(activationKey.toLowerCase());
2930
+ nextPressedKeys.delete(activationKey.toUpperCase());
2931
+ nextTimestamps.delete(activationKey.toLowerCase());
2932
+ nextTimestamps.delete(activationKey.toUpperCase());
2933
+ } else {
2934
+ nextPressedKeys.delete(activationKey);
2935
+ nextTimestamps.delete(activationKey);
2936
+ }
2937
+ }
2938
+ return {
2939
+ ...state,
2940
+ keyPressTimestamps: nextTimestamps,
2941
+ overlayMode: "hidden",
2942
+ pressedKeys: nextPressedKeys
2943
+ };
2944
+ });
2945
+ if (cleanupActivationHotkeyWatcher) {
2946
+ cleanupActivationHotkeyWatcher();
2947
+ cleanupActivationHotkeyWatcher = null;
2948
+ }
2515
2949
  return;
2516
2950
  }
2517
- const computedStyle = window.getComputedStyle(element);
2518
- if (!isElementVisible(element, computedStyle)) return;
2519
- const rect = element.getBoundingClientRect();
2520
- currentHoveredElement = element;
2521
- selectionOverlay.update({
2522
- borderRadius: computedStyle.borderRadius,
2523
- height: rect.height,
2524
- transform: computedStyle.transform,
2525
- width: rect.width,
2526
- x: rect.left,
2527
- y: rect.top
2528
- });
2951
+ const isActivationHotkeyPressed = checkIsActivationHotkeyPressed();
2952
+ if (!isActivationHotkeyPressed) {
2953
+ if (cleanupActivationHotkeyWatcher) {
2954
+ cleanupActivationHotkeyWatcher();
2955
+ cleanupActivationHotkeyWatcher = null;
2956
+ }
2957
+ if (overlayMode !== "hidden") {
2958
+ libStore.setState((state) => ({
2959
+ ...state,
2960
+ overlayMode: "hidden"
2961
+ }));
2962
+ }
2963
+ return;
2964
+ }
2965
+ if (overlayMode === "hidden" && !cleanupActivationHotkeyWatcher) {
2966
+ cleanupActivationHotkeyWatcher = watchKeyHeldFor(
2967
+ resolvedOptions.hotkey,
2968
+ resolvedOptions.keyHoldDuration,
2969
+ () => {
2970
+ libStore.setState((state) => ({
2971
+ ...state,
2972
+ overlayMode: "visible"
2973
+ }));
2974
+ cleanupActivationHotkeyWatcher = null;
2975
+ }
2976
+ );
2977
+ }
2529
2978
  };
2530
- const throttledRender = throttle(() => {
2531
- scheduleRunWhenIdle(() => {
2532
- render();
2533
- });
2979
+ const cleanupKeyStateChangeSubscription = libStore.subscribe(
2980
+ handleKeyStateChange,
2981
+ (state) => state.pressedKeys
2982
+ );
2983
+ const handleMouseMove = throttle((event) => {
2984
+ libStore.setState((state) => ({
2985
+ ...state,
2986
+ mouseX: event.clientX,
2987
+ mouseY: event.clientY
2988
+ }));
2534
2989
  }, THROTTLE_DELAY);
2535
- window.addEventListener("mousemove", (event) => {
2536
- mouseX = event.clientX;
2537
- mouseY = event.clientY;
2538
- throttledRender();
2539
- });
2540
- window.addEventListener("resize", () => {
2541
- throttledRender();
2542
- });
2543
- window.addEventListener("scroll", () => {
2544
- throttledRender();
2545
- });
2546
- let timeout = null;
2547
- const nextTick = () => {
2548
- timeout = window.setTimeout(() => {
2549
- throttledRender();
2550
- nextTick();
2551
- }, TICK_INTERVAL);
2552
- };
2553
- nextTick();
2554
- window.addEventListener("keydown", (event) => {
2555
- if (event.key === "Meta") {
2556
- if (isFocusedInInput()) return;
2557
- if (shiftKeyTimeout !== null) {
2558
- window.clearTimeout(shiftKeyTimeout);
2990
+ const handleMouseDown = (event) => {
2991
+ if (event.button !== 0) {
2992
+ return;
2993
+ }
2994
+ const { overlayMode } = libStore.getState();
2995
+ if (overlayMode === "hidden") {
2996
+ return;
2997
+ }
2998
+ event.preventDefault();
2999
+ event.stopPropagation();
3000
+ event.stopImmediatePropagation();
3001
+ libStore.setState((state) => ({
3002
+ ...state,
3003
+ overlayMode: "copying"
3004
+ }));
3005
+ };
3006
+ window.addEventListener("mousemove", handleMouseMove);
3007
+ window.addEventListener("mousedown", handleMouseDown);
3008
+ const cleanupTrackHotkeys = trackHotkeys();
3009
+ const getElementAtPosition = (x, y) => {
3010
+ const elements = document.elementsFromPoint(x, y);
3011
+ for (const element of elements) {
3012
+ if (element.closest(`[${ATTRIBUTE_NAME}]`)) {
3013
+ continue;
2559
3014
  }
2560
- shiftKeyTimeout = window.setTimeout(() => {
2561
- selectionOverlay.show();
2562
- throttledRender();
2563
- }, 200);
3015
+ const computedStyle = window.getComputedStyle(element);
3016
+ if (!isElementVisible(element, computedStyle)) {
3017
+ continue;
3018
+ }
3019
+ return element;
2564
3020
  }
2565
- if (event.key === "Escape") {
3021
+ return null;
3022
+ };
3023
+ const handleCopy = async (element) => {
3024
+ const cleanupIndicator = updateLabelToProcessing();
3025
+ try {
3026
+ const stack = await getStack(element);
3027
+ const htmlSnippet = getHTMLSnippet(element);
3028
+ let text = htmlSnippet;
3029
+ if (stack) {
3030
+ const filteredStack = filterStack(stack);
3031
+ const serializedStack = serializeStack(filteredStack);
3032
+ text = `${htmlSnippet}
3033
+
3034
+ Component owner stack:
3035
+ ${serializedStack}`;
3036
+ }
3037
+ await copyTextToClipboard(
3038
+ `
3039
+
3040
+ <referenced_element>
3041
+ ${text}
3042
+ </referenced_element>`
3043
+ );
3044
+ const tagName = (element.tagName || "").toLowerCase();
3045
+ cleanupIndicator(tagName);
3046
+ } catch {
3047
+ cleanupIndicator();
3048
+ }
3049
+ };
3050
+ const handleRender = throttle((state) => {
3051
+ const { mouseX, mouseY, overlayMode } = state;
3052
+ if (overlayMode === "hidden") {
2566
3053
  if (selectionOverlay.isVisible()) {
2567
3054
  selectionOverlay.hide();
2568
- if (shiftKeyTimeout !== null) {
2569
- window.clearTimeout(shiftKeyTimeout);
2570
- shiftKeyTimeout = null;
3055
+ if (!isCopying) {
3056
+ hideLabel();
2571
3057
  }
3058
+ hoveredElement = null;
2572
3059
  }
3060
+ return;
2573
3061
  }
2574
- if ((event.key === "c" || event.key === "C") && event.metaKey) {
2575
- if (selectionOverlay.isVisible() && currentHoveredElement) {
2576
- event.preventDefault();
2577
- event.stopPropagation();
2578
- event.stopImmediatePropagation();
2579
- const element = currentHoveredElement;
2580
- const rect = element.getBoundingClientRect();
2581
- const showSuccess = showCopyIndicator(rect.left, rect.top);
2582
- getStack(element).then((stack) => {
2583
- const htmlSnippet = getHTMLSnippet(element);
2584
- let output = htmlSnippet;
2585
- if (stack) {
2586
- const filteredStack = filterStack(stack);
2587
- const serializedStack = serializeStack(filteredStack);
2588
- output = `${serializedStack}
2589
-
2590
- ${htmlSnippet}`;
2591
- }
2592
- return copyText(output);
2593
- }).then((success) => {
2594
- if (success) {
2595
- showSuccess();
2596
- }
2597
- }).catch(() => {
2598
- });
3062
+ if (overlayMode === "copying" && hoveredElement) {
3063
+ const computedStyle2 = window.getComputedStyle(hoveredElement);
3064
+ const rect2 = hoveredElement.getBoundingClientRect();
3065
+ selectionOverlay.update({
3066
+ borderRadius: computedStyle2.borderRadius || "0px",
3067
+ height: rect2.height,
3068
+ transform: computedStyle2.transform || "none",
3069
+ width: rect2.width,
3070
+ x: rect2.left,
3071
+ y: rect2.top
3072
+ });
3073
+ if (!selectionOverlay.isVisible()) {
3074
+ selectionOverlay.show();
2599
3075
  }
2600
- }
2601
- });
2602
- window.addEventListener("mousedown", (event) => {
2603
- if (event.button !== 0) return;
2604
- if (selectionOverlay.isVisible()) {
2605
- event.preventDefault();
2606
- event.stopPropagation();
2607
- event.stopImmediatePropagation();
2608
- if (currentHoveredElement) {
2609
- const element = currentHoveredElement;
2610
- const rect = element.getBoundingClientRect();
2611
- const showSuccess = showCopyIndicator(rect.left, rect.top);
2612
- getStack(element).then((stack) => {
2613
- const htmlSnippet = getHTMLSnippet(element);
2614
- let output = htmlSnippet;
2615
- if (stack) {
2616
- const filteredStack = filterStack(stack);
2617
- const serializedStack = serializeStack(filteredStack);
2618
- output = `## Referenced element:
2619
- ${htmlSnippet}
2620
- Stack trace:
2621
- ${serializedStack}
2622
-
2623
- Path: ${window.location.pathname}`;
2624
- }
2625
- return copyText(output);
2626
- }).then((success) => {
2627
- if (success) {
2628
- showSuccess();
2629
- }
2630
- }).catch(() => {
3076
+ if (!isCopying) {
3077
+ isCopying = true;
3078
+ void handleCopy(hoveredElement).finally(() => {
3079
+ libStore.setState((state2) => ({
3080
+ ...state2,
3081
+ overlayMode: "hidden"
3082
+ }));
3083
+ selectionOverlay.hide();
3084
+ window.setTimeout(() => {
3085
+ isCopying = false;
3086
+ }, INDICATOR_TOTAL_HIDE_DELAY_MS);
2631
3087
  });
2632
3088
  }
3089
+ return;
2633
3090
  }
2634
- });
2635
- window.addEventListener("keyup", (event) => {
2636
- if (event.key === "Meta") {
2637
- if (shiftKeyTimeout !== null) {
2638
- window.clearTimeout(shiftKeyTimeout);
2639
- shiftKeyTimeout = null;
3091
+ const element = getElementAtPosition(mouseX, mouseY);
3092
+ if (!element) {
3093
+ if (selectionOverlay.isVisible()) {
3094
+ selectionOverlay.hide();
3095
+ if (!isCopying) {
3096
+ hideLabel();
3097
+ }
2640
3098
  }
2641
- selectionOverlay.hide();
3099
+ hoveredElement = null;
3100
+ return;
3101
+ }
3102
+ const tagName = (element.tagName || "").toLowerCase();
3103
+ hoveredElement = element;
3104
+ const rect = element.getBoundingClientRect();
3105
+ const computedStyle = window.getComputedStyle(element);
3106
+ const borderRadius = computedStyle.borderRadius || "0px";
3107
+ const transform = computedStyle.transform || "none";
3108
+ selectionOverlay.update({
3109
+ borderRadius,
3110
+ height: rect.height,
3111
+ transform,
3112
+ width: rect.width,
3113
+ x: rect.left,
3114
+ y: rect.top
3115
+ });
3116
+ if (!selectionOverlay.isVisible()) {
3117
+ selectionOverlay.show();
2642
3118
  }
3119
+ showLabel(rect.left, rect.top, tagName);
3120
+ }, 10);
3121
+ const cleanupRenderSubscription = libStore.subscribe((state) => {
3122
+ scheduleRunWhenIdle(() => {
3123
+ handleRender(state);
3124
+ });
2643
3125
  });
3126
+ let timeout = null;
3127
+ const render = () => {
3128
+ timeout = window.setTimeout(() => {
3129
+ scheduleRunWhenIdle(() => {
3130
+ handleRender(libStore.getState());
3131
+ render();
3132
+ });
3133
+ }, 100);
3134
+ };
3135
+ render();
2644
3136
  return () => {
3137
+ window.removeEventListener("mousemove", handleMouseMove);
3138
+ window.removeEventListener("mousedown", handleMouseDown);
3139
+ cleanupTrackHotkeys();
3140
+ cleanupRenderSubscription();
3141
+ cleanupKeyStateChangeSubscription();
2645
3142
  if (timeout) {
2646
3143
  window.clearTimeout(timeout);
2647
- timeout = null;
2648
3144
  }
2649
- if (shiftKeyTimeout !== null) {
2650
- window.clearTimeout(shiftKeyTimeout);
2651
- shiftKeyTimeout = null;
3145
+ if (cleanupActivationHotkeyWatcher) {
3146
+ cleanupActivationHotkeyWatcher();
2652
3147
  }
2653
- root.remove();
2654
- throttledRender.cancel();
2655
3148
  };
2656
3149
  };
2657
- init();
3150
+ if (typeof window !== "undefined" && typeof document !== "undefined") {
3151
+ const currentScript = document.currentScript;
3152
+ let options = {};
3153
+ if (currentScript) {
3154
+ const maybeOptions = currentScript.getAttribute("data-options");
3155
+ if (maybeOptions) {
3156
+ try {
3157
+ options = JSON.parse(maybeOptions);
3158
+ } catch {
3159
+ }
3160
+ }
3161
+ }
3162
+ init(options);
3163
+ }
2658
3164
  /*! Bundled license information:
2659
3165
 
2660
3166
  bippy/dist/src-R2iEnVC1.js:
@@ -2699,6 +3205,7 @@ Path: ${window.location.pathname}`;
2699
3205
  */
2700
3206
 
2701
3207
  exports.init = init;
3208
+ exports.libStore = libStore;
2702
3209
 
2703
3210
  return exports;
2704
3211
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-grab",
3
- "version": "0.0.11",
3
+ "version": "0.0.13",
4
4
  "description": "",
5
5
  "keywords": [],
6
6
  "homepage": "https://github.com/aidenybai/react-grab#readme",